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

Mediasite.namespace("MediaPlayer");
Mediasite.namespace("MediaPlayer.Views");

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

    var element;
    var mediaElementObject;
    var sourcesOnDeck = [];
    var mediaSource, mediaServerType, mediaMimeType;

    var playbackRates = viewOptions.playbackRates;
    var skipBackAmount = 3000;
    var skipForwardAmount = 10000;
    var maxLiveSSReconnects = 15;
    var wmsTimeCorrectionThreshold = 500; // milliseconds
    var lastSeekTime = 0;
    var _manualSeekThreshold = -500;
    var ssSegmentLength = 2 * 1000;
    var volume = 100;
    var inactivityExceeded = false;
    var liveMediaWasReset = false;
    var playCount = 0;
    var wasMediaOpened = false;
    var isLively = false;
    var isStandAlone = viewOptions.isStandAlone;

    var liveStartTime;
    var liveStartOffset = 0;
    var liveStartOffsetPlayingOffset = 15000; // Approximate value, based on lab tests

    var positionUpdateTimer = null;
    var positionUpdateInterval = 200;

    var logger = Mediasite.Logging.LoggerFactory("Media XAP" + ":" + viewOptions.streamType, { separator: "\n" });

    var liveMediaOpenReconnector = new Mediasite.MediaPlayer.LiveMediaOpenReconnector();
    var mediaPlayerView;
    var mediaWrapper;
    var currentPlaybackRate = 1.0;

    var unloadingPage = false;
    $(window).on('beforeunload', nowUnloading);
    function nowUnloading(e) {
        unloadingPage = true;
    }

    var clientBookmark = { position: 0, savedAt: 0 };
    var serverBookmark = { position: 0, savedAt: 0 };

    var _events = new Mediasite.Player.EventBundle();
    var _eventNames = Mediasite.MediaPlayer.ModelEvent;

    var disableVolume = viewOptions.disableVolume == undefined ? false : viewOptions.disableVolume;

    this.GetMediaWrapper = function () {
        return mediaWrapper;
    };

    this.SupportsPlayAsVideo = function () {
        return false;
    };

    this.AddEventHandler = function (eventName, handler) {
        _events.addHandler(eventName, handler);
    };

    function positionUpdateTimerCallback() {
        var currentPosition = self.GetPosition();

        /*
        Mediasite.Debug.Repo.Push("timeupdate", {
        currentPosition: currentPosition,
        playbackRate: currentPlaybackRate,
        streamType: viewOptions.streamType,
        timestamp: Date.now()
        });
        */

        _events.fire(_eventNames.PositionChanged, currentPosition, true);
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

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

    function stopPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
    }

    function reconnectOnLiveMediaOpenFailure() {
        resetLiveMedia();
    }

    function handleMediaError(sender, args) {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        var errorCode = args.ErrorCode || parseInt(args.ErrorMessage);

        if (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.
        }

        if (!wasMediaOpened && !isLively && sourcesOnDeck.length > 0) {
            // we have not successfully opened media yet, try the next video source available.
            cueNextSource();
            _scriptApi.Source = mediaSource;
            logger.Info("Media element failover source set", mediaSource, "of type", mediaMimeType, "on server type", mediaServerType);

            return;
        }

        var errorDescription;
        switch (errorCode) {
            case 4001:
                errorDescription = Mediasite.Player.Localization.MediaPlayer.SilverlightNetworkError + " (" + args.ErrorMessage + ")";
                break;
            case 3137:
                if (unloadingPage) {
                    errorDescription = Mediasite.Player.Localization.MediaPlayer.ServerConnectionClosedError + " (" + args.ErrorCode + " - " + args.ErrorMessage + ")";
                    break;
                }
            default:
                errorDescription = Mediasite.Player.Localization.MediaPlayer.GeneralError + " (" + args.ErrorCode + " - " + args.ErrorMessage + ")";
                break;
        }
        _events.fire(_eventNames.MediaError, 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 notifyMediaLoaded() {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        var canChangeSpeed = false;
        if (!isLively) {
            switch (mediaMimeType) {
                case "video/x-ms-wmv":
                    canChangeSpeed = (mediaServerType == "IIS") || isStandAlone;
                    break;
                case "video/mp4":
                    canChangeSpeed = true;
                    break;
                case "video/x-mp4-fragmented":
                default:
                    canChangeSpeed = false;
                    break;
            }
        }

        var aspectRatio = 0;
        if (_scriptApi.NaturalVideoHeight > 0 && _scriptApi.NaturalVideoWidth > 0) {
            aspectRatio = _scriptApi.NaturalVideoWidth / _scriptApi.NaturalVideoHeight;
        }

        var mediaInfo = {
            AspectRatio: aspectRatio,
            Duration: self.GetDuration(),
            AudioOnly: _scriptApi.NaturalVideoHeight === 0,
            CanChangeSpeed: canChangeSpeed,
            CanFullScreen: false, // Full Screen UI is handled inside Silverlight object.
            CurrentLanguageIndex: self.GetCurrentAudioLanguageIndex(),
            Languages: self.GetAudioLanguages(),
            StreamType: viewOptions.streamType,
            IsMainStream: viewOptions.isMainStream
        };

        mediaPlayerView.UpdateMediaInfo(mediaInfo);
        _events.fire(_eventNames.MediaLoaded, mediaInfo);
    }

    function handleMediaEvent(event, param) {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        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 {
                    wasMediaOpened = true;
                    mediaPlayerView.MediaLoadStart();
                    _events.fire(_eventNames.MediaLoadStart);
                    notifyMediaLoaded();
                }
                break;
            case "MediaEnded":
                _events.fire(_eventNames.PositionChanged, self.GetPosition());
                self.Stop();
                lastSeekTime = 0;
                _events.fire(_eventNames.PositionChanged, 0);
                _events.fire(_eventNames.StateChanged, Mediasite.Player.MediaState.MediaEnded);
                break;
            case "StateChanged":
                _events.fire(_eventNames.PositionChanged, self.GetPosition());
                var state = convertState(_scriptApi.CurrentState);
                switch (state) {
                    case Mediasite.Player.MediaState.Playing:
                        self.SetPlaybackRate(currentPlaybackRate);
                        if (liveStartTime && !_scriptApi.IsSmoothStreaming) {
                            if (_scriptApi.Position < 1000) {
                                // This operation expects that _scriptApi.Position == 0, which happens on _scriptApi.Stop()
                                // However, during buffering/playing hiccups, _scriptApi.Position does not reset
                                liveStartOffset = new Date() - liveStartTime - liveStartOffsetPlayingOffset;
                            }
                        }
                        startPositionUpdateTimer();
                        break;
                    case Mediasite.Player.MediaState.Paused:
                    case Mediasite.Player.MediaState.Stopped:
                        stopPositionUpdateTimer();
                        break;
                    default:
                        stopPositionUpdateTimer();
                        break;
                }
                liveMediaOpenReconnector.setMediaState(state);
                _events.fire(_eventNames.StateChanged, state);
                break;
            case "MarkerReached":
                if (param.Type === "MS6") {
                    var actualMediaPosition = parseInt(param.Command, 10)
                    var newOffset, offsetDiff;
                    if (!isNaN(actualMediaPosition)) {
                        liveStartOffset = liveStartOffset || 0;
                        newOffset = actualMediaPosition - _scriptApi.Position;
                        offsetDiff = Math.abs(newOffset - liveStartOffset);
                        if (offsetDiff >= wmsTimeCorrectionThreshold) {
                            logger.Debug('Correcting WMS Live Offset -- old: ' + liveStartOffset + ', new: ' + newOffset);
                            liveStartOffset = newOffset;
                        }
                    }
                }
                _events.fire(_eventNames.MediaEvent, param);
                break;
            case "RateChanged":
                var playbackRateInfo = {
                    Min: playbackRates[0],
                    Max: playbackRates[playbackRates.length - 1],
                    Current: Math.round(self.GetPlaybackRate() * 100) / 100
                };

                logger.Debug("Playback rate now " + playbackRateInfo.Current);
                _events.fire(_eventNames.PlaybackRateChanged, playbackRateInfo);
                break;
        }
    }

    function handleSmoothStreamingTrackSummaryChanged(sender, args) {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;


        var trackSummary = _scriptApi.SmoothStreamingTrackSummary;

        // Deserialize track metadata
        var trackBitrates = trackSummary.TrackBitrates.split(",");
        var trackWidths = trackSummary.TrackWidths.split(",");
        var trackHeights = trackSummary.TrackHeights.split(",");

        var tracks = [];
        for (var i = 0, len = trackSummary.TrackCount; i < len; i++) {
            tracks[i] = {
                bitrate: parseInt(trackBitrates[i], 10),
                width: parseInt(trackWidths[i], 10),
                height: parseInt(trackHeights[i], 10)
            };
        }

        var fidelityInfo = {
            tracks: tracks,
            currentTrackIndex: trackSummary.CurrentPlayTrackIndex,
            streamType: viewOptions.streamType
        };
        fidelityInfo.currentTrack = fidelityInfo.tracks[fidelityInfo.currentTrackIndex];

        _events.fire(_eventNames.MediaFidelityChanged, fidelityInfo);

    }

    function getScriptApi() {
        try {
            if (mediaElementObject && mediaElementObject.content && mediaElementObject.content.ScriptApi) {
                return mediaElementObject.content.ScriptApi;
            }
        } catch (err) { }

        return false;
    }

    function waitForPlayerInit() {
        var _scriptApi = getScriptApi();
        if (!_scriptApi || mediaElementObject.usable === false) {
            setTimeout(waitForPlayerInit, 200);
            return;
        }

        _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;
        logger.Info("Media element source set", mediaSource, "of type", mediaMimeType, "on server type", mediaServerType);

        if (!_scriptApi.IsSmoothStreaming) {
            liveMediaOpenReconnector.setLiveStartGraceSeconds(60); // Live WMS is non-adaptive media and requires less time.
        }

        self.SetVolume(volume);
    }

    function resetLiveMedia() {
        liveMediaWasReset = true;

        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        // Remember audio values from before reset.
        var volumeBefore = self.GetVolume();
        var mutedBefore = self.IsMuted();

        // Setting the Source causes the media to be reloaded, 
        // even if the source URL doesn't change.
        _scriptApi.Source = mediaSource;
        logger.Info("Reset media source for live");

        // Restore the pre-reset audio values.
        self.SetVolume(volumeBefore);
        if (mutedBefore) {
            self.Mute();
        }

        if (disableVolume) {
            self.SetVolume(0);
        }
    }

    function isLive() {
        return !!liveStartTime;
    }

    function getPreviousSegmentBoundary(positionInMs) {
        return positionInMs - (positionInMs % ssSegmentLength);
    }

    this.SetupMediaElement = function (sources) {

        mediaPlayerView = $.extend({}, new Mediasite.MediaPlayer.BaseView(), new SilverlightView(viewOptions));
        element = mediaPlayerView.CreateMediaElement();
        mediaWrapper = mediaPlayerView.Wrapper;

        mediaElementObject = element.get(0);

        sourcesOnDeck = [viewOptions.selectedVideoSource].concat(sources);
        cueNextSource();

        if (typeof (mediaSource) === 'undefined') {
            _events.fire(_eventNames.MediaError, Mediasite.Player.Localization.MediaPlayer.NoCompatibleMediaError);
            logger.Error("Media - no compatible media source");
            return;
        }

        setTimeout(waitForPlayerInit, 500);

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

    function cueNextSource() {
        var source = sourcesOnDeck.shift();
        mediaSource = source.Location;
        mediaServerType = source.ServerType;
        mediaMimeType = source.MimeType;
    }

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

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

        liveMediaOpenReconnector.setLiveStatus(status);
        if (status === Mediasite.Player.PresentationStatus.LiveEnded) {
            handleMediaEvent = function () { };
            handleMediaError = function () { };
            self.Stop();
            _events.fire(_eventNames.StateChanged, Mediasite.Player.MediaState.Stopped);
        }
    };

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

    this.SetPosition = function (positionInMS) {
        if (inactivityExceeded === true) {
            window.location.reload();
            return;
        }

        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        if (positionInMS >= 0 && positionInMS <= self.GetDuration()) {
            lastSeekTime = positionInMS;

            var seekToPosition = positionInMS;

            if (_scriptApi.IsSmoothStreaming) {
                // Smooth Streaming content will seek to a segment boundary. 
                // Unfortunately, if the seeked to time is closer to the 
                // next boundary than the previous boundary, the media element skips forward. 
                // For the sake of timed events, it's better to always play from the previous 
                // boundary than to skip a section of timeline. We will end up playing some 
                // extra content, but we can mask the position returned by GetPosition show 
                // the seeked to time until the media catches up.
                seekToPosition = getPreviousSegmentBoundary(positionInMS);
            }

            _scriptApi.Position = seekToPosition;
            _events.fire(_eventNames.PositionChanged, positionInMS);
        }
    };

    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
        // Use _scriptApi.PlaybackRate directly so that our fudging of 
        // 1.99 to 2.00 doesn't break the rate down keyboard shortcut.
        //var speed = Math.round(_scriptApi.PlaybackRate * 100) / 100; 
        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

        // Nuthin.
    };

    this.Pause = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        if (self.GetDuration() > 0) {
            _scriptApi.Pause();
        } else {
            self.Stop();
        }

    };

    this.Play = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

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

    this.Stop = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        if (liveStartTime && !_scriptApi.IsSmoothStreaming) {
            stopPositionUpdateTimer(); // prevent reporting an incorrect time after _scriptApi.Position resets to 0
        }

        try {
            _scriptApi.Stop();
        } catch (e) { }

    };

    this.GetPosition = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        try {
            var position = _scriptApi.Position + liveStartOffset;

            if (lastSeekTime > 0) {
                var variance = position - lastSeekTime;
                if (_manualSeekThreshold < variance && variance < 0) {
                    // The media element was recently instructed to seek to a given position,
                    // but the media element is reporting a time slightly before that.
                    // However, for consistency, the model should report the requested position
                    // and assume the media will catch up (or seek elsewhere) soon.

                    return lastSeekTime;
                }
            }

            // Smooth Streaming content will seek to a segment boundary. 
            // We're going to mask that fact so that the time reported 
            // after a seek makes sense. This is important for timed events.
            if (_scriptApi.IsSmoothStreaming && position >= getPreviousSegmentBoundary(position) && position < lastSeekTime) {
                return lastSeekTime;
            }

            return position;

        } catch (e) {
            return 0;
        }
    };

    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.
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        try {
            return isLive() ? 0.0 : _scriptApi.NaturalDuration;
        } catch (e) {
            return 0.0;
        }
    };

    this.SetVolume = function (val) {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        if (disableVolume) {
            _scriptApi.Volume = 0;
            _scriptApi.IsMuted = true;
        } else {
            _scriptApi.Volume = val / 100;
            _scriptApi.IsMuted = (val == 0);
            _events.fire(_eventNames.VolumeChanged, Math.round(self.GetVolume()));
        }
    };

    this.GetVolume = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        return _scriptApi.Volume * 100;
    };

    this.StepVolumeUp = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

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

    this.StepVolumeDown = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        if (!disableVolume) {
            if (_scriptApi.Volume > 0.2) {
                this.SetVolume((_scriptApi.Volume - 0.2) * 100);
            } else {
                this.SetVolume(0);
            }
        }
    };

    this.ToggleMute = function () {
        if (!disableVolume) {
            if (self.IsMuted()) {
                self.UnMute();
            } else {
                self.Mute();
            }
        }
    };

    this.Mute = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        _scriptApi.IsMuted = true;

        if (self.IsMuted()) {
            _events.fire(_eventNames.VolumeChanged, 0);
        }
    };

    this.UnMute = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        if (!disableVolume) {
            _scriptApi.IsMuted = false;

            if (self.GetVolume() === 0) {
                this.SetVolume(50);
            } else {
                _events.fire(_eventNames.VolumeChanged, Math.round(self.GetVolume()));
            }
        }
    };

    this.IsMuted = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        return _scriptApi.IsMuted;
    };

    this.TogglePlayPause = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        if (convertState(_scriptApi.CurrentState) === Mediasite.Player.MediaState.Playing) {
            self.Pause();
        } else {
            self.Play();
        }
    };

    this.SetPlaybackRate = function (speed) {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        currentPlaybackRate = speed;
        _scriptApi.PlaybackRate = speed;
    };

    this.GetPlaybackRate = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        return _scriptApi.PlaybackRate;
    };

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

    this.GetAudioLanguages = function () {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        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) {
        var _scriptApi = getScriptApi();
        if (!_scriptApi) return;

        _scriptApi.AudioStreamIndex = language.Index;
    };

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

    this.RetrieveBookmark = function (bookmark) {
        clientBookmark = bookmark;
    };


    //*****************************************************************
    //* SilverlightView
    //*
    //* options 
    //*  mediaContainer
    //*  backgroundElement
    //*  allowFullScreenVideo
    //*  playerLayoutOptions
    //* 
    //*****************************************************************
    var SilverlightView = function (options) {
        var element;
        var slCanvas; // TODO: delete.
        var _scriptApi;
        var _isStandAlone = options && options.isStandAlone ? options.isStandAlone : false;
        this.Wrapper = undefined;

        var userActivation;

        this.CreateMediaElement = function () {

            options.mediaContainer.find('.MediaWrapper').remove();

            var versionRequired = '5.0.61118.0';

            var relativeXapPath = _isStandAlone ? 'SilverlightMediaPlayerView.xap' : 'Javascript/ClientBin/SilverlightMediaPlayerView.xap';

            var silverlightSource;
            var sourcePath = $('script[src*="/Javascript"]').attr("src");
            if (sourcePath) {
                var pathEnd = sourcePath.lastIndexOf('/Javascript');
                silverlightSource = sourcePath.substring(0, pathEnd);
                silverlightSource += '/' + relativeXapPath;
            } else {
                silverlightSource = relativeXapPath;
            }

            var $mediaWrapper = $('<div id="MediaWrapper" class="MediaWrapper"></div>').appendTo(options.mediaContainer);
            this.Wrapper = $mediaWrapper;
            var $backgroundElement = $(options.backgroundElement.selector);
            var backgroundColor = $backgroundElement.css('background-color', $backgroundElement.css('background-color')).css('background-color'); // leverage jQuery to get calculated color
            backgroundColor = backgroundColor ? backgroundColor.replace(/,/g, "|" /* working around initParam not handling embedded commas */) : "";

            Silverlight.createObjectEx({
                source: silverlightSource,
                parentElement: $mediaWrapper.get(0),
                id: 'MediaElement',
                properties:
            {
                width: options.width,
                height: options.height,
                isWindowless: 'true',
                framerate: '24',
                version: versionRequired
            },
                initParams: "FullScreenPromptText=" + options.silverlightFullScreenPrompt
                        + ",EnableFullScreenOnClick=" + options.allowFullScreenVideo
                        + ",BackgroundColor=" + backgroundColor
                        + ",LiveSmoothStreamingOffset=" + options.liveSmoothStreamingOffset,

                context: null
            });

            waitForCanvasInit();

            element = options.mediaContainer.find('#MediaElement');

            if (userActivation && userActivation.destroy) {
                userActivation.destroy();
            }
            userActivation = Mediasite.MediaPlayer.UserActivation.Create(this, options);

            return element;
        };

        this.UpdateMediaInfo = function (mediaInfo) {
            if (mediaInfo.AudioOnly) {
                element.css("height", "0px");
            }
        };

        function waitForCanvasInit() {
            var slContent = $('#MediaElement').get(0).content;
            if (slContent) {
                try {
                    _scriptApi = slContent.ScriptApi;
                } catch (e) { }
            }

            if (!_scriptApi) {
                setTimeout(waitForCanvasInit, 200);
                return;
            } else {
                // Do something that needs initialized sl object.

                $("#MediaElement").attr("tabindex", "-1");
                _scriptApi.OnFocus = function () {
                    $("#MediaElement").parent().focus();
                };

            }
        }

        this.SetIsStandAlone = function (value) {
            _isStandAlone = value;
        };
    };


};



