Beispiel #1
0
        /// <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);
            }
        }
Beispiel #2
0
        /// <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
        }
Beispiel #4
0
        /// <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();
            }
        }
Beispiel #5
0
        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();
            }
        }
Beispiel #6
0
        /// <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();
        }