Example #1
0
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool alsoManaged)
        {
            if (IsDisposed)
            {
                return;
            }

            if (alsoManaged)
            {
                // free managed resources
                Commands.Close();

                if (Container != null)
                {
                    Container.Dispose();
                    Container = null;
                }

                if (UIPropertyUpdateTimer != null)
                {
                    UIPropertyUpdateTimer.Stop();
                    UIPropertyUpdateTimer.IsEnabled = false;
                    UIPropertyUpdateTimer           = null;
                }

                PacketReadingCycle.Dispose();
                FrameDecodingCycle.Dispose();
                BlockRenderingCycle.Dispose();
                SeekingDone.Dispose();
                DelayLock.Dispose();
            }

            IsDisposed = true;
        }
Example #2
0
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// Please not that this call is non-blocking/asynchronous.
        /// </summary>
        /// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool alsoManaged)
        {
            if (IsDisposed)
            {
                return;
            }

            // Run the close command immediately
            if (BeginSynchronousCommand() == false)
            {
                return;
            }

            // Dispose the wait handle: No more command accepted from this point forward.
            SynchronousCommandDone.Dispose();

            try
            {
                var closeCommand = new CloseCommand(Commands);
                closeCommand.RunSynchronously();
            }
            catch { throw; }
            finally
            {
                IsDisposed = true;
            }

            // Dispose the container
            Container?.Dispose();
            Container = null;

            // Dispose the RTC
            Clock.Dispose();

            // Dispose the Wait Event objects as they are
            // backed by unmanaged code
            PacketReadingCycle.Dispose();
            FrameDecodingCycle.Dispose();
            BlockRenderingCycle.Dispose();
            SeekingDone.Dispose();
            MediaChangingDone.Dispose();
        }
        /// <summary>
        /// Performs a seek operation to the specified position.
        /// </summary>
        /// <param name="position">The position.</param>
        private void Seek(TimeSpan position)
        {
            SeekingDone.Wait();
            var startTime   = DateTime.UtcNow;
            var resumeClock = Clock.IsRunning;

            Clock.Pause();

            SeekingDone.Reset();
            PacketReadingCycle.Wait();
            FrameDecodingCycle.Wait();
            BlockRenderingCycle.Wait();

            // Clear Blocks and frames, reset the render times
            foreach (var t in Container.Components.MediaTypes)
            {
                Frames[t].Clear();
                Blocks[t].Clear();
                LastRenderTime[t] = TimeSpan.MinValue;
            }

            // Populate frame with after-seek operation
            var frames = Container.Seek(position);

            foreach (var frame in frames)
            {
                Frames[frame.MediaType].Push(frame);
            }

            // Resume the clock if it was running before the seek operation
            OnPropertyChanged(nameof(Position));
            if (resumeClock)
            {
                Clock.Play();
            }

            Container.Log(MediaLogMessageType.Debug,
                          $"SEEK D: Elapsed: {startTime.DebugElapsedUtc()}");

            RequestedSeekPosition = null;
            SeekingDone.Set();
        }
        /// <summary>
        /// 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
        }
Example #5
0
        /// <summary>
        /// Runs the read task which keeps a packet buffer as full as possible.
        /// It reports on DownloadProgress by enqueueing an update to the property
        /// in order to avoid any kind of disruption to this thread caused by the UI thread.
        /// </summary>
        internal void RunPacketReadingWorker()
        {
            #region Worker State Setup

            // The delay provider prevents 100% core usage
            var delay = new DelayProvider();

            // Holds the packet count for each read cycle
            var packetsRead = new MediaTypeDictionary <int>();

            // State variables for media types
            var t = MediaType.None;

            // Store Container in local variable to prevent NullReferenceException
            // when dispose occurs sametime with read cycle
            var mediaContainer = Container;

            #endregion

            #region Worker Loop

            try
            {
                // Worker logic begins here
                while (IsTaskCancellationPending == false)
                {
                    // Wait for seeking or changing to be done.
                    MediaChangingDone.Wait();
                    SeekingDone.Wait();

                    // Enter a packet reading cycle
                    PacketReadingCycle.Begin();

                    // Initialize Packets read to 0 for each component and state variables
                    foreach (var k in mediaContainer.Components.MediaTypes)
                    {
                        packetsRead[k] = 0;
                    }

                    // Start to perform the read loop
                    // NOTE: Disrupting the packet reader causes errors in UPD streams. Disrupt as little as possible
                    while (CanReadMorePackets && ShouldReadMorePackets && IsTaskCancellationPending == false)
                    {
                        // Perform a packet read. t will hold the packet type.
                        try
                        {
                            t = mediaContainer.Read();
                        }
                        catch (MediaContainerException)
                        {
                            continue;
                        }

                        // Discard packets that we don't need (i.e. MediaType == None)
                        if (mediaContainer.Components.MediaTypes.HasMediaType(t) == false)
                        {
                            continue;
                        }

                        // Update the packet count for the components
                        packetsRead[t] += 1;

                        // Ensure we have read at least some packets from main and auxiliary streams.
                        if (packetsRead.FundamentalsGreaterThan(0))
                        {
                            break;
                        }
                    }

                    // finish the reading cycle.
                    PacketReadingCycle.Complete();

                    // Don't evaluate a pause condition if we are seeking
                    if (SeekingDone.IsInProgress)
                    {
                        continue;
                    }

                    // Wait some if we have a full packet buffer or we are unable to read more packets (i.e. EOF).
                    if (ShouldReadMorePackets == false ||
                        CanReadMorePackets == false ||
                        packetsRead.GetSum() <= 0)
                    {
                        delay.WaitOne();
                    }
                }
            }
            catch (ThreadAbortException) { /* swallow */ }
            catch { if (!IsDisposed)
                    {
                        throw;
                    }
            }
            finally
            {
                // Always exit notifying the reading cycle is done.
                PacketReadingCycle.Complete();
                delay.Dispose();
            }

            #endregion
        }
