Exemple #1
0
        internal static int GetSum(this MediaTypeDictionary <int> all)
        {
            var result = default(int);

            foreach (var kvp in all)
            {
                result += kvp.Value;
            }

            return(result);
        }
Exemple #2
0
        internal static int GetBlockCount(this MediaTypeDictionary <MediaBlockBuffer> blocks)
        {
            var result = 0;

            foreach (var buffer in blocks.Values)
            {
                result += buffer.Count;
            }

            return(result);
        }
Exemple #3
0
        internal static TimeSpan GetMinStartTime(this MediaTypeDictionary <MediaBlockBuffer> blocks)
        {
            var minimum = TimeSpan.Zero;

            foreach (var buffer in blocks.Values)
            {
                if (buffer.RangeStartTime.Ticks < minimum.Ticks)
                {
                    minimum = buffer.RangeStartTime;
                }
            }

            return(minimum);
        }
Exemple #4
0
        public Task CallSwagger(HttpContext httpContext, NetStitchServer server)
        {
            const string prefix = "NetStitch.Swagger.SwaggerUI.";

            var path = httpContext.Request.Path.Value.Trim('/');

            if (path == "")
            {
                path = "index.html";
            }
            var filePath = prefix + path.Replace("/", ".");

            if (MediaTypeDictionary.TryGetValue(filePath.Split('.').Last(), out string mediaType))
            {
                if (path.EndsWith(options.JsonName))
                {
                    var builder = new SwaggerBuilder(options, httpContext);
                    var bytes   = builder.BuildSwagger(server);
                    httpContext.Response.ContentType = "application/json";
                    httpContext.Response.StatusCode  = HttpStatus.OK;
                    httpContext.Response.Body.Write(bytes, 0, bytes.Length);
                    return(Task.CompletedTask);
                }

                var myAssembly = typeof(NetStitchSwaggerMiddleware).GetTypeInfo().Assembly;

                using (var stream = myAssembly.GetManifestResourceStream(filePath))
                {
                    if (stream == null)
                    {
                        return(next(httpContext));
                    }
                    httpContext.Response.ContentType = mediaType;
                    httpContext.Response.StatusCode  = HttpStatus.OK;
                    var response = httpContext.Response.Body;
                    stream.CopyTo(response);
                }
            }
            ;

            return(Task.CompletedTask);
        }
Exemple #5
0
        internal static bool ContainsMoreThan(this MediaTypeDictionary <int> all, int value)
        {
            var hasFundamentals = false;

            foreach (var kvp in all)
            {
                // Skip over non-fundamental types
                if (kvp.Key != MediaType.Audio && kvp.Key != MediaType.Video)
                {
                    continue;
                }

                hasFundamentals = true;
                if (kvp.Value <= value)
                {
                    return(false);
                }
            }

            return(hasFundamentals);
        }
Exemple #6
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
        }
 protected override void SetUp()
 {
     base.SetUp();
     _repository = new MediaTypeDictionary<string>();
 }
Exemple #8
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
        }
