| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089 | // ==========================================================================// Plyr// plyr.js v1.3.6// https://github.com/selz/plyr// License: The MIT License (MIT)// ==========================================================================// Credits: http://paypal.github.io/accessible-html5-video-player/// ==========================================================================(function (api) {    'use strict';    /*global YT*/    // Globals    var fullscreen, config, callbacks = {        youtube: []    };    // Default config    var defaults = {        enabled: true,        debug: false,        seekTime: 10,        volume: 5,        click: true,        tooltips: true,        displayDuration: true,        iconPrefix: 'icon',        selectors: {            container: '.player',            controls: '.player-controls',            labels: '[data-player] .sr-only, label .sr-only',            buttons: {                seek: '[data-player="seek"]',                play: '[data-player="play"]',                pause: '[data-player="pause"]',                restart: '[data-player="restart"]',                rewind: '[data-player="rewind"]',                forward: '[data-player="fast-forward"]',                mute: '[data-player="mute"]',                volume: '[data-player="volume"]',                captions: '[data-player="captions"]',                fullscreen: '[data-player="fullscreen"]'            },            progress: {                container: '.player-progress',                buffer: '.player-progress-buffer',                played: '.player-progress-played'            },            captions: '.player-captions',            currentTime: '.player-current-time',            duration: '.player-duration'        },        classes: {            videoWrapper: 'player-video-wrapper',            embedWrapper: 'player-video-embed',            type: 'player-{0}',            stopped: 'stopped',            playing: 'playing',            muted: 'muted',            loading: 'loading',            tooltip: 'player-tooltip',            hidden: 'sr-only',            hover: 'player-hover',            captions: {                enabled: 'captions-enabled',                active: 'captions-active'            },            fullscreen: {                enabled: 'fullscreen-enabled',                active: 'fullscreen-active',                hideControls: 'fullscreen-hide-controls'            }        },        captions: {            defaultActive: false        },        fullscreen: {            enabled: false,            fallback: true,            hideControls: true        },        storage: {            enabled: true,            key: 'plyr_volume'        },        controls: ['restart', 'rewind', 'play', 'fast-forward', 'current-time', 'duration', 'mute', 'volume', /*'captions',*/ 'fullscreen'],        i18n: {            restart: '重新播放',            rewind: '后退{seektime}秒',            play: '播放',            pause: '暂停',            forward: '快进{seektime}秒',            played: '播放中',            buffered: '缓冲中',            currentTime: '当前时间',            duration: '持续时间',            volume: '音量',            toggleMute: '静音',            toggleCaptions: '字幕',            toggleFullscreen: '全屏'        }    };    // Build the default HTML    function _buildControls() {        // Open and add the progress and seek elements        var html = [        '<div class="player-controls">',            '<div class="player-progress">',                '<label for="seek{id}" class="sr-only">Seek</label>',                '<input id="seek{id}" class="player-progress-seek" type="range" min="0" max="100" step="0.5" value="0" data-player="seek">',                '<progress class="player-progress-played" max="100" value="0">',                    '<span>0</span>% ' + config.i18n.played,                '</progress>',                '<progress class="player-progress-buffer" max="100" value="0">',                    '<span>0</span>% ' + config.i18n.buffered,                '</progress>',            '</div>',            '<span class="player-controls-left">'];        // Restart button        if (_inArray(config.controls, 'restart')) {            html.push(                '<button type="button" data-player="restart">',                '<svg><use xlink:href="#' + config.iconPrefix + '-restart" /></svg>',                '<span class="sr-only">' + config.i18n.restart + '</span>',                '</button>'            );        }        // Rewind button        if (_inArray(config.controls, 'rewind')) {            html.push(                '<button type="button" data-player="rewind">',                '<svg><use xlink:href="#' + config.iconPrefix + '-rewind" /></svg>',                '<span class="sr-only">' + config.i18n.rewind + '</span>',                '</button>'            );        }        // Play/pause button        if (_inArray(config.controls, 'play')) {            html.push(                '<button type="button" data-player="play">',                '<svg><use xlink:href="#' + config.iconPrefix + '-play" /></svg>',                '<span class="sr-only">' + config.i18n.play + '</span>',                '</button>',                '<button type="button" data-player="pause">',                '<svg><use xlink:href="#' + config.iconPrefix + '-pause" /></svg>',                '<span class="sr-only">' + config.i18n.pause + '</span>',                '</button>'            );        }        // Fast forward button        if (_inArray(config.controls, 'fast-forward')) {            html.push(                '<button type="button" data-player="fast-forward">',                '<svg><use xlink:href="#' + config.iconPrefix + '-fast-forward" /></svg>',                '<span class="sr-only">' + config.i18n.forward + '</span>',                '</button>'            );        }        // Media current time display        if (_inArray(config.controls, 'current-time')) {            html.push(                '<span class="player-time">',                '<span class="sr-only">' + config.i18n.currentTime + '</span>',                '<span class="player-current-time">00:00</span>',                '</span>'            );        }        // Media duration display        if (_inArray(config.controls, 'duration')) {            html.push(                '<span class="player-time">',                '<span class="sr-only">' + config.i18n.duration + '</span>',                '<span class="player-duration">00:00</span>',                '</span>'            );        }        // Close left controls        html.push(            '</span>',            '<span class="player-controls-right">'        );        // Toggle mute button        if (_inArray(config.controls, 'mute')) {            html.push(                '<button type="button" data-player="mute">',                '<svg class="icon-muted"><use xlink:href="#' + config.iconPrefix + '-muted" /></svg>',                '<svg><use xlink:href="#' + config.iconPrefix + '-volume" /></svg>',                '<span class="sr-only">' + config.i18n.toggleMute + '</span>',                '</button>'            );        }        // Volume range control        if (_inArray(config.controls, 'volume')) {            html.push(                '<label for="volume{id}" class="sr-only">' + config.i18n.volume + '</label>',                '<input id="volume{id}" class="player-volume" type="range" min="0" max="10" value="5" data-player="volume">'            );        }        // Toggle captions button        if (_inArray(config.controls, 'captions')) {            html.push(                '<button type="button" data-player="captions">',                '<svg class="icon-captions-on"><use xlink:href="#' + config.iconPrefix + '-captions-on" /></svg>',                '<svg><use xlink:href="#' + config.iconPrefix + '-captions-off" /></svg>',                '<span class="sr-only">' + config.i18n.toggleCaptions + '</span>',                '</button>'            );        }        // Toggle fullscreen button        if (_inArray(config.controls, 'fullscreen')) {            html.push(                '<button type="button" data-player="fullscreen">',                '<svg class="icon-exit-fullscreen"><use xlink:href="#' + config.iconPrefix + '-exit-fullscreen" /></svg>',                '<svg><use xlink:href="#' + config.iconPrefix + '-enter-fullscreen" /></svg>',                '<span class="sr-only">' + config.i18n.toggleFullscreen + '</span>',                '</button>'            );        }        // Close everything        html.push(            '</span>',            '</div>'        );        return html.join('');    }    // Debugging    function _log(text, error) {        if (config.debug && window.console) {            console[(error ? 'error' : 'log')](text);        }    }    // Credits: http://paypal.github.io/accessible-html5-video-player/    // Unfortunately, due to mixed support, UA sniffing is required    function _browserSniff() {        var nAgt = navigator.userAgent,            name = navigator.appName,            fullVersion = '' + parseFloat(navigator.appVersion),            majorVersion = parseInt(navigator.appVersion, 10),            nameOffset,            verOffset,            ix;        // MSIE 11        if ((navigator.appVersion.indexOf('Windows NT') !== -1) && (navigator.appVersion.indexOf('rv:11') !== -1)) {            name = 'IE';            fullVersion = '11;';        }        // MSIE        else if ((verOffset = nAgt.indexOf('MSIE')) !== -1) {            name = 'IE';            fullVersion = nAgt.substring(verOffset + 5);        }        // Chrome        else if ((verOffset = nAgt.indexOf('Chrome')) !== -1) {            name = 'Chrome';            fullVersion = nAgt.substring(verOffset + 7);        }        // Safari        else if ((verOffset = nAgt.indexOf('Safari')) !== -1) {            name = 'Safari';            fullVersion = nAgt.substring(verOffset + 7);            if ((verOffset = nAgt.indexOf('Version')) !== -1) {                fullVersion = nAgt.substring(verOffset + 8);            }        }        // Firefox        else if ((verOffset = nAgt.indexOf('Firefox')) !== -1) {            name = 'Firefox';            fullVersion = nAgt.substring(verOffset + 8);        }        // In most other browsers, 'name/version' is at the end of userAgent        else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {            name = nAgt.substring(nameOffset, verOffset);            fullVersion = nAgt.substring(verOffset + 1);            if (name.toLowerCase() == name.toUpperCase()) {                name = navigator.appName;            }        }        // Trim the fullVersion string at semicolon/space if present        if ((ix = fullVersion.indexOf(';')) !== -1) {            fullVersion = fullVersion.substring(0, ix);        }        if ((ix = fullVersion.indexOf(' ')) !== -1) {            fullVersion = fullVersion.substring(0, ix);        }        // Get major version        majorVersion = parseInt('' + fullVersion, 10);        if (isNaN(majorVersion)) {            fullVersion = '' + parseFloat(navigator.appVersion);            majorVersion = parseInt(navigator.appVersion, 10);        }        // Return data        return {            name: name,            version: majorVersion,            ios: /(iPad|iPhone|iPod)/g.test(navigator.platform)        };    }    // Check for mime type support against a player instance    // Credits: http://diveintohtml5.info/everything.html    // Related: http://www.leanbackplayer.com/test/h5mt.html    function _supportMime(player, mimeType) {        var media = player.media;        // Only check video types for video players        if (player.type == 'video') {            // Check type            switch (mimeType) {            case 'video/webm':                return !!(media.canPlayType && media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, ''));            case 'video/mp4':                return !!(media.canPlayType && media.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ''));            case 'video/ogg':                return !!(media.canPlayType && media.canPlayType('video/ogg; codecs="theora"').replace(/no/, ''));            }        }        // Only check audio types for audio players        else if (player.type == 'audio') {            // Check type            switch (mimeType) {            case 'audio/mpeg':                return !!(media.canPlayType && media.canPlayType('audio/mpeg;').replace(/no/, ''));            case 'audio/ogg':                return !!(media.canPlayType && media.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''));            case 'audio/wav':                return !!(media.canPlayType && media.canPlayType('audio/wav; codecs="1"').replace(/no/, ''));            }        }        // If we got this far, we're stuffed        return false;    }    // Inject a script    function _injectScript(source) {        if (document.querySelectorAll('script[src="' + source + '"]').length) {            return;        }        var tag = document.createElement('script');        tag.src = source;        var firstScriptTag = document.getElementsByTagName('script')[0];        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);    }    // Element exists in an array    function _inArray(haystack, needle) {        return Array.prototype.indexOf && (haystack.indexOf(needle) != -1);    }    // Replace all    function _replaceAll(string, find, replace) {        return string.replace(new RegExp(find.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'), replace);    }    // Wrap an element    function _wrap(elements, wrapper) {        // Convert `elements` to an array, if necessary.        if (!elements.length) {            elements = [elements];        }        // Loops backwards to prevent having to clone the wrapper on the        // first element (see `child` below).        for (var i = elements.length - 1; i >= 0; i--) {            var child = (i > 0) ? wrapper.cloneNode(true) : wrapper;            var element = elements[i];            // Cache the current parent and sibling.            var parent = element.parentNode;            var sibling = element.nextSibling;            // Wrap the element (is automatically removed from its current            // parent).            child.appendChild(element);            // If the element had a sibling, insert the wrapper before            // the sibling to maintain the HTML structure; otherwise, just            // append it to the parent.            if (sibling) {                parent.insertBefore(child, sibling);            } else {                parent.appendChild(child);            }        }    }    // Unwrap an element    // http://plainjs.com/javascript/manipulation/unwrap-a-dom-element-35/    function _unwrap(wrapper) {        // Get the element's parent node        var parent = wrapper.parentNode;        // Move all children out of the element        while (wrapper.firstChild) {            parent.insertBefore(wrapper.firstChild, wrapper);        }        // Remove the empty element        parent.removeChild(wrapper);    }    // Remove an element    function _remove(element) {        element.parentNode.removeChild(element);    }    // Prepend child    function _prependChild(parent, element) {        parent.insertBefore(element, parent.firstChild);    }    // Set attributes    function _setAttributes(element, attributes) {        for (var key in attributes) {            element.setAttribute(key, attributes[key]);        }    }    // Toggle class on an element    function _toggleClass(element, name, state) {        if (element) {            if (element.classList) {                element.classList[state ? 'add' : 'remove'](name);            } else {                var className = (' ' + element.className + ' ').replace(/\s+/g, ' ').replace(' ' + name + ' ', '');                element.className = className + (state ? ' ' + name : '');            }        }    }    // Toggle event    function _toggleHandler(element, events, callback, toggle) {        var eventList = events.split(' ');        // If a nodelist is passed, call itself on each node        if (element instanceof NodeList) {            for (var x = 0; x < element.length; x++) {                if (element[x] instanceof Node) {                    _toggleHandler(element[x], arguments[1], arguments[2], arguments[3]);                }            }            return;        }        // If a single node is passed, bind the event listener        for (var i = 0; i < eventList.length; i++) {            element[toggle ? 'addEventListener' : 'removeEventListener'](eventList[i], callback, false);        }    }    // Bind event    function _on(element, events, callback) {        if (element) {            _toggleHandler(element, events, callback, true);        }    }    // Unbind event    function _off(element, events, callback) {        if (element) {            _toggleHandler(element, events, callback, false);        }    }    // Trigger event    function _triggerEvent(element, event) {        // Create faux event        var fauxEvent = document.createEvent('MouseEvents');        // Set the event type        fauxEvent.initEvent(event, true, true);        // Dispatch the event        element.dispatchEvent(fauxEvent);    }    // Toggle aria-pressed state on a toggle button    function _toggleState(target, state) {        // Get state        state = (typeof state === 'boolean' ? state : !target.getAttribute('aria-pressed'));        // Set the attribute on target        target.setAttribute('aria-pressed', state);        return state;    }    // Get percentage    function _getPercentage(current, max) {        if (current === 0 || max === 0 || isNaN(current) || isNaN(max)) {            return 0;        }        return ((current / max) * 100).toFixed(2);    }    // Deep extend/merge two Objects    // http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/    // Removed call to arguments.callee (used explicit function name instead)    function _extend(destination, source) {        for (var property in source) {            if (source[property] && source[property].constructor && source[property].constructor === Object) {                destination[property] = destination[property] || {};                _extend(destination[property], source[property]);            } else {                destination[property] = source[property];            }        }        return destination;    }    // Fullscreen API    function _fullscreen() {        var fullscreen = {                supportsFullScreen: false,                isFullScreen: function () {                    return false;                },                requestFullScreen: function () {},                cancelFullScreen: function () {},                fullScreenEventName: '',                element: null,                prefix: ''            },            browserPrefixes = 'webkit moz o ms khtml'.split(' ');        // Check for native support        if (typeof document.cancelFullScreen !== 'undefined') {            fullscreen.supportsFullScreen = true;        } else {            // Check for fullscreen support by vendor prefix            for (var i = 0, il = browserPrefixes.length; i < il; i++) {                fullscreen.prefix = browserPrefixes[i];                if (typeof document[fullscreen.prefix + 'CancelFullScreen'] !== 'undefined') {                    fullscreen.supportsFullScreen = true;                    break;                }                // Special case for MS (when isn't it?)                else if (typeof document.msExitFullscreen !== 'undefined' && document.msFullscreenEnabled) {                    fullscreen.prefix = 'ms';                    fullscreen.supportsFullScreen = true;                    break;                }            }        }        // Update methods to do something useful        if (fullscreen.supportsFullScreen) {            // Yet again Microsoft awesomeness,            // Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes            fullscreen.fullScreenEventName = (fullscreen.prefix == 'ms' ? 'MSFullscreenChange' : fullscreen.prefix + 'fullscreenchange');            fullscreen.isFullScreen = function (element) {                if (typeof element === 'undefined') {                    element = document.body;                }                switch (this.prefix) {                case '':                    return document.fullscreenElement == element;                case 'moz':                    return document.mozFullScreenElement == element;                default:                    return document[this.prefix + 'FullscreenElement'] == element;                }            };            fullscreen.requestFullScreen = function (element) {                if (typeof element === 'undefined') {                    element = document.body;                }                return (this.prefix === '') ? element.requestFullScreen() : element[this.prefix + (this.prefix == 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();            };            fullscreen.cancelFullScreen = function () {                return (this.prefix === '') ? document.cancelFullScreen() : document[this.prefix + (this.prefix == 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();            };            fullscreen.element = function () {                return (this.prefix === '') ? document.fullscreenElement : document[this.prefix + 'FullscreenElement'];            };        }        return fullscreen;    }    // Local storage    function _storage() {        var storage = {            supported: (function () {                try {                    return 'localStorage' in window && window.localStorage !== null;                } catch (e) {                    return false;                }            })()        };        return storage;    }    // Player instance    function Plyr(container) {        var player = this;        player.container = container;        // Captions functions        // Seek the manual caption time and update UI        function _seekManualCaptions(time) {            // If it's not video, or we're using textTracks, bail.            if (player.usingTextTracks || player.type !== 'video' || !player.supported.full) {                return;            }            // Reset subcount            player.subcount = 0;            // Check time is a number, if not use currentTime            // IE has a bug where currentTime doesn't go to 0            // https://twitter.com/Sam_Potts/status/573715746506731521            time = typeof time === 'number' ? time : player.media.currentTime;            while (_timecodeMax(player.captions[player.subcount][0]) < time.toFixed(1)) {                player.subcount++;                if (player.subcount > player.captions.length - 1) {                    player.subcount = player.captions.length - 1;                    break;                }            }            // Check if the next caption is in the current time range            if (player.media.currentTime.toFixed(1) >= _timecodeMin(player.captions[player.subcount][0]) &&                player.media.currentTime.toFixed(1) <= _timecodeMax(player.captions[player.subcount][0])) {                player.currentCaption = player.captions[player.subcount][1];                // Trim caption text                var content = player.currentCaption.trim();                // Render the caption (only if changed)                if (player.captionsContainer.innerHTML != content) {                    // Empty caption                    // Otherwise NVDA reads it twice                    player.captionsContainer.innerHTML = '';                    // Set new caption text                    player.captionsContainer.innerHTML = content;                }            } else {                player.captionsContainer.innerHTML = '';            }        }        // Display captions container and button (for initialization)        function _showCaptions() {            // If there's no caption toggle, bail            if (!player.buttons.captions) {                return;            }            _toggleClass(player.container, config.classes.captions.enabled, true);            if (config.captions.defaultActive) {                _toggleClass(player.container, config.classes.captions.active, true);                _toggleState(player.buttons.captions, true);            }        }        // Utilities for caption time codes        function _timecodeMin(tc) {            var tcpair = [];            tcpair = tc.split(' --> ');            return _subTcSecs(tcpair[0]);        }        function _timecodeMax(tc) {            var tcpair = [];            tcpair = tc.split(' --> ');            return _subTcSecs(tcpair[1]);        }        function _subTcSecs(tc) {            if (tc === null || tc === undefined) {                return 0;            } else {                var tc1 = [],                    tc2 = [],                    seconds;                tc1 = tc.split(',');                tc2 = tc1[0].split(':');                seconds = Math.floor(tc2[0] * 60 * 60) + Math.floor(tc2[1] * 60) + Math.floor(tc2[2]);                return seconds;            }        }        // Find all elements        function _getElements(selector) {            return player.container.querySelectorAll(selector);        }        // Find a single element        function _getElement(selector) {            return _getElements(selector)[0];        }        // Determine if we're in an iframe        function _inFrame() {            try {                return window.self !== window.top;            } catch (e) {                return true;            }        }        // Insert controls        function _injectControls() {            // Make a copy of the html            var html = config.html;            // Insert custom video controls            _log('Injecting custom controls.');            // If no controls are specified, create default            if (!html) {                html = _buildControls();            }            // Replace seek time instances            html = _replaceAll(html, '{seektime}', config.seekTime);            // Replace all id references with random numbers            html = _replaceAll(html, '{id}', Math.floor(Math.random() * (10000)));            // Inject into the container            player.container.insertAdjacentHTML('beforeend', html);            // Setup tooltips            if (config.tooltips) {                var labels = _getElements(config.selectors.labels);                for (var i = labels.length - 1; i >= 0; i--) {                    var label = labels[i];                    _toggleClass(label, config.classes.hidden, false);                    _toggleClass(label, config.classes.tooltip, true);                }            }        }        // Find the UI controls and store references        function _findElements() {            try {                player.controls = _getElement(config.selectors.controls);                // Buttons                player.buttons = {};                player.buttons.seek = _getElement(config.selectors.buttons.seek);                player.buttons.play = _getElement(config.selectors.buttons.play);                player.buttons.pause = _getElement(config.selectors.buttons.pause);                player.buttons.restart = _getElement(config.selectors.buttons.restart);                player.buttons.rewind = _getElement(config.selectors.buttons.rewind);                player.buttons.forward = _getElement(config.selectors.buttons.forward);                player.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen);                // Inputs                player.buttons.mute = _getElement(config.selectors.buttons.mute);                player.buttons.captions = _getElement(config.selectors.buttons.captions);                player.checkboxes = _getElements('[type="checkbox"]');                // Progress                player.progress = {};                player.progress.container = _getElement(config.selectors.progress.container);                // Progress - Buffering                player.progress.buffer = {};                player.progress.buffer.bar = _getElement(config.selectors.progress.buffer);                player.progress.buffer.text = player.progress.buffer.bar && player.progress.buffer.bar.getElementsByTagName('span')[0];                // Progress - Played                player.progress.played = {};                player.progress.played.bar = _getElement(config.selectors.progress.played);                player.progress.played.text = player.progress.played.bar && player.progress.played.bar.getElementsByTagName('span')[0];                // Volume                player.volume = _getElement(config.selectors.buttons.volume);                // Timing                player.duration = _getElement(config.selectors.duration);                player.currentTime = _getElement(config.selectors.currentTime);                player.seekTime = _getElements(config.selectors.seekTime);                return true;            } catch (e) {                _log('It looks like there\'s a problem with your controls html. Bailing.', true);                // Restore native video controls                player.media.setAttribute('controls', '');                return false;            }        }        // Setup aria attribute for play        function _setupPlayAria() {            // If there's no play button, bail            if (!player.buttons.play) {                return;            }            // Find the current text            var label = player.buttons.play.innerText || config.i18n.play;            // If there's a media title set, use that for the label            if (typeof (config.title) !== 'undefined' && config.title.length) {                label += ', ' + config.title;            }            player.buttons.play.setAttribute('aria-label', label);        }        // Setup media        function _setupMedia() {            // If there's no media, bail            if (!player.media) {                _log('No audio or video element found!', true);                return false;            }            if (player.supported.full) {                // Remove native video controls                player.media.removeAttribute('controls');                // Add type class                _toggleClass(player.container, config.classes.type.replace('{0}', player.type), true);                // If there's no autoplay attribute, assume the video is stopped and add state class                _toggleClass(player.container, config.classes.stopped, (player.media.getAttribute('autoplay') === null));                // Add iOS class                if (player.browser.ios) {                    _toggleClass(player.container, 'ios', true);                }                // Inject the player wrapper                if (player.type === 'video') {                    // Create the wrapper div                    var wrapper = document.createElement('div');                    wrapper.setAttribute('class', config.classes.videoWrapper);                    // Wrap the video in a container                    _wrap(player.media, wrapper);                    // Cache the container                    player.videoContainer = wrapper;                }            }            // YouTube            if (player.type == 'youtube') {                _setupYouTube(player.media.getAttribute('data-video-id'));            }            // Autoplay            if (player.media.getAttribute('autoplay') !== null) {                _play();            }        }        // Setup YouTube        function _setupYouTube(id) {            // Remove old containers            var containers = _getElements('[id^="youtube"]');            for (var i = containers.length - 1; i >= 0; i--) {                _remove(containers[i]);            }            // Create the YouTube container            var container = document.createElement('div');            container.setAttribute('id', 'youtube-' + Math.floor(Math.random() * (10000)));            player.media.appendChild(container);            // Add embed class for responsive            _toggleClass(player.media, config.classes.videoWrapper, true);            _toggleClass(player.media, config.classes.embedWrapper, true);            if (typeof YT === 'object') {                _YTReady(id, container);            } else {                // Load the API                _injectScript('https://www.youtube.com/iframe_api');                // Add callback to queue                callbacks.youtube.push(function () {                    _YTReady(id, container);                });                // Setup callback for the API                window.onYouTubeIframeAPIReady = function () {                    for (var i = callbacks.youtube.length - 1; i >= 0; i--) {                        // Fire callback                        callbacks.youtube[i]();                        // Remove from queue                        callbacks.youtube.splice(i, 1);                    }                };            }        }        // Handle API ready        function _YTReady(id, container) {            _log('YouTube API Ready');            // Setup timers object            // We have to poll YouTube for updates            if (!('timer' in player)) {                player.timer = {};            }            // Setup instance            // https://developers.google.com/youtube/iframe_api_reference            player.embed = new YT.Player(container.id, {                videoId: id,                playerVars: {                    autoplay: 0,                    controls: (player.supported.full ? 0 : 1),                    rel: 0,                    showinfo: 0,                    iv_load_policy: 3,                    cc_load_policy: (config.captions.defaultActive ? 1 : 0),                    cc_lang_pref: 'en',                    wmode: 'transparent',                    modestbranding: 1,                    disablekb: 1                },                events: {                    'onReady': function (event) {                        // Get the instance                        var instance = event.target;                        // Create a faux HTML5 API using the YouTube API                        player.media.play = function () {                            instance.playVideo();                        };                        player.media.pause = function () {                            instance.pauseVideo();                        };                        player.media.stop = function () {                            instance.stopVideo();                        };                        player.media.duration = instance.getDuration();                        player.media.paused = true;                        player.media.currentTime = instance.getCurrentTime();                        player.media.muted = instance.isMuted();                        // Trigger timeupdate                        _triggerEvent(player.media, 'timeupdate');                        // Reset timer                        window.clearInterval(player.timer.buffering);                        // Setup buffering                        player.timer.buffering = window.setInterval(function () {                            // Get loaded % from YouTube                            player.media.buffered = instance.getVideoLoadedFraction();                            // Trigger progress                            _triggerEvent(player.media, 'progress');                            // Bail if we're at 100%                            if (player.media.buffered === 1) {                                window.clearInterval(player.timer.buffering);                            }                        }, 200);                        if (player.supported.full) {                            // Only setup controls once                            if (!player.container.querySelectorAll(config.selectors.controls).length) {                                _setupInterface();                            }                            // Display duration if available                            if (config.displayDuration) {                                _displayDuration();                            }                        }                    },                    'onStateChange': function (event) {                        // Get the instance                        var instance = event.target;                        // Reset timer                        window.clearInterval(player.timer.playing);                        // Handle events                        // -1   Unstarted                        // 0    Ended                        // 1    Playing                        // 2    Paused                        // 3    Buffering                        // 5    Video cued                        switch (event.data) {                        case 0:                            player.media.paused = true;                            _triggerEvent(player.media, 'ended');                            break;                        case 1:                            player.media.paused = false;                            _triggerEvent(player.media, 'play');                            // Poll to get playback progress                            player.timer.playing = window.setInterval(function () {                                // Set the current time                                player.media.currentTime = instance.getCurrentTime();                                // Trigger timeupdate                                _triggerEvent(player.media, 'timeupdate');                            }, 200);                            break;                        case 2:                            player.media.paused = true;                            _triggerEvent(player.media, 'pause');                        }                    }                }            });        }        // Setup captions        function _setupCaptions() {            if (player.type === 'video') {                // Inject the container                player.videoContainer.insertAdjacentHTML('afterbegin', '<div class="' + config.selectors.captions.replace('.', '') + '"><span></span></div>');                // Cache selector                player.captionsContainer = _getElement(config.selectors.captions).querySelector('span');                // Determine if HTML5 textTracks is supported                player.usingTextTracks = false;                if (player.media.textTracks) {                    player.usingTextTracks = true;                }                // Get URL of caption file if exists                var captionSrc = '',                    kind,                    children = player.media.childNodes;                for (var i = 0; i < children.length; i++) {                    if (children[i].nodeName.toLowerCase() === 'track') {                        kind = children[i].kind;                        if (kind === 'captions' || kind === 'subtitles') {                            captionSrc = children[i].getAttribute('src');                        }                    }                }                // Record if caption file exists or not                player.captionExists = true;                if (captionSrc === '') {                    player.captionExists = false;                    _log('No caption track found.');                } else {                    _log('Caption track found; URI: ' + captionSrc);                }                // If no caption file exists, hide container for caption text                if (!player.captionExists) {                    _toggleClass(player.container, config.classes.captions.enabled);                }                // If caption file exists, process captions                else {                    // Turn off native caption rendering to avoid double captions                    // This doesn't seem to work in Safari 7+, so the <track> elements are removed from the dom below                    var tracks = player.media.textTracks;                    for (var x = 0; x < tracks.length; x++) {                        tracks[x].mode = 'hidden';                    }                    // Enable UI                    _showCaptions(player);                    // Disable unsupported browsers than report false positive                    if ((player.browser.name === 'IE' && player.browser.version >= 10) ||                        (player.browser.name === 'Firefox' && player.browser.version >= 31) ||                        (player.browser.name === 'Chrome' && player.browser.version >= 43) ||                        (player.browser.name === 'Safari' && player.browser.version >= 7)) {                        // Debugging                        _log('Detected unsupported browser for HTML5 captions. Using fallback.');                        // Set to false so skips to 'manual' captioning                        player.usingTextTracks = false;                    }                    // Rendering caption tracks                    // Native support required - http://caniuse.com/webvtt                    if (player.usingTextTracks) {                        _log('TextTracks supported.');                        for (var y = 0; y < tracks.length; y++) {                            var track = tracks[y];                            if (track.kind === 'captions' || track.kind === 'subtitles') {                                _on(track, 'cuechange', function () {                                    // Clear container                                    player.captionsContainer.innerHTML = '';                                    // Display a cue, if there is one                                    if (this.activeCues[0] && this.activeCues[0].hasOwnProperty('text')) {                                        player.captionsContainer.appendChild(this.activeCues[0].getCueAsHTML().trim());                                    }                                });                            }                        }                    }                    // Caption tracks not natively supported                    else {                        _log('TextTracks not supported so rendering captions manually.');                        // Render captions from array at appropriate time                        player.currentCaption = '';                        player.captions = [];                        if (captionSrc !== '') {                            // Create XMLHttpRequest Object                            var xhr = new XMLHttpRequest();                            xhr.onreadystatechange = function () {                                if (xhr.readyState === 4) {                                    if (xhr.status === 200) {                                        var records = [],                                            record,                                            req = xhr.responseText;                                        records = req.split('\n\n');                                        for (var r = 0; r < records.length; r++) {                                            record = records[r];                                            player.captions[r] = [];                                            player.captions[r] = record.split('\n');                                        }                                        // Remove first element ('VTT')                                        player.captions.shift();                                        _log('Successfully loaded the caption file via AJAX.');                                    } else {                                        _log('There was a problem loading the caption file via AJAX.', true);                                    }                                }                            };                            xhr.open('get', captionSrc, true);                            xhr.send();                        }                    }                    // If Safari 7+, removing track from DOM [see 'turn off native caption rendering' above]                    if (player.browser.name === 'Safari' && player.browser.version >= 7) {                        _log('Safari 7+ detected; removing track from DOM.');                        // Find all <track> elements                        tracks = player.media.getElementsByTagName('track');                        // Loop through and remove one by one                        for (var t = 0; t < tracks.length; t++) {                            player.media.removeChild(tracks[t]);                        }                    }                }            }        }        // Setup fullscreen        function _setupFullscreen() {            if (player.type != 'audio' && config.fullscreen.enabled) {                // Check for native support                var nativeSupport = fullscreen.supportsFullScreen;                if (nativeSupport || (config.fullscreen.fallback && !_inFrame())) {                    _log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled.');                    // Add styling hook                    _toggleClass(player.container, config.classes.fullscreen.enabled, true);                } else {                    _log('Fullscreen not supported and fallback disabled.');                }                // Toggle state                _toggleState(player.buttons.fullscreen, false);                // Set control hide class hook                if (config.fullscreen.hideControls) {                    _toggleClass(player.container, config.classes.fullscreen.hideControls, true);                }            }        }        // Play media        function _play() {            player.media.play();        }        // Pause media        function _pause() {            player.media.pause();        }        // Toggle playback        function _togglePlay(toggle) {            // Play            if (toggle === true) {                _play();            }            // Pause            else if (toggle === false) {                _pause();            }            // True toggle            else {                player.media[player.media.paused ? 'play' : 'pause']();            }        }        // Rewind        function _rewind(seekTime) {            // Use default if needed            if (typeof seekTime !== 'number') {                seekTime = config.seekTime;            }            _seek(player.media.currentTime - seekTime);        }        // Fast forward        function _forward(seekTime) {            // Use default if needed            if (typeof seekTime !== 'number') {                seekTime = config.seekTime;            }            _seek(player.media.currentTime + seekTime);        }        // Seek to time        // The input parameter can be an event or a number        function _seek(input) {            var targetTime = 0,                paused = player.media.paused;            // Explicit position            if (typeof input === 'number') {                targetTime = input;            }            // Event            else if (typeof input === 'object' && (input.type === 'input' || input.type === 'change')) {                // It's the seek slider                // Seek to the selected time                targetTime = ((input.target.value / input.target.max) * player.media.duration);            }            // Normalise targetTime            if (targetTime < 0) {                targetTime = 0;            } else if (targetTime > player.media.duration) {                targetTime = player.media.duration;            }            // Set the current time            // Try/catch incase the media isn't set and we're calling seek() from source() and IE moans            try {                player.media.currentTime = targetTime.toFixed(1);            } catch (e) {}            // YouTube            if (player.type == 'youtube') {                player.embed.seekTo(targetTime);                if (paused) {                    _pause();                }                // Trigger timeupdate                _triggerEvent(player.media, 'timeupdate');            }            // Logging            _log('Seeking to ' + player.media.currentTime + ' seconds');            // Special handling for 'manual' captions            _seekManualCaptions(targetTime);        }        // Check playing state        function _checkPlaying() {            _toggleClass(player.container, config.classes.playing, !player.media.paused);            _toggleClass(player.container, config.classes.stopped, player.media.paused);        }        // Toggle fullscreen        function _toggleFullscreen(event) {            // Check for native support            var nativeSupport = fullscreen.supportsFullScreen;            // If it's a fullscreen change event, it's probably a native close            if (event && event.type === fullscreen.fullScreenEventName) {                player.isFullscreen = fullscreen.isFullScreen(player.container);            }            // If there's native support, use it            else if (nativeSupport) {                // Request fullscreen                if (!fullscreen.isFullScreen(player.container)) {                    fullscreen.requestFullScreen(player.container);                }                // Bail from fullscreen                else {                    fullscreen.cancelFullScreen();                }                // Check if we're actually full screen (it could fail)                player.isFullscreen = fullscreen.isFullScreen(player.container);            } else {                // Otherwise, it's a simple toggle                player.isFullscreen = !player.isFullscreen;                // Bind/unbind escape key                if (player.isFullscreen) {                    _on(document, 'keyup', _handleEscapeFullscreen);                    document.body.style.overflow = 'hidden';                } else {                    _off(document, 'keyup', _handleEscapeFullscreen);                    document.body.style.overflow = '';                }            }            // Set class hook            _toggleClass(player.container, config.classes.fullscreen.active, player.isFullscreen);            // Set button state            _toggleState(player.buttons.fullscreen, player.isFullscreen);            // Toggle controls visibility based on mouse movement and location            var hoverTimer, isMouseOver = false;            // Show the player controls            function _showControls() {                // Set shown class                _toggleClass(player.container, config.classes.hover, true);                // Clear timer every movement                window.clearTimeout(hoverTimer);                // If the mouse is not over the controls, set a timeout to hide them                if (!isMouseOver) {                    hoverTimer = window.setTimeout(function () {                        _toggleClass(player.container, config.classes.hover, false);                    }, 2000);                }            }            // Check mouse is over the controls            function _setMouseOver(event) {                isMouseOver = (event.type === 'mouseenter');            }            if (config.fullscreen.hideControls) {                // Hide on entering full screen                _toggleClass(player.controls, config.classes.hover, false);                // Keep an eye on the mouse location in relation to controls                _toggleHandler(player.controls, 'mouseenter mouseleave', _setMouseOver, player.isFullscreen);                // Show the controls on mouse move                _toggleHandler(player.container, 'mousemove', _showControls, player.isFullscreen);            }        }        // Bail from faux-fullscreen        function _handleEscapeFullscreen(event) {            // If it's a keypress and not escape, bail            if ((event.which || event.charCode || event.keyCode) === 27 && player.isFullscreen) {                _toggleFullscreen();            }        }        // Set volume        function _setVolume(volume) {            // Use default if no value specified            if (typeof volume === 'undefined') {                if (config.storage.enabled && _storage().supported) {                    volume = window.localStorage[config.storage.key] || config.volume;                } else {                    volume = config.volume;                }            }            // Maximum is 10            if (volume > 10) {                volume = 10;            }            // Minimum is 0            if (volume < 0) {                volume = 0;            }            // Set the player volume            player.media.volume = parseFloat(volume / 10);            // YouTube            if (player.type == 'youtube') {                player.embed.setVolume(player.media.volume * 100);                // Trigger timeupdate                _triggerEvent(player.media, 'volumechange');            }            // Toggle muted state            if (player.media.muted && volume > 0) {                _toggleMute();            }        }        // Mute        function _toggleMute(muted) {            // If the method is called without parameter, toggle based on current value            if (typeof muted !== 'boolean') {                muted = !player.media.muted;            }            // Set button state            _toggleState(player.buttons.mute, muted);            // Set mute on the player            player.media.muted = muted;            // YouTube            if (player.type === 'youtube') {                player.embed[player.media.muted ? 'mute' : 'unMute']();                // Trigger timeupdate                _triggerEvent(player.media, 'volumechange');            }        }        // Update volume UI and storage        function _updateVolume() {            // Get the current volume            var volume = player.media.muted ? 0 : (player.media.volume * 10);            // Update the <input type="range"> if present            if (player.supported.full && player.volume) {                player.volume.value = volume;            }            // Store the volume in storage            if (config.storage.enabled && _storage().supported) {                window.localStorage.setItem(config.storage.key, volume);            }            // Toggle class if muted            _toggleClass(player.container, config.classes.muted, (volume === 0));            // Update checkbox for mute state            if (player.supported.full && player.buttons.mute) {                _toggleState(player.buttons.mute, (volume === 0));            }        }        // Toggle captions        function _toggleCaptions(show) {            // If there's no full support, or there's no caption toggle            if (!player.supported.full || !player.buttons.captions) {                return;            }            // If the method is called without parameter, toggle based on current value            if (typeof show !== 'boolean') {                show = (player.container.className.indexOf(config.classes.captions.active) === -1);            }            // Toggle state            _toggleState(player.buttons.captions, show);            // Add class hook            _toggleClass(player.container, config.classes.captions.active, show);        }        // Check if media is loading        function _checkLoading(event) {            var loading = (event.type === 'waiting');            // Clear timer            clearTimeout(player.loadingTimer);            // Timer to prevent flicker when seeking            player.loadingTimer = setTimeout(function () {                _toggleClass(player.container, config.classes.loading, loading);            }, (loading ? 250 : 0));        }        // Update <progress> elements        function _updateProgress(event) {            var progress = player.progress.played.bar,                text = player.progress.played.text,                value = 0;            if (event) {                switch (event.type) {                    // Video playing                case 'timeupdate':                case 'seeking':                    value = _getPercentage(player.media.currentTime, player.media.duration);                    // Set seek range value only if it's a 'natural' time event                    if (event.type == 'timeupdate' && player.buttons.seek) {                        player.buttons.seek.value = value;                    }                    break;                    // Events from seek range                case 'change':                case 'input':                    value = event.target.value;                    break;                    // Check buffer status                case 'playing':                case 'progress':                    progress = player.progress.buffer.bar;                    text = player.progress.buffer.text;                    value = (function () {                        var buffered = player.media.buffered;                        // HTML5                        if (buffered && buffered.length) {                            return _getPercentage(buffered.end(0), player.media.duration);                        }                        // YouTube returns between 0 and 1                        else if (typeof buffered === 'number') {                            return (buffered * 100);                        }                        return 0;                    })();                }            }            // Set values            if (progress) {                progress.value = value;            }            if (text) {                text.innerHTML = value;            }        }        // Update the displayed time        function _updateTimeDisplay(time, element) {            // Bail if there's no duration display            if (!element) {                return;            }            player.secs = parseInt(time % 60);            player.mins = parseInt((time / 60) % 60);            player.hours = parseInt(((time / 60) / 60) % 60);            // Do we need to display hours?            var displayHours = (parseInt(((player.media.duration / 60) / 60) % 60) > 0);            // Ensure it's two digits. For example, 03 rather than 3.            player.secs = ('0' + player.secs).slice(-2);            player.mins = ('0' + player.mins).slice(-2);            // Render            element.innerHTML = (displayHours ? player.hours + ':' : '') + player.mins + ':' + player.secs;        }        // Show the duration on metadataloaded        function _displayDuration() {            var duration = player.media.duration || 0;            // If there's only one time display, display duration there            if (!player.duration && config.displayDuration && player.media.paused) {                _updateTimeDisplay(duration, player.currentTime);            }            // If there's a duration element, update content            if (player.duration) {                _updateTimeDisplay(duration, player.duration);            }        }        // Handle time change event        function _timeUpdate(event) {            // Duration            _updateTimeDisplay(player.media.currentTime, player.currentTime);            // Playing progress            _updateProgress(event);        }        // Remove <source> children and src attribute        function _removeSources() {            // Find child <source> elements            var sources = player.media.querySelectorAll('source');            // Remove each            for (var i = sources.length - 1; i >= 0; i--) {                _remove(sources[i]);            }            // Remove src attribute            player.media.removeAttribute('src');        }        // Inject a source        function _addSource(attributes) {            if (attributes.src) {                // Create a new <source>                var element = document.createElement('source');                // Set all passed attributes                _setAttributes(element, attributes);                // Inject the new source                _prependChild(player.media, element);            }        }        // Update source        // Sources are not checked for support so be careful        function _parseSource(sources) {            // YouTube            if (player.type === 'youtube' && typeof sources === 'string') {                // Destroy YouTube instance                player.embed.destroy();                // Re-setup YouTube                // We don't use loadVideoBy[x] here since it has issues                _setupYouTube(sources);                // Update times                _timeUpdate();                // Bail                return;            }            // Pause playback (webkit freaks out)            _pause();            // Restart            _seek();            // Remove current sources            _removeSources();            // If a single source is passed            // .source('path/to/video.mp4')            if (typeof sources === 'string') {                _addSource({                    src: sources                });            }            // An array of source objects            // Check if a source exists, use that or set the 'src' attribute?            // .source([{ src: 'path/to/video.mp4', type: 'video/mp4' },{ src: 'path/to/video.webm', type: 'video/webm' }])            else if (sources.constructor === Array) {                for (var index in sources) {                    _addSource(sources[index]);                }            }            if (player.supported.full) {                // Reset time display                _timeUpdate();                // Update the UI                _checkPlaying();            }            // Re-load sources            player.media.load();            // Play if autoplay attribute is present            if (player.media.getAttribute('autoplay') !== null) {                _play();            }        }        // Update poster        function _updatePoster(source) {            if (player.type === 'video') {                player.media.setAttribute('poster', source);            }        }        // Listen for events        function _listeners() {            // IE doesn't support input event, so we fallback to change            var inputEvent = (player.browser.name == 'IE' ? 'change' : 'input');            // Detect tab focus            function checkFocus() {                var focused = document.activeElement;                if (!focused || focused == document.body) {                    focused = null;                } else if (document.querySelector) {                    focused = document.querySelector(':focus');                }                for (var button in player.buttons) {                    var element = player.buttons[button];                    _toggleClass(element, 'tab-focus', (element === focused));                }            }            _on(window, 'keyup', function (event) {                var code = (event.keyCode ? event.keyCode : event.which);                if (code == 9) {                    checkFocus();                }            });            for (var button in player.buttons) {                var element = player.buttons[button];                _on(element, 'blur', function () {                    _toggleClass(element, 'tab-focus', false);                });            }            // Play            _on(player.buttons.play, 'click', function () {                _play();                setTimeout(function () {                    player.buttons.pause.focus();                }, 100);            });            // Pause            _on(player.buttons.pause, 'click', function () {                _pause();                setTimeout(function () {                    player.buttons.play.focus();                }, 100);            });            // Restart            _on(player.buttons.restart, 'click', _seek);            // Rewind            _on(player.buttons.rewind, 'click', _rewind);            // Fast forward            _on(player.buttons.forward, 'click', _forward);            // Seek            _on(player.buttons.seek, inputEvent, _seek);            // Set volume            _on(player.volume, inputEvent, function () {                _setVolume(this.value);            });            // Mute            _on(player.buttons.mute, 'click', _toggleMute);            // Fullscreen            _on(player.buttons.fullscreen, 'click', _toggleFullscreen);            // Handle user exiting fullscreen by escaping etc            if (fullscreen.supportsFullScreen) {                _on(document, fullscreen.fullScreenEventName, _toggleFullscreen);            }            // Time change on media            _on(player.media, 'timeupdate seeking', _timeUpdate);            // Update manual captions            _on(player.media, 'timeupdate', _seekManualCaptions);            // Display duration            _on(player.media, 'loadedmetadata', _displayDuration);            // Captions            _on(player.buttons.captions, 'click', _toggleCaptions);            // Handle the media finishing            _on(player.media, 'ended', function () {                // Clear                if (player.type === 'video') {                    player.captionsContainer.innerHTML = '';                }                // Reset UI                _checkPlaying();            });            // Check for buffer progress            _on(player.media, 'progress playing', _updateProgress);            // Handle native mute            _on(player.media, 'volumechange', _updateVolume);            // Handle native play/pause            _on(player.media, 'play pause', _checkPlaying);            // Loading            _on(player.media, 'waiting canplay seeked', _checkLoading);            // Click video            if (player.type === 'video' && config.click) {                _on(player.videoContainer, 'click', function () {                    if (player.media.paused) {                        _triggerEvent(player.buttons.play, 'click');                    } else if (player.media.ended) {                        _seek();                        _triggerEvent(player.buttons.play, 'click');                    } else {                        _triggerEvent(player.buttons.pause, 'click');                    }                });            }        }        // Destroy an instance        // Event listeners are removed when elements are removed        // http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory        function _destroy() {            // Bail if the element is not initialized            if (!player.init) {                return null;            }            // Reset container classname            player.container.setAttribute('class', config.selectors.container.replace('.', ''));            // Remove init flag            player.init = false;            // Remove controls            _remove(_getElement(config.selectors.controls));            // YouTube            if (player.type === 'youtube') {                player.embed.destroy();                return;            }            // If video, we need to remove some more            if (player.type === 'video') {                // Remove captions                _remove(_getElement(config.selectors.captions));                // Remove video wrapper                _unwrap(player.videoContainer);            }            // Restore native video controls            player.media.setAttribute('controls', '');            // Clone the media element to remove listeners            // http://stackoverflow.com/questions/19469881/javascript-remove-all-event-listeners-of-specific-type            var clone = player.media.cloneNode(true);            player.media.parentNode.replaceChild(clone, player.media);        }        // Setup a player        function _init() {            // Bail if the element is initialized            if (player.init) {                return null;            }            // Setup the fullscreen api            fullscreen = _fullscreen();            // Sniff out the browser            player.browser = _browserSniff();            // Get the media element            player.media = player.container.querySelectorAll('audio, video, div')[0];            // Set media type            var tagName = player.media.tagName.toLowerCase();            if (tagName === 'div') {                player.type = player.media.getAttribute('data-type');            } else {                player.type = tagName;            }            // Check for full support            player.supported = api.supported(player.type);            // If no native support, bail            if (!player.supported.basic) {                return false;            }            // Debug info            _log(player.browser.name + ' ' + player.browser.version);            // Setup media            _setupMedia();            // Setup interface            if (player.type == 'video' || player.type == 'audio') {                // Bail if no support                if (!player.supported.full) {                    // Successful setup                    player.init = true;                    // Don't inject controls if no full support                    return;                }                // Setup UI                _setupInterface();                // Display duration if available                if (config.displayDuration) {                    _displayDuration();                }                // Set up aria-label for Play button with the title option                _setupPlayAria();            }            // Successful setup            player.init = true;        }        function _setupInterface() {            // Inject custom controls            _injectControls();            // Find the elements            if (!_findElements()) {                return false;            }            // Captions            _setupCaptions();            // Set volume            _setVolume();            _updateVolume();            // Setup fullscreen            _setupFullscreen();            // Listeners            _listeners();        }        // Initialize instance        _init();        // If init failed, return an empty object        if (!player.init) {            return {};        }        return {            media: player.media,            play: _play,            pause: _pause,            restart: _seek,            rewind: _rewind,            forward: _forward,            seek: _seek,            source: _parseSource,            poster: _updatePoster,            setVolume: _setVolume,            togglePlay: _togglePlay,            toggleMute: _toggleMute,            toggleCaptions: _toggleCaptions,            toggleFullscreen: _toggleFullscreen,            isFullscreen: function () {                return player.isFullscreen || false;            },            support: function (mimeType) {                return _supportMime(player, mimeType);            },            destroy: _destroy,            restore: _init        };    }    // Check for support    api.supported = function (type) {        var browser = _browserSniff(),            oldIE = (browser.name === 'IE' && browser.version <= 9),            iPhone = /iPhone|iPod/i.test(navigator.userAgent),            audio = !!document.createElement('audio').canPlayType,            video = !!document.createElement('video').canPlayType,            basic, full;        switch (type) {        case 'video':            basic = video;            full = (basic && (!oldIE && !iPhone));            break;        case 'audio':            basic = audio;            full = (basic && !oldIE);            break;        case 'youtube':            basic = true;            full = (!oldIE && !iPhone);            break;        default:            basic = (audio && video);            full = (basic && !oldIE);        }        return {            basic: basic,            full: full        };    };    // Expose setup function    api.setup = function (options) {        // Extend the default options with user specified        config = _extend(defaults, options);        // Bail if disabled or no basic support        // You may want to disable certain UAs etc        if (!config.enabled || !api.supported().basic) {            return false;        }        // Get the players        var elements = document.querySelectorAll(config.selectors.container),            players = [];        // Create a player instance for each element        for (var i = elements.length - 1; i >= 0; i--) {            // Get the current element            var element = elements[i];            // Setup a player instance and add to the element            if (typeof element.plyr === 'undefined') {                // Create new instance                var instance = new Plyr(element);                // Set plyr to false if setup failed                element.plyr = (Object.keys(instance).length ? instance : false);                // Callback                if (typeof config.onSetup === 'function') {                    config.onSetup.apply(element.plyr);                }            }            // Add to return array even if it's already setup            players.push(element.plyr);        }        return players;    };}(this.plyr = this.plyr || {}));
 |