/// <summary> /// Provides the implementation for the Pause Media Command. /// </summary> /// <returns>True if the command was successful</returns> private bool CommandPauseMedia() { if (State.CanPause == false) { return(false); } MediaCore.Clock.Pause(); foreach (var renderer in MediaCore.Renderers.Values) { renderer.Pause(); } MediaCore.ChangePosition(SnapPositionToBlockPosition(MediaCore.WallClock)); State.UpdateMediaState(PlaybackStatus.Pause); return(true); }
/// <inheritdoc /> protected override void ExecuteCycleLogic(CancellationToken ct) { #region Setup the Decoding Cycle // Update state properties -- this must be done on every cycle // because a direct command might have changed the components var wallClock = MediaCore.WallClock; var main = Container.Components.MainMediaType; MediaBlockBuffer blocks; DecodedFrameCount = 0; var rangePercent = 0d; #endregion try { // The 2-part logic blocks detect a sync-buffering scenario // and then decodes the necessary frames. if (MediaCore.HasDecodingEnded || ct.IsCancellationRequested) { return; } #region Sync-Buffering Detection // Capture the blocks for easier readability blocks = MediaCore.Blocks[main]; // If we are not in range then we need to enter the sync-buffering state if (NeedsMorePackets && !MediaCore.IsSyncBuffering && blocks.IsInRange(wallClock) == false) { // Enter sync-buffering scenario MediaCore.Clock.Pause(); wallClock = MediaCore.WallClock; MediaCore.IsSyncBuffering = true; SyncBufferStartTime = DateTime.UtcNow; this.LogInfo(Aspects.DecodingWorker, $"SYNC-BUFFER: Started. Buffer: {State.BufferingProgress:p}. Clock: {wallClock.Format()}"); } #endregion #region Component Decoding // We need to add blocks if the wall clock is over 75% // for each of the components so that we have some buffer. foreach (var t in Container.Components.MediaTypes) { if (ct.IsCancellationRequested) { break; } // Capture a reference to the blocks and the current Range Percent const double rangePercentThreshold = 0.75d; blocks = MediaCore.Blocks[t]; rangePercent = blocks.GetRangePercent(wallClock); // Read as much as we can for this cycle but always within range. while (blocks.IsFull == false || rangePercent > rangePercentThreshold) { if (ct.IsCancellationRequested || AddNextBlock(t) == false) { break; } DecodedFrameCount += 1; rangePercent = blocks.GetRangePercent(wallClock); // Determine break conditions to save CPU time if (rangePercent > 0 && rangePercent <= rangePercentThreshold && blocks.IsFull == false && blocks.CapacityPercent >= 0.25d && blocks.IsInRange(wallClock)) { break; } } #endregion } } finally { // Provide updates to decoding stats State.UpdateDecodingBitRate( MediaCore.Blocks.Values.Sum(b => b.IsInRange(wallClock) ? b.RangeBitRate : 0)); // Detect End of Decoding Scenarios // The Rendering will check for end of media when this // condition is set. var hasDecodingEnded = DetectHasDecodingEnded(wallClock, DecodedFrameCount, main); MediaCore.HasDecodingEnded = hasDecodingEnded; // Detect if an exit from Sync Buffering is required var mustExitSyncBuffering = MediaCore.IsSyncBuffering && (ct.IsCancellationRequested || hasDecodingEnded || State.BufferingProgress >= 0.95); // Detect if we need an immediate exit from sync buffering if (mustExitSyncBuffering || (MediaCore.IsSyncBuffering && !NeedsMorePackets)) { blocks = MediaCore.Blocks[main]; if (blocks.Count > 0 && !blocks.IsInRange(wallClock)) { wallClock = blocks[wallClock].StartTime; } MediaCore.ChangePosition(wallClock); MediaCore.IsSyncBuffering = false; this.LogInfo(Aspects.DecodingWorker, $"SYNC-BUFFER: Completed in {DateTime.UtcNow.Subtract(SyncBufferStartTime).TotalMilliseconds:0.0} ms"); if (State.MediaState == PlaybackStatus.Play) { MediaCore.ResumePlayback(); } } } }
/// <inheritdoc /> protected override void ExecuteCycleLogic(CancellationToken ct) { // Update Status Properties var main = Container.Components.MainMediaType; var all = MediaCore.Renderers.Keys.ToArray(); var currentBlock = new MediaTypeDictionary <MediaBlock>(); if (HasInitialized == false) { // wait for main component blocks or EOF or cancellation pending if (MediaCore.Blocks[main].Count <= 0) { return; } // Set the initial clock position MediaCore.ChangePosition(MediaCore.Blocks[main].RangeStartTime); // Wait for renderers to be ready foreach (var t in all) { MediaCore.Renderers[t]?.WaitForReadyState(); } // Mark as initialized HasInitialized.Value = true; } #region Run the Rendering Cycle try { // TODO: wait for active seek command try { Commands.WaitForSeekBlocks(ct); } catch { return; } #region 2. Handle Block Rendering // capture the wall clock for this cycle var wallClock = MediaCore.WallClock; // Capture the blocks to render foreach (var t in all) { // Get the audio, video, or subtitle block to render currentBlock[t] = t == MediaType.Subtitle && MediaCore.PreloadedSubtitles != null ? MediaCore.PreloadedSubtitles[wallClock] : MediaCore.Blocks[t][wallClock]; } // Render each of the Media Types if it is time to do so. foreach (var t in all) { // Skip rendering for nulls if (currentBlock[t] == null || currentBlock[t].IsDisposed) { continue; } // Render by forced signal (TimeSpan.MinValue) or because simply it is time to do so if (MediaCore.LastRenderTime[t] == TimeSpan.MinValue || currentBlock[t].StartTime != MediaCore.LastRenderTime[t]) { SendBlockToRenderer(currentBlock[t], wallClock); } } #endregion #region 3. Finalize the Rendering Cycle // Call the update method on all renderers so they receive what the new wall clock is. foreach (var t in all) { MediaCore.Renderers[t]?.Update(wallClock); } #endregion } catch (Exception ex) { MediaCore.LogError(Aspects.RenderingWorker, "Error while in rendering worker cycle", ex); throw; } finally { // Check End of Media Scenarios if (MediaCore.HasDecodingEnded && Commands.IsSeeking == false && MediaCore.WallClock >= MediaCore.LastRenderTime[main] && MediaCore.WallClock >= MediaCore.Blocks[main].RangeEndTime) { // Rendered all and nothing else to render if (State.HasMediaEnded == false) { MediaCore.Clock.Pause(); var endPosition = MediaCore.ChangePosition(MediaCore.Blocks[main].RangeEndTime); State.UpdateMediaEnded(true, endPosition); State.UpdateMediaState(PlaybackStatus.Stop); foreach (var mt in Container.Components.MediaTypes) { MediaCore.InvalidateRenderer(mt); } } } else { State.UpdateMediaEnded(false, TimeSpan.Zero); } // Update the Position if (!ct.IsCancellationRequested) { State.UpdatePosition(); } } #endregion }
/// <inheritdoc /> protected override void ExecuteCycleLogic(CancellationToken ct) { // Update Status Properties var main = Container.Components.MainMediaType; var all = MediaCore.Renderers.Keys.ToArray(); var currentBlock = new MediaTypeDictionary <MediaBlock>(); if (HasInitialized == false) { // wait for main component blocks or EOF or cancellation pending if (MediaCore.Blocks[main].Count <= 0) { return; } // Set the initial clock position MediaCore.ChangePosition(MediaCore.Blocks[main].RangeStartTime); // Wait for renderers to be ready foreach (var t in all) { MediaCore.Renderers[t]?.WaitForReadyState(); } // Mark as initialized HasInitialized.Value = true; } #region Run the Rendering Cycle try { #region 1. Wait for any seek operation to make blocks available in a loop while (!ct.IsCancellationRequested && Commands.IsActivelySeeking && !MediaCore.Blocks[main].IsInRange(MediaCore.WallClock)) { // Check if we finally have seek blocks available // if we don't get seek blocks in range and we are not step-seeking, // then we simply break out of the loop and render whatever it is we have // to create the illussion of smooth seeking. For precision seeking we // continue the loop. if (!Commands.WaitForSeekBlocks(DefaultPeriod.Milliseconds) && Commands.ActiveSeekMode == CommandManager.SeekMode.Normal) { break; } } #endregion #region 2. Handle Block Rendering // Capture the blocks to render at a fixed wall clock position // so all blocks are aligned to the same timestamp var wallClock = MediaCore.WallClock; foreach (var t in all) { // skip blocks if we are seeking and they are not video blocks if (Commands.IsSeeking && t != MediaType.Video) { currentBlock[t] = null; continue; } // Get the audio, video, or subtitle block to render currentBlock[t] = t == MediaType.Subtitle && MediaCore.PreloadedSubtitles != null ? MediaCore.PreloadedSubtitles[wallClock] : MediaCore.Blocks[t][wallClock]; } // Render each of the Media Types if it is time to do so. foreach (var t in all) { // Don't send null blocks to renderer if (currentBlock[t] == null || currentBlock[t].IsDisposed) { continue; } // Render by forced signal (TimeSpan.MinValue) or because simply it is time to do so if (MediaCore.LastRenderTime[t] == TimeSpan.MinValue || currentBlock[t].StartTime != MediaCore.LastRenderTime[t]) { SendBlockToRenderer(currentBlock[t], wallClock); } } #endregion #region 3. Finalize the Rendering Cycle // Call the update method on all renderers so they receive what the new wall clock is. foreach (var t in all) { MediaCore.Renderers[t]?.Update(wallClock); } #endregion } catch (Exception ex) { MediaCore.LogError(Aspects.RenderingWorker, "Error while in rendering worker cycle", ex); throw; } finally { // Check End of Media Scenarios if (Commands.IsSeeking == false && MediaCore.HasDecodingEnded && MediaCore.WallClock >= MediaCore.LastRenderTime[main] && MediaCore.WallClock >= MediaCore.Blocks[main].RangeEndTime) { // Rendered all and nothing else to render if (State.HasMediaEnded == false) { MediaCore.Clock.Pause(); var endPosition = MediaCore.ChangePosition(MediaCore.Blocks[main].RangeEndTime); State.UpdateMediaEnded(true, endPosition); State.UpdateMediaState(PlaybackStatus.Stop); foreach (var mt in Container.Components.MediaTypes) { MediaCore.InvalidateRenderer(mt); } } } else { State.UpdateMediaEnded(false, TimeSpan.Zero); } // Update the Position if (!ct.IsCancellationRequested && Commands.IsSeeking == false) { State.UpdatePosition(); } } #endregion }