Exemple #9
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));
        }
        /// <summary>
        /// Runs the read task which keeps a packet buffer as full as possible.
        /// It reports on DownloadProgress by enqueueing an update to the property
        /// in order to avoid any kind of disruption to this thread caused by the UI thread.
        /// </summary>
        internal void RunPacketReadingWorker()
        {
            #region Worker State Setup

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

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

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

            // Signal the start of a buffering operation
            State.SignalBufferingStarted();

            #endregion

            #region Worker Loop

            try
            {
                // Worker logic begins here
                while (Commands.IsStopWorkersPending == false)
                {
                    // Determine what to do on a priority command
                    if (Commands.IsExecutingDirectCommand)
                    {
                        if (Commands.IsClosing)
                        {
                            break;
                        }
                        if (Commands.IsChanging)
                        {
                            Commands.WaitForDirectCommand();
                        }
                    }

                    // Wait for seeking or changing to be done.
                    Commands.WaitForActiveSeekCommand();

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

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

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

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

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

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

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

                    // Don't evaluate a pause/delay condition if we are seeking
                    if (Commands.IsActivelySeeking)
                    {
                        continue;
                    }

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

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

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

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

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

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

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

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

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

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

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

                if (Commands.IsStopWorkersPending || BlockRenderingWorkerExit.IsCompleted || IsDisposed)
                {
                    BlockRenderingWorkerExit?.Complete();
                    return;
                }

                // Skip the cycle if it's already running
                if (BlockRenderingCycle.IsInProgress)
                {
                    Log(MediaLogMessageType.Trace, $"SKIP: {nameof(BlockRenderingWorker)} already in a cycle. {WallClock}");
                    return;
                }

                #endregion

                #region Run the Rendering Cycle

                try
                {
                    #region 1. Control and Capture

                    // Wait for the seek op to finish before we capture blocks
                    Commands.WaitForActiveSeekCommand();

                    // Signal the start of a block rendering cycle
                    BlockRenderingCycle.Begin();

                    // Skip the cycle if we are running a priority command
                    if (Commands.IsExecutingDirectCommand)
                    {
                        return;
                    }

                    // Updatete Status Properties
                    main = Container.Components.MainMediaType;
                    all  = Renderers.Keys.ToArray();

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

                    #endregion

                    #region 2. Handle Block Rendering

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

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

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

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

                    #endregion

                    #region 3. Finalize the Rendering Cycle

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

                    #endregion
                }
                catch { throw; }
                finally
                {
                    // Update the Position
                    if (IsWorkerInterruptRequested == false && IsSyncBuffering == false)
                    {
                        State.UpdatePosition(Clock.IsRunning ? wallClock : Clock.Position);
                    }

                    // Always exit notifying the cycle is done.
                    BlockRenderingCycle.Complete();
                }

                #endregion
            },
                                             this, // the state argument passed on to the ticker
                                             0,
                                             Convert.ToInt32(Constants.Interval.HighPriority.TotalMilliseconds));
        }