Example #6
0
        /// <summary>
        /// Starts the block rendering worker.
        /// </summary>
        private void StartBlockRenderingWorker()
        {
            if (HasBlockRenderingWorkerExited != null)
            {
                return;
            }

            HasBlockRenderingWorkerExited = new ManualResetEvent(false);

            // Synchronized access to parts of the run cycle
            var isRunningRenderingCycle = false;

            // Holds the main media type
            var main = Container.Components.Main.MediaType;

            // Holds the auxiliary media types
            var auxs = Container.Components.MediaTypes.ExcludeMediaType(main);

            // Holds all components
            var all = Container.Components.MediaTypes.DeepCopy();

            // Holds a snapshot of the current block to render
            var currentBlock = new MediaTypeDictionary <MediaBlock>();

            // Keeps track of how many blocks were rendered in the cycle.
            var renderedBlockCount = new MediaTypeDictionary <int>();

            // reset render times for all components
            foreach (var t in all)
            {
                LastRenderTime[t] = TimeSpan.MinValue;
            }

            // Ensure packet reading is running
            PacketReadingCycle.WaitOne();

            // wait for main component blocks or EOF or cancellation pending
            while (CanReadMoreFramesOf(main) && Blocks[main].Count <= 0)
            {
                FrameDecodingCycle.WaitOne();
            }

            // Set the initial clock position
            // TODO: maybe update media start time offset to this Minimum, initial Start Time intead of relying on contained meta?
            Clock.Update(Blocks[main].RangeStartTime); // .GetMinStartTime()
            var wallClock = WallClock;

            // Wait for renderers to be ready
            foreach (var t in all)
            {
                Renderers[t]?.WaitForReadyState();
            }

            // The Render timer is responsible for sending frames to renders
            BlockRenderingWorker = new Timer((s) =>
            {
                #region Detect a Timer Stop

                if (IsTaskCancellationPending || HasBlockRenderingWorkerExited.IsSet() || IsDisposed)
                {
                    HasBlockRenderingWorkerExited.Set();
                    return;
                }

                #endregion

                #region Run the Rendering Cycle

                // Updatete Status  Properties
                State.UpdateBufferingProperties();

                // Don't run the cycle if it's already running
                if (isRunningRenderingCycle)
                {
                    // TODO: Maybe Log a frame skip here?
                    return;
                }

                try
                {
                    #region 1. Control and Capture

                    // Flag the current rendering cycle
                    isRunningRenderingCycle = true;

                    // Reset the rendered count to 0
                    foreach (var t in all)
                    {
                        renderedBlockCount[t] = 0;
                    }

                    // Capture current clock position for the rest of this cycle
                    BlockRenderingCycle.Reset();

                    #endregion

                    #region 2. Handle Block Rendering

                    // Wait for the seek op to finish before we capture blocks
                    if (HasDecoderSeeked)
                    {
                        SeekingDone.WaitOne();
                    }

                    // capture the wall clock for this cycle
                    wallClock = WallClock;

                    // Capture the blocks to render
                    foreach (var t in all)
                    {
                        currentBlock[t] = Blocks[t][wallClock];
                    }

                    // Render each of the Media Types if it is time to do so.
                    foreach (var t in all)
                    {
                        // Skip rendering for nulls
                        if (currentBlock[t] == null)
                        {
                            continue;
                        }

                        // Render by forced signal (TimeSpan.MinValue)
                        if (LastRenderTime[t] == TimeSpan.MinValue)
                        {
                            renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock);
                            continue;
                        }

                        // Render because we simply have not rendered
                        if (currentBlock[t].StartTime != LastRenderTime[t])
                        {
                            renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock);
                            continue;
                        }
                    }

                    #endregion

                    #region 6. Finalize the Rendering Cycle

                    // Call the update method on all renderers so they receive what the new wall clock is.
                    foreach (var t in all)
                    {
                        Renderers[t]?.Update(wallClock);
                    }

                    #endregion
                }
                catch (ThreadAbortException) { /* swallow */ }
                catch { if (!IsDisposed)
                        {
                            throw;
                        }
                }
                finally
                {
                    // Always exit notifying the cycle is done.
                    BlockRenderingCycle.Set();
                    isRunningRenderingCycle = false;
                }

                #endregion
            },
                                             this, // the state argument passed on to the ticker
                                             0,
                                             (int)Constants.Interval.HighPriority.TotalMilliseconds);
        }
        /// <summary>
        /// Starts the block rendering worker.
        /// </summary>
        private void StartBlockRenderingWorker()
        {
            if (BlockRenderingWorkerExit != null)
            {
                return;
            }

            BlockRenderingWorkerExit = WaitEventFactory.Create(isCompleted: false, useSlim: true);

            // Synchronized access to parts of the run cycle
            var isRunningRenderingCycle = false;

            // Holds the main media type
            var main = Container.Components.Main.MediaType;

            // Holds all components
            var all = Renderers.Keys.ToArray();

            // Holds a snapshot of the current block to render
            var currentBlock = new MediaTypeDictionary <MediaBlock>();

            // Keeps track of how many blocks were rendered in the cycle.
            var renderedBlockCount = new MediaTypeDictionary <int>();

            // reset render times for all components
            foreach (var t in all)
            {
                LastRenderTime[t] = TimeSpan.MinValue;
            }

            // Ensure packet reading is running
            PacketReadingCycle.Wait();

            // wait for main component blocks or EOF or cancellation pending
            while (CanReadMoreFramesOf(main) && Blocks[main].Count <= 0)
            {
                FrameDecodingCycle.Wait();
            }

            // Set the initial clock position
            Clock.Update(Blocks[main].RangeStartTime);
            var wallClock = WallClock;

            // Wait for renderers to be ready
            foreach (var t in all)
            {
                Renderers[t]?.WaitForReadyState();
            }

            // The Render timer is responsible for sending frames to renders
            BlockRenderingWorker = new Timer((s) =>
            {
                #region Detect a Timer Stop

                if (IsTaskCancellationPending || BlockRenderingWorkerExit.IsCompleted || IsDisposed)
                {
                    BlockRenderingWorkerExit.Complete();
                    return;
                }

                #endregion

                #region Run the Rendering Cycle

                // Updatete Status  Properties
                State.UpdateBufferingProperties();

                // Don't run the cycle if it's already running
                if (isRunningRenderingCycle)
                {
                    Log(MediaLogMessageType.Trace, $"SKIP: {nameof(BlockRenderingWorker)} alredy in a cycle. {WallClock}");
                    return;
                }

                try
                {
                    #region 1. Control and Capture

                    // Flag the current rendering cycle
                    isRunningRenderingCycle = true;

                    // Reset the rendered count to 0
                    foreach (var t in all)
                    {
                        renderedBlockCount[t] = 0;
                    }

                    // Capture current clock position for the rest of this cycle
                    BlockRenderingCycle.Begin();

                    #endregion

                    #region 2. Handle Block Rendering

                    // Wait for the seek op to finish before we capture blocks
                    if (HasDecoderSeeked)
                    {
                        SeekingDone.Wait();
                    }

                    // capture the wall clock for this cycle
                    wallClock = WallClock;

                    // Capture the blocks to render
                    foreach (var t in all)
                    {
                        if (t == MediaType.Subtitle && PreloadedSubtitles != null)
                        {
                            // Get the preloaded, cached subtitle block
                            currentBlock[t] = PreloadedSubtitles[wallClock];
                        }
                        else
                        {
                            // Get the regular audio, video, or sub block
                            currentBlock[t] = Blocks[t][wallClock];
                        }
                    }

                    // Render each of the Media Types if it is time to do so.
                    foreach (var t in all)
                    {
                        // Skip rendering for nulls
                        if (currentBlock[t] == null)
                        {
                            continue;
                        }

                        // Render by forced signal (TimeSpan.MinValue) or because simply it is time to do so
                        if (LastRenderTime[t] == TimeSpan.MinValue || currentBlock[t].StartTime != LastRenderTime[t])
                        {
                            renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock);
                            continue;
                        }
                    }

                    #endregion

                    #region 6. Finalize the Rendering Cycle

                    // Call the update method on all renderers so they receive what the new wall clock is.
                    foreach (var t in all)
                    {
                        Renderers[t]?.Update(wallClock);
                    }

                    #endregion
                }
                catch (ThreadAbortException) { /* swallow */ }
                catch { if (!IsDisposed)
                        {
                            throw;
                        }
                }
                finally
                {
                    // Always exit notifying the cycle is done.
                    BlockRenderingCycle.Complete();
                    isRunningRenderingCycle = false;
                }

                #endregion
            },
                                             this, // the state argument passed on to the ticker
                                             0,
                                             Convert.ToInt32(Constants.Interval.HighPriority.TotalMilliseconds));
        }
