/**
 * MPEG-DASH video controller.
 *
 * @constructor FORGE.VideoDash
 * @param {FORGE.Viewer} viewer - {@link FORGE.Viewer} reference.
 * @param {string} key - The video file id reference.
 * @param {?(string|FORGE.VideoQuality|Array<(string|FORGE.VideoQuality)>)=} config - Either a {@link FORGE.VideoQuality} or a string URL.
 * @param {string=} qualityMode - The default quality mode.
 * @extends {FORGE.VideoBase}
 *
 * @todo  ability to call the QualityAbort event.
 * @todo  add protection controller and protection key controller to be able to manage external secured streams (DRM)
 * @todo  Deal with playback speeds.
 * @todo  Add subtitles management with <track> and VTT/TTML(EBU-TT-D) files
 */
FORGE.VideoDash = function(viewer, key, config, qualityMode)
{
    /**
     * The video identifier.
     * @name FORGE.VideoDash#_key
     * @type {string}
     * @private
     */
    this._key = key;

    /**
     * {@link FORGE.VideoQuality} for this video, it can be temporarily a string into the constructor.
     * @name FORGE.VideoDash#_config
     * @type {?(string|FORGE.VideoQuality|Array<(string|FORGE.VideoQuality)>)}
     * @private
     */
    this._config = config || null;

    /**
     * The manifest URL.
     * @name FORGE.VideoDash#_manifestUrl
     * @type {string}
     * @private
     */
    this._manifestUrl = "";

    /**
     * Array of {@link FORGE.VideoQuality}.
     * @name  FORGE.VideoDash#_qualities
     * @type {Array<FORGE.VideoQuality>}
     * @private
     */
    this._qualities = null;

    /**
     * The index of the current quality (if -1 no video is selected as the current one).
     * @name  FORGE.Video#_currentQuality
     * @type {number}
     * @private
     */
    // this._currentQuality = -1;

    /**
     * First quality is initialized?
     * @name  FORGE.VideoDash#_qualityInitialized
     * @type {boolean}
     * @private
     */
    // this._qualityInitialized = false;

    /**
     * Default quality mode, it can be either "auto" or "manual", modes are listed by FORGE.VideoQualityMode constants.
     * @name FORGE.VideoDash#_defaultQualityMode
     * @type {string}
     * @private
     */
    this._defaultQualityMode = qualityMode || FORGE.VideoQualityMode.AUTO;

    /**
     * Current Quality mode.
     * @name  FORGE.VideoDash#_qualityMode
     * @type {string}
     * @private
     */
    this._qualityMode = "";

    /**
     * Video object that handle the dom and some stats about the video.
     * @name FORGE.VideoDash#_video
     * @type {Object}
     * @private
     */
    this._video = null;

    /**
     * Does the video loop?
     * @name  FORGE.VideoDash#_loop
     * @type {boolean}
     * @private
     */
    this._loop = false;

    /**
     * The volume of the video.
     * @name  FORGE.VideoDash#_volume
     * @type {number}
     * @private
     */
    this._volume = 1;

    /**
     * Is the video volume is muted?
     * @name  FORGE.VideoDash#_muted
     * @type {boolean}
     * @private
     */
    this._muted = false;

    /**
     * Private reference to the previous volume before mute.
     * @name  FORGE.VideoDash#_mutedVolume
     * @type {number}
     * @private
     */
    this._mutedVolume = 0;

    /**
     * Playback rate of the video
     * @name FORGE.VideoDash#_playbackRate
     * @type {number}
     * @private
     */
    this._playbackRate = 1;

    /**
     * Does the video have received its metaData?
     * @name  FORGE.VideoDash#_metaDataLoaded
     * @type {boolean}
     * @private
     */
    this._metaDataLoaded = false;

    /**
     * The dash.js Media Player library.<br>
     * The dash.js library must be loaded prior to the video instanciation!
     * @name  FORGE.VideoDash#_dashMediaPlayer
     * @type {dashjs.MediaPlayer}
     * @private
     */
    this._dashMediaPlayer = null;

    /**
     * The dash stream info object.
     * @name  FORGE.VideoDash#_streamInfo
     * @type {StreamInfo}
     * @private
     */
    this._streamInfo = null;

    /**
     * The current index for video.
     * @name FORGE.VideoDash#_currentIndex
     * @type {number}
     * @private
     */
    this._currentVideoIndex = 0;

    /**
     * The current pending index for video.
     * @name FORGE.VideoDash#_currentPendingIndex
     * @type {number}
     * @private
     */
    this._currentVideoPendingIndex = 0;

    /**
     * The current index for audio.
     * @name FORGE.VideoDash#_currentIndex
     * @type {number}
     * @private
     */
    this._currentAudioIndex = 0;

    /**
     * The current pending index for audio.
     * @name FORGE.VideoDash#_currentPendingIndex
     * @type {number}
     * @private
     */
    this._currentAudioPendingIndex = 0;

    /**
     * The dash media player video metrics object.
     * @name  FORGE.VideoDash#_playerVideoMetrics
     * @type {Object}
     * @private
     */
    this._playerVideoMetrics = null;

    /**
     * The dash media player audio metrics object.
     * @name  FORGE.VideoDash#_playerAudioMetrics
     * @type {Object}
     * @private
     */
    this._playerAudioMetrics = null;

    /**
     * The dash stream metrics object.
     * @name  FORGE.VideoDash#_dashMetrics
     * @type {DashMetrics}
     * @private
     */
    this._dashMetrics = null;

    /**
     * The monitoring object.
     * @name  FORGE.VideoDash#_monitoring
     * @type {Object}
     * @private
     */
    this._monitoring = null;

    /**
     * On load start event dispatcher.
     * @name  FORGE.VideoDash#_onLoadStart
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onLoadStart = null;

    /**
     * On loaded metadata event dispatcher.
     * @name  FORGE.VideoDash#_onLoadedMetaData
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onLoadedMetaData = null;

    /**
     * On loaded data event dispatcher.
     * @name  FORGE.VideoDash#_onLoadedData
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onLoadedData = null;

    /**
     * On progress event dispatcher.
     * @name  FORGE.VideoDash#_onProgress
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onProgress = null;

    /**
     * On duration change event dispatcher.
     * @name  FORGE.VideoDash#_onDurationChange
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onDurationChange = null;

    /**
     * On can play event dispatcher.
     * @name  FORGE.VideoDash#_onCanPlay
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onCanPlay = null;

    /**
     * On can play through event dispatcher.
     * @name  FORGE.VideoDash#_onCanPlayThrough
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onCanPlayThrough = null;

    /**
     * On play event dispatcher.
     * @name  FORGE.VideoDash#_onPlay
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onPlay = null;

    /**
     * On pause event dispatcher.
     * @name  FORGE.VideoDash#_onPause
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onPause = null;

    /**
     * On time update event dispatcher.
     * @name  FORGE.VideoDash#_onTimeUpdate
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onTimeUpdate = null;

    /**
     * On current time change event dispatcher.
     * @name FORGE.VideoDash#_onCurrentTimeChange
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onCurrentTimeChange = null;

    /**
     * On volume change event dispatcher.
     * @name  FORGE.VideoDash#_onVolumeChange
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onVolumeChange = null;

    /**
     * On seeking event dispatcher.
     * @name  FORGE.VideoDash#_onSeeking
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onSeeking = null;

    /**
     * On seeked event dispatcher.
     * @name  FORGE.VideoDash#_onSeeked
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onSeeked = null;

    /**
     * On ended event dispatcher.
     * @name  FORGE.VideoDash#_onEnded
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onEnded = null;

    /**
     * On error event dispatcher.
     * @name  FORGE.VideoDash#_onError
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onError = null;

    /**
     * On stalled event dispatcher.
     * @name  FORGE.VideoDash#_onStalled
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onStalled = null;

    /**
     * On rate change event dispatcher.
     * @name  FORGE.VideoDash#_onRateChange
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onRateChange = null;

    /**
     * On playing event dispatcher.
     * @name  FORGE.VideoDash#_onPlaying
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onPlaying = null;

    /**
     * On mute event dispatcher.
     * @name  FORGE.VideoDash#_onMute
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onMute = null;

    /**
     * On unmute event dispatcher.
     * @name  FORGE.VideoDash#_onUnmute
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onUnmute = null;

    /**
     * On qualityRequest event dispatcher.
     * @name  FORGE.VideoDash#_onQualityRequest
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onQualityRequest = null;

    /**
     * On qualityChange event dispatcher.
     * @name  FORGE.VideoDash#_onQualityChange
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onQualityChange = null;

    /**
     * On qualityAbort event dispatcher.
     * @name  FORGE.VideoDash#_onQualityAbort
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onQualityAbort = null;

    /**
     * On qualties loaded event dispatcher.
     * @name  FORGE.VideoDash#_onQualitiesLoaded
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onQualitiesLoaded = null;

    /**
     * On qualityModeChange event dispatcher.
     * @name  FORGE.VideoDash#_onQualityModeChange
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onQualityModeChange = null;

    /**
     * On metrics changed event dispatcher.
     * @name  FORGE.VideoDash#_onMetricsChanged
     * @type {?FORGE.EventDispatcher}
     * @private
     */
    this._onMetricsChanged = null;

    /**
     * Event handler for current video load start binded to this.
     * @name FORGE.VideoDash#_onLoadStartBind
     * @type {Function}
     * @private
     */
    this._onLoadStartBind = null;

    /**
     * Event handler for current video loaded meta data binded to this.
     * @name FORGE.VideoDash#_onLoadedMetaDataBind
     * @type {Function}
     * @private
     */
    this._onLoadedMetaDataBind = null;

    /**
     * Event handler for current video loaded data binded to this.
     * @name FORGE.VideoDash#_onLoadedDataBind
     * @type {Function}
     * @private
     */
    this._onLoadedDataBind = null;

    /**
     * Event handler for current video progress binded to this.
     * @name FORGE.VideoDash#_onProgressBind
     * @type {Function}
     * @private
     */
    this._onProgressBind = null;

    /**
     * Event handler for current video duration change binded to this.
     * @name FORGE.VideoDash#_onDurationChangeBind
     * @type {Function}
     * @private
     */
    this._onDurationChangeBind = null;

    /**
     * Event handler for current video can play binded to this.
     * @name FORGE.VideoDash#_onCanPlayBind
     * @type {Function}
     * @private
     */
    this._onCanPlayBind = null;

    /**
     * Event handler for current video can play throught binded to this.
     * @name FORGE.VideoDash#_onCanPlayThroughBind
     * @type {Function}
     * @private
     */
    this._onCanPlayThroughBind = null;

    /**
     * Event handler for current video play binded to this.
     * @name FORGE.VideoDash#_onPlayBind
     * @type {Function}
     * @private
     */
    this._onPlayBind = null;

    /**
     * Event handler for current video pause binded to this.
     * @name FORGE.VideoDash#_onPauseBind
     * @type {Function}
     * @private
     */
    this._onPauseBind = null;

    /**
     * Event handler for current video time update binded to this.
     * @name FORGE.VideoDash#_onTimeUpdateBind
     * @type {Function}
     * @private
     */
    this._onTimeUpdateBind = null;

    /**
     * Event handler for current video volume change binded to this.
     * @name FORGE.VideoDash#_onVolumeChangeBind
     * @type {Function}
     * @private
     */
    this._onVolumeChangeBind = null;

    /**
     * Event handler for current video seeking binded to this.
     * @name FORGE.VideoDash#_onSeekingBind
     * @type {Function}
     * @private
     */
    this._onSeekingBind = null;

    /**
     * Event handler for current video seeked binded to this.
     * @name FORGE.VideoDash#_onSeekedBind
     * @type {Function}
     * @private
     */
    this._onSeekedBind = null;

    /**
     * Event handler for current video ended binded to this.
     * @name FORGE.VideoDash#_onEndedBind
     * @type {Function}
     * @private
     */
    this._onEndedBind = null;

    /**
     * Event handler for current video error binded to this.
     * @name FORGE.VideoDash#_onErrorBind
     * @type {Function}
     * @private
     */
    this._onErrorBind = null;

    /**
     * Event handler for current video stalled binded to this.
     * @name FORGE.VideoDash#_onStalledBind
     * @type {Function}
     * @private
     */
    this._onStalledBind = null;

    /**
     * Event handler for current video rate change binded to this.
     * @name FORGE.VideoDash#_onRateChangeBind
     * @type {Function}
     * @private
     */
    this._onRateChangeBind = null;

    /**
     * Event handler for current video playing binded to this.
     * @name FORGE.VideoDash#_onPlayingBind
     * @type {Function}
     * @private
     */
    this._onPlayingBind = null;

    /**
     * Event handler for current video quality request binded to this.
     * @name  FORGE.VideoDash#_onQualityRequestBind
     * @type {Function}
     * @private
     */
    this._onQualityRequestBind = null;

    /**
     * Event handler for current video quality change binded to this.
     * @name  FORGE.VideoDash#_onQualityChangeBind
     * @type {Function}
     * @private
     */
    this._onQualityChangeBind = null;

    /**
     * Event handler for current video quality change aborted binded to this.
     * @name  FORGE.VideoDash#_onQualityAbortBind
     * @type {Function}
     * @private
     */
    //this._onQualityAbortBind = null;

    /**
     * Event handler for stream switch completed.
     * @name  FORGE.VideoDash#_onSwitchCompletedBind
     * @type {Function}
     * @private
     */
    this._onSwitchCompletedBind = null;

    /**
     * Event handler for metrics changed.
     * @name  FORGE.VideoDash#_onMetricsChangedBind
     * @type {Function}
     * @private
     */
    this._onMetricsChangedBind = null;

    FORGE.VideoBase.call(this, viewer, "VideoDash");
};

