private void DetectPlaybackEnded(MediaType main) { var playbackEndClock = MediaCore.Blocks[main].Count > 0 ? MediaCore.Blocks[main].RangeEndTime : MediaCore.Timing.GetEndTime(main) ?? TimeSpan.MaxValue; // Check End of Media Scenarios if (!Commands.HasPendingCommands && MediaCore.HasDecodingEnded && !CanResumeClock(main)) { // Rendered all and nothing else to render if (State.HasMediaEnded == false) { if (Container.IsStreamSeekable) { var componentStartTime = Container.Components[main].StartTime; var actualComponentDuration = TimeSpan.FromTicks(playbackEndClock.Ticks - componentStartTime.Ticks); Container.Components[main].Duration = actualComponentDuration; } MediaCore.PausePlayback(); MediaCore.ChangePlaybackPosition(playbackEndClock); } State.MediaState = MediaPlaybackState.Stop; State.HasMediaEnded = true; } else { State.HasMediaEnded = false; } }
/// <summary> /// Performs actions when the command has been executed. /// This is useful to notify exceptions or update the state of the media. /// </summary> public override void PostProcess() { MediaCore.SendOnMediaClosed(); MediaCore.State.UpdateFixedContainerProperties(); LogReferenceCounter(MediaCore); MediaCore.Log(MediaLogMessageType.Debug, $"Command {CommandType}: Completed"); }
private void DetectPlaybackEnded(MediaType main) { var playbackEndClock = MediaCore.Blocks[main].Count > 0 ? MediaCore.Blocks[main].RangeEndTime : Container.Components.PlaybackEndTime ?? TimeSpan.MaxValue; var isAtEndOfPlayback = MediaCore.PlaybackPosition.Ticks >= playbackEndClock.Ticks || MediaCore.Timing.HasDisconnectedClocks; // Check End of Media Scenarios if (!Commands.HasPendingCommands && MediaCore.HasDecodingEnded && isAtEndOfPlayback) { // Rendered all and nothing else to render if (State.HasMediaEnded == false) { MediaCore.PausePlayback(); MediaCore.ChangePlaybackPosition(playbackEndClock); } State.MediaState = MediaPlaybackState.Stop; State.HasMediaEnded = true; } else { State.HasMediaEnded = false; } }
private void LogEventDone(RoutedEvent e) { if (WindowsPlatform.Instance.IsInDebugMode) { MediaCore?.Log(MediaLogMessageType.Trace, $"EVENT DONE : {e.Name}"); } }
internal void RaiseMediaFailedEvent(Exception ex) { LogEventStart(MediaFailedEvent); MediaCore?.Log(MediaLogMessageType.Error, $"Media Failure - {ex?.GetType()}: {ex?.Message}"); WindowsPlatform.Instance.Gui?.Invoke(DispatcherPriority.DataBind, () => { RaiseEvent(CreateExceptionRoutedEventArgs(MediaFailedEvent, this, ex)); }); LogEventDone(MediaFailedEvent); }
internal void RaiseMediaFailedEvent(Exception ex) { LogEventStart(MediaFailedEvent); MediaCore?.Log(MediaLogMessageType.Error, $"Media Failure - {ex?.GetType()}: {ex?.Message}"); GuiContext.Current.EnqueueInvoke(() => { RaiseEvent(CreateExceptionRoutedEventArgs(MediaFailedEvent, this, ex)); LogEventDone(MediaFailedEvent); }); }
/// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> /// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> private void Dispose(bool alsoManaged) { if (!m_IsDisposed) { if (alsoManaged) { MediaCore.Dispose(); } m_IsDisposed = true; } }
/// <summary> /// Opens the specified URI. /// This command gets processed in a threadpool thread asynchronously. /// </summary> /// <param name="uri">The URI.</param> /// <returns>The asynchronous task</returns> public async Task OpenAsync(Uri uri) { // Check Uri Argument if (uri == null) { MediaCore?.Log( MediaLogMessageType.Warning, $"{nameof(MediaCommandManager)}.{nameof(OpenAsync)}: '{nameof(uri)}' cannot be null"); return; // Task.CompletedTask; } if (CanExecuteCommands == false) { return; // Task.CompletedTask; } else { IsOpening.Value = true; } var command = new OpenCommand(this, uri); ExecutingCommand = command; ClearCommandQueue(); var action = new Action(() => { try { if (command.HasCompleted) { return; } command.RunSynchronously(); } catch (Exception ex) { MediaCore?.Log( MediaLogMessageType.Error, $"{nameof(MediaCommandManager)}.{nameof(OpenAsync)}: {ex.GetType()} - {ex.Message}"); } finally { ExecutingCommand?.Complete(); ExecutingCommand = null; IsOpening.Value = false; } }); await TaskEx.Run(action); }
private void LogEventDone(RoutedEvent e) { if (e.Name.Equals(nameof(BufferingEnded))) { MediaCore?.Log(MediaLogMessageType.Debug, $"EVENT DONE: {e.Name}"); return; } if (WindowsPlatform.Instance.IsInDebugMode) { MediaCore?.Log(MediaLogMessageType.Trace, $"EVENT DONE : {e.Name}"); } }
/// <inheritdoc /> protected override void ExecuteCycleLogic(CancellationToken ct) { // Update Status Properties var main = Container.Components.MainMediaType; var all = MediaCore.Renderers.Keys.ToArray(); // Ensure we have renderers ready and main blocks available if (!Initialize(all)) { return; } try { // If we are in the middle of a seek, wait for seek blocks WaitForSeekBlocks(main, ct); // Ensure the RTC clocks match the playback position AlignClocksToPlayback(main, all); // Check for and enter a sync-buffering scenario EnterSyncBuffering(main, all); // Render each of the Media Types if it is time to do so. if (MediaOptions.UseParallelRendering) { ParallelRenderBlocks.Invoke(all); } else { SerialRenderBlocks.Invoke(all); } } catch (Exception ex) { MediaCore.LogError( Aspects.RenderingWorker, "Error while in rendering worker cycle", ex); throw; } finally { DetectPlaybackEnded(main); // CatchUpWithLiveStream(); // TODO: We are on to something good here ExitSyncBuffering(main, all, ct); ReportAndResumePlayback(main); AdjustWorkerPeriod(main); } }
/// <summary> /// Performs actions when the command has been executed. /// This is useful to notify exceptions or update the state of the media. /// </summary> public override void PostProcess() { MediaCore.State.UpdateFixedContainerProperties(); if (ErrorException == null) { MediaCore.SendOnMediaChanged(); } else { MediaCore.SendOnMediaFailed(ErrorException); } MediaCore.Log(MediaLogMessageType.Debug, $"Command {CommandType}: Completed"); }
/// <summary> /// Handles the asynchronous dispose of the underlying Media Engine. /// </summary> private void HandledAsynchronousDispose() { // Dispose outside of the current thread to avoid deadlocks ThreadPool.QueueUserWorkItem(s => { MediaCore.Dispose(); // Notify the one last state GuiContext.Current.EnqueueInvoke(() => { UpdateNotificationProperties(); UpdateDependencyProperties(); }); }); }
/// <summary> /// Opens the specified custom input stream. /// This command gets processed in a threadpool thread asynchronously. /// </summary> /// <param name="stream">The custom input stream.</param> /// <returns> /// The asynchronous task /// </returns> public async Task OpenAsync(IMediaInputStream stream) { // Check Uri Argument if (stream == null) { MediaCore?.Log( MediaLogMessageType.Warning, $"{nameof(MediaCommandManager)}.{nameof(OpenAsync)}: '{nameof(stream)}' cannot be null"); return; } if (CanExecuteCommands == false) { return; } else { IsOpening.Value = true; } var command = new OpenCommand(this, stream); ExecutingCommand = command; ClearCommandQueue(); try { if (command.HasCompleted) { return; } await command.StartAsync(); } catch (Exception ex) { MediaCore?.Log( MediaLogMessageType.Error, $"{nameof(MediaCommandManager)}.{nameof(OpenAsync)}: {ex.GetType()} - {ex.Message}"); } finally { ExecutingCommand?.Complete(); ExecutingCommand = null; IsOpening.Value = false; } }
/// <summary> /// Provides the implementation for the Play Media Command. /// </summary> /// <returns>True if the command was successful</returns> private bool CommandPlayMedia() { if (!CanResumeMedia) { return(false); } foreach (var renderer in MediaCore.Renderers.Values) { renderer.Play(); } MediaCore.ResumePlayback(); return(true); }
/// <summary> /// Performs actions when the command has been executed. /// This is useful to notify exceptions or update the state of the media. /// </summary> public override void PostProcess() { MediaCore.State.UpdateFixedContainerProperties(); if (ExceptionResult == null) { MediaCore.State.UpdateMediaState(PlaybackStatus.Stop); MediaCore.SendOnMediaOpened(); } else { MediaCore.State.UpdateMediaState(PlaybackStatus.Close); MediaCore.SendOnMediaFailed(ExceptionResult); } MediaCore.Log(MediaLogMessageType.Debug, $"Command {CommandType}: Completed"); }
/// <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.PausePlayback(); foreach (var renderer in MediaCore.Renderers.Values) { renderer.OnPause(); } MediaCore.ChangePlaybackPosition(SnapPositionToBlockPosition(MediaCore.PlaybackPosition)); State.MediaState = MediaPlaybackState.Pause; return(true); }
/// <inheritdoc /> public override void PostProcess() { MediaCore.State.UpdateFixedContainerProperties(); if (ExceptionResult == null) { MediaCore.State.UpdateMediaState(PlaybackStatus.Stop); MediaCore.SendOnMediaOpened(); } else { MediaCore.ResetPosition(); MediaCore.State.UpdateMediaState(PlaybackStatus.Close); MediaCore.SendOnMediaFailed(ExceptionResult); } this.LogDebug(Aspects.EngineCommand, $"{CommandType} Completed"); }
/// <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); }
/// <summary> /// Provides the implementation for the Stop Media Command. /// </summary> /// <returns>True if the command was successful.</returns> private bool CommandStopMedia() { if (State.IsSeekable == false) { return(false); } MediaCore.ResetPlaybackPosition(); SeekMedia(new SeekOperation(TimeSpan.MinValue, SeekMode.Stop), CancellationToken.None); foreach (var renderer in MediaCore.Renderers.Values) { renderer.OnStop(); } State.MediaState = MediaPlaybackState.Stop; return(true); }
/// <summary> /// Closes the specified media. /// This command gets processed in a threadpool thread asynchronously. /// </summary> /// <returns>Returns the background task.</returns> public async Task CloseAsync() { if (CanExecuteCommands == false) { return; } else { IsClosing.Value = true; } var command = new CloseCommand(this); ExecutingCommand = command; ClearCommandQueue(); var action = new Action(() => { try { if (command.HasCompleted) { return; } command.RunSynchronously(); } catch (Exception ex) { MediaCore?.Log( MediaLogMessageType.Error, $"{nameof(MediaCommandManager)}.{nameof(CloseAsync)}: {ex.GetType()} - {ex.Message}"); } finally { ExecutingCommand?.Complete(); ExecutingCommand = null; IsClosing.Value = false; } }); await TaskEx.Run(action); }
/// <inheritdoc /> public override void PostProcess() { var m = MediaCore; if (m == null) { return; } // Update notification properties m.State.ResetAll(); m.ResetPosition(); m.State.UpdateMediaState(PlaybackStatus.Close); m.State.UpdateSource(null); // Notify media has closed MediaCore.SendOnMediaClosed(); LogReferenceCounter(); this.LogDebug(Aspects.EngineCommand, $"{CommandType} Completed"); }
/// <summary> /// Performs actions when the command has been executed. /// This is useful to notify exceptions or update the state of the media. /// </summary> public override void PostProcess() { var m = MediaCore; if (m == null) { return; } // Update notification properties m.State.ResetAll(); m.ResetPosition(); m.State.UpdateMediaState(PlaybackStatus.Close); m.State.UpdateSource(null); // Notify media has closed MediaCore.SendOnMediaClosed(); LogReferenceCounter(MediaCore); MediaCore.Log(MediaLogMessageType.Debug, $"Command {CommandType}: Completed"); }
/// <summary> /// Outputs the state of the queue /// </summary> /// <param name="operation">The operation.</param> /// <param name="outputEmpty">if set to <c>true</c> [output empty].</param> private void DumpQueue(string operation, bool outputEmpty) { if (MediaEngine.Platform.IsInDebugMode == false) { return; } lock (SyncLock) { if (outputEmpty == false && Commands.Count <= 0) { return; // Prevent output for empty commands } MediaCore.Log(MediaLogMessageType.Trace, $"Command Queue ({Commands.Count} commands): {operation}"); foreach (var c in Commands) { MediaCore.Log(MediaLogMessageType.Trace, $" {c.ToString()}"); } } }
/// <summary> /// Initializes the audio renderer. /// Call the Play Method to start reading samples /// </summary> private void Initialize() { Destroy(); // Enumerate devices. The default device is the first one so we check // that we have more than 1 device (other than the default stub) var hasAudioDevices = MediaElement.RendererOptions.UseLegacyAudioOut ? LegacyAudioPlayer.EnumerateDevices().Count > 1 : DirectSoundPlayer.EnumerateDevices().Count > 1; // Check if we have an audio output device. if (hasAudioDevices == false) { WaitForReadyEvent = null; MediaCore.Log(MediaLogMessageType.Warning, $"AUDIO OUT: No audio device found for output."); return; } // Initialize the SoundTouch Audio Processor (if available) AudioProcessor = (SoundTouch.IsAvailable == false) ? null : new SoundTouch { Channels = Convert.ToUInt32(WaveFormat.Channels), SampleRate = Convert.ToUInt32(WaveFormat.SampleRate) }; // Initialize the Audio Device AudioDevice = MediaElement.RendererOptions.UseLegacyAudioOut ? new LegacyAudioPlayer(this, MediaElement.RendererOptions.LegacyAudioDevice?.DeviceId ?? -1) as IWavePlayer : new DirectSoundPlayer(this, MediaElement.RendererOptions.DirectSoundDevice?.DeviceId ?? DirectSoundPlayer.DefaultPlaybackDeviceId); // Create the Audio Buffer SampleBlockSize = Constants.Audio.BytesPerSample * Constants.Audio.ChannelCount; var bufferLength = WaveFormat.ConvertMillisToByteSize(2000); // 2-second buffer AudioBuffer = new CircularBuffer(bufferLength); AudioDevice.Start(); }
private void EnterSyncBuffering(MediaType main, MediaType[] all) { // Determine if Sync-buffering can be potentially entered. // Entering the sync-buffering state pauses the RTC and forces the decoder make // components catch up with the main component. if (MediaCore.IsSyncBuffering || HasDisconnectedClocks || Commands.HasPendingCommands || State.MediaState != MediaPlaybackState.Play || State.HasMediaEnded || Container.IsAtEndOfStream) { return; } foreach (var t in all) { if (t == MediaType.Subtitle || t == main) { continue; } // We don't want to sync-buffer on attached pictures if (Container.Components[t].IsStillPictures) { continue; } // If we have data on the t component beyond the start time of the main // we don't need to enter sync-buffering. if (MediaCore.Blocks[t].RangeEndTime >= MediaCore.Blocks[main].RangeStartTime) { continue; } // If we are not in range of the non-main component we need to // enter sync-buffering MediaCore.SignalSyncBufferingEntered(); return; } }
/// <inheritdoc /> public override void PostProcess() { MediaCore.State.UpdateFixedContainerProperties(); if (ErrorException == null) { MediaCore.SendOnMediaChanged(); if (PlayWhenCompleted) { MediaCore.Clock.Play(); } MediaCore.State.UpdateMediaState( MediaCore.Clock.IsRunning ? PlaybackStatus.Play : PlaybackStatus.Pause); } else { MediaCore.SendOnMediaFailed(ErrorException); MediaCore.State.UpdateMediaState(PlaybackStatus.Pause); } this.LogDebug(Aspects.EngineCommand, $"{CommandType} Completed"); }
/// <summary> /// Processes the next command in the command queue. /// This method is called in every block rendering cycle. /// </summary> public void ProcessNext() { DumpQueue($"Before {nameof(ProcessNext)}", false); if (MediaCore.IsTaskCancellationPending) { return; } MediaCommand command = null; lock (SyncLock) { if (Commands.Count == 0) { return; } command = Commands[0]; Commands.RemoveAt(0); } try { ExecutingCommand = command; command.RunSynchronously(); DumpQueue($"After {nameof(ProcessNext)}", false); } catch (Exception ex) { MediaCore?.Log(MediaLogMessageType.Error, $"{ex.GetType()}: {ex.Message}"); throw; } finally { ExecutingCommand = null; } }
/// <summary> /// Renders the specified media block. /// This needs to return immediately so the calling thread is not disturbed. /// </summary> /// <param name="mediaBlock">The media block.</param> /// <param name="clockPosition">The clock position.</param> public void Render(MediaBlock mediaBlock, TimeSpan clockPosition) { var block = mediaBlock as VideoBlock; if (block == null) { return; } if (IsRenderingInProgress.Value == true) { if (MediaCore?.State.IsPlaying ?? false) { MediaCore?.Log(MediaLogMessageType.Debug, $"{nameof(VideoRenderer)}: Frame skipped at {mediaBlock.StartTime}"); } return; } // Flag the start of a rendering cycle IsRenderingInProgress.Value = true; // Send the packets to the CC renderer MediaElement?.CaptionsView?.SendPackets(block, MediaCore); // Create an action that holds GUI thread actions var foregroundAction = new Action(() => { MediaElement?.CaptionsView?.Render(MediaElement.ClosedCaptionsChannel, clockPosition); ApplyLayoutTransforms(block); }); var canStartForegroundTask = MediaElement.VideoView.ElementDispatcher != MediaElement.Dispatcher; var foregroundTask = canStartForegroundTask ? MediaElement.Dispatcher.InvokeAsync(foregroundAction) : null; // Ensure the target bitmap can be loaded MediaElement?.VideoView?.InvokeAsync(DispatcherPriority.Render, () => { if (block.IsDisposed) { IsRenderingInProgress.Value = false; return; } // Run the foreground action if we could not start it in parallel. if (foregroundTask == null) { try { foregroundAction(); } catch (Exception ex) { MediaElement?.MediaCore?.Log( MediaLogMessageType.Error, $"{nameof(VideoRenderer)} {ex.GetType()}: {nameof(Render)} layout/CC failed. {ex.Message}."); } } try { // Render the bitmap data var bitmapData = LockTargetBitmap(block); if (bitmapData != null) { LoadTargetBitmapBuffer(bitmapData, block); MediaElement.RaiseRenderingVideoEvent(block, bitmapData, clockPosition); RenderTargetBitmap(bitmapData, clockPosition); } } catch (Exception ex) { MediaElement?.MediaCore?.Log( MediaLogMessageType.Error, $"{nameof(VideoRenderer)} {ex.GetType()}: {nameof(Render)} bitmap failed. {ex.Message}."); } finally { if (foregroundTask != null) { try { foregroundTask?.Wait(); } catch (Exception ex) { MediaElement?.MediaCore?.Log( MediaLogMessageType.Error, $"{nameof(VideoRenderer)} {ex.GetType()}: {nameof(Render)} layout/CC failed. {ex.Message}."); } } // Always reset the rendering state IsRenderingInProgress.Value = false; } }); }
private void AlignClocksToPlayback(MediaType main, MediaType[] all) { // we don't want to disturb the clock or align it if we are not ready if (Commands.HasPendingCommands) { return; } if (HasDisconnectedClocks) { foreach (var t in all) { if (t == MediaType.Subtitle) { continue; } var compBlocks = MediaCore.Blocks[t]; var compPosition = MediaCore.Timing.GetPosition(t); if (compBlocks.Count <= 0) { MediaCore.PausePlayback(t, false); if (MediaCore.Timing.GetIsRunning(t)) { this.LogDebug(Aspects.Timing, $"CLOCK PAUSED: {t} clock was paused at {compPosition.Format()} because no decoded {t} content was found"); } continue; } // Don't let the RTC lag behind the blocks or move beyond them if (compPosition.Ticks < compBlocks.RangeStartTime.Ticks) { MediaCore.ChangePlaybackPosition(compBlocks.RangeStartTime, t, false); this.LogDebug(Aspects.Timing, $"CLOCK BEHIND: {t} clock was {compPosition.Format()}. It was updated to {compBlocks.RangeStartTime.Format()}"); } else if (compPosition.Ticks > compBlocks.RangeEndTime.Ticks) { if (t != MediaType.Audio) { MediaCore.PausePlayback(t, false); } MediaCore.ChangePlaybackPosition(compBlocks.RangeEndTime, t, false); this.LogDebug(Aspects.Timing, $"CLOCK AHEAD : {t} clock was {compPosition.Format()}. It was updated to {compBlocks.RangeEndTime.Format()}"); } } return; } // Get a reference to the main blocks. // The range will be 0 if there are no blocks. var blocks = MediaCore.Blocks[main]; var position = MediaCore.PlaybackPosition; if (blocks.Count == 0) { // We have no main blocks in range. All we can do is pause the clock if (MediaCore.Timing.IsRunning) { this.LogDebug(Aspects.Timing, $"CLOCK PAUSED: playback clock was paused at {position.Format()} because no decoded {main} content was found"); } MediaCore.PausePlayback(); return; } if (position.Ticks < blocks.RangeStartTime.Ticks) { // Don't let the RTC lag behind what is available on the main component MediaCore.ChangePlaybackPosition(blocks.RangeStartTime); this.LogTrace(Aspects.Timing, $"CLOCK BEHIND: playback clock was {position.Format()}. It was updated to {blocks.RangeStartTime.Format()}"); } else if (position.Ticks > blocks.RangeEndTime.Ticks) { // Don't let the RTC move beyond what is available on the main component MediaCore.PausePlayback(); MediaCore.ChangePlaybackPosition(blocks.RangeEndTime); this.LogTrace(Aspects.Timing, $"CLOCK AHEAD : playback clock was {position.Format()}. It was updated to {blocks.RangeEndTime.Format()}"); } }
private void ExitSyncBuffering(MediaType main, MediaType[] all, CancellationToken ct) { // Don't exit syc-buffering if we are not in syncbuffering if (!MediaCore.IsSyncBuffering) { return; } // Detect if an exit from Sync Buffering is required var canExitSyncBuffering = MediaCore.Blocks[main].Count > 0; var mustExitSyncBuffering = ct.IsCancellationRequested || MediaCore.HasDecodingEnded || Container.IsAtEndOfStream || State.HasMediaEnded || Commands.HasPendingCommands || HasDisconnectedClocks; try { if (mustExitSyncBuffering) { this.LogDebug(Aspects.ReadingWorker, $"SYNC-BUFFER: 'must exit' condition met."); return; } if (!canExitSyncBuffering) { return; } foreach (var t in all) { if (t == MediaType.Subtitle || t == main) { continue; } // We don't want to consider sync-buffer on attached pictures if (Container.Components[t].IsStillPictures) { continue; } // If we don't have data on the t component beyond the mid time of the main // we can't exit sync-buffering. if (MediaCore.Blocks[t].RangeEndTime < MediaCore.Blocks[main].RangeMidTime) { canExitSyncBuffering = false; break; } } } finally { // Exit sync-buffering state if we can or we must if (mustExitSyncBuffering || canExitSyncBuffering) { AlignClocksToPlayback(main, all); MediaCore.SignalSyncBufferingExited(); } } }