Exemple #13
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();
            }
        }
        /// <summary>
        /// Continuously converts frmes and places them on the corresponding
        /// block buffer. This task is responsible for keeping track of the clock
        /// and calling the render methods appropriate for the current clock position.
        /// </summary>
        internal async void RunBlockRenderingWorker()
        {
            #region 0. Initialize Running State

            // Holds the main media type
            var main = Container.Components.Main.MediaType;
            // Holds the auxiliary media types
            var auxs = Container.Components.MediaTypes.Where(t => t != main).ToArray();
            // Holds all components
            var all = Container.Components.MediaTypes.ToArray();

            // Create and reset all the tracking variables
            var hasRendered = new MediaTypeDictionary <bool>();
            var renderIndex = new MediaTypeDictionary <int>();
            var renderBlock = new MediaTypeDictionary <MediaBlock>();

            // reset all state variables for all components
            foreach (var t in all)
            {
                hasRendered[t]    = false;
                renderIndex[t]    = -1;
                renderBlock[t]    = null;
                LastRenderTime[t] = TimeSpan.MinValue;
            }

            // Buffer some blocks and adjust the clock to the start position
            BufferBlocks(BufferCacheLength, false);
            Clock.Position = Blocks[main].RangeStartTime;
            var wallClock = Clock.Position;

            #endregion

            while (true)
            {
                #region 1. Control and Capture

                // Execute commands at the beginning of the cycle
                while (Commands.PendingCount > 0)
                {
                    await Commands.ProcessNext();
                }

                // Check if one of the commands has requested an exit
                if (IsTaskCancellationPending)
                {
                    break;
                }

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

                #endregion

                #region 2. Handle Main Component

                // Reset the hasRendered tracker
                hasRendered[main] = false;

                // Check for out-of sync issues (i.e. after seeking), being cautious about EOF/media ended scenarios
                // in which more blocks cannot be read. (The clock is on or beyond the Duration)
                if ((Blocks[main].Count <= 0 || Blocks[main].IsInRange(wallClock) == false) && CanReadMoreBlocksOf(main))
                {
                    BufferBlocks(BufferCacheLength, true);
                    wallClock = Blocks[main].IsInRange(wallClock) ? wallClock : Blocks[main].RangeStartTime;
                    Container.Logger?.Log(MediaLogMessageType.Warning, $"SYNC CLOCK: {Clock.Position.Format()} | TGT: {wallClock.Format()}");
                    Clock.Position       = wallClock;
                    LastRenderTime[main] = TimeSpan.MinValue;

                    // a forced sync is basically a seek operation.
                    foreach (var t in all)
                    {
                        Renderers[t].Seek();
                    }
                }

                // capture the render block based on its index
                if ((renderIndex[main] = Blocks[main].IndexOf(wallClock)) >= 0)
                {
                    renderBlock[main] = Blocks[main][renderIndex[main]];

                    // render the frame if we have not rendered it
                    if ((renderBlock[main].StartTime != LastRenderTime[main] || LastRenderTime[main] == TimeSpan.MinValue) &&
                        (IsPlaying == false || wallClock.Ticks >= renderBlock[main].StartTime.Ticks))
                    {
                        // Record the render time
                        LastRenderTime[main] = renderBlock[main].StartTime;

                        // Send the block to the renderer
                        RenderBlock(renderBlock[main], wallClock, renderIndex[main]);
                        hasRendered[main] = true;
                    }
                }

                #endregion

                #region 3. Handle Auxiliary Components

                // Render each of the Media Types if it is time to do so.
                foreach (var t in auxs)
                {
                    hasRendered[t] = false;

                    // Extract the render index
                    renderIndex[t] = Blocks[t].IndexOf(wallClock);

                    // If it's a secondary stream, try to catch up with the primary stream as quickly as possible
                    // by skipping the queued blocks and adding new ones as quickly as possible.
                    while (Blocks[t].RangeEndTime <= Blocks[main].RangeStartTime &&
                           renderIndex[t] >= Blocks[t].Count - 1 &&
                           CanReadMoreBlocksOf(t))
                    {
                        if (AddNextBlock(t) == null)
                        {
                            break;
                        }
                        renderIndex[t]    = Blocks[t].IndexOf(wallClock);
                        LastRenderTime[t] = TimeSpan.MinValue;
                    }

                    // capture the latest renderindex
                    renderIndex[t] = Blocks[t].IndexOf(wallClock);

                    // Skip to next stream component if we have nothing left to do here :(
                    if (renderIndex[t] < 0)
                    {
                        continue;
                    }

                    // Retrieve the render block
                    renderBlock[t] = Blocks[t][renderIndex[t]];

                    // render the frame if we have not rendered
                    if ((renderBlock[t].StartTime != LastRenderTime[t] || LastRenderTime[t] == TimeSpan.MinValue) &&
                        (IsPlaying == false || wallClock.Ticks >= renderBlock[t].StartTime.Ticks))
                    {
                        LastRenderTime[t] = renderBlock[t].StartTime;
                        RenderBlock(renderBlock[t], wallClock, renderIndex[t]);
                        hasRendered[t] = true;
                    }
                }

                #endregion

                #region 4. Keep Blocks Buffered

                foreach (var t in all)
                {
                    if (hasRendered[t] == false)
                    {
                        continue;
                    }

                    // Add the next block if the conditions require us to do so:
                    // If rendered, then we need to discard the oldest and add the newest
                    // If the render index is greater than half, the capacity, add a new block
                    while (Blocks[t].IsFull == false || renderIndex[t] + 1 > Blocks[t].Capacity / 2)
                    {
                        if (AddNextBlock(t) == null)
                        {
                            break;
                        }
                        renderIndex[t] = Blocks[t].IndexOf(wallClock);
                    }

                    hasRendered[t] = false;
                    renderIndex[t] = Blocks[t].IndexOf(wallClock);
                }

                #endregion

                #region 5. Detect End of Media

                // Detect end of block rendering
                if (CanReadMoreBlocksOf(main) == false && renderIndex[main] == Blocks[main].Count - 1)
                {
                    if (HasMediaEnded == false)
                    {
                        // Rendered all and nothing else to read
                        Clock.Pause();
                        Clock.Position = NaturalDuration.HasTimeSpan ?
                                         NaturalDuration.TimeSpan : Blocks[main].RangeEndTime;

                        HasMediaEnded = true;
                        MediaState    = MediaState.Pause;
                        UpdatePosition(Clock.Position);
                        RaiseMediaEndedEvent();
                    }
                }
                else
                {
                    HasMediaEnded = false;
                }

                #endregion

                #region 6. Finalize the Rendering Cycle

                BlockRenderingCycle.Set();

                // Pause for a bit if we have no more commands to process.
                if (Commands.PendingCount <= 0)
                {
                    await Task.Delay(1);
                }

                #endregion
            }

            BlockRenderingCycle.Set();
        }