FORGE.VideoDash.prototype = Object.create(FORGE.VideoBase.prototype);
FORGE.VideoDash.prototype.constructor = FORGE.VideoDash;

/**
 * Media types list.
 * @name FORGE.VideoDash.mediaType
 * @type {Object}
 * @const
 */
FORGE.VideoDash.mediaType = {};

/**
 * @name FORGE.VideoDash.mediaType.VIDEO
 * @type {string}
 * @const
 */
FORGE.VideoDash.mediaType.VIDEO = "video";

/**
 * @name FORGE.VideoDash.mediaType.AUDIO
 * @type {string}
 * @const
 */
FORGE.VideoDash.mediaType.AUDIO = "audio";

/**
 * Boot sequence.
 * @method FORGE.VideoDash#_boot
 * @private
 */
FORGE.VideoDash.prototype._boot = function()
{
    FORGE.VideoBase.prototype._boot.call(this);

    if (typeof dashjs === "undefined")
    {
        throw "FORGE.VideoDash._boot: Can't create MPEG-DASH video stream without dash.js Media Player.";
    }

    //register the uid
    this._uid = this._key;
    this._register();

    this._onLoadStartBind = this._onLoadStartHandler.bind(this);
    this._onDurationChangeBind = this._onDurationChangeHandler.bind(this);
    this._onLoadedMetaDataBind = this._onLoadedMetaDataHandler.bind(this);
    this._onLoadedDataBind = this._onLoadedDataHandler.bind(this);
    this._onProgressBind = this._onProgressHandler.bind(this);
    this._onCanPlayBind = this._onCanPlayHandler.bind(this);
    this._onCanPlayThroughBind = this._onCanPlayThroughHandler.bind(this);
    this._onPlayBind = this._onPlayHandler.bind(this);
    this._onPauseBind = this._onPauseHandler.bind(this);
    this._onTimeUpdateBind = this._onTimeUpdateHandler.bind(this);
    this._onVolumeChangeBind = this._onVolumeChangeHandler.bind(this);
    this._onSeekingBind = this._onSeekingHandler.bind(this);
    this._onSeekedBind = this._onSeekedHandler.bind(this);
    this._onEndedBind = this._onEndedHandler.bind(this);
    this._onErrorBind = this._onErrorHandler.bind(this);
    this._onStalledBind = this._onStalledHandler.bind(this);
    this._onRateChangeBind = this._onRateChangeHandler.bind(this);
    this._onPlayingBind = this._onPlayingHandler.bind(this);
    this._onQualityRequestBind = this._onQualityRequestHandler.bind(this);
    this._onQualityChangeBind = this._onQualityChangeHandler.bind(this);
    //this._onQualityAbortBind = this._onQualityAbortHandler.bind(this);

    // DASH specific
    this._onSwitchCompletedBind = this._onSwitchCompletedHandler.bind(this);
    this._onMetricsChangedBind = this._onMetricsChangedHandler.bind(this);

    //Listen to the main volume change to adapt the video volume accordingly.
    this._viewer.audio.onVolumeChange.add(this._mainVolumeChangeHandler, this);

    //Listen to the enabled state of the sound manager.
    this._viewer.audio.onDisable.add(this._disableSoundHandler, this);

    //force the creation of "onQualitiesLoaded" event dispatcher and memorize it's data
    this._onQualitiesLoaded = new FORGE.EventDispatcher(this, true);

    if (this._config !== null)
    {
        this.load(this._config);
    }

    this._viewer.display.register(this);
    this._notifyReady();
    this._applyPending(false);
};

/**
 * Parse the video configuration object.
 * @method FORGE.VideoDash#_parseConfig
 * @private
 * @param  {?(string|FORGE.VideoQuality|Array<(string|FORGE.VideoQuality)>)} config - The config object to parse.
 * @return {string} Returns the manifest url for DASH.
 */
