/// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> /// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> private void Dispose(bool alsoManaged) { if (IsDisposed) { return; } if (alsoManaged) { // free managed resources Commands.Close(); if (Container != null) { Container.Dispose(); Container = null; } if (UIPropertyUpdateTimer != null) { UIPropertyUpdateTimer.Stop(); UIPropertyUpdateTimer.IsEnabled = false; UIPropertyUpdateTimer = null; } PacketReadingCycle.Dispose(); FrameDecodingCycle.Dispose(); BlockRenderingCycle.Dispose(); SeekingDone.Dispose(); DelayLock.Dispose(); } IsDisposed = true; }
/// <summary> /// Releases unmanaged and - optionally - managed resources. /// Please not that this call is non-blocking/asynchronous. /// </summary> /// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> private void Dispose(bool alsoManaged) { if (IsDisposed) { return; } // Run the close command immediately if (BeginSynchronousCommand() == false) { return; } // Dispose the wait handle: No more command accepted from this point forward. SynchronousCommandDone.Dispose(); try { var closeCommand = new CloseCommand(Commands); closeCommand.RunSynchronously(); } catch { throw; } finally { IsDisposed = true; } // Dispose the container Container?.Dispose(); Container = null; // Dispose the RTC Clock.Dispose(); // Dispose the Wait Event objects as they are // backed by unmanaged code PacketReadingCycle.Dispose(); FrameDecodingCycle.Dispose(); BlockRenderingCycle.Dispose(); SeekingDone.Dispose(); MediaChangingDone.Dispose(); }
/// <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> /// Initializes the media block buffers and /// starts packet reader, frame decoder, and block rendering workers. /// </summary> internal void StartWorkers() { // Initialize the block buffers foreach (var t in Container.Components.MediaTypes) { Blocks[t] = new MediaBlockBuffer(Constants.MaxBlocks[t], t); Renderers[t] = Platform.CreateRenderer(t, this); InvalidateRenderer(t); } // Create the renderer for the preloaded subs if (PreloadedSubtitles != null) { Renderers[PreloadedSubtitles.MediaType] = Platform.CreateRenderer(PreloadedSubtitles.MediaType, this); InvalidateRenderer(PreloadedSubtitles.MediaType); } Clock.SpeedRatio = Constants.Controller.DefaultSpeedRatio; Commands.IsStopWorkersPending = false; // Set the initial state of the task cycles. BlockRenderingCycle.Complete(); FrameDecodingCycle.Begin(); PacketReadingCycle.Begin(); // Create the thread runners PacketReadingTask = new Thread(RunPacketReadingWorker) { IsBackground = true, Name = nameof(PacketReadingTask), Priority = ThreadPriority.Normal }; FrameDecodingTask = new Thread(RunFrameDecodingWorker) { IsBackground = true, Name = nameof(FrameDecodingTask), Priority = ThreadPriority.AboveNormal }; // Fire up the threads PacketReadingTask.Start(); FrameDecodingTask.Start(); StartBlockRenderingWorker(); }
private void WaitForPackets(MediaComponent mediaComponent, int cycleCount = -1) { var cycleIndex = 0; while (mediaComponent.PacketBufferCount <= 0 && CanReadMorePackets && ShouldReadMorePackets) { PacketReadingCycle.Wait(Constants.Interval.LowPriority); if (cycleCount <= 0) { continue; } cycleIndex++; if (cycleCount >= cycleIndex) { break; } } }
/// <inheritdoc /> public void Dispose() { if (m_IsDisposed == true) { return; } m_IsDisposed.Value = true; // Dispose of commands. This closes the // Media automatically and signals an exit // This also causes the Container to get disposed. Commands.Dispose(); // Reset the RTC ResetPosition(); // Dispose the Wait Event objects as they are // backed by unmanaged code PacketReadingCycle.Dispose(); FrameDecodingCycle.Dispose(); BlockRenderingCycle.Dispose(); BufferChangedEvent.Dispose(); }
/// <summary> /// Runs the read task which keeps a packet buffer as full as possible. /// It reports on DownloadProgress by enqueueing an update to the property /// in order to avoid any kind of disruption to this thread caused by the UI thread. /// </summary> internal void RunPacketReadingWorker() { #region Worker State Setup // The delay provider prevents 100% core usage var delay = new DelayProvider(); // Holds the packet count for each read cycle var packetsRead = new MediaTypeDictionary <int>(); // State variables for media types var t = MediaType.None; // Store Container in local variable to prevent NullReferenceException // when dispose occurs sametime with read cycle var mediaContainer = Container; #endregion #region Worker Loop try { // Worker logic begins here while (IsTaskCancellationPending == false) { // Wait for seeking or changing to be done. MediaChangingDone.Wait(); SeekingDone.Wait(); // Enter a packet reading cycle PacketReadingCycle.Begin(); // Initialize Packets read to 0 for each component and state variables foreach (var k in mediaContainer.Components.MediaTypes) { packetsRead[k] = 0; } // Start to perform the read loop // NOTE: Disrupting the packet reader causes errors in UPD streams. Disrupt as little as possible while (CanReadMorePackets && ShouldReadMorePackets && IsTaskCancellationPending == false) { // Perform a packet read. t will hold the packet type. try { t = mediaContainer.Read(); } catch (MediaContainerException) { continue; } // Discard packets that we don't need (i.e. MediaType == None) if (mediaContainer.Components.MediaTypes.HasMediaType(t) == false) { continue; } // Update the packet count for the components packetsRead[t] += 1; // Ensure we have read at least some packets from main and auxiliary streams. if (packetsRead.FundamentalsGreaterThan(0)) { break; } } // finish the reading cycle. PacketReadingCycle.Complete(); // Don't evaluate a pause condition if we are seeking if (SeekingDone.IsInProgress) { continue; } // Wait some if we have a full packet buffer or we are unable to read more packets (i.e. EOF). if (ShouldReadMorePackets == false || CanReadMorePackets == false || packetsRead.GetSum() <= 0) { delay.WaitOne(); } } } catch (ThreadAbortException) { /* swallow */ } catch { if (!IsDisposed) { throw; } } finally { // Always exit notifying the reading cycle is done. PacketReadingCycle.Complete(); delay.Dispose(); } #endregion }
/// <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.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) { InvalidateRenderer(t); } // 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 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)} alredy 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.Main.MediaType; 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) { 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 || 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 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 { throw; } finally { // Always exit notifying the cycle is done. BlockRenderingCycle.Complete(); // Notify position changes continuously on the state object // only if we are not currently seeking if (State.IsSeeking == false) { State.UpdatePosition(wallClock); } } #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 (HasBlockRenderingWorkerExited != null) { return; } HasBlockRenderingWorkerExited = new ManualResetEvent(false); // Synchronized access to parts of the run cycle var isRunningRenderingCycle = false; // Holds the main media type var main = Container.Components.Main.MediaType; // Holds the auxiliary media types var auxs = Container.Components.MediaTypes.ExcludeMediaType(main); // Holds all components var all = Container.Components.MediaTypes.DeepCopy(); // 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.WaitOne(); // wait for main component blocks or EOF or cancellation pending while (CanReadMoreFramesOf(main) && Blocks[main].Count <= 0) { FrameDecodingCycle.WaitOne(); } // Set the initial clock position // TODO: maybe update media start time offset to this Minimum, initial Start Time intead of relying on contained meta? Clock.Update(Blocks[main].RangeStartTime); // .GetMinStartTime() 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 || HasBlockRenderingWorkerExited.IsSet() || IsDisposed) { HasBlockRenderingWorkerExited.Set(); return; } #endregion #region Run the Rendering Cycle // Updatete Status Properties State.UpdateBufferingProperties(); // Don't run the cycle if it's already running if (isRunningRenderingCycle) { // TODO: Maybe Log a frame skip here? 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.Reset(); #endregion #region 2. Handle Block Rendering // Wait for the seek op to finish before we capture blocks if (HasDecoderSeeked) { SeekingDone.WaitOne(); } // capture the wall clock for this cycle wallClock = WallClock; // Capture the blocks to render foreach (var t in all) { 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) if (LastRenderTime[t] == TimeSpan.MinValue) { renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock); continue; } // Render because we simply have not rendered if (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.Set(); isRunningRenderingCycle = false; } #endregion }, this, // the state argument passed on to the ticker 0, (int)Constants.Interval.HighPriority.TotalMilliseconds); }
/// <summary> /// Continuously converts frmes and places them on the corresponding /// block buffer. This task is responsible for keeping track of the clock /// and calling the render methods appropriate for the current clock position. /// </summary> internal void RunBlockRenderingWorker() { try { #region 0. Initialize Running State // Holds the main media type var main = Container.Components.Main.MediaType; // Holds the auxiliary media types var auxs = Container.Components.MediaTypes.Where(t => t != main).ToArray(); // Holds all components var all = Container.Components.MediaTypes.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 = 0; // reset render times for all components foreach (var t in all) { LastRenderTime[t] = TimeSpan.MinValue; } // Ensure the other workers are running PacketReadingCycle?.WaitOne(); FrameDecodingCycle?.WaitOne(); // Set the initial clock position Clock.Position = Blocks[main].RangeStartTime; var wallClock = Clock.Position; // Wait for renderers to be ready foreach (var t in all) { Renderers[t]?.WaitForReadyState(); } #endregion while (IsTaskCancellationPending == false) { #region 1. Control and Capture // Reset the rendered count to 0 renderedBlockCount = 0; // Capture current clock position for the rest of this cycle BlockRenderingCycle?.Reset(); // capture the wall clock for this cycle wallClock = Clock.Position; #endregion #region 2. Handle Block Rendering // Capture the blocks to render foreach (var t in all) { currentBlock[t] = HasDecoderSeeked ? null : 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) if (LastRenderTime[t] == TimeSpan.MinValue) { renderedBlockCount += SendBlockToRenderer(currentBlock[t], wallClock); continue; } // Render because we simply have not rendered if (currentBlock[t].StartTime != LastRenderTime[t]) { renderedBlockCount += SendBlockToRenderer(currentBlock[t], wallClock); continue; } } #endregion #region 6. Finalize the Rendering Cycle // Signal the rendering cycle was set. BlockRenderingCycle?.Set(); // 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); } // Delay the thread for a bit if we have no more stuff to process if (IsSeeking == false && renderedBlockCount <= 0 && Commands.PendingCount <= 0) { Task.Delay(1).GetAwaiter().GetResult(); } #endregion } } catch (ThreadAbortException) { } finally { // Always exit notifying the cycle is done. BlockRenderingCycle?.Set(); } }
/// <summary> /// Runs the read task which keeps a packet buffer as full as possible. /// It reports on DownloadProgress by enqueueing an update to the property /// in order to avoid any kind of disruption to this thread caused by the UI thread. /// </summary> internal void RunPacketReadingWorker() { var delay = TimeSpan.FromMilliseconds(10); var needsMorePackets = false; IsSyncBuffering = false; try { // Worker logic begins here while (Commands.IsStopWorkersPending == false) { // Determine what to do on a priority command if (Commands.IsExecutingDirectCommand) { if (Commands.IsClosing) { break; } if (Commands.IsChanging) { Commands.WaitForDirectCommand(); } } // Wait for seeking or changing to be done. Commands.WaitForActiveSeekCommand(); // Enter a packet reading cycle PacketReadingCycle.Begin(); // Perform a packet read. t will hold the packet type. if (ShouldWorkerReadPackets) { try { Container.Read(); } catch (MediaContainerException) { break; } } else { // Give it a break until there are packet changes // this prevent pegging the cpu core BufferChangedEvent.Begin(); while (IsWorkerInterruptRequested == false) { needsMorePackets = ShouldWorkerReadPackets; // We now need more packets, we need to stop waiting if (needsMorePackets) { break; } // we are sync-buffering but we don't need more packets if (IsSyncBuffering && needsMorePackets == false) { break; } // We detected a change in buffered packets if (BufferChangedEvent.Wait(delay)) { break; } } } // No more sync-buffering if we have enough data if (CanExitSyncBuffering) { IsSyncBuffering = false; } // finish the reading cycle. PacketReadingCycle.Complete(); } } catch { throw; } finally { // Always exit notifying the reading cycle is done. PacketReadingCycle.Complete(); IsSyncBuffering = false; } }
/// <summary> /// Runs the read task which keeps a packet buffer as full as possible. /// It reports on DownloadProgress by enqueueing an update to the property /// in order to avoid any kind of disruption to this thread caused by the UI thread. /// </summary> internal void RunPacketReadingWorker() { try { // Holds the packet count for each read cycle var packetsRead = new MediaTypeDictionary <int>(); // State variables for media types var t = MediaType.None; // Store Container in local variable to prevent NullReferenceException // when dispose occurs sametime with read cycle var mediaContainer = Container; var main = mediaContainer.Components.Main.MediaType; var auxs = mediaContainer.Components.MediaTypes.Where(c => c != main && (c == MediaType.Audio || c == MediaType.Video)).ToArray(); var all = auxs.Union(new[] { main }).ToArray(); // State variables for bytes read (give-up condition) var startBytesRead = 0UL; var currentBytesRead = 0UL; // Worker logic begins here while (IsTaskCancellationPending == false) { // Enter a read cycle SeekingDone?.WaitOne(); // Enter a packet reading cycle PacketReadingCycle?.Reset(); if (CanReadMorePackets && mediaContainer.Components.PacketBufferLength < DownloadCacheLength) { // Initialize Packets read to 0 for each component and state variables foreach (var k in mediaContainer.Components.MediaTypes) { packetsRead[k] = 0; } startBytesRead = mediaContainer.Components.TotalBytesRead; currentBytesRead = 0UL; // Start to perform the read loop while (CanReadMorePackets) { // Perform a packet read. t will hold the packet type. t = mediaContainer.Read(); // Discard packets that we don't need (i.e. MediaType == None) if (mediaContainer.Components.MediaTypes.Contains(t) == false) { continue; } // Update the packet count for the components packetsRead[t] += 1; // Ensure we have read at least some packets from main and auxiliary streams. if (packetsRead.Where(k => all.Contains(k.Key)).All(c => c.Value > 0)) { break; } // The give-up condition is that in spite of efforts to read at least one of each, // we could not find the required packet types. currentBytesRead = mediaContainer.Components.TotalBytesRead - startBytesRead; if (currentBytesRead > (ulong)DownloadCacheLength) { break; } } } // finish the reading cycle. PacketReadingCycle?.Set(); // Wait some if we have a full packet buffer or we are unable to read more packets (i.e. EOF). if (mediaContainer.Components.PacketBufferLength >= DownloadCacheLength || CanReadMorePackets == false || currentBytesRead <= 0) { Task.Delay(1).GetAwaiter().GetResult(); } } } catch (ThreadAbortException) { } finally { // Always exit notifying the reading cycle is done. PacketReadingCycle?.Set(); } }
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; }
/// <summary> /// Opens the specified media Asynchronously /// </summary> /// <param name="uri">The URI.</param> /// <returns></returns> private async Task OpenAsync(Uri uri) { try { await Task.Run(() => { var mediaUrl = uri.IsFile ? uri.LocalPath : uri.ToString(); Container = new MediaContainer(mediaUrl); RaiseMediaOpeningEvent(); Container.Log(MediaLogMessageType.Debug, $"{nameof(OpenAsync)}: Entered"); Container.Initialize(); }); foreach (var t in Container.Components.MediaTypes) { Blocks[t] = new MediaBlockBuffer(MaxBlocks[t], t); Frames[t] = new MediaFrameQueue(); LastRenderTime[t] = TimeSpan.MinValue; Renderers[t] = CreateRenderer(t); } IsTaskCancellationPending = false; BlockRenderingCycle.Set(); FrameDecodingCycle.Set(); PacketReadingCycle.Set(); PacketReadingTask = new Thread(RunPacketReadingWorker) { IsBackground = true }; FrameDecodingTask = new Thread(RunFrameDecodingWorker) { IsBackground = true }; BlockRenderingTask = new Thread(RunBlockRenderingWorker) { IsBackground = true }; PacketReadingTask.Start(); FrameDecodingTask.Start(); BlockRenderingTask.Start(); RaiseMediaOpenedEvent(); if (LoadedBehavior == MediaState.Play) { Play(); } } catch (Exception ex) { RaiseMediaFailedEvent(ex); } finally { UpdateMediaProperties(); Container.Log(MediaLogMessageType.Debug, $"{nameof(OpenAsync)}: Completed"); } }
/// <summary> /// Runs the read task which keeps a packet buffer as full as possible. /// It reports on DownloadProgress by enqueueing an update to the property /// in order to avoid any kind of disruption to this thread caused by the UI thread. /// </summary> internal void RunPacketReadingWorker() { #region Worker State Setup // The delay provider prevents 100% core usage var delay = new DelayProvider(); // Holds the packet count for each read cycle var packetsRead = new MediaTypeDictionary <int>(); // State variables for media types var t = MediaType.None; // Signal the start of a buffering operation State.SignalBufferingStarted(); #endregion #region Worker Loop try { // Worker logic begins here while (Commands.IsStopWorkersPending == false) { // Determine what to do on a priority command if (Commands.IsExecutingDirectCommand) { if (Commands.IsClosing) { break; } if (Commands.IsChanging) { Commands.WaitForDirectCommand(); } } // Wait for seeking or changing to be done. Commands.WaitForActiveSeekCommand(); // Enter a packet reading cycle PacketReadingCycle.Begin(); // Initialize Packets read to 0 for each component and state variables foreach (var k in Container.Components.MediaTypes) { packetsRead[k] = 0; } // Start to perform the read loop // NOTE: Disrupting the packet reader causes errors in UPD streams. Disrupt as little as possible while (ShouldReadMorePackets && CanReadMorePackets && Commands.IsActivelySeeking == false) { // Perform a packet read. t will hold the packet type. try { t = Container.Read(); } catch (MediaContainerException) { continue; } // Discard packets that we don't need (i.e. MediaType == None) if (Container.Components.MediaTypes.HasMediaType(t) == false) { continue; } // Update the packet count for the components packetsRead[t] += 1; // Ensure we have read at least some packets from main and auxiliary streams. if (packetsRead.ContainsMoreThan(0)) { break; } } // finish the reading cycle. PacketReadingCycle.Complete(); // Don't evaluate a pause/delay condition if we are seeking if (Commands.IsActivelySeeking) { continue; } // Wait some if we have a full packet buffer or we are unable to read more packets (i.e. EOF). if (ShouldReadMorePackets == false || CanReadMorePackets == false || packetsRead.GetSum() <= 0) { delay.WaitOne(); } } } catch { throw; } finally { // Always exit notifying the reading cycle is done. PacketReadingCycle.Complete(); delay.Dispose(); } #endregion }
/// <summary> /// Continually decodes the available packet buffer to have as /// many frames as possible in each frame queue and /// up to the MaxFrames on each component /// </summary> internal void RunFrameDecodingWorker() { try { // State variables var decodedFrameCount = 0; var wallClock = TimeSpan.Zero; var rangePercent = 0d; var isInRange = false; // Holds the main media type var main = Container.Components.Main.MediaType; // Holds the auxiliary media types var auxs = Container.Components.MediaTypes.Where(x => x != main).ToArray(); // Holds all components var all = Container.Components.MediaTypes.ToArray(); var isBuffering = false; var resumeClock = false; var hasPendingSeeks = false; MediaComponent comp = null; MediaBlockBuffer blocks = null; while (IsTaskCancellationPending == false) { #region 1. Setup the Decoding Cycle // Singal a Seek starting operation hasPendingSeeks = Commands.PendingCountOf(MediaCommandType.Seek) > 0; if (IsSeeking == false && hasPendingSeeks) { IsSeeking = true; RaiseSeekingStartedEvent(); } // Execute the following command at the beginning of the cycle Commands.ProcessNext(); // Signal a Seek ending operation hasPendingSeeks = Commands.PendingCountOf(MediaCommandType.Seek) > 0; if (IsSeeking == true && hasPendingSeeks == false) { SnapVideoPosition(Clock.Position); IsSeeking = false; // Call the seek method on all renderers foreach (var kvp in Renderers) { kvp.Value.Seek(); } RaiseSeekingEndedEvent(); } // Wait for a seek operation to complete (if any) // and initiate a frame decoding cycle. SeekingDone?.WaitOne(); // Initiate the frame docding cycle FrameDecodingCycle?.Reset(); // Set initial state wallClock = Clock.Position; decodedFrameCount = 0; #endregion #region 2. Main Component Decoding // Capture component and blocks for easier readability comp = Container.Components[main]; blocks = Blocks[main]; // Handle the main component decoding; Start by checking we have some packets if (comp.PacketBufferCount > 0) { // Detect if we are in range for the main component isInRange = blocks.IsInRange(wallClock); if (isInRange == false) { // Signal the start of a sync-buffering scenario HasDecoderSeeked = true; isBuffering = true; resumeClock = Clock.IsRunning; Clock.Pause(); Logger.Log(MediaLogMessageType.Debug, $"SYNC-BUFFER: Started."); // Read some frames and try to get a valid range do { // Try to get more packets by waiting for read cycles. if (CanReadMorePackets && comp.PacketBufferCount <= 0) { PacketReadingCycle?.WaitOne(); } // Decode some frames and check if we are in reange now decodedFrameCount += AddBlocks(main); isInRange = blocks.IsInRange(wallClock); // Break the cycle if we are in range if (isInRange || CanReadMorePackets == false) { break; } }while (decodedFrameCount <= 0 && blocks.IsFull == false); // Unfortunately at this point we will need to adjust the clock after creating the frames. // to ensure tha mian component is within the clock range if the decoded // frames are not with range. This is normal while buffering though. if (isInRange == false) { // Update the wall clock to the most appropriate available block. if (blocks.Count > 0) { wallClock = blocks[wallClock].StartTime; } else { resumeClock = false; // Hard stop the clock. } // Update the clock to what the main component range mandates Clock.Position = wallClock; // Call seek to invalidate renderer Renderers[main].Seek(); } } else { // Check if we need more blocks for the current components rangePercent = blocks.GetRangePercent(wallClock); // Read as many blocks as we possibly can while (comp.PacketBufferCount > 0 && ((rangePercent > 0.75d && blocks.IsFull) || blocks.IsFull == false)) { decodedFrameCount += AddBlocks(main); rangePercent = blocks.GetRangePercent(wallClock); } } } #endregion #region 3. Auxiliary Component Decoding foreach (var t in auxs) { if (IsSeeking) { continue; } // Capture the current block buffer and component // for easier readability comp = Container.Components[t]; blocks = Blocks[t]; isInRange = blocks.IsInRange(wallClock); // Invalidate the renderer if we don't have the block. if (isInRange == false) { Renderers[t].Seek(); } // wait for component to get there if we only have furutre blocks // in auxiliary component. if (blocks.Count > 0 && blocks.RangeStartTime > wallClock) { continue; } // Try to catch up with the wall clock while (blocks.Count == 0 || blocks.RangeEndTime <= wallClock) { // Wait for packets if we don't have enough packets if (CanReadMorePackets && comp.PacketBufferCount <= 0) { PacketReadingCycle?.WaitOne(); } if (comp.PacketBufferCount <= 0) { break; } else { decodedFrameCount += AddBlocks(t); } } isInRange = blocks.IsInRange(wallClock); // Move to the next component if we don't meet a regular conditions if (isInRange == false || isBuffering || comp.PacketBufferCount <= 0) { continue; } // Read as much as we can for this cycle. while (comp.PacketBufferCount > 0) { rangePercent = blocks.GetRangePercent(wallClock); if (blocks.IsFull == false || (blocks.IsFull && rangePercent > 0.75d && rangePercent < 1d)) { decodedFrameCount += AddBlocks(t); } else { break; } } } #endregion #region 4. Detect End of Media // Detect end of block rendering if (isBuffering == false && IsSeeking == false && CanReadMoreFramesOf(main) == false && Blocks[main].IndexOf(wallClock) == Blocks[main].Count - 1) { if (HasMediaEnded == false) { // Rendered all and nothing else to read Clock.Pause(); Clock.Position = NaturalDuration.HasTimeSpan ? NaturalDuration.TimeSpan : Blocks[main].RangeEndTime; wallClock = Clock.Position; HasMediaEnded = true; MediaState = MediaState.Pause; RaiseMediaEndedEvent(); } } else { HasMediaEnded = false; } #endregion #region 6. Finish the Cycle // complete buffering notifications if (isBuffering) { // Reset the buffering flag isBuffering = false; // Resume the clock if it was playing if (resumeClock) { Clock.Play(); } // log some message Logger.Log( MediaLogMessageType.Debug, $"SYNC-BUFFER: Finished. Clock set to {wallClock.Format()}"); } // Complete the frame decoding cycle FrameDecodingCycle?.Set(); // After a seek operation, always reset the has seeked flag. HasDecoderSeeked = false; // Give it a break if there was nothing to decode. // We probably need to wait for some more input if (decodedFrameCount <= 0 && Commands.PendingCount <= 0) { Task.Delay(1).GetAwaiter().GetResult(); } #endregion } } catch (ThreadAbortException) { } finally { // Always exit notifying the cycle is done. FrameDecodingCycle?.Set(); } }
/// <summary> /// Continually decodes the available packet buffer to have as /// many frames as possible in each frame queue and /// up to the MaxFrames on each component /// </summary> internal void RunFrameDecodingWorker() { #region Worker State Setup // The delay provider prevents 100% core usage var delay = new DelayProvider(); // State variables var decodedFrameCount = 0; var wallClock = TimeSpan.Zero; var rangePercent = 0d; var isInRange = false; var playAfterSeek = false; // Holds the main media type var main = Container.Components.Main.MediaType; // Holds the auxiliary media types var auxs = Container.Components.MediaTypes.ExcludeMediaType(main); // Holds all components var all = Container.Components.MediaTypes.DeepCopy(); var isBuffering = false; var resumeClock = false; var hasPendingSeeks = false; MediaComponent comp = null; MediaBlockBuffer blocks = null; #endregion #region Worker Loop try { while (IsTaskCancellationPending == false) { #region 1. Setup the Decoding Cycle // Singal a Seek starting operation hasPendingSeeks = Commands.PendingCountOf(MediaCommandType.Seek) > 0; if (State.IsSeeking == false && hasPendingSeeks) { playAfterSeek = State.IsPlaying; State.IsSeeking = true; SendOnSeekingStarted(); } // Execute the following command at the beginning of the cycle Commands.ProcessNext(); // Wait for a seek operation to complete (if any) // and initiate a frame decoding cycle. SeekingDone.Wait(); // Set initial state wallClock = WallClock; decodedFrameCount = 0; // Signal a Seek ending operation // TOD: Maybe this should go on the block rendering worker? hasPendingSeeks = Commands.PendingCountOf(MediaCommandType.Seek) > 0; if (State.IsSeeking && hasPendingSeeks == false) { // Detect a end of seek cycle and update to the final position wallClock = SnapToFramePosition(WallClock); Clock.Update(wallClock); State.UpdatePosition(wallClock); // Call the seek method on all renderers foreach (var kvp in Renderers) { LastRenderTime[kvp.Key] = TimeSpan.MinValue; kvp.Value.Seek(); } SendOnSeekingEnded(); State.IsSeeking = false; if (playAfterSeek) { Clock.Play(); State.UpdateMediaState(PlaybackStatus.Play); } else { State.UpdateMediaState(PlaybackStatus.Pause); } } else if (State.IsSeeking == false) { // Notify position changes State.UpdatePosition(wallClock); } // Initiate the frame docding cycle FrameDecodingCycle.Begin(); #endregion #region 2. Main Component Decoding // Capture component and blocks for easier readability comp = Container.Components[main]; blocks = Blocks[main]; // Handle the main component decoding; Start by checking we have some packets while (comp.PacketBufferCount <= 0 && CanReadMorePackets && ShouldReadMorePackets) { PacketReadingCycle.Wait(Constants.Interval.LowPriority); } if (comp.PacketBufferCount > 0) { // Detect if we are in range for the main component isInRange = blocks.IsInRange(wallClock); if (isInRange == false) { // Signal the start of a sync-buffering scenario HasDecoderSeeked = true; isBuffering = true; resumeClock = Clock.IsRunning; Clock.Pause(); Log(MediaLogMessageType.Debug, $"SYNC-BUFFER: Started."); // Read some frames and try to get a valid range do { // Try to get more packets by waiting for read cycles. if (CanReadMorePackets && comp.PacketBufferCount <= 0) { PacketReadingCycle.Wait(); } // Decode some frames and check if we are in reange now decodedFrameCount += AddBlocks(main); isInRange = blocks.IsInRange(wallClock); // Break the cycle if we are in range if (isInRange || CanReadMorePackets == false) { break; } }while (decodedFrameCount <= 0 && blocks.IsFull == false); // Unfortunately at this point we will need to adjust the clock after creating the frames. // to ensure tha mian component is within the clock range if the decoded // frames are not with range. This is normal while buffering though. if (isInRange == false) { // Update the wall clock to the most appropriate available block. if (blocks.Count > 0) { wallClock = blocks[wallClock].StartTime; } else { resumeClock = false; // Hard stop the clock. } // Update the clock to what the main component range mandates Clock.Update(wallClock); // Call seek to invalidate renderer LastRenderTime[main] = TimeSpan.MinValue; Renderers[main].Seek(); // Try to recover the regular loop isInRange = true; while (CanReadMorePackets && comp.PacketBufferCount <= 0) { PacketReadingCycle.Wait(); } } } if (isInRange) { // Check if we need more blocks for the current components rangePercent = blocks.GetRangePercent(wallClock); // Read as much as we can for this cycle. while (comp.PacketBufferCount > 0) { rangePercent = blocks.GetRangePercent(wallClock); if (blocks.IsFull == false || (blocks.IsFull && rangePercent > 0.75d && rangePercent < 1d)) { decodedFrameCount += AddBlocks(main); } else { break; } } } } #endregion #region 3. Auxiliary Component Decoding foreach (var t in auxs) { if (State.IsSeeking) { continue; } // Capture the current block buffer and component // for easier readability comp = Container.Components[t]; blocks = Blocks[t]; isInRange = blocks.IsInRange(wallClock); // Invalidate the renderer if we don't have the block. if (isInRange == false) { LastRenderTime[t] = TimeSpan.MinValue; Renderers[t].Seek(); } // wait for component to get there if we only have furutre blocks // in auxiliary component. if (blocks.Count > 0 && blocks.RangeStartTime > wallClock) { continue; } // Try to catch up with the wall clock while (blocks.Count == 0 || blocks.RangeEndTime <= wallClock) { // Wait for packets if we don't have enough packets if (CanReadMorePackets && comp.PacketBufferCount <= 0) { PacketReadingCycle.Wait(); } if (comp.PacketBufferCount <= 0) { break; } else { decodedFrameCount += AddBlocks(t); } } isInRange = blocks.IsInRange(wallClock); // Move to the next component if we don't meet a regular conditions if (isInRange == false || isBuffering || comp.PacketBufferCount <= 0) { continue; } // Read as much as we can for this cycle. while (comp.PacketBufferCount > 0) { rangePercent = blocks.GetRangePercent(wallClock); if (blocks.IsFull == false || (blocks.IsFull && rangePercent > 0.75d && rangePercent < 1d)) { decodedFrameCount += AddBlocks(t); } else { break; } } } #endregion #region 4. Detect End of Media // Detect end of block rendering // TODO: Maybe this detection should be performed on the BlockRendering worker? if (isBuffering == false && State.IsSeeking == false && CanReadMoreFramesOf(main) == false && Blocks[main].IndexOf(wallClock) == Blocks[main].Count - 1) { if (State.HasMediaEnded == false) { // Rendered all and nothing else to read Clock.Pause(); if (State.NaturalDuration != null && State.NaturalDuration != TimeSpan.MinValue) { wallClock = State.NaturalDuration.Value; } else { wallClock = Blocks[main].RangeEndTime; } Clock.Update(wallClock); State.HasMediaEnded = true; State.UpdateMediaState(PlaybackStatus.Stop, wallClock); SendOnMediaEnded(); } } else { State.HasMediaEnded = false; } #endregion #region 6. Finish the Cycle // complete buffering notifications if (isBuffering) { // Reset the buffering flag isBuffering = false; // Resume the clock if it was playing if (resumeClock) { Clock.Play(); } // log some message Log( MediaLogMessageType.Debug, $"SYNC-BUFFER: Finished. Clock set to {wallClock.Format()}"); } // Complete the frame decoding cycle FrameDecodingCycle.Complete(); // After a seek operation, always reset the has seeked flag. HasDecoderSeeked = false; // If not already set, guess the 1-second buffer length State.GuessBufferingProperties(); // Give it a break if there was nothing to decode. // We probably need to wait for some more input if (decodedFrameCount <= 0 && Commands.PendingCount <= 0) { delay.WaitOne(); } #endregion } } catch (ThreadAbortException) { /* swallow */ } catch { if (!IsDisposed) { throw; } } finally { // Always exit notifying the cycle is done. FrameDecodingCycle.Complete(); delay.Dispose(); } #endregion }
/// <summary> /// Starts the block rendering worker. /// </summary> private void StartBlockRenderingWorker() { if (HasBlockRenderingWorkerExited != null) { return; } HasBlockRenderingWorkerExited = new ManualResetEvent(false); // Synchronized access to parts of the run cycle var isRunningPropertyUpdates = false; var isRunningRenderingCycle = false; // Holds the main media type var main = Container.Components.Main.MediaType; // Holds the auxiliary media types var auxs = Container.Components.MediaTypes.ExcludeMediaType(main); // Holds all components var all = Container.Components.MediaTypes.DeepCopy(); // 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 the other workers are running PacketReadingCycle.WaitOne(); FrameDecodingCycle.WaitOne(); // Set the initial clock position Clock.Position = Blocks[main].RangeStartTime; var wallClock = Clock.Position; // Wait for renderers to be ready foreach (var t in all) { Renderers[t]?.WaitForReadyState(); } // The Property update timer is responsible for timely updates to properties outside of the worker threads BlockRenderingWorker = new Timer((s) => { #region Detect a Timer Stop if (IsTaskCancellationPending || HasBlockRenderingWorkerExited.IsSet() || m_IsDisposing.Value) { HasBlockRenderingWorkerExited.Set(); return; } #endregion #region Run the property Updates if (isRunningPropertyUpdates == false) { isRunningPropertyUpdates = true; try { UpdatePosition(IsOpen ? Clock?.Position ?? TimeSpan.Zero : TimeSpan.Zero); UpdateBufferingProperties(); } catch (Exception ex) { Log(MediaLogMessageType.Error, $"{nameof(BlockRenderingWorker)} callabck failed. {ex.GetType()}: {ex.Message}"); } finally { isRunningPropertyUpdates = false; } } #endregion #region Run the Rendering Cycle // Don't run the cycle if it's already running if (isRunningRenderingCycle) { // TODO: Log a frame skip 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.Reset(); // capture the wall clock for this cycle wallClock = Clock.Position; #endregion #region 2. Handle Block Rendering // Wait for the seek op to finish before we capture blocks if (HasDecoderSeeked) { SeekingDone.WaitOne(); } // Capture the blocks to render foreach (var t in all) { 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) if (LastRenderTime[t] == TimeSpan.MinValue) { renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock); continue; } // Render because we simply have not rendered if (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) { } catch { throw; } finally { // Always exit notifying the cycle is done. BlockRenderingCycle.Set(); isRunningRenderingCycle = false; } #endregion }, this, // the state argument passed on to the ticker 0, (int)Constants.Interval.HighPriority.TotalMilliseconds); }