/// <reference path="../ScriptReference/jquery-1.5.2.js"/>
/// <reference path="Mediasite.Core.js"/>
/// <reference path="Mediasite.Player.Core.js"/>
/// <reference path="Mediasite.Player.Localization.js"/>
/*global Mediasite OnDSPlayStateChangeEvt : true, OnDSPositionChangeEvt : true, OnDSOpenStateChangeEvt : true, OnDSScriptCommandEvt : true, OnDSMediaErrorEvt : true, OnFlashMediaPlayerEvent : true */

Mediasite.namespace("MediaPlayer");

Mediasite.MediaPlayer.ModelHtml5 = function (options) {
    /// <summary>Media player model for HTML5 media element</summary>
    /// <param name="options" type="object">{ playBackRates: (array) }</param>
    var $ = jQuery;
    var self = this;

    var mediaElement;
    var mediaSources;
    var isSetUp = false;
    var defaultPlaybackRate = 1;
    var playbackRates = [0.5, 1, 1.5, 2];
    var skipBackAmount = 3000;
    var skipForwardAmount = 10000;
    var lastSeekTime = 0;
    var lastStartTime = null;
    var currentPosition = null;
    var manualSeekFlag = false;
    var inactivityExceeded = false;
    var volume = 50;
    var playFrom = 0;   /// Initial starting position for media, in milliseconds
    var pauseAt = -1;   /// Position to automatically pause media, in milliseconds
    var defaultAutoStart = !Mediasite.BrowserDetect.Embedded();
    var forcedAutoStart = null; /// true, false, or null
    var isOpeningState = false;
    var liveStartTime, liveStartOffset = 0;
    var _isSeekingToEnd = false;
    var _playCount = 0;
    var _safariHlsBadStartCorrector;
    var _hasReachedLiveEnd = false;
    var _liveStartTimeWasSetAt = 0;
    var _zeroEquivalencyThreshold = (1 / 5) * 1000 * 1.05; // 5fps + 5% slop = worst case.

    var fullScreenPollDelay = 500;  /// How often to check that fullscreen is still active, in milliseconds
    var fullScreenTimeoutDelay = 10000; /// When attempting to enter full screen, how long to wait before giving up on logging polling, in milliseconds
    var fullScreenHeartbeatDelay = 20000;   /// When in full screen, how often to tell the server that the user is still watching, in milliseconds
    var canFullScreen = false;
    var enterFullScreen;    // null or function()
    var isFullScreen;       // null or function()
    var fullScreenMode, fullScreenPoll, fullScreenLastHeartbeat, fullScreenTimeout, lastFullScreenTimeUpdate;


    var notifier = new Mediasite.MediaPlayer.ModelEventNotifier();
    this.AddListener = function (list) {
        notifier.AddListener(list);
    };

    function isLivePresentation() {
        return !!liveStartTime;
    }

    // Parse options
    if ($.isPlainObject(options)) {
        // Playback rates
        if ($.isArray(options.playbackRates)) {
            playbackRates = options.playbackRates;
            playbackRates.sort(function (a, b) { return a - b; }) // sort numerically
        }
    }

    // Parse query parameters
    (function parseQueryParams() {
        // Set forcedAutoStart from autoStart param
        var autoStartParam = Mediasite.GetQueryParam("autoStart").toLowerCase();
        if (autoStartParam === "true") {
            forcedAutoStart = true;
        } else if (autoStartParam === "false") {
            forcedAutoStart = false;
        }

        // Set playFrom from playFrom param
        try {
            var playFromParam = parseInt(Mediasite.GetQueryParam("playFrom"), 10);
            if (!isNaN(playFromParam)) {
                playFrom = playFromParam;
            }
        }
        catch (ex) {
        }


        // Set pauseAt from playFrom and duration params
        try {
            var durationParam = parseInt(Mediasite.GetQueryParam("duration"), 10);
            if (!isNaN(durationParam)) {
                pauseAt = playFrom + durationParam;
            }
        }
        catch (ex) {
        }
    })();

    function detectFullScreen(videoElement) {
        /// <summary>Define the appropriate fullscreen functionality for a given browser, if available</summary>

        // This should not be checked until the media is ready (and any on-demand/live settings have already been applied), 
        // since that affects whether fullscreen functionality is enabled

        Mediasite.Debug.Log("Detecting fullscreen functionality...");
        canFullScreen = false; // Default state

        // TODO: see if IE9 ever exposes its own method

        if (typeof videoElement.requestFullScreen === 'function') {
            // W3C standard
            enterFullScreen = function () {
                try {
                    document.requestFullScreen();
                    return true;
                } catch (err) { }
            };

            isFullScreen = function () {
                return document.fullScreen;
            };
        }
        else if (typeof videoElement.webkitEnterFullScreen === 'function') {
            // Webkit browsers

            if (Mediasite.BrowserDetect.Chrome()) {
                // Chrome's implementation is still broken
                // relevant: http://stackoverflow.com/questions/1055214/is-there-a-way-to-make-html5-video-fullScreen
                return;
            } else if (isLivePresentation() && Mediasite.BrowserDetect.Android()) {
                // Android has issues resuming live streams after returning from fullscreen (in Mediasite players)
                return;
            } else {

                // Safari, Mobile Safari, Android (on-demand)
                enterFullScreen = function () {
                    try {
                        videoElement.webkitEnterFullScreen();
                        return true;
                    } catch (err) { }
                };

                isFullScreen = function () {
                    return videoElement.webkitDisplayingFullscreen;
                };
            }
        } else if (typeof videoElement.mozFullScreen === 'function') {
            // Mozilla browsers
            enterFullScreen = function () {
                try {
                    document.mozRequestFullScreen();
                    return true;
                } catch (err) { }
            };

            isFullScreen = function () {
                return document.mozFullScreen;
            };
        }


        if (typeof (enterFullScreen) == "function" && typeof (isFullScreen) == "function") {
            canFullScreen = true;
        }
    }

    function getPositionAndTime() {
        return {
            Position: self.GetPosition(),
            ReportedAt: new Date()
        };
    }

    function startFullScreenLogging() {
        Mediasite.Debug.Log("FULLSCREEN: Preparing for fullscreen mode");
        logPlayback();
        logPlaybackStart();
        lastFullScreenTimeUpdate = null;

        // Clean up any extant timeouts
        clearTimeout(fullScreenPoll);
        clearTimeout(fullScreenTimeout);
        fullScreenLastHeartbeat = null;

        // Wait for fullscreen to activate, with timeout to belay that
        waitForFullScreen();
        fullScreenTimeout = setTimeout(cancelWaitingForFullScreen, fullScreenTimeoutDelay);
    }

    function waitForFullScreen() {
        if (isFullScreen()) {
            clearTimeout(fullScreenTimeout);
            clearTimeout(fullScreenPoll);
            fullScreenMode = true;
            Mediasite.Debug.Log("FULLSCREEN: Fullscreen ready, starting media playback polling");
            whileFullScreen();
        } else {
            setTimeout(waitForFullScreen, 200);
        }
    }

    function cancelWaitingForFullScreen() {
        Mediasite.Debug.Log("FULLSCREEN: Cancelling");
        clearTimeout(fullScreenPoll);
    }

    function whileFullScreen() {
        if (!isFullScreen()) {
            stopFullScreenLogging();
        } else {
            // Still fullscreen

            var now = new Date();
            if (!fullScreenLastHeartbeat) {
                fullScreenLastHeartbeat = now;
            }

            if (now - fullScreenLastHeartbeat > fullScreenHeartbeatDelay) {
                // Send a "heartbeat" logging report for the live tracker

                logFullScreenPlayback(true);
                fullScreenLastHeartbeat = now;
            } else {
                // Use normal logging
                logFullScreenPlayback();
            }


            fullScreenPoll = setTimeout(whileFullScreen, fullScreenPollDelay);
        }
    }

    function stopFullScreenLogging() {
        Mediasite.Debug.Log("FULLSCREEN: User exited fullscreen, stopping media playback polling");
        clearTimeout(fullScreenPoll);
        fullScreenMode = false;

        logPlayback();
        if (!mediaElement.paused) {
            setTimeout(function () { self.Pause() }, 300);  // Wait a moment to accommodate iOS
        }
    }

    function resetMediaElement() {
        self.SetupMediaElement(mediaElement.Rebuild(), mediaSources);
    }


    function logFullScreenPlayback(forceWhilePlaying) {
        /// <summary>
        /// Simulate normal playback reporting while in full-screen mode, which only reliably fires <pre>timeupdate</pre> events.
        /// </summary>
        /// <param name="force" type="bool">Force sending a report</param>

        if (!(isFullScreen() || forceWhilePlaying)) return;

        var now = getPositionAndTime();
        if (lastFullScreenTimeUpdate) {
            var positionDelta = (now.Position - lastFullScreenTimeUpdate.Position);
            var timeDelta = now.ReportedAt - lastFullScreenTimeUpdate.ReportedAt;

            var pd_td = positionDelta / timeDelta;
            if (0 <= pd_td && pd_td < 2) {
                // Normal playback

                if (forceWhilePlaying) {
                    // Just report the current chunk
                    logPlayback(null, lastFullScreenTimeUpdate.Position);
                } else {
                    // Report normally
                    logPlaybackStart(lastFullScreenTimeUpdate.Position);
                }
            } else {
                // User seeked forward or backward

                // Report duration already watched (in theory)
                logPlayback(null, lastFullScreenTimeUpdate.Position);
            }
        }

        lastFullScreenTimeUpdate = now;
    }

    function logPlaybackStart(overridePosition) {
        if (lastStartTime === null) {
            notifier.OnMediaStarted(Mediasite.Player.MediaPlayerType.HTML5);
            var time = typeof (overridePosition) !== "undefined" ? overridePosition : self.GetPosition();
            lastStartTime = time;
            notifier.OnMediaView({ StartTime: Math.floor(time / 1000), Duration: -1 }, false);
        }
    }

    function logPlayback(synchronous, overridePosition) {
        /// <summary>Report that a user has watched a span in the video</summary>
        /// <param name="synchronous" type="bool">Pause JavaScript execution while reporting</param>
        /// <param name="currentPositionOverride" type="number, milliseconds">Position of video that should be reported, instead of the current position</param>

        if (lastStartTime !== null) {
            var reportedPosition = (typeof (overridePosition) != 'undefined' ? overridePosition : currentPosition);

            var start = Math.floor(lastStartTime / 1000);
            var duration = Math.floor((reportedPosition - lastStartTime) / 1000);
            if (duration > 0) {
                notifier.OnMediaView({ StartTime: start, Duration: duration }, synchronous);
            }
            lastStartTime = null;
        }
    }

    function handleMediaError() {
        var errorDescription;

        switch (mediaElement.error.code) {
            case 1: //MEDIA_ERR_ABORTED
                errorDescription = "Media aborted";
                break;
            case 2: //MEDIA_ERR_NETWORK
                errorDescription = "There was a network error loading the media";
                break;
            case 3: //MEDIA_ERR_DECODE
                errorDescription = "There was a media decoding error";
                break;
            case 4: //MEDIA_ERR_NOT_SUPPORTED
                errorDescription = "The current media is not supported";
                break;
            default:
                errorDescription = "Error" + " (" + mediaElement.error.code + ")";
                break;
        }

        notifier.OnMediaError(errorDescription);
    }

    function handleMediaEvent(event) {
        if (_safariHlsBadStartCorrector) {
            _safariHlsBadStartCorrector.noteMediaEvent(event.type);
        }

        switch (event.type) {
            case "loadstart":
                isOpeningState = true;
                notifier.OnStateChanged(Mediasite.Player.MediaState.Opening);
                notifier.OnMediaLoadStart();
                break;
            case "canplay":
            case "suspend":
            case "stalled":
                if (isOpeningState) {
                    // Invalidate the Opening state when we can play or have encounterd 
                    // suspend or stalled. This is intended to prevent the player from 
                    // getting stuck showing "Opening" when it's really not doing so.
                    notifier.OnStateChanged(Mediasite.Player.MediaState.Undefined);
                    isOpeningState = false;
                }
                break;
            case "durationchange": // fall through to loadedmetadata
            case "loadedmetadata":
                // In desktop browsers, this fires when the video loads on pageload and is a reliable indicator that the media metadata is available.
                // On mobile devices, this fires several times during pageload, but should only be acted on when the media is actually loaded, after user initiates playing.
                // To indicate if a media metadata is available, check if the media has a plausible duration.
                // 
                // Before properly loading the media, various browsers report various values for media.duration:
                //      Android:       video.duration = 1
                //      Blackberry:    video.duration = Infinity

                // Do initial setup if video is available
                var isLive = isLivePresentation();
                var mediaIsReady = isLive
                    || (mediaElement.duration > 1 && mediaElement.duration != Infinity);

                Mediasite.Debug.Log("Attempting to load presentation metadata.", "\n  Is presentation live broadcast?", isLive, "\n  Is presentation metadata ready?", mediaIsReady);
                if (!isSetUp && mediaIsReady) {
                    Mediasite.Debug.Log("Loading presentation metadata ... ");

                    self.SetVolume(volume);
                    var canChangeSpeed = !(isLive || Mediasite.BrowserDetect.IOS() || Mediasite.BrowserDetect.Android());

                    detectFullScreen(mediaElement);

                    notifier.OnMediaLoaded({
                        Duration: self.GetDuration(),
                        CanChangeSpeed: canChangeSpeed,
                        CanFullScreen: canFullScreen,
                        CurrentLanguageIndex: 0,
                        Languages: {}
                    });

                    // Set starting point for presentation playback
                    if (playFrom > 0) {
                        // Change presentation position to start position

                        var setPosition = function () {
                            self.SetPosition(playFrom);
                            playFrom = 0;
                        };

                        if (Mediasite.BrowserDetect.Mobile()) {
                            setTimeout(setPosition, 300); // TODO: determine if this timeout is dependable
                        } else {
                            setPosition();
                        }
                    }

                    // Autostart or pause, as appropriate
                    if (Mediasite.BrowserDetect.Mobile()) {
                        // Do nothing: user initiated playing, so carry on playing
                    }
                    else if (forcedAutoStart === false) {
                        self.Pause();
                    }
                    else if (forcedAutoStart === true) {
                        self.Play();
                    }
                    else if (defaultAutoStart) {
                        self.Play();
                    }
                    else {
                        self.Pause();
                    }

                    Mediasite.Debug.Log("... finished loading presentation metadata.");
                    isSetUp = true;
                }
                break;
            case "volumechange":
                var vol = 0;
                if (!mediaElement.muted) {
                    vol = Math.round(self.GetVolume());
                }
                notifier.OnVolumeChanged(vol);
                break;
            case "ratechange":
                var playbackRateInfo = {
                    Min: playbackRates[0],
                    Max: playbackRates[playbackRates.length - 1],
                    Current: Math.round(self.GetPlaybackRate() * 100) / 100,
                    Rates: $.extend([], playbackRates)
                };
                notifier.OnPlaybackRateChanged(playbackRateInfo);
                break;
            case "playing":
                notifier.OnStateChanged(Mediasite.Player.MediaState.Playing);

                // HLS in iOS provides a true position, but Safari on OS X gives us no way to 
                // know where we really are in a live HLS stream. For OS X Safari, guestimate 
                // how far off the reported position is from true position. Android seems to 
                // always start playing at zero, so we need to use the position reported.
                if (isLivePresentation() && Mediasite.BrowserDetect.Safari() && !Mediasite.BrowserDetect.Mobile()) {
                    var now = new Date();
                    liveStartOffset = now - liveStartTime
                        - 39000    // Fudge it a little for lag
                        - (mediaElement.currentTime * 1000); // to account for media not resetting its current time to 0 when stopping
                    liveStartOffset = Math.max(0, liveStartOffset);
                }



                // When pausing and then playing, some clients continue playing from 
                // where they were paused. We'd them to catch up to live.
                if (isLivePresentation() && mediaElement.seekable && mediaElement.seekable.length > 0) {
                    var end = mediaElement.seekable.end(mediaElement.seekable.length - 1);
                    end -= 1; // Back off from absolute end to avoid over-seeking problems.

                    // Only attempt to seek to end/live if current time is more than a couple of seconds behind.
                    if (end - 2 > mediaElement.currentTime) {
                        Mediasite.Debug.Log('Seeking closer to end.');
                        _isSeekingToEnd = true;
                        mediaElement.currentTime = end;
                        return; // ### Early out that intentionally avoids loggin playback start. ###
                    }
                }

                _isSeekingToEnd = false;

                _playCount++;


                logPlaybackStart();

                break;
            case "pause":
                logPlayback();
                //html5 mediaelement uses pause to stop as well as to pause,
                //so only record paused when position is not 0
                if (self.GetPosition() != 0 || !defaultAutoStart || Mediasite.GetQueryParam("autoStart").toLowerCase() === "false") {
                    notifier.OnStateChanged(Mediasite.Player.MediaState.Paused);
                }
                break;
            case "seeking":
                logPlayback();
                break;
            case "seeked":
                _isSeekingToEnd = false;
                logPlayback();
                if (manualSeekFlag) {
                    manualSeekFlag = false;
                }
                else {
                    lastSeekTime = self.GetPosition();
                }
                if (!mediaElement.paused) {
                    logPlaybackStart();
                }
                break;
            case "timeupdate":
                if (_isSeekingToEnd) {
                    // We don't want to update our current position until 
                    // we've settled on where we want to play from. 
                    return;
                }

                if (_hasReachedLiveEnd) {
                    // Don't listen to that silly media element. 
                    // If live has ended, we're at zero.
                    notifier.OnPositionChanged(0);
                    return;
                }

                if (!manualSeekFlag) {
                    currentPosition = self.GetPosition();
                    if (currentPosition >= lastSeekTime) {
                        notifier.OnPositionChanged(currentPosition);
                    }
                }
                if (pauseAt > 0 && pauseAt <= currentPosition) {
                    self.Pause();
                    pauseAt = -1;
                }
                break;
            case "ended":
                logPlayback();
                notifier.OnStateChanged(Mediasite.Player.MediaState.MediaEnded);
                self.Stop();
                lastSeekTime = 0;
                notifier.OnPositionChanged(0);
                break;
        }
    }

    function getNextPlaybackRate(faster, loop) {
        /// <summary>Select the playback rate which is after (faster or slower) than the current playback rate</summary>
        /// <param name="faster" type="bool">True for faster playrate (default), false for a slower playrate</param>
        /// <param name="loop" type="bool">True for loop-around (speed > max ==> min and vice versa) (default), false for stopping at the min/max</param>

        // Sanitize parameters
        faster = faster !== false; // cast to boolean, undefined = true
        loop = loop !== false;  // cast to boolean, undefined = true

        // Get current playback rate
        var speed = Math.round(self.GetPlaybackRate() * 100) / 100;

        // Fetch and sanitize available playback rates (non-destructively)
        var toggleSteps = $.extend([], playbackRates);
        if (toggleSteps.length == 0) {
            toggleSteps = [1];
        }

        // If selecting a slower speed, scan the list in reverse.  (Mentally flip "min" and "max")
        var sortCoefficient = (faster ? 1 : -1);
        toggleSteps.sort(function (a, b) { return sortCoefficient * (a - b); });

        // Select the next playback rate
        if (loop && 0 >= sortCoefficient * (toggleSteps[toggleSteps.length - 1] - speed)) { // speed > max, accounting for faster/slower
            // Speed > max, so loop around to min 
            speed = toggleSteps[0];
        } else {
            // Select the speed faster/slower than the current speed
            $.each(toggleSteps, function (i, thisToggleStep) {
                if (0 >= sortCoefficient * (thisToggleStep - speed) // speed >= thisToggleSpeed, accounting for faster/slower
                    && 0 < sortCoefficient * (toggleSteps[i + 1] - speed)) { // speed < toggleSteps[i + 1], same
                    speed = toggleSteps[i + 1];
                    return false;
                }
            });
        }

        return speed;
    };

    this.setDefaultAutoStart = function (autoStart) {
        defaultAutoStart = autoStart;
    };

    this.SetupMediaElement = function (element, sources) {
        var msSinceLiveStartWasSet = (new Date()).getTime() - _liveStartTimeWasSetAt;
        var isTransitionFromWaitingToLive = msSinceLiveStartWasSet < 2 * 1000;

        mediaElement = element.get(0);
        mediaSources = sources;

        // Set up video element's properties        
        if (Mediasite.GetQueryParam("autoStart").toLowerCase() !== "false") {
            mediaElement.setAttribute('autoplay', 'autoplay');
        }

        // Android allegedly works better if preload=none; autoplay overrides preload. (Android shouldn't autoplay anyway.)
        if (Mediasite.BrowserDetect.Android()) {
            mediaElement.removeAttribute('autoplay');
            mediaElement.setAttribute('preload', 'none');
        }

        // Set up event handling
        element.bind("stalled canplaythrough emptied suspend waiting abort error loadeddata canplay loadstart play playing pause loadedmetadata ended seeking seeked ratechange durationchange volumechange timeupdate", handleMediaEvent);
        element.bind("error", handleMediaError);
        element.bind('playCoverPressed', function () { self.Play(); });

        // Determine which media source to use
        var mediaSource;
        var mediaType;
        for (var i = 0; i < sources.length; i++) {
            if (sources[i].MimeType === "video/mp4" || sources[i].MimeType === "audio/x-mpegurl") {
                mediaSource = sources[i].Location;
                mediaType = sources[i].MimeType;

                break;
            }
        }

        // Sanity check media source
        if (typeof (mediaSource) === 'undefined') {
            notifier.OnMediaError(Mediasite.Player.Localization.MediaPlayer.NoCompatibleMediaError);
            return;
        }


        if (isTransitionFromWaitingToLive) {
            touchThePublishingPoint(mediaSource, mediaType, function () {
                setVideoElementSource(element, mediaSource, mediaType);
            });
        } else {
            setVideoElementSource(element, mediaSource, mediaType);
        }


        $(window).unload(function () { logPlayback(true); });
    };

    function setVideoElementSource(element, mediaSource, mediaType) {
        var sourceElement = $('<source></source>');
        sourceElement.attr({
            'src': mediaSource,
            'type': mediaType
        });
        element.prepend(sourceElement);
    }

    function touchThePublishingPoint(mediaSource, mediaType, callback) {
        Mediasite.Debug.Log('Kicking the HLS publishing point to make sure it is awake.');
        var $vid = $('<video webkit-playsinline></video>');
        var vid = $vid.get(0);
        setVideoElementSource($vid, mediaSource, mediaType);
        vid.load();

        window.setTimeout(function () {
            vid.pause();
            $vid.remove();
            $vid = null;
            vid = null;

            callback();
        }, 1000);
    }

    this.UpdateOptions = function (options) {
        volume = options.Volume;

        // When we get the UpdateOptions call, we know that the 
        // controller is ready for our notifications. This is the 
        // closest thing we have to a Ready or Initialized method.

        // HLS is always significantly delayed, so we can be lazy 
        // about transitioning from the waiting to live states. 
        // If we're too eager to load an HLS stream, we can 
        // attempt to load before the publishing point is ready, 
        // causing the video element to get into an unrecoverable 
        // non-loaded state.
        notifier.OnEnableLiveStartDelay(10 * 1000);
    };

    this.LiveStatusChange = function (status) {
        var ps = Mediasite.Player.PresentationStatus;
        var isLively = (status == ps.Live || status == ps.LivePaused || status == ps.OpenForLive || status == ps.ScheduledForLive);

        if (isLively) {
            notifier.OnEnableLiveJumpBackDetection();
        }

        if (isLively && !_safariHlsBadStartCorrector && Mediasite.BrowserDetect.MacSafari() && Mediasite.BrowserDetect.MacOS10DotWhat() < 7) {
            _safariHlsBadStartCorrector = new SafariHlsBadStartCorrector(resetMediaElement);
        }

        if (status == ps.LiveEnded) {
            _hasReachedLiveEnd = true;
            notifier.OnPositionChanged(0);
            this.Stop();
        }
    };

    this.InactivityExceeded = function () {
        inactivityExceeded = true;

        if (status === Mediasite.Player.PresentationStatus.Live || status === Mediasite.Player.PresentationStatus.LiveEnded) {
            window.location.reload();
        }
    };

    this.SetPosition = function (positionInMS) {
        var canSeek = false;
        if (mediaElement.seekable.length > 0) {
            for (i = 0; i < mediaElement.seekable.length; i++) {
                if (positionInMS >= (mediaElement.seekable.start(i) * 1000) && positionInMS <= (mediaElement.seekable.end(i) * 1000)) {
                    canSeek = true;
                }
            }
        }

        if (canSeek) {
            manualSeekFlag = true;
            lastSeekTime = positionInMS;
            mediaElement.currentTime = (positionInMS / 1000).toFixed(1);
            notifier.OnPositionChanged(positionInMS);
        } else {
            // Set start position for when video can play
            playFrom = positionInMS;
        }

    };

    this.PlayDefaultRate = function () {
        var playbackRate = defaultPlaybackRate;
        self.SetPlaybackRate(playbackRate);
    };

    this.PlayFaster = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(true, doLoop);
        self.SetPlaybackRate(playbackRate);
    };

    this.PlaySlower = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(false, doLoop);
        self.SetPlaybackRate(playbackRate);
    };

    this.SkipBack = function () {
        var newPosition = self.GetPosition() - skipBackAmount;

        if (newPosition < 0) {
            newPosition = 0;
        }

        self.SetPosition(newPosition);
    };

    this.SkipForward = function () {
        var newPosition = self.GetPosition() + skipForwardAmount;

        if (newPosition < self.GetDuration()) {
            self.SetPosition(newPosition);
        }
    };

    this.FullScreen = function () {
        if (canFullScreen && typeof (enterFullScreen) == "function") {
            startFullScreenLogging();
            enterFullScreen();
        }
    };

    this.SetAudioLanguage = function (language) {
    };

    this.Pause = function () {
        mediaElement.pause();
        notifier.OnStateChanged(Mediasite.Player.MediaState.Paused);
    };

    this.Play = function () {
        if (inactivityExceeded === true) {
            window.location.reload();
        }
        else {
            if (isLivePresentation() && _playCount > 0) {
                if (Mediasite.BrowserDetect.IOS4()) {
                    // iOS 4.3 tends to freeze when attempting to play a live HLS stream 
                    // after pausing. We can cause the stream to play from the correct 
                    // (live) position if we load() the video element. Unfortunately, 
                    // other user agents (e.g. iOS 5.x) don't like this, so we'll target 
                    // this behavior to iOS 4.x.
                    mediaElement.load();
                    return;
                } else if (Mediasite.BrowserDetect.MacSafari() && Mediasite.BrowserDetect.MacOS10DotWhat() < 7) {
                    // Mac Safari on OS X 10.6 plays from the paused position 
                    // when resuming play. This can cause the video to lag (significantly) 
                    // behind live. Destroy and recreate the media element to avoid issues.
                    // This approach does not work well on clients that require user interaction 
                    // on first play, since the user will have to press play again.
                    resetMediaElement();
                    return;
                }
            }

            // Remember the pre-play playback rate.
            var rateBeforePlay = mediaElement.playbackRate;
            mediaElement.play();

            if (mediaElement.playbackRate != rateBeforePlay) {
                // Restore the pre-play rate.
                // IE9 sets the rate to 1.0 whenever play() is called, 
                // but the correct behavior for our player is to maintain 
                // playback rate. Silly IE! Note that if the user 
                // right-clicks on the video and chooses "Play" from the 
                // context menu, the playback rate will still be set to 1.0.
                mediaElement.playbackRate = rateBeforePlay;
            }
        }
    };

    this.Stop = function () {
        mediaElement.pause();
        mediaElement.currentTime = 0;
        notifier.OnStateChanged(Mediasite.Player.MediaState.Stopped);
    };

    this.GetPosition = function () {
        var position = mediaElement.currentTime * 1000 + liveStartOffset;

        if (position < _zeroEquivalencyThreshold) {
            // In iOS, seeking back to zero may cause the video element to 
            // come to rest at a position slightly greater than zero. This 
            // seems to occur when the MP4 content was transcoded from 
            // a Smooth Streaming source using the MATCH SOURCE option. 
            // The position at which the video element rests is equal to 
            // the time between frames in the video. For example, for 25fps 
            // content, the position is 40ms. To work around this behavior, 
            // we will return zero from positions very close to zero.
            return 0;
        }

        return position;
    };

    this.GetDuration = function () {
        if (mediaElement.duration == Number.POSITIVE_INFINITY) {
            return 0;
        } else if (isLivePresentation()) {
            return 0;
        }
        return mediaElement.duration * 1000;
    };

    this.SetVolume = function (val) {
        mediaElement.volume = val / 100;
        notifier.OnVolumeChanged(Math.round(self.GetVolume()));
    };

    this.GetVolume = function () {
        return mediaElement.volume * 100;
    };

    this.StepVolumeUp = function () {
        if (mediaElement.volume <= 0.8) {
            this.SetVolume((mediaElement.volume + 0.2) * 100);
        }
        else {
            this.SetVolume(100);
        }
    };

    this.StepVolumeDown = function () {
        if (mediaElement.volume > 0.2) {
            this.SetVolume((mediaElement.volume - 0.2) * 100);
        }
        else {
            this.SetVolume(0);
        }
    };

    this.ToggleMute = function () {
        mediaElement.muted = !mediaElement.muted;

        if (mediaElement.muted) {
            notifier.OnVolumeChanged(0);
        }
        else {
            if (mediaElement.volume === 0) {
                this.SetVolume(50);
            }
            else {
                notifier.OnVolumeChanged(Math.round(self.GetVolume()));
            }
        }
    };

    this.TogglePlayPause = function () {
        // In iOS 4.3 mediaElement.ended is always true after about the first three 
        // seconds of live HLS content, so only trust that during on-demand.
        if (mediaElement.paused || (!isLivePresentation() && mediaElement.ended)) {
            self.Play();
        }
        else {
            self.Pause();
        }
    };

    this.SetPlaybackRate = function (speed) {
        mediaElement.playbackRate = speed;
    };

    this.GetPlaybackRate = function () {
        return mediaElement.playbackRate;
    };

    this.GetVideoWidth = function () {
        return mediaElement.videoWidth;
    };

    this.SetLiveStartTime = function (time) {
        liveStartTime = time;
        _liveStartTimeWasSetAt = (new Date()).getTime();
    };




    function SafariHlsBadStartCorrector(resetFunction) {

        // Mac Safari on OS X 10.6 can fail to start live HLS 
        // playback if connecting at the beginning of the live stream. 
        // In this case the media element raises the loadstart event, 
        // but no events after that until we intervene.


        var _resetFunction = resetFunction;
        var _detectionIntervals = [5 * 1000, 10 * 1000, 20 * 1000]; // Three attempts with progressively longer delays.
        var _correctionAttempts = 0;
        var _timer = null;
        var _enabled = true;

        this.noteMediaEvent = function (eventType) {
            if (eventType == "canplay") {
                // If we see the canplay event, we're safely past the 
                // Safari HSL false start issue. Stop all detection.

                if (correctionIsScheduled()) {
                    cancelScheduledCorrection();
                }

                _enabled = false;

            } else if (eventType == "loadstart" && shouldScheduleCorrection()) {
                scheduleNextCorrection();
            }
        };


        function correctionIsScheduled() {
            return !!_timer;
        }

        function cancelScheduledCorrection() {
            window.clearTimeout(_timer);
            _timer = null;
        }

        function scheduleNextCorrection() {
            var delay = _detectionIntervals[_correctionAttempts];
            _timer = window.setTimeout(timer_tick, delay);
        }

        function timer_tick() {
            _resetFunction();
            _correctionAttempts++;
            _timer = null;
        }

        function shouldScheduleCorrection() {
            return _enabled && (_correctionAttempts < _detectionIntervals.length);
        }
    }
};