FORGE.VideoDash.prototype._parseConfig = function(config)
{
    if (config !== null)
    {
        if (FORGE.Utils.isTypeOf(config, "string") === true)
        {
            config = [config];
        }
        else if (FORGE.Utils.isTypeOf(config, "VideoQuality") === true)
        {
            config = [config];
        }

        if (FORGE.Utils.isArrayOf(config, "string") === true)
        {
            this._manifestUrl = config[0];
        }
        else if (FORGE.Utils.isArrayOf(config, "VideoQuality") === true)
        {
            this._manifestUrl = config[0].url;
        }
    }

    return this._manifestUrl;
};

/**
 * Notify that the dispay object has been resized.<br>
 * This method ovverrides the {@link FORGE.DisplayObject} method.
 * @method  FORGE.VideoDash#_notifyResize
 * @private
 * @param  {PropertyToUpdate} data - The data contains the property that have changed.
 */
FORGE.VideoDash.prototype._notifyResize = function(data)
{
    var video = this._video;

    if (video !== null && video.element !== null)
    {
        video.element.setAttribute("width", this.pixelWidth);
        video.element.setAttribute("height", this.pixelHeight);
    }

    FORGE.DisplayObject.prototype._notifyResize.call(this, data);
};

/**
 * Create placeholders objects for videos and theirs attributes.
 * @method FORGE.VideoDash#_createVideoObject
 * @private
 * @return {Object} Returns the populated videos array.
 */
FORGE.VideoDash.prototype._createVideoObject = function()
{
    var video = {
        index: 0,
        element: null,
        buffer: null,
        played: null
    };

    this._video = video;

    return video;
};

/**
 * Populate a video object with an element <video> and a buffer/played {@link FORGE.VideoTimeRanges} managers.
 * @method  FORGE.VideoDash#_createVideoTag
 * @private
 * @return {Object} Returns the populated video object.
 */
FORGE.VideoDash.prototype._createVideoTag = function()
{
    //Create a video tag and get the quality
    var element = document.createElement("video");
    element.setAttribute("webkit-playsinline", "webkit-playsinline");
    element.setAttribute("width", this.pixelWidth);
    element.setAttribute("height", this.pixelHeight);
    element.volume = 0;
    element.playbackRate = this._playbackRate;
    element.crossOrigin = "anonymous";
    element.id = "FORGE-VideoDash-" + this._uid;

    var buffer = new FORGE.VideoTimeRanges(element, "buffered");
    var played = new FORGE.VideoTimeRanges(element, "played");

    //Update the video object with the element and fresh buffer and played
    var video = this._video;
    video.element = element;
    video.buffer = buffer;
    video.played = played;
    video.lastTimeStamp = 0;

    return video;
};

/**
 * Get a property of the video element of the current video object.
 * @method  FORGE.VideoDash#_getCurrentVideoElementProperty
 * @param  {string} property - The property you want to get from the current video element.
 * @param  {*} defaultReturnValue - The default return value if the video object or its element is null.
 * @return {*} Return the requested property value or the default one if necessary.
 * @private
 */
FORGE.VideoDash.prototype._getCurrentVideoElementProperty = function(property, defaultReturnValue)
{
    var video = this._video;

    if (video !== null && video.element !== null)
    {
        return video.element[property];
    }

    return defaultReturnValue;
};

/**
 * Create instance of the dash.js Media Player.
 * @method FORGE.VideoDash#_instanciateDashMediaPlayer
 * @private
 */
FORGE.VideoDash.prototype._initDashMediaPlayer = function()
{
    // create the Media Player
    this._dashMediaPlayer = dashjs.MediaPlayer().create();
    //deactivate logs
    this._dashMediaPlayer.getDebug().setLogToBrowserConsole(false);
    //init media player
    this._dashMediaPlayer.initialize();
    // playback is paused on start
    this._dashMediaPlayer.setAutoPlay(false);
    //keep downloading fragments in the background when paused
    this._dashMediaPlayer.setScheduleWhilePaused(true);
    //fast switch with ABR
    this._dashMediaPlayer.setFastSwitchEnabled(true);
    // add video tag element and video source file
    this._dashMediaPlayer.attachView(this._video.element);
    this._dashMediaPlayer.attachSource(this._manifestUrl);

    //add events if element and source are defined
    if (this._dashMediaPlayer.isReady() === true)
    {
        this._installEvents();

        this._addVideoToDom();
    }

    if (this._qualityMode === "")
    {
        this._setQualityMode(this._defaultQualityMode);
    }
};

/**
 * Add video element to the DOM.
 * @method FORGE.VideoDash#_addVideoToDom
 * @private
 */
FORGE.VideoDash.prototype._addVideoToDom = function()
{
    // Remove video tag from our container div
    this._dom.innerHTML = "";

    this._dom.appendChild(this._video.element);
};

/**
 * Bind native events handler for the current video.
 * @method FORGE.VideoDash#_installEvents
 * @private
 */
FORGE.VideoDash.prototype._installEvents = function()
{
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["CAN_PLAY"], this._onCanPlayBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["ERROR"], this._onErrorBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_ENDED"], this._onEndedBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_ERROR"], this._onErrorBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_METADATA_LOADED"], this._onLoadedMetaDataBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_PAUSED"], this._onPauseBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_PROGRESS"], this._onProgressBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_SEEKING"], this._onSeekingBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_SEEKED"], this._onSeekedBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_STARTED"], this._onPlayBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_TIME_UPDATED"], this._onTimeUpdateBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["QUALITY_CHANGE_RENDERED"], this._onQualityChangeBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["QUALITY_CHANGE_REQUESTED"], this._onQualityRequestBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["STREAM_INITIALIZED"], this._onLoadStartBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_PLAYING"], this._onPlayingBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["BUFFER_EMPTY"], this._onStalledBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PLAYBACK_RATE_CHANGED"], this._onRateChangeBind);

    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["PERIOD_SWITCH_COMPLETED"], this._onSwitchCompletedBind);
    this._dashMediaPlayer.on(dashjs.MediaPlayer.events["METRIC_CHANGED"], this._onMetricsChangedBind);

    this._video.element.addEventListener("loadeddata", this._onLoadedDataBind, false);
    this._video.element.addEventListener("durationchange", this._onDurationChangeBind, false);
    this._video.element.addEventListener("canplaythrough", this._onCanPlayThroughBind, false);
    this._video.element.addEventListener("volumechange", this._onVolumeChangeBind, false);
};

/**
 * Unbind events handler for video.
 * @method FORGE.VideoDash#_uninstallEvents
 * @private
 */
FORGE.VideoDash.prototype._uninstallEvents = function()
{
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["CAN_PLAY"], this._onCanPlayBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["ERROR"], this._onErrorBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_ENDED"], this._onEndedBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_ERROR"], this._onErrorBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_METADATA_LOADED"], this._onLoadedMetaDataBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_PAUSED"], this._onPauseBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_PROGRESS"], this._onProgressBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_SEEKING"], this._onSeekingBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_SEEKED"], this._onSeekedBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_STARTED"], this._onPlayBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_TIME_UPDATED"], this._onTimeUpdateBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["QUALITY_CHANGE_RENDERED"], this._onQualityChangeBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["QUALITY_CHANGE_REQUESTED"], this._onQualityRequestBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["STREAM_INITIALIZED"], this._onLoadStartBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_PLAYING"], this._onPlayingBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["BUFFER_EMPTY"], this._onStalledBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PLAYBACK_RATE_CHANGED"], this._onRateChangeBind);

    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["PERIOD_SWITCH_COMPLETED"], this._onSwitchCompletedBind);
    this._dashMediaPlayer.off(dashjs.MediaPlayer.events["METRIC_CHANGED"], this._onMetricsChangedBind);

    this._video.element.removeEventListener("loadeddata", this._onLoadedDataBind, false);
    this._video.element.removeEventListener("durationchange", this._onDurationChangeBind, false);
    this._video.element.removeEventListener("canplaythrough", this._onCanPlayThroughBind, false);
    this._video.element.removeEventListener("volumechange", this._onVolumeChangeBind, false);
};

/**
 * Private event handler for loadStart.
 * @method  FORGE.VideoDash#_onLoadStartHandler
 * @param  {Event} event - The native video event.
 * @private
 */
FORGE.VideoDash.prototype._onLoadStartHandler = function(event)
{
    var element = this._video.element;
    this.log("onLoadStart [readyState: " + element.readyState + "]");

    //populate qualities array with the MPEG-DASH bitrates info list
    this._createQualitiesFromBitrateInfoList(this._manifestUrl, this._dashMediaPlayer.getBitrateInfoListFor(FORGE.VideoDash.mediaType.VIDEO));

    if (this._onLoadStart !== null)
    {
        this._onLoadStart.dispatch(event);
    }
};

