using System; using System.Threading; using System.Windows; using System.Windows.Input; using WPFMediaKit.DirectShow.MediaPlayers; namespace WPFMediaKit.DirectShow.Controls; /// /// The MediaElementBase is the base WPF control for /// making custom media players. The MediaElement uses the /// D3DRenderer class for rendering video /// public abstract class MediaElementBase : D3DRenderer { private Window m_currentWindow; private bool m_windowHooked; ~MediaElementBase() { } #region Routed Events #region MediaOpened public static readonly RoutedEvent MediaOpenedEvent = EventManager.RegisterRoutedEvent("MediaOpened", RoutingStrategy.Bubble, typeof(RoutedEventHandler ), typeof(MediaElementBase)); /// /// Fires when media has successfully been opened /// public event RoutedEventHandler MediaOpened { add { AddHandler(MediaOpenedEvent, value); } remove { RemoveHandler(MediaOpenedEvent, value); } } #endregion MediaOpened #region MediaClosed public static readonly RoutedEvent MediaClosedEvent = EventManager.RegisterRoutedEvent("MediaClosed", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MediaElementBase)); /// /// Fires when media has been closed /// public event RoutedEventHandler MediaClosed { add { AddHandler(MediaClosedEvent, value); } remove { RemoveHandler(MediaClosedEvent, value); } } #endregion MediaClosed #region MediaEnded public static readonly RoutedEvent MediaEndedEvent = EventManager.RegisterRoutedEvent("MediaEnded", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MediaElementBase)); /// /// Fires when media has completed playing /// public event RoutedEventHandler MediaEnded { add { AddHandler(MediaEndedEvent, value); } remove { RemoveHandler(MediaEndedEvent, value); } } #endregion MediaEnded #endregion Routed Events #region Dependency Properties #region PlayerState public static readonly DependencyProperty PlayerStateProperty = DependencyProperty.Register("PlayerState", typeof(PlayerState), typeof(MediaElementBase), new FrameworkPropertyMetadata(PlayerState.Closed)); /// /// Get the current state of the media player /// public PlayerState PlayerState { get { return (PlayerState)GetValue(PlayerStateProperty); } protected set { SetValue(PlayerStateProperty, value); } } #endregion PlayerState #region UnloadedBehavior public static readonly DependencyProperty UnloadedBehaviorProperty = DependencyProperty.Register("UnloadedBehavior", typeof(MediaState), typeof(MediaElementBase), new FrameworkPropertyMetadata(MediaState.Close)); /// /// Defines the behavior of the control when it is unloaded /// public MediaState UnloadedBehavior { get { return (MediaState)GetValue(UnloadedBehaviorProperty); } set { SetValue(UnloadedBehaviorProperty, value); } } #endregion UnloadedBehavior #region LoadedBehavior public static readonly DependencyProperty LoadedBehaviorProperty = DependencyProperty.Register("LoadedBehavior", typeof(MediaState), typeof(MediaElementBase), new FrameworkPropertyMetadata(MediaState.Play)); /// /// Defines the behavior of the control when it is loaded /// public MediaState LoadedBehavior { get { return (MediaState)GetValue(LoadedBehaviorProperty); } set { SetValue(LoadedBehaviorProperty, value); } } #endregion LoadedBehavior #region Volume public static readonly DependencyProperty VolumeProperty = DependencyProperty.Register("Volume", typeof(double), typeof(MediaElementBase), new FrameworkPropertyMetadata(1.0d, new PropertyChangedCallback(OnVolumeChanged))); /// /// Gets or sets the audio volume. Specifies the volume, as a /// number from 0 to 1. Full volume is 1, and 0 is silence. /// public double Volume { get { return (double)GetValue(VolumeProperty); } set { SetValue(VolumeProperty, value); } } private static void OnVolumeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((MediaElementBase)d).OnVolumeChanged(e); } protected virtual void OnVolumeChanged(DependencyPropertyChangedEventArgs e) { if (HasInitialized) MediaPlayerBase.Dispatcher.BeginInvoke((Action)delegate { MediaPlayerBase.Volume = (double)e.NewValue; }); } #endregion Volume #region Balance public static readonly DependencyProperty BalanceProperty = DependencyProperty.Register("Balance", typeof(double), typeof(MediaElementBase), new FrameworkPropertyMetadata(0d, new PropertyChangedCallback(OnBalanceChanged))); /// /// Gets or sets the balance on the audio. /// The value can range from -1 to 1. The value -1 means the right channel is attenuated by 100 dB /// and is effectively silent. The value 1 means the left channel is silent. The neutral value is 0, /// which means that both channels are at full volume. When one channel is attenuated, the other /// remains at full volume. /// public double Balance { get { return (double)GetValue(BalanceProperty); } set { SetValue(BalanceProperty, value); } } private static void OnBalanceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((MediaElementBase)d).OnBalanceChanged(e); } protected virtual void OnBalanceChanged(DependencyPropertyChangedEventArgs e) { if (HasInitialized) MediaPlayerBase.Dispatcher.BeginInvoke((Action)delegate { MediaPlayerBase.Balance = (double)e.NewValue; }); } #endregion Balance #region IsPlaying private static readonly DependencyPropertyKey IsPlayingPropertyKey = DependencyProperty.RegisterReadOnly("IsPlaying", typeof(bool), typeof(MediaElementBase), new FrameworkPropertyMetadata(false)); public static readonly DependencyProperty IsPlayingProperty = IsPlayingPropertyKey.DependencyProperty; public bool IsPlaying { get { return (bool)GetValue(IsPlayingProperty); } } protected void SetIsPlaying(bool value) { SetValue(IsPlayingPropertyKey, value); } #endregion IsPlaying #endregion Dependency Properties #region Commands public static readonly RoutedCommand PlayerStateCommand = new RoutedCommand(); public static readonly RoutedCommand TogglePlayPauseCommand = new RoutedCommand(); protected virtual void OnPlayerStateCommandExecuted(object sender, ExecutedRoutedEventArgs e) { if (e.Parameter is MediaState == false) return; var state = (MediaState)e.Parameter; ExecuteMediaState(state); } protected virtual void OnCanExecutePlayerStateCommand(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } protected virtual void OnTogglePlayPauseCommandExecuted(object sender, ExecutedRoutedEventArgs e) { if (IsPlaying) Pause(); else Play(); } protected virtual void OnCanExecuteTogglePlayPauseCommand(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } #endregion Commands /// /// Notifies when the media has failed and produced an exception /// public event EventHandler MediaFailed; protected MediaElementBase() { DefaultApartmentState = ApartmentState.MTA; InitializeMediaPlayerPrivate(); Loaded += MediaElementBaseLoaded; Unloaded += MediaElementBaseUnloaded; CommandBindings.Add(new CommandBinding(PlayerStateCommand, OnPlayerStateCommandExecuted, OnCanExecutePlayerStateCommand)); CommandBindings.Add(new CommandBinding(TogglePlayPauseCommand, OnTogglePlayPauseCommandExecuted, OnCanExecuteTogglePlayPauseCommand)); } private void InitializeMediaPlayerPrivate() { InitializeMediaPlayer(); } protected MediaPlayerBase MediaPlayerBase { get; set; } protected ApartmentState DefaultApartmentState { get; set; } protected void EnsurePlayerThread() { MediaPlayerBase.EnsureThread(DefaultApartmentState); } /// /// Initializes the media player, hooking into events /// and other general setup. /// protected virtual void InitializeMediaPlayer() { if (MediaPlayerBase != null) return; MediaPlayerBase = OnRequestMediaPlayer(); EnsurePlayerThread(); if (MediaPlayerBase == null) { throw new WPFMediaKitException("OnRequestMediaPlayer cannot return null"); } /* Hook into the normal .NET events */ MediaPlayerBase.MediaOpened += OnMediaPlayerOpenedPrivate; MediaPlayerBase.MediaClosed += OnMediaPlayerClosedPrivate; MediaPlayerBase.MediaFailed += OnMediaPlayerFailedPrivate; MediaPlayerBase.MediaEnded += OnMediaPlayerEndedPrivate; MediaPlayerBase.PlayerStateChanged += OnPlayerStateChanged; /* These events fire when we get new D3Dsurfaces or frames */ MediaPlayerBase.NewAllocatorFrame += OnMediaPlayerNewAllocatorFramePrivate; MediaPlayerBase.NewAllocatorSurface += OnMediaPlayerNewAllocatorSurfacePrivate; } #region Private Event Handlers private void OnMediaPlayerFailedPrivate(object sender, MediaFailedEventArgs e) { OnMediaPlayerFailed(e); } private void OnMediaPlayerNewAllocatorSurfacePrivate(object sender, IntPtr pSurface) { OnMediaPlayerNewAllocatorSurface(pSurface); } private void OnMediaPlayerNewAllocatorFramePrivate() { OnMediaPlayerNewAllocatorFrame(); } private void OnMediaPlayerClosedPrivate() { OnMediaPlayerClosed(); } private void OnMediaPlayerEndedPrivate() { OnMediaPlayerEnded(); } private void OnMediaPlayerOpenedPrivate() { OnMediaPlayerOpened(); } #endregion Private Event Handlers /// /// Fires the MediaFailed event /// /// The failed media arguments protected void InvokeMediaFailed(MediaFailedEventArgs e) { EventHandler mediaFailedHandler = MediaFailed; if (mediaFailedHandler != null) mediaFailedHandler(this, e); } /// /// Executes when a media operation failed /// /// The failed event arguments protected virtual void OnMediaPlayerFailed(MediaFailedEventArgs e) { Dispatcher.BeginInvoke((Action)(() => SetIsPlaying(false))); InvokeMediaFailed(e); } /// /// Is executes when a new D3D surfaces has been allocated /// /// The pointer to the D3D surface protected virtual void OnMediaPlayerNewAllocatorSurface(IntPtr pSurface) { SetBackBuffer(pSurface); } /// /// Called for every frame in media that has video /// protected virtual void OnMediaPlayerNewAllocatorFrame() { InvalidateVideoImage(); } /// /// /// Called when the state of the player has changed /// /// Previous state /// New State protected virtual void OnPlayerStateChanged(PlayerState oldState, PlayerState newState) { Dispatcher.BeginInvoke((Action)((a, b) => b.PlayerState = a), newState, this); } /// /// Called when the media has been closed /// protected virtual void OnMediaPlayerClosed() { Dispatcher.BeginInvoke((Action)(() => SetIsPlaying(false))); Dispatcher.BeginInvoke((Action)(() => RaiseEvent(new RoutedEventArgs(MediaClosedEvent)))); } /// /// Called when the media has ended /// protected virtual void OnMediaPlayerEnded() { Dispatcher.BeginInvoke((Action)(() => SetIsPlaying(false))); Dispatcher.BeginInvoke((Action)(() => RaiseEvent(new RoutedEventArgs(MediaEndedEvent)))); } /// /// Executed when media has successfully been opened. /// protected virtual void OnMediaPlayerOpened() { /* Safely grab out our values */ bool hasVideo = MediaPlayerBase.HasVideo; int videoWidth = MediaPlayerBase.NaturalVideoWidth; int videoHeight = MediaPlayerBase.NaturalVideoHeight; double volume; double balance; Dispatcher.BeginInvoke((Action)delegate { /* If we have no video just black out the video * area by releasing the D3D surface */ if (!hasVideo) { SetBackBuffer(IntPtr.Zero); } SetNaturalVideoWidth(videoWidth); SetNaturalVideoHeight(videoHeight); /* Set our dp values to match the media player */ SetHasVideo(hasVideo); /* Get our DP values */ volume = Volume; balance = Balance; /* Make sure our volume and balances are set */ MediaPlayerBase.Dispatcher.BeginInvoke((Action)delegate { MediaPlayerBase.Volume = volume; MediaPlayerBase.Balance = balance; }); SetIsPlaying(true); RaiseEvent(new RoutedEventArgs(MediaOpenedEvent)); }); } /// /// Fires when the owner window is closed. Nothing will happen /// if the visual does not belong to the visual tree with a root /// of a WPF window /// private void WindowOwnerClosed(object sender, EventArgs e) { ExecuteMediaState(UnloadedBehavior); } /// /// Local handler for the Loaded event /// private void MediaElementBaseUnloaded(object sender, RoutedEventArgs e) { /* Make sure we call our virtual method every time! */ OnUnloadedOverride(); if (Application.Current == null) return; m_windowHooked = false; if (m_currentWindow == null) return; m_currentWindow.Closed -= WindowOwnerClosed; m_currentWindow = null; } /// /// Local handler for the Unloaded event /// private void MediaElementBaseLoaded(object sender, RoutedEventArgs e) { m_currentWindow = Window.GetWindow(this); if (m_currentWindow != null && !m_windowHooked) { m_currentWindow.Closed += WindowOwnerClosed; m_windowHooked = true; } OnLoadedOverride(); } /// /// Runs when the Loaded event is fired and executes /// the LoadedBehavior /// protected virtual void OnLoadedOverride() { ExecuteMediaState(LoadedBehavior); } /// /// Runs when the Unloaded event is fired and executes /// the UnloadedBehavior /// protected virtual void OnUnloadedOverride() { ExecuteMediaState(UnloadedBehavior); } /// /// Executes the actions associated to a MediaState /// /// The MediaState to execute protected void ExecuteMediaState(MediaState state) { switch (state) { case MediaState.Manual: break; case MediaState.Play: Play(); break; case MediaState.Stop: Stop(); break; case MediaState.Close: Close(); break; case MediaState.Pause: Pause(); break; default: throw new ArgumentOutOfRangeException("state"); } } public override void BeginInit() { HasInitialized = false; base.BeginInit(); } public override void EndInit() { double balance = Balance; double volume = Volume; MediaPlayerBase.Dispatcher.BeginInvoke((Action)delegate { MediaPlayerBase.Balance = balance; MediaPlayerBase.Volume = volume; }); HasInitialized = true; base.EndInit(); } public bool HasInitialized { get; protected set; } /// /// Plays the media /// public virtual void Play() { MediaPlayerBase.EnsureThread(DefaultApartmentState); MediaPlayerBase.Dispatcher.BeginInvoke((Action)(delegate { MediaPlayerBase.Play(); Dispatcher.BeginInvoke(((Action)(() => SetIsPlaying(true)))); })); } /// /// Pauses the media /// public virtual void Pause() { MediaPlayerBase.EnsureThread(DefaultApartmentState); MediaPlayerBase.Dispatcher.BeginInvoke((Action)(() => MediaPlayerBase.Pause())); SetIsPlaying(false); } /// /// Closes the media /// public virtual void Close() { SetBackBuffer(IntPtr.Zero); InvalidateVideoImage(); if (!MediaPlayerBase.Dispatcher.ShuttingOrShutDown) MediaPlayerBase.Dispatcher.BeginInvoke((Action)(delegate { MediaPlayerBase.Close(); MediaPlayerBase.Dispose(); })); SetIsPlaying(false); } /// /// Stops the media /// public virtual void Stop() { if (!MediaPlayerBase.Dispatcher.ShuttingOrShutDown) MediaPlayerBase.Dispatcher.BeginInvoke((Action)(() => MediaPlayerBase.Stop())); SetIsPlaying(false); } /// /// Called when a MediaPlayerBase is required. /// /// This method must return a valid (not null) MediaPlayerBase protected virtual MediaPlayerBase OnRequestMediaPlayer() { return null; } }