internal static int GetSum(this MediaTypeDictionary <int> all) { var result = default(int); foreach (var kvp in all) { result += kvp.Value; } return(result); }
internal static int GetBlockCount(this MediaTypeDictionary <MediaBlockBuffer> blocks) { var result = 0; foreach (var buffer in blocks.Values) { result += buffer.Count; } return(result); }
internal static TimeSpan GetMinStartTime(this MediaTypeDictionary <MediaBlockBuffer> blocks) { var minimum = TimeSpan.Zero; foreach (var buffer in blocks.Values) { if (buffer.RangeStartTime.Ticks < minimum.Ticks) { minimum = buffer.RangeStartTime; } } return(minimum); }
public Task CallSwagger(HttpContext httpContext, NetStitchServer server) { const string prefix = "NetStitch.Swagger.SwaggerUI."; var path = httpContext.Request.Path.Value.Trim('/'); if (path == "") { path = "index.html"; } var filePath = prefix + path.Replace("/", "."); if (MediaTypeDictionary.TryGetValue(filePath.Split('.').Last(), out string mediaType)) { if (path.EndsWith(options.JsonName)) { var builder = new SwaggerBuilder(options, httpContext); var bytes = builder.BuildSwagger(server); httpContext.Response.ContentType = "application/json"; httpContext.Response.StatusCode = HttpStatus.OK; httpContext.Response.Body.Write(bytes, 0, bytes.Length); return(Task.CompletedTask); } var myAssembly = typeof(NetStitchSwaggerMiddleware).GetTypeInfo().Assembly; using (var stream = myAssembly.GetManifestResourceStream(filePath)) { if (stream == null) { return(next(httpContext)); } httpContext.Response.ContentType = mediaType; httpContext.Response.StatusCode = HttpStatus.OK; var response = httpContext.Response.Body; stream.CopyTo(response); } } ; return(Task.CompletedTask); }
internal static bool ContainsMoreThan(this MediaTypeDictionary <int> all, int value) { var hasFundamentals = false; foreach (var kvp in all) { // Skip over non-fundamental types if (kvp.Key != MediaType.Audio && kvp.Key != MediaType.Video) { continue; } hasFundamentals = true; if (kvp.Value <= value) { return(false); } } return(hasFundamentals); }
/// <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 }
protected override void SetUp() { base.SetUp(); _repository = new MediaTypeDictionary<string>(); }
/// <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 }
/// <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> /// 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)); }
/// <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> /// 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> /// 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(); } }
/// <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 async void RunBlockRenderingWorker() { #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(); // Create and reset all the tracking variables var hasRendered = new MediaTypeDictionary <bool>(); var renderIndex = new MediaTypeDictionary <int>(); var renderBlock = new MediaTypeDictionary <MediaBlock>(); // reset all state variables for all components foreach (var t in all) { hasRendered[t] = false; renderIndex[t] = -1; renderBlock[t] = null; LastRenderTime[t] = TimeSpan.MinValue; } // Buffer some blocks and adjust the clock to the start position BufferBlocks(BufferCacheLength, false); Clock.Position = Blocks[main].RangeStartTime; var wallClock = Clock.Position; #endregion while (true) { #region 1. Control and Capture // Execute commands at the beginning of the cycle while (Commands.PendingCount > 0) { await Commands.ProcessNext(); } // Check if one of the commands has requested an exit if (IsTaskCancellationPending) { break; } // Capture current clock position for the rest of this cycle BlockRenderingCycle.Reset(); wallClock = Clock.Position; #endregion #region 2. Handle Main Component // Reset the hasRendered tracker hasRendered[main] = false; // Check for out-of sync issues (i.e. after seeking), being cautious about EOF/media ended scenarios // in which more blocks cannot be read. (The clock is on or beyond the Duration) if ((Blocks[main].Count <= 0 || Blocks[main].IsInRange(wallClock) == false) && CanReadMoreBlocksOf(main)) { BufferBlocks(BufferCacheLength, true); wallClock = Blocks[main].IsInRange(wallClock) ? wallClock : Blocks[main].RangeStartTime; Container.Logger?.Log(MediaLogMessageType.Warning, $"SYNC CLOCK: {Clock.Position.Format()} | TGT: {wallClock.Format()}"); Clock.Position = wallClock; LastRenderTime[main] = TimeSpan.MinValue; // a forced sync is basically a seek operation. foreach (var t in all) { Renderers[t].Seek(); } } // capture the render block based on its index if ((renderIndex[main] = Blocks[main].IndexOf(wallClock)) >= 0) { renderBlock[main] = Blocks[main][renderIndex[main]]; // render the frame if we have not rendered it if ((renderBlock[main].StartTime != LastRenderTime[main] || LastRenderTime[main] == TimeSpan.MinValue) && (IsPlaying == false || wallClock.Ticks >= renderBlock[main].StartTime.Ticks)) { // Record the render time LastRenderTime[main] = renderBlock[main].StartTime; // Send the block to the renderer RenderBlock(renderBlock[main], wallClock, renderIndex[main]); hasRendered[main] = true; } } #endregion #region 3. Handle Auxiliary Components // Render each of the Media Types if it is time to do so. foreach (var t in auxs) { hasRendered[t] = false; // Extract the render index renderIndex[t] = Blocks[t].IndexOf(wallClock); // If it's a secondary stream, try to catch up with the primary stream as quickly as possible // by skipping the queued blocks and adding new ones as quickly as possible. while (Blocks[t].RangeEndTime <= Blocks[main].RangeStartTime && renderIndex[t] >= Blocks[t].Count - 1 && CanReadMoreBlocksOf(t)) { if (AddNextBlock(t) == null) { break; } renderIndex[t] = Blocks[t].IndexOf(wallClock); LastRenderTime[t] = TimeSpan.MinValue; } // capture the latest renderindex renderIndex[t] = Blocks[t].IndexOf(wallClock); // Skip to next stream component if we have nothing left to do here :( if (renderIndex[t] < 0) { continue; } // Retrieve the render block renderBlock[t] = Blocks[t][renderIndex[t]]; // render the frame if we have not rendered if ((renderBlock[t].StartTime != LastRenderTime[t] || LastRenderTime[t] == TimeSpan.MinValue) && (IsPlaying == false || wallClock.Ticks >= renderBlock[t].StartTime.Ticks)) { LastRenderTime[t] = renderBlock[t].StartTime; RenderBlock(renderBlock[t], wallClock, renderIndex[t]); hasRendered[t] = true; } } #endregion #region 4. Keep Blocks Buffered foreach (var t in all) { if (hasRendered[t] == false) { continue; } // Add the next block if the conditions require us to do so: // If rendered, then we need to discard the oldest and add the newest // If the render index is greater than half, the capacity, add a new block while (Blocks[t].IsFull == false || renderIndex[t] + 1 > Blocks[t].Capacity / 2) { if (AddNextBlock(t) == null) { break; } renderIndex[t] = Blocks[t].IndexOf(wallClock); } hasRendered[t] = false; renderIndex[t] = Blocks[t].IndexOf(wallClock); } #endregion #region 5. Detect End of Media // Detect end of block rendering if (CanReadMoreBlocksOf(main) == false && renderIndex[main] == 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; HasMediaEnded = true; MediaState = MediaState.Pause; UpdatePosition(Clock.Position); RaiseMediaEndedEvent(); } } else { HasMediaEnded = false; } #endregion #region 6. Finalize the Rendering Cycle BlockRenderingCycle.Set(); // Pause for a bit if we have no more commands to process. if (Commands.PendingCount <= 0) { await Task.Delay(1); } #endregion } BlockRenderingCycle.Set(); }
/// <summary> /// Sets up timing and clocks. Call this method when media components change. /// </summary> public void Setup() { lock (SyncLock) { var options = MediaCore?.MediaOptions; var components = MediaCore?.Container?.Components; if (components == null || options == null) { MediaCore?.LogError(Aspects.Timing, "Unable to setup the timing controller. No components or options found."); Reset(); return; } // Save the current clocks so they can be recreated with the // same properties (position and speed ratio) var lastClocks = new MediaTypeDictionary <RealTimeClock>(); foreach (var kvp in Clocks) { lastClocks[kvp.Key] = kvp.Value; } try { if (options.IsTimeSyncDisabled) { if (!MediaCore.Container.IsLiveStream) { MediaCore.LogWarning(Aspects.Timing, $"Media options had {nameof(MediaOptions.IsTimeSyncDisabled)} set to true. This is not recommended for non-live streams."); } return; } if (!components.HasAudio || !components.HasVideo) { return; } var audioStartTime = GetComponentStartOffset(MediaType.Audio); var videoStartTime = GetComponentStartOffset(MediaType.Video); var startTimeDifference = TimeSpan.FromTicks(Math.Abs(audioStartTime.Ticks - videoStartTime.Ticks)); if (startTimeDifference > Constants.TimeSyncMaxOffset) { MediaCore.LogWarning(Aspects.Timing, $"{nameof(MediaOptions)}.{nameof(MediaOptions.IsTimeSyncDisabled)} has been ignored because the " + $"streams seem to have unrelated timing information. Time Difference: {startTimeDifference.Format()} s."); options.IsTimeSyncDisabled = true; } } finally { if (components.HasAudio && components.HasVideo) { Clocks[MediaType.Audio] = new RealTimeClock(); Clocks[MediaType.Video] = new RealTimeClock(); Offsets[MediaType.Audio] = GetComponentStartOffset(MediaType.Audio); Offsets[MediaType.Video] = GetComponentStartOffset(MediaType.Video); } else { Clocks[MediaType.Audio] = new RealTimeClock(); Clocks[MediaType.Video] = Clocks[MediaType.Audio]; Offsets[MediaType.Audio] = GetComponentStartOffset(components.HasAudio ? MediaType.Audio : MediaType.Video); Offsets[MediaType.Video] = Offsets[MediaType.Audio]; } // Subtitles will always be whatever the video data is. Clocks[MediaType.Subtitle] = Clocks[MediaType.Video]; Offsets[MediaType.Subtitle] = Offsets[MediaType.Video]; // Update from previous clocks to keep state foreach (var clock in lastClocks) { Clocks[clock.Key].SpeedRatio = clock.Value.SpeedRatio; Clocks[clock.Key].Update(clock.Value.Position); } // By default the continuous type is the audio component if it's a live stream var continuousType = components.HasAudio && !MediaCore.Container.IsStreamSeekable ? MediaType.Audio : components.SeekableMediaType; var discreteType = components.SeekableMediaType; HasDisconnectedClocks = options.IsTimeSyncDisabled && Clocks[MediaType.Audio] != Clocks[MediaType.Video]; ReferenceType = HasDisconnectedClocks ? continuousType : discreteType; // The default data is what the clock reference contains Clocks[MediaType.None] = Clocks[ReferenceType]; Offsets[MediaType.None] = Offsets[ReferenceType]; IsReady = true; MediaCore.State.ReportTimingStatus(); } } }
/// <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> /// <param name="control">The control.</param> /// <returns></returns> internal async void RunBlockRenderingWorker() { var main = Container.Components.Main.MediaType; // Create and reset all the tracking variables var hasRendered = new MediaTypeDictionary <bool>(); var renderIndex = new MediaTypeDictionary <int>(); var renderBlock = new MediaTypeDictionary <MediaBlock>(); foreach (var t in Container.Components.MediaTypes) { hasRendered[t] = false; renderIndex[t] = -1; renderBlock[t] = null; } // Buffer some blocks BufferBlocks(BufferCacheLength); Clock.Position = Blocks[main].RangeStartTime; var clockPosition = Clock.Position; while (true) { await Commands.ProcessNext(); if (IsTaskCancellationPending) { break; } // Capture current time and render index BlockRenderingCycle.Reset(); clockPosition = Clock.Position; renderIndex[main] = Blocks[main].IndexOf(clockPosition); // Check for out-of sync issues (i.e. after seeking), being cautious about EOF/media ended scenarios // in which more blocks cannot be read. (The clock is on or beyond the Duration) if (CanReadMoreBlocksOf(main) && (Blocks[main].IsInRange(clockPosition) == false || renderIndex[main] < 0)) { BufferBlocks(BufferCacheLength); Clock.Position = Blocks[main].RangeStartTime; Container.Log(MediaLogMessageType.Warning, $"SYNC CLK: {clockPosition.Debug()} | TGT: {Blocks[main].RangeStartTime.Debug()} | SET: {Clock.Position.Debug()}"); clockPosition = Clock.Position; renderIndex[main] = Blocks[main].IndexOf(clockPosition); } // Render each of the Media Types if it is time to do so. foreach (var t in Container.Components.MediaTypes) { var blocks = Blocks[t]; renderIndex[t] = blocks.IndexOf(clockPosition); // If it's a secondary stream, try to catch up with the primary stream as quickly as possible while (t != main && blocks.RangeEndTime <= Blocks[main].RangeStartTime && renderIndex[t] >= blocks.Count - 1 && CanReadMoreBlocksOf(t)) { if (AddNextBlock(t) == null) { break; } renderIndex[t] = blocks.IndexOf(clockPosition); LastRenderTime[t] = TimeSpan.MinValue; } // Skip to next stream component if we have nothing left to do here :( if ((renderIndex[t] = blocks.IndexOf(clockPosition)) < 0) { continue; } // Retrieve the render block renderBlock[t] = blocks[renderIndex[t]]; hasRendered[t] = false; // render the frame if we have not rendered if ((renderBlock[t].StartTime != LastRenderTime[t] || LastRenderTime[t] == TimeSpan.MinValue) && clockPosition.Ticks >= renderBlock[t].StartTime.Ticks) { // only render a secondary block if the primary block has been rendered first // otherwise, wait for the primary block to occur and skip rendering of secondary block. // TODO: still needs more logic for lip-sync quality sync :) var skewTime = TimeSpan.FromTicks(renderBlock[t].StartTime.Ticks - LastRenderTime[main].Ticks); if (t != main && IsPlaying && skewTime.TotalMilliseconds <= 0) { continue; } // Record the render time LastRenderTime[t] = renderBlock[t].StartTime; // Update the position; if (t == main) { UpdatePosition(clockPosition); } RenderBlock(renderBlock[t], clockPosition, renderIndex[t]); hasRendered[t] = true; } // Add the next block if the conditions require us to do so: // If rendered, then we need to discard the oldest and add the newest // If the render index is greater than half, the capacity, add a new block if (hasRendered[t]) { while (Blocks[t].IsFull == false || renderIndex[t] + 1 > Blocks[t].Capacity / 2) { if (AddNextBlock(t) == null) { break; } renderIndex[t] = blocks.IndexOf(clockPosition); } hasRendered[t] = false; renderIndex[t] = Blocks[t].IndexOf(clockPosition); } } // Detect end of block rendering if (CanReadMoreBlocksOf(main) == false && renderIndex[main] == 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; MediaState = MediaState.Pause; UpdatePosition(Clock.Position); HasMediaEnded = true; RaiseMediaEndedEvent(); } } else { HasMediaEnded = false; } BlockRenderingCycle.Set(); // Pause for a bit if we have no more commands to process. if (Commands.PendingCount <= 0) { await Task.Delay(1); } } BlockRenderingCycle.Set(); }
/// <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 }
/// <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(); } }
protected override void SetUp() { base.SetUp(); _repository = new MediaTypeDictionary <string>(); }
internal static MediaBlockBuffer Main(this MediaTypeDictionary <MediaBlockBuffer> blocks, MediaContainer container) => blocks[container.Components?.MainMediaType ?? MediaType.None];
/// <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); }