/**
 * Create a qualities array from the bitrates info list.
 * @method FORGE.VideoDash#_createQualitiesFromBitrateInfoList
 * @param  {string} url - The manifest url.
 * @param  {Array<BitrateInfo>} bitrates - The bitrates array.
 * @param  {boolean=} checkURL - URL must be checked?
 * @return {Array<FORGE.VideoQuality>} The qualities array.
 * @private
 */
FORGE.VideoDash.prototype._createQualitiesFromBitrateInfoList = function(url, bitrates, checkURL)
{
    var qualities = [];
    var quality;

    if (bitrates !== null)
    {
        for (var i = 0, ii = bitrates.length; i < ii; i++)
        {
            if (checkURL === true && FORGE.URL.isValid(url) === false)
            {
                throw "FORGE.Video: URL " + url + " is invalid";
            }

            quality = new FORGE.VideoQuality(url);
            quality.id = bitrates[i].qualityIndex;
            quality.bitrate = bitrates[i].bitrate;
            quality.width = bitrates[i].width;
            quality.height = bitrates[i].height;

            qualities.push(quality);
        }

        this._qualities = /** @type {Array<FORGE.VideoQuality>} */ (qualities);

        if (this._onQualitiesLoaded !== null)
        {
            this._onQualitiesLoaded.dispatch(qualities);
        }
    }
    else
    {
        this.destroy();
    }

    return qualities;
};

/**
 * Get the index of a {@link FORGE.VideoQuality} that is in the _videoQualities array.
 * @method  FORGE.VideoDash#_indexOfQuality
 * @private
 * @param  {FORGE.VideoQuality} quality - The quality you need to get its index.
 * @return {number} Returns the index of the quality if found, -1 if not found.
 */
FORGE.VideoDash.prototype._indexOfQuality = function(quality)
{
    var q;
    for (var i = 0, ii = this._qualities.length; i < ii; i++)
    {
        q = this._qualities[i];

        if (q === quality)
        {
            return i;
        }
    }

    return -1;
};

/**
 * Private event handler for period switch completed.
 * @method  FORGE.VideoDash#_onSwitchCompletedHandler
 * @param  {SwitchEvents} event - The dash.js media player event.
 * @private
 */
FORGE.VideoDash.prototype._onSwitchCompletedHandler = function(event)
{
    var element = this._video.element;
    this.log("_onSwitchCompleted [readyState: " + element.readyState + "]");

    this._streamInfo = /** @type {StreamInfo} */ (event.toStreamInfo);
};

/**
 * Private event handler for mertics changed.
 * @method  FORGE.VideoDash#_onMetricsChangedHandler
 * @param  {MetricChangedEvents} event - The dash.js media player event.
 * @private
 */
FORGE.VideoDash.prototype._onMetricsChangedHandler = function(event)
{
    var element = this._video.element;
    this.log("_onMetricsChangedHandler [readyState: " + element.readyState + "]");

    if (this._playerVideoMetrics === null)
    {
        this._playerVideoMetrics = this._dashMediaPlayer.getMetricsFor(FORGE.VideoDash.mediaType.VIDEO);
    }

    if (this._playerAudioMetrics === null)
    {
        this._playerAudioMetrics = this._dashMediaPlayer.getMetricsFor(FORGE.VideoDash.mediaType.AUDIO);
    }

    if (this._dashMetrics === null)
    {
        this._dashMetrics = this._dashMediaPlayer.getDashMetrics();
    }

    //set the current quality index
    // if (this._playerVideoMetrics !== null && this._dashMetrics !== null)
    // {
    //     var repSwitch = /** @type {RepresentationSwitch} */ (this._dashMetrics.getCurrentRepresentationSwitch(this._playerVideoMetrics));
    //     if (repSwitch !== null)
    //     {
    //         this._currentQuality = this._dashMetrics.getIndexForRepresentation(repSwitch.to, this._streamInfo.index);
    //         if(this._qualityInitialized === false)
    //         {
    //             this._setRequestQuality(this._currentQuality);
    //             this._qualityInitialized = true;
    //         }
    //     }
    // }

    if (this._onMetricsChanged !== null)
    {
        var metrics;

        if (event.mediaType === FORGE.VideoDash.mediaType.VIDEO)
        {
            metrics = this._getMetricsFor(FORGE.VideoDash.mediaType.VIDEO, this._playerVideoMetrics, this._dashMetrics);
            if (metrics)
            {
                this._monitoring = {
                    "videoBitrate": metrics.bitrate,
                    "videoIndex": metrics.index,
                    "videoPendingIndex": metrics.pendingIndex,
                    "videoMaxIndex": metrics.maxIndex,
                    "videoBufferLength": metrics.bufferLength,
                    "videoDroppedFrames": metrics.droppedFrames
                        //"videoRequestsQueue": metrics.requestsQueue
                };
            }
        }

        if (event.mediaType === FORGE.VideoDash.mediaType.AUDIO)
        {
            metrics = this._getMetricsFor(FORGE.VideoDash.mediaType.AUDIO, this._playerAudioMetrics, this._dashMetrics);
            if (metrics)
            {
                this._monitoring = {
                    "audioBitrate": metrics.bitrate,
                    "audioIndex": metrics.index,
                    "audioPendingIndex": metrics.pendingIndex,
                    "audioMaxIndex": metrics.maxIndex,
                    "audioBufferLength": metrics.bufferLength,
                    "audioDroppedFrames": metrics.droppedFrames
                        //"audioRequestsQueue": metrics.requestsQueue
                };
            }
        }

        this._onMetricsChanged.dispatch(event);
    }
};


/**
 * Prepare a metrics object for video/audio data.
 * @method  FORGE.VideoDash#_getMetricsFor
 * @param {string} type - The video or audio type.
 * @param {Object} metrics - The player video or audio metrics.
 * @param {DashMetrics} dashMetrics - The dash stream video or audio metrics.
 * @return {?Object} The monitoring object
 * @private
 */
FORGE.VideoDash.prototype._getMetricsFor = function(type, metrics, dashMetrics)
{
    if (metrics !== null && dashMetrics !== null)
    {
        var streamIndex = this._streamInfo.index;
        var repSwitch, maxIndex, bufferLevel, droppedFramesMetrics, bitrate, index, pendingIndex, bufferLength, droppedFrames; //httpRequests requestsQueue pendingValue

        repSwitch = /** @type {RepresentationSwitch} */ (dashMetrics.getCurrentRepresentationSwitch(metrics));
        if (repSwitch !== null)
        {
            // index = dashMetrics.getIndexForRepresentation(repSwitch.to, streamIndex);
            bitrate = Math.round(dashMetrics.getBandwidthForRepresentation(repSwitch.to, streamIndex) / 1000);
        }
        if (isNaN(bitrate) || bitrate === undefined)
        {
            bitrate = 0;
        }

        if (type === FORGE.VideoDash.mediaType.VIDEO)
        {
            index = this._currentVideoIndex + 1;
            pendingIndex = this._currentVideoPendingIndex + 1;
        }
        else if (type === FORGE.VideoDash.mediaType.AUDIO)
        {
            index = this._currentAudioIndex + 1;
            pendingIndex = this._currentAudioPendingIndex + 1;
        }

        if (isNaN(index) || index === undefined)
        {
            index = 0;
        }
        if (isNaN(pendingIndex) || pendingIndex === undefined)
        {
            pendingIndex = 0;
        }

        maxIndex = dashMetrics.getMaxIndexForBufferType(type, streamIndex);
        if (isNaN(maxIndex) || maxIndex === undefined)
        {
            maxIndex = 0;
        }

        bufferLevel = dashMetrics.getCurrentBufferLevel(metrics);
        if (bufferLevel !== null)
        {
            bufferLength = bufferLevel.toPrecision(5);
        }
        if (isNaN(bufferLength) || bufferLength === undefined)
        {
            bufferLength = 0;
        }

        //httpRequests = dashMetrics.getHttpRequests(metrics);

        droppedFramesMetrics = /** @type {DroppedFrames} */ (dashMetrics.getCurrentDroppedFrames(metrics));
        if (droppedFramesMetrics !== null)
        {
            droppedFrames = droppedFramesMetrics.droppedFrames;
        }

        //pendingValue = this._dashMediaPlayer.getQualityFor(type);

        //requestsQueue = dashMetrics.getRequestsQueue(metrics);

        var result = {
            bitrate: bitrate,
            index: index,
            pendingIndex: pendingIndex,
            maxIndex: maxIndex,
            bufferLength: bufferLength,
            droppedFrames: droppedFrames
                //requestsQueue: requestsQueue
        };

        return result;
    }

    return null;
};

/**
 * Private event handler for quality change.
 * @method  FORGE.VideoDash#_onQualityChangeHandler
 * @private
 * @param  {QualityEvents} event - The dash.js media player event.
 */
