Skip to content

Commit

Permalink
Change GuiContext from system-wide to a per-element basis (#610)
Browse files Browse the repository at this point in the history
A new GUI Context object is now instantiated for each MediaElement and is populated with the current dispatcher. This allows MediaElements to be created on non-UI threads and events will be returned on the correct dispatcher
  • Loading branch information
ememadegbola authored Feb 1, 2022
1 parent bae88aa commit c7fe5ab
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 88 deletions.
42 changes: 0 additions & 42 deletions Unosquare.FFME.MediaElement/Library.cs

This file was deleted.

24 changes: 12 additions & 12 deletions Unosquare.FFME.MediaElement/MediaElement.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ internal unsafe void RaiseSubtitleDecodedEvent(AVSubtitle* subtitle, AVFormatCon
internal void PostMediaFailedEvent(Exception ex)
{
LogEventStart(nameof(MediaFailed));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
MediaFailed?.Invoke(this, new MediaFailedEventArgs(ex));
LogEventDone(nameof(MediaFailed));
Expand All @@ -267,7 +267,7 @@ internal void PostMediaFailedEvent(Exception ex)
internal void PostMediaOpenedEvent(MediaInfo mediaInfo)
{
LogEventStart(nameof(MediaOpened));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
MediaOpened?.Invoke(this, new MediaOpenedEventArgs(mediaInfo));
LogEventDone(nameof(MediaOpened));
Expand All @@ -281,7 +281,7 @@ internal void PostMediaOpenedEvent(MediaInfo mediaInfo)
internal void PostMediaReadyEvent()
{
LogEventStart(nameof(MediaReady));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
MediaReady?.Invoke(this, EventArgs.Empty);
LogEventDone(nameof(MediaReady));
Expand All @@ -295,7 +295,7 @@ internal void PostMediaReadyEvent()
internal void PostMediaClosedEvent()
{
LogEventStart(nameof(MediaClosed));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
MediaClosed?.Invoke(this, EventArgs.Empty);
LogEventDone(nameof(MediaClosed));
Expand All @@ -310,7 +310,7 @@ internal void PostMediaClosedEvent()
internal void PostMediaChangedEvent(MediaInfo mediaInfo)
{
LogEventStart(nameof(MediaChanged));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
MediaChanged?.Invoke(this, new MediaOpenedEventArgs(mediaInfo));
LogEventDone(nameof(MediaChanged));
Expand All @@ -326,7 +326,7 @@ internal void PostMediaChangedEvent(MediaInfo mediaInfo)
internal void PostPositionChangedEvent(TimeSpan oldValue, TimeSpan newValue)
{
// Event logging disabled because this happens too often.
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
PositionChanged?.Invoke(
this, new PositionChangedEventArgs(MediaCore.State, oldValue, newValue));
Expand All @@ -342,7 +342,7 @@ internal void PostPositionChangedEvent(TimeSpan oldValue, TimeSpan newValue)
internal void PostMediaStateChangedEvent(MediaPlaybackState oldValue, MediaPlaybackState newValue)
{
LogEventStart(nameof(MediaStateChanged));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
MediaStateChanged?.Invoke(this, new MediaStateChangedEventArgs(oldValue, newValue));
LogEventDone(nameof(MediaStateChanged));
Expand All @@ -356,7 +356,7 @@ internal void PostMediaStateChangedEvent(MediaPlaybackState oldValue, MediaPlayb
internal void PostBufferingStartedEvent()
{
LogEventStart(nameof(BufferingStarted));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
BufferingStarted?.Invoke(this, EventArgs.Empty);
LogEventDone(nameof(BufferingStarted));
Expand All @@ -370,7 +370,7 @@ internal void PostBufferingStartedEvent()
internal void PostBufferingEndedEvent()
{
LogEventStart(nameof(BufferingEnded));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
BufferingEnded?.Invoke(this, EventArgs.Empty);
LogEventDone(nameof(BufferingEnded));
Expand All @@ -384,7 +384,7 @@ internal void PostBufferingEndedEvent()
internal void PostSeekingStartedEvent()
{
LogEventStart(nameof(SeekingStarted));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
SeekingStarted?.Invoke(this, EventArgs.Empty);
LogEventDone(nameof(SeekingStarted));
Expand All @@ -398,7 +398,7 @@ internal void PostSeekingStartedEvent()
internal void PostSeekingEndedEvent()
{
LogEventStart(nameof(SeekingEnded));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
SeekingEnded?.Invoke(this, EventArgs.Empty);
LogEventDone(nameof(SeekingEnded));
Expand All @@ -412,7 +412,7 @@ internal void PostSeekingEndedEvent()
internal void PostMediaEndedEvent()
{
LogEventStart(nameof(MediaEnded));
Library.GuiContext.EnqueueInvoke(() =>
GuiContext.EnqueueInvoke(() =>
{
MediaEnded?.Invoke(this, EventArgs.Empty);
LogEventDone(nameof(MediaEnded));
Expand Down
5 changes: 5 additions & 0 deletions Unosquare.FFME.MediaElement/Platform/IGuiContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
/// </summary>
internal interface IGuiContext
{
/// <summary>
/// Gets the type of the context.
/// </summary>
GuiContextType Type { get; }

/// <summary>
/// Invokes a task on the GUI thread with the possibility of awaiting it.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions Unosquare.FFME.MediaElement/Platform/MediaConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void OnMediaEnded(MediaEngine sender)
{
if (Parent == null || sender == null) return;

Library.GuiContext.EnqueueInvoke(async () =>
Parent.GuiContext.EnqueueInvoke(async () =>
{
Parent.PostMediaEndedEvent();
var behavior = Parent.LoopingBehavior;
Expand Down Expand Up @@ -70,7 +70,7 @@ public void OnMediaOpened(MediaEngine sender, MediaInfo mediaInfo)
{
if (Parent == null || sender == null) return;

Library.GuiContext.EnqueueInvoke(async () =>
Parent.GuiContext.EnqueueInvoke(async () =>
{
// Set initial controller properties
// Has to be on the GUI thread as we are reading dependency properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Common\DataFrameReceivedEventArgs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\ViewModelBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Constants.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Library.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\MediaFailedEventArgs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\FrameDecodedEventArgs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\InputFormatEventArgs.cs" />
Expand Down
18 changes: 12 additions & 6 deletions Unosquare.FFME.Windows/MediaElement.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Unosquare.FFME
namespace Unosquare.FFME
{
using Common;
using Engine;
Expand Down Expand Up @@ -63,9 +63,6 @@ static MediaElement()
MediaEngine.FFmpegMessageLogged += (s, message) =>
FFmpegMessageLogged?.Invoke(typeof(MediaElement), new MediaLogMessageEventArgs(message));

// A GUI context must be registered
Library.RegisterGuiContext(GuiContext.Current);

// Content property cannot be changed.
ContentProperty.OverrideMetadata(typeof(MediaElement), new FrameworkPropertyMetadata(null, OnCoerceContentValue));

Expand All @@ -86,6 +83,11 @@ public MediaElement()

if (!Library.IsInDesignMode)
{
GuiContext = new GuiContext();

VideoView = new ImageHost(GuiContext.Type == GuiContextType.WPF && Library.EnableWpfMultiThreadedVideo)
{ Name = nameof(VideoView) };

// Setup the media engine and property updates timer
MediaCore = new MediaEngine(this, new MediaConnector(this));
MediaCore.State.PropertyChanged += (s, e) => PropertyUpdates.Add(e.PropertyName);
Expand Down Expand Up @@ -139,12 +141,16 @@ public MediaElement()
/// </summary>
public RendererOptions RendererOptions { get; } = new RendererOptions();

/// <summary>
/// The GUI context used to create this media element.
/// </summary>
internal IGuiContext GuiContext { get; private set; }

/// <summary>
/// This is the image that holds video bitmaps. It is a Hosted Image which means that in a WPF
/// GUI context, it runs on its own dispatcher (multi-threaded UI).
/// </summary>
internal ImageHost VideoView { get; } = new ImageHost(GuiContext.Current.Type == GuiContextType.WPF && Library.EnableWpfMultiThreadedVideo)
{ Name = nameof(VideoView) };
internal ImageHost VideoView { get; }

/// <summary>
/// Gets the closed captions view control.
Expand Down
31 changes: 8 additions & 23 deletions Unosquare.FFME.Windows/Platform/GuiContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Unosquare.FFME.Platform
namespace Unosquare.FFME.Platform
{
using System;
using System.Diagnostics;
Expand All @@ -16,29 +16,21 @@
internal sealed class GuiContext : IGuiContext
{
/// <summary>
/// Initializes static members of the <see cref="GuiContext"/> class.
/// Initializes a new instance of the <see cref="GuiContext"/> class.
/// </summary>
static GuiContext()
{
Current = new GuiContext();
}

/// <summary>
/// Prevents a default instance of the <see cref="GuiContext"/> class from being created.
/// </summary>
private GuiContext()
public GuiContext()
{
Thread = Thread.CurrentThread;
ThreadContext = SynchronizationContext.Current;

// Try to extract the dispatcher for the application
try { GuiDispatcher = Application.Current.Dispatcher; }
// Try to extract the dispatcher from the current thread
try { GuiDispatcher = Dispatcher.CurrentDispatcher; }
catch { /* Ignore error as app might not be available or context is not WPF */ }

// If the above was unsuccessful, try to extract the dispatcher from the current thread.
// If the above was unsuccessful, try to extract the dispatcher for the application
if (GuiDispatcher == null)
{
try { GuiDispatcher = Dispatcher.CurrentDispatcher; }
try { GuiDispatcher = Application.Current.Dispatcher; }
catch { /* Ignore error as app might not be available or context is not WPF */ }
}

Expand All @@ -49,14 +41,7 @@ private GuiContext()
IsValid = Type != GuiContextType.None;
}

/// <summary>
/// Gets the current instance.
/// </summary>
public static GuiContext Current { get; }

/// <summary>
/// Gets the type of the context.
/// </summary>
/// <inheritdoc />
public GuiContextType Type { get; }

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Unosquare.FFME.Windows/Rendering/SubtitleRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ private void SetText(string text)
}

// We fire-and-forget the update of the text
Library.GuiContext.EnqueueInvoke(() =>
MediaElement.GuiContext.EnqueueInvoke(() =>
{
lock (SyncLock)
{
Expand Down
2 changes: 1 addition & 1 deletion Unosquare.FFME.Windows/Rendering/VideoRendererBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected VideoRendererBase(MediaEngine mediaCore)
MediaCore = mediaCore;

// Set the DPI
Library.GuiContext.EnqueueInvoke(() =>
MediaElement.GuiContext.EnqueueInvoke(() =>
{
var media = MediaElement;
if (media != null)
Expand Down

0 comments on commit c7fe5ab

Please sign in to comment.