Mediasite.MediaPlayer.UserActivation.Register({
    mediaPlayerType: Mediasite.Player.MediaPlayerType.Silverlight,
    usable: function () {
        return Mediasite.BrowserDetect.Firefox();
    },
    constructor: function (mediaContainer, options) {
        var logger = Mediasite.Logging.LoggerFactory("MediaPlayer UserActivation", { separator: " - " });

        var showDelay = 3000;
        var pollDelay = 500;

        var wrapper = mediaContainer.get(0);
        var object = wrapper.querySelector('object');
        var parents = $(object).parentsUntil(options.contentContainer || document.body);

        var userActivationAttempted = false;

        var checkIfPluginIsLoadedTimeout;
        var loadCheckInterval;

        checkIfPluginIsLoadedTimeout = setTimeout(function () {
            loadCheck();
            loadCheckInterval = setInterval(loadCheck, pollDelay);
        }, showDelay);
        //loadCheck();

        addOnLoadHandler();

        function addOnLoadHandler() {
            var onLoadKey = 'onLoadSilverlight' + Math.floor(Math.random() * 1000);
            window[onLoadKey] = onLoad.bind(this, object);

            var onLoadParam = document.createElement('param');
            onLoadParam.setAttribute('name', 'onload');
            onLoadParam.setAttribute('value', onLoadKey);

            object.appendChild(onLoadParam);

            function onLoad() {
                logger.Debug("Media element loaded", "detected via onLoad handler");
                reset();
            }
        }

        function loadCheck() {
            if (isAvailable()) {
                logger.Debug("Media element loaded", "detected via poll");
                reset();
            } else {
                enableUserActivation();
            }
        }

        function enableUserActivation() {
            if (!userActivationAttempted) {
                userActivationAttempted = true;
                Mediasite.MediaPlayer.UserActivation.ShowPrompt(null, onShowMessage);

                logger.Debug("Showing prompt");
            }
        }

        function onShowMessage(messageContainer) {
            logger.Debug("Adding 'click-me' element");

            var dummyObject = $(object).clone();
            dummyObject.find('param[name=onload]').remove();


            $(object).after(dummyObject);


            messageContainer.prepend(dummyObject);

            var objectCss = Mediasite.MediaPlayer.UserActivation.MediaElementCss;
            dummyObject.css(objectCss);
        }

        function reset() {
            clearTimeout(checkIfPluginIsLoadedTimeout);
            clearInterval(loadCheckInterval);
            if (userActivationAttempted) {
                Mediasite.MediaPlayer.UserActivation.RemovePrompt();

                logger.Debug("Removing prompt");
            }
        }

        function isAvailable() {
            try {
                return object && object.content && object.content.ScriptApi && true;
            } catch (err) { }
        }
    }
});



