<commit_before>/* global $, APP, YT, onPlayerReady, onPlayerStateChange, onPlayerError */

import messageHandler from '../util/MessageHandler';
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';

import VideoLayout from "../videolayout/VideoLayout";
import LargeContainer from '../videolayout/LargeContainer';
import SmallVideo from '../videolayout/SmallVideo';
import FilmStrip from '../videolayout/FilmStrip';
import ToolbarToggler from "../toolbars/ToolbarToggler";

export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";

/**
 * Example shared video link.
 * @type {string}
 */
const defaultSharedVideoLink = "https://www.youtube.com/watch?v=xNXN7CZk8X0";
const updateInterval = 5000; // milliseconds
/**
 * Manager of shared video.
 */
export default class SharedVideoManager {
    constructor (emitter) {
        this.emitter = emitter;
        this.isSharedVideoShown = false;
        this.isPlayerAPILoaded = false;
    }

    /**
     * Starts shared video by asking user for url, or if its already working
     * asks whether the user wants to stop sharing the video.
     */
    toggleSharedVideo () {
        if(!this.isSharedVideoShown) {
            requestVideoLink().then(
                    url => this.emitter.emit(
                                UIEvents.UPDATE_SHARED_VIDEO, url, 'start'),
                    err => console.error('SHARED VIDEO CANCELED', err)
            );
            return;
        }

        if(APP.conference.isLocalId(this.from)) {
            showStopVideoPropmpt().then(() =>
                this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, null, 'stop'));
        } else {
            messageHandler.openMessageDialog(
                "dialog.shareVideoTitle",
                "dialog.alreadySharedVideoMsg"
            );
        }
    }

    /**
     * Shows the player component and starts the checking function
     * that will be sending updates, if we are the one shared the video
     * @param id the id of the sender of the command
     * @param url the video url
     * @param attributes
     */
    showSharedVideo (id, url, attributes) {
        if (this.isSharedVideoShown)
            return;

        // the video url
        this.url = url;

        // the owner of the video
        this.from = id;

        // This code loads the IFrame Player API code asynchronously.
        var tag = document.createElement('script');

        tag.src = "https://www.youtube.com/iframe_api";
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

        // sometimes we receive errors like player not defined
        // or player.pauseVideo is not a function
        // we need to operate with player after start playing
        // self.player will be defined once it start playing
        // and will process any initial attributes if any
        this.initialAttributes = null;

        var self = this;
        if(self.isPlayerAPILoaded)
            window.onYouTubeIframeAPIReady();
        else
            window.onYouTubeIframeAPIReady = function() {
                self.isPlayerAPILoaded = true;
                let showControls = APP.conference.isLocalId(self.from) ? 1 : 0;
                new YT.Player('sharedVideoIFrame', {
                    height: '100%',
                    width: '100%',
                    videoId: self.url,
                    playerVars: {
                        'origin': location.origin,
                        'fs': '0',
                        'autoplay': 0,
                        'controls': showControls,
                        'rel' : 0
                    },
                    events: {
                        'onReady': onPlayerReady,
                        'onStateChange': onPlayerStateChange,
                        'onError': onPlayerError
                    }
                });
            };

        window.onPlayerStateChange = function(event) {
            if (event.data == YT.PlayerState.PLAYING) {
                self.playerPaused = false;

                self.player = event.target;

                if(self.initialAttributes)
                {
                    self.processAttributes(
                        self.player, self.initialAttributes, self.playerPaused);
                    self.initialAttributes = null;
                }

                self.updateCheck();
            } else if (event.data == YT.PlayerState.PAUSED) {
                self.playerPaused = true;
                self.updateCheck(true);
            }
        };

        window.onPlayerReady = function(event) {
            let player = event.target;
            // do not relay on autoplay as it is not sending all of the events
            // in onPlayerStateChange
            player.playVideo();

            let thumb = new SharedVideoThumb(self.url);
            thumb.setDisplayName(player.getVideoData().title);
            VideoLayout.addParticipantContainer(self.url, thumb);

            let iframe = player.getIframe();
            self.sharedVideo = new SharedVideoContainer(
                {url, iframe, player});

            VideoLayout.addLargeVideoContainer(
                SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
            VideoLayout.handleVideoThumbClicked(self.url);

            self.isSharedVideoShown = true;

            // If we are sending the command and we are starting the player
            // we need to continuously send the player current time position
            if(APP.conference.isLocalId(self.from)) {
                self.intervalId = setInterval(
                    self.updateCheck.bind(self),
                    updateInterval);
            }

            if(self.player)
                self.processAttributes(
                    self.player, attributes, self.playerPaused);
            else {
                self.initialAttributes = attributes;
            }
        };

        window.onPlayerError = function(event) {
            console.error("Error in the player:" + event.data);
        };
    }

    /**
     * Process attributes, whether player needs to be paused or seek.
     * @param player the player to operate over
     * @param attributes the attributes with the player state we want
     * @param playerPaused current saved state for the player
     */
    processAttributes (player, attributes, playerPaused)
    {
        if(!attributes)
            return;

        if (attributes.state == 'playing') {

            this.processTime(player, attributes);

            // lets check the volume
            if (attributes.volume !== undefined &&
                player.getVolume() != attributes.volume) {
                player.setVolume(attributes.volume);
                console.info("Player change of volume:" + attributes.volume);
            }

            if(playerPaused)
                player.playVideo();

        } else if (attributes.state == 'pause') {
            // if its not paused, pause it
            player.pauseVideo();

            this.processTime(player, attributes);
        }
    }

    /**
     * Check for time in attributes and if needed seek in current player
     * @param player the player to operate over
     * @param attributes the attributes with the player state we want
     */
    processTime (player, attributes)
    {
        // check received time and current time
        let currentPosition = player.getCurrentTime();
        let diff = Math.abs(attributes.time - currentPosition);

        // if we drift more than the interval for checking
        // sync, the interval is in milliseconds
        if(diff > updateInterval/1000) {
            console.info("Player seekTo:", attributes.time,
                " current time is:", currentPosition, " diff:", diff);
            player.seekTo(attributes.time);
        }
    }

    /**
     * Checks current state of the player and fire an event with the values.
     */
    updateCheck(sendPauseEvent)
    {
        // ignore update checks if we are not the owner of the video
        // or there is still no player defined
        if(!APP.conference.isLocalId(this.from) || !this.player)
            return;

        let state = this.player.getPlayerState();
        // if its paused and haven't been pause - send paused
        if (state === YT.PlayerState.PAUSED && sendPauseEvent) {
            this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
                this.url, 'pause', this.player.getCurrentTime());
        }
        // if its playing and it was paused - send update with time
        // if its playing and was playing just send update with time
        else if (state === YT.PlayerState.PLAYING) {
            this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
                this.url, 'playing',
                this.player.getCurrentTime(),
                this.player.isMuted() ? 0 : this.player.getVolume());
        }
    }

    /**
     * Updates video, if its not playing and needs starting or
     * if its playing and needs to be paysed
     * @param id the id of the sender of the command
     * @param url the video url
     * @param attributes
     */
    updateSharedVideo (id, url, attributes) {
        // if we are sending the event ignore
        if(APP.conference.isLocalId(this.from)) {
            return;
        }

        if(!this.isSharedVideoShown) {
            this.showSharedVideo(id, url, attributes);
            return;
        }

        if(!this.player)
            this.initialAttributes = attributes;
        else {
            this.processAttributes(this.player, attributes, this.playerPaused);
        }
    }

    /**
     * Stop shared video if it is currently showed. If the user started the
     * shared video is the one in the id (called when user
     * left and we want to remove video if the user sharing it left).
     * @param id the id of the sender of the command
     */
    stopSharedVideo (id) {
        if (!this.isSharedVideoShown)
            return;

        if(this.from !== id)
            return;

        if(this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }

        VideoLayout.removeParticipantContainer(this.url);

        VideoLayout.showLargeVideoContainer(SHARED_VIDEO_CONTAINER_TYPE, false)
            .then(() => {
                VideoLayout.removeLargeVideoContainer(
                    SHARED_VIDEO_CONTAINER_TYPE);

                this.player.destroy();
                this.player = null;
        });

        this.url = null;
        this.isSharedVideoShown = false;
    }
}