Mediasite.MediaPlayer.ModelSilverlight = function () {
    var $ = jQuery;
    var self = this;

    var mediaElement;
    var mediaElementObject;
    var mediaSource;

    var playbackRates = [0.5, 1, 1.5, 1.99]; // Setting to 2.0x seems to go to 1.0x instead. Bug?
    var currentPlaybackRateIndex = 4;
    var skipBackAmount = 3000;
    var skipForwardAmount = 10000;
    var lastSeekTime = 0;
    var lastStartTime = null;
    var currentPosition;
    var volume = 50;
    var pauseAt = -1;
    var inactivityExceeded = false;
    var defaultAutoStart = !Mediasite.BrowserDetect.Embedded();

    var liveStartTime;
    var liveStartOffset = 0;

    var positionUpdateTimer = null;
    var positionUpdateInterval = 200;

    var notifier = new Mediasite.MediaPlayer.ModelEventNotifier();
    this.AddListener = function (list) {
        notifier.AddListener(list);
    };

    function positionUpdateTimerCallback() {
        currentPosition = self.GetPosition();
        if (currentPosition >= lastSeekTime) {
            notifier.OnPositionChanged(currentPosition);
        }
        if (pauseAt > 0 && pauseAt <= currentPosition) {
            self.Pause();
            pauseAt = -1;
        }
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function startPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function stopPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
    }

    function handleMediaError(sender, args) {
        if (args.errorCode === 1001 && mediaSource.toLowerCase().substr(0, 4) === 'http') {
            return; //ignore progressive download -> streaming transition
        }

        var errorDescription;
        switch (args.errorCode) {
            case 4001:
                errorDescription = Mediasite.Player.Localization.MediaPlayer.SilverlightNetworkError + " (" + args.errorMessage + ")";
                break;
            case 2203:
                errorDescription = Mediasite.Player.Localization.MediaPlayer.SilverlightSetValueError + args.methodName;
                break;
            default:
                errorDescription = Mediasite.Player.Localization.MediaPlayer.GeneralError + " (" + args.errorCode + " - " + args.errorMessage + ")";
                break;
        }
        notifier.OnMediaError(errorDescription);
    }

    function convertState(state) {
        switch (state.toLowerCase()) {
            case 'buffering':
                return Mediasite.Player.MediaState.Buffering;
            case 'closed':
                return Mediasite.Player.MediaState.Closed;
            case 'error':
                return Mediasite.Player.MediaState.Error;
            case 'opening':
                return Mediasite.Player.MediaState.Opening;
            case 'paused':
                return Mediasite.Player.MediaState.Paused;
            case 'playing':
                return Mediasite.Player.MediaState.Playing;
            case 'stopped':
                return Mediasite.Player.MediaState.Stopped;
            default:
                return Mediasite.Player.MediaState.Undefined;
        }
    }

    function logPlaybackStart() {
        if (lastStartTime === null) {
            notifier.OnMediaStarted(Mediasite.Player.MediaPlayerType.Silverlight);
            var time = self.GetPosition();
            notifier.OnMediaView({ StartTime: Math.floor(time / 1000), Duration: -1 }, false);
            lastStartTime = time;
        }
    }

    function logPlayback(synchronous) {
        if (lastStartTime !== null) {
            var start = Math.floor(lastStartTime / 1000);
            var duration = Math.floor((currentPosition - lastStartTime) / 1000);
            if (duration > 0) {
                notifier.OnMediaView({ StartTime: start, Duration: duration }, synchronous);
            }
            lastStartTime = null;
        }
    }

    function handleMediaEvent(event, param) {
        switch (event) {
            case "MediaOpened":
                notifier.OnMediaLoadStart();
                notifier.OnMediaLoaded({
                    Duration: self.GetDuration(),
                    AudioOnly: (mediaElement.NaturalVideoHeight === 0),
                    CanChangeSpeed: false, //Not working in SL5 beta yet: Silverlight.isInstalled("5"),
                    CanFullScreen: false,
                    CurrentLanguageIndex: self.GetCurrentAudioLanguageIndex(),
                    Languages: self.GetAudioLanguages()
                });

                var playFrom = 0;
                try {
                    playFrom = parseInt(Mediasite.GetQueryParam("playFrom"), 10);
                    if (isNaN(playFrom)) {
                        playFrom = 0;
                    }
                }
                catch (ex) {
                }
                if (playFrom > 0) {
                    self.SetPosition(playFrom);
                }
                if (Mediasite.GetQueryParam("autoStart").toLowerCase() === "false") {
                    self.Pause();
                }
                else {
                    if (Mediasite.GetQueryParam("autoStart").toLowerCase() === "true") {
                        self.Play();
                    }
                    else {
                        if (defaultAutoStart) {
                            self.Play();
                        }
                        else {
                            self.Pause();
                        }
                    }
                }

                var duration = 0;
                try {
                    duration = parseInt(Mediasite.GetQueryParam("duration"), 10);
                }
                catch (ex2) {
                }
                if (duration > 0) {
                    pauseAt = playFrom + duration;
                }
                break;
            case "MediaEnded":
                logPlayback();
                self.Stop();
                lastSeekTime = 0;
                notifier.OnPositionChanged(0);
                notifier.OnStateChanged(Mediasite.Player.MediaState.MediaEnded);
                break;
            case "StateChanged":
                var state = convertState(mediaElement.currentState);
                switch (state) {
                    case Mediasite.Player.MediaState.Playing:
                        if (liveStartTime) {
                            liveStartOffset = (new Date().getTime() - liveStartTime.getTime() - 10000);
                        }
                        logPlaybackStart();
                        startPositionUpdateTimer();
                        break;
                    case Mediasite.Player.MediaState.Paused:
                    case Mediasite.Player.MediaState.Stopped:
                        logPlayback();
                        stopPositionUpdateTimer();
                        break;
                    default:
                        stopPositionUpdateTimer();
                        break;
                }
                notifier.OnStateChanged(state);
                break;
            case "MarkerReached":
                if (param.Type === "MS6") {
                    var newOffset = parseInt(param.Command, 10) - mediaElement.position.seconds * 1000;
                    if (!isNaN(newOffset)) {
                        liveStartOffset = newOffset;
                    }
                }
                notifier.OnMediaEvent(param);
                break;
            case "RateChanged":
                var playbackRateInfo = {
                    Min: playbackRates[0],
                    Max: playbackRates[playbackRates.length - 1],
                    Current: Math.round(self.GetPlaybackRate() * 100) / 100
                };
                notifier.OnPlaybackRateChanged(playbackRateInfo);
                break;
        }
    }

    function waitForPlayerInit() {
        if (!mediaElementObject.content || !mediaElementObject.content.findName('media')) {
            setTimeout(waitForPlayerInit, 200);
            return;
        }

        mediaElement = mediaElementObject.content.findName('media');
        mediaElement.width = mediaElementObject.width;
        mediaElement.height = mediaElementObject.height;

        mediaElement.addEventListener("mediaFailed", function (sender, args) { handleMediaError(sender, args); });
        mediaElement.addEventListener("mediaEnded", function (sender, args) { handleMediaEvent("MediaEnded", args); });
        mediaElement.addEventListener("mediaOpened", function (sender, args) { handleMediaEvent("MediaOpened", args); });
        mediaElement.addEventListener("currentStateChanged", function (sender, args) { handleMediaEvent("StateChanged", args); });
        mediaElement.addEventListener('markerReached', function (sender, args) { handleMediaEvent("MarkerReached", { Type: args.marker.type, Command: args.marker.text }); });

        try {
            mediaElement.addEventListener('rateChanged', function (sender, args) { handleMediaEvent("RateChanged", args); });
        }
        catch (e) {
        }

        mediaElement.source = mediaSource;
        self.SetVolume(volume);
    }

    function parseTime(timeInMS) {
        var hh = Math.floor(timeInMS / 3600000);

        var remainingMS = timeInMS - hh * 3600000;
        var mm = Math.floor(remainingMS / 60000);

        remainingMS = remainingMS - mm * 60000;
        var ss = Math.floor(remainingMS / 1000);

        remainingMS = remainingMS = ss * 1000;
        var ff = Math.floor(remainingMS);

        return { HH: hh, MM: mm, SS: ss, FF: ff };
    }

    this.setDefaultAutoStart = function (autoStart) {
        defaultAutoStart = autoStart;
    };

    this.SetupMediaElement = function (element, sources) {
        var i;
        mediaElementObject = element.get(0);

        for (i = 0; i < sources.length; i++) {
            if (sources[i].MimeType === "video/x-ms-wmv" || sources[i].MimeType === "video/mp4") {
                mediaSource = sources[i].Location;

                break;
            }
        }

        if (typeof (mediaSource) === 'undefined') {
            notifier.OnMediaError(Mediasite.Player.Localization.MediaPlayer.NoCompatibleMediaError);
            return;
        }

        setTimeout(waitForPlayerInit, 500);

        $(window).unload(function () { logPlayback(true); });
    };

    this.UpdateOptions = function (options) {
        volume = options.Volume;
    };

    this.LiveStatusChange = function (status) {
        if (status === Mediasite.Player.PresentationStatus.LiveEnded) {
            handleMediaEvent = function () { };
            handleMediaError = function () { };
            self.Stop();
            notifier.OnStateChanged(Mediasite.Player.MediaState.Stopped);
        }
    };

    this.InactivityExceeded = function () {
        inactivityExceeded = true;
    };

    this.SetPosition = function (positionInMS) {
        if (positionInMS >= 0 && positionInMS < self.GetDuration()) {
            logPlayback();
            lastSeekTime = positionInMS;
            var parsed = parseTime(positionInMS);
            mediaElement.position = parsed.HH + ":" + parsed.MM + ":" + parsed.SS + "." + parsed.FF;
            notifier.OnPositionChanged(positionInMS);

            if (convertState(mediaElement.currentState) === Mediasite.Player.MediaState.Playing) {
                logPlaybackStart();
            }
        }
    };

    this.PlayFaster = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(true, doLoop);
        self.SetPlaybackRate(playbackRate);
    };

    this.PlaySlower = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(false, doLoop);
        self.SetPlaybackRate(playbackRate);
    };

    function getNextPlaybackRate(faster, loop) {
        /// <summary>Select the playback rate which is after (faster or slower) than the current playback rate</summary>
        /// <param name="faster" type="bool">True for faster playrate (default), false for a slower playrate</param>
        /// <param name="loop" type="bool">True for loop-around (speed > max ==> min and vice versa) (default), false for stopping at the min/max</param>

        // Sanitize parameters
        faster = faster !== false; // cast to boolean, undefined = true
        loop = loop !== false;  // cast to boolean, undefined = true

        // Get current playback rate
        var speed = Math.round(self.GetPlaybackRate() * 100) / 100;

        // Fetch and sanitize available playback rates (non-destructively)
        var toggleSteps = $.extend([], playbackRates);
        if (toggleSteps.length == 0) {
            toggleSteps = [1];
        }

        // If selecting a slower speed, scan the list in reverse.  (Mentally flip "min" and "max")
        var sortCoefficient = (faster ? 1 : -1);
        toggleSteps.sort(function (a, b) { return sortCoefficient * (a - b); });

        // Select the next playback rate
        if (loop && 0 >= sortCoefficient * (toggleSteps[toggleSteps.length - 1] - speed)) { // speed > max, accounting for faster/slower
            // Speed > max, so loop around to min 
            speed = toggleSteps[0];
        } else {
            // Select the speed faster/slower than the current speed
            $.each(toggleSteps, function (i, thisToggleStep) {
                if (0 >= sortCoefficient * (thisToggleStep - speed) // speed >= thisToggleSpeed, accounting for faster/slower
                    && 0 < sortCoefficient * (toggleSteps[i + 1] - speed)) { // speed < toggleSteps[i + 1], same
                    speed = toggleSteps[i + 1];
                    return false;
                }
            });
        }

        return speed;
    };

    this.SkipBack = function () {
        var newPosition = self.GetPosition() - skipBackAmount;

        if (newPosition < 0) {
            newPosition = 0;
        }

        self.SetPosition(newPosition);
    };

    this.SkipForward = function () {
        var newPosition = self.GetPosition() + skipForwardAmount;

        if (newPosition < self.GetDuration()) {
            self.SetPosition(newPosition);
        }
    };

    this.FullScreen = function () {
        /// <summary>This only works if the event initiating this originates from Silverlight</summary>
        // http://codeclimber.net.nz/archive/2008/02/06/Sharing-some-Silverlights-bits-Full-Screen-Mode-cannot-be-changed.aspx

        mediaElementObject.content.fullScreen = true;
    };

    this.Pause = function () {
        mediaElement.pause();
    };

    this.Play = function () {
        if (inactivityExceeded === true) {
            window.location.reload();
        }
        else {
            mediaElement.play();
        }
    };

    this.Stop = function () {
        mediaElement.stop();
    };

    this.GetPosition = function () {
        return mediaElement.position.seconds * 1000 + liveStartOffset;
    };

    this.GetDuration = function () {
        return mediaElement.naturalDuration.seconds * 1000;
    };

    this.SetVolume = function (val) {
        mediaElement.volume = val / 100;
        notifier.OnVolumeChanged(Math.round(self.GetVolume()));
    };

    this.GetVolume = function () {
        return mediaElement.volume * 100;
    };

    this.StepVolumeUp = function () {
        if (mediaElement.volume <= 0.8) {
            this.SetVolume((mediaElement.volume + 0.2) * 100);
        }
        else {
            this.SetVolume(100);
        }
    };

    this.StepVolumeDown = function () {
        if (mediaElement.volume > 0.2) {
            this.SetVolume((mediaElement.volume - 0.2) * 100);
        }
        else {
            this.SetVolume(0);
        }
    };

    this.ToggleMute = function () {
        mediaElement.isMuted = !mediaElement.isMuted;

        if (mediaElement.isMuted) {
            notifier.OnVolumeChanged(0);
        }
        else {
            var volume = Math.round(self.GetVolume());

            if (volume === 0) {
                self.SetVolume(50);
            }
            else {
                notifier.OnVolumeChanged(Math.round(self.GetVolume()));
            }
        }
    };

    this.TogglePlayPause = function () {
        if (convertState(mediaElement.currentState) === Mediasite.Player.MediaState.Playing) {
            if (self.GetDuration() > 0) {
                self.Pause();
            }
            else {
                self.Stop();
            }
        }
        else {
            self.Play();
        }
    };

    this.SetPlaybackRate = function (speed) {
        mediaElement.PlaybackRate = speed;
    };

    this.GetPlaybackRate = function () {
        if (mediaElement.PlaybackRate > 1.9 && mediaElement.PlaybackRate < 2.0) {
            return 2.0;
        }
        return mediaElement.PlaybackRate;
    };

    this.GetCurrentAudioLanguageIndex = function () {
        return 1;
    };

    this.GetAudioLanguages = function () {
        var i, languageCount = mediaElement.AudioStreamCount;
        if (languageCount > 1) {
            var languages = [];
            for (i = 0; i < languageCount; ++i) {
                //Silverlight currently does not provide locale info
                languages[i] = { Index: i, Locale: 0, DisplayName: Mediasite.Player.Localization.Common.AudioTrack + ' ' + (i + 1) };
            }
            return languages;
        }
        return {};
    };

    this.SetAudioLanguage = function (language) {
        mediaElement.AudioStreamIndex = language.Index;
    };

    this.SetLiveStartTime = function (time) {
        liveStartTime = time;
    };
};