Exemple #15
0
        /// <summary>
        /// Sets up timing and clocks. Call this method when media components change.
        /// </summary>
        public void Setup()
        {
            lock (SyncLock)
            {
                var options    = MediaCore?.MediaOptions;
                var components = MediaCore?.Container?.Components;

                if (components == null || options == null)
                {
                    MediaCore?.LogError(Aspects.Timing, "Unable to setup the timing controller. No components or options found.");
                    Reset();
                    return;
                }

                // Save the current clocks so they can be recreated with the
                // same properties (position and speed ratio)
                var lastClocks = new MediaTypeDictionary <RealTimeClock>();
                foreach (var kvp in Clocks)
                {
                    lastClocks[kvp.Key] = kvp.Value;
                }

                try
                {
                    if (options.IsTimeSyncDisabled)
                    {
                        if (!MediaCore.Container.IsLiveStream)
                        {
                            MediaCore.LogWarning(Aspects.Timing,
                                                 $"Media options had {nameof(MediaOptions.IsTimeSyncDisabled)} set to true. This is not recommended for non-live streams.");
                        }

                        return;
                    }

                    if (!components.HasAudio || !components.HasVideo)
                    {
                        return;
                    }

                    var audioStartTime      = GetComponentStartOffset(MediaType.Audio);
                    var videoStartTime      = GetComponentStartOffset(MediaType.Video);
                    var startTimeDifference = TimeSpan.FromTicks(Math.Abs(audioStartTime.Ticks - videoStartTime.Ticks));

                    if (startTimeDifference > Constants.TimeSyncMaxOffset)
                    {
                        MediaCore.LogWarning(Aspects.Timing,
                                             $"{nameof(MediaOptions)}.{nameof(MediaOptions.IsTimeSyncDisabled)} has been ignored because the " +
                                             $"streams seem to have unrelated timing information. Time Difference: {startTimeDifference.Format()} s.");

                        options.IsTimeSyncDisabled = true;
                    }
                }
                finally
                {
                    if (components.HasAudio && components.HasVideo)
                    {
                        Clocks[MediaType.Audio] = new RealTimeClock();
                        Clocks[MediaType.Video] = new RealTimeClock();

                        Offsets[MediaType.Audio] = GetComponentStartOffset(MediaType.Audio);
                        Offsets[MediaType.Video] = GetComponentStartOffset(MediaType.Video);
                    }
                    else
                    {
                        Clocks[MediaType.Audio] = new RealTimeClock();
                        Clocks[MediaType.Video] = Clocks[MediaType.Audio];

                        Offsets[MediaType.Audio] = GetComponentStartOffset(components.HasAudio ? MediaType.Audio : MediaType.Video);
                        Offsets[MediaType.Video] = Offsets[MediaType.Audio];
                    }

                    // Subtitles will always be whatever the video data is.
                    Clocks[MediaType.Subtitle]  = Clocks[MediaType.Video];
                    Offsets[MediaType.Subtitle] = Offsets[MediaType.Video];

                    // Update from previous clocks to keep state
                    foreach (var clock in lastClocks)
                    {
                        Clocks[clock.Key].SpeedRatio = clock.Value.SpeedRatio;
                        Clocks[clock.Key].Update(clock.Value.Position);
                    }

                    // By default the continuous type is the audio component if it's a live stream
                    var continuousType = components.HasAudio && !MediaCore.Container.IsStreamSeekable
                        ? MediaType.Audio
                        : components.SeekableMediaType;

                    var discreteType = components.SeekableMediaType;
                    HasDisconnectedClocks = options.IsTimeSyncDisabled && Clocks[MediaType.Audio] != Clocks[MediaType.Video];
                    ReferenceType         = HasDisconnectedClocks ? continuousType : discreteType;

                    // The default data is what the clock reference contains
                    Clocks[MediaType.None]  = Clocks[ReferenceType];
                    Offsets[MediaType.None] = Offsets[ReferenceType];
                    IsReady = true;

                    MediaCore.State.ReportTimingStatus();
                }
            }
        }