FORGE.VideoDash.prototype._onQualityChangeHandler = function(event)
{
    var element = this._video.element;
    this.log("onQualityChange [readyState: " + element.readyState + "]");

    if (event.mediaType === FORGE.VideoDash.mediaType.AUDIO)
    {
        this._currentAudioIndex = event.newQuality;
        this._currentAudioPendingIndex = event.newQuality;
    }
    else if (event.mediaType === FORGE.VideoDash.mediaType.VIDEO)
    {
        this._currentVideoIndex = event.newQuality;
        this._currentVideoPendingIndex = event.newQuality;

        if (this._onQualityChange !== null)
        {
            this._onQualityChange.dispatch(this._currentVideoPendingIndex);
        }
    }
};

/**
 * Private event handler for quality request.
 * @method  FORGE.VideoDash#_onQualityRequestHandler
 * @private
 * @param  {QualityEvents} event - The dash.js media player event.
 */
FORGE.VideoDash.prototype._onQualityRequestHandler = function(event)
{
    var element = this._video.element;
    this.log("onQualityRequest [readyState: " + element.readyState + "]");

    if (event.mediaType === FORGE.VideoDash.mediaType.AUDIO)
    {
        this._currentAudioIndex = event.oldQuality;
        this._currentAudioPendingIndex = event.newQuality;
    }
    else if (event.mediaType === FORGE.VideoDash.mediaType.VIDEO)
    {
        this._currentVideoIndex = event.oldQuality;
        this._currentVideoPendingIndex = event.newQuality;

        if (this._onQualityRequest !== null)
        {
            this._onQualityRequest.dispatch(this._currentVideoPendingIndex);
        }
    }
};

/**
 * Private event handler for quality request aborted.
 * @method  FORGE.VideoDash#_onQualityAbortHandler
 * @private
 * @param  {Event} event - The dash.js media player event.
 */
// FORGE.VideoDash.prototype._onQualityAbortHandler = function(event)
// {
//     var element = this._video.element;
//     this.log("onQualityAbort [readyState: "+element.readyState+"]");

//     if(this._onQualityAbort !== null)
//     {
//         this._onQualityAbort.dispatch(this._video.requestIndex);
//     }
// };

/**
 * Private event handler for durationChange.
 * @method  FORGE.VideoDash#_onDurationChangeHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onDurationChangeHandler = function(event)
{
    var element = this._video.element;
    this.log("onDurationChange [readyState: " + element.readyState + "]");

    //@firefox - FF disptach durationchange twice on readystate HAVE_METADATA (1) & HAVE_ENOUGH_DATA (4)
    //I will not dispatch this event if readystate is HAVE_ENOUGH_DATA (4) !
    if (this._onDurationChange !== null && element.readyState === HTMLMediaElement.HAVE_METADATA)
    {
        this._onDurationChange.dispatch(event);
    }
};

/**
 * Private event handler for loadedMetaData.
 * @method  FORGE.VideoDash#_onLoadedMetaDataHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onLoadedMetaDataHandler = function(event)
{
    var element = this._video.element;
    this.log("onLoadedMetaData [readyState: " + element.readyState + "]");

    this._metaDataLoaded = true;

    if (this._onLoadedMetaData !== null)
    {
        this._onLoadedMetaData.dispatch(event);
    }
};

/**
 * Private event handler for loadedData.
 * @method  FORGE.VideoDash#_onLoadedDataHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onLoadedDataHandler = function(event)
{
    var element = this._video.element;
    this.log("onLoadedData [readyState: " + element.readyState + "]");

    if (this._onLoadedData !== null)
    {
        this._onLoadedData.dispatch(event);
    }
};

/**
 * Private event handler for progress.
 * @method  FORGE.VideoDash#_onProgressHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onProgressHandler = function(event)
{
    //var element = this._video.element;
    //this.log("onProgress [readyState: "+element.readyState+"]");

    if (this._onProgress !== null)
    {
        this._onProgress.dispatch(event);
    }
};

/**
 * Private event handler for canPlay.
 * @method  FORGE.VideoDash#_onCanPlayHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onCanPlayHandler = function(event)
{
    var element = this._video.element;
    this.log("onCanPlay [readyState: " + element.readyState + "]");

    this._canPlay = true;

    if (this._onCanPlay !== null)
    {
        this._onCanPlay.dispatch(event);
    }
};

/**
 * Private event handler for canPlayThrough.
 * @method  FORGE.VideoDash#_onCanPlayThroughHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onCanPlayThroughHandler = function(event)
{
    var element = this._video.element;
    this.log("onCanPlayThrough [readyState: " + element.readyState + "]");

    this._canPlay = true;

    if (this._onCanPlayThrough !== null)
    {
        this._onCanPlayThrough.dispatch(event);
    }
};

/**
 * Private event handler for play.
 * @method  FORGE.VideoDash#_onPlayHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onPlayHandler = function(event)
{
    var element = this._video.element;
    this.log("onPlay [readyState: " + element.readyState + "]");

    if (this._onPlay !== null)
    {
        this._onPlay.dispatch(event);
    }
};

/**
 * Private event handler for pause.
 * @method  FORGE.VideoDash#_onPauseHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onPauseHandler = function(event)
{
    var element = this._video.element;
    this.log("onPause [readyState: " + element.readyState + "]");

    this._playing = false;

    if (this._onPause !== null)
    {
        this._onPause.dispatch(event);
    }
};

/**
 * Private event handler for timeUpdate.
 * @method  FORGE.VideoDash#_onTimeUpdateHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onTimeUpdateHandler = function(event)
{
    //var element = this._video.element;
    //this.log("onTimeUpdate [readyState: "+element.readyState+"]");

    if (this._onTimeUpdate !== null)
    {
        this._onTimeUpdate.dispatch(event);
    }
};

/**
 * Private event handler for volumeChange.
 * @method  FORGE.VideoDash#_onVolumeChangeHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onVolumeChangeHandler = function(event)
{
    var element = this._video.element;
    this.log("onVolumeChange [readyState: " + element.readyState + "]");

    //I do not dispatch the volume change if readyState is HAVE_NOTHING (0).
    //because I set the volume at 0 when I create the video element, it is not usefull to dispatch this internal volume change ?
    if (this._onVolumeChange !== null && element.readyState !== HTMLMediaElement.HAVE_NOTHING)
    {
        this._onVolumeChange.dispatch(event);
    }
};

/**
 * Private event handler for seeking.
 * @method  FORGE.VideoDash#_onSeekingHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onSeekingHandler = function(event)
{
    var element = this._video.element;
    this.log("onSeeking [readyState: " + element.readyState + "]");

    this._canPlay = false;

    if (this._onSeeking !== null)
    {
        this._onSeeking.dispatch(event);
    }
};

/**
 * Private event handler for seeked.
 * @method  FORGE.VideoDash#_onSeekedHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onSeekedHandler = function(event)
{
    var element = this._video.element;
    this.log("onSeeked [readyState: " + element.readyState + "]");

    this._canPlay = false;

    if (this._onSeeked !== null)
    {
        this._onSeeked.dispatch(event);
    }
};

/**
 * Private event handler for ended.
 * @method  FORGE.VideoDash#_onEndedHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onEndedHandler = function(event)
{
    var element = this._video.element;
    this.log("onEnded [readyState: " + element.readyState + "]");

    this._playing = false;
    //this._dom.currentTime = 0;
    this._endCount++;

    if (this._loop === true)
    {
        this.play();
    }

    if (this._onEnded !== null)
    {
        this._onEnded.dispatch(event);
    }
};

/**
 * Private event handler for error.
 * @method  FORGE.VideoDash#_onErrorHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onErrorHandler = function(event)
{
    var element = this._video.element;
    this.log("onError [readyState: " + element.readyState + "]");

    if (this._onError !== null)
    {
        this._onError.dispatch(event);
    }
};

/**
 * Private event handler for stalled.
 * @method  FORGE.VideoDash#_onStalledHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onStalledHandler = function(event)
{
    var element = this._video.element;
    this.log("onStalled [readyState: " + element.readyState + "]");

    if (this._onStalled !== null)
    {
        this._onStalled.dispatch(event);
    }
};

/**
 * Private event handler for rate change.
 * @method  FORGE.VideoDash#_onRateChangeHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onRateChangeHandler = function(event)
{
    var element = this._video.element;
    this.log("onRateChange [readyState: " + element.readyState + "]");

    if (this._onRateChange !== null)
    {
        this._onRateChange.dispatch(event);
    }
};

/**
 * Private event handler for playing.
 * @method  FORGE.VideoDash#_onPlayingHandler
 * @private
 * @param  {Event} event - The native video event.
 */
