/// <summary>
        /// Provides the implementation for the Pause Media Command.
        /// </summary>
        /// <returns>True if the command was successful</returns>
        private bool CommandPauseMedia()
        {
            if (State.CanPause == false)
            {
                return(false);
            }

            MediaCore.Clock.Pause();

            foreach (var renderer in MediaCore.Renderers.Values)
            {
                renderer.Pause();
            }

            MediaCore.ChangePosition(SnapPositionToBlockPosition(MediaCore.WallClock));
            State.UpdateMediaState(PlaybackStatus.Pause);
            return(true);
        }
Esempio n. 2
0
        /// <inheritdoc />
        protected override void ExecuteCycleLogic(CancellationToken ct)
        {
            #region Setup the Decoding Cycle

            // Update state properties -- this must be done on every cycle
            // because a direct command might have changed the components
            var wallClock = MediaCore.WallClock;
            var main      = Container.Components.MainMediaType;
            MediaBlockBuffer blocks;
            DecodedFrameCount = 0;
            var rangePercent = 0d;

            #endregion

            try
            {
                // The 2-part logic blocks detect a sync-buffering scenario
                // and then decodes the necessary frames.
                if (MediaCore.HasDecodingEnded || ct.IsCancellationRequested)
                {
                    return;
                }

                #region Sync-Buffering Detection

                // Capture the blocks for easier readability
                blocks = MediaCore.Blocks[main];

                // If we are not in range then we need to enter the sync-buffering state
                if (NeedsMorePackets && !MediaCore.IsSyncBuffering && blocks.IsInRange(wallClock) == false)
                {
                    // Enter sync-buffering scenario
                    MediaCore.Clock.Pause();
                    wallClock = MediaCore.WallClock;
                    MediaCore.IsSyncBuffering = true;
                    SyncBufferStartTime       = DateTime.UtcNow;
                    this.LogInfo(Aspects.DecodingWorker, $"SYNC-BUFFER: Started. Buffer: {State.BufferingProgress:p}. Clock: {wallClock.Format()}");
                }

                #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 (ct.IsCancellationRequested)
                    {
                        break;
                    }

                    // Capture a reference to the blocks and the current Range Percent
                    const double rangePercentThreshold = 0.75d;
                    blocks       = MediaCore.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)
                    {
                        if (ct.IsCancellationRequested || AddNextBlock(t) == false)
                        {
                            break;
                        }

                        DecodedFrameCount += 1;
                        rangePercent       = blocks.GetRangePercent(wallClock);

                        // Determine break conditions to save CPU time
                        if (rangePercent > 0 &&
                            rangePercent <= rangePercentThreshold &&
                            blocks.IsFull == false &&
                            blocks.CapacityPercent >= 0.25d &&
                            blocks.IsInRange(wallClock))
                        {
                            break;
                        }
                    }

                    #endregion
                }
            }
            finally
            {
                // Provide updates to decoding stats
                State.UpdateDecodingBitRate(
                    MediaCore.Blocks.Values.Sum(b => b.IsInRange(wallClock) ? b.RangeBitRate : 0));

                // Detect End of Decoding Scenarios
                // The Rendering will check for end of media when this
                // condition is set.
                var hasDecodingEnded = DetectHasDecodingEnded(wallClock, DecodedFrameCount, main);
                MediaCore.HasDecodingEnded = hasDecodingEnded;

                // Detect if an exit from Sync Buffering is required
                var mustExitSyncBuffering = MediaCore.IsSyncBuffering &&
                                            (ct.IsCancellationRequested || hasDecodingEnded || State.BufferingProgress >= 0.95);

                // Detect if we need an immediate exit from sync buffering
                if (mustExitSyncBuffering || (MediaCore.IsSyncBuffering && !NeedsMorePackets))
                {
                    blocks = MediaCore.Blocks[main];
                    if (blocks.Count > 0 && !blocks.IsInRange(wallClock))
                    {
                        wallClock = blocks[wallClock].StartTime;
                    }

                    MediaCore.ChangePosition(wallClock);
                    MediaCore.IsSyncBuffering = false;
                    this.LogInfo(Aspects.DecodingWorker, $"SYNC-BUFFER: Completed in {DateTime.UtcNow.Subtract(SyncBufferStartTime).TotalMilliseconds:0.0} ms");

                    if (State.MediaState == PlaybackStatus.Play)
                    {
                        MediaCore.ResumePlayback();
                    }
                }
            }
        }