Mediasite.MediaPlayer.ModelXap = function () {
    var $ = jQuery;
    var self = this;

    var mediaElementObject;
    var mediaSource;
    var _scriptApi;

    var playbackRates = [0.5, 0.66, 0.75, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2];
    var currentPlaybackRateIndex = 4;
    var skipBackAmount = 3000;
    var skipForwardAmount = 10000;
    var maxLiveSSReconnects = 15;
    var lastSeekTime = 0;
    var lastStartTime = null;
    var currentPosition;
    var volume = 50;
    var pauseAt = -1;
    var inactivityExceeded = false;
    var liveMediaWasReset = false;
    var playCount = 0;
    var defaultAutoStart = !Mediasite.BrowserDetect.Embedded();

    var liveStartTime;
    var liveStartOffset = 0;

    var positionUpdateTimer = null;
    var positionUpdateInterval = 200;

    var liveMediaOpenReconnector = new Mediasite.MediaPlayer.LiveMediaOpenReconnector();

    var notifier = new Mediasite.MediaPlayer.ModelEventNotifier();
    this.AddListener = function (list) {
        notifier.AddListener(list);
    };

    var isStandAlone = false;
    this.SetIsStandAlone = function (value) {
        isStandAlone = value;
    };

    function positionUpdateTimerCallback() {
        currentPosition = self.GetPosition();
        if (currentPosition >= lastSeekTime) {
            notifier.OnPositionChanged(currentPosition);
        }
        if (pauseAt > 0 && pauseAt <= currentPosition) {
            self.Pause();
            pauseAt = -1;
        }
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function startPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function stopPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
    }

    function reconnectOnLiveMediaOpenFailure() {
        resetLiveMedia();
    }

    function handleMediaError(sender, args) {
        if (args.ErrorCode === 1001 && mediaSource.toLowerCase().substr(0, 4) === 'http') {
            return; //ignore progressive download -> streaming transition
        }

        if (liveMediaOpenReconnector.HandleLiveMediaOpenFailure(reconnectOnLiveMediaOpenFailure)) {
            return; // The error is being handled by the LiveMediaOpenReconnector.
        }

        var errorDescription;
        switch (args.ErrorCode) {
            case 4001:
                errorDescription = Mediasite.Player.Localization.MediaPlayer.SilverlightNetworkError + " (" + args.ErrorMessage + ")";
                break;
            //case 2203:             
            //    errorDescription = Mediasite.Player.Localization.MediaPlayer.SilverlightSetValueError + args.methodName;             
            //    break;             
            default:
                errorDescription = Mediasite.Player.Localization.MediaPlayer.GeneralError + " (" + args.ErrorCode + " - " + args.ErrorMessage + ")";
                break;
        }
        notifier.OnMediaError(errorDescription);
    }

    function convertState(state) {
        switch (state.toLowerCase()) {
            case 'buffering':
                return Mediasite.Player.MediaState.Buffering;
            case 'closed':
                return Mediasite.Player.MediaState.Closed;
            case 'error':
                return Mediasite.Player.MediaState.Error;
            case 'opening':
                return Mediasite.Player.MediaState.Opening;
            case 'paused':
                return Mediasite.Player.MediaState.Paused;
            case 'playing':
                return Mediasite.Player.MediaState.Playing;
            case 'stopped':
                return Mediasite.Player.MediaState.Stopped;
            default:
                return Mediasite.Player.MediaState.Undefined;
        }
    }

    function logPlaybackStart() {
        if (lastStartTime === null) {
            notifier.OnMediaStarted(Mediasite.Player.MediaPlayerType.Silverlight);
            var time = self.GetPosition();
            notifier.OnMediaView({ StartTime: Math.floor(time / 1000), Duration: -1 }, false);
            lastStartTime = time;
        }
    }

    function logPlayback(synchronous) {
        if (lastStartTime !== null) {
            var start = Math.floor(lastStartTime / 1000);
            var duration = Math.floor((currentPosition - lastStartTime) / 1000);
            if (duration > 0) {
                notifier.OnMediaView({ StartTime: start, Duration: duration }, synchronous);
            }
            lastStartTime = null;
        }
    }
    function notifyMediaLoaded() {
        notifier.OnMediaLoaded({
            Duration: self.GetDuration(),
            AudioOnly: _scriptApi.NaturalVideoHeight === 0,
            CanChangeSpeed: _scriptApi.MayAdjustPlaybackRate,
            CanFullScreen: false, // Full Screen UI is handled inside Silverlight object.
            CurrentLanguageIndex: self.GetCurrentAudioLanguageIndex(),
            Languages: self.GetAudioLanguages()
        });
    }

    function handleMediaEvent(event, param) {
        switch (event) {
            case "MediaOpened":
                if (liveMediaWasReset) {
                    // if media is being opened right after a reset on live, just play it right away
                    _scriptApi.Play();
                    liveMediaWasReset = false;
                    notifyMediaLoaded();
                } else {
                    notifier.OnMediaLoadStart();
                    notifyMediaLoaded();

                    var playFrom = 0;
                    try {
                        playFrom = parseInt(Mediasite.GetQueryParam("playFrom"), 10);
                        if (isNaN(playFrom)) {
                            playFrom = 0;
                        }
                    }
                    catch (ex) {
                    }
                    if (playFrom > 0) {
                        self.SetPosition(playFrom);
                    }
                    if (Mediasite.GetQueryParam("autoStart").toLowerCase() === "false") {
                        self.Pause();
                    }
                    else {
                        if (Mediasite.GetQueryParam("autoStart").toLowerCase() === "true") {
                            self.Play();
                        }
                        else {
                            if (defaultAutoStart) {
                                self.Play();
                            }
                            else {
                                self.Pause();
                            }
                        }
                    }

                    var duration = 0;
                    try {
                        duration = parseInt(Mediasite.GetQueryParam("duration"), 10);
                    }
                    catch (ex2) {
                    }
                    if (duration > 0) {
                        pauseAt = playFrom + duration;
                    }
                }
                break;
            case "MediaEnded":
                logPlayback();
                self.Stop();
                lastSeekTime = 0;
                notifier.OnPositionChanged(0);
                notifier.OnStateChanged(Mediasite.Player.MediaState.MediaEnded);
                break;
            case "StateChanged":
                var state = convertState(_scriptApi.CurrentState);
                switch (state) {
                    case Mediasite.Player.MediaState.Playing:
                        if (liveStartTime && !_scriptApi.IsSmoothStreaming) {
                            liveStartOffset = (new Date().getTime() - liveStartTime.getTime() - 10000);
                        }
                        logPlaybackStart();
                        startPositionUpdateTimer();
                        break;
                    case Mediasite.Player.MediaState.Paused:
                    case Mediasite.Player.MediaState.Stopped:
                        logPlayback();
                        stopPositionUpdateTimer();
                        break;
                    default:
                        stopPositionUpdateTimer();
                        break;
                }
                liveMediaOpenReconnector.setMediaState(state);
                notifier.OnStateChanged(state);
                break;
            case "MarkerReached":
                if (param.Type === "MS6") {
                    var newOffset = parseInt(param.Command, 10) - _scriptApi.Position;
                    if (!isNaN(newOffset)) {
                        liveStartOffset = newOffset;
                    }
                }
                notifier.OnMediaEvent(param);
                break;
            case "RateChanged":
                var playbackRateInfo = {
                    Min: playbackRates[0],
                    Max: playbackRates[playbackRates.length - 1],
                    Current: Math.round(self.GetPlaybackRate() * 100) / 100
                };
                notifier.OnPlaybackRateChanged(playbackRateInfo);
                break;
        }
    }

    function handleSmoothStreamingTrackSummaryChanged(sender, args) {
        // TODO: Notify interested parties.
    }

    function waitForPlayerInit() {
        if (!mediaElementObject || !mediaElementObject.content || !mediaElementObject.content.ScriptApi) {
            setTimeout(waitForPlayerInit, 200);
            return;
        }

        _scriptApi = mediaElementObject.content.ScriptApi;

        _scriptApi.MediaOpened = function (sender, args) { handleMediaEvent("MediaOpened", args); };
        _scriptApi.MediaEnded = function (sender, args) { handleMediaEvent("MediaEnded", args); };
        _scriptApi.CurrentStateChanged = function (sender, args) { handleMediaEvent("StateChanged", args); };
        _scriptApi.MarkerReached = function (sender, args) {
            handleMediaEvent("MarkerReached", { Type: args.Marker.Type, Command: args.Marker.Text });
        };
        _scriptApi.MediaFailed = function (sender, args) { handleMediaError(sender, args); };
        _scriptApi.SmoothStreamingTrackSummaryChanged = handleSmoothStreamingTrackSummaryChanged;
        _scriptApi.RateChanged = function (sender, args) { handleMediaEvent("RateChanged", args); };
        // TODO: ??? _scriptApi.MediaEnded = function (sender, args) { handleMediaEvent("MediaEnded", args); };

        _scriptApi.Source = mediaSource;

        if (_scriptApi.IsSmoothStreaming) {
            liveMediaOpenReconnector.setAllowedReconnectAttempts(maxLiveSSReconnects);
        }

        self.SetVolume(volume);
    }

    function resetLiveMedia() {
        liveMediaWasReset = true;

        // Setting the Source causes the media to be reloaded, 
        // even if the source URL doesn't change.
        _scriptApi.Source = mediaSource;
    }

    function isLive() {
        return !!liveStartTime;
    }

    function parseTime(timeInMS) {
        var hh = Math.floor(timeInMS / 3600000);

        var remainingMS = timeInMS - hh * 3600000;
        var mm = Math.floor(remainingMS / 60000);

        remainingMS = remainingMS - mm * 60000;
        var ss = Math.floor(remainingMS / 1000);

        remainingMS = remainingMS = ss * 1000;
        var ff = Math.floor(remainingMS);

        return { HH: hh, MM: mm, SS: ss, FF: ff };
    }

    this.setDefaultAutoStart = function (autoStart) {
        defaultAutoStart = autoStart;
    };

    this.SetupMediaElement = function (element, sources) {
        var i;
        mediaElementObject = element.get(0);

        for (i = 0; i < sources.length; i++) {
            if (sources[i].MimeType === "video/x-ms-wmv" || sources[i].MimeType === "video/mp4" || sources[i].MimeType === "video/x-mp4-fragmented") {
                mediaSource = sources[i].Location;

                break;
            }
        }

        if (typeof (mediaSource) === 'undefined') {
            notifier.OnMediaError(Mediasite.Player.Localization.MediaPlayer.NoCompatibleMediaError);
            return;
        }

        setTimeout(waitForPlayerInit, 500);

        
        var onError = document.createElement('param');
        onError.name = "onError";
        onError.value = "OnSilverlightError";
        window.OnSilverlightError  = function () {
            if (isStandAlone && Mediasite.Urls.ContainsUnicode(document.location.href) && !Mediasite.BrowserDetect.InternetExplorer()) {
                notifier.OnMediaError(Mediasite.Player.Localization.MediaPlayer.SilverlightLoadErrorNonAsciiPath);
            }
        };
        mediaElementObject.appendChild(onError);

        $(window).unload(function () { logPlayback(true); });
    };

    this.UpdateOptions = function (options) {
        volume = options.Volume;
    };

    this.LiveStatusChange = function (status) {
        liveMediaOpenReconnector.setLiveStatus(status);
        if (status === Mediasite.Player.PresentationStatus.LiveEnded) {
            handleMediaEvent = function () { };
            handleMediaError = function () { };
            self.Stop();
            notifier.OnStateChanged(Mediasite.Player.MediaState.Stopped);
        }
    };

    this.InactivityExceeded = function () {
        inactivityExceeded = true;
    };

    this.SetPosition = function (positionInMS) {
        if (positionInMS >= 0 && positionInMS < self.GetDuration()) {
            logPlayback();
            lastSeekTime = positionInMS;
            _scriptApi.Position = positionInMS;
            notifier.OnPositionChanged(positionInMS);

            if (convertState(_scriptApi.CurrentState) === Mediasite.Player.MediaState.Playing) {
                logPlaybackStart();
            }
        }
    };

    this.PlayFaster = function () {
        if (currentPlaybackRateIndex < playbackRates.length - 1) {
            currentPlaybackRateIndex++;
            self.SetPlaybackRate(playbackRates[currentPlaybackRateIndex]);
        }
    };

    this.PlaySlower = function () {
        if (currentPlaybackRateIndex > 0) {
            currentPlaybackRateIndex--;
            self.SetPlaybackRate(playbackRates[currentPlaybackRateIndex]);
        }
    };

    this.SkipBack = function () {
        var newPosition = self.GetPosition() - skipBackAmount;

        if (newPosition < 0) {
            newPosition = 0;
        }

        self.SetPosition(newPosition);
    };

    this.SkipForward = function () {
        var newPosition = self.GetPosition() + skipForwardAmount;

        if (newPosition < self.GetDuration()) {
            self.SetPosition(newPosition);
        }
    };

    this.FullScreen = function () {
        /// <summary>This only works if the event initiating this originates from Silverlight</summary>
        // http://codeclimber.net.nz/archive/2008/02/06/Sharing-some-Silverlights-bits-Full-Screen-Mode-cannot-be-changed.aspx

        // Nuthin.
    };

    this.Pause = function () {
        _scriptApi.Pause();
    };

    this.Play = function () {
        if (inactivityExceeded === true) {
            window.location.reload();
        }
        else {
            if (isLive() && playCount > 0) {
                resetLiveMedia();
            } else {
                _scriptApi.Play();
            }
            playCount++;
        }
    };

    this.Stop = function () {
        _scriptApi.Stop();
    };

    this.GetPosition = function () {
        return _scriptApi.Position + liveStartOffset;
    };

    this.GetDuration = function () {
        // Non-Smooth Streaming live content results in a reported natural duration of 0.0, 
        // but Smooth Streaming live content results in a natural duration equal to the current position.
        // This throws off transport UI, so always report 0.0 for live content.
        return isLive() ? 0.0 : _scriptApi.NaturalDuration;
    };

    this.SetVolume = function (val) {
        _scriptApi.Volume = val / 100;
        notifier.OnVolumeChanged(Math.round(self.GetVolume()));
    };

    this.GetVolume = function () {
        return _scriptApi.Volume * 100;
    };

    this.StepVolumeUp = function () {
        if (_scriptApi.Volume <= 0.8) {
            this.SetVolume((_scriptApi.Volume + 0.2) * 100);
        }
        else {
            this.SetVolume(100);
        }
    };

    this.StepVolumeDown = function () {
        if (_scriptApi.Volume > 0.2) {
            this.SetVolume((_scriptApi.Volume - 0.2) * 100);
        }
        else {
            this.SetVolume(0);
        }
    };

    this.ToggleMute = function () {
        _scriptApi.IsMuted = !_scriptApi.IsMuted;

        if (_scriptApi.IsMuted) {
            notifier.OnVolumeChanged(0);
        }
        else {
            var volume = Math.round(self.GetVolume());

            if (volume === 0) {
                self.SetVolume(50);
            }
            else {
                notifier.OnVolumeChanged(Math.round(self.GetVolume()));
            }
        }
    };

    this.TogglePlayPause = function () {
        if (convertState(_scriptApi.CurrentState) === Mediasite.Player.MediaState.Playing) {
            if (self.GetDuration() > 0) {
                self.Pause();
            }
            else {
                self.Stop();
            }
        }
        else {
            self.Play();
        }
    };

    this.SetPlaybackRate = function (speed) {
        _scriptApi.PlaybackRate = speed;
    };

    this.GetPlaybackRate = function () {
        return _scriptApi.PlaybackRate;
    };

    this.GetCurrentAudioLanguageIndex = function () {
        return 1; // Why is this hardcoded to one (not zero)?
    };

    this.GetAudioLanguages = function () {
        var i, languageCount = _scriptApi.AudioStreamCount;
        if (languageCount > 1) {
            var languages = [];
            for (i = 0; i < languageCount; ++i) {
                //Silverlight currently does not provide locale info
                languages[i] = { Index: i, Locale: 0, DisplayName: Mediasite.Player.Localization.Common.AudioTrack + ' ' + (i + 1) };
            }
            return languages;
        }
        return {};
    };

    this.SetAudioLanguage = function (language) {
        _scriptApi.AudioStreamIndex = language.Index;
    };

    this.SetLiveStartTime = function (time) {
        liveStartTime = time;
    };
};

