betajs/betajs-media-components

View on GitHub
src/dynamics/video_recorder/recorder/states.js

Summary

Maintainability
F
3 wks
Test Coverage
Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.State", [
    "base:States.State",
    "base:Events.ListenMixin",
    "base:Objs"
], function(State, ListenMixin, Objs, scoped) {
    return State.extend({
        scoped: scoped
    }, [ListenMixin, {

        dynamics: [],

        _start: function() {
            this.dyn = this.host.dynamic;
            Objs.iter(Objs.extend({
                "message": false,
                "chooser": false,
                "topmessage": false,
                "controlbar": false,
                "loader": false,
                "imagegallery": false,
                "helperframe": false
            }, Objs.objectify(this.dynamics)), function(value, key) {
                this.dyn.set(key + "_active", value);
            }, this);
            this.dyn.set("placeholderStyle", "");
            this.dyn.set("playertopmessage", "");
            this.dyn.set("message_links", null);
            this.dyn._accessing_camera = false;
            this._started();
        },

        _started: function() {},

        record: function() {
            this.dyn.set("autorecord", true);
        },

        stop: function() {
            this.dyn.scopes.player.execute('stop');
        },

        play: function() {
            this.dyn.scopes.player.execute('play');
        },

        pause: function() {
            this.dyn.scopes.player.execute('pause');
        },

        rerecord: function() {},

        selectRecord: function() {},

        selectRecordScreen: function(isMultiStream) {},

        selectUpload: function(file) {},

        uploadCovershot: function(file) {},

        checkOrientation: function(isPortrait, next) {
            next = next || "FatalError";
            if (this.dyn.get("media-orientation")) {
                if (
                    (this.dyn.get("media-orientation") === "portrait" && !isPortrait) ||
                    (this.dyn.get("media-orientation") === "landscape" && isPortrait)
                ) {
                    this.dyn.set("recordvisible", false);
                    var message = this.dyn.string("supported-mode")
                        .replace("%s", isPortrait ? "landscape" : "portrait");
                    message += " " + this.dyn.string("re-choose-action");
                    this.next(next, {
                        message: message,
                        retry: "Chooser"
                    });
                    return false;
                }
            }
            return true;
        }


    }]);
});



Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.FatalError", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "browser:Info",
    "base:Timers.Timer"
], function(State, Info, Timer, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["message"],
        _locals: ["message", "retry"],

        _started: function() {
            this.dyn.set("message", this._message || this.dyn.string("recorder-error"));
            this.dyn.set("shortMessage", this.dyn.get("message").length < 30);
            this.listenOn(this.dyn, "message-click", function() {
                this.dyn.set("placeholderstyle", "");
                if (this._retry)
                    this.next(this._retry);
            });
        }
    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.ChooseAlternativeDevice", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "browser:Info"
], function(State, Info, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["message"],
        _locals: ["message", "retry"],


        _started: function() {
            this.dyn.set("controlbar_active", true);
            this.dyn.set("message", this._message || this.dyn.string("recorder-error"));
            this.dyn.set("shortMessage", this.dyn.get("message").length < 30);

            this.listenOn(this.dyn, "message-click", function() {
                this.next("Chooser");
            }, this);

            // source
            // this.listenOn(this.dyn, "change:selectedcamera", function() {
            if (typeof this.dyn.recorder._recorder !== "undefined") {
                this.listenOn(this.dyn.recorder._recorder, "rebound", function() {
                    if (Info.isChromiumBased())
                        this.next("CameraHasAccess");
                }, this);
            }
        }
    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.Initial", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "browser:Dom"
], function(State, Dom, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        _started: function() {
            this.dyn.set("is_initial_state", true);
            this.dyn.set("verified", false);
            this.dyn.set("playbacksource", null);
            this.dyn.set("playbackposter", null);
            this.dyn.set("player_active", false);
            // On rerecord need to setup as empty
            this.dyn.set("placeholderstyle", "");
            this.dyn._videoFileName = null;
            this.dyn._videoFile = null;
            this.dyn._videoFilePlaybackable = false;
            this.dyn.__firstFrameSnapshot = null;
            this.dyn._initializeUploader();
            if (!this.dyn.get("recordermode")) {
                if (!this.dyn.get("video")) {
                    console.warn("recordermode:false requires an existing video to be present and provided.");
                    this.dyn.set("recordermode", true);
                } else
                    this.next("Player");
            } else if (this.dyn.get("autorecord") || this.dyn.get("skipinitial"))
                if (this.dyn.get("onlyaudio")) {
                    Dom.userInteraction(function() {
                        this.eventualNext("RequiredSoftwareCheck");
                    }, this);
                } else {
                    this.eventualNext("RequiredSoftwareCheck");
                }
            else
                this.next("Chooser");
        },

        _end: function() {
            this.dyn.set("is_initial_state", false);
        }

    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.Player", [
    "module:VideoRecorder.Dynamics.RecorderStates.State"
], function(State, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        rerecord: function() {
            this.dyn.trigger("rerecord");
            this.dyn.set("recordermode", true);
            this.next("Initial");
        },

        _started: function() {
            this.dyn.set("player_active", true);
            if (this.dyn.get("allowtexttrackupload"))
                this.dyn.set("uploadtexttracksvisible", true);
        },

        _end: function() {
            this.dyn.set("player_active", false);
            this.dyn.set("uploadtexttracksvisible", false);
        }

    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.Chooser", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Strings",
    "base:Objs",
    "browser:Info",
    "module:PopupHelper",
    "media:Player.Support"
], function(State, Strings, Objs, Info, PopupHelper, PlayerSupport, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["chooser"],

        _started: function() {
            this.listenOn(this.dyn, "change:orientation change:currentorientation", function() {
                var orientation = this.dyn.get("orientation");
                var currentorientation = this.dyn.get("currentorientation");
                var result = orientation && orientation !== currentorientation;
                if (result)
                    this.dyn.set("message", this.dyn.string("orientation-" + orientation + "-required"));
                this.dyn.set("message_active", result);
                this.dyn.set("chooser_active", !result);
            }, this, {
                initcall: true
            });
        },

        _popup: function() {
            var popup = this.auto_destroy(new PopupHelper());
            var dynamic = this.auto_destroy(new this.dyn.cls({
                element: popup.containerInner,
                attrs: Objs.extend(this.dyn.cloneAttrs(), this.dyn.popupAttrs())
            }));
            this._delegatedRecorder = dynamic;
            this.dyn.delegateEvents(null, dynamic);
            popup.on("hide", function() {
                this._delegatedRecorder = null;
                dynamic.destroy();
                popup.destroy();
            }, this);
            popup.show();
            dynamic.activate();
        },

        record: function() {
            if (this.dyn.get("popup")) {
                this._popup();
                return;
            }
            this.dyn.set("autorecord", true);
            this.selectRecord();
        },

        /**
         * Will launch multistream
         * @param isMultiStream // Does stream has additional stream
         */
        selectRecordScreen: function(isMultiStream) {
            if (this.dyn.get("popup")) {
                this._popup();
                return;
            }
            this.dyn.set("record_media", isMultiStream ? "multistream" : "screen");
            this.next("RequiredSoftwareCheck");
        },

        selectRecord: function() {
            if (this.dyn.get("popup")) {
                this._popup();
                return;
            }
            this.dyn.set("record_media", "camera");
            this.next("RequiredSoftwareCheck");
        },

        selectUpload: function(file) {
            if (this.dyn.get("popup")) {
                this._popup();
                return;
            }
            if (!(Info.isMobile() && Info.isAndroid() && Info.isCordova())) {
                if (this.dyn.get("allowedextensions")) {
                    var filename = (file.files[0].name || "").toLowerCase();
                    var found = false;
                    this.dyn.get("allowedextensions").forEach(function(extension) {
                        if (Strings.ends_with(filename, "." + extension.toLowerCase()))
                            found = true;
                    }, this);
                    if (!found) {
                        var error_message = this.dyn.string("unsupported_video_type").replace("%s", this.dyn.get("allowedextensions").join(" / "));
                        this.dyn.trigger("error", {
                            error_type: "upload",
                            error_code: error_message
                        });
                        this.next("FatalError", {
                            message: error_message,
                            retry: "Chooser"
                        });
                        return;
                    }
                }
                if (this.dyn.get("filesizelimit") && file.files && file.files.length > 0 && file.files[0].size && file.files[0].size > this.dyn.get("filesizelimit")) {
                    var fact = "KB";
                    var size = Math.round(file.files[0].size / 1000);
                    var limit = Math.round(this.dyn.get("filesizelimit") / 1000);
                    if (size > 999) {
                        fact = "MB";
                        size = Math.round(size / 1000);
                        limit = Math.round(limit / 1000);
                    }
                    this.next("FatalError", {
                        message: this.dyn.string("video_file_too_large").replace("%s", size + fact + " / " + limit + fact),
                        retry: "Chooser"
                    });
                    return;
                }
            }
            try {
                PlayerSupport.videoFileInfo(file.files[0]).success(function(data) {

                    if (typeof data.width !== "undefined" && typeof data.height !== "undefined")
                        if (!this.checkOrientation((data.width / data.height) > 1))
                            return;

                    if (data.duration && this.dyn.get("enforce-duration")) {
                        if ((this.dyn.get("timeminlimit") && data.duration < this.dyn.get("timeminlimit")) || (this.dyn.get("timelimit") && data.duration > this.dyn.get("timelimit"))) {
                            this.next("FatalError", {
                                message: this.dyn.string("upload-error-duration"),
                                retry: "Chooser"
                            });
                            return;
                        }
                    }
                    if ((data.width && this.dyn.get("minuploadingwidth") && this.dyn.get("minuploadingwidth") > data.width) ||
                        (data.width && this.dyn.get("maxuploadingwidth") && this.dyn.get("maxuploadingwidth") < data.width) ||
                        (data.height && this.dyn.get("minuploadingheight") && this.dyn.get("minuploadingheight") > data.height) ||
                        (data.height && this.dyn.get("maxuploadingheight") && this.dyn.get("maxuploadingheight") < data.height)) {
                        this.next("FatalError", {
                            message: this.dyn.string("resolution-constraint-error"),
                            retry: "Chooser"
                        });
                        return;
                    }
                    this.dyn._videoFilePlaybackable = true;
                    this.dyn.set("duration", data.duration);
                    if (data.width <= 0 || data.height <= 0) {
                        this.dyn._videoFilePlaybackable = false;
                        this.dyn.set("media_src_not_supported", true);
                        this._disablePlaybackOnRecorder();
                    }
                    this._uploadFile(file);
                }, this).error(function(e) {
                    if (e.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) {
                        this.dyn.set("media_src_not_supported", true);
                        this._disablePlaybackOnRecorder();
                    }
                    this._uploadFile(file);
                }, this);
            } catch (e) {
                this._uploadFile(file);
            }
        },

        /**
         * @param {File} file
         * @private
         */
        _uploadFile: function(file) {
            if (this.__blocked)
                return;
            this.__blocked = true;
            this.dyn.set("creation-type", Info.isMobile() ? "mobile" : "upload");
            try {
                this.dyn._videoFileName = file.files[0].name;
                this.dyn._videoFile = file.files[0];
            } catch (e) {}
            this.dyn._prepareRecording().success(function() {
                this.dyn.trigger("upload_selected", file);
                this.dyn._uploadVideoFile(file);
                this._setValueToEmpty(file);
                this.__blocked = false;
                this.next("CovershotSelection");
            }, this).error(function(s) {
                this._setValueToEmpty(file);
                this.__blocked = false;
                this.next("FatalError", {
                    message: s,
                    retry: "Chooser"
                });
            }, this);
        },

        /**
         * Try to fix twice file upload behaviour, (on change event won't be executed twice with the same file)
         * Don't set null to value, will not solve an issue
         * @param {HTMLInputElement} file
         */
        _setValueToEmpty: function(file) {
            try {
                file.value = '';
            } catch (e) {}
        },
        _disablePlaybackOnRecorder: function() {
            // skip allowtrim/localplayback/snapshotfromuploader, show different error message on uploading.
            // anything that call playback on recorder will be skipped, unless it's returned from server (transcoded)
            if (this.dyn.get("allowtrim") === true) {
                this.dyn.set("allowtrim", false);
                this.dyn.set("was_allowtrim", true);
            }
            if (this.dyn.get("localplayback") === true) {
                this.dyn.set("localplayback", false);
                this.dyn.set("was_localplayback", true);
            }
            if (this.dyn.get("snapshotfromuploader") === true) {
                this.dyn.set("snapshotfromuploader", false);
                this.dyn.set("was_snapshotfromuploader", true);
            }
        }
    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.CreateUploadCovershot", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "media:Recorder.Support",
    "base:Objs",
    "base:Timers.Timer",
    "browser:Dom",
    "browser:Events",
    "browser:Info",
    "base:Async"
], function(State, RecorderSupport, Objs, Timer, Dom, DomEvents, Info, Async, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["loader", "message"],

        _started: function() {
            this.dyn.set("cancancel", true);
            this.dyn.set("loader_active", true);
            this.dyn.set("topmessage", this.dyn.string('please-wait'));
            this.dyn.set("message", this.dyn.string("prepare-covershot"));
            if (this.dyn.get("cancancel") && this.dyn.get("allowcancel"))
                this.dyn.set("controlbar_active", true);

            try {
                this.dyn.set("player_active", false);


                // this.dyn._videoFile only for playback able browsers
                if (this.dyn._videoFile)
                    this.dyn.set("playbacksource", (window.URL || window.webkitURL).createObjectURL(this.dyn._videoFile));
                else {
                    console.warn('Could not find source file to be able start player');
                    return this.next("Trimming");
                }

                var _video = document.createElement('video');
                var _currentTime = 0;
                var _totalDuration = 0;
                var _seekPeriod = 1;
                _video.src = this.dyn.get("playbacksource");
                _video.setAttribute('preload', 'metadata');
                _video.volume = 0;
                _video.muted = true;

                // Wait for 5 seconds before checking if any video data was be able loaded, if not proceed
                Async.eventually(function() {
                    // Have no metadata
                    if (_video.readyState < 1) {
                        console.warn('Could not be able load video metadata');
                        return this.next("Trimming");
                    }
                }, this, 5000);

                var _playerLoadedData = this.auto_destroy(new DomEvents());

                // Note that loadeddata event will not fire in mobile/tablet devices if data-saver is on in browser settings
                // So using loadedmetadata ( or canplaythrough) to be available both desktop and mobile
                // readyState is newly equal to HAVE_ENOUGH_DATA
                // HAVE_NOTHING == 0; HAVE_METADATA == 1; HAVE_CURRENT_DATA == 2; HAVE_FUTURE_DATA == 3; HAVE_ENOUGH_DATA == 4
                _playerLoadedData.on(_video, "loadedmetadata", function(ev) {
                    _totalDuration = _video.duration;
                    if (_totalDuration === Infinity || !_totalDuration) {
                        console.warn('Could not generate video covershots from uploaded file');
                        return this.next("Trimming");
                    }
                    _seekPeriod = this._calculateSeekPeriod(_totalDuration);
                    if (_video.videoWidth > 0 && _video.videoHeight > 0) {
                        var _thumbWidth = _video.videoWidth > _video.videoHeight ? 80 : 35;
                        this.dyn.set("videometadata", Objs.tree_merge(this.dyn.get("videometadata"), {
                            height: _video.videoHeight,
                            width: _video.videoWidth,
                            ratio: +(_video.videoWidth / _video.videoHeight).toFixed(2),
                            "thumbnails": {
                                width: _thumbWidth,
                                height: Math.floor(_thumbWidth / _video.videoWidth * _video.videoHeight)
                            }
                        }));
                        this.__videoSeekTimer = new Timer({
                            context: this,
                            fire: function() {
                                if (_video.volume < 0.1 || _video.muted) {
                                    _video.currentTime = _currentTime;
                                } else _video.volume = 0;
                                _currentTime = _currentTime + _seekPeriod;
                            },
                            destroy_on_stop: true,
                            delay: 500,
                            start: true
                        });
                    } else {
                        console.warn('Could not find video dimensions information to be able create covershot');
                        return this.next("Trimming");
                    }
                }, this);

                // Will take only one snapshot for selectfirstcovershotonskip & process placeholder
                if (!this.dyn.get("picksnapshots")) {
                    _playerLoadedData.on(_video, "canplay", function(ev) {
                        _video.currentTime = 0;
                        var __snap = RecorderSupport.createSnapshot(this.dyn.get("snapshottype"), _video, true);
                        if (__snap) {
                            if (this.dyn.get("selectfirstcovershotonskip"))
                                this.dyn.get("snapshots")[0] = __snap;
                            this.dyn.get("videometadata").processingPlaceholder = __snap;
                            Dom.triggerDomEvent(_video, "ended");
                        }
                    }, this);
                } else {
                    _playerLoadedData.on(_video, "seeked", function(ev) {
                        var __snap = RecorderSupport.createSnapshot(this.dyn.get("snapshottype"), _video, true);
                        if (__snap) {
                            // Will add snap images as thumbnails
                            if (this.dyn.get("createthumbnails")) {
                                this.dyn.get("videometadata").thumbnails.images.push({
                                    time: _video.currentTime,
                                    snap: __snap
                                });
                            }
                            if (this.dyn.get("snapshots").length < this.dyn.get("snapshotmax")) {
                                this.dyn.get("snapshots").push(__snap);
                            } else {
                                var i = Math.floor(Math.random() * this.dyn.get("snapshotmax"));
                                RecorderSupport.removeSnapshot(this.dyn.get("snapshots")[i]);
                                this.dyn.get("snapshots")[i] = __snap;
                            }
                        }

                        // Should trigger ended event
                        if ((_video.currentTime + _seekPeriod) >= _totalDuration) {
                            _video.currentTime = _video.currentTime + _seekPeriod;
                            // Will fire ended event if not fired already, fixes IE/Edge related bug
                            if (!_video.ended) {
                                Dom.triggerDomEvent(_video, "ended");
                            }
                        }
                    }, this);
                }

                _playerLoadedData.on(_video, "ended", function(ev) {
                    this.__videoSeekTimer.stop();
                    if (typeof _video.remove === 'function')
                        _video.remove();
                    else
                        _video.style.display = 'none';
                    this.next("CovershotSelection");
                }, this);

            } catch (exe) {
                console.warn(exe);
                this.next("Trimming");
            }
        },

        stop: function() {
            this.dyn.set("loader_active", false);
            this.dyn.set("loaderlabel", "");
            this.dyn.set("topmessage", "");
        },

        /**
         * @param {number} duration
         * @return {number}
         * @private
         */
        _calculateSeekPeriod: function(duration) {
            if (duration < 15) return 1;
            if (duration < 40) return 3;
            if (duration < 100) return 4;
            else
                return Math.floor(duration / 100) + 4;
        }
    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.RequiredSoftwareCheck", [
    "module:VideoRecorder.Dynamics.RecorderStates.State"
], function(State, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["loader"],

        _started: function() {
            this.dyn.set("settingsvisible", false);
            this.dyn.set("recordvisible", false);
            this.dyn.set("rerecordvisible", false);
            this.dyn.set("stopvisible", false);
            this.dyn.set("skipvisible", false);
            this.dyn.set("uploadcovershotvisible", false);
            this.dyn.set("controlbarlabel", "");
            this.dyn.set("loaderlabel", "");
            this.listenOn(this.dyn, "error", function(s) {
                this.next("FatalError", {
                    message: this.dyn.string("attach-error"),
                    retry: "Initial"
                });
            }, this);
            this.dyn._attachRecorder();
            if (this.dyn) {
                this.dyn.on("message-link-click", function(link) {
                    link.execute();
                    this.next("RequiredSoftwareWait");
                }, this);
                this.dyn._softwareDependencies().error(function(dependencies) {
                    this.dyn.set("message_links", dependencies);
                    this.dyn.set("loader_active", false);
                    this.dyn.set("message_active", true);
                    this.dyn.set("message", this.dyn.string("software-required"));
                }, this).success(function() {
                    this.next("CameraAccess");
                }, this);
            }
        }
    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.RequiredSoftwareWait", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Promise",
    "browser:Dom"
], function(State, Promise, Dom, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["message"],

        _started: function() {
            this.dyn.set("settingsvisible", false);
            this.dyn.set("recordvisible", false);
            this.dyn.set("rerecordvisible", false);
            this.dyn.set("stopvisible", false);
            this.dyn.set("skipvisible", false);
            this.dyn.set("uploadcovershotvisible", false);
            this.dyn.set("controlbarlabel", "");
            this.dyn.set("loaderlabel", "");
            this.dyn.set("message", this.dyn.string("software-waiting"));
            Promise.resilience(function() {
                if (Dom.isTabHidden())
                    return Promise.error("Not ready");
                return this.dyn._softwareDependencies();
            }, this, 120, [], 1000).success(function() {
                this.next("CameraAccess");
            }, this).error(function() {
                this.next("RequiredSoftwareCheck");
            }, this);
            this.dyn.on("message-click", function() {
                this.next("RequiredSoftwareCheck");
            }, this);
        }

    });
});



Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.CameraAccess", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Objs",
    "base:Types",
    "base:Timers.Timer",
    "base:Collections.Collection"
], function(State, Objs, Types, Timer, Collection, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["loader"],

        _started: function() {
            this.dyn.set("settingsvisible", true);
            this.dyn.set("recordvisible", true);
            this.dyn.set("rerecordvisible", false);
            this.dyn.set("stopvisible", false);
            this.dyn.set("skipvisible", false);
            this.dyn.set("uploadcovershotvisible", false);
            this.dyn.set("controlbarlabel", "");
            this.dyn.set("loaderlabel", "");
            this.listenOn(this.dyn, "bound", function() {
                this.dyn.set("creation-type", "webrtc");
                if (this.dyn.get("onlyaudio") || this.dyn.get("record_media") === "screen" || this.dyn.get("record_media") === "multistream") {
                    if (this.dyn.get("allowmultistreams") && this.dyn.get("record_media") === "multistream") {
                        this.dyn.recorder.enumerateDevices().success(function(devices) {
                            this.set("cameras", new Collection(Objs.values(devices.video)));
                            this.trigger(Types.is_empty(devices.video) ? "no_camera" : "has_camera");
                            this._add_new_stream();
                        }, this.dyn);
                    }
                    this.next("CameraHasAccess");
                    return;
                }
                var timer = this.auto_destroy(new Timer({
                    start: true,
                    delay: 100,
                    context: this,
                    fire: function() {
                        if (this.dyn.blankLevel() >= 0.01 && this.dyn.deltaCoefficient() >= 0.01) {
                            timer.stop();
                            this.next("CameraHasAccess");
                        }
                    }
                }));
            }, this);
            this.listenOn(this.dyn, "error", function(s) {
                this.next("FatalError", {
                    message: this.dyn.string("attach-error"),
                    retry: "Initial"
                });
            }, this);
            this.listenOn(this.dyn, "access_forbidden", function(e) {
                var message = this.dyn.string("access-forbidden");

                if (typeof e.name === 'string' && typeof this.dyn.recorder.errorHandler === 'function') {
                    var errorHandler = this.dyn.recorder.errorHandler(e.name);
                    if (typeof errorHandler === 'object') {
                        if (errorHandler.userLevel)
                            message = this.dyn.string(errorHandler.key);
                        else
                            console.warn(errorHandler.message + '. Please inform us!');
                    }
                }
                this.next("FatalError", {
                    message: message,
                    retry: "Initial"
                });
            }, this);
            this.dyn._bindMedia();
        }
    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.CameraHasAccess", [
    "module:VideoRecorder.Dynamics.RecorderStates.State"
], function(State, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["topmessage", "controlbar"],

        _started: function() {
            if (!this.checkOrientation(this.dyn.isPortrait(), "ChooseAlternativeDevice"))
                return;
            this.dyn.trigger("ready_to_record");
            this._preparePromise = null;
            if (this.dyn.get("countdown") > 0 && this.dyn.recorder && this.dyn.recorder.recordDelay(this.dyn.get("uploadoptions")) > this.dyn.get("countdown") * 1000)
                this._preparePromise = this.dyn._prepareRecording();
            //  For now available for WebRTC only
            if (this.dyn.get("pausable"))
                this.dyn.set("pausable", this.dyn.recorder.canPause());
            this.dyn.set("hovermessage", "");
            this.dyn.set("topmessage", "");
            this.dyn.set("settingsvisible", true);
            this.dyn.set("recordvisible", true);
            this.dyn.set("rerecordvisible", false);
            this.dyn.set("stopvisible", false);
            this.dyn.set("skipvisible", false);
            this.dyn.set("uploadcovershotvisible", false);
            this.dyn.set("controlbarlabel", "");
            this.dyn.set("isrecorderready", true);
            if (this.dyn.get("autorecord"))
                this.next("RecordPrepare", {
                    preparePromise: this._preparePromise
                });
        },

        record: function() {
            if (this.dyn.get("autorecord"))
                return;
            if (this.dyn.get("audio-test-mandatory") && !this.dyn.get("microphonehealthy") && !this._preparePromise)
                return;
            this.next("RecordPrepare", {
                preparePromise: this._preparePromise
            });
        }
    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.RecordPrepare", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Timers.Timer",
    "base:Time"
], function(State, Timer, Time, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["loader"],
        _locals: ["preparePromise"],

        _started: function() {
            this.dyn.set("message", "");
            this.dyn.set("loaderlabel", "");
            var startedRecording = false;
            this.dyn._accessing_camera = true;
            this._preparePromise = this._preparePromise || this.dyn._prepareRecording();
            var countdown = this.dyn.get("countdown") ? this.dyn.get("countdown") * 1000 : 0;
            var delay = this.dyn.recorder.recordDelay(this.dyn.get("uploadoptions")) || 0;
            if (countdown) {
                var displayDenominator = 1000;
                var silentTime = 0;
                var startTime = Time.now();
                var endTime = startTime + Math.max(delay, countdown);
                if (delay > countdown) {
                    silentTime = Math.min(500, delay - countdown);
                    displayDenominator = (delay - silentTime) / countdown * 1000;
                } else
                    this.dyn.set("loaderlabel", this.dyn.get("countdown"));
                var timer = new Timer({
                    context: this,
                    delay: 50,
                    fire: function() {
                        var now = Time.now();
                        var time_left = Math.max(0, endTime - now);
                        if (now > silentTime + startTime) {
                            this.dyn.set("loaderlabel", "" + Math.ceil((time_left - silentTime) / displayDenominator));
                            this.dyn.trigger("countdown", Math.round((time_left - silentTime) / displayDenominator * 1000));
                        }
                        if (endTime <= now) {
                            this.dyn.set("loaderlabel", "");
                            timer.stop();
                        }
                        if ((time_left <= delay) && !startedRecording) {
                            startedRecording = true;
                            this._startRecording();
                        }
                    }
                });
                this.auto_destroy(timer);
            } else
                this._startRecording();
        },

        record: function() {
            this._startRecording();
        },

        _startRecording: function() {
            this._preparePromise.success(function() {
                this.dyn._startRecording().success(function() {
                    this.next("Recording");
                }, this).error(function(s) {
                    this.next("FatalError", {
                        message: s,
                        retry: "RequiredSoftwareCheck"
                    });
                }, this);
            }, this).error(function(s) {
                this.next("FatalError", {
                    message: s,
                    retry: "RequiredSoftwareCheck"
                });
            }, this);
        }

    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.Recording", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Timers.Timer",
    "base:Time",
    "base:TimeFormat",
    "base:Async",
    "browser:Info"
], function(State, Timer, Time, TimeFormat, Async, Info, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["topmessage", "controlbar"],

        _started: function() {
            this.dyn.set("hovermessage", "");
            this.dyn.set("topmessage", "");
            this.dyn._accessing_camera = true;
            this.dyn.trigger("recording");
            this.dyn.set("settingsvisible", false);
            this.dyn.set("rerecordvisible", false);
            this.dyn.set("recordvisible", false);
            this.dyn.set("stopvisible", true);
            this.dyn.set("skipvisible", false);
            this.dyn.set("uploadcovershotvisible", false);
            this._startTime = Time.now();
            this._stopping = false;
            this.__timerDelay = 10;
            this._timer = this.auto_destroy(new Timer({
                immediate: true,
                delay: this.__timerDelay,
                context: this,
                fire: this._timerFire
            }));
            this._framerateWarning = false;
        },

        _timerFire: function() {
            var limit = this.dyn.get("timelimit");
            if (this.dyn.__paused) return;
            var current = Time.now() - this.dyn.__pauseDelta;
            var display = Math.max(0, limit ? (this._startTime + limit * 1000 - current) : (current - this._startTime));
            this.dyn.trigger("recording_progress", current - this._startTime, !!this.dyn.__paused);
            this.dyn.set("controlbarlabel", this.dyn.get("display-timer") ? TimeFormat.format(TimeFormat.ELAPSED_MINUTES_SECONDS, display) : "");

            if (this.dyn.get("timeminlimit"))
                this.dyn.set("mintimeindicator", (Time.now() - this._startTime) / 1000 <= this.dyn.get("timeminlimit"));

            if (limit && this._startTime + limit * 1000 <= current) {
                this._timer.stop();
                this.stop();
            }


            if (this.dyn.get("framerate-warning") && this.dyn.averageFrameRate()) {
                var framerateWarning = this.dyn.averageFrameRate() < this.dyn.get("framerate-warning");
                if (framerateWarning !== this._framerateWarning) {
                    this._framerateWarning = framerateWarning;
                    if (framerateWarning)
                        this.dyn.set("hovermessage", this.dyn.string("framerate-warning"));
                    else
                        this.dyn.set("hovermessage", "");
                }
            }
        },

        stop: function() {
            var minlimit = this.dyn.get("timeminlimit");
            if (minlimit) {
                var delta = (Time.now() - (this._startTime + this.dyn.__pauseDelta)) / 1000;
                if (delta < minlimit) {
                    var limit = this.dyn.get("timelimit");
                    if (!limit || limit > delta)
                        return;
                }
            }
            if (this._stopping)
                return;
            this.dyn.set("loader_active", true);
            this.dyn.set("controlbar_active", false);
            this.dyn.set("topmessage_active", false);
            this.dyn.set("message_active", true);
            this.dyn.set("message", "");
            this._stopping = true;
            Async.eventually(function() {
                this.dyn._stopRecording().success(function() {
                    this._hasStopped();
                    this.next("CovershotSelection");
                }, this).error(function(s) {
                    this.next("FatalError", {
                        message: s,
                        retry: "RequiredSoftwareCheck"
                    });
                }, this);
            }, this);
        },

        _hasStopped: function() {
            this.dyn.set("duration", (Time.now() - (this._startTime + this.dyn.__pauseDelta)) / 1000);
            if (this.dyn.get("snapshots").length > 0)
                this.dyn._showBackgroundSnapshot();
            this.dyn._unbindMedia();
            this.dyn.trigger("recording_stopped");
        }

    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.Trimming", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Types"
], function(State, Types, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        _started: function() {
            if (!this.dyn.isFormatSupported() || !this.dyn.get("allowtrim") || this.dyn.get("duration") < this.dyn.get("timeminlimit")) {
                if (!this.dyn.isFormatSupported()) {
                    this.dyn.set("allowtrim", false);
                    this.dyn.set("was_allowtrim", true);
                }
                this.next("Uploading");
            } else {
                if (this.dyn.get("trimoverlay")) {
                    this.dyn._getFirstFrameSnapshot()
                        .success(function(snapshot) {
                            this.showTrimmingOverlay(snapshot);
                        }, this)
                        .error(function() {
                            this.showTrimmingOverlay(this.dyn.__backgroundSnapshot);
                        }, this);
                } else {
                    this.dyn.set("message_active", true);
                    this.dyn.set("message", this.dyn.string("wait-for-trim"));
                    this.listenOnce(this.dyn, "manual-trim", function(start, end) {
                        if (Types.isNumber(start) && start > 0) this.dyn.set("starttime", start);
                        if (Types.isNumber(end) && end <= this.get("duration")) this.dyn.set("endtime", end);
                        this.dyn.trigger("video-trimmed", this.dyn.get("starttime"), this.dyn.get("endtime"), this.dyn.get("duration"));
                        this.next("Uploading");
                    });
                }
                this.dyn.trigger("ready-to-trim");
            }
        },

        showTrimmingOverlay: function(poster) {
            this._playerAttrs = this.dyn.get("playerattrs");

            this.dyn.set("playerattrs", {
                poster: poster,
                source: this.dyn._videoFile || this.dyn.recorder.localPlaybackSource(),
                trimmingmode: true,
                hidecontrolbar: true
            });
            this.dyn.set("playertopmessage", this.dyn.string("trim-prompt"));
            this.dyn.set("player_active", true);

            this.listenOnce(this.dyn.scopes.player, "playing", function() {
                this.dyn.scopes.player.set("skipinitial", true);
                this.dyn.set("playertopmessage", this.dyn.string("trim-video"));
            });

            this.listenOnce(this.dyn.scopes.player, "video-trimmed skip", function(data) {
                if (data && data.start) this.dyn.set("starttime", data.start);
                if (data && data.end) this.dyn.set("endtime", data.end);
                this.dyn.trigger("video-trimmed", this.dyn.get("starttime"), this.dyn.get("endtime"), this.dyn.get("duration"));
                this.hideTrimmingOverlay();
                this.next("Uploading");
            }, this);
        },

        hideTrimmingOverlay: function() {
            this.dyn.set("trimmingmode", false);
            this.dyn.set("playerattrs", this._playerAttrs);
            this.dyn.set("player_active", false);
        }
    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.CovershotSelection", [
    "module:VideoRecorder.Dynamics.RecorderStates.State"
], function(State, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        _started: function() {
            if ((this.dyn.get("picksnapshots") || this.dyn.get("custom-covershots")) && !this.dyn.get("onlyaudio")) {
                if (this.dyn.get("pickcovershotframe")) {
                    if (this.dyn.recorder && this.dyn.recorder.supportsLocalPlayback()) {
                        this.next("CovershotSelectionFromPlayer");
                    } else if (this.dyn.get("snapshotfromuploader") && this.dyn.isFormatSupported()) {
                        this.next("CovershotSelectionFromPlayer");
                    } else {
                        this._next(true);
                    }
                } else if (this.dyn.get("snapshots") && this.dyn.get("snapshots").length > 0) {
                    this.next("CovershotSelectionFromGallery");
                } else if (this.dyn.get("snapshotfromuploader") || (this.dyn.get("snapshotfrommobilecapture") && this.dyn.get("recordviafilecapture"))) {
                    this.next("CreateUploadCovershot");
                } else {
                    this._next(true);
                }
            } else if (!this.dyn.get("snapshots") && this.dyn.get("snapshotfromuploader") || (this.dyn.get("snapshotfrommobilecapture") && this.dyn.get("recordviafilecapture"))) {
                this.dyn.set("snapshotmax", 1);
                this.dyn.set("snapshots", []);
                this.next("CreateUploadCovershot");
            } else {
                this._next(true);
            }
        },

        rerecord: function() {
            this.dyn._hideBackgroundSnapshot();
            this.dyn._detachRecorder();
            this.dyn.trigger("rerecord");
            this.dyn.set("recordermode", true);
            this.next("Initial");
        },

        _next: function(skippedCovershot) {
            if (skippedCovershot && this.dyn.get("selectfirstcovershotonskip") && this.dyn.get("snapshots")) {
                if (this.dyn.get("snapshots")[0]) {
                    this.dyn._uploadCovershot(this.dyn.get("snapshots")[0]);
                }
            }
            if (!this.dyn.get("videometadata").processingPlaceholder && this.dyn.get("snapshots")[0])
                this.dyn.get("videometadata").processingPlaceholder = this.dyn.get("snapshots")[0];
            if (this.dyn.get("videometadata").thumbnails.images.length > 3 && this.dyn.get("createthumbnails")) {
                this.next("UploadThumbnails");
            } else {
                this.next("Trimming");
            }
        }

    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.CovershotSelectionFromGallery", [
    "module:VideoRecorder.Dynamics.RecorderStates.CovershotSelection"
], function(CovershotSelectionState, scoped) {
    return CovershotSelectionState.extend({
        scoped: scoped
    }, {

        dynamics: ["imagegallery", "topmessage", "controlbar"],

        _started: function() {
            this.dyn.set("settingsvisible", false);
            this.dyn.set("recordvisible", false);
            this.dyn.set("stopvisible", false);
            this.dyn.set("skipvisible", !this.dyn.get("picksnapshotmandatory"));
            this.dyn.set("controlbarlabel", "");
            this.dyn.set("rerecordvisible", this.dyn.get("early-rerecord"));
            this.dyn.set("uploadcovershotvisible", this.dyn.get("custom-covershots"));
            this.dyn.set("hovermessage", "");
            this.dyn.set("topmessage", this.dyn.string('pick-covershot'));
            this.dyn.set("isrecorderready", false);
            if (this.dyn.get("snapshots").length > 0) {
                var imagegallery = this.dyn.scope(">[tagname='ba-videorecorder-imagegallery']").materialize(true);
                imagegallery.loadSnapshots();
                imagegallery.updateContainerSize();
            }
            this.listenOn(this.dyn, "invoke-skip", function() {
                this._next(true);
            }, this);
            this.listenOn(this.dyn, "select-image", function(image) {
                this.dyn._uploadCovershot(image);
                this.dyn.get("videometadata").processingPlaceholder = image;
                this._next(false);
            }, this);
        },

        uploadCovershot: function(file) {
            // If passed file in HTMLInputElement get file
            if (typeof file.files !== 'undefined')
                if (file.files[0])
                    file = file.files[0];
            this.dyn._uploadCovershotFile(file);
            this._next(false);
        }
    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.CovershotSelectionFromPlayer", [
    "module:VideoRecorder.Dynamics.RecorderStates.CovershotSelection"
], function(CovershotSelectionState, scoped) {
    return CovershotSelectionState.extend({
        scoped: scoped
    }, {

        _started: function() {
            this.dyn._getFirstFrameSnapshot()
                .success(function(snapshot) {
                    this.startFrameSelection(snapshot);
                }, this)
                .error(function() {
                    this.startFrameSelection(this.dyn.__backgroundSnapshot);
                }, this);
        },

        startFrameSelection: function(poster) {
            this._playerattrs = this.dyn.get("playerattrs");

            this.dyn.set("playerattrs", {
                poster: poster,
                source: this.dyn._videoFile || this.dyn.recorder.localPlaybackSource(),
                skipinitial: true
            });

            this.dyn.set("frameselectionmode", true);
            this.dyn.set("playertopmessage", this.dyn.string("pick-covershot-frame"));
            this.dyn.set("player_active", true);

            this.listenOn(this.dyn.scopes.player, "image-selected", function(image) {
                this.dyn._uploadCovershot(image);
                this._next(false);
            }, this);
        },

        endFrameSelection: function() {
            this.dyn.set("frameselectionmode", false);
            this.dyn.set("playerattrs", this._playerattrs);
        },

        _end: function() {
            this.endFrameSelection();
        }
    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.UploadThumbnails", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Objs",
    "base:Promise",
    "base:Time",
    "base:TimeFormat",
    "media:WebRTC.Support",
    "browser:Events"
], function(State, Objs, Promise, Time, TimeFormat, Support, Events, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["loader"],

        _started: function() {
            this.dyn.set("loader_active", true);
            this.dyn.set("loadlabel", "Thumbnails");
            this.dyn.set("message", this.dyn.string("prepare-thumbnails"));
            this.dyn.set("hovermessage", "");
            this.dyn.set("topmessage", "");

            this._drawIntoCanvas(this.dyn.get("videometadata").thumbnails)
                .success(function(canvas) {
                    this.dyn._uploadThumbnails(Support.dataURItoBlob(canvas.toDataURL('image/jpg')));
                    this.dyn._uploadThumbnailTracks(new Blob(this._vttDescripitions, {
                        type: "text/vtt"
                    }));
                    this.next("Trimming");
                }, this)
                .error(function(err) {
                    console.warn(err);
                    this.next("Trimming");
                }, this);
        },

        _drawIntoCanvas: function(thumbnails) {
            var promise = Promise.create();
            var w = thumbnails.width;
            var h = thumbnails.height;
            var imagesCount = thumbnails.images.length;
            var rowsCount = thumbnails.images.length > 10 ? Math.ceil(imagesCount / 10) : 1;
            var canvas = document.createElement('canvas');
            canvas.width = rowsCount > 1 ? w * 10 : w * imagesCount;
            canvas.height = rowsCount * h;
            this._vttDescripitions = [];
            this._vttDescripitions.push('WEBVTT \n\n');
            var ctx = canvas.getContext('2d');
            var index = 0;
            try {
                if (typeof thumbnails.images[index] !== 'undefined') {
                    this._imageEvent = this.auto_destroy(new Events());
                    var image = image || new Image();
                    image.width = w;
                    image.height = h;
                    image.src = (window.URL || window.webkitURL).createObjectURL(thumbnails.images[index].snap);

                    this._imageEvent.on(image, "load", function() {
                        this._recursivelyDrawImage(canvas, thumbnails, ctx, w, h, image, index, promise);
                    }, this);

                    this._imageEvent.on(image, "error", function(err) {
                        throw "Error with loading thumbnail image. ".err;
                    }, this);
                }
            } catch (err) {
                promise.asyncError(err);
            }
            return promise;
        },

        _recursivelyDrawImage: function(canvas, thumbnails, ctx, w, h, image, index, promise, column, row) {
            column = column || 0;
            row = row || 0;
            index = index || 0;
            ctx.drawImage(image, column * w, row * h, w, h);
            if ((index > 0 && (index % 10 === 0))) {
                row++;
                column = 0;
            } else if (index !== 0) column++;
            index++;
            if (typeof thumbnails.images[index] !== 'undefined' && thumbnails.images.length >= index) {
                var _image, _prevIndex, _nextIndex, _startTime, _endTime, _averageSecond, _formattedStartTime, _formattedEndTime;
                _prevIndex = index - 1;
                _nextIndex = index + 1;

                _averageSecond = Math.round((thumbnails.images[index].time - thumbnails.images[_prevIndex].time) / 2);
                _startTime = thumbnails.images[_prevIndex].time + _averageSecond;
                // For the latest thumb no need add average time
                if (!thumbnails.images[_nextIndex + 1]) _averageSecond = 0;
                _endTime = thumbnails.images[index].time + _averageSecond;

                _formattedStartTime = _startTime === 0 ? '00:00:00' : TimeFormat.format('HH:MM:ss', _startTime * 1000);
                _formattedEndTime = _endTime === 0 ? '00:00:00' : TimeFormat.format('HH:MM:ss', _endTime * 1000);

                // If we have have next index
                if (typeof thumbnails.images[_nextIndex] !== 'undefined') {
                    this._vttDescripitions.push(
                        _formattedStartTime + ".000" + " --> " + _formattedEndTime + ".000" + "\n" + this.dyn.get("uploadoptions").thumbnail.url + "#xywh=" + (column * w) + "," + (row * h) + "," + w + "," + h + "\n\n"
                    );
                }

                _image = new Image();
                _image.width = w;
                _image.height = h;
                _image.src = (window.URL || window.webkitURL).createObjectURL(thumbnails.images[index].snap);

                this._imageEvent.on(_image, "load", function() {
                    this._recursivelyDrawImage(canvas, thumbnails, ctx, w, h, _image, index, promise, column, row);
                }, this);

                this._imageEvent.on(_image, "error", function(err) {
                    throw "Error with loading thumbnail image. Error: ".err;
                }, this);

            } else {
                if (thumbnails.images.length <= index) {
                    promise.asyncSuccess(canvas);
                } else {
                    throw "Could not draw all images";
                }
            }
        }
    });
});

Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.Uploading", [
    "module:VideoRecorder.Dynamics.RecorderStates.State",
    "base:Time",
    "base:Async",
    "base:Objs"
], function(State, Time, Async, Objs, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["loader", "message"],

        _started: function() {
            this.dyn.set("cancancel", true);
            this.dyn.set("skipinitial", this.dyn.get("skipinitial") || this.dyn.get("skipinitialonrerecord"));
            this.dyn.set("settingsvisible", false);
            this.dyn.set("recordvisible", false);
            this.dyn.set("stopvisible", false);
            this.dyn.set("loadlabel", "");
            this.dyn.set("controlbarlabel", "");
            this.dyn.set("isrecorderready", false);
            this.dyn.trigger("uploading");
            this.dyn.set("rerecordvisible", this.dyn.get("early-rerecord"));
            if (this.dyn.get("early-rerecord") || (this.dyn.get("cancancel") && this.dyn.get("allowcancel")))
                this.dyn.set("controlbar_active", true);
            this.dyn.set("hovermessage", "");
            this.dyn.set("topmessage", "");

            if (this.dyn.get("media_src_not_supported") === true &&
                ((this.dyn.get("was_allowtrim") === true) ||
                    (this.dyn.get("was_localplayback") === true) ||
                    (this.dyn.get("was_snapshotfromuploader") === true)
                )
            ) {
                this.dyn.set("uploading-message", this.dyn.string("uploading-src-error"));
            } else {
                this.dyn.set("uploading-message", this.dyn.string("uploading"));
            }

            this.dyn.set("message", this.dyn.get("uploading-message"));
            this.dyn.set("playertopmessage", this.dyn.get("message"));
            var uploader = this.dyn._dataUploader;
            this.listenOn(uploader, "success", function() {
                Async.eventually(function() {
                    if (this.destroyed())
                        return;
                    this._finished();
                    this.next("Verifying");
                }, this);
            });
            this.listenOn(uploader, "error", function(e) {
                var bestError = this.dyn.string("uploading-failed");
                try {
                    e.forEach(function(ee) {
                        for (var key in ee)
                            if (this.dyn.string("upload-error-" + key))
                                bestError = this.dyn.string("upload-error-" + key);
                    }, this);
                } catch (err) {}
                this.dyn.set("player_active", false);
                this.next("FatalError", {
                    message: bestError,
                    retry: this.dyn.recorderAttached() ? "Uploading" : "Initial"
                });
            });
            this.listenOn(uploader, "progress", function(uploaded, total) {
                this.dyn.trigger("upload_progress", uploaded, total);
                if (total !== 0 && total > 0 && uploaded >= 0) {
                    var up = Math.min(100, Math.round(uploaded / total * 100));
                    if (!isNaN(up)) {
                        this.dyn.set("message", this.dyn.get("uploading-message") + ": " + up + "%");
                        this.dyn.set("playertopmessage", this.dyn.get("message"));
                    }
                }
            });
            if (this.dyn.get("localplayback") && this.dyn.isFormatSupported()) {
                if (this.dyn.recorder && this.dyn.recorder.supportsLocalPlayback())
                    this.dyn.set("playbacksource", this.dyn.recorder.localPlaybackSource());
                else
                    this.dyn.set("playbacksource", (window.URL || window.webkitURL).createObjectURL(this.dyn._videoFile));
                if (this.dyn.__lastCovershotUpload && this.dyn.recorder)
                    this.dyn.set("playbackposter", this.dyn.recorder.snapshotToLocalPoster(this.dyn.__lastCovershotUpload));
                this.dyn.set("loader_active", false);
                this.dyn.set("message_active", false);
                this.dyn._hideBackgroundSnapshot();
                this.dyn.set("player_active", true);
            } else {
                // show background image while verify and processing
                if (this.dyn.get("videometadata").processingPlaceholder && typeof window.URL !== "undefined") {
                    var dyn = this.dyn;
                    var placeholder = URL.createObjectURL(this.dyn.get("videometadata").processingPlaceholder);

                    var XHR = new XMLHttpRequest();
                    XHR.open('GET', placeholder);
                    XHR.onload = function() {
                        var reader = new FileReader();
                        reader.onloadend = function() {
                            // bs-styles not works as expected
                            dyn.set("placeholderstyle", "background: url('" + reader.result + "') center/contain no-repeat");
                        };
                        reader.readAsDataURL(XHR.response);
                    };
                    XHR.responseType = 'blob';
                    XHR.send();
                }
            }
            this.dyn.set("start-upload-time", Time.now());
            uploader.reset();
            uploader.upload();
        },

        rerecord: function() {
            this.dyn._hideBackgroundSnapshot();
            this.dyn._detachRecorder();
            this.dyn.trigger("rerecord");
            this.dyn.set("recordermode", true);
            this.dyn.set("placeholderstyle", "");
            this.next("Initial");
        },

        _finished: function() {
            this.dyn.set("cancancel", false);
            this.dyn.trigger("uploaded");
            this.dyn.set("end-upload-time", Time.now());
        }

    });
});