Exemple #16
0
        /// <summary>
        /// Continuously converts frmes and places them on the corresponding
        /// block buffer. This task is responsible for keeping track of the clock
        /// and calling the render methods appropriate for the current clock position.
        /// </summary>
        /// <param name="control">The control.</param>
        /// <returns></returns>
        internal async void RunBlockRenderingWorker()
        {
            var main = Container.Components.Main.MediaType;

            // Create and reset all the tracking variables
            var hasRendered = new MediaTypeDictionary <bool>();
            var renderIndex = new MediaTypeDictionary <int>();
            var renderBlock = new MediaTypeDictionary <MediaBlock>();

            foreach (var t in Container.Components.MediaTypes)
            {
                hasRendered[t] = false;
                renderIndex[t] = -1;
                renderBlock[t] = null;
            }

            // Buffer some blocks
            BufferBlocks(BufferCacheLength);
            Clock.Position = Blocks[main].RangeStartTime;
            var clockPosition = Clock.Position;

            while (true)
            {
                await Commands.ProcessNext();

                if (IsTaskCancellationPending)
                {
                    break;
                }

                // Capture current time and render index
                BlockRenderingCycle.Reset();
                clockPosition     = Clock.Position;
                renderIndex[main] = Blocks[main].IndexOf(clockPosition);

                // Check for out-of sync issues (i.e. after seeking), being cautious about EOF/media ended scenarios
                // in which more blocks cannot be read. (The clock is on or beyond the Duration)
                if (CanReadMoreBlocksOf(main) && (Blocks[main].IsInRange(clockPosition) == false || renderIndex[main] < 0))
                {
                    BufferBlocks(BufferCacheLength);
                    Clock.Position = Blocks[main].RangeStartTime;
                    Container.Log(MediaLogMessageType.Warning,
                                  $"SYNC              CLK: {clockPosition.Debug()} | TGT: {Blocks[main].RangeStartTime.Debug()} | SET: {Clock.Position.Debug()}");

                    clockPosition     = Clock.Position;
                    renderIndex[main] = Blocks[main].IndexOf(clockPosition);
                }

                // Render each of the Media Types if it is time to do so.
                foreach (var t in Container.Components.MediaTypes)
                {
                    var blocks = Blocks[t];
                    renderIndex[t] = blocks.IndexOf(clockPosition);

                    // If it's a secondary stream, try to catch up with the primary stream as quickly as possible
                    while (t != main &&
                           blocks.RangeEndTime <= Blocks[main].RangeStartTime &&
                           renderIndex[t] >= blocks.Count - 1 &&
                           CanReadMoreBlocksOf(t))
                    {
                        if (AddNextBlock(t) == null)
                        {
                            break;
                        }

                        renderIndex[t]    = blocks.IndexOf(clockPosition);
                        LastRenderTime[t] = TimeSpan.MinValue;
                    }

                    // Skip to next stream component if we have nothing left to do here :(
                    if ((renderIndex[t] = blocks.IndexOf(clockPosition)) < 0)
                    {
                        continue;
                    }

                    // Retrieve the render block
                    renderBlock[t] = blocks[renderIndex[t]];
                    hasRendered[t] = false;

                    // render the frame if we have not rendered
                    if ((renderBlock[t].StartTime != LastRenderTime[t] || LastRenderTime[t] == TimeSpan.MinValue) &&
                        clockPosition.Ticks >= renderBlock[t].StartTime.Ticks)
                    {
                        // only render a secondary block if the primary block has been rendered first
                        // otherwise, wait for the primary block to occur and skip rendering of secondary block.
                        // TODO: still needs more logic for lip-sync quality sync :)
                        var skewTime = TimeSpan.FromTicks(renderBlock[t].StartTime.Ticks - LastRenderTime[main].Ticks);
                        if (t != main && IsPlaying && skewTime.TotalMilliseconds <= 0)
                        {
                            continue;
                        }

                        // Record the render time
                        LastRenderTime[t] = renderBlock[t].StartTime;

                        // Update the position;
                        if (t == main)
                        {
                            UpdatePosition(clockPosition);
                        }

                        RenderBlock(renderBlock[t], clockPosition, renderIndex[t]);
                        hasRendered[t] = true;
                    }

                    // Add the next block if the conditions require us to do so:
                    // If rendered, then we need to discard the oldest and add the newest
                    // If the render index is greater than half, the capacity, add a new block
                    if (hasRendered[t])
                    {
                        while (Blocks[t].IsFull == false || renderIndex[t] + 1 > Blocks[t].Capacity / 2)
                        {
                            if (AddNextBlock(t) == null)
                            {
                                break;
                            }
                            renderIndex[t] = blocks.IndexOf(clockPosition);
                        }

                        hasRendered[t] = false;
                        renderIndex[t] = Blocks[t].IndexOf(clockPosition);
                    }
                }

                // Detect end of block rendering
                if (CanReadMoreBlocksOf(main) == false && renderIndex[main] == Blocks[main].Count - 1)
                {
                    if (HasMediaEnded == false)
                    {
                        // Rendered all and nothing else to read
                        Clock.Pause();
                        Clock.Position = NaturalDuration.HasTimeSpan ?
                                         NaturalDuration.TimeSpan : Blocks[main].RangeEndTime;
                        MediaState = MediaState.Pause;
                        UpdatePosition(Clock.Position);
                        HasMediaEnded = true;
                        RaiseMediaEndedEvent();
                    }
                }
                else
                {
                    HasMediaEnded = false;
                }

                BlockRenderingCycle.Set();

                // Pause for a bit if we have no more commands to process.
                if (Commands.PendingCount <= 0)
                {
                    await Task.Delay(1);
                }
            }

            BlockRenderingCycle.Set();
        }
