// ==========================================================================
// 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: true,
            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 || {}));