FORGE.VideoDash.prototype._onPlayingHandler = function(event)
{
    var element = this._video.element;
    this.log("onPlaying [readyState: " + element.readyState + "]");

    if (this._onPlaying !== null)
    {
        this._onPlaying.dispatch(event);
    }
};

/**
 * Destroy a video object at a specified index, wiil look at the video object into _videos array then detoy it.
 * @method FORGE.VideoDash#_destroyVideo
 * @private
 */
FORGE.VideoDash.prototype._destroyVideo = function()
{
    this.log("_destroyVideo");

    if (this._dashMediaPlayer !== null && this._dashMediaPlayer.isReady() === true)
    {
        this._uninstallEvents();

        this._dashMediaPlayer.pause();
        this._dashMediaPlayer.reset();
    }

    var video = this._video;
    var element = video.element;

    if (typeof element !== "undefined" && element !== null)
    {
        if (element.parentNode === this._dom)
        {
            this._dom.removeChild(element);
        }
    }

    element = null;
    video.element = null;

    if (video.buffer !== null)
    {
        video.buffer.destroy();
        video.buffer = null;
    }

    if (video.played !== null)
    {
        video.played.destroy();
        video.played = null;
    }

    video = null;
};

/**
 * Handles the disable status of the sound manager.
 * @method FORGE.VideoDash#_disableSoundHandler
 * @private
 */
FORGE.VideoDash.prototype._disableSoundHandler = function()
{
    var v = this._video;

    if (v !== null && v.element !== null && this._viewer.audio.enabled === false)
    {
        this._dashMediaPlayer.setVolume(0);
    }
};

/**
 * Handles the main volume change, update the volume factor to the video volume.
 * @method FORGE.VideoDash#_mainVolumeChangeHandler
 * @private
 */
FORGE.VideoDash.prototype._mainVolumeChangeHandler = function()
{
    this._updateVolume();
};

/**
 * Apply the main volume factor to the video volume.
 * @method FORGE.VideoDash#_updateVolume
 * @private
 */
FORGE.VideoDash.prototype._updateVolume = function()
{
    var v = this._video;

    if (v !== null && v.element !== null && this._viewer.audio.enabled === true)
    {
        this._dashMediaPlayer.setVolume(this._volume * this._viewer.audio.volume);
        if (this._muted === true)
        {
            this._dashMediaPlayer.setMute(true);
        }
        else
        {
            this._dashMediaPlayer.setMute(false);
        }
    }
};

/**
 * Apply the requested quality index.
 * @method FORGE.VideoDash#_setRequestQuality
 * @param {number} index - The quality index.
 * @private
 */
FORGE.VideoDash.prototype._setRequestQuality = function(index)
{
    this._currentVideoPendingIndex = index;

    if (this._streamInfo !== null && this._dashMediaPlayer !== null && this._dashMediaPlayer.isReady() === true && this._dashMetrics !== null)
    {
        var streamIndex = this._streamInfo.index;
        var maxVideoIndex = this._dashMetrics.getMaxIndexForBufferType(FORGE.VideoDash.mediaType.VIDEO, streamIndex);
        var maxAudioIndex = this._dashMetrics.getMaxIndexForBufferType(FORGE.VideoDash.mediaType.AUDIO, streamIndex);

        var newVideoIndex = index,
            newAudioIndex = index;

        // zero based indexes
        if (newVideoIndex >= maxVideoIndex)
        {
            newVideoIndex = maxVideoIndex - 1;
        }
        if (newVideoIndex < 0)
        {
            newVideoIndex = 0;
        }
        if (newAudioIndex >= maxAudioIndex)
        {
            newAudioIndex = maxAudioIndex - 1;
        }
        if (newAudioIndex < 0)
        {
            newAudioIndex = 0;
        }

        this._dashMediaPlayer.setQualityFor(FORGE.VideoDash.mediaType.VIDEO, newVideoIndex);
        this._dashMediaPlayer.setQualityFor(FORGE.VideoDash.mediaType.AUDIO, newAudioIndex);
    }

    // Update the volume for the requested video
    this._updateVolume();
};

/**
 * Sets the quality mode.<br>
 * This activate or deactivate the adaptative bitrate switching for video and audio (ABR).
 * @method  FORGE.VideoDash#_setQualityMode
 * @private
 * @param {string} mode - Quality mode to be set.
 */
FORGE.VideoDash.prototype._setQualityMode = function(mode)
{
    if (this._qualityMode === mode)
    {
        return;
    }

    if (mode === FORGE.VideoQualityMode.AUTO || mode === FORGE.VideoQualityMode.MANUAL)
    {
        this._qualityMode = mode;

        if (this._dashMediaPlayer !== null && this._dashMediaPlayer.isReady() === true)
        {
            if (mode === FORGE.VideoQualityMode.MANUAL)
            {
                this._dashMediaPlayer.setAutoSwitchQualityFor(FORGE.VideoDash.mediaType.VIDEO, false);
                this._dashMediaPlayer.setAutoSwitchQualityFor(FORGE.VideoDash.mediaType.AUDIO, false);
            }
            else
            {
                this._dashMediaPlayer.setAutoSwitchQualityFor(FORGE.VideoDash.mediaType.VIDEO, true);
                this._dashMediaPlayer.setAutoSwitchQualityFor(FORGE.VideoDash.mediaType.AUDIO, true);
            }
        }

        if (this._onQualityModeChange !== null)
        {
            this._onQualityModeChange.dispatch(this._qualityMode);
        }
    }
};

/**
 * Load a config or a video url to the source.
 * @method FORGE.VideoDash#load
 * @param {?(string|FORGE.VideoQuality|Array<(string|FORGE.VideoQuality)>)} config - The {@link FORGE.VideoQuality} or the url string to load.
 */
FORGE.VideoDash.prototype.load = function(config)
{
    //parse the config in parameters
    this._parseConfig(config);

    if (this._manifestUrl === "")
    {
        throw "FORGE.VideoDash : Can't create video with no manifest!";
    }

    //Create place holders for video and video element
    this._createVideoObject();
    this._createVideoTag();

    this._initDashMediaPlayer();
};

/**
 * Plays the video.
 * @method  FORGE.VideoDash#play
 * @param {number=} time - Current video time to start playback.
 * @param {boolean=} loop - Media must be looped?
 */
FORGE.VideoDash.prototype.play = function(time, loop)
{
    FORGE.VideoBase.prototype.play.call(this, time, loop);

    if (this._dashMediaPlayer !== null && this._dashMediaPlayer.isReady() === true)
    {
        this._dashMediaPlayer.play();
        this._playing = true;
        this._paused = false;
        this._playCount++;
    }
};

/**
 * Pauses the video.
 * @method  FORGE.VideoDash#pause
 */
FORGE.VideoDash.prototype.pause = function()
{
    if (this._dashMediaPlayer !== null && this._dashMediaPlayer.isReady() === true)
    {
        this._dashMediaPlayer.pause();
        this._playing = false;
        this._paused = true;
    }
};

/**
 * Stop the video the video (pause it and set time to 0).
 * @method  FORGE.VideoDash#stop
 */
FORGE.VideoDash.prototype.stop = function()
{
    if (this._dashMediaPlayer !== null && this._dashMediaPlayer.isReady() === true)
    {
        this._dashMediaPlayer.pause();
        this._dashMediaPlayer.seek(0);
        this._video.element.currentTime = 0;
        this._playing = false;
        this._paused = true;
    }
};

/**
 * Toggles the playback status, if play toggle to pause and vice versa.
 * @method  FORGE.VideoDash#togglePlayback
 */
FORGE.VideoDash.prototype.togglePlayback = function()
{
    if (this._playing === true || this._viewer.audio.enabled === false)
    {
        this.pause();
    }
    else
    {
        this.play();
    }
};

/**
 * Mute the video sound.
 * @method  FORGE.VideoDash#mute
 */
FORGE.VideoDash.prototype.mute = function()
{
    if (this._muted === true || this._viewer.audio.enabled === false)
    {
        return;
    }

    this._muted = true;
    this._mutedVolume = this._volume;

    this._volume = 0;
    this._updateVolume();

    if (this._onMute !== null)
    {
        this._onMute.dispatch();
    }
};

/**
 * Unmute the video sound.
 * @method  FORGE.VideoDash#unmute
 * @param {number=} volume - The volume to be restored on unmute.
 */
FORGE.VideoDash.prototype.unmute = function(volume)
{
    if (this._muted === false)
    {
        return;
    }

    var v = (typeof volume === "number") ? volume : this._mutedVolume;

    this._muted = false;
    this._volume = FORGE.Math.clamp(v, 0, 1);
    this._updateVolume();

    if (this._onUnmute !== null)
    {
        this._onUnmute.dispatch();
    }
};