/**
 * Container for shared video iframe.
 */
class SharedVideoContainer extends LargeContainer {

    constructor ({url, iframe, player}) {
        super();

        this.$iframe = $(iframe);
        this.url = url;
        this.player = player;
    }

    get $video () {
        return this.$iframe;
    }

    show () {
        let self = this;
        return new Promise(resolve => {
            this.$iframe.fadeIn(300, () => {
                self.bodyBackground = document.body.style.background;
                document.body.style.background = 'black';
                this.$iframe.css({opacity: 1});
                resolve();
            });
        });
    }

    hide () {
        let self = this;
        return new Promise(resolve => {
            this.$iframe.fadeOut(300, () => {
                document.body.style.background = self.bodyBackground;
                this.$iframe.css({opacity: 0});
                resolve();<commit_msg>Docks toolbar when shared video is shown.<commit_after>/* global $, APP, YT, onPlayerReady, onPlayerStateChange, onPlayerError */

import messageHandler from '../util/MessageHandler';
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';

import VideoLayout from "../videolayout/VideoLayout";
import LargeContainer from '../videolayout/LargeContainer';
import SmallVideo from '../videolayout/SmallVideo';
import FilmStrip from '../videolayout/FilmStrip';
import ToolbarToggler from "../toolbars/ToolbarToggler";

export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";

/**
 * Example shared video link.
 * @type {string}
 */
const defaultSharedVideoLink = "https://www.youtube.com/watch?v=xNXN7CZk8X0";
const updateInterval = 5000; // milliseconds
/**
 * Manager of shared video.
 */
export default class SharedVideoManager {
    constructor (emitter) {
        this.emitter = emitter;
        this.isSharedVideoShown = false;
        this.isPlayerAPILoaded = false;
    }

    /**
     * Starts shared video by asking user for url, or if its already working
     * asks whether the user wants to stop sharing the video.
     */
    toggleSharedVideo () {
        if(!this.isSharedVideoShown) {
            requestVideoLink().then(
                    url => this.emitter.emit(
                                UIEvents.UPDATE_SHARED_VIDEO, url, 'start'),
                    err => console.error('SHARED VIDEO CANCELED', err)
            );
            return;
        }

        if(APP.conference.isLocalId(this.from)) {
            showStopVideoPropmpt().then(() =>
                this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, null, 'stop'));
        } else {
            messageHandler.openMessageDialog(
                "dialog.shareVideoTitle",
                "dialog.alreadySharedVideoMsg"
            );
        }
    }

    /**
     * Shows the player component and starts the checking function
     * that will be sending updates, if we are the one shared the video
     * @param id the id of the sender of the command
     * @param url the video url
     * @param attributes
     */
    showSharedVideo (id, url, attributes) {
        if (this.isSharedVideoShown)
            return;

        // the video url
        this.url = url;

        // the owner of the video
        this.from = id;

        // This code loads the IFrame Player API code asynchronously.
        var tag = document.createElement('script');

        tag.src = "https://www.youtube.com/iframe_api";
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

        // sometimes we receive errors like player not defined
        // or player.pauseVideo is not a function
        // we need to operate with player after start playing
        // self.player will be defined once it start playing
        // and will process any initial attributes if any
        this.initialAttributes = null;

        var self = this;
        if(self.isPlayerAPILoaded)
            window.onYouTubeIframeAPIReady();
        else
            window.onYouTubeIframeAPIReady = function() {
                self.isPlayerAPILoaded = true;
                let showControls = APP.conference.isLocalId(self.from) ? 1 : 0;
                new YT.Player('sharedVideoIFrame', {
                    height: '100%',
                    width: '100%',
                    videoId: self.url,
                    playerVars: {
                        'origin': location.origin,
                        'fs': '0',
                        'autoplay': 0,
                        'controls': showControls,
                        'rel' : 0
                    },
                    events: {
                        'onReady': onPlayerReady,
                        'onStateChange': onPlayerStateChange,
                        'onError': onPlayerError
                    }
                });
            };

        window.onPlayerStateChange = function(event) {
            if (event.data == YT.PlayerState.PLAYING) {
                self.playerPaused = false;

                self.player = event.target;

                if(self.initialAttributes)
                {
                    self.processAttributes(
                        self.player, self.initialAttributes, self.playerPaused);
                    self.initialAttributes = null;
                }

                self.updateCheck();
            } else if (event.data == YT.PlayerState.PAUSED) {
                self.playerPaused = true;
                self.updateCheck(true);
            }
        };

        window.onPlayerReady = function(event) {
            let player = event.target;
            // do not relay on autoplay as it is not sending all of the events
            // in onPlayerStateChange
            player.playVideo();

            let thumb = new SharedVideoThumb(self.url);
            thumb.setDisplayName(player.getVideoData().title);
            VideoLayout.addParticipantContainer(self.url, thumb);

            let iframe = player.getIframe();
            self.sharedVideo = new SharedVideoContainer(
                {url, iframe, player});

            VideoLayout.addLargeVideoContainer(
                SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
            VideoLayout.handleVideoThumbClicked(self.url);

            self.isSharedVideoShown = true;

            // If we are sending the command and we are starting the player
            // we need to continuously send the player current time position
            if(APP.conference.isLocalId(self.from)) {
                self.intervalId = setInterval(
                    self.updateCheck.bind(self),
                    updateInterval);
            }

            if(self.player)
                self.processAttributes(
                    self.player, attributes, self.playerPaused);
            else {
                self.initialAttributes = attributes;
            }
        };

        window.onPlayerError = function(event) {
            console.error("Error in the player:" + event.data);
        };
    }

    /**
     * Process attributes, whether player needs to be paused or seek.
     * @param player the player to operate over
     * @param attributes the attributes with the player state we want
     * @param playerPaused current saved state for the player
     */
    processAttributes (player, attributes, playerPaused)
    {
        if(!attributes)
            return;

        if (attributes.state == 'playing') {

            this.processTime(player, attributes);

            // lets check the volume
            if (attributes.volume !== undefined &&
                player.getVolume() != attributes.volume) {
                player.setVolume(attributes.volume);
                console.info("Player change of volume:" + attributes.volume);
            }

            if(playerPaused)
                player.playVideo();

        } else if (attributes.state == 'pause') {
            // if its not paused, pause it
            player.pauseVideo();

            this.processTime(player, attributes);
        }
    }

    /**
     * Check for time in attributes and if needed seek in current player
     * @param player the player to operate over
     * @param attributes the attributes with the player state we want
     */
    processTime (player, attributes)
    {
        // check received time and current time
        let currentPosition = player.getCurrentTime();
        let diff = Math.abs(attributes.time - currentPosition);

        // if we drift more than the interval for checking
        // sync, the interval is in milliseconds
        if(diff > updateInterval/1000) {
            console.info("Player seekTo:", attributes.time,
                " current time is:", currentPosition, " diff:", diff);
            player.seekTo(attributes.time);
        }
    }

    /**
     * Checks current state of the player and fire an event with the values.
     */
    updateCheck(sendPauseEvent)
    {
        // ignore update checks if we are not the owner of the video
        // or there is still no player defined
        if(!APP.conference.isLocalId(this.from) || !this.player)
            return;

        let state = this.player.getPlayerState();
        // if its paused and haven't been pause - send paused
        if (state === YT.PlayerState.PAUSED && sendPauseEvent) {
            this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
                this.url, 'pause', this.player.getCurrentTime());
        }
        // if its playing and it was paused - send update with time
        // if its playing and was playing just send update with time
        else if (state === YT.PlayerState.PLAYING) {
            this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
                this.url, 'playing',
                this.player.getCurrentTime(),
                this.player.isMuted() ? 0 : this.player.getVolume());
        }
    }

    /**
     * Updates video, if its not playing and needs starting or
     * if its playing and needs to be paysed
     * @param id the id of the sender of the command
     * @param url the video url
     * @param attributes
     */
    updateSharedVideo (id, url, attributes) {
        // if we are sending the event ignore
        if(APP.conference.isLocalId(this.from)) {
            return;
        }

        if(!this.isSharedVideoShown) {
            this.showSharedVideo(id, url, attributes);
            return;
        }

        if(!this.player)
            this.initialAttributes = attributes;
        else {
            this.processAttributes(this.player, attributes, this.playerPaused);
        }
    }

    /**
     * Stop shared video if it is currently showed. If the user started the
     * shared video is the one in the id (called when user
     * left and we want to remove video if the user sharing it left).
     * @param id the id of the sender of the command
     */
    stopSharedVideo (id) {
        if (!this.isSharedVideoShown)
            return;

        if(this.from !== id)
            return;

        if(this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }

        VideoLayout.removeParticipantContainer(this.url);

        VideoLayout.showLargeVideoContainer(SHARED_VIDEO_CONTAINER_TYPE, false)
            .then(() => {
                VideoLayout.removeLargeVideoContainer(
                    SHARED_VIDEO_CONTAINER_TYPE);

                this.player.destroy();
                this.player = null;
        });

        this.url = null;
        this.isSharedVideoShown = false;
    }
}

/**
 * Container for shared video iframe.
 */
class SharedVideoContainer extends LargeContainer {

    constructor ({url, iframe, player}) {
        super();

        this.$iframe = $(iframe);
        this.url = url;
        this.player = player;
    }

    get $video () {
        return this.$iframe;
    }

    show () {
        let self = this;
        return new Promise(resolve => {
            this.$iframe.fadeIn(300, () => {
                self.bodyBackground = document.body.style.background;
                document.body.style.background = 'black';
                this.$iframe.css({opacity: 1});
                ToolbarToggler.dockToolbar(true);
                resolve();
            });
        });
    }

    hide () {
        let self = this;
        ToolbarToggler.dockToolbar(false);
        return new Promise(resolve => {
            this.$iframe.fadeOut(300, () => {
                document.body.style.background = self.bodyBackground;
                this.$iframe.css({opacity: 0});
                resolve();
{'fraction_non_alphanumeric': 0.0953790706654653, 'fraction_numerical': 0.0027459561505127216, 'mean_word_length': 1.7235335358728674, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 4, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5921994', 'n_tokens_mistral': 6156, 'n_tokens_neox': 5833, 'n_words': 1623}