Exemple #17
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
        }
Exemple #18
0
        /// <summary>
        /// Continuously converts frmes and places them on the corresponding
        /// block buffer. This task is responsible for keeping track of the clock
        /// and calling the render methods appropriate for the current clock position.
        /// </summary>
        internal void RunBlockRenderingWorker()
        {
            try
            {
                #region 0. Initialize Running State

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

                // Holds the auxiliary media types
                var auxs = Container.Components.MediaTypes.Where(t => t != main).ToArray();

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

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

                // Keeps track of how many blocks were rendered in the cycle.
                var renderedBlockCount = 0;

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

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

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

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

                #endregion

                while (IsTaskCancellationPending == false)
                {
                    #region 1. Control and Capture

                    // Reset the rendered count to 0
                    renderedBlockCount = 0;

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

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

                    #endregion

                    #region 2. Handle Block Rendering

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

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

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

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

                    #endregion

                    #region 6. Finalize the Rendering Cycle

                    // Signal the rendering cycle was set.
                    BlockRenderingCycle?.Set();

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

                    // Delay the thread for a bit if we have no more stuff to process
                    if (IsSeeking == false && renderedBlockCount <= 0 && Commands.PendingCount <= 0)
                    {
                        Task.Delay(1).GetAwaiter().GetResult();
                    }

                    #endregion
                }
            }
            catch (ThreadAbortException)
            {
            }
            finally
            {
                // Always exit notifying the cycle is done.
                BlockRenderingCycle?.Set();
            }
        }
 protected override void SetUp()
 {
     base.SetUp();
     _repository = new MediaTypeDictionary <string>();
 }
 internal static MediaBlockBuffer Main(this MediaTypeDictionary <MediaBlockBuffer> blocks, MediaContainer container) =>
 blocks[container.Components?.MainMediaType ?? MediaType.None];
Exemple #21
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);
        }