/**
 * Enable/Disable the buffer-occupancy-based (BOLA) ABR rule set.<br>
 * BOLA white paper: http://arxiv.org/abs/1601.06748.
 * @method  FORGE.VideoDash#enableBufferOccupancyABR
 * @param {boolean} bool - Is the BOLA rule set is activated?
 */
FORGE.VideoDash.prototype.enableBufferOccupancyABR = function(bool)
{
    if (this._dashMediaPlayer !== null && this._dashMediaPlayer.isReady() === true)
    {
        this._dashMediaPlayer.enableBufferOccupancyABR(bool);
    }
};

/**
 * Destroy method.
 * @method FORGE.VideoDash#destroy
 */
FORGE.VideoDash.prototype.destroy = function()
{
    this._destroyVideo();

    if (this._onLoadStart !== null)
    {
        this._onLoadStart.destroy();
        this._onLoadStart = null;
    }

    if (this._onLoadedMetaData !== null)
    {
        this._onLoadedMetaData.destroy();
        this._onLoadedMetaData = null;
    }

    if (this._onLoadedData !== null)
    {
        this._onLoadedData.destroy();
        this._onLoadedData = null;
    }

    if (this._onProgress !== null)
    {
        this._onProgress.destroy();
        this._onProgress = null;
    }

    if (this._onDurationChange !== null)
    {
        this._onDurationChange.destroy();
        this._onDurationChange = null;
    }

    if (this._onCanPlay !== null)
    {
        this._onCanPlay.destroy();
        this._onCanPlay = null;
    }

    if (this._onCanPlayThrough !== null)
    {
        this._onCanPlayThrough.destroy();
        this._onCanPlayThrough = null;
    }

    if (this._onPlay !== null)
    {
        this._onPlay.destroy();
        this._onPlay = null;
    }

    if (this._onPause !== null)
    {
        this._onPause.destroy();
        this._onPause = null;
    }

    if (this._onTimeUpdate !== null)
    {
        this._onTimeUpdate.destroy();
        this._onTimeUpdate = null;
    }

    if (this._onCurrentTimeChange !== null)
    {
        this._onCurrentTimeChange.destroy();
        this._onCurrentTimeChange = null;
    }

    if (this._onVolumeChange !== null)
    {
        this._onVolumeChange.destroy();
        this._onVolumeChange = null;
    }

    if (this._onSeeking !== null)
    {
        this._onSeeking.destroy();
        this._onSeeking = null;
    }

    if (this._onSeeked !== null)
    {
        this._onSeeked.destroy();
        this._onSeeked = null;
    }

    if (this._onEnded !== null)
    {
        this._onEnded.destroy();
        this._onEnded = null;
    }

    if (this._onError !== null)
    {
        this._onError.destroy();
        this._onError = null;
    }

    if (this._onStalled !== null)
    {
        this._onStalled.destroy();
        this._onStalled = null;
    }

    if (this._onRateChange !== null)
    {
        this._onRateChange.destroy();
        this._onRateChange = null;
    }

    if (this._onPlaying !== null)
    {
        this._onPlaying.destroy();
        this._onPlaying = null;
    }

    if (this._onQualityRequest !== null)
    {
        this._onQualityRequest.destroy();
        this._onQualityRequest = null;
    }

    if (this._onQualityChange !== null)
    {
        this._onQualityChange.destroy();
        this._onQualityChange = null;
    }

    if (this._onQualityAbort !== null)
    {
        this._onQualityAbort.destroy();
        this._onQualityAbort = null;
    }

    if (this._onQualitiesLoaded !== null)
    {
        this._onQualitiesLoaded.destroy();
        this._onQualitiesLoaded = null;
    }

    if (this._onQualityModeChange !== null)
    {
        this._onQualityModeChange.destroy();
        this._onQualityModeChange = null;
    }

    if (this._onMetricsChanged !== null)
    {
        this._onMetricsChanged.destroy();
        this._onMetricsChanged = null;
    }

    this._onLoadStartBind = null;
    this._onLoadedMetaDataBind = null;
    this._onLoadedDataBind = null;
    this._onProgressBind = null;
    this._onDurationChangeBind = null;
    this._onCanPlayBind = null;
    this._onCanPlayThroughBind = null;
    this._onPlayBind = null;
    this._onPauseBind = null;
    this._onTimeUpdateBind = null;
    this._onVolumeChangeBind = null;
    this._onSeekingBind = null;
    this._onSeekedBind = null;
    this._onEndedBind = null;
    this._onErrorBind = null;
    this._onPlayingBind = null;
    this._onStalledBind = null;
    this._onRateChangeBind = null;
    this._onQualityRequestBind = null;
    this._onQualityChangeBind = null;
    //this._onQualityAbortBind = null;
    this._onMetricsChangedBind = null;

    //Unbind main volume event
    this._viewer.audio.onVolumeChange.remove(this._mainVolumeChangeHandler, this);

    this._viewer.audio.onDisable.remove(this._disableSoundHandler, this);

    this._dashMediaPlayer = null;
    this._playerVideoMetrics = null;
    this._playerAudioMetrics = null;
    this._dashMetrics = null;

    this._monitoring = null;
    this._streamInfo = null;

    this._config = null;

    this._qualities = null;

    this._video = null;

    FORGE.VideoBase.prototype.destroy.call(this);
};

/**
 * Get the monitoring object related to the video.
 * @name FORGE.VideoDash#monitoring
 * @type {Object}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "monitoring",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._monitoring;
    }
});

/**
 * Get and set the quality index of the video.
 * @name FORGE.VideoDash#quality
 * @type {(number|FORGE.VideoQuality)}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "quality",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._qualities[this._currentVideoIndex];
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        if (typeof value === "number" && value >= 0 && value < this._qualities.length)
        {
            this._setQualityMode(FORGE.VideoQualityMode.MANUAL);
            this._setRequestQuality(value);
        }
        else if (FORGE.Utils.isTypeOf(value, "VideoQuality") === true)
        {
            var i = this._indexOfQuality(value);

            if (i !== -1)
            {
                this._setQualityMode(FORGE.VideoQualityMode.MANUAL);
                this._setRequestQuality(i);
            }
            else
            {
                throw "Unknown quality";
            }
        }
        else
        {
            throw "Video quality " + value + " out of bounds";
        }
    }
});

/**
 * Get and set the quality mode.<br>
 * Available quality mode are listed in FORGE.VideoQualityMode const.
 * @name  FORGE.VideoDash#qualityMode
 * @type {string}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "qualityMode",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._qualityMode;
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        if (value === FORGE.VideoQualityMode.AUTO || value === FORGE.VideoQualityMode.MANUAL)
        {
            this._setQualityMode(value);
        }
    }
});

/**
 * Get the quality array.
 * @name  FORGE.VideoDash#qualities
 * @readonly
 * @type {Array<FORGE.VideoQuality>}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "qualities",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._qualities;
    }
});

/**
 * Get the requested quality index, returns -1 if no request is being processed.
 * @name  FORGE.VideoDash#requestIndex
 * @readonly
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "requestIndex",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._currentVideoPendingIndex;
    }
});

/**
 * Get the current quality index, returns -1 if no current is playing.
 * @name  FORGE.VideoDash#currentIndex
 * @readonly
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "currentIndex",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._currentVideoIndex;
    }
});

/**
 * Get the video object array.
 * @name  FORGE.VideoDash#video
 * @readonly
 * @type {Object}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "video",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._video;
    }
});

/**
 * Get the html element of the current video.
 * @name FORGE.VideoDash#element
 * @readonly
 * @type {?HTMLVideoElement}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "element",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        var video = this._video;

        if (video !== null && video.element !== null)
        {
            return video.element;
        }

        return null;
    }
});

/**
 * Get the {@link FORGE.VideoTimeRanges} of the video for buffered ranges.
 * @name FORGE.VideoDash#buffer
 * @readonly
 * @type {?FORGE.VideoTimeRanges}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "buffer",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        var video = this._video;

        if (video !== null && video.buffer !== null)
        {
            return video.buffer;
        }

        return null;
    }
});

/**
 * Get the {@link FORGE.VideoTimeRanges} of the video for played ranges.
 * @name FORGE.VideoDash#played
 * @readonly
 * @type {?FORGE.VideoTimeRanges}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "played",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        var video = this._video;

        if (video !== null && video.played !== null)
        {
            return video.played;
        }

        return null;
    }
});

/**
 * Get the original width of the video source.
 * @name FORGE.VideoDash#originalWidth
 * @readonly
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "originalWidth",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._getCurrentVideoElementProperty("videoWidth", 0);
    }
});

/**
 * Get the original height of the video source.
 * @name FORGE.VideoDash#originalHeight
 * @readonly
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "originalHeight",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._getCurrentVideoElementProperty("videoHeight", 0);
    }
});

/**
 * Get and set the currentTime  of the video.
 * @name FORGE.VideoDash#currentTime
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "currentTime",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._dashMediaPlayer.time();
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        if (typeof value === "number") // && value < this.duration) //@todo see if we can put video currentTime in pending if no metadata received ?
        {
            this._dashMediaPlayer.seek(value);

            if (this._onCurrentTimeChange !== null)
            {
                this._onCurrentTimeChange.dispatch(value);
            }
        }
    }
});

/**
 * Get and set the currentTime  of the video in milliseconds.
 * @name FORGE.VideoDash#currentTimeMS
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "currentTimeMS",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this.currentTime * 1000;
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        this.currentTime = value / 1000;
    }
});

/**
 * Get the remainingTime of the video.
 * @name FORGE.VideoDash#remainingTime
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "remainingTime",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this.duration - this.currentTime;
    }
});

/**
 * Get the duration of the video in seconds.
 * @name FORGE.VideoDash#duration
 * @readonly
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "duration",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._dashMediaPlayer.duration();
    }
});

/**
 * Get the duration of the video in milli seconds.
 * @name FORGE.VideoDash#durationMS
 * @readonly
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "durationMS",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return Math.round(this.duration * 1000);
    }
});

/**
 * Get the metaDataLoaded status of the video.
 * @name FORGE.VideoDash#metaDataLoaded
 * @readonly
 * @type {boolean}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "metaDataLoaded",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._metaDataLoaded;
    }
});

/**
 * Get and set the loop status of the video.
 * @name FORGE.VideoDash#loop
 * @type {boolean}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "loop",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._loop;
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        if (typeof value === "boolean")
        {
            this._loop = value;
        }
    }
});

/**
 * Get and set the volume of the video.
 * Set a volume unmute the video
 * @name FORGE.VideoDash#volume
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "volume",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._volume;
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        if (typeof value === "number")
        {
            this._volume = FORGE.Math.clamp(value, 0, 1);
            this._muted = false;
            this._updateVolume();
        }
    }
});

/**
 * Get and set the mute status of the video.
 * @name FORGE.VideoDash#muted
 * @type {boolean}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "muted",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._muted;
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        if (Boolean(value) === true)
        {
            this.mute();
        }
        else
        {
            this.unmute();
        }
    }
});

/**
 * Get and set the playback rate of the video.
 * @name FORGE.VideoDash#playbackRate
 * @type {number}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "playbackRate",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this._playbackRate;
    },

    /** @this {FORGE.VideoDash} */
    set: function(value)
    {
        if(typeof value === "number")
        {
            this._playbackRate = Math.abs(value);

            if (this._video !== null && this._video.element !== null)
            {
                this._video.element.playbackRate = this._playbackRate;
            }
        }
    }
});

