(function ($) {

    // default player values
    mejs.MepDefaults = {
        // url to poster (to fix iOS 3.x)
        poster: '',
        // When the video is ended, we can show the poster.
        showPosterWhenEnded: false,
        // default if the <video width> is not specified
        defaultVideoWidth: 480,
        // default if the <video height> is not specified
        defaultVideoHeight: 270,
        // if set, overrides <video width>
        videoWidth: -1,
        // if set, overrides <video height>
        videoHeight: -1,
        // default if the user doesn't specify
        defaultAudioWidth: 400,
        // default if the user doesn't specify
        defaultAudioHeight: 30,

        // default amount to move back when back key is pressed
        defaultSeekBackwardInterval: function (media) {
            return (media.duration * 0.05);
        },
        // default amount to move forward when forward key is pressed
        defaultSeekForwardInterval: function (media) {
            return (media.duration * 0.05);
        },

        // set dimensions via JS instead of CSS
        setDimensions: true,

        // width of audio player
        audioWidth: -1,
        // height of audio player
        audioHeight: -1,
        // initial volume when the player starts (overrided by user cookie)
        startVolume: 0.8,
        // useful for <audio> player loops
        loop: false,
        // rewind to beginning when media ends
        autoRewind: true,
        // resize to media dimensions
        enableAutosize: true,

        /*
         * Time format to use. Default: 'mm:ss'
         * Supported units:
         *   h: hour
         *   m: minute
         *   s: second
         *   f: frame count
         * When using 'hh', 'mm', 'ss' or 'ff' we always display 2 digits.
         * If you use 'h', 'm', 's' or 'f' we display 1 digit if possible.
         *
         * Example to display 75 seconds:
         * Format 'mm:ss': 01:15
         * Format 'm:ss': 1:15
         * Format 'm:s': 1:15
         */
        timeFormat: '',
        // forces the hour marker (##:00:00)
        alwaysShowHours: false,
        // show framecount in timecode (##:00:00:00)
        showTimecodeFrameCount: false,
        // used when showTimecodeFrameCount is set to true
        framesPerSecond: 25,

        // automatically calculate the width of the progress bar based on the sizes of other elements
        autosizeProgress: true,
        // Hide controls when playing and mouse is not over the video
        alwaysShowControls: false,
        // Display the video control
        hideVideoControlsOnLoad: false,
        // Enable click video element to toggle play/pause
        clickToPlayPause: true,
        // force iPad's native controls
        iPadUseNativeControls: false,
        // force iPhone's native controls
        iPhoneUseNativeControls: false,
        // force Android's native controls
        AndroidUseNativeControls: false,
        // features to show
        features: ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen'],
        // only for dynamic
        isVideo: true,

        // stretching modes (auto, fill, responsive, none)
        stretching: 'auto',

        // turns keyboard support on and off for this instance
        enableKeyboard: true,

        // whenthis player starts, it will pause other players
        pauseOtherPlayers: true,

        // array of keyboard actions such as play pause
        keyActions: [
            {
                keys: [
                    32, // SPACE
                    179 // GOOGLE play/pause button
                ],
                action: function (player, media) {
                    if (media.paused || media.ended) {
                        media.play();
                    } else {
                        media.pause();
                    }
                }
            },
            {
                keys: [38], // UP
                action: function (player, media) {
                    player.container.find('.mejs-volume-slider').css('display', 'block');
                    if (player.isVideo) {
                        player.showControls();
                        player.startControlsTimer();
                    }

                    var newVolume = Math.min(media.volume + 0.1, 1);
                    media.setVolume(newVolume);
                }
            },
            {
                keys: [40], // DOWN
                action: function (player, media) {
                    player.container.find('.mejs-volume-slider').css('display', 'block');
                    if (player.isVideo) {
                        player.showControls();
                        player.startControlsTimer();
                    }

                    var newVolume = Math.max(media.volume - 0.1, 0);
                    media.setVolume(newVolume);
                }
            },
            {
                keys: [
                    37, // LEFT
                    227 // Google TV rewind
                ],
                action: function (player, media) {
                    if (!isNaN(media.duration) && media.duration > 0) {
                        if (player.isVideo) {
                            player.showControls();
                            player.startControlsTimer();
                        }

                        // 5%
                        var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0);
                        media.setCurrentTime(newTime);
                    }
                }
            },
            {
                keys: [
                    39, // RIGHT
                    228 // Google TV forward
                ],
                action: function (player, media) {
                    if (!isNaN(media.duration) && media.duration > 0) {
                        if (player.isVideo) {
                            player.showControls();
                            player.startControlsTimer();
                        }

                        // 5%
                        var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration);
                        media.setCurrentTime(newTime);
                    }
                }
            },
            {
                keys: [70], // F
                action: function (player, media) {
                    if (typeof player.enterFullScreen != 'undefined') {
                        if (player.isFullScreen) {
                            player.exitFullScreen();
                        } else {
                            player.enterFullScreen();
                        }
                    }
                }
            },
            {
                keys: [77], // M
                action: function (player, media) {
                    player.container.find('.mejs-volume-slider').css('display', 'block');
                    if (player.isVideo) {
                        player.showControls();
                        player.startControlsTimer();
                    }
                    if (player.media.muted) {
                        player.setMuted(false);
                    } else {
                        player.setMuted(true);
                    }
                }
            }
        ]
    };

    mejs.mepIndex = 0;

    mejs.players = {};

    // wraps a MediaElement object in player controls
    mejs.MediaElementPlayer = function (node, o) {
        // enforce object, even without "new" (via John Resig)
        if (!(this instanceof mejs.MediaElementPlayer)) {
            return new mejs.MediaElementPlayer(node, o);
        }

        var t = this;

        // these will be reset after the MediaElement.success fires
        t.$media = t.$node = $(node);
        t.node = t.media = t.$media[0];

        if (!t.node) {
            return
        }

        // check for existing player
        if (typeof t.node.player != 'undefined') {
            return t.node.player;
        }


        // try to get options from data-mejsoptions
        if (typeof o == 'undefined') {
            o = t.$node.data('mejsoptions');
        }

        // extend default options
        t.options = $.extend({}, mejs.MepDefaults, o);

        if (!t.options.timeFormat) {
            // Generate the time format according to options
            t.options.timeFormat = 'mm:ss';
            if (t.options.alwaysShowHours) {
                t.options.timeFormat = 'hh:mm:ss';
            }
            if (t.options.showTimecodeFrameCount) {
                t.options.timeFormat += ':ff';
            }
        }

        mejs.Utility.calculateTimeFormat(0, t.options, t.options.framesPerSecond || 25);

        // unique ID
        t.id = 'mep_' + mejs.mepIndex++;

        // add to player array (for focus events)
        mejs.players[t.id] = t;

        // start up
        t.init();

        return t;
    };

    // actual player
    mejs.MediaElementPlayer.prototype = {

        hasFocus: false,

        controlsAreVisible: true,

        init: function () {

            var
                t = this,
                mf = mejs.MediaFeatures,
                // options for MediaElement (shim)
                meOptions = $.extend(true, {}, t.options, {
                    success: function (media, domNode) {
                        t.meReady(media, domNode);
                    },
                    error: function (e) {
                        t.handleError(e);
                    }
                }),
                tagName = t.media.tagName.toLowerCase();

            t.isDynamic = (tagName !== 'audio' && tagName !== 'video');

            if (t.isDynamic) {
                // get video from src or href?
                t.isVideo = t.options.isVideo;
            } else {
                t.isVideo = (tagName !== 'audio' && t.options.isVideo);
            }

            // use native controls in iPad, iPhone, and Android
            if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) {

                // add controls and stop
                t.$media.attr('controls', 'controls');

                // attempt to fix iOS 3 bug
                //t.$media.removeAttr('poster');
                // no Issue found on iOS3 -ttroxell

                // override Apple's autoplay override for iPads
                if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
                    t.play();
                }

            } else if (mf.isAndroid && t.options.AndroidUseNativeControls) {

                // leave default player

            } else {

                // DESKTOP: use MediaElementPlayer controls

                // remove native controls
                t.$media.removeAttr('controls');
                var videoPlayerTitle = t.isVideo ?
                    mejs.i18n.t('Video Player') : mejs.i18n.t('Audio Player');
                // insert description for screen readers
                $('<span class="mejs-offscreen">' + videoPlayerTitle + '</span>').insertBefore(t.$media);
                // build container
                t.container =
                    $('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svgAsImg ? 'svg' : 'no-svg') +
                        '" tabindex="0" role="application" aria-label="' + videoPlayerTitle + '">' +
                        '<div class="mejs-inner">' +
                        '<div class="mejs-mediaelement"></div>' +
                        '<div class="mejs-layers"></div>' +
                        '<div class="mejs-controls"></div>' +
                        '<div class="mejs-clear"></div>' +
                        '</div>' +
                        '</div>')
                        .addClass(t.$media[0].className)
                        .insertBefore(t.$media)
                        .focus(function (e) {
                            if (!t.controlsAreVisible && !t.hasFocus) {
                                t.showControls(true);
                                // In versions older than IE11, the focus causes the playbar to be displayed
                                // if user clicks on the Play/Pause button in the control bar once it attempts
                                // to hide it
                                if (!t.hasMsNativeFullScreen) {
                                    var playButton = t.container.find('.mejs-playpause-button > button');
                                    playButton.focus();
                                }
                            }
                        });

                if (t.options.stretching === 'fill' && !t.container.parent('mejs-fill-container').length) {
                    // outer container
                    t.outerContainer = t.$media.parent();
                    t.container.wrap('<div class="mejs-fill-container"/>');
                }

                // add classes for user and content
                t.container.addClass(
                    (mf.isAndroid ? 'mejs-android ' : '') +
                    (mf.isiOS ? 'mejs-ios ' : '') +
                    (mf.isiPad ? 'mejs-ipad ' : '') +
                    (mf.isiPhone ? 'mejs-iphone ' : '') +
                    (t.isVideo ? 'mejs-video ' : 'mejs-audio ')
                );


                // move the <video/video> tag into the right spot
                t.container.find('.mejs-mediaelement').append(t.$media);

                // needs to be assigned here, after iOS remap
                t.node.player = t;

                // find parts
                t.controls = t.container.find('.mejs-controls');
                t.layers = t.container.find('.mejs-layers');

                // determine the size

                /* size priority:
                    (1) videoWidth (forced),
                    (2) style="width;height;"
                    (3) width attribute,
                    (4) defaultVideoWidth (for unspecified cases)
                */

                var tagType = (t.isVideo ? 'video' : 'audio'),
                    capsTagName = tagType.substring(0, 1).toUpperCase() + tagType.substring(1);


                if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) {
                    t.width = t.options[tagType + 'Width'];
                } else if (t.media.style.width !== '' && t.media.style.width !== null) {
                    t.width = t.media.style.width;
                } else if (t.media.getAttribute('width') !== null) {
                    t.width = t.$media.attr('width');
                } else {
                    t.width = t.options['default' + capsTagName + 'Width'];
                }

                if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) {
                    t.height = t.options[tagType + 'Height'];
                } else if (t.media.style.height !== '' && t.media.style.height !== null) {
                    t.height = t.media.style.height;
                } else if (t.$media[0].getAttribute('height') !== null) {
                    t.height = t.$media.attr('height');
                } else {
                    t.height = t.options['default' + capsTagName + 'Height'];
                }

                // set the size, while we wait for the plugins to load below
                t.setPlayerSize(t.width, t.height);

                // create MediaElementShim
                meOptions.pluginWidth = t.width;
                meOptions.pluginHeight = t.height;
            }

            // create MediaElement shim
            mejs.MediaElement(t.$media[0], meOptions);

            if (typeof(t.container) != 'undefined' && t.controlsAreVisible) {
                // controls are shown when loaded
                t.container.trigger('controlsshown');
            }
        },

        showControls: function (doAnimation) {
            var t = this;

            doAnimation = typeof doAnimation == 'undefined' || doAnimation;

            if (t.controlsAreVisible)
                return;

            if (doAnimation) {
                t.controls
                    .removeClass('mejs-offscreen')
                    .stop(true, true).fadeIn(200, function () {
                    t.controlsAreVisible = true;
                    t.container.trigger('controlsshown');
                });

                // any additional controls people might add and want to hide
                t.container.find('.mejs-control')
                    .removeClass('mejs-offscreen')
                    .stop(true, true).fadeIn(200, function () {
                    t.controlsAreVisible = true;
                });

            } else {
                t.controls
                    .removeClass('mejs-offscreen')
                    .css('display', 'block');

                // any additional controls people might add and want to hide
                t.container.find('.mejs-control')
                    .removeClass('mejs-offscreen')
                    .css('display', 'block');

                t.controlsAreVisible = true;
                t.container.trigger('controlsshown');
            }

            t.setControlsSize();

        },

        hideControls: function (doAnimation) {
            var t = this;

            doAnimation = typeof doAnimation == 'undefined' || doAnimation;

            if (!t.controlsAreVisible || t.options.alwaysShowControls || t.keyboardAction)
                return;

            if (doAnimation) {
                // fade out main controls
                t.controls.stop(true, true).fadeOut(200, function () {
                    $(this)
                        .addClass('mejs-offscreen')
                        .css('display', 'block');

                    t.controlsAreVisible = false;
                    t.container.trigger('controlshidden');
                });

                // any additional controls people might add and want to hide
                t.container.find('.mejs-control').stop(true, true).fadeOut(200, function () {
                    $(this)
                        .addClass('mejs-offscreen')
                        .css('display', 'block');
                });
            } else {

                // hide main controls
                t.controls
                    .addClass('mejs-offscreen')
                    .css('display', 'block');

                // hide others
                t.container.find('.mejs-control')
                    .addClass('mejs-offscreen')
                    .css('display', 'block');

                t.controlsAreVisible = false;
                t.container.trigger('controlshidden');
            }
        },

        controlsTimer: null,

        startControlsTimer: function (timeout) {

            var t = this;

            timeout = typeof timeout != 'undefined' ? timeout : 1500;

            t.killControlsTimer('start');

            t.controlsTimer = setTimeout(function () {
                //console.log('timer fired');
                t.hideControls();
                t.killControlsTimer('hide');
            }, timeout);
        },

        killControlsTimer: function (src) {

            var t = this;

            if (t.controlsTimer !== null) {
                clearTimeout(t.controlsTimer);
                delete t.controlsTimer;
                t.controlsTimer = null;
            }
        },

        controlsEnabled: true,

        disableControls: function () {
            var t = this;

            t.killControlsTimer();
            t.hideControls(false);
            this.controlsEnabled = false;
        },

        enableControls: function () {
            var t = this;

            t.showControls(false);

            t.controlsEnabled = true;
        },

        // Sets up all controls and events
        meReady: function (media, domNode) {


            var t = this,
                mf = mejs.MediaFeatures,
                autoplayAttr = domNode.getAttribute('autoplay'),
                autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'),
                featureIndex,
                feature;

            // make sure it can't create itself again if a plugin reloads
            if (t.created) {
                return;
            } else {
                t.created = true;
            }

            t.media = media;
            t.domNode = domNode;

            if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) {

                // two built in features
                t.buildposter(t, t.controls, t.layers, t.media);
                t.buildkeyboard(t, t.controls, t.layers, t.media);
                t.buildoverlays(t, t.controls, t.layers, t.media);

                // grab for use by features
                t.findTracks();

                // add user-defined features/controls
                for (featureIndex in t.options.features) {
                    feature = t.options.features[featureIndex];
                    if (t['build' + feature]) {
                        try {
                            t['build' + feature](t, t.controls, t.layers, t.media);
                        } catch (e) {
                            // TODO: report control error
                            //throw e;
                            console.log('error building ' + feature);
                            console.log(e);
                        }
                    }
                }

                t.container.trigger('controlsready');

                // reset all layers and controls
                t.setPlayerSize(t.width, t.height);
                t.setControlsSize();


                // controls fade
                if (t.isVideo) {

                    if (mejs.MediaFeatures.hasTouch) {

                        // for touch devices (iOS, Android)
                        // show/hide without animation on touch

                        t.$media.bind('touchstart', function () {

                            // toggle controls
                            if (t.controlsAreVisible) {
                                t.hideControls(false);
                            } else {
                                if (t.controlsEnabled) {
                                    t.showControls(false);
                                }
                            }
                        });

                    } else {

                        // create callback here since it needs access to current
                        // MediaElement object
                        t.clickToPlayPauseCallback = function () {
                            //console.log('media clicked', t.media, t.media.paused);

                            if (t.options.clickToPlayPause) {
                                if (t.media.paused) {
                                    t.play();
                                } else {
                                    t.pause();
                                }
                            }
                        };

                        // click to play/pause
                        t.media.addEventListener('click', t.clickToPlayPauseCallback, false);

                        // show/hide controls
                        t.container
                            .bind('mouseenter', function () {
                                if (t.controlsEnabled) {
                                    if (!t.options.alwaysShowControls) {
                                        t.killControlsTimer('enter');
                                        t.showControls();
                                        t.startControlsTimer(2500);
                                    }
                                }
                            })
                            .bind('mousemove', function () {
                                if (t.controlsEnabled) {
                                    if (!t.controlsAreVisible) {
                                        t.showControls();
                                    }
                                    if (!t.options.alwaysShowControls) {
                                        t.startControlsTimer(2500);
                                    }
                                }
                            })
                            .bind('mouseleave', function () {
                                if (t.controlsEnabled) {
                                    if (!t.media.paused && !t.options.alwaysShowControls) {
                                        t.startControlsTimer(1000);
                                    }
                                }
                            });
                    }

                    if (t.options.hideVideoControlsOnLoad) {
                        t.hideControls(false);
                    }

                    // check for autoplay
                    if (autoplay && !t.options.alwaysShowControls) {
                        t.hideControls();
                    }

                    // resizer
                    if (t.options.enableAutosize) {
                        t.media.addEventListener('loadedmetadata', function (e) {
                            // if the <video height> was not set and the options.videoHeight was not set
                            // then resize to the real dimensions
                            if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
                                t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
                                t.setControlsSize();
                                t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
                            }
                        }, false);
                    }
                }

                // EVENTS

                // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them)
                t.media.addEventListener('play', function () {
                    var playerIndex;

                    // go through all other players
                    for (playerIndex in mejs.players) {
                        var p = mejs.players[playerIndex];
                        if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) {
                            p.pause();
                        }
                        p.hasFocus = false;
                    }

                    t.hasFocus = true;
                }, false);


                // ended for all
                t.media.addEventListener('ended', function (e) {
                    if (t.options.autoRewind) {
                        try {
                            t.media.setCurrentTime(0);
                            // Fixing an Android stock browser bug, where "seeked" isn't fired correctly after ending the video and jumping to the beginning
                            window.setTimeout(function () {
                                $(t.container).find('.mejs-overlay-loading').parent().hide();
                            }, 20);
                        } catch (exp) {

                        }
                    }
                    t.media.pause();

                    if (t.setProgressRail) {
                        t.setProgressRail();
                    }
                    if (t.setCurrentRail) {
                        t.setCurrentRail();
                    }

                    if (t.options.loop) {
                        t.play();
                    } else if (!t.options.alwaysShowControls && t.controlsEnabled) {
                        t.showControls();
                    }
                }, false);

                // resize on the first play
                t.media.addEventListener('loadedmetadata', function (e) {
                    if (t.updateDuration) {
                        t.updateDuration();
                    }
                    if (t.updateCurrent) {
                        t.updateCurrent();
                    }

                    if (!t.isFullScreen) {
                        t.setPlayerSize(t.width, t.height);
                        t.setControlsSize();
                    }
                }, false);

                // Only change the time format when necessary
                var duration = null;
                t.media.addEventListener('timeupdate', function () {
                    if (duration !== this.duration) {
                        duration = this.duration;
                        mejs.Utility.calculateTimeFormat(duration, t.options, t.options.framesPerSecond || 25);

                        // make sure to fill in and resize the controls (e.g., 00:00 => 01:13:15
                        if (t.updateDuration) {
                            t.updateDuration();
                        }
                        if (t.updateCurrent) {
                            t.updateCurrent();
                        }
                        t.setControlsSize();

                    }
                }, false);

                t.container.focusout(function (e) {
                    if (e.relatedTarget) { //FF is working on supporting focusout https://bugzilla.mozilla.org/show_bug.cgi?id=687787
                        var $target = $(e.relatedTarget);
                        if (t.keyboardAction && $target.parents('.mejs-container').length === 0) {
                            t.keyboardAction = false;
                            t.hideControls(true);
                        }
                    }
                });

                // webkit has trouble doing this without a delay
                setTimeout(function () {
                    t.setPlayerSize(t.width, t.height);
                    t.setControlsSize();
                }, 50);

                // adjust controls whenever window sizes (used to be in fullscreen only)
                t.globalBind('resize', function () {

                    // don't resize for fullscreen mode
                    if (!(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen))) {
                        t.setPlayerSize(t.width, t.height);
                    }

                    // always adjust controls
                    t.setControlsSize();
                });

                // This is a work-around for a bug in the YouTube iFrame player, which means
                //	we can't use the play() API for the initial playback on iOS or Android;
                //	user has to start playback directly by tapping on the iFrame.
                if (t.media.pluginType == 'youtube' && ( mf.isiOS || mf.isAndroid )) {
                    t.container.find('.mejs-overlay-play').hide();
                    t.container.find('.mejs-poster').hide();
                }
            }

            // force autoplay for HTML5
            if (autoplay && media.pluginType == 'native') {
                t.play();
            }


            if (t.options.success) {

                if (typeof t.options.success == 'string') {
                    window[t.options.success](t.media, t.domNode, t);
                } else {
                    t.options.success(t.media, t.domNode, t);
                }
            }
        },

        handleError: function (e) {
            var t = this;

            if (t.controls) {
                t.controls.hide();
            }

            // Tell user that the file cannot be played
            if (t.options.error) {
                t.options.error(e);
            }
        },

        setPlayerSize: function (width, height) {
            var t = this;

            if (!t.options.setDimensions) {
                return false;
            }

            if (typeof width != 'undefined') {
                t.width = width;
            }

            if (typeof height != 'undefined') {
                t.height = height;
            }

            // check stretching modes
            switch (t.options.stretching) {
                case 'fill':
                    // The 'fill' effect only makes sense on video; for audio we will set the dimensions
                    if (t.isVideo) {
                        this.setFillMode();
                    } else {
                        this.setDimensions(t.width, t.height);
                    }
                    break;
                case 'responsive':
                    this.setResponsiveMode();
                    break;
                case 'none':
                    this.setDimensions(t.width, t.height);
                    break;
                // This is the 'auto' mode
                default:
                    if (this.hasFluidMode() === true) {
                        this.setResponsiveMode();
                    } else {
                        this.setDimensions(t.width, t.height);
                    }
                    break;
            }
        },

        hasFluidMode: function () {
            var t = this;

            // detect 100% mode - use currentStyle for IE since css() doesn't return percentages
            return (t.height.toString().indexOf('%') > 0 || (t.$node.css('max-width') !== 'none' && t.$node.css('max-width') !== 't.width') || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%'));
        },

        setResponsiveMode: function () {
            var t = this;

            // do we have the native dimensions yet?
            var nativeWidth = (function () {
                if (t.isVideo) {
                    if (t.media.videoWidth && t.media.videoWidth > 0) {
                        return t.media.videoWidth;
                    } else if (t.media.getAttribute('width') !== null) {
                        return t.media.getAttribute('width');
                    } else {
                        return t.options.defaultVideoWidth;
                    }
                } else {
                    return t.options.defaultAudioWidth;
                }
            })();

            var nativeHeight = (function () {
                if (t.isVideo) {
                    if (t.media.videoHeight && t.media.videoHeight > 0) {
                        return t.media.videoHeight;
                    } else if (t.media.getAttribute('height') !== null) {
                        return t.media.getAttribute('height');
                    } else {
                        return t.options.defaultVideoHeight;
                    }
                } else {
                    return t.options.defaultAudioHeight;
                }
            })();

            var parentWidth = t.container.parent().closest(':visible').width(),
                parentHeight = t.container.parent().closest(':visible').height(),
                newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight / nativeWidth, 10) : nativeHeight;

            // When we use percent, the newHeight can't be calculated so we get the container height
            if (isNaN(newHeight) || ( parentHeight !== 0 && newHeight > parentHeight && parentHeight > nativeHeight)) {
                newHeight = parentHeight;
            }

            if (t.container.parent().length > 0 && t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) {
                parentWidth = $(window).width();
                newHeight = $(window).height();
            }

            if (newHeight && parentWidth) {

                // set outer container size
                t.container
                    .width(parentWidth)
                    .height(newHeight);

                // set native <video> or <audio> and shims
                t.$media.add(t.container.find('.mejs-shim'))
                    .width('100%')
                    .height('100%');

                // if shim is ready, send the size to the embeded plugin
                if (t.isVideo) {
                    if (t.media.setVideoSize) {
                        t.media.setVideoSize(parentWidth, newHeight);
                    }
                }

                // set the layers
                t.layers.children('.mejs-layer')
                    .width('100%')
                    .height('100%');
            }
        },

        setFillMode: function () {
            var t = this,
                parent = t.outerContainer;

            if (!parent.width()) {
                parent.height(t.$media.width());
            }

            if (!parent.height()) {
                parent.height(t.$media.height());
            }

            var parentWidth = parent.width(),
                parentHeight = parent.height();

            t.setDimensions('100%', '100%');

            // This prevents an issue when displaying poster
            t.container.find('.mejs-poster img').css('display', 'block');

            targetElement = t.container.find('object, embed, iframe, video');

            // calculate new width and height
            var initHeight = t.height,
                initWidth = t.width,
                // scale to the target width
                scaleX1 = parentWidth,
                scaleY1 = (initHeight * parentWidth) / initWidth,
                // scale to the target height
                scaleX2 = (initWidth * parentHeight) / initHeight,
                scaleY2 = parentHeight,
                // now figure out which one we should use
                bScaleOnWidth = !(scaleX2 > parentWidth),
                finalWidth = bScaleOnWidth ? Math.floor(scaleX1) : Math.floor(scaleX2),
                finalHeight = bScaleOnWidth ? Math.floor(scaleY1) : Math.floor(scaleY2);

            if (bScaleOnWidth) {
                targetElement.height(finalHeight).width(parentWidth);
                if (t.media.setVideoSize) {
                    t.media.setVideoSize(parentWidth, finalHeight);
                }
            } else {
                targetElement.height(parentHeight).width(finalWidth);
                if (t.media.setVideoSize) {
                    t.media.setVideoSize(finalWidth, parentHeight);
                }
            }

            targetElement.css({
                'margin-left': Math.floor((parentWidth - finalWidth) / 2),
                'margin-top': 0
            });
        },

        setDimensions: function (width, height) {
            var t = this;

            t.container
                .width(width)
                .height(height);

            t.layers.children('.mejs-layer')
                .width(width)
                .height(height);
        },

        setControlsSize: function () {
            var t = this,
                usedWidth = 0,
                railWidth = 0,
                rail = t.controls.find('.mejs-time-rail'),
                total = t.controls.find('.mejs-time-total'),
                others = rail.siblings(),
                lastControl = others.last(),
                lastControlPosition = null;

            // skip calculation if hidden
            if (!t.container.is(':visible') || !rail.length || !rail.is(':visible')) {
                return;
            }

            // allow the size to come from custom CSS
            if (t.options && !t.options.autosizeProgress) {
                // Also, frontends devs can be more flexible
                // due the opportunity of absolute positioning.
                railWidth = parseInt(rail.css('width'), 10);
            }

            // attempt to autosize
            if (railWidth === 0 || !railWidth) {

                // find the size of all the other controls besides the rail
                others.each(function () {
                    var $this = $(this);
                    if ($this.css('position') != 'absolute' && $this.is(':visible')) {
                        usedWidth += $(this).outerWidth(true);
                    }
                });

                // fit the rail into the remaining space
                railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width());
            }

            // resize the rail,
            // but then check if the last control (say, the fullscreen button) got pushed down
            // this often happens when zoomed
            do {
                // outer area
                rail.width(railWidth);
                // dark space
                total.width(railWidth - (total.outerWidth(true) - total.width()));

                if (lastControl.css('position') != 'absolute') {
                    lastControlPosition = lastControl.length ? lastControl.position() : null;
                    railWidth--;
                }
            } while (lastControlPosition !== null && lastControlPosition.top.toFixed(2) > 0 && railWidth > 0);

            t.container.trigger('controlsresize');
        },


        buildposter: function (player, controls, layers, media) {
            var t = this,
                poster =
                    $('<div class="mejs-poster mejs-layer">' +
                        '</div>')
                        .appendTo(layers),
                posterUrl = player.$media.attr('poster');

            // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
            if (player.options.poster !== '') {
                posterUrl = player.options.poster;
            }

            // second, try the real poster
            if (posterUrl) {
                t.setPoster(posterUrl);
            } else {
                poster.hide();
            }

            media.addEventListener('play', function () {
                poster.hide();
            }, false);

            if (player.options.showPosterWhenEnded && player.options.autoRewind) {
                media.addEventListener('ended', function () {
                    poster.show();
                }, false);
            }
        },

        setPoster: function (url) {
            var t = this,
                posterDiv = t.container.find('.mejs-poster'),
                posterImg = posterDiv.find('img');

            if (posterImg.length === 0) {
                posterImg = $('<img width="100%" height="100%" alt="" />').appendTo(posterDiv);
            }

            posterImg.attr('src', url);
            posterDiv.css({'background-image': 'url(' + url + ')'});
        },

        buildoverlays: function (player, controls, layers, media) {
            var t = this;
            if (!player.isVideo)
                return;

            var
                loading =
                    $('<div class="mejs-overlay mejs-layer">' +
                        '<div class="mejs-overlay-loading"><span></span></div>' +
                        '</div>')
                        .hide() // start out hidden
                        .appendTo(layers),
                error =
                    $('<div class="mejs-overlay mejs-layer">' +
                        '<div class="mejs-overlay-error"></div>' +
                        '</div>')
                        .hide() // start out hidden
                        .appendTo(layers),
                // this needs to come last so it's on top
                bigPlay =
                    $('<div class="mejs-overlay mejs-layer mejs-overlay-play">' +
                        '<div class="mejs-overlay-button"></div>' +
                        '</div>')
                        .appendTo(layers)
                        .bind('click', function () {	 // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video
                            if (t.options.clickToPlayPause) {
                                if (media.paused) {
                                    media.play();
                                }
                            }
                        });

            /*
            if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) {
                bigPlay.remove();
                loading.remove();
            }
            */


            // show/hide big play button
            media.addEventListener('play', function () {
                bigPlay.hide();
                loading.hide();
                controls.find('.mejs-time-buffering').hide();
                error.hide();
            }, false);

            media.addEventListener('playing', function () {
                bigPlay.hide();
                loading.hide();
                controls.find('.mejs-time-buffering').hide();
                error.hide();
            }, false);

            media.addEventListener('seeking', function () {
                loading.show();
                controls.find('.mejs-time-buffering').show();
            }, false);

            media.addEventListener('seeked', function () {
                loading.hide();
                controls.find('.mejs-time-buffering').hide();
            }, false);

            media.addEventListener('pause', function () {
                if (!mejs.MediaFeatures.isiPhone) {
                    bigPlay.show();
                }
            }, false);

            media.addEventListener('waiting', function () {
                loading.show();
                controls.find('.mejs-time-buffering').show();
            }, false);


            // show/hide loading
            media.addEventListener('loadeddata', function () {
                // for some reason Chrome is firing this event
                //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
                //	return;

                loading.show();
                controls.find('.mejs-time-buffering').show();
                // Firing the 'canplay' event after a timeout which isn't getting fired on some Android 4.1 devices (https://github.com/johndyer/mediaelement/issues/1305)
                if (mejs.MediaFeatures.isAndroid) {
                    media.canplayTimeout = window.setTimeout(
                        function () {
                            if (document.createEvent) {
                                var evt = document.createEvent('HTMLEvents');
                                evt.initEvent('canplay', true, true);
                                return media.dispatchEvent(evt);
                            }
                        }, 300
                    );
                }
            }, false);
            media.addEventListener('canplay', function () {
                loading.hide();
                controls.find('.mejs-time-buffering').hide();
                clearTimeout(media.canplayTimeout); // Clear timeout inside 'loadeddata' to prevent 'canplay' to fire twice
            }, false);

            // error handling
            media.addEventListener('error', function (e) {
                t.handleError(e);
                loading.hide();
                bigPlay.hide();
                error.show();
                error.find('.mejs-overlay-error').html("Error loading this resource");
            }, false);

            media.addEventListener('keydown', function (e) {
                t.onkeydown(player, media, e);
            }, false);
        },

        buildkeyboard: function (player, controls, layers, media) {

            var t = this;

            t.container.keydown(function () {
                t.keyboardAction = true;
            });

            // listen for key presses
            t.globalBind('keydown', function (event) {
                player.hasFocus = $(event.target).closest('.mejs-container').length !== 0
                    && $(event.target).closest('.mejs-container').attr('id') === player.$media.closest('.mejs-container').attr('id');
                return t.onkeydown(player, media, event);
            });


            // check if someone clicked outside a player region, then kill its focus
            t.globalBind('click', function (event) {
                player.hasFocus = $(event.target).closest('.mejs-container').length !== 0;
            });

        },
        onkeydown: function (player, media, e) {
            if (player.hasFocus && player.options.enableKeyboard) {
                // find a matching key
                for (var i = 0, il = player.options.keyActions.length; i < il; i++) {
                    var keyAction = player.options.keyActions[i];

                    for (var j = 0, jl = keyAction.keys.length; j < jl; j++) {
                        if (e.keyCode == keyAction.keys[j]) {
                            if (typeof(e.preventDefault) == "function") e.preventDefault();
                            keyAction.action(player, media, e.keyCode, e);
                            return false;
                        }
                    }
                }
            }

            return true;
        },

        findTracks: function () {
            var t = this,
                tracktags = t.$media.find('track');

            // store for use by plugins
            t.tracks = [];
            tracktags.each(function (index, track) {

                track = $(track);

                t.tracks.push({
                    srclang: (track.attr('srclang')) ? track.attr('srclang').toLowerCase() : '',
                    src: track.attr('src'),
                    kind: track.attr('kind'),
                    label: track.attr('label') || '',
                    entries: [],
                    isLoaded: false
                });
            });
        },
        changeSkin: function (className) {
            this.container[0].className = 'mejs-container ' + className;
            this.setPlayerSize(this.width, this.height);
            this.setControlsSize();
        },
        play: function () {
            this.load();
            this.media.play();
        },
        pause: function () {
            try {
                this.media.pause();
            } catch (e) {
            }
        },
        load: function () {
            if (!this.isLoaded) {
                this.media.load();
            }

            this.isLoaded = true;
        },
        setMuted: function (muted) {
            this.media.setMuted(muted);
        },
        setCurrentTime: function (time) {
            this.media.setCurrentTime(time);
        },
        getCurrentTime: function () {
            return this.media.currentTime;
        },
        setVolume: function (volume) {
            this.media.setVolume(volume);
        },
        getVolume: function () {
            return this.media.volume;
        },
        setSrc: function (src) {
            this.media.setSrc(src);
        },
        remove: function () {
            var t = this, featureIndex, feature;

            t.container.prev('.mejs-offscreen').remove();

            // invoke features cleanup
            for (featureIndex in t.options.features) {
                feature = t.options.features[featureIndex];
                if (t['clean' + feature]) {
                    try {
                        t['clean' + feature](t);
                    } catch (e) {
                        // TODO: report control error
                        //throw e;
                        //console.log('error building ' + feature);
                        //console.log(e);
                    }
                }
            }

            // grab video and put it back in place
            if (!t.isDynamic) {
                t.$media.prop('controls', true);
                // detach events from the video
                // TODO: detach event listeners better than this;
                //		 also detach ONLY the events attached by this plugin!
                t.$node.clone().insertBefore(t.container).show();
                t.$node.remove();
            } else {
                t.$node.insertBefore(t.container);
            }

            if (t.media.pluginType !== 'native') {
                t.media.remove();
            }

            // Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api.
            delete mejs.players[t.id];

            if (typeof t.container == 'object') {
                t.container.remove();
            }
            t.globalUnbind();
            delete t.node.player;
        },
        rebuildtracks: function () {
            var t = this;
            t.findTracks();
            t.buildtracks(t, t.controls, t.layers, t.media);
        },
        resetSize: function () {
            var t = this;
            // webkit has trouble doing this without a delay
            setTimeout(function () {
                //console.log("resetSize");
                t.setPlayerSize(t.width, t.height);
                t.setControlsSize();
            }, 50);
        }
    };

    (function () {
        var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/;

        function splitEvents(events, id) {
            // add player ID as an event namespace so it's easier to unbind them all later
            var ret = {d: [], w: []};
            $.each((events || '').split(' '), function (k, v) {
                var eventname = v + '.' + id;
                if (eventname.indexOf('.') === 0) {
                    ret.d.push(eventname);
                    ret.w.push(eventname);
                }
                else {
                    ret[rwindow.test(v) ? 'w' : 'd'].push(eventname);
                }
            });
            ret.d = ret.d.join(' ');
            ret.w = ret.w.join(' ');
            return ret;
        }

        mejs.MediaElementPlayer.prototype.globalBind = function (events, data, callback) {
            var t = this;
            var doc = t.node ? t.node.ownerDocument : document;

            events = splitEvents(events, t.id);
            if (events.d) $(doc).bind(events.d, data, callback);
            if (events.w) $(window).bind(events.w, data, callback);
        };

        mejs.MediaElementPlayer.prototype.globalUnbind = function (events, callback) {
            var t = this;
            var doc = t.node ? t.node.ownerDocument : document;

            events = splitEvents(events, t.id);
            if (events.d) $(doc).unbind(events.d, callback);
            if (events.w) $(window).unbind(events.w, callback);
        };
    })();

    // turn into jQuery plugin
    if (typeof $ != 'undefined') {
        $.fn.mediaelementplayer = function (options) {
            if (options === false) {
                this.each(function () {
                    var player = $(this).data('mediaelementplayer');
                    if (player) {
                        player.remove();
                    }
                    $(this).removeData('mediaelementplayer');
                });
            }
            else {
                this.each(function () {
                    $(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options));
                });
            }
            return this;
        };


        $(document).ready(function () {
            // auto enable using JSON attribute
            $('.mejs-player').mediaelementplayer();
        });
    }

    // push out to window
    window.MediaElementPlayer = mejs.MediaElementPlayer;

})(mejs.$);
