/// <summary> /// Performs a seek operation to the specified position. /// </summary> /// <param name="position">The position.</param> private void Seek(TimeSpan position) { SeekingDone.Wait(); var startTime = DateTime.UtcNow; var resumeClock = Clock.IsRunning; Clock.Pause(); SeekingDone.Reset(); PacketReadingCycle.Wait(); FrameDecodingCycle.Wait(); BlockRenderingCycle.Wait(); // Clear Blocks and frames, reset the render times foreach (var t in Container.Components.MediaTypes) { Frames[t].Clear(); Blocks[t].Clear(); LastRenderTime[t] = TimeSpan.MinValue; } // Populate frame with after-seek operation var frames = Container.Seek(position); foreach (var frame in frames) { Frames[frame.MediaType].Push(frame); } // Resume the clock if it was running before the seek operation OnPropertyChanged(nameof(Position)); if (resumeClock) { Clock.Play(); } Container.Log(MediaLogMessageType.Debug, $"SEEK D: Elapsed: {startTime.DebugElapsedUtc()}"); RequestedSeekPosition = null; SeekingDone.Set(); }
/// <summary> /// Starts the block rendering worker. /// </summary> private void StartBlockRenderingWorker() { if (BlockRenderingWorkerExit != null) { return; } BlockRenderingWorkerExit = WaitEventFactory.Create(isCompleted: false, useSlim: true); // Holds the main media type var main = Container.Components.MainMediaType; // Holds all components var all = Renderers.Keys.ToArray(); // Holds a snapshot of the current block to render var currentBlock = new MediaTypeDictionary <MediaBlock>(); // Keeps track of how many blocks were rendered in the cycle. var renderedBlockCount = new MediaTypeDictionary <int>(); // wait for main component blocks or EOF or cancellation pending while (CanReadMoreFramesOf(main) && Blocks[main].Count <= 0) { FrameDecodingCycle.Wait(Constants.Interval.LowPriority); } // Set the initial clock position var wallClock = ChangePosition(Blocks[main].RangeStartTime); // Wait for renderers to be ready foreach (var t in all) { Renderers[t]?.WaitForReadyState(); } // The Render timer is responsible for sending frames to renders BlockRenderingWorker = new Timer((s) => { #region Detect Exit/Skip Conditions if (Commands.IsStopWorkersPending || BlockRenderingWorkerExit.IsCompleted || IsDisposed) { BlockRenderingWorkerExit?.Complete(); return; } // Skip the cycle if it's already running if (BlockRenderingCycle.IsInProgress) { Log(MediaLogMessageType.Trace, $"SKIP: {nameof(BlockRenderingWorker)} already in a cycle. {WallClock}"); return; } #endregion #region Run the Rendering Cycle try { #region 1. Control and Capture // Wait for the seek op to finish before we capture blocks Commands.WaitForActiveSeekCommand(); // Signal the start of a block rendering cycle BlockRenderingCycle.Begin(); // Skip the cycle if we are running a priority command if (Commands.IsExecutingDirectCommand) { return; } // Updatete Status Properties main = Container.Components.MainMediaType; all = Renderers.Keys.ToArray(); // Reset the rendered count to 0 foreach (var t in all) { renderedBlockCount[t] = 0; } #endregion #region 2. Handle Block Rendering // capture the wall clock for this cycle wallClock = 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 && PreloadedSubtitles != null) ? PreloadedSubtitles[wallClock] : currentBlock[t] = 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 (LastRenderTime[t] == TimeSpan.MinValue || currentBlock[t].StartTime != LastRenderTime[t]) { renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock, main); } } #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) { Renderers[t]?.Update(wallClock); } #endregion } catch { throw; } finally { // Update the Position if (IsWorkerInterruptRequested == false && IsSyncBuffering == false) { State.UpdatePosition(Clock.IsRunning ? wallClock : Clock.Position); } // Always exit notifying the cycle is done. BlockRenderingCycle.Complete(); } #endregion }, this, // the state argument passed on to the ticker 0, Convert.ToInt32(Constants.Interval.HighPriority.TotalMilliseconds)); }
/// <summary> /// Starts the block rendering worker. /// </summary> private void StartBlockRenderingWorker() { if (BlockRenderingWorkerExit != null) { return; } BlockRenderingWorkerExit = WaitEventFactory.Create(isCompleted: false, useSlim: true); // Synchronized access to parts of the run cycle var isRunningRenderingCycle = false; // Holds the main media type var main = Container.Components.Main.MediaType; // Holds all components var all = Renderers.Keys.ToArray(); // Holds a snapshot of the current block to render var currentBlock = new MediaTypeDictionary <MediaBlock>(); // Keeps track of how many blocks were rendered in the cycle. var renderedBlockCount = new MediaTypeDictionary <int>(); // reset render times for all components foreach (var t in all) { LastRenderTime[t] = TimeSpan.MinValue; } // Ensure packet reading is running PacketReadingCycle.Wait(); // wait for main component blocks or EOF or cancellation pending while (CanReadMoreFramesOf(main) && Blocks[main].Count <= 0) { FrameDecodingCycle.Wait(); } // Set the initial clock position Clock.Update(Blocks[main].RangeStartTime); var wallClock = WallClock; // Wait for renderers to be ready foreach (var t in all) { Renderers[t]?.WaitForReadyState(); } // The Render timer is responsible for sending frames to renders BlockRenderingWorker = new Timer((s) => { #region Detect a Timer Stop if (IsTaskCancellationPending || BlockRenderingWorkerExit.IsCompleted || IsDisposed) { BlockRenderingWorkerExit.Complete(); return; } #endregion #region Run the Rendering Cycle // Updatete Status Properties State.UpdateBufferingProperties(); // Don't run the cycle if it's already running if (isRunningRenderingCycle) { Log(MediaLogMessageType.Trace, $"SKIP: {nameof(BlockRenderingWorker)} alredy in a cycle. {WallClock}"); return; } try { #region 1. Control and Capture // Flag the current rendering cycle isRunningRenderingCycle = true; // Reset the rendered count to 0 foreach (var t in all) { renderedBlockCount[t] = 0; } // Capture current clock position for the rest of this cycle BlockRenderingCycle.Begin(); #endregion #region 2. Handle Block Rendering // Wait for the seek op to finish before we capture blocks if (HasDecoderSeeked) { SeekingDone.Wait(); } // capture the wall clock for this cycle wallClock = WallClock; // Capture the blocks to render foreach (var t in all) { if (t == MediaType.Subtitle && PreloadedSubtitles != null) { // Get the preloaded, cached subtitle block currentBlock[t] = PreloadedSubtitles[wallClock]; } else { // Get the regular audio, video, or sub block currentBlock[t] = 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) { continue; } // Render by forced signal (TimeSpan.MinValue) or because simply it is time to do so if (LastRenderTime[t] == TimeSpan.MinValue || currentBlock[t].StartTime != LastRenderTime[t]) { renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock); continue; } } #endregion #region 6. 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) { Renderers[t]?.Update(wallClock); } #endregion } catch (ThreadAbortException) { /* swallow */ } catch { if (!IsDisposed) { throw; } } finally { // Always exit notifying the cycle is done. BlockRenderingCycle.Complete(); isRunningRenderingCycle = false; } #endregion }, this, // the state argument passed on to the ticker 0, Convert.ToInt32(Constants.Interval.HighPriority.TotalMilliseconds)); }
public async Task CloseAsync() { Container?.Log(MediaLogMessageType.Debug, $"{nameof(CloseAsync)}: Entered"); Clock.Pause(); IsTaskCancellationPending = true; // Wait for cycles to complete. await Task.Run(() => { while (!BlockRenderingCycle.Wait(1)) { } while (!FrameDecodingCycle.Wait(1)) { } while (!PacketReadingCycle.Wait(1)) { } }); BlockRenderingTask?.Join(); FrameDecodingTask?.Join(); PacketReadingTask?.Join(); BlockRenderingTask = null; FrameDecodingTask = null; PacketReadingTask = null; foreach (var renderer in Renderers.Values) { renderer.Close(); } Renderers.Clear(); // Reset the clock Clock.Reset(); Container?.Log(MediaLogMessageType.Debug, $"{nameof(CloseAsync)}: Completed"); // Dispose the container if (Container != null) { Container.Dispose(); Container = null; } // Dispose the Blocks for all components foreach (var kvp in Blocks) { kvp.Value.Dispose(); } Blocks.Clear(); // Dispose the Frames for all components foreach (var kvp in Frames) { kvp.Value.Dispose(); } Frames.Clear(); // Clear the render times LastRenderTime.Clear(); // Update notification properties UpdateMediaProperties(); MediaState = MediaState.Close; }