/// <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 a Timer Stop if (IsTaskCancellationPending || BlockRenderingWorkerExit.IsCompleted || IsDisposed) { BlockRenderingWorkerExit?.Complete(); return; } #endregion #region Run the Rendering Cycle // Don't run the cycle if it's already running if (BlockRenderingCycle.IsInProgress) { Log(MediaLogMessageType.Trace, $"SKIP: {nameof(BlockRenderingWorker)} alredy in a cycle. {WallClock}"); return; } try { #region 1. Control and Capture // Wait for Media Changing MediaChangingDone.Wait(); // Wait for the seek op to finish before we capture blocks if (HasDecoderSeeked) { SeekingDone.Wait(); } // Signal the start of a block rendering cycle BlockRenderingCycle.Begin(); // Updatete Status Properties main = Container.Components.Main.MediaType; all = Renderers.Keys.ToArray(); State.UpdateBufferingProperties(); // 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); } } #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(); } #endregion }, this, // the state argument passed on to the ticker 0, Convert.ToInt32(Constants.Interval.HighPriority.TotalMilliseconds)); }