Example #8
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();
            }
        }
Example #9
0
        /// <summary>
        /// Runs the read task which keeps a packet buffer as full as possible.
        /// It reports on DownloadProgress by enqueueing an update to the property
        /// in order to avoid any kind of disruption to this thread caused by the UI thread.
        /// </summary>
        internal void RunPacketReadingWorker()
        {
            try
            {
                // Holds the packet count for each read cycle
                var packetsRead = new MediaTypeDictionary <int>();

                // State variables for media types
                var t = MediaType.None;

                // Store Container in local variable to prevent NullReferenceException
                // when dispose occurs sametime with read cycle
                var mediaContainer = Container;

                var main = mediaContainer.Components.Main.MediaType;
                var auxs = mediaContainer.Components.MediaTypes.Where(c => c != main && (c == MediaType.Audio || c == MediaType.Video)).ToArray();
                var all  = auxs.Union(new[] { main }).ToArray();

                // State variables for bytes read (give-up condition)
                var startBytesRead   = 0UL;
                var currentBytesRead = 0UL;

                // Worker logic begins here
                while (IsTaskCancellationPending == false)
                {
                    // Enter a read cycle
                    SeekingDone?.WaitOne();

                    // Enter a packet reading cycle
                    PacketReadingCycle?.Reset();

                    if (CanReadMorePackets && mediaContainer.Components.PacketBufferLength < DownloadCacheLength)
                    {
                        // Initialize Packets read to 0 for each component and state variables
                        foreach (var k in mediaContainer.Components.MediaTypes)
                        {
                            packetsRead[k] = 0;
                        }

                        startBytesRead   = mediaContainer.Components.TotalBytesRead;
                        currentBytesRead = 0UL;

                        // Start to perform the read loop
                        while (CanReadMorePackets)
                        {
                            // Perform a packet read. t will hold the packet type.
                            t = mediaContainer.Read();

                            // Discard packets that we don't need (i.e. MediaType == None)
                            if (mediaContainer.Components.MediaTypes.Contains(t) == false)
                            {
                                continue;
                            }

                            // Update the packet count for the components
                            packetsRead[t] += 1;

                            // Ensure we have read at least some packets from main and auxiliary streams.
                            if (packetsRead.Where(k => all.Contains(k.Key)).All(c => c.Value > 0))
                            {
                                break;
                            }

                            // The give-up condition is that in spite of efforts to read at least one of each,
                            // we could not find the required packet types.
                            currentBytesRead = mediaContainer.Components.TotalBytesRead - startBytesRead;
                            if (currentBytesRead > (ulong)DownloadCacheLength)
                            {
                                break;
                            }
                        }
                    }

                    // finish the reading cycle.
                    PacketReadingCycle?.Set();

                    // Wait some if we have a full packet buffer or we are unable to read more packets (i.e. EOF).
                    if (mediaContainer.Components.PacketBufferLength >= DownloadCacheLength || CanReadMorePackets == false || currentBytesRead <= 0)
                    {
                        Task.Delay(1).GetAwaiter().GetResult();
                    }
                }
            }
            catch (ThreadAbortException)
            {
            }
            finally
            {
                // Always exit notifying the reading cycle is done.
                PacketReadingCycle?.Set();
            }
        }