Esempio n. 3
0
        /// <inheritdoc />
        protected override void ExecuteCycleLogic(CancellationToken ct)
        {
            // Update Status Properties
            var main         = Container.Components.MainMediaType;
            var all          = MediaCore.Renderers.Keys.ToArray();
            var currentBlock = new MediaTypeDictionary <MediaBlock>();

            if (HasInitialized == false)
            {
                // wait for main component blocks or EOF or cancellation pending
                if (MediaCore.Blocks[main].Count <= 0)
                {
                    return;
                }

                // Set the initial clock position
                MediaCore.ChangePosition(MediaCore.Blocks[main].RangeStartTime);

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

                // Mark as initialized
                HasInitialized.Value = true;
            }

            #region Run the Rendering Cycle

            try
            {
                // TODO: wait for active seek command
                try { Commands.WaitForSeekBlocks(ct); }
                catch { return; }

                #region 2. Handle Block Rendering

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

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

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

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

                #endregion

                #region 3. Finalize the Rendering Cycle

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

                #endregion
            }
            catch (Exception ex)
            {
                MediaCore.LogError(Aspects.RenderingWorker, "Error while in rendering worker cycle", ex);
                throw;
            }
            finally
            {
                // Check End of Media Scenarios
                if (MediaCore.HasDecodingEnded &&
                    Commands.IsSeeking == false &&
                    MediaCore.WallClock >= MediaCore.LastRenderTime[main] &&
                    MediaCore.WallClock >= MediaCore.Blocks[main].RangeEndTime)
                {
                    // Rendered all and nothing else to render
                    if (State.HasMediaEnded == false)
                    {
                        MediaCore.Clock.Pause();
                        var endPosition = MediaCore.ChangePosition(MediaCore.Blocks[main].RangeEndTime);
                        State.UpdateMediaEnded(true, endPosition);
                        State.UpdateMediaState(PlaybackStatus.Stop);
                        foreach (var mt in Container.Components.MediaTypes)
                        {
                            MediaCore.InvalidateRenderer(mt);
                        }
                    }
                }
                else
                {
                    State.UpdateMediaEnded(false, TimeSpan.Zero);
                }

                // Update the Position
                if (!ct.IsCancellationRequested)
                {
                    State.UpdatePosition();
                }
            }

            #endregion
        }
Esempio n. 4
0
        /// <inheritdoc />
        protected override void ExecuteCycleLogic(CancellationToken ct)
        {
            // Update Status Properties
            var main         = Container.Components.MainMediaType;
            var all          = MediaCore.Renderers.Keys.ToArray();
            var currentBlock = new MediaTypeDictionary <MediaBlock>();

            if (HasInitialized == false)
            {
                // wait for main component blocks or EOF or cancellation pending
                if (MediaCore.Blocks[main].Count <= 0)
                {
                    return;
                }

                // Set the initial clock position
                MediaCore.ChangePosition(MediaCore.Blocks[main].RangeStartTime);

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

                // Mark as initialized
                HasInitialized.Value = true;
            }

            #region Run the Rendering Cycle

            try
            {
                #region 1. Wait for any seek operation to make blocks available in a loop

                while (!ct.IsCancellationRequested &&
                       Commands.IsActivelySeeking &&
                       !MediaCore.Blocks[main].IsInRange(MediaCore.WallClock))
                {
                    // Check if we finally have seek blocks available
                    // if we don't get seek blocks in range and we are not step-seeking,
                    // then we simply break out of the loop and render whatever it is we have
                    // to create the illussion of smooth seeking. For precision seeking we
                    // continue the loop.
                    if (!Commands.WaitForSeekBlocks(DefaultPeriod.Milliseconds) &&
                        Commands.ActiveSeekMode == CommandManager.SeekMode.Normal)
                    {
                        break;
                    }
                }

                #endregion

                #region 2. Handle Block Rendering

                // Capture the blocks to render at a fixed wall clock position
                // so all blocks are aligned to the same timestamp
                var wallClock = MediaCore.WallClock;

                foreach (var t in all)
                {
                    // skip blocks if we are seeking and they are not video blocks
                    if (Commands.IsSeeking && t != MediaType.Video)
                    {
                        currentBlock[t] = null;
                        continue;
                    }

                    // Get the audio, video, or subtitle block to render
                    currentBlock[t] = t == MediaType.Subtitle && MediaCore.PreloadedSubtitles != null ?
                                      MediaCore.PreloadedSubtitles[wallClock] :
                                      MediaCore.Blocks[t][wallClock];
                }

                // Render each of the Media Types if it is time to do so.
                foreach (var t in all)
                {
                    // Don't send null blocks to renderer
                    if (currentBlock[t] == null || currentBlock[t].IsDisposed)
                    {
                        continue;
                    }

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

                #endregion

                #region 3. Finalize the Rendering Cycle

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

                #endregion
            }
            catch (Exception ex)
            {
                MediaCore.LogError(Aspects.RenderingWorker, "Error while in rendering worker cycle", ex);
                throw;
            }
            finally
            {
                // Check End of Media Scenarios
                if (Commands.IsSeeking == false &&
                    MediaCore.HasDecodingEnded &&
                    MediaCore.WallClock >= MediaCore.LastRenderTime[main] &&
                    MediaCore.WallClock >= MediaCore.Blocks[main].RangeEndTime)
                {
                    // Rendered all and nothing else to render
                    if (State.HasMediaEnded == false)
                    {
                        MediaCore.Clock.Pause();
                        var endPosition = MediaCore.ChangePosition(MediaCore.Blocks[main].RangeEndTime);
                        State.UpdateMediaEnded(true, endPosition);
                        State.UpdateMediaState(PlaybackStatus.Stop);
                        foreach (var mt in Container.Components.MediaTypes)
                        {
                            MediaCore.InvalidateRenderer(mt);
                        }
                    }
                }
                else
                {
                    State.UpdateMediaEnded(false, TimeSpan.Zero);
                }

                // Update the Position
                if (!ct.IsCancellationRequested && Commands.IsSeeking == false)
                {
                    State.UpdatePosition();
                }
            }

            #endregion
        }