Mediasite.MediaPlayer.ModelWindowsMedia = function (options) {
    /// <summary>Media player model for HTML5 media element</summary>
    /// <param name="options" type="object">{ mediaPlayerType: Mediasite.Player.MediaPlayerType, playBackRates: (array) }</param>
    var $ = jQuery;
    var self = this;

    var mediaElement;
    var mediaSource;

    var defaultPlaybackRate = 1;
    var playbackRates = [0.5, 1, 1.5, 2];
    var skipBackAmount = 3000;
    var skipForwardAmount = 10000;
    var lastSeekTime = 0;
    var lastStartTime = null;
    var currentPosition;
    var manualSeekFlag = false;
    var inactivityExceeded = false;
    var volume = 50;
    var pauseAt = -1;
    var liveStartTime;
    var liveStartOffset = 0;
    var defaultAutoStart = !Mediasite.BrowserDetect.Embedded();
    var liveMediaOpenReconnector = new Mediasite.MediaPlayer.LiveMediaOpenReconnector();

    var positionUpdateTimer = null;
    var positionUpdateInterval = 200;

    // TODO: Use one timer for rate and position updates.
    var _rateCheckTimer = null;
    var _rateCheckInterval = 100; // milliseconds

    var _playbackRate = 1.0;
    var _lastStopMediaEventTime = 0; // milliseconds
    var _mediaStopRateCorrectionWindow = 300; // milliseconds

    var notifier = new Mediasite.MediaPlayer.ModelEventNotifier();
    this.AddListener = function (list) {
        notifier.AddListener(list);
    };

    // Parse options
    var mediaPlayerType;
    if (typeof (options) !== "undefined") {
        if ($.isPlainObject(options)) {
            // Playback rates
            if ($.isArray(options.playbackRates)) {
                playbackRates = options.playbackRates;
                playbackRates.sort(function (a, b) { return a - b; }) // sort numerically
            }

            mediaPlayerType = options.mediaPlayerType;
        } else {
            mediaPlayerType = options;
        }
    }

    _rateCheckTimer = setInterval(rateCheckTimer_tick, _rateCheckInterval);

    function rateCheckTimer_tick() {
        if (mediaElement && mediaElement.settings && mediaElement.settings.rate) {
            if (mediaElement.settings.rate != _playbackRate) {

                // Check to see if IE/WMP is automatically set the rate to 1.0
                // on media stop. If so, try to set it back to the desired rate.
                var now = new Date().getTime();
                if (now < _lastStopMediaEventTime + _mediaStopRateCorrectionWindow) {
                    mediaElement.settings.rate = _playbackRate;
                    return;
                }


                _playbackRate = mediaElement.settings.rate;
                raisePlaybackRateChanged(_playbackRate);
            }
        }
    }

    function raisePlaybackRateChanged(rate) {
        var playbackRateInfo = {
            Min: playbackRates[0],
            Max: playbackRates[playbackRates.length - 1],
            Current: Math.round(rate * 100) / 100,
            Rates: $.extend([], playbackRates)
        };
        notifier.OnPlaybackRateChanged(playbackRateInfo);
    }


    function getNextPlaybackRate(faster, loop) {
        /// <summary>Select the playback rate which is after (faster or slower) than the current playback rate</summary>
        /// <param name="faster" type="bool">True for faster playrate (default), false for a slower playrate</param>
        /// <param name="loop" type="bool">True for loop-around (speed > max ==> min and vice versa) (default), false for stopping at the min/max</param>

        // Sanitize parameters
        faster = faster !== false; // cast to boolean, undefined = true
        loop = loop !== false;  // cast to boolean, undefined = true

        // Get current playback rate
        var speed = Math.round(self.GetPlaybackRate() * 100) / 100;

        // Fetch and sanitize available playback rates (non-destructively)
        var toggleSteps = $.extend([], playbackRates);
        if (toggleSteps.length == 0) {
            toggleSteps = [1];
        }

        // If selecting a slower speed, scan the list in reverse.  (Mentally flip "min" and "max")
        var sortCoefficient = (faster ? 1 : -1);
        toggleSteps.sort(function (a, b) { return sortCoefficient * (a - b); });

        // Select the next playback rate
        if (loop && 0 >= sortCoefficient * (toggleSteps[toggleSteps.length - 1] - speed)) { // speed > max, accounting for faster/slower
            // Speed > max, so loop around to min 
            speed = toggleSteps[0];
        } else {
            // Select the speed faster/slower than the current speed
            $.each(toggleSteps, function (i, thisToggleStep) {
                if (0 >= sortCoefficient * (thisToggleStep - speed) // speed >= thisToggleSpeed, accounting for faster/slower
                    && 0 < sortCoefficient * (toggleSteps[i + 1] - speed)) { // speed < toggleSteps[i + 1], same
                    speed = toggleSteps[i + 1];
                    return false;
                }
            });
        }

        return speed;
    };

    function positionUpdateTimerCallback() {
        if (!manualSeekFlag) {
            currentPosition = self.GetPosition();
            if (currentPosition >= lastSeekTime) {
                notifier.OnPositionChanged(currentPosition);
            }
            if (pauseAt > 0 && pauseAt <= currentPosition) {
                self.Pause();
                pauseAt = -1;
            }
        }
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function startPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function stopPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
    }

    function reconnectOnLiveMediaOpenFailure() {
        mediaElement.controls.play();
    }

    function handleMediaError() {
        if (liveMediaOpenReconnector.HandleLiveMediaOpenFailure(reconnectOnLiveMediaOpenFailure)) {
            return; // The error is being handled by the LiveMediaOpenReconnector.
        }

        var errorDescription;
        switch (mediaElement.currentMedia.error.errorCode) {
            case -1072885294: //C00D11D2
            case -1072885353: //C00D1197			
                errorDescription = Mediasite.Player.Localization.MediaPlayer.FileNotFoundError;
                break;
            case -1072885352: //C00D1198
                errorDescription = Mediasite.Player.Localization.MediaPlayer.ServerConnectionError;
                break;
            case -1072885328: //C00D11B0 
                errorDescription = Mediasite.Player.Localization.MediaPlayer.ServerNotAvailable;
                break;
            default:
                errorDescription = mediaElement.currentMedia.error.errorDescription + " (" + mediaElement.currentMedia.error.errorCode + ")";
                break;
        }
        notifier.OnMediaError(errorDescription);
    }

    function UseDefaultAudioLanguage() {
        mediaElement.controls.currentAudioLanguageIndex = mediaElement.controls.audioLanguageCount;
    }

    function logPlaybackStart() {
        if (lastStartTime === null) {
            notifier.OnMediaStarted(mediaPlayerType);
            var time = self.GetPosition();
            notifier.OnMediaView({ StartTime: Math.floor(time / 1000), Duration: -1 }, false);
            lastStartTime = time;
            if (mediaElement.controls.audioLanguageCount > 1) {
                UseDefaultAudioLanguage();
            }
        }
    }

    function logPlayback(synchronous) {
        if (lastStartTime !== null) {
            var start = Math.floor(lastStartTime / 1000);
            var duration = Math.floor((currentPosition - lastStartTime) / 1000);
            if (duration > 0) {
                notifier.OnMediaView({ StartTime: start, Duration: duration }, synchronous);
            }
            lastStartTime = null;
        }
    }

    function handleMediaEvent(event, param) {
        switch (event) {
            case "OpenStateChange":
                if (param === 12) {
                    notifier.OnMediaLoadStart();
                } else if (param === 13) {
                    notifier.OnMediaLoaded({
                        Duration: self.GetDuration(),
                        AudioOnly: (mediaElement.currentMedia.getItemInfo("MediaType") === "audio"),
                        CanChangeSpeed: true,
                        CanFullScreen: true,
                        CurrentLanguageIndex: self.GetCurrentAudioLanguageIndex(),
                        Languages: self.GetAudioLanguages()
                    });

                    var playFrom = 0;
                    try {
                        playFrom = parseInt(Mediasite.GetQueryParam("playFrom"), 10);
                        if (isNaN(playFrom)) {
                            playFrom = 0;
                        }
                    }
                    catch (ex) {
                    }
                    if (playFrom > 0) {
                        self.SetPosition(playFrom);
                    }
                    if (Mediasite.GetQueryParam("autoStart").toLowerCase() === "false") {
                        self.Pause();
                    }
                    else {
                        if (Mediasite.GetQueryParam("autoStart").toLowerCase() === "true") {
                            self.Play();
                        }
                        else {
                            if (defaultAutoStart) {
                                self.Play();
                            }
                            else {
                                self.Pause();
                            }
                        }
                    }
                    var duration = 0;
                    try {
                        duration = parseInt(Mediasite.GetQueryParam("duration"), 10);
                    }
                    catch (ex2) {
                    }
                    if (duration > 0) {
                        pauseAt = playFrom + duration;
                    }
                }
                break;
            case "PlayStateChange":
                switch (param) {
                    case Mediasite.Player.MediaState.Playing:
                        if (self.GetPosition() == 0) self.SetPosition(1);
                        if (liveStartTime) {
                            liveStartOffset = (new Date().getTime() - liveStartTime.getTime() - 10000);
                        }
                        logPlaybackStart();
                        startPositionUpdateTimer();
                        break;
                    case Mediasite.Player.MediaState.Stopped:
                    case Mediasite.Player.MediaState.MediaEnded:
                        stopPositionUpdateTimer();
                        logPlayback();
                        lastSeekTime = 0;
                        _lastStopMediaEventTime = new Date().getTime();
                        notifier.OnPositionChanged(0);
                        break;
                    case Mediasite.Player.MediaState.Paused:
                        stopPositionUpdateTimer();
                        logPlayback();
                        break;
                    default:
                        stopPositionUpdateTimer();
                        break;
                }
                liveMediaOpenReconnector.setMediaState(param);
                notifier.OnStateChanged(param);
                break;
            case "PositionChange":
                logPlayback();
                if (manualSeekFlag) {
                    manualSeekFlag = false;
                }
                else {
                    lastSeekTime = self.GetPosition();
                }
                if (mediaElement.playState === Mediasite.Player.MediaState.Playing) {
                    //seek can occur without a state change in WMP
                    logPlaybackStart();
                }
                break;
            case "ScriptEvent":
                if (param.Type === "MS6") {
                    var newOffset = parseInt(param.Command, 10) - mediaElement.controls.currentPosition * 1000;
                    if (!isNaN(newOffset)) {
                        liveStartOffset = newOffset;
                    }
                }
                notifier.OnMediaEvent(param);
                break;
        }
    }

    this.setDefaultAutoStart = function (autoStart) {
        defaultAutoStart = autoStart;
    };

    function waitForPlayerInit() {

        if (!mediaElement.settings) {
            setTimeout(waitForPlayerInit, 200);
            return;
        }

        mediaElement.settings.invokeURLs = false;

        if (mediaPlayerType === Mediasite.Player.MediaPlayerType.Port25) {
            // Port25 Global Events
            OnDSPlayStateChangeEvt = function (newState) {
                handleMediaEvent("PlayStateChange", newState);
            };
            OnDSPositionChangeEvt = function (newPosition, oldPosition) {
                handleMediaEvent("PositionChange", { newPosition: newPosition, oldPosition: oldPosition });
            };
            OnDSOpenStateChangeEvt = function (newState) {
                handleMediaEvent("OpenStateChange", newState);
            };
            OnDSScriptCommandEvt = function (scriptType, scriptParam) {
                handleMediaEvent("ScriptEvent", { Type: scriptType, Command: scriptParam });
            };
            OnDSMediaErrorEvt = function () {
                handleMediaError();
            };
        }
        else if (mediaPlayerType === Mediasite.Player.MediaPlayerType.WindowsMedia && document.attachEvent) {
            // ActiveX in IE
            mediaElement.attachEvent("OpenStateChange", function (state) { handleMediaEvent("OpenStateChange", state); });
            mediaElement.attachEvent("PlayStateChange", function (state) { handleMediaEvent("PlayStateChange", state); });
            mediaElement.attachEvent("PositionChange", function (position) { handleMediaEvent("PositionChange", position); });
            mediaElement.attachEvent("ScriptCommand", function (scriptType, scriptParam) { handleMediaEvent("ScriptEvent", { Type: scriptType, Command: scriptParam }); });
            mediaElement.attachEvent("MediaError", function () { handleMediaError(); });
        }

        mediaElement.URL = mediaSource;

        self.SetVolume(volume);

        if (volume === 0) {
            mediaElement.settings.mute = true;
        }
    }

    this.SetupMediaElement = function (element, sources) {
        var i;
        mediaElement = element.get(0);

        for (i = 0; i < sources.length; i++) {
            if (sources[i].MimeType === "video/x-ms-wmv") {
                mediaSource = sources[i].Location;

                break;
            }
        }

        if (typeof (mediaSource) === 'undefined') {
            notifier.OnMediaError(Mediasite.Player.Localization.MediaPlayer.NoCompatibleMediaError);
            return;
        }

        setTimeout(waitForPlayerInit, 200);

        $(window).unload(function () { logPlayback(true); });
    };

    this.UpdateOptions = function (options) {
        volume = options.Volume;
    };

    this.LiveStatusChange = function (status) {
        liveMediaOpenReconnector.setLiveStatus(status);
        if (status === Mediasite.Player.PresentationStatus.LiveEnded) {
            self.Stop();
            handleMediaEvent = function () { };
            handleMediaError = function () { };
        }
    };

    this.InactivityExceeded = function () {
        inactivityExceeded = true;
    };

    this.SetPosition = function (positionInMS) {
        if (positionInMS >= 0 && positionInMS < self.GetDuration()) {
            manualSeekFlag = true;
            lastSeekTime = positionInMS;
            mediaElement.controls.currentPosition = positionInMS / 1000;
            notifier.OnPositionChanged(positionInMS);
        }
    };

    this.PlayDefaultRate = function () {
        var playbackRate = defaultPlaybackRate;
        self.SetPlaybackRate(playbackRate);
    };

    this.PlayFaster = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(true, doLoop);
        self.SetPlaybackRate(playbackRate);
    };

    this.PlaySlower = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(false, doLoop);
        self.SetPlaybackRate(playbackRate);
    };
    this.SkipBack = function () {
        var newPosition = self.GetPosition() - skipBackAmount;

        if (newPosition < 0) {
            newPosition = 0;
        }

        self.SetPosition(newPosition);
    };

    this.SkipForward = function () {
        var newPosition = self.GetPosition() + skipForwardAmount;

        if (newPosition < self.GetDuration()) {
            self.SetPosition(newPosition);
        }
    };

    this.FullScreen = function () {
        mediaElement.fullScreen = true;
    };

    this.Pause = function () {
        mediaElement.controls.pause();
    };

    this.Play = function () {
        if (inactivityExceeded === true) {
            window.location.reload();
        }
        else {
            mediaElement.controls.play();

            if (mediaElement.settings.rate != _playbackRate) {
                // IE9 sets the rate to 1.0 whenever play() is called, 
                // but the correct behavior for our player is to maintain 
                // playback rate. Silly IE!
                mediaElement.settings.rate = _playbackRate;
            }
        }
    };

    this.Stop = function () {
        mediaElement.controls.stop();
    };

    this.GetPosition = function () {
        return mediaElement.controls.currentPosition * 1000 + liveStartOffset;
    };

    this.GetDuration = function () {
        return mediaElement.currentMedia.duration * 1000;
    };

    this.SetVolume = function (val) {
        mediaElement.settings.volume = Math.round(val);
        notifier.OnVolumeChanged(Math.round(self.GetVolume()));
    };

    this.GetVolume = function () {
        return mediaElement.settings.volume;
    };

    this.StepVolumeUp = function () {
        if (mediaElement.settings.volume <= 80) {
            this.SetVolume(this.GetVolume() + 20);
        }
        else {
            this.SetVolume(100);
        }
    };

    this.StepVolumeDown = function () {
        if (mediaElement.settings.volume > 0.2) {
            this.SetVolume(this.GetVolume() - 20);
        }
        else {
            this.SetVolume(0);
        }
    };

    this.ToggleMute = function () {
        mediaElement.settings.mute = !mediaElement.settings.mute;

        if (mediaElement.settings.mute) {
            notifier.OnVolumeChanged(0);
        }
        else {
            var volume = Math.round(self.GetVolume());

            if (volume === 0) {
                self.SetVolume(50);
            }
            else {
                notifier.OnVolumeChanged(Math.round(self.GetVolume()));
            }
        }

    };

    this.TogglePlayPause = function () {
        if (mediaElement.playState === Mediasite.Player.MediaState.Playing) {
            if (self.GetDuration() > 0) {
                self.Pause();
            }
            else {
                self.Stop();
            }
        }
        else {
            self.Play();
        }
    };

    this.SetPlaybackRate = function (speed) {
        mediaElement.settings.rate = speed;
    };

    this.GetPlaybackRate = function () {
        return mediaElement.settings.rate;
    };

    this.GetCurrentAudioLanguageIndex = function () {
        try {
            return mediaElement.controls.currentAudioLanguageIndex;
        }
        catch (ex) {
            return 0;
        }

    };

    this.GetAudioLanguages = function () {
        var i, languageCount = 1;
        try {
            languageCount = mediaElement.controls.audioLanguageCount;
        }
        catch (ex) {
        }

        if (languageCount > 1) {
            var languages = [];
            for (i = 0; i < languageCount; ++i) {
                var lcid = mediaElement.controls.getAudioLanguageID(i + 1);
                languages[i] = { Index: i + 1, Locale: lcid, DisplayName: mediaElement.controls.getLanguageName(lcid) };
            }
            return languages;
        }
        return {};
    };

    this.SetAudioLanguage = function (language) {
        try {
            mediaElement.controls.currentAudioLanguageIndex = language.Index;
        } catch (ex) { }

    };

    this.SetLiveStartTime = function (time) {
        liveStartTime = time;
    };
};


