/// <inheritdoc /> protected override void ExecuteCycleLogic(CancellationToken ct) { // Update Status Properties var main = Container.Components.MainMediaType; var all = MediaCore.Renderers.Keys.ToArray(); // Ensure we have renderers ready and main blocks available if (!Initialize(all)) { return; } try { // If we are in the middle of a seek, wait for seek blocks WaitForSeekBlocks(main, ct); // Ensure the RTC clocks match the playback position AlignClocksToPlayback(main, all); // Check for and enter a sync-buffering scenario EnterSyncBuffering(main, all); // Render each of the Media Types if it is time to do so. if (MediaOptions.UseParallelRendering) { ParallelRenderBlocks.Invoke(all); } else { SerialRenderBlocks.Invoke(all); } } catch (Exception ex) { MediaCore.LogError( Aspects.RenderingWorker, "Error while in rendering worker cycle", ex); throw; } finally { DetectPlaybackEnded(main); // CatchUpWithLiveStream(); // TODO: We are on to something good here ExitSyncBuffering(main, all, ct); ReportAndResumePlayback(main); AdjustWorkerPeriod(main); } }
/// <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 }