/**
 * Get the "onLoadStart" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onLoadStart
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onLoadStart",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onLoadStart === null)
        {
            this._onLoadStart = new FORGE.EventDispatcher(this);
        }

        return this._onLoadStart;
    }
});

/**
 * Get the "onLoadedMetaData" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onLoadedMetaData
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onLoadedMetaData",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onLoadedMetaData === null)
        {
            this._onLoadedMetaData = new FORGE.EventDispatcher(this);
        }

        return this._onLoadedMetaData;
    }
});

/**
 * Get the "onLoadedData" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onLoadedData
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onLoadedData",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onLoadedData === null)
        {
            this._onLoadedData = new FORGE.EventDispatcher(this);
        }

        return this._onLoadedData;
    }
});

/**
 * Get the "onProgress" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onProgress
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onProgress",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onProgress === null)
        {
            this._onProgress = new FORGE.EventDispatcher(this);
        }

        return this._onProgress;
    }
});

/**
 * Get the "onDurationChange" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onDurationChange
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onDurationChange",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onDurationChange === null)
        {
            this._onDurationChange = new FORGE.EventDispatcher(this);
        }

        return this._onDurationChange;
    }
});

/**
 * Get the "onCanPlay" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onCanPlay
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onCanPlay",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onCanPlay === null)
        {
            this._onCanPlay = new FORGE.EventDispatcher(this);
        }

        return this._onCanPlay;
    }
});

/**
 * Get the "onCanPlayThrough" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onCanPlayThrough
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onCanPlayThrough",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onCanPlayThrough === null)
        {
            this._onCanPlayThrough = new FORGE.EventDispatcher(this);
        }

        return this._onCanPlayThrough;
    }
});

/**
 * Get the "onPlay" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onPlay
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onPlay",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onPlay === null)
        {
            this._onPlay = new FORGE.EventDispatcher(this);
        }

        return this._onPlay;
    }
});

/**
 * Get the "onPause" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onPause
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onPause",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onPause === null)
        {
            this._onPause = new FORGE.EventDispatcher(this);
        }

        return this._onPause;
    }
});

/**
 * Get the "onTimeUpdate" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onTimeUpdate
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onTimeUpdate",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onTimeUpdate === null)
        {
            this._onTimeUpdate = new FORGE.EventDispatcher(this);
        }

        return this._onTimeUpdate;
    }
});

/**
 * Get the "onCurrentTimeChange" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onCurrentTimeChange
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onCurrentTimeChange",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onCurrentTimeChange === null)
        {
            this._onCurrentTimeChange = new FORGE.EventDispatcher(this);
        }

        return this._onCurrentTimeChange;
    }
});

/**
 * Get the "onVolumeChange" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onVolumeChange
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onVolumeChange",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onVolumeChange === null)
        {
            this._onVolumeChange = new FORGE.EventDispatcher(this);
        }

        return this._onVolumeChange;
    }
});

/**
 * Get the "onSeeking" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onSeeking
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onSeeking",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onSeeking === null)
        {
            this._onSeeking = new FORGE.EventDispatcher(this);
        }

        return this._onSeeking;
    }
});

/**
 * Get the "onSeeked" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onSeeked
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onSeeked",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onSeeked === null)
        {
            this._onSeeked = new FORGE.EventDispatcher(this);
        }

        return this._onSeeked;
    }
});

/**
 * Get the "onEnded" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onEnded
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onEnded",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onEnded === null)
        {
            this._onEnded = new FORGE.EventDispatcher(this);
        }

        return this._onEnded;
    }
});

/**
 * Get the "onError" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onError
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onError",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onError === null)
        {
            this._onError = new FORGE.EventDispatcher(this);
        }

        return this._onError;
    }
});

/**
 * Get the "onStalled" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onStalled
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onStalled",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onStalled === null)
        {
            this._onStalled = new FORGE.EventDispatcher(this);
        }

        return this._onStalled;
    }
});

/**
 * Get the "onRateChange" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onRateChange
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onRateChange",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onRateChange === null)
        {
            this._onRateChange = new FORGE.EventDispatcher(this);
        }

        return this._onRateChange;
    }
});

/**
 * Get a copy of the "onStalled" event for the "onWaiting" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onWaiting
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onWaiting",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        return this.onStalled;
    }
});

/**
 * Get the "onPlaying" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onPlaying
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onPlaying",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onPlaying === null)
        {
            this._onPlaying = new FORGE.EventDispatcher(this);
        }

        return this._onPlaying;
    }
});

/**
 * Get the "onQualityRequest" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onQualityRequest
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onQualityRequest",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onQualityRequest === null)
        {
            this._onQualityRequest = new FORGE.EventDispatcher(this);
        }

        return this._onQualityRequest;
    }
});

/**
 * Get the "onQualityChange" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onQualityChange
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onQualityChange",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onQualityChange === null)
        {
            this._onQualityChange = new FORGE.EventDispatcher(this);
        }

        return this._onQualityChange;
    }
});

/**
 * Get the "onQualityAbort" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onQualityAbort
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onQualityAbort",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onQualityAbort === null)
        {
            this._onQualityAbort = new FORGE.EventDispatcher(this);
        }

        return this._onQualityAbort;
    }
});

/**
 * Get the "onMetricsChanged" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onMetricChanged
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onMetricsChanged",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onMetricsChanged === null)
        {
            this._onMetricsChanged = new FORGE.EventDispatcher(this);
        }

        return this._onMetricsChanged;
    }
});

/**
 * Get the "onQualitiesLoaded" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onQualitiesLoaded
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onQualitiesLoaded",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onQualitiesLoaded === null)
        {
            this._onQualitiesLoaded = new FORGE.EventDispatcher(this, true);
        }

        return this._onQualitiesLoaded;
    }
});

/**
 * Get the "onQualityModeChange" event {@link FORGE.EventDispatcher} of the video.
 * @name FORGE.VideoDash#onQualityModeChange
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.VideoDash.prototype, "onQualityModeChange",
{
    /** @this {FORGE.VideoDash} */
    get: function()
    {
        if (this._onQualityModeChange === null)
        {
            this._onQualityModeChange = new FORGE.EventDispatcher(this);
        }

        return this._onQualityModeChange;
    }
});