Mediasite.MediaPlayer.ModelFlash = function () {
    var $ = jQuery;
    var self = this;

    var mediaElement;
    var mediaSource;

    var skipBackAmount = 3000;
    var skipForwardAmount = 10000;
    var lastStartTime = null;
    var currentPosition = null;
    var inactivityExceeded = false;
    var volume = 50;
    var pauseAt = -1;
    var videoWidth;
    var defaultAutoStart = !Mediasite.BrowserDetect.Embedded();

    var notifier = new Mediasite.MediaPlayer.ModelEventNotifier();
    this.AddListener = function (list) {
        notifier.AddListener(list);
    };

    function logPlaybackStart() {
        if (lastStartTime === null) {
            notifier.OnMediaStarted(Mediasite.Player.MediaPlayerType.Flash);
            var time = self.GetPosition();
            notifier.OnMediaView({ StartTime: Math.floor(time / 1000), Duration: -1 }, false);
            lastStartTime = time;
        }
    }

    function logPlayback(synchronous) {
        if (lastStartTime !== null) {
            var start = Math.floor(lastStartTime / 1000);
            var duration = Math.floor((currentPosition - lastStartTime) / 1000);
            if (duration > 0) {
                notifier.OnMediaView({ StartTime: start, Duration: duration }, synchronous);
            }
            lastStartTime = null;
        }
    }

    function handleMediaEvent(event, args) {
        switch (event) {
            case "stateChange":
                switch (args) {
                    case "playing":
                        notifier.OnStateChanged(Mediasite.Player.MediaState.Playing);
                        break;
                    case "stopped":
                    case "ready":
                        logPlayback();
                        notifier.OnStateChanged(Mediasite.Player.MediaState.Stopped);
                        break;
                    case "seeking":
                        logPlayback();
                        break;
                    case "paused":
                        logPlayback();
                        notifier.OnStateChanged(Mediasite.Player.MediaState.Paused);
                        break;
                    case "loading":
                        notifier.OnMediaLoadStart();
                        notifier.OnStateChanged(Mediasite.Player.MediaState.Opening);
                        break;
                    case "buffering":
                        notifier.OnStateChanged(Mediasite.Player.MediaState.Buffering);
                        break;
                    case "playbackError":
                        notifier.OnStateChanged(Mediasite.Player.MediaState.Error);
                        break;
                    default:
                        break;
                }
                break;
            case "positionChange":
                currentPosition = args * 1000;
                if (mediaElement.isPlaying()) {
                    logPlaybackStart();
                    notifier.OnStateChanged(Mediasite.Player.MediaState.Playing);
                }
                if (pauseAt > 0 && pauseAt <= currentPosition) {
                    self.Pause();
                    pauseAt = -1;
                }
                notifier.OnPositionChanged(currentPosition);
                break;
            case "durationChange":
                break;
            case "mediaSizeChange":
                videoWidth = args.width;
                notifier.OnMediaLoadStart();
                notifier.OnMediaLoaded({
                    Duration: self.GetDuration(),
                    AudioOnly: (videoWidth === 0),
                    CanChangeSpeed: false,
                    CanFullScreen: false,
                    CurrentLanguageIndex: 0,
                    Languages: {}
                });


                var playFrom = 0;
                try {
                    playFrom = parseInt(Mediasite.GetQueryParam("playFrom"), 10);
                    if (isNaN(playFrom)) {
                        playFrom = 0;
                    }
                }
                catch (ex) {
                }
                if (playFrom > 0) {
                    self.SetPosition(playFrom);
                }
                if (Mediasite.GetQueryParam("autoStart").toLowerCase() === "false") {
                    self.Pause();
                }
                var duration = 0;
                try {
                    duration = parseInt(Mediasite.GetQueryParam("duration"), 10);
                }
                catch (ex2) {
                }
                if (duration > 0) {
                    pauseAt = playFrom + duration;
                }
                break;
            case "cuePoint":
                //notifier.OnMediaEvent(args);
                break;
            case "mediaError":
                notifier.OnMediaError(args);
                break;
        }
    }

    function waitForPlayerInit() {

        if (!mediaElement.hasOwnProperty('setSource')) {
            setTimeout(waitForPlayerInit, 200);
            return;
        }

        mediaElement.setSource(mediaSource);
        self.SetVolume(volume);
    }

    this.setDefaultAutoStart = function (autoStart) {
        defaultAutoStart = autoStart;
    };

    this.SetupMediaElement = function (element, sources) {
        var i;
        mediaElement = element.get(0);

        for (i = 0; i < sources.length; i++) {
            if (sources[i].MimeType === "video/mp4") {
                mediaSource = sources[i].Location;

                break;
            }
        }

        if (typeof (mediaSource) === 'undefined') {
            notifier.OnMediaError(Mediasite.Player.Localization.MediaPlayer.NoCompatibleMediaError);
            return;
        }

        //Global Flash callback object
        OnFlashMediaPlayerEvent = function (eventType, eventArg) {
            handleMediaEvent(eventType, eventArg);
        };

        setTimeout(waitForPlayerInit, 200);

        $(window).unload(function () { logPlayback(true); });
    };

    this.UpdateOptions = function (options) {
        volume = options.Volume;
    };

    this.LiveStatusChange = function (status) {
    };

    this.InactivityExceeded = function () {
        inactivityExceeded = true;
    };

    this.SetPosition = function (positionInMS) {
        if (positionInMS >= 0 && positionInMS < self.GetDuration()) {
            mediaElement.setPosition(positionInMS / 1000);
        }
    };

    this.PlayFaster = function () {
    };

    this.PlaySlower = function () {
    };

    this.SkipBack = function () {
        var newPosition = currentPosition - skipBackAmount;

        if (newPosition < 0) {
            newPosition = 0;
        }

        self.SetPosition(newPosition);
    };

    this.SkipForward = function () {
        var newPosition = currentPosition + skipForwardAmount;

        if (newPosition < self.GetDuration()) {
            self.SetPosition(newPosition);
        }
    };

    this.FullScreen = function () {
    };

    this.SetAudioLanguage = function (language) {
    };

    this.Pause = function () {
        mediaElement.pause();
    };

    this.Play = function () {
        if (inactivityExceeded === true) {
            window.location.reload();
        }
        else {
            mediaElement.play();
        }
    };

    this.Stop = function () {
        mediaElement.stop();
    };

    this.GetPosition = function () {
        return currentPosition;
    };

    this.GetDuration = function () {
        return mediaElement.getDuration() * 1000;
    };

    this.SetVolume = function (val) {
        mediaElement.setVolume(val / 100);
        notifier.OnVolumeChanged(val);
    };

    this.GetVolume = function () {
        return mediaElement.getVolume() * 100;
    };

    this.StepVolumeUp = function () {
        if (mediaElement.volume <= 0.8) {
            this.SetVolume((mediaElement.volume + 0.2) * 100);
        }
        else {
            this.SetVolume(100);
        }
    };

    this.StepVolumeDown = function () {
        if (mediaElement.volume > 0.2) {
            this.SetVolume((mediaElement.volume - 0.2) * 100);
        }
        else {
            this.SetVolume(0);
        }
    };

    this.ToggleMute = function () {
        mediaElement.toggleMute();
        var vol;
        if (mediaElement.isMuted()) {
            vol = 0;
        }
        else {
            vol = this.GetVolume();
        }
        notifier.OnVolumeChanged(vol);
    };

    this.TogglePlayPause = function () {
        if (mediaElement.isPlaying()) {
            if (self.GetDuration() > 0) {
                self.Pause();
            }
            else {
                self.Stop();
            }
        }
        else {
            self.Play();
        }
    };

    this.SetPlaybackRate = function (speed) {
    };

    this.GetPlaybackRate = function () {
    };

    this.GetVideoWidth = function () {
        return videoWidth;
    };

    this.FullScreen = function () {
    };
};



