Source: views/player.js

define('application/views/player', [
    'rofl/widgets/container',
    'application/widgets/player/channelswitcher',
    'application/widgets/player/warningbox',
    'application/widgets/clock',
    'application/widgets/detail/iconbutton',
    'rofl/widgets/label',
    'application/widgets/player/zappbanner',
    'application/widgets/gradient',
    'application/widgets/player/programdetails',
    'rofl/lib/utils',
    'rofl/devices/input/pointer',
    'antie/runtimecontext',
    'application/widgets/player/controls',
    'application/widgets/pointerfocusablebutton',
    'application/managers/recording',
    'application/managers/session',
    'rofl/lib/l10n',
    'antie/storageprovider',
    'rofl/devices/mediaplayer/mediaplayer',
    'antie/events/keyevent',
    'application/managers/feature',
    'application/utils',
    'application/helpers/parentalHelper',
    'application/widgets/player/miniepg',
    'rofl/analytics/web/google'
], function (
    Container,
    ChannelSwitcher,
    WarningBox,
    Clock,
    IconButton,
    Label,
    ZappBanner,
    Gradient,
    ProgramDetails,
    Utils,
    PointerManager,
    RuntimeContext,
    Controls,
    PointerButton,
    RecordingManager,
    SessionManager,
    L10N,
    StorageProvider,
    MediaPlayer,
    KeyEvent,
    FeatureManager,
    AppUtils,
    ParentalHelper,
    MiniEPG,
    GoogleAnalytics
) {
    'use strict';

    var device = RuntimeContext.getDevice(),
        pointerManager,
        application = RuntimeContext.getCurrentApplication(),
        configuration = application.getConfiguration(),
        recordingManager = RecordingManager.getInstance(),
        sessionManager = SessionManager.getInstance(),
        mediaPlayer = device.getMediaPlayer(),
        l10n = L10N.getInstance(),
        storage = device.getStorage(StorageProvider.STORAGE_TYPE_SESSION),
        GA = GoogleAnalytics.getInstance(),
        layout = application.getLayout(),
        descriptionLayout = layout.player.description;

    return Container.extend({

        /**
         * Initialises the player view.
         */
        init: function init () {
            if (!pointerManager) {
                pointerManager = PointerManager.getInstance();
                this._pointerSupport = true;
            }

            this._handleParental = false;
            this._currentProgram = null;

            init.base.call(this);

            this._build();

            this.addClass(['view', 'player-view']);
        },

        /**
         * Builds the player view.
         *
         * @private
         */
        _build: function () {

            this._buildGradients();
            this._buildMainContainer();
            this._buildZapControlsContainer();
            this._buildZappBanner();
            this._buildControls();
            this._buildProgamInfo();
            this._buildClock();
            this._buildChannelSwitcher();
            this._buildWarningBox();
            this._buildBackButton();
            this._buildMiniEpg();
        },

        /**
         * Builds WarningBox.
         *
         * @private
         */
        _buildWarningBox: function () {
            var warningBox = this._warningBox = new WarningBox();

            this.appendChildWidget(warningBox);
        },

        /**
         * Builds the back button.
         *
         * @private
         */
        _buildBackButton: function () {
            var btn = this._backButton = new IconButton('back-button', '', ['icon', 'icon-back-v2']);

            this._backButton.addClass('back-button');

            this.appendChildWidget(btn);
        },

        /**
         * Builds the main container that will include zappingbanner/controls, miniEPG and info.
         *
         * @private
         */
        _buildMainContainer: function () {
            var mainContainer = this._mainContainer = new Container();

            mainContainer.addClass('main-content');

            this.appendChildWidget(mainContainer);
        },

        /**
         * Builds the main container that will include zappBanner & controls.
         *
         * @private
         */
        _buildZapControlsContainer: function () {
            var zapControlsContainer = this._zapControlsContainer = new Container();

            zapControlsContainer.addClass('zapping-controls-content');
            zapControlsContainer.appendChildWidget(this._bottomGradient);

            this._mainContainer.appendChildWidget(zapControlsContainer);
        },

        /**
         * Builds the video controls.
         *
         * @private
         */
        _buildControls: function () {
            var controls = this._controls = new Controls();

            this._controls.addEventListener('focus', Utils.bind(this._onFocus, this));

            this._zapControlsContainer.appendChildWidget(controls);
        },

        /**
         * Builds the ZappBanner.
         *
         * @private
         */
        _buildZappBanner: function () {
            var zappBanner = this._zappBanner = new ZappBanner();

            this._zapControlsContainer.appendChildWidget(zappBanner);
        },

        /**
         * Builds the channel switcher.
         *
         * @private
         */
        _buildChannelSwitcher: function () {
            var channelSwitcher = this._channelSwitcher = new ChannelSwitcher();

            this.appendChildWidget(channelSwitcher);
        },

        /**
         * Builds the gradients.
         *
         * @private
         */
        _buildGradients: function () {
            var topDownGradient = this._topGradient = new Gradient(Gradient.DIRECTIONS.TOPDOWN);

            this._bottomGradient = new Gradient(Gradient.DIRECTIONS.BOTTOMUP);

            this.appendChildWidget(topDownGradient);
        },

        /**
         * Builds the clock.
         *
         * @private
         */
        _buildClock: function () {
            var clock = this._clock = new Clock();

            this.appendChildWidget(clock);
        },

        /**
         * Builds the mini epg.
         *
         * @private
         */
        _buildMiniEpg: function () {
            this._miniEPG = this._mainContainer.appendChildWidget(new MiniEPG());
        },

        /**
         * Builds the program detail info.
         */
        _buildProgamInfo: function () {
            var programDetails = this._programDetails = new ProgramDetails();

            this._mainContainer.appendChildWidget(programDetails);
        },

        /**
         * Show progress bar.
         *
         */
        showProgress: function () {
            this._zappBanner.getProgress().show();
        },


        /**
         * Hide progress bar.
         *
         * @param {Object} [options] - Pass animation options if required.
         *
         */
        hideProgress: function (options) {
            this._zappBanner.getProgress().hide(options);
        },

        /**
         * Shows the ui.
         *
         * @param {boolean} [hide] - True if it should hide after 3 seconds.
         * @param {boolean} [hideClock] - True if clock should be hidden.
         */
        showUI: function (hide, hideClock) {
            var zappbannerDissapearTime = configuration.ZAPPBANNER_DISSAPPEAR_TIME,
                playbackStatus = this.parentWidget.getPlaybackStatus(),
                isProgressVisible = this._zappBanner.getProgress().isVisible();

            clearTimeout(this._hideUITimeout);

            if (!this.isUIVisible()) {
                this._mainContainer.show();
                this._backButton.show();
                this._topGradient.show();
                this._controls.show();

                if (!hideClock) {
                    this._clock.show();
                }
            }

            if (!playbackStatus.active && !isProgressVisible) {
                playbackStatus.callback = Utils.bind(this.showProgress, this);
            } else {
                if (!isProgressVisible && !playbackStatus.callback) {
                    playbackStatus.callback = null;
                    this.showProgress();
                }
            }

            /*
             * Hide recording button for user without such a functionality,
             * to prevent any unwanted showings.
             */
            if (application.getUser() && !application.getUser().canRecord()) {
                this._controls.hideRecord();
            }

            if (hide) {
                this._hideUITimeout = setTimeout(Utils.bind(this.hideUI, this), zappbannerDissapearTime);
            }
        },

        /**
         * Hides the ui.
         *
         * @param {Object} [opts] - Options.
         */
        hideUI: function (opts) {

            if (this.isContentExpanded()) {
                return;
            }

            if (this.isUIVisible()) {
                this._mainContainer.hide(opts);
                this._backButton.hide(opts);
                this._clock.hide(opts);
                this._topGradient.hide(opts);
                this._controls.hide(opts);
                this.closeExpandedContents();
            }
        },

        /**
         * Returns true if the UI is visible.
         *
         * @returns {boolean} - True if the UI is visible.
         */
        isUIVisible: function () {

            // Use the zappbanner as the ui default visibility.
            return this._mainContainer.isVisible();
        },

        /**
         * Enable pointer support.
         */
        enablePointerSupport: function () {
            this._pointerSupport = true;
            this._zappBanner.getProgress().setPointerSupport(true);

            Utils.each(this._controls._childWidgets, function (button) {
                if (button instanceof PointerButton) {
                    button.setPointerSupport(true);
                }
            });

            if (!application.getUser().canRecord()) {
                this._controls.hideRecord();
            }
        },

        /**
         * Disable pointer support.
         */
        disablePointerSupport: function () {
            this._pointerSupport = false;
            this._zappBanner.getProgress().setPointerSupport(false);

            Utils.each(this._controls._childWidgets, function (button) {
                if (button instanceof PointerButton) {
                    button.setPointerSupport(false);
                }
            });
        },

        /**
         * Hides the warning box.
         *
         * @param {boolean} [skipAnim] - True if should skip the animations.
         */
        hideWarningBox: function (skipAnim) {
            this._warningBox.hide({
                skipAnim: skipAnim
            });
        },

        /**
         * Shows the warning box.
         *
         * @param {Object} config - The config.
         */
        showWarningBox: function (config) {
            this._warningBox.show(config);
        },

        /**
         * Sets the progress.
         *
         * @param {Object} data - The data.
         */
        setLiveProgress: function (data) {
            this._zappBanner.setLiveProgress(data);

            if (data.behind < 0) {
                this._controls.disableLiveButton(false);
            } else {
                this.resetLiveLabels();
            }
        },

        /**
         * Prevents playing for locked asset.
         *
         * @param {Object} event - Event.
         * @private
         */
        _preventPlayWhenPlaying: function (event) {
            if (event.type === 'playing') {
                mediaPlayer.pause();
            }
        },

        /**
         * Hides player under black overlay.
         *
         * @private
         */
        _hidePlayer: function () {

            mediaPlayer.addEventCallback(this, this._preventPlayWhenPlaying);

            if (!this.hasClass('player-hidden')) {
                this.addClass('player-hidden');
            }

            this._controls.setPlayerHiddenFlag(true);
        },

        /**
         * Shows player after parental is passed.
         *
         * @private
         */
        _showPlayer: function () {
            mediaPlayer.removeEventCallback(this, this._preventPlayWhenPlaying);
            if (this.hasClass('player-hidden')) {
                this.removeClass('player-hidden');
            }

            if (mediaPlayer.getState() === MediaPlayer.STATE.PAUSED) {
                mediaPlayer.resume();
            }

            this._controls.setPlayerHiddenFlag(false);
        },

        /**
         * Adds limitation error to black overlay.
         *
         * @private
         */
        _addLimitWarning: function () {
            var warningContainer = new Container('limit-warning'),
                warningContent = new Container('limit-warning-content'),
                warningTitle = new Label({ text: l10n.get('parental.player_warning.title'), classNames: ['warning-title'] }),
                description = new Label({ text: l10n.get('parental.player_warning.description'), classNames: ['warning-description'], enableHTML: true });

            warningContainer.appendChildWidget(warningContent);
            warningContent.appendChildWidget(warningTitle);
            warningContent.appendChildWidget(description);

            this.appendChildWidget(warningContainer);
        },

        /**
         * Sets the VOD progress.
         *
         * @param {Object} data - The data.
         */
        setVODProgress: function (data) {
            this._zappBanner.setVODProgress(data);
        },

        /**
         * Stores timestamp to session storage that represents when parental was locked.
         *
         * @private
         */
        _storeLockTime: function () {
            storage.setItem('parentalLockTimestamp', application.getDate().getTime());
        },

        /**
         * Checks whether still locked.
         *
         * @returns {boolean} - True if locked.
         * @private
         */
        _isLockedFor24H: function () {

            var storedTimestamp = parseInt(storage.getItem('parentalLockTimestamp'));

            if (!storedTimestamp) {
                return false;
            }

            return (application.getDate().getTime() - storedTimestamp) < 1000 * 60 * 60 * 24;
        },

        /**
         * Starts parental control flow.
         *
         * @private
         */
        _startParentalFlow: function () {

            if (!this._currentProgram.getParentalWhitelisted() && !sessionManager.getUserPin()) {
                this._hidePlayer();

                if (!this._isLockedFor24H()) {
                    if (mediaPlayer.getState() === MediaPlayer.STATE.PLAYING) {
                        mediaPlayer.pause();
                    }

                    application.route('parentalpin', {
                        successCallback: Utils.bind(function () {
                            this._showPlayer();
                            this.onPlay();
                            this._currentProgram.setParentalWhitelisted();
                        }, this),
                        escapeCallback: Utils.bind(ParentalHelper.parentaleEscapeCallback, this.parentWidget, true),
                        errorCallback: Utils.bind(function (error) {

                            // in case of 5 times wrong pin
                            if (Utils.getNested(error, 'errorDescription') === '403-8117') {
                                this._addLimitWarning();
                                this._storeLockTime();
                            }
                        }, this)
                    });
                }
            }
        },

        /**
         * Checks whether asset is child protected.
         *
         * @returns {boolean} - True if is child protected.
         */
        _isLocked: function () {
            var parentalControlParams = SessionManager.getInstance().getUserPCParams(),
                parentalControlLevel = parseInt(Utils.getNested(parentalControlParams, 'parentalControlLevel')),
                parentalControlGenres = Utils.getNested(parentalControlParams, 'parentalGenres'),
                genres = this._currentProgram.getParentalGenres(),
                isGenreProtected,
                VODContentTypes = ['VOD', 'GROUP_OF_BUNDLES', 'BUNDLE'];

            if (VODContentTypes.indexOf(this.getContentType()) >= 0) {
                parentalControlLevel = parseInt(Utils.getNested(parentalControlParams, 'parentalVODControlLevel'));
            }

            isGenreProtected = function () {
                var i;

                if (parentalControlGenres.length) {
                    for (i = 0; i < parentalControlGenres.length; i++) {
                        if (genres.indexOf(parentalControlGenres[i]) > -1) {
                            return true;
                        }
                    }
                }
                return false;
            };

            return !(parentalControlLevel >= this._currentProgram.getPCLevel()) || isGenreProtected();
        },

        /**
         * Handles onFocus.
         *
         * @private
         */
        _onFocus: function () {
            this._handleParental = false;
        },

        /**
         * Attempts to set the program.
         *
         * @param {Object} programParams - Contains the params for setting the program.
         * @param {Object} programParams.program - The program data to set.
         * @param {string} programParams.type - The type.
         * @param {boolean|null} [programParams.hasNextEpisode] - Flag whether it has next episode or not.
         */
        setProgram: function (programParams) {
            var controls = this._controls,
                data = programParams.program,
                type = programParams.type,
                hasNextEpisode = programParams.hasNextEpisode,
                hasRecording,
                hasInfo = !!data.getDetailsAction();

            this._currentProgram = data;

            type = type.toLowerCase();

            this._showPlayer();

            if (type === 'vod' || type === 'vod_movie') {

                // TODO: Fix this at some point.
                type = type.toUpperCase();
            }

            if (type === 'VOD' || type === 'VOD_MOVIE' || type === 'RECORDING') {

                if (hasInfo) {
                    controls.showInfo();
                } else {
                    controls.hideInfo();
                }
                controls.hideNowNext();
                controls.hideLive();
                controls.hideRecord();
                if (hasNextEpisode) {
                    controls.showNextEpisode();
                } else {
                    controls.hideNextEpisode();
                }
                controls.setStyleTo('display', 'block');
            } else if (type === 'live' || type === 'restart') {
                this._miniEPG.setChannel(data.getChannel().getId());
                controls.showInfo();
                controls.showReplay();
                controls.showNowNext();
                controls.disableLiveButton(type !== 'restart');
                controls.showLive();
                controls.hideNextEpisode();

                if (application.getUser().canRecord()) {
                    controls.showRecord();
                } else {
                    controls.hideRecord();
                }
                controls.setStyleTo('display', 'block');
            } else {

                controls.setStyleTo('display', 'none');
            }

            hasRecording = recordingManager.hasRecording(data.getId(), data.getSeriesId());

            if (!hasRecording.episode && !hasRecording.series) {
                controls.onRecord();
            } else {
                controls.onRecording();
            }

            this._zappBanner.setProgram(data, type);
        },

        /**
         * Resets live button and labels to default live state.
         */
        resetLiveLabels: function () {
            this._zappBanner.resetPauseLabel();
            this._controls.disableLiveButton(true);
        },

        /**
         * Attempts to set the full promo video.
         *
         * @param {Object} data - The data to set.
         * @param {string} type - The type.
         */
        setPromo: function (data, type) {
            var controls = this._controls;

            this._currentProgram = data;

            type = type.toLowerCase();

            this._showPlayer();

            controls.hideReplay();
            controls.hideRecord();
            controls.hideInfo();
            controls.hideNowNext();
            controls.hideNextEpisode();
            controls.hideLive();
            controls.hideMenu();
            controls.focus(); // Focus the pause button.
            controls.setStyleTo('display', 'block');

            this._zappBanner.setProgram(data, type);
        },

        /**
         * Returns the channel switcher.
         *
         * @returns {Object} - The channel switcher.
         */
        getChannelSwitcher: function () {
            return this._channelSwitcher;
        },

        /**
         * Returns the zappbanner.
         *
         * @returns {Object} - The zappbanner.
         */
        getZappbanner: function () {
            return this._zappBanner;
        },

        /**
         * Gets the back button.
         *
         * @returns {Object} - The back button.
         */
        getBackButton: function () {
            return this._backButton;
        },

        /**
         * Returns the controls.
         *
         * @returns {Object} - The controls.
         */
        getControls: function () {
            return this._controls;
        },

        /**
         * Removes the mouse over handler.
         */
        removeMouseOverHandler: function removeMouseOverHandlerFn () {

            if (this.outputElement) {

                this.outputElement.onmouseover = null;
            }
        },

        /**
         * Attempts to reset the controls.
         */
        resetControls: function () {
            this._controls.reset();
        },

        /**
         * Updates the seek speed.
         *
         * @param {number} speed - The seek speed.
         */
        updateSeekSpeed: function (speed) {
            this._controls.updateSeekSpeed(speed);
        },

        /**
         * Handles the mouse over event.
         */
        mouseOverHandler: function () {
            if (pointerManager.pointerIsOn() && this._pointerSupport) {
                this.showUI(device.getMediaPlayer().getState() !== 'paused');
            }
        },

        /**
         * Adds the mouse over handler.
         */
        addMouseOverHandler: function () {

            if (this.outputElement) {

                this.outputElement.onmouseover = Utils
                    .bind(this.mouseOverHandler, this);
            }
        },

        /**
         * Should be called when the video player plays.
         */
        onPlay: function () {
            this._controls.onPlay();
        },

        /**
         * Should be called when the video player pauses.
         */
        onPause: function () {
            this._controls.onPause();
        },

        /**
         * Sets the record state.
         */
        onRecord: function () {
            this._controls.onRecord();
        },

        /**
         * Sets the recording state.
         */
        onRecording: function () {
            this._controls.onRecording();
        },

        /**
         * Renders the button.
         *
         * @returns {Object} DOM node.
         */
        render: function render () {
            var element = render.base.call(this, device);

            this.addMouseOverHandler();

            return element;
        },

        /**
         * Disposes of the button.
         */
        dispose: function dispose () {

            this.removeMouseOverHandler();

            dispose.base.call(this);
        },

        /**
         * Focuses the view.
         */
        focus: function focus () {
            focus.base.call(this);
            this._controls.focus();
        },

        /**
         * Show/hide the program detail for the current live program.
         *
         * @param {boolean} show - True to show info detail, false to hide it.
         * @param {Object} programDetails - The program containing the details to show.
         **/
        showProgramInfo: function (show, programDetails) {
            var infoContainerHeight = 0,
                metadataOutputElement,
                descriptionLayoutHeight;

            this._controls.onProgramInfo(!show);

            if (show) {
                this.showMiniEPG(true);
                this._programDetails.setDataItem(programDetails);

                // Retrieve metadata widget and apply dynamic height to the info container.
                metadataOutputElement = this._programDetails.getChildWidgetByIndex(1).outputElement;
                descriptionLayoutHeight = descriptionLayout.height;
                infoContainerHeight = metadataOutputElement.getBoundingClientRect().height;
                infoContainerHeight = (infoContainerHeight < descriptionLayoutHeight ? descriptionLayoutHeight : infoContainerHeight) + 'px';
                this._programDetails.setStyleTo('height', infoContainerHeight);
                this._programDetails.addClass('expand');
            } else {
                this._programDetails.setStyleTo('height', infoContainerHeight);
                this._programDetails.removeClass('expand');
            }
        },

        /**
         * Returns the program details container.
         *
         * @returns {Object} - The program details container.
         */
        getProgramDetails: function () {
            return this._programDetails;
        },

        /**
         * Returns the mini epg.
         *
         * @returns {Object} - The mini epg.
         */
        getMiniEPG: function () {
            return this._miniEPG;
        },

        /**
         * Shows the mini epg.
         *
         * @param {boolean} hide - True if the miniEPG should be hidden.
         */
        showMiniEPG: function (hide) {
            this._controls.onMiniEPG(hide);

            if (this._miniEPG.isExpanded() || hide) {
                this._miniEPG.removeClass('expand');
            } else {
                this.showProgramInfo(false);

                GA.onEvent(
                    'Action',
                    'MiniEPGOpened',
                    {
                        eventLabel: 'LiveTV'
                    }
                );

                this._miniEPG.addClass('expand');
            }
        },

        /**
         * Checks if there's come content currently expanded.
         *
         * @returns {boolean} - True if there's expanded content, false if not.
         */
        isContentExpanded: function () {
            return this._programDetails.isExpanded() || this._miniEPG.isExpanded();
        },

        /**
         * Closes any expanded content (i.e. miniEPG or program's detail).
         */
        closeExpandedContents: function () {
            if (this._programDetails.isExpanded()) {
                this.showProgramInfo(false);
            }

            this.showMiniEPG(true);
        }
    });
});