Scoped.define("module:VideoRecorder.Dynamics.RecorderStates.Verifying", [
    "module:VideoRecorder.Dynamics.RecorderStates.State"
], function(State, scoped) {
    return State.extend({
        scoped: scoped
    }, {

        dynamics: ["loader", "message"],

        _started: function() {
            this.dyn.set("loadlabel", "");
            this.dyn.trigger("verifying");
            this.dyn.set("message", this.dyn.string("verifying") + "...");
            this.dyn.set("playertopmessage", this.dyn.get("message"));
            if (this.dyn.get("localplayback") && this.dyn.isFormatSupported()) {
                this.dyn.set("loader_active", false);
                this.dyn.set("message_active", false);
            } else {
                this.dyn.set("rerecordvisible", this.dyn.get("early-rerecord"));
                if (this.dyn.get("early-rerecord"))
                    this.dyn.set("controlbar_active", true);
            }
            this.dyn._verifyRecording().success(function() {
                this.dyn.trigger("verified");
                this.dyn._hideBackgroundSnapshot();
                this.dyn._detachRecorder();
                if (this.dyn.get("recordings"))
                    this.dyn.set("recordings", this.dyn.get("recordings") - 1);
                this.dyn.set("message", "");
                this.dyn.set("playertopmessage", "");
                this.dyn.set("verified", true);
                this.next("Player");
            }, this).error(function() {
                this.dyn.set("player_active", false);
                this.dyn.set("placeholderstyle", "");
                this.next("FatalError", {
                    message: this.dyn.string("verifying-failed"),
                    retry: this.dyn.recorderAttached() ? "Verifying" : "Initial"
                });
            }, this);
        },

        rerecord: function() {
            this.dyn._hideBackgroundSnapshot();
            this.dyn._detachRecorder();
            this.dyn.trigger("rerecord");
            this.dyn.set("recordermode", true);
            this.next("Initial");
        }

    });
});