Source: components/players/live.js

define('application/components/players/live', [
    'application/components/player',
    'application/helpers/playerproperties',
    'application/helpers/drmconfig',
    'application/views/player/live',
    'rofl/lib/utils',
    'rofl/events/keyevent',
    'rofl/lib/promise',
    'antie/runtimecontext',
    'application/managers/api',
    'application/managers/channel',
    'application/managers/progress',
    'application/widgets/player/liveseeker',
    'application/constants',
    'application/utils',
    'application/managers/player',
    'application/models/epg/item',
    'application/decorators/player/livemanipulation',
    'application/decorators/player/interface',
    'application/managers/session',
    'rofl/analytics/web/google'
], function (
    Component,
    PlayerProperties,
    DRMConfig,
    LiveView,
    Utils,
    KeyEvent,
    Promise,
    RuntimeContext,
    ApiManager,
    ChannelManager,
    ProgressManager,
    Seeker,
    Constants,
    AppUtils,
    PlayerManager,
    EPGItem,
    PlayerManipulation,
    PlaybackInterface,
    SessionManager,
    GoogleAnalytics
) {
    'use strict';

    var api = ApiManager.getKPNAPI(),
        channelManager = ChannelManager.getInstance(),
        application = RuntimeContext.getCurrentApplication(),
        configuration = application.getConfiguration(),
        LIVE_PROGRESS_TIMEOUT = configuration.player.liveProgressTimeout,
        CHANNEL_SWITCHER_TIMEOUT = configuration.channelSwitcherTimeout,
        GA = GoogleAnalytics.getInstance(),
        sessionManager = SessionManager.getInstance();

    return Component.extend({

        /**
         * Sets the view for the player.
         *
         * @private
         */
        _setView: function () {
            this._view = new LiveView({
                mouseOverHandler: Utils.bind(function () {
                    if (this._view.supportsPointer()) {
                        this._showUI();
                    }
                }, this)
            });

            this.appendChildWidget(this._view);
        },

        /**
         * Sets the given program.
         *
         * @param {Object} program - The program to set.
         * @returns {Promise} - Solved promise with program.
         * @private
         */
        _setProgram: function (program) {
            this._view.setProgram(program);

            this._program = program;
            this._startTime = new Date(program.getStartTime() * 1000);
            this._endTime = new Date(program.getEndTime() * 1000);

            // Attach seeker once the program is set.
            this._buildSeeker();

            // We can start progress at this point.
            this._setProgress();

            if (AppUtils.isBroadcastLocked(program)) {
                application.hideLoader();
                this._checkingParental = true;

                this.playerInterface.unload();
                this._playbackStatus.active = false;
                this._showParental({
                    successCallback: Utils.bind(this._onParentalSuccess, this, program),
                    errorCallback: Utils.bind(this._onBack, this),
                    escapeCallback: Utils.bind(this._tunePreviousChannel, this),
                    keyEventCallback: Utils.bind(this._zapChannel, this)
                });

                // Close detail info if program is locked.
                if (this._view.getProgramDetails().isExpanded()) {
                    this._view.closeExpandedContents();
                }

                this._view.hideUI();
                return Promise.resolve(program);
            }

            // Sets the new program's detail info if it's expanded.
            if (this._view.getProgramDetails().isExpanded()) {
                this._requestDetailInfo()
                    .then(Utils.bind(this._onInfo, this, true));
            }

            // Set current program if channel stream is already playing.
            if (this.playerInterface.isPlaying()) {
                this._currentProgram = program;
            }

            return Promise.resolve(program);
        },

        /**
         * Prepares player to reset state and prepares playback.
         * Gets executed at:
         * 1.- initial playback.
         * 2.- zapping channel.
         * 3.- Toggle from live to restart.
         * 4.- Toggle from restart to live.
         *
         * @param {Object} playbackData - Playback content data.
         * @param {Object} playbackData.type - The video playback data.
         * @private
         */
        _preparePlayer: function (playbackData) {
            var programData = playbackData.data,
                channelId = programData && programData.getChannelId() || playbackData.getId(),
                type = playbackData.type || Constants.LIVE_VIDEO_TYPE;

            this._view.focus();
            this._view.hideWarningBox(true);
            this._type = type;
            this._restartStartTime = playbackData.startTime;

            if (type === Constants.LIVE_VIDEO_TYPE) {
                this._delayedStreamTime = null;
                this._pauseTime = null;
                this._delayedStartTime = null;
            } else if (!this._restartStartTime) {
                this._pauseTime = programData.getStartTime() * 1000;
            }

            this._preparePlayback(channelId, true);
        },

        /**
         * Prepares channel and broadcast event, requests streams and will start playback.
         *
         * @param {number} channelId - The channelId to be tuned.
         * @param {boolean} toggleStartoverLive - True if new stream should be requested.
         * @private
         */
        _preparePlayback: function (channelId, toggleStartoverLive) {

            // Don't prepare playback if there's a parental modal.
            if (this._checkingParental) {
                return;
            }

            if (this._channelId !== channelId || toggleStartoverLive) {
                channelManager.setLastWatchedChannel(this._channelId || channelId);

                this._channelId = channelId;

                this.playerInterface.unload();
                this._playbackStatus.active = false;

                // Resets to play button.
                this._view.onPlay();

                this._getCurrentBroadcastForChannel(channelId)
                    .then(Utils.bind(this._setProgram, this))
                    .then(Utils.bind(this._requestStream, this));

            } else {

                // If broadcast is already set, just update the program.
                this._playbackStatus.active = true;
                this._updateProgram(channelId);
            }
        },

        /**
         * Updates the program data for the given channelId.
         *
         * @param {number} channelId - The channel id to get its current broadcast.
         * @private
         */
        _updateProgram: function (channelId) {
            this._getCurrentBroadcastForChannel(channelId)
                .then(Utils.bind(this._setProgram, this))
                .then(Utils.bind(application.hideLoader, application))
                ['catch'](Utils.bind(this._onPlayerError, this));
        },

        /**
         * Requests the content's stream data.
         *
         * @param {Object} program - The requested program.
         * @returns {Object} - Stream data.
         */
        _requestStream: function (program) {
            var headers = {},
                channel = program && program.getChannel && program.getChannel() || program,
                channelId = channel && channel.getId(),
                promises = [],
                isLive = this._type === Constants.LIVE_VIDEO_TYPE;

            if (this._streamWithParental || program &&
                (program.isLocked() || program.streamRequiresPin())) {
                headers.pcPin = this.get;

                this._streamWithParental = false;
            }

            // Get the streams if we're not checking parental.
            if (!this._checkingParental) {
                if (isLive) {
                    this._pauseTime = null;
                }

                promises.push(Promise.resolve(program));
                promises.push(api.read('streams/live', {
                    params: {
                        type: this._type,
                        assetId: channel.getAssetId(),
                        contentId: channelId,
                        startTime: program.getStartTime() * 1000
                    },
                    withCredentials: true,
                    headers: this.getStreamHeaders(program)
                }));

                return Promise.all(promises)
                    .then(Utils.bind(this._startPlayback, this))
                    ['catch'](Utils.bind(this._onPlayerError, this));
            }

            this._view.hideUI();
        },

        /**
         * Prepares the player properties, sets the miniEPG channel, attempts to start playback.
         *
         * @param {Array} dataParams - Parameters needed to create properties and miniEPG update.
         * @param {Object} dataParams.0 - Contains the current broadcasting program.
         * @param {Object} dataParams.1 - Contains the stream data.
         * @private
         */
        _startPlayback: function _startPlayback (dataParams) {
            var program = dataParams[0],
                data = dataParams[1],
                channelId = program.getChannelId(),
                restartTime,
                properties,
                programStartTime;

            if (this._type === Constants.RESTART_VIDEO_TYPE) {
                programStartTime = program.getStartTime() * 1000;
                restartTime = this._restartStartTime || programStartTime;
            }

            properties = new PlayerProperties({
                source: {
                    src: data.url,
                    mimeType: data.mimeType || PlayerProperties.MIME_TYPES.ssm
                },
                startTime: restartTime,
                programStartTime: programStartTime,
                delayedStartTime: this._delayedStartTime,
                pauseTime: this._pauseTime,
                async: true,
                autoplay: true,
                isLiveItem: this._type === Constants.LIVE_VIDEO_TYPE,
                isRestartItem: this._type === Constants.RESTART_VIDEO_TYPE,
                drmConfig: new DRMConfig({
                    type: DRMConfig.TYPES.PLAYREADY,
                    options: {
                        licenseServer: data.licenseUrl
                    }
                })
            });

            if (this._channelId === channelId) {

                // Sets data for miniEPG.
                this._view.getMiniEPG().setChannel(channelId);

                _startPlayback.base.call(this, properties);
            }
        },

        /**
         * Parental pin success callback.
         *
         * @param {Object} program - THe program that was unlocked.
         * @private
         */
        _onParentalSuccess: function (program) {

            // Update Program info.
            this._setProgram(program)
                .then(Utils.bind(this._requestStream, this));
        },

        /**
         * On before show event.
         *
         * @param {Object} e - The on before show event data.
         */
        onBeforeShow: function onBeforeShow (e) {
            this._keys = '';

            onBeforeShow.base.call(this, e);

        },

        /**
         * PlayerEvent.
         *
         * @param {Object} e - The player event data.
         * @private
         */
        onPlayerEvent: function onPlayerEvent (e) {
            switch (e.type) {
                case this.MEDIA_PLAYER_EVENTS.ERROR:
                case this.MEDIA_PLAYER_EVENTS.BITRATE_CHANGED:

                    onPlayerEvent.base.call(this, e);
                    break;

                case this.MEDIA_PLAYER_EVENTS.PLAYING:

                    /**
                     * Set delayed stream time to be used on player properties
                     * when toggling from live to restart.
                     */
                    if (this._type === Constants.LIVE_VIDEO_TYPE && !this._delayedStreamTime) {
                        this._delayedStartTime = this.playerInterface.getCurrentTime();
                    } else {
                        this._delayedStartTime = null;
                    }

                    this._sendZapAnalytics();

                    onPlayerEvent.base.call(this, e);
                    break;

                case this.MEDIA_PLAYER_EVENTS.PAUSED:
                    if (this._progress !== null && (this._progress.behind >= 0 || isNaN(this._progress.behind))) {
                        this._pauseTime = application.getDate().getTime();
                    }

                    onPlayerEvent.base.call(this, e);
                    break;

                case this.MEDIA_PLAYER_EVENTS.STATUS:
                    if (!this._program.isLive() && !this._pauseTime) {

                        // Restart playback.
                        if (this._currentProgram === this._program) {
                            application.hideLoader();

                            if (!this._playbackStatus.active) {
                                this._showUI();
                                this._playbackStatus.active = true;
                            }
                        }
                    } else if (this._currentProgram === this._program && !this._playbackStatus.active) {
                        application.hideLoader();

                        if (!this._playbackStatus.active && this.playerInterface.isPlaying()) {
                            this._showUI();
                            this._playbackStatus.active = true;
                        }
                    }
                    break;
            }
        },

        /**
         * Starts the live progress.
         *
         * @private
         */
        _setProgress: function () {
            this._stopLiveProgressTimeout();

            if (this._type === Constants.LIVE_VIDEO_TYPE) {
                this._setLiveProgress();
            } else {
                this._setCurrentTimeToSeeker();
            }

            this._liveProgressInterval = setInterval(
                Utils.bind(this._setLiveProgress, this),
                LIVE_PROGRESS_TIMEOUT);
        },

        /**
         * Sets the live progress.
         *
         * @param {boolean} [preventSeekingWhileLoading] - Prevent seeking on timeline while loading restart..
         * @private
         */
        _setLiveProgress: function (preventSeekingWhileLoading) {
            var program = this._program,
                startTime = new Date(program.getStartTime() * 1000),
                endTime = new Date(program.getEndTime() * 1000),
                progress = this._progress = ProgressManager.getLiveProgressPercentage(
                    startTime,
                    endTime,
                    this._pauseTime),
                currentTime;

            // Check if the progress is behind the current time.
            if (this.playerInterface.isPlaying() && progress.behind < 0) {
                currentTime = application.getDate();
                this._pauseTime = currentTime.getTime() + progress.behind + 1000;
            }

            if (preventSeekingWhileLoading && !this._pauseTime && this._restartStarted) {
                return;
            }
            this._restartStarted = false;

            PlayerManager.setLivePlaybackPosition(this._pauseTime || application.getDate().getTime());

            if (this.playerInterface.isPlaying()) {
                if (this._seeker && typeof this._seeker.setCurrentTime === 'function') {
                    if (this._pauseTime) {
                        this._seeker.setCurrentTime(this._pauseTime);
                    } else {
                        this._seeker.setCurrentTime(application.getDate().getTime());
                    }
                }
            }

            // Checks if second head has finished to request the next program.
            if (progress.secondHead >= 100) {

                // First cancel the timeout.
                this._stopLiveProgressTimeout();

                // Retrieve the new program.
                this._updateProgram(program.getChannelId());
            }

            this._view.setProgress(progress);
        },

        /**
         * Sets the live progress.
         *
         * @param {Date} fixedTime - Time when button was pressed.
         * @private
         */
        _setRestartProgress: function (fixedTime) {
            var program = this._program,
                startTime = new Date(program.getStartTime() * 1000),
                endTime = new Date(program.getEndTime() * 1000),
                currentTime = application.getDate(),
                behind = fixedTime - startTime,
                progress;

            this._pauseTime = currentTime.getTime() - behind;
            progress = this._progress = ProgressManager.getRestartProgressPercentage(
                startTime,
                endTime,
                behind);

            if (progress.secondHead >= 100) {

                // First cancel the timeout.
                this._stopRestartProgressTimeout();

                // Retrieve the new program.
                this._updateProgram(program.getChannelId());
            }

            this._view.setProgress(progress);
        },

        /**
         * Retrieves current broadcast program from the given channelId.
         * Note: If Restart is playing, request broadcast with time.
         *
         * @param {number} channelId - Channel Id to retrieve current program.
         * @returns {Object} - The current broadcast program for the given channelId.
         * @private
         */
        _getCurrentBroadcastForChannel: function (channelId) {
            if (this._pauseTime && this._progress) {
                return channelManager.getBroadcastAtTime(channelId,
                    application.getDate() - Math.abs(this._progress.behind))
                    || this.createMissingEpgData(channelId);
            }

            return channelManager.getCurrentBroadcastForChannel(channelId) || this.createMissingEpgData(channelId);
        },

        /**
         * Creates any missing information for missing epgData item.
         *
         * @param {number} channelId - ChannelId of the missing epgData item.
         * @returns {*} - The new epgItem created from channel.
         */
        createMissingEpgData: function (channelId) {
            return new EPGItem({}, channelManager.getChannelById(channelId));
        },

        /**
         * KeyDown event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onKeyDown: function _onKeyDown (e) {

            // Prevents losing focus when an error is triggered but error component is loading.
            if (this._errorState) {
                return;
            }

            switch (e.keyCode) {
                case KeyEvent.VK_CHANNEL_UP:
                case KeyEvent.VK_CHANNEL_DOWN:

                    // Don't set keyHold while parental is being loaded. Prevents async triggers.
                    if (this._deviceBrand === 'samsung' && !this._checkingParental) {
                        this._setKeyHoldData(e, this.onKeyHoldHandler);
                    } else {
                        this._showUI(true);
                        this._zapChannel(e);
                    }

                    e.preventDefault();
                    e.stopPropagation();
                    break;

                case KeyEvent.VK_REWIND:
                case KeyEvent.VK_FAST_FWD:
                    if (this._program && this._program.hasEpgData()) {
                        _onKeyDown.base.call(this, e);
                    }

                    break;

                case KeyEvent.VK_0:
                case KeyEvent.VK_1:
                case KeyEvent.VK_2:
                case KeyEvent.VK_3:
                case KeyEvent.VK_4:
                case KeyEvent.VK_5:
                case KeyEvent.VK_6:
                case KeyEvent.VK_7:
                case KeyEvent.VK_8:
                case KeyEvent.VK_9:
                    this._handleSwitchChannel(e);
                    break;

                default:
                    _onKeyDown.base.call(this, e);
            }
        },

        /**
         * KeyDown hold event. Currently supports channel up/down for Samsung RC.
         *
         * @param {Object} e - The event data.
         * @param {boolean} keyHoldTriggered - True if triggered by keyHold timeout or cancel by keyup event.
         * @private
         */
        onKeyHoldHandler: function (e, keyHoldTriggered) {
            var hideMiniEPG = this._view.getMiniEPG().isExpanded();

            /**
             * Prevents losing focus when an error is triggered but error component is loading.
             * Key hold handler may be executed after showing error.
             */
            if (this._errorState) {
                return;
            }

            switch (e.keyCode) {
                case KeyEvent.VK_CHANNEL_UP:
                case KeyEvent.VK_CHANNEL_DOWN:
                    if (keyHoldTriggered && !this._checkingParental) {

                        this._view.showMiniEPG(hideMiniEPG);

                        if (hideMiniEPG) {
                            this._view.focus();
                        } else {
                            if (!this._view.isUIVisible()) {
                                this._showUI();
                            }
                            this._view.getMiniEPG().focus();
                        }
                    } else {
                        this._zapChannel(e);
                    }
                    break;
            }
        },

        /**
         * Prepares to zap to next or previous channel depending on key event.
         *
         * @param {Object} ev - Key Event.
         */
        _zapChannel: function (ev) {
            var channelToSwitch,
                currentChannel = channelManager.getChannelById(this._channelId);

            if (ev.keyCode === KeyEvent.VK_CHANNEL_UP) {
                channelToSwitch = channelManager.getNextChannel(currentChannel);
            } else {
                channelToSwitch = channelManager.getPreviousChannel(currentChannel);
            }

            this._zapToChannel(channelToSwitch);
        },

        /**
         * Zaps to the given channel.
         *
         * @param {Object} channel - Chanel to zap to..
         */
        _zapToChannel: function (channel) {
            this._switchChannelStartTime = application.getDate();
            this._clearChannelInput();
            this._preparePlayer(channel);
        },

        /**
         * Handle switching the channel with numeric input.
         *
         * @param {Object} e - The event parameters.
         * @private
         */
        _handleSwitchChannel: function (e) {

            if (this._keys.length >= 3) {
                this._keys = '';
            }

            this._keys += e.keyChar;

            if (this._view.isUIVisible()) {
                this._view.hideUI({
                    duration: 500
                });
            }

            this._setChannelSwitchNumber();
        },

        /**
         * Sets the channel switching number.
         *
         * @private
         */
        _setChannelSwitchNumber: function () {
            var timeoutValue = CHANNEL_SWITCHER_TIMEOUT;

            this._cancelChannelInput();
            this._view.setChannelSwitcherLabel(this._keys);

            this._inputTimeout = setTimeout(Utils.bind(function () {
                this._zapToChannel(channelManager.getChannelByNumber(this._keys));
                this._clearChannelInput();
            }, this), timeoutValue);
        },

        /**
         * Cancels the channel input zapping.
         *
         * @private
         */
        _cancelChannelInput: function () {
            if (this._inputTimeout) {
                clearTimeout(this._inputTimeout);
                this._inputTimeout = null;
            }
        },

        /**
         * Clears the channel input widget.
         */
        _clearChannelInput: function () {
            this._cancelChannelInput();
            this._keys = '';
            this._view.setChannelSwitcherLabel();
        },

        /**
         * Attempts to tune previous channel from the given program.
         *
         * @private
         */
        _tunePreviousChannel: function () {
            var previousChannelId = channelManager.getLastWatchedChannel(),
                channelToSwitch;

            // Check if there's a previous channel id, otherwise go back.
            if (previousChannelId && previousChannelId !== this._channelId) {
                channelToSwitch = channelManager.getChannelById(previousChannelId);

                this._zapToChannel(channelToSwitch);
            } else {
                this._onBack();
            }
        },

        /**
         * Select event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onSelect: function _onSelect (e) {
            var target = e.target,
                dataItem,
                dataItemChannel;

            if (this._view.isUIVisible()) {

                switch (target.id) {
                    case LiveView.CONTROLS.INFO:
                    case LiveView.CONTROLS.MENU:
                    case LiveView.CONTROLS.PLAYPAUSE:

                        // Falls through common actions
                        _onSelect.base.call(this, e);
                        break;

                    case LiveView.CONTROLS.REWIND:
                    case LiveView.CONTROLS.FORWARD:
                        if (this._program && this._program.hasEpgData()) {
                            _onSelect.base.call(this, e);
                        }

                        break;

                    case LiveView.CONTROLS.RESTART:
                        this._onRestart();
                        break;
                    case LiveView.CONTROLS.RECORD:
                        this._onRecord();
                        break;
                    case LiveView.CONTROLS.MINIEPG:
                        this._onMiniEPG();
                        break;
                    case LiveView.CONTROLS.BACK:
                        this._view.closeExpandedContents();
                        this._onBack();
                        break;
                    case LiveView.CONTROLS.LIVE:
                        this._onLive();
                        break;
                }

                if (target.hasClass('carouselItem')) {
                    dataItem = target.getChildWidgetByIndex(0).getDataItem().item;
                    dataItemChannel = channelManager.getChannelById(dataItem.getChannelId());

                    if (dataItem.isLocked() && !sessionManager.getUserPin()) {
                        this._showParental({
                            successCallback: Utils.bind(this._onMiniEPGProgram, this, dataItemChannel),
                            escapeCallback: Utils.bind(this._onMiniEPGCancelled, this)
                        });
                    } else {
                        this._onMiniEPGProgram(dataItemChannel);
                    }
                }
            } else {

                this._showUI();
            }
        },

        /**
         * Builds the live seeker.
         *
         * @private
         */
        _buildSeeker: function () {
            var program = this._program,
                playbackTime = application.getDate().getTime(),
                startTime = program.getStartTime() * 1000,
                endTime = program.getEndTime() * 1000;

            if (this._seeker && this._seeker.isActive()) {
                this._seeker.detach();
                this._seeker = null;
            }

            this._seeker = Seeker();

            this._seeker.attach({
                onSpeedChanged: this._onSeekSpeedChangeBound,
                onSeek: this._onSeekBound,
                onCurrentTimeUpdated: this._onSeekCurrentTimeChangeBound,
                steps: configuration.player.seekSteps
            });

            if (this._pauseTime) {
                playbackTime = this._pauseTime;
            }

            this._seeker.setProgramTime(startTime, endTime, playbackTime);
        },

        /**
         * Gets executed when the seek is confirmed.
         *
         * @param {Object} e - The seek event data.
         * @private
         */
        _onSeek: function (e) {
            var isLive = this._type === Constants.LIVE_VIDEO_TYPE,
                isRestart = this._type === Constants.RESTART_VIDEO_TYPE;

            this._seekSpeed = null;
            this._seekCurrentTime = null;

            this._view.resetControls();
            this._view.setProgressMaxPointerPosition();
            this._setProgress();

            // Below only gets executed for live trickplay.
            if (e && e.seekTo) {

                if (isLive) {
                    if (e.currentTime >= application.getDate()) {
                        this._onLive();
                    } else {
                        this._pauseTime = e.currentTime;
                        this._onRestart(e.seekTo);
                    }

                } else if (isRestart) {
                    if (e.currentTime >= application.getDate()) {
                        this._onLive();
                    } else {
                        this.playerInterface.resume();

                        if (e.relativePlaybackTime === 0) {
                            this._pauseTime = this._program.getStartTime() * 1000;
                            this.playerInterface.seek(0);
                        } else {
                            this._pauseTime = e.currentTime;
                            this.playerInterface.seek(e.seekTo);
                        }
                    }
                }
            }
        },

        /**
         * Gets executed when the current seek time changes for live video.
         *
         * @param {Object} data - The data.
         * @param {number} data.currentTime - The current seek time.
         * @param {number} data.relativePlaybackTime - The current relative playback time.
         *
         * @private
         */
        _onSeekCurrentTime: function (data) {
            var programStart = this._program.getStartTime() * 1000,
                seeker = this._seeker,
                currentSeekTime = data.currentTime,
                maxSeekTime = PlayerManager.getMaxSeekValue() * 1000,
                now = application.getDate();

            if (currentSeekTime < programStart) {
                this._stopLiveProgressTimeout();
                if (this._type === Constants.LIVE_VIDEO_TYPE) {
                    this._pauseTime = programStart;
                    this._onRestart();
                } else {
                    seeker.confirm(programStart);
                }
                return;
            }

            if (currentSeekTime >= now) {
                this._confirmSeekerToLive();
            }

            if (!this._program.canSeek()
                && seeker.getDirection() === 1
                && data.relativePlaybackTime > maxSeekTime) {

                seeker.confirm(programStart + maxSeekTime);
                return;
            }

            this._pauseTime = currentSeekTime;

            this._setLiveProgress();
        },

        /**
         * Sets the value of time to seeker.
         *
         * @private
         */
        _setCurrentTimeToSeeker: function () {
            var timeValue,
                program = this._program,
                seeker = this._seeker,
                currentTime = application.getDate();

            timeValue = currentTime.getTime() / 1000 - program.getStartTime();

            seeker.setCurrentTime(timeValue);
        },

        /**
         * VOD Mouse Seek event.
         *
         * @param {Object} e - The event data.
         * @param {number} e.percentage - The seek percentage.
         * @param {boolean} e.finished - True if the mouse seek event has finished.
         * @private
         */
        _onMouseSeek: function (e) {
            var start = this._program.getStartTime() * 1000,
                end = this._program.getEndTime() * 1000,
                now = application.getDate(),
                seeker = this._seeker,
                duration = end - start,
                difference = (duration / 100) * (100 - e.percentage),
                seekTo = end - difference;

            this._pauseTime = Math.round(seekTo);
            this._setLiveProgress();

            if (!this._prevProgress) {
                this._prevProgress = e.percentage;
            }

            if (e.finished) {
                this._seeking = this._getMouseSeekDirection(this._prevProgress, e.percentage);
                this._sendTrickplayAnalytics();

                this._prevProgress = e.percentage;

                if (seekTo < start) {

                    if (this._type === Constants.LIVE_VIDEO_TYPE) {
                        this._pauseTime = start;
                        this._onRestart();
                    } else {
                        seeker.confirm(start);
                    }
                    return;
                }

                // Because of timing add 10 seconds to this check.
                if ((seekTo + 10000) >= now) {
                    this._confirmSeekerToLive();
                } else {
                    seeker.confirm(seekTo);
                }
            }
        },

        /**
         * Intentionally set the playfrom time higher than the current date.
         * This way the seeker will think it's past the live moment and start
         * playing from live.
         *
         * @private
         */
        _confirmSeekerToLive: function () {
            var seeker = this._seeker,
                playFrom = application.getDate(); // now

            playFrom.setMinutes(playFrom.getMinutes() + 1);
            seeker.setCurrentTime(playFrom.getTime());
            seeker.confirm(playFrom.getTime());
        },

        /**
         * Returns the player manipulation decorator.
         *
         * @returns {Object} - Player manipulation decorator.
         */
        getPlayerManipulation: function () {
            return PlayerManipulation;
        },

        /**
         * Gets the playback interface.
         *
         * @returns {Object} - The playback interface.
         */
        getPlaybackInterface: function () {
            return new PlaybackInterface();
        },

        /**
         * Sends the zap analytics when video start playing.
         *
         * @private
         */
        _sendZapAnalytics: function () {
            var timeOfLoadingChannel;

            if (this._switchChannelStartTime) {
                timeOfLoadingChannel = application.getDate() - this._switchChannelStartTime;

                GA.onEvent(Constants.ANALYTICS_EVENT_CATEGORY_PLAYER,
                    Constants.ANALYTICS_EVENT_ACTION_ZAP,
                    {
                        eventLabel: this._program.getChannelId(),
                        eventValue: timeOfLoadingChannel
                    }
                );

                this._switchChannelStartTime = null;
            }
        },

        /**
         * On back action.
         *
         * @private
         */
        _onBack: function _onBack () {
            GA.onEvent(Constants.ANALYTICS_EVENT_CATEGORY_ACTION,
                Constants.ANALYTICS_EVENT_ACTION_STOP_VIDEO,
                {
                    eventLabel: AppUtils.getAnalyticsEventLabel(this._type)
                }
            );

            this._view.hideUI({
                skipAnim: true
            });
            this._view.resetControls();
            this.playerInterface.destroy();
            _onBack.base.call(this);

            this._stopProgressTimeouts();
            this._clearChannelInput();
            this._keys = '';
            this._pauseTime = null;
            this._delayedStreamTime = null;
            this._delayedStartTime = null;
            this._channelId = null;
            this._playbackStatus.active = false;
        },

        /**
         * Close player.
         */
        closePlayer: function closePlayer () {
            GA.onEvent(Constants.ANALYTICS_EVENT_CATEGORY_ACTION,
                Constants.ANALYTICS_EVENT_ACTION_STOP_VIDEO,
                {
                    eventLabel: AppUtils.getAnalyticsEventLabel(this._type)
                }
            );

            this._view.hideUI({
                skipAnim: true
            });
            this._view.resetControls();
            this.playerInterface.destroy();
            closePlayer.base.call(this);

            this._stopProgressTimeouts();
            this._clearChannelInput();
            this._keys = '';
            this._pauseTime = null;
            this._delayedStreamTime = null;
            this._delayedStartTime = null;
            this._channelId = null;
            this._playbackStatus.active = false;
        }
    });
});