/// <summary> /// Handles PlaybackTrackChanged for our SSMEs /// </summary> private void SSME_PlaybackTrackChanged(object sender, TrackChangedEventArgs e) { var ssme = sender as SmoothStreamingMediaElement; Debug.Assert(ssme != null); if (ssme != null) { // work around a bug in the SSME where it raises events on a non-UI thread // TODO: this code can be removed once that bug is fixed if (!ssme.Dispatcher.CheckAccess()) { ssme.Dispatcher.BeginInvoke(() => SSME_PlaybackTrackChanged(sender, e)); return; } if (e.StreamType == MediaStreamType.Video) { SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoBySSME(ssme); ulong lastPlaybackBitrate = e.NewTrack != null ? e.NewTrack.Bitrate : 0; ssmeStateInfo.LastPlaybackTrackBitrate = lastPlaybackBitrate; } } }
/// <summary> /// Returns the last observed DownloadTrack.Bitrate /// </summary> /// <param name="smoothStreamingMediaElement">smoothStreamingMediaElement</param> public ulong GetLastDownloadBitrate(SmoothStreamingMediaElement smoothStreamingMediaElement) { SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoThrowIfNotFound(smoothStreamingMediaElement); return(ssmeStateInfo.LastDownloadTrackBitrate); }
/// <summary> /// Updates the AverageRenderedFramesPerSecond for the SSME /// </summary> /// <param name="ssmeStateInfo"></param> private void UpdateAverageFramesPerSecond(SSMEStateInfo ssmeStateInfo) { double averageFPS = ssmeStateInfo.AverageRenderedFramesPerSecond; averageFPS = ((averageFPS + ssmeStateInfo.SmoothStreamingMediaElement.RenderedFramesPerSecond) / 2); ssmeStateInfo.AverageRenderedFramesPerSecond = averageFPS; }
/// <summary> /// GetMinimumPlaybackBitrate - gets the bitrate set during the call to SetMinimumPlaybackBitrate /// </summary> /// <param name="smoothStreamingMediaElement">smoothStreamingMediaElement</param> public ulong GetMinimumPlaybackBitrate(SmoothStreamingMediaElement smoothStreamingMediaElement) { SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoThrowIfNotFound(smoothStreamingMediaElement); return(ssmeStateInfo.MinimumPlaybackBitrate); }
/// <summary> /// Returns the last observed RenderedFramesPerSecond /// </summary> /// <param name="smoothStreamingMediaElement">smoothStreamingMediaElement</param> public double GetAverageRenderedFramesPerSecond(SmoothStreamingMediaElement smoothStreamingMediaElement) { SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoThrowIfNotFound(smoothStreamingMediaElement); return(ssmeStateInfo.AverageRenderedFramesPerSecond); }
/// <summary> /// SetMinimumPlaybackBitrate /// </summary> /// <param name="smoothStreamingMediaElement">smoothStreamingMediaElement</param> /// <param name="minimumPlaybackBitrate"> /// The minimum bitrate (in bps) this SmoothStreamingMediaElement /// must be capable of playing before the next SmoothStreamingMediaElement /// should be enabled /// </param> public void SetMinimumPlaybackBitrate(SmoothStreamingMediaElement smoothStreamingMediaElement, ulong minimumPlaybackBitrate) { SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoThrowIfNotFound(smoothStreamingMediaElement); ssmeStateInfo.MinimumPlaybackBitrate = minimumPlaybackBitrate; }
/// <summary> /// Removes a SmoothStreamingMediaElement /// </summary> /// <param name="smoothStreamingMediaElement">smoothStreamingMediaElement</param> public void RemoveSmoothStreamingMediaElement(SmoothStreamingMediaElement smoothStreamingMediaElement) { SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoThrowIfNotFound(smoothStreamingMediaElement); UnhookEvents(ssmeStateInfo.SmoothStreamingMediaElement); _ssmeStateInfoList.Remove(ssmeStateInfo); }
/// <summary> /// GetSSMEStateInfoThrowIfNotFound /// </summary> /// <param name="smoothStreamingMediaElement">smoothStreamingMediaElement</param> private SSMEStateInfo GetSSMEStateInfoThrowIfNotFound(SmoothStreamingMediaElement smoothStreamingMediaElement) { SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoBySSME(smoothStreamingMediaElement); if (ssmeStateInfo == null) { throw new ArgumentException( "smoothStreamingMediaElement is not known to this MultiSmoothStreamingMediaElementHeuristicsManager", "smoothStreamingMediaElement"); } return(ssmeStateInfo); }
// clear reenable count every tick. private void _clearReEnableTimer_Tick(object sender, EventArgs e) { for (int i = 0; i < _ssmeStateInfoList.Count; i++) { SSMEStateInfo ssmeStateInfo = _ssmeStateInfoList[i]; if (ssmeStateInfo.ReEnableCount >= _maxReEnableAttempts) { ssmeStateInfo.ReEnableCount = -1; } } }
/// <summary> /// SetMinimumRenderedFramesPerSecond specifies the minimum RenderedFramesPerSecond /// each SmoothStreamingMediaElement should sustain before the next /// SmoothStreamingMediaElement is enabled /// </summary> /// <param name="smoothStreamingMediaElement">smoothStreamingMediaElement</param> /// <param name="minimumRenderedFramesPerSecond">minimumRenderedFramesPerSecond</param> public void SetMinimumRenderedFramesPerSecond(SmoothStreamingMediaElement smoothStreamingMediaElement, double minimumRenderedFramesPerSecond) { if (minimumRenderedFramesPerSecond < 0.0) { throw new ArgumentOutOfRangeException("minimumRenderedFramesPerSecond"); } SSMEStateInfo ssmeStateInfo = GetSSMEStateInfoThrowIfNotFound(smoothStreamingMediaElement); ssmeStateInfo.MinimumRenderedFramesPerSecond = minimumRenderedFramesPerSecond; }
/// <summary> /// AddSmoothStreamingMediaElement /// </summary> /// <param name="smoothStreamingMediaElement">the SmoothStreamingMediaElement</param> /// <param name="minimumPlaybackBitrate"> /// The minimum bitrate (in bps) this SmoothStreamingMediaElement /// must be capable of playing before the next SmoothStreamingMediaElement /// should be enabled /// </param> /// <param name="minimumRenderedFramesPerSecond"> /// The minimum RenderedFramesPerSecond this SmoothStreamingMediaElement /// must be achieving at the minimum bitrate before the next /// SmoothStreamingMediaElement should be enabled /// </param> public void AddSmoothStreamingMediaElement(SmoothStreamingMediaElement smoothStreamingMediaElement, ulong minimumPlaybackBitrate, double minimumRenderedFramesPerSecond) { if (ContainsSmoothStreamingMediaElement(smoothStreamingMediaElement)) { throw new ArgumentException("SmoothStreamingMediaElement has already been added", "smoothStreamingMediaElement"); } if (smoothStreamingMediaElement == null) { throw new ArgumentNullException("smoothStreamingMediaElement"); } if (minimumRenderedFramesPerSecond < 0.0) { throw new ArgumentOutOfRangeException("minimumRenderedFramesPerSecond"); } //finally, add to our collection var ssmeStateInfo = new SSMEStateInfo(); ssmeStateInfo.SmoothStreamingMediaElement = smoothStreamingMediaElement; ssmeStateInfo.MinimumPlaybackBitrate = minimumPlaybackBitrate; ssmeStateInfo.MinimumRenderedFramesPerSecond = minimumRenderedFramesPerSecond; // TODOL: workaround to get the initial download and playback bitrates, // this can be removed when the SSME control exposes the properties SmoothStreamingMediaElement coreSmoothStreamingMediaElement = smoothStreamingMediaElement; if (coreSmoothStreamingMediaElement != null) { ssmeStateInfo.LastDownloadTrackBitrate = coreSmoothStreamingMediaElement.VideoDownloadTrack != null ? coreSmoothStreamingMediaElement.VideoDownloadTrack. Bitrate : 0; ssmeStateInfo.LastPlaybackTrackBitrate = coreSmoothStreamingMediaElement.VideoPlaybackTrack != null ? coreSmoothStreamingMediaElement.VideoPlaybackTrack. Bitrate : 0; } _ssmeStateInfoList.Add(ssmeStateInfo); HookEvents(smoothStreamingMediaElement); }
/// <summary> /// Main heuristics logic /// </summary> private void Dispatcher_Tick(object sender, EventArgs e) { _intervalDelta += 1000; if (_intervalDelta < _monitorIntervalInMilliseconds) { // just update the FPS averages for (int i = 0; i < _ssmeStateInfoList.Count; i++) { UpdateAverageFramesPerSecond(_ssmeStateInfoList[i]); } } else { _intervalDelta = 0; MatchPlaySpeeds(); var enable = new List <SmoothStreamingMediaElement>(); var disable = new List <SmoothStreamingMediaElement>(); // Simple algoritm for determining which SSMEs to enable // or disable // 1) SSME 0 is always enabled // 2) If SSME 0 can't meet the minimum bitrate, disable // all of the other SSME's // 3) If SSME 0 can meet the minimum bitrate, enable // the next disabled SSME // 4) When enabling the next disabled SSME, check // all of the intermediate SSMEs and make sure they // are keeping up and disable them if they are not bool disableRest = false; bool enableNext = false; for (int i = 0; i < _ssmeStateInfoList.Count; i++) { SSMEStateInfo ssmeStateInfo = _ssmeStateInfoList[i]; if (disableRest) { ssmeStateInfo.RecommendedEnable = false; disable.Add(ssmeStateInfo.SmoothStreamingMediaElement); continue; } // if we're not disabling the rest, see how the // current SSME is doing ulong minBitrateRequested = ssmeStateInfo.MinimumPlaybackBitrate; double minRenderedFPS = ssmeStateInfo.MinimumRenderedFramesPerSecond; ulong playbackBitrate = ssmeStateInfo.LastPlaybackTrackBitrate; ulong downloadBitrate = ssmeStateInfo.LastDownloadTrackBitrate; UpdateAverageFramesPerSecond(ssmeStateInfo); double renderedFPS = ssmeStateInfo.AverageRenderedFramesPerSecond; // this next section can be replaced with a call to // Math.Min(downloadTrack.Bitrate, playbackTrack.Bitrate) // but I've expanded it for clarity (and to allow // special tweaks for things like ads, etc) ulong effectiveBitrate = 0; if (downloadBitrate < playbackBitrate) { // we're downloading a lower bitrate than // we're playing - so we're scaling down // which means we can't handle this bitrate effectiveBitrate = downloadBitrate; } else if (downloadBitrate > playbackBitrate) { // we're downloading a higher bitrate, so we can // handle playing this bitrate. We're still not // sure if we can handle playing this bitrate, so // use the bitrate we know we can handle effectiveBitrate = playbackBitrate; } else { effectiveBitrate = playbackBitrate; } if (effectiveBitrate == 0) { // we can get into this state if downloadBitrate // has never been updated effectiveBitrate = Math.Max(playbackBitrate, downloadBitrate); } if (enableNext) { // The previous SSME was playing or downloading at or above // the minBitrate - enable the next disabled SSME // but check enabled SSME's along the way and make sure // they can still handle playback if (ssmeStateInfo.RecommendedEnable) { // we previously recommended enabling this // SSME - let's check in and make sure // it should still be enabled if (effectiveBitrate < minBitrateRequested || (renderedFPS < minRenderedFPS && !GetIsMinimized(renderedFPS))) { // this enabled SSME is not keeping up ssmeStateInfo.RecommendedEnable = false; disable.Add(ssmeStateInfo.SmoothStreamingMediaElement); disableRest = true; } else { // continue recommeding this SSME be enabled enable.Add(ssmeStateInfo.SmoothStreamingMediaElement); } } else { // we were asked to enable the next disabled // SSME, and along our way to find it we have // validated the all of the other enabled SSME's // should still be enabled. It's time to turn on // a disabled SSME (if it hasn't been re-enabled // too many times) ssmeStateInfo.ReEnableCount++; if (ssmeStateInfo.ReEnableCount > MaxReenableAttempts) { // changed our mind ssmeStateInfo.RecommendedEnable = false; disable.Add(ssmeStateInfo.SmoothStreamingMediaElement); // since we disable SSME's in a cascading fashion // if index 1 is being turned off, we're disabling all // of the secondary SSMEs - set our flag if (i == 1) { _recommendPermanentlyDisableAllSecondary = true; } } else { // go ahead and turn this SSME back on ssmeStateInfo.RecommendedEnable = true; enable.Add(ssmeStateInfo.SmoothStreamingMediaElement); } // either way - the rest of you can just chill out disableRest = true; } } if (i == 0) { // this is our first pass through the heuristics loop and it // is a special one because we treat the primary SSME special. // It is never disabled and if it can't keep up, it can // disable all of the secondary SSMEs enable.Add(ssmeStateInfo.SmoothStreamingMediaElement); ssmeStateInfo.RecommendedEnable = true; bool browserIsMinimized = GetIsMinimized(renderedFPS); // if we're minimized we need to disable our secondary SSMEs if (effectiveBitrate < minBitrateRequested || renderedFPS < minRenderedFPS || browserIsMinimized) { // we're not keeping up (or are minimized), disable the other SSMEs disableRest = true; if (browserIsMinimized) { // if the browser is minimized we're disabling the secondary // SSME's to prevent a slideshow, but we don't want // this disabling to count against the ReEnable counts for (int j = 1; j < _ssmeStateInfoList.Count; j++) { _ssmeStateInfoList[j].DecrementReEnableCount(); } } } else { // we're keeping up - evaluating enabling the next SSME enableNext = true; } } } //raise our event RaiseRecommendationChanged(enable, disable); } }
/// <summary> /// AddSmoothStreamingMediaElement /// </summary> /// <param name="smoothStreamingMediaElement">the SmoothStreamingMediaElement</param> /// <param name="minimumPlaybackBitrate"> /// The minimum bitrate (in bps) this SmoothStreamingMediaElement /// must be capable of playing before the next SmoothStreamingMediaElement /// should be enabled /// </param> /// <param name="minimumRenderedFramesPerSecond"> /// The minimum RenderedFramesPerSecond this SmoothStreamingMediaElement /// must be achieving at the minimum bitrate before the next /// SmoothStreamingMediaElement should be enabled /// </param> public void AddSmoothStreamingMediaElement(SmoothStreamingMediaElement smoothStreamingMediaElement, ulong minimumPlaybackBitrate, double minimumRenderedFramesPerSecond) { if (ContainsSmoothStreamingMediaElement(smoothStreamingMediaElement)) { throw new ArgumentException("SmoothStreamingMediaElement has already been added", "smoothStreamingMediaElement"); } if (smoothStreamingMediaElement == null) { throw new ArgumentNullException("smoothStreamingMediaElement"); } if (minimumRenderedFramesPerSecond < 0.0) { throw new ArgumentOutOfRangeException("minimumRenderedFramesPerSecond"); } //finally, add to our collection var ssmeStateInfo = new SSMEStateInfo(); ssmeStateInfo.SmoothStreamingMediaElement = smoothStreamingMediaElement; ssmeStateInfo.MinimumPlaybackBitrate = minimumPlaybackBitrate; ssmeStateInfo.MinimumRenderedFramesPerSecond = minimumRenderedFramesPerSecond; // TODOL: workaround to get the initial download and playback bitrates, // this can be removed when the SSME control exposes the properties SmoothStreamingMediaElement coreSmoothStreamingMediaElement = smoothStreamingMediaElement; if (coreSmoothStreamingMediaElement != null) { ssmeStateInfo.LastDownloadTrackBitrate = coreSmoothStreamingMediaElement.VideoDownloadTrack != null ? coreSmoothStreamingMediaElement.VideoDownloadTrack. Bitrate : 0; ssmeStateInfo.LastPlaybackTrackBitrate = coreSmoothStreamingMediaElement.VideoPlaybackTrack != null ? coreSmoothStreamingMediaElement.VideoPlaybackTrack. Bitrate : 0; } _ssmeStateInfoList.Add(ssmeStateInfo); HookEvents(smoothStreamingMediaElement); }
/// <summary> /// Updates the AverageRenderedFramesPerSecond for the SSME /// </summary> /// <param name="ssmeStateInfo"></param> private void UpdateAverageFramesPerSecond(SSMEStateInfo ssmeStateInfo) { double averageFPS = ssmeStateInfo.AverageRenderedFramesPerSecond; averageFPS = ((averageFPS + ssmeStateInfo.SmoothStreamingMediaElement.RenderedFramesPerSecond)/2); ssmeStateInfo.AverageRenderedFramesPerSecond = averageFPS; }