Untitled

                Never    
const sortSources = (sources) => {
    if (!sources) {
        console.error('sortSources() called with null source list');
        return [];
    }

    const qualities = ['2160', '1440', '1080', '720', '540', '480', '360', '240'];
    let pref = String(USEROPTS.default_quality);
    if (USEROPTS.default_quality == 'best') {
        pref = '2160';
    }
    let idx = qualities.indexOf(pref);
    if (idx < 0) {
        idx = 5 // 480p
    }

    const qualityOrder = qualities.slice(idx).concat(qualities.slice(0, idx).reverse());
    qualityOrder.unshift('auto');
    let sourceOrder = [];
    let flvOrder = [];
    for (j = 0, len = qualityOrder.length; j < len; j++)  {
        quality = qualityOrder[j];
        if (quality in sources) {
            flv = [];
            nonflv = [];
            sources[quality].forEach((source) => {
                source.quality = quality;
                if (source.contentType == 'video/flv') {
                    flv.push(source);
                }
                else {
                    nonflv.push(source);
                }
            })
            sourceOrder = sourceOrder.concat(nonflv);
            flvOrder = flvOrder.concat(flv);
        }
    }

    return sourceOrder.concat(flvOrder).map((source) => {
        return {
            type: source.contentType,
            src: source.link,
            res: source.quality,
            label: getSourceLabel(source)
        }
    })
}

const getSourceLabel = (source) => {
    if (source.res == 'auto') {
        return 'auto';
    }
    else {
        return `${source.quality}p ${source.contentType.split('/')[1]}`;
    }
}

waitUntilDefined(window, 'videojs', () => {
    videojs.options.flash.swf = '/video-js.swf';
})

window.VideoJSPlayer = class VideoJSPlayer extends Player {
    constructor(data) {
        if (!this instanceof VideoJSPlayer) {
            return new VideoJSPlayer(data);
        }
        this.load(data);
    }

    loadPlayer(data) {
        waitUntilDefined(window, 'videojs', () => {
            const attrs = {
                width: '100%',
                height: '100%'
            };

            if (this.mediaType == 'cm' && data.meta.textTracks) {
                attrs.crossorigin = 'anonymous';
            }

            const video = $('<video/>')
                .addClass('video-js vjs-default-skin embed-responsive-item')
                .attr(attrs)
            removeOld(video);

            this.sources = sortSources(data.meta.direct);
            if (this.sources.length == 0) {
                console.error(`VideoJSPlayer::constructor(): data.meta.direct
                               has no sources!`)
                this.mediaType = null;
                return;
            }

            this.sourceIdx = 0;

            // TODO: Refactor VideoJSPlayer to use a preLoad()/load()/postLoad() pattern
            // VideoJSPlayer should provide the core functionality and logic for specific
            // dependent player types (gdrive) should be an extension
            if (data.meta.gdrive_subtitles) {
                data.meta.gdrive_subtitles.available.forEach((subt) => {
                    label = subt.lang_original;
                    if (subt.name) {
                        label += " (#{subt.name})";
                    }
                    $('<track/>').attr({
                        src: "/gdvtt/#{data.id}/#{subt.lang}/#{subt.name}.vtt?\
                                vid=#{data.meta.gdrive_subtitles.vid}",
                        kind: 'subtitles',
                        srclang: subt.lang,
                        label: label
                    }).appendTo(video);
                })
            }

            if (data.meta.textTracks) {
                data.meta.textTracks.forEach((track) => {
                    label = track.name;
                    $('<track/>').attr({
                        src: track.url,
                        kind: 'subtitles',
                        type: track.type,
                        label: label
                    }).appendTo(video);
                })
            }

            this.player = videojs(video[0], {
                    // https://github.com/Dash-Industry-Forum/dash.js/issues/2184
                    autoplay: this.sources[0].type != 'application/dash+xml',
                    controls: true,
                    plugins: {
                        videoJsResolutionSwitcher: {
                            default: this.sources[0].res
                        }
                    }
                }
            );
            this.player.ready(() => {
                // Have to use updateSrc instead of <source> tags
                // see: https://github.com/videojs/video.js/issues/3428
                this.player.updateSrc(this.sources);
                this.player.on('error', () => {
                    err = this.player.error();
                    if (err && err.code == 4) {
                        console.error('Caught error, trying next source');
                        // Does this really need to be done manually?
                        this.sourceIdx++;
                        if (this.sourceIdx < this.sources.length) {
                            this.player.src(this.sources[this.sourceIdx]);
                        }
                        else {
                            console.error('Out of sources, video will not play')
                            if (this.mediaType == 'gd' && !window.hasDriveUserscript) {
                                window.promptToInstallDriveUserscript();
                            }
                        }
                    }
                });
                this.setVolume(VOLUME);
                this.player.on('ended', () => {
                    if (CLIENT.leader) {
                        socket.emit('playNext');
                    }
                });

                this.player.on('pause', () => {
                    this.paused = true;
                    if (CLIENT.leader) {
                        sendVideoUpdate();
                    }
                });

                this.player.on('play', () => {
                    this.paused = false;
                    if (CLIENT.leader) {
                        sendVideoUpdate();
                    }
                });

                // Workaround for IE-- even after seeking completes, the loading
                // spinner remains.
                this.player.on('seeked', () => {
                    $('.vjs-waiting').removeClass('vjs-waiting');
                });

                // Workaround for Chrome-- it seems that the click bindings for
                // the subtitle menu aren't quite set up until after the ready
                // event finishes, so set a timeout for 1ms to force this code
                // not to run until the ready() function returns.
                setTimeout(() => {
                    $('#ytapiplayer .vjs-subtitles-button .vjs-menu-item').each((i, elem) => {
                        textNode = elem.childNodes[0];
                        if (textNode.textContent == localStorage.lastSubtitle) {
                            elem.click();
                        }

                        elem.onclick = () => {
                            if (elem.attributes['aria-checked'].value == 'true') {
                                localStorage.lastSubtitle = textNode.textContent;
                            }
                        }
                    });
                }, 1);
            });
        });
    }

    load(data) {
        this.setMediaProperties(data);
        // Note: VideoJS does have facilities for loading new videos into the
        // existing player object, however it appears to be pretty glitchy when
        // a video can't be played (either previous or next video).  It's safer
        // to just reset the entire thing.
        this.destroy();
        this.loadPlayer(data);
    }

    play() {
        this.paused = false;
        if (this.player && this.player.readyState() > 0) {
            this.player.play();
        }
    }

    pause() {
        this.paused = true;
        if (this.player && this.player.readyState() > 0) {
            this.player.pause();
        }
    }

    seekTo(time) {
        if (this.player && this.player.readyState() > 0) {
            this.player.currentTime(time);
        }
    }

    setVolume(volume) {
        if (this.player)  {
            this.player.volume(volume);
        }
    }

    getTime(cb) {
        if (this.player && this.player.readyState() > 0) {
            cb(this.player.currentTime());
        }
        else {
            cb(0);
        }
    }

    getVolume(cb) {
        if (this.player && this.player.readyState() > 0) {
            if (this.player.muted()) {
                cb(0);
            }
            else {
                cb(this.player.volume());
            }
        }
        else {
            cb(VOLUME);
        }
    }

    destroy() {
        removeOld();
        if (this.player) {
            this.player.dispose();
        }
    }
}

Raw Text