Mediasite.MediaPlayer.LiveMediaOpenReconnector = function () {
    var allowedReconnectAttempts = 5;
    var reconnectDelay = 2 * 1000;
    var reconnectAttempts = 0;
    var willAttemptReconnect = false;
    var liveStatus;
    var hasSeenPlayingState = false;


    this.setAllowedReconnectAttempts = function (num) {
        allowedReconnectAttempts = num;
    }

    this.setLiveStatus = function (status) {
        liveStatus = status;
    };

    this.setMediaState = function (state) {
        hasSeenPlayingState = hasSeenPlayingState || (state == Mediasite.Player.MediaState.Playing);
    };

    this.HandleLiveMediaOpenFailure = function (reconnectDelegate) {
        if (hasSeenPlayingState || liveStatus != Mediasite.Player.PresentationStatus.Live) {
            // This does not appear to be an error during initial live media open.
            return false;
        }

        if (willAttemptReconnect) {
            // We've already got a reconnect scheduled.
            // Notify caller that this is already being handled.
            return true;
        }

        // Consider the media error as unrecoverable, if we've exceeded the allowed reconnection attempts.
        if (reconnectAttempts >= allowedReconnectAttempts) {
            Mediasite.Debug.Log('The live media reconnection attempts has exceeded the allowed maximum of ' + allowedReconnectAttempts);

            reconnectAttempts = 0;
            return false; // Notify caller that we're not going to handle this.
        }

        // Schedule a reconnection attempt for a short time in the future.
        window.setTimeout(function () {
            Mediasite.Debug.Log('Attempting live media reconnection ' + reconnectAttempts);
            reconnectDelegate();
            willAttemptReconnect = false;
        }, reconnectDelay);


        reconnectAttempts++;
        willAttemptReconnect = true;
        return true; // Notify caller that we're handling everything.
    };
}
