/// <summary> /// Reads all the blocks of the specified media type from the source url. /// </summary> /// <param name="mediaSource">The subtitles URL.</param> /// <param name="sourceType">Type of the source.</param> /// <param name="parent">The parent.</param> /// <returns>A buffer containing all the blocks.</returns> internal static MediaBlockBuffer LoadBlocks(string mediaSource, MediaType sourceType, ILoggingHandler parent) { if (string.IsNullOrWhiteSpace(mediaSource)) { throw new ArgumentNullException(nameof(mediaSource)); } using (var tempContainer = new MediaContainer(mediaSource, null, parent)) { tempContainer.Initialize(); // Skip reading and decoding unused blocks tempContainer.MediaOptions.IsAudioDisabled = sourceType != MediaType.Audio; tempContainer.MediaOptions.IsVideoDisabled = sourceType != MediaType.Video; tempContainer.MediaOptions.IsSubtitleDisabled = sourceType != MediaType.Subtitle; // Open the container tempContainer.Open(); if (tempContainer.Components.Main == null || tempContainer.Components.MainMediaType != sourceType) { throw new MediaContainerException($"Could not find a stream of type '{sourceType}' to load blocks from"); } // read all the packets and decode them var outputFrames = new List <MediaFrame>(1024 * 8); while (true) { tempContainer.Read(); var frames = tempContainer.Decode(); foreach (var frame in frames) { if (frame.MediaType != sourceType) { continue; } outputFrames.Add(frame); } if (frames.Count <= 0 && tempContainer.IsAtEndOfStream) { break; } } // Build the result var result = new MediaBlockBuffer(outputFrames.Count, sourceType); foreach (var frame in outputFrames) { result.Add(frame, tempContainer); } tempContainer.Close(); return(result); } }
/// <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(); }
/// <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> /// 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() { // TODO: Don't use State properties in workers as they are only for // TODO: Check the use of wall clock. Maybe it's be more consistent // to use a single atomic wallclock value per cycle. Check other workers as well. // state notification purposes. // State variables var wasSyncBuffering = false; var delay = new DelayProvider(); // The delay provider prevents 100% core usage var decodedFrameCount = 0; var rangePercent = 0d; var main = Container.Components.MainMediaType; // Holds the main media type var resumeSyncBufferingClock = false; MediaBlockBuffer blocks = null; try { while (Commands.IsStopWorkersPending == false) { #region Setup the Decoding Cycle // Determine what to do on a priority command if (Commands.IsExecutingDirectCommand) { if (Commands.IsClosing) { break; } if (Commands.IsChanging) { Commands.WaitForDirectCommand(); } } // Execute the following command at the beginning of the cycle if (IsSyncBuffering == false) { Commands.ExecuteNextQueuedCommand(); } // Signal a Seek starting operation and set the initial state FrameDecodingCycle.Begin(); // Update state properties -- this must be after processing commanmds as // a direct command might have changed the components main = Container.Components.MainMediaType; decodedFrameCount = 0; #endregion // The 2-part logic blocks detect a sync-buffering scenario // and then decodes the necessary frames. if (State.HasMediaEnded == false && IsWorkerInterruptRequested == false) { #region Sync-Buffering // Capture the blocks for easier readability blocks = Blocks[main]; // If we are not then we need to begin sync-buffering if (wasSyncBuffering == false && blocks.IsInRange(WallClock) == false) { // Signal the start of a sync-buffering scenario IsSyncBuffering = true; wasSyncBuffering = true; resumeSyncBufferingClock = Clock.IsRunning; Clock.Pause(); State.UpdateMediaState(PlaybackStatus.Manual); Log(MediaLogMessageType.Debug, $"SYNC-BUFFER: Started."); } #endregion #region Component Decoding // We need to add blocks if the wall clock is over 75% // for each of the components so that we have some buffer. foreach (var t in Container.Components.MediaTypes) { if (IsWorkerInterruptRequested) { break; } // Capture a reference to the blocks and the current Range Percent const double rangePercentThreshold = 0.75d; blocks = Blocks[t]; rangePercent = blocks.GetRangePercent(WallClock); // Read as much as we can for this cycle but always within range. while (blocks.IsFull == false || rangePercent > rangePercentThreshold) { // Stop decoding under sync-buffering conditions if (IsSyncBuffering && blocks.IsFull) { break; } if (IsWorkerInterruptRequested || AddNextBlock(t) == false) { break; } decodedFrameCount += 1; rangePercent = blocks.GetRangePercent(WallClock); // Determine break conditions to save CPU time if (IsSyncBuffering == false && rangePercent > 0 && rangePercent <= rangePercentThreshold && blocks.IsFull == false && blocks.CapacityPercent >= 0.25d && blocks.IsInRange(WallClock)) { break; } } } // Give it a break if we are still buffering packets if (IsSyncBuffering) { delay.WaitOne(); FrameDecodingCycle.Complete(); continue; } #endregion } #region Finish the Cycle // Detect End of Media Scenarios DetectEndOfMedia(decodedFrameCount, main); // Resume sync-buffering clock if (wasSyncBuffering && IsSyncBuffering == false) { // Sync-buffering blocks blocks = Blocks[main]; // 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 (blocks.IsInRange(WallClock) == false) { // Update the wall clock to the most appropriate available block. if (blocks.Count > 0) { ChangePosition(blocks[WallClock].StartTime); } else { resumeSyncBufferingClock = false; // Hard stop the clock. } } // log some message and resume the clock if it was playing Log(MediaLogMessageType.Debug, $"SYNC-BUFFER: Finished. Clock set to {WallClock.Format()}"); if (resumeSyncBufferingClock && State.HasMediaEnded == false) { ResumePlayback(); } wasSyncBuffering = false; resumeSyncBufferingClock = false; } // Provide updates to decoding stats State.UpdateDecodingBitrate( Blocks.Values.Sum(b => b.IsInRange(WallClock) ? b.RangeBitrate : 0)); // Complete the frame decoding cycle FrameDecodingCycle.Complete(); // Give it a break if there was nothing to decode. DelayDecoder(delay, decodedFrameCount); #endregion } } catch { throw; } finally { // Reset decoding stats State.UpdateDecodingBitrate(0); // Always exit notifying the cycle is done. FrameDecodingCycle.Complete(); delay.Dispose(); } }
private void AlignClocksToPlayback(MediaType main, MediaType[] all) { // we don't want to disturb the clock or align it if we are not ready if (Commands.HasPendingCommands) { return; } MediaBlockBuffer blocks = null; TimeSpan position = MediaCore.PlaybackPosition; if (HasDisconnectedClocks) { foreach (var t in all) { if (t == MediaType.Subtitle) { continue; } blocks = MediaCore.Blocks[t]; position = MediaCore.Timing.Position(t); // Don't let the RTC lag behind the blocks or move beyond them if (position.Ticks < blocks.RangeStartTime.Ticks) { MediaCore.ChangePlaybackPosition(blocks.RangeStartTime, t); this.LogTrace(Aspects.Timing, $"CLOCK BEHIND: {t} clock was {position.Format()}. It was updated to {blocks.RangeStartTime.Format()}"); } else if (position.Ticks > blocks.RangeEndTime.Ticks) { // we don't use the pause playback method to prevent // reporting the current playback position MediaCore.Timing.Pause(t); MediaCore.ChangePlaybackPosition(blocks.RangeEndTime, t); this.LogTrace(Aspects.Timing, $"CLOCK AHEAD : {t} clock was {position.Format()}. It was updated to {blocks.RangeEndTime.Format()}"); } } return; } // Get a reference to the main blocks. // The range will be 0 if there are no blocks. blocks = MediaCore.Blocks[main]; var range = blocks.GetRangePercent(position); if (range < 0) { // Don't let the RTC lag behind what is available on the main component MediaCore.ChangePlaybackPosition(blocks.RangeStartTime); this.LogTrace(Aspects.Timing, $"CLOCK BEHIND: playback clock was {position.Format()}. It was updated to {blocks.RangeStartTime.Format()}"); } else if (range > 1d) { // Don't let the RTC move beyond what is available on the main component MediaCore.PausePlayback(); MediaCore.ChangePlaybackPosition(blocks.RangeEndTime); this.LogTrace(Aspects.Timing, $"CLOCK AHEAD : playback clock was {position.Format()}. It was updated to {blocks.RangeEndTime.Format()}"); } else if (range == 0 && blocks.Count == 0 && MediaCore.Timing.IsRunning) { // We have no main blocks in range. All we can do is pause the clock this.LogTrace(Aspects.Timing, $"CLOCK PAUSED: playback clock was {position.Format()} but no {main} content was found"); MediaCore.PausePlayback(); } }
/// <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> /// 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> /// 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; // Holds the main media type var main = Container.Components.Main.MediaType; // Holds the auxiliary media types var auxs = Container.Components.MediaTypes.Except(main); // State properties var isBuffering = false; var resumeClock = false; MediaComponent comp = null; MediaBlockBuffer blocks = null; #endregion #region Worker Loop try { while (Commands.IsStopWorkersPending == false) { #region 1. Setup the Decoding Cycle // Determine what to do on a priority command if (Commands.IsExecutingDirectCommand) { if (Commands.IsClosing) { break; } if (Commands.IsChanging) { Commands.WaitForDirectCommand(); } } // Update state properties -- this must be after processing commanmds as // a direct command might have changed the components main = Container.Components.Main.MediaType; auxs = Container.Components.MediaTypes.Except(main); // Execute the following command at the beginning of the cycle Commands.ExecuteNextQueuedCommand(); // Signal a Seek starting operation FrameDecodingCycle.Begin(); // Set initial state wallClock = WallClock; decodedFrameCount = 0; #endregion if (State.HasMediaEnded == false) { #region 2. Main Component Decoding // Capture component and blocks for easier readability // comp is current component, blocks is the block collection for the component comp = Container.Components[main]; blocks = Blocks[main]; // 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 isBuffering = true; State.SignalBufferingStarted(); 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. WaitForPackets(comp, 1); // Decode some frames and check if we are in reange now if (AddNextBlock(main) == false) { break; } decodedFrameCount += 1; isInRange = blocks.IsInRange(wallClock); // Break the cycle if we are in range if (isInRange || CanReadMorePackets == false || ShouldReadMorePackets == false) { break; } }while (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); // Force renderer invalidation InvalidateRenderer(main); // Try to recover the regular loop isInRange = true; } } 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 but always within range. while (blocks.IsFull == false || (blocks.IsFull && rangePercent > 0.75d && rangePercent < 1d)) { if (AddNextBlock(main) == false) { break; } decodedFrameCount += 1; rangePercent = blocks.GetRangePercent(wallClock); continue; } } #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); // wait for component to get there if we only have furutre blocks // in auxiliary component. if (blocks.Count > 0 && blocks.RangeStartTime > wallClock) { continue; } // We need the other components to catch up with the main while (blocks.Count == 0 || blocks.RangeEndTime <= wallClock || (Blocks[main].Count > 0 && blocks.RangeEndTime < Blocks[main].RangeEndTime)) { // give up if we never received frames for the expected component if (AddNextBlock(t) == false) { break; } } // Check if we are finally within range isInRange = blocks.IsInRange(wallClock); // Invalidate the renderer if we don't have the block. if (isInRange == false) { InvalidateRenderer(t); } // Move to the next component if we don't meet a regular conditions if (isInRange == false || isBuffering) { continue; } // Decode as much as we can off the packet buffer for this cycle. rangePercent = blocks.GetRangePercent(wallClock); while (blocks.IsFull == false || (blocks.IsFull && rangePercent > 0.75d && rangePercent < 1d)) { if (AddNextBlock(t) == false) { break; } rangePercent = blocks.GetRangePercent(wallClock); } } #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 && decodedFrameCount <= 0 && 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(); wallClock = Blocks[main].RangeEndTime; Clock.Update(wallClock); if (State.NaturalDuration != null && State.NaturalDuration != TimeSpan.MinValue && State.NaturalDuration < wallClock) { Log(MediaLogMessageType.Warning, $"{nameof(State.HasMediaEnded)} conditions met at {wallClock.Format()} but " + $"{nameof(State.NaturalDuration)} reports {State.NaturalDuration.Value.Format()}"); } State.UpdateMediaEnded(true); State.UpdateMediaState(PlaybackStatus.Stop, wallClock); SendOnMediaEnded(); } } else { State.UpdateMediaEnded(false); } #endregion #region 6. Finish the Cycle // complete buffering notifications if (isBuffering) { // Always 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()}"); } // If not already set, guess the 1-second buffer length State.GuessBufferingProperties(); // Complete the frame decoding cycle FrameDecodingCycle.Complete(); // Give it a break if there was nothing to decode. // We probably need to wait for some more input if (Commands.IsStopWorkersPending == false && decodedFrameCount <= 0 && Commands.HasQueuedCommands == false) { delay.WaitOne(); } #endregion } } catch { throw; } finally { // Always exit notifying the cycle is done. FrameDecodingCycle.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> /// <returns>The task.</returns> internal async Task RunFrameDecodingWorker() { 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 hasPendingSeeks = Commands.PendingCountOf(MediaCommandType.Seek) > 0; if (IsSeeking == false && hasPendingSeeks) { IsSeeking = true; RaiseSeekingStartedEvent(); } // Execute the following command at the beginning of the cycle await Commands.ProcessNext(); 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(); // Check if one of the commands has requested an exit if (IsTaskCancellationPending) { break; } // 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) { // Clear the media blocks if we are outside of the required range // we don't need them and we now need as many playback blocks as we can have available if (blocks.IsFull) { blocks.Clear(); } // detect a buffering scenario if (blocks.Count <= 0) { HasDecoderSeeked = true; isBuffering = true; resumeClock = Clock.IsRunning; Clock.Pause(); Logger.Log(MediaLogMessageType.Debug, $"SYNC BUFFER: Buffering Started."); } // Read some frames and try to get a valid range while (comp.PacketBufferCount > 0 && blocks.IsFull == false) { decodedFrameCount = AddBlocks(main); isInRange = blocks.IsInRange(wallClock); if (isInRange) { break; } // Try to get more packets by waiting for read cycles. if (CanReadMorePackets && comp.PacketBufferCount <= 0 && isInRange == false) { PacketReadingCycle.WaitOne(); } } // 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) { wallClock = wallClock <= blocks.RangeStartTime ? blocks.RangeStartTime : blocks.RangeEndTime; if (isBuffering == false) { Logger.Log(MediaLogMessageType.Warning, $"SYNC CLOCK: {Clock.Position.Format()} set to {wallClock.Format()}"); } // Update the clock to what the main component range mandates Clock.Position = wallClock; } } 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]; // wait for component to get there if we only have furutre blocks // in auxiliary component. if (blocks.RangeStartTime > wallClock) { continue; } // Wait for packets if we are buffering or we don't have enough packets if (CanReadMorePackets && (isBuffering || comp.PacketBufferCount <= 0)) { PacketReadingCycle.WaitOne(); } // catch up with the wall clock while (comp.PacketBufferCount > 0 && blocks.RangeEndTime <= wallClock) { decodedFrameCount = AddBlocks(t); // don't care if we are buffering // always try to catch up by reading more packets. if (comp.PacketBufferCount <= 0 && CanReadMorePackets) { PacketReadingCycle.WaitOne(); } } rangePercent = blocks.GetRangePercent(wallClock); isInRange = blocks.IsInRange(wallClock); // Wait for packets if we are buffering if (CanReadMorePackets && isBuffering) { PacketReadingCycle.WaitOne(); } while (comp.PacketBufferCount > 0 && ( (blocks.IsFull == true && isInRange && rangePercent > 0.75d && rangePercent < 1d) || (blocks.IsFull == false) )) { decodedFrameCount = AddBlocks(t); rangePercent = blocks.GetRangePercent(wallClock); isInRange = blocks.IsInRange(wallClock); if (CanReadMorePackets && isBuffering) { PacketReadingCycle.WaitOne(); } } } #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) { isBuffering = false; if (resumeClock) { Clock.Play(); } Logger.Log(MediaLogMessageType.Debug, $"SYNC BUFFER: Buffering 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; // Simply exit the thread when cancellation has been requested if (IsTaskCancellationPending) { break; } // 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) { await Task.Delay(1); } #endregion } // Always exit notifying the cycle is done. FrameDecodingCycle.Set(); }