Mediasite.MediaPlayer.UserActivation.Register({
    mediaPlayerType: Mediasite.Player.MediaPlayerType.Silverlight,
    usable: function () {
        return Mediasite.BrowserDetect.Safari();
    },
    constructor: function (mediaContainer, options) {
        var logger = Mediasite.Logging.LoggerFactory("MediaPlayer UserActivation", { separator: " - " });

        var loadSilverlightDelay = 2000;
        var pollDelay = 500;

        var zIndexData = 'CTA-z-index';

        var object = $(mediaContainer).find('object')[0];

        var checkIfPluginIsLoadedTimeout;
        var loadCheckInterval;

        var alreadyLoaded = 0;
        var userActivationAttempted = false;
        var userActivationEnabled = false;

        markUsable(false);

        /*
        onLoad is more reliable
        checkIfPluginIsLoadedTimeout = setTimeout(function () {
        logger.Debug("Checking if media element is available (first attempt)");
        onLoadCheck();
        }, loadSilverlightDelay);
        */
        addOnLoadHandler(object, onLoad);


        function addOnLoadHandler(object, onLoad) {
            var onLoadKey = 'onLoadSilverlight' + Math.floor(Math.random() * 1000);
            window[onLoadKey] = onLoad.bind(this, object);

            var onLoadParam = document.createElement('param');
            onLoadParam.setAttribute('name', 'onLoad');
            onLoadParam.setAttribute('value', onLoadKey);

            object.appendChild(onLoadParam);
        }

        function onLoad() {
            logger.Debug("Media element loaded", "detected via onLoad handler", "onLoad called before: " + alreadyLoaded);

            clearTimeout(checkIfPluginIsLoadedTimeout);
            clearInterval(loadCheckInterval);

            if (!alreadyLoaded) {
                checkIfPluginIsLoadedTimeout = setTimeout(onLoadCheck, loadSilverlightDelay)
            }

            if (alreadyLoaded) {
                markUsable(true);
            }

            alreadyLoaded++;
        }

        function onLoadCheck() {
            if (isAvailable()) {
                logger.Debug("Media element appears loaded");
                markUsable(true);
            } else {
                enableUserActivation();
                clearInterval(loadCheckInterval);
                loadCheckInterval = setInterval(onLoadCheck, pollDelay);
            }
        }

        function enableUserActivation() {
            if (!userActivationAttempted) {
                userActivationAttempted = true;
                Mediasite.MediaPlayer.UserActivation.ShowPrompt({ top: '50px' }, onShowMessage);
                $(object).parent().on('click', stopPropagation);

                logger.Debug("Showing click-me prompt");
            }
        }

        var parent;
        function onShowMessage(messageContainer) {

            // Only one plugin <object> will have user activation enabled (ie. be made clickable).  
            // We need to know this fact and only call UserActivation.RemovePrompt() from reset() for the 
            // plugin where userActivationEnabled === true.

            userActivationEnabled = true;

            logger.Debug("Making media element clickable");
            css = $.extend({}, Mediasite.MediaPlayer.UserActivation.MediaElementCss, {
                position: 'fixed'
            });

            parent = object.parentNode;
            $(object)
                .appendTo(messageContainer)
                .css(css);
        }

        function reset() {
            clearTimeout(checkIfPluginIsLoadedTimeout);
            clearInterval(loadCheckInterval);

            // only re-parent and remove prompt (which deletes the container DIV) if this was the object 
            // that was made clickable.

            if (userActivationAttempted && userActivationEnabled) {
                userActivationAttempted = false;
                $(object).appendTo(parent);
                Mediasite.MediaPlayer.UserActivation.RemovePrompt();

                var resetCss = {};
                for (var prop in css) {
                    if (!css.hasOwnProperty(prop)) continue;
                    resetCss[prop] = '';
                }
                $(object).css(resetCss);

                logger.Debug("Removed 'click-me'");
            }
        }

        function markUsable(usable) {
            if (usable === void 0) usable = false;

            object.usable = usable;
            if (usable) {
                reset();
            }
        }

        function isAvailable() {
            return object.content && true;
        }

        function stopPropagation(e) {
            e.stopPropagation();
        }
    }
});