Example #10
0
        /// <summary>
        /// Starts the block rendering worker.
        /// </summary>
        private void StartBlockRenderingWorker()
        {
            if (HasBlockRenderingWorkerExited != null)
            {
                return;
            }

            HasBlockRenderingWorkerExited = new ManualResetEvent(false);

            // Synchronized access to parts of the run cycle
            var isRunningPropertyUpdates = false;
            var isRunningRenderingCycle  = false;

            // Holds the main media type
            var main = Container.Components.Main.MediaType;

            // Holds the auxiliary media types
            var auxs = Container.Components.MediaTypes.ExcludeMediaType(main);

            // Holds all components
            var all = Container.Components.MediaTypes.DeepCopy();

            // Holds a snapshot of the current block to render
            var currentBlock = new MediaTypeDictionary <MediaBlock>();

            // Keeps track of how many blocks were rendered in the cycle.
            var renderedBlockCount = new MediaTypeDictionary <int>();

            // reset render times for all components
            foreach (var t in all)
            {
                LastRenderTime[t] = TimeSpan.MinValue;
            }

            // Ensure the other workers are running
            PacketReadingCycle.WaitOne();
            FrameDecodingCycle.WaitOne();

            // Set the initial clock position
            Clock.Position = Blocks[main].RangeStartTime;
            var wallClock = Clock.Position;

            // Wait for renderers to be ready
            foreach (var t in all)
            {
                Renderers[t]?.WaitForReadyState();
            }

            // The Property update timer is responsible for timely updates to properties outside of the worker threads
            BlockRenderingWorker = new Timer((s) =>
            {
                #region Detect a Timer Stop

                if (IsTaskCancellationPending || HasBlockRenderingWorkerExited.IsSet() || m_IsDisposing.Value)
                {
                    HasBlockRenderingWorkerExited.Set();
                    return;
                }

                #endregion

                #region Run the property Updates

                if (isRunningPropertyUpdates == false)
                {
                    isRunningPropertyUpdates = true;

                    try
                    {
                        UpdatePosition(IsOpen ? Clock?.Position ?? TimeSpan.Zero : TimeSpan.Zero);
                        UpdateBufferingProperties();
                    }
                    catch (Exception ex)
                    {
                        Log(MediaLogMessageType.Error, $"{nameof(BlockRenderingWorker)} callabck failed. {ex.GetType()}: {ex.Message}");
                    }
                    finally
                    {
                        isRunningPropertyUpdates = false;
                    }
                }

                #endregion

                #region Run the Rendering Cycle

                // Don't run the cycle if it's already running
                if (isRunningRenderingCycle)
                {
                    // TODO: Log a frame skip
                    return;
                }

                try
                {
                    #region 1. Control and Capture

                    // Flag the current rendering cycle
                    isRunningRenderingCycle = true;

                    // Reset the rendered count to 0
                    foreach (var t in all)
                    {
                        renderedBlockCount[t] = 0;
                    }

                    // Capture current clock position for the rest of this cycle
                    BlockRenderingCycle.Reset();

                    // capture the wall clock for this cycle
                    wallClock = Clock.Position;

                    #endregion

                    #region 2. Handle Block Rendering

                    // Wait for the seek op to finish before we capture blocks
                    if (HasDecoderSeeked)
                    {
                        SeekingDone.WaitOne();
                    }

                    // Capture the blocks to render
                    foreach (var t in all)
                    {
                        currentBlock[t] = Blocks[t][wallClock];
                    }

                    // Render each of the Media Types if it is time to do so.
                    foreach (var t in all)
                    {
                        // Skip rendering for nulls
                        if (currentBlock[t] == null)
                        {
                            continue;
                        }

                        // Render by forced signal (TimeSpan.MinValue)
                        if (LastRenderTime[t] == TimeSpan.MinValue)
                        {
                            renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock);
                            continue;
                        }

                        // Render because we simply have not rendered
                        if (currentBlock[t].StartTime != LastRenderTime[t])
                        {
                            renderedBlockCount[t] += SendBlockToRenderer(currentBlock[t], wallClock);
                            continue;
                        }
                    }

                    #endregion

                    #region 6. Finalize the Rendering Cycle

                    // Call the update method on all renderers so they receive what the new wall clock is.
                    foreach (var t in all)
                    {
                        Renderers[t]?.Update(wallClock);
                    }

                    #endregion
                }
                catch (ThreadAbortException) { }
                catch { throw; }
                finally
                {
                    // Always exit notifying the cycle is done.
                    BlockRenderingCycle.Set();
                    isRunningRenderingCycle = false;
                }

                #endregion
            },
                                             this, // the state argument passed on to the ticker
                                             0,
                                             (int)Constants.Interval.HighPriority.TotalMilliseconds);
        }