diff --git a/build/PackageDiffIgnore.xml b/build/PackageDiffIgnore.xml index 2a99cdfd83085919bce9f4e67c5dfc9a0b74069b..fb6af43feda7f0e77b9fa924fb38c9764f3382b4 100644 --- a/build/PackageDiffIgnore.xml +++ b/build/PackageDiffIgnore.xml @@ -9491,6 +9491,7 @@ + diff --git a/src/Uno.Foundation/Metadata/ApiInformation.cs b/src/Uno.Foundation/Metadata/ApiInformation.cs index 733168b1e471196dbc638996ce2e786a170f6499..f1ff95761a1b3606350415e584f52069a45ffa05 100644 --- a/src/Uno.Foundation/Metadata/ApiInformation.cs +++ b/src/Uno.Foundation/Metadata/ApiInformation.cs @@ -52,6 +52,9 @@ public partial class ApiInformation } } + internal static bool IsMethodPresent(Type type, string methodName) + => IsImplementedByUno(type?.GetMethod(methodName)); + public static bool IsMethodPresent(string typeName, string methodName) => IsImplementedByUno( GetValidType(typeName) @@ -63,11 +66,17 @@ public partial class ApiInformation ?.GetMethods() ?.FirstOrDefault(m => m.Name == methodName && m.GetParameters().Length == inputParameterCount)); + internal static bool IsEventPresent(Type type, string methodName) + => IsImplementedByUno(type?.GetEvent(methodName)); + public static bool IsEventPresent(string typeName, string eventName) => IsImplementedByUno( GetValidType(typeName) ?.GetEvent(eventName)); + internal static bool IsPropertyPresent(Type type, string methodName) + => IsImplementedByUno(type?.GetProperty(methodName)); + public static bool IsPropertyPresent(string typeName, string propertyName) => IsImplementedByUno( GetValidType(typeName) diff --git a/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.MediaPlayer.cs b/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.MediaPlayer.cs index f27f94d4efd6145cb03b515817052709355c887b..999349bb4c8c39e3f2db16d89570a5cfd72c4754 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.MediaPlayer.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.MediaPlayer.cs @@ -12,19 +12,16 @@ using Windows.UI.Input; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Input; +using _MediaPlayer = Windows.Media.Playback.MediaPlayer; // alias to avoid same name root namespace from ios/macos + namespace Windows.UI.Xaml.Controls { public partial class MediaTransportControls : Control { - private Windows.Media.Playback.MediaPlayer? _mediaPlayer; + private _MediaPlayer? _mediaPlayer; private SerialDisposable _mediaPlayerSubscriptions = new(); - // The player will be temporarily paused while the progress slider is being manipulated. - // This flag prevents the update of play/pause button while that happens. - private bool _skipPlayPauseStateUpdate; - private bool _isScrubbing; - - internal void SetMediaPlayer(Windows.Media.Playback.MediaPlayer mediaPlayer) + internal void SetMediaPlayer(_MediaPlayer mediaPlayer) { _mediaPlayerSubscriptions.Disposable = null; @@ -38,66 +35,118 @@ namespace Windows.UI.Xaml.Controls _mpe = mediaPlayerElement; } - private void BindMediaPlayer() + private void BindMediaPlayer(bool updateAllVisualAndPropertyStates = true) { if (_mediaPlayer is null) { return; } - _mediaPlayerSubscriptions.Disposable = null; - - _mediaPlayer.PlaybackSession.PlaybackStateChanged += OnPlaybackStateChanged; - _mediaPlayer.PlaybackSession.BufferingProgressChanged += OnBufferingProgressChanged; - _mediaPlayer.PlaybackSession.NaturalDurationChanged += OnNaturalDurationChanged; - _mediaPlayer.PlaybackSession.PositionChanged += OnPositionChanged; + var disposables = new CompositeDisposable(); + _mediaPlayerSubscriptions.Disposable = disposables; + + //Bind(_mediaPlayer, x => x.MediaOpened += OnMediaOpened, x => x.MediaOpened -= OnMediaOpened); + //Bind(_mediaPlayer, x => x.MediaFailed += OnMediaFailed, x => x.MediaFailed -= OnMediaFailed); + //Bind(_mediaPlayer, x => x.VolumeChanged += OnVolumeChanged, x => x.VolumeChanged -= OnVolumeChanged); + //IFC_RETURN(spMediaPlayerExt->add_SourceChanged( + //IFC_RETURN(spMediaPlayerExt->add_IsMutedChanged( + Bind(_mediaPlayer.PlaybackSession, x => x.PositionChanged += OnPositionChanged, x => x.PositionChanged -= OnPositionChanged); + //Bind(_mediaPlayer.PlaybackSession, x => x.DownloadProgressChanged += OnDownloadProgressChanged, x => x.PlaybackStateChanged -= OnDownloadProgressChanged); + Bind(_mediaPlayer.PlaybackSession, x => x.PlaybackStateChanged += OnPlaybackStateChanged, x => x.PlaybackStateChanged -= OnPlaybackStateChanged); + Bind(_mediaPlayer.PlaybackSession, x => x.NaturalDurationChanged += OnNaturalDurationChanged, x => x.NaturalDurationChanged -= OnNaturalDurationChanged); + Bind(_mediaPlayer.PlaybackSession, x => x.BufferingProgressChanged += OnBufferingProgressChanged, x => x.BufferingProgressChanged -= OnBufferingProgressChanged); + //IFC_RETURN(spPlayCommandBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spPauseCommandBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spNextCommandBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spPrevCommandBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spFastforwardCommandBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spRewindCommandBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spPositionBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spRateBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spAutoRepeatBehaviour->add_IsEnabledChanged( + //IFC_RETURN(spBreakManager->add_BreakStarted( + //IFC_RETURN(spBreakManager->add_BreakEnded( + //IFC_RETURN(spBreakManager->add_BreakSkipped( + //IFC_RETURN(spBreakPlaybackSession->add_PlaybackStateChanged( + //IFC_RETURN(spBreakPlaybackSession->add_PositionChanged( + //IFC_RETURN(spBreakPlaybackSession->add_DownloadProgressChanged( + //IFC_RETURN(MediaPlayerExtension_AddIsLoopingEnabledChanged( + + if (updateAllVisualAndPropertyStates) + { + UpdateVisualState(); + UpdateMediaControlAllStates(); + } - _mediaPlayerSubscriptions.Disposable = Disposable.Create(() => + void Bind(T? target, Action addHandler, Action removeHandler) { - if (_mediaPlayer is { }) + if (target is { }) { - _mediaPlayer.PlaybackSession.PlaybackStateChanged -= OnPlaybackStateChanged; - _mediaPlayer.PlaybackSession.BufferingProgressChanged -= OnBufferingProgressChanged; - _mediaPlayer.PlaybackSession.NaturalDurationChanged -= OnNaturalDurationChanged; - _mediaPlayer.PlaybackSession.PositionChanged -= OnPositionChanged; + addHandler(target); + disposables.Add(() => removeHandler(target)); } - }); - - UpdateAllVisualStates(); - UpdateMediaControlAllStates(); + } } private void OnPlaybackStateChanged(MediaPlaybackSession sender, object args) { - var state = (MediaPlaybackState)args; - - switch (state) + if (sender is { } session) { - case MediaPlaybackState.Opening: - case MediaPlaybackState.Paused: - case MediaPlaybackState.None: - if (_mediaPlayer is not null) + var currentState = (MediaPlaybackState)args; + var previousIsPlaying = m_isPlaying; + var previousIsBuffering = m_isBuffering; + + m_sourceLoaded = currentState + is not MediaPlaybackState.None + and not MediaPlaybackState.Opening; + if (currentState != MediaPlaybackState.Buffering) + { + m_isPlaying = currentState == MediaPlaybackState.Playing; + } + + m_isBuffering = currentState == MediaPlaybackState.Buffering; + +#if !HAS_UNO + // If playing state changed, toggle position timer + // to avoid ticking if position is not updating + if (previousIsPlaying != m_isPlaying) + { + if (m_isPlaying) { - _mediaPlayer.PlaybackSession.UpdateTimePositionRate = 0; + StartPositionUpdateTimer(); } - CancelControlsVisibilityTimer(); - break; - case MediaPlaybackState.Playing: - if (_mediaPlayer is not null) + else { - _mediaPlayer.PlaybackSession.UpdateTimePositionRate = 0; + StopPositionUpdateTimer(); } - ResetControlsVisibilityTimer(); - break; - case MediaPlaybackState.Buffering: - break; - } - - // skip transition, as the event may originate from non-ui thread - UpdateMediaStates(useTransition: false); - if (!_skipPlayPauseStateUpdate) - { - UpdatePlayPauseStates(useTransition: false); + } +#endif + if ((previousIsPlaying != m_isPlaying || previousIsBuffering != m_isBuffering) && !m_isInScrubMode) + { + if ((m_isPlaying && !m_isBuffering || m_shouldDismissControlPanel) /*&& !m_isthruScrubber*/) + { + m_shouldDismissControlPanel = true; + HideControlPanel(); + } + else + { + ShowControlPanel(); + } + } +#if !HAS_UNO + if (!m_isPlaying) + { + IFC(ResetTrickMode()); + } + // Timing issues still Natural duration values zero even after source loaded. + if (m_sourceLoaded && m_naturalDuration.TimeSpan.Duration == 0) + { + wf::TimeSpan value; + IFC(spPlaybackSession->get_NaturalDuration(&value)); + m_naturalDuration.TimeSpan = value; + } +#endif + UpdateVisualState(); } } @@ -138,17 +187,16 @@ namespace Windows.UI.Xaml.Controls { var elapsed = args as TimeSpan? ?? TimeSpan.Zero; m_tpTimeElapsedElement.Maybe(p => p.Text = FormatTime(elapsed)); + if ( #if __ANDROID__ || __IOS__ || __MACOS__ - if (m_tpMediaPositionSlider is not null) - { - m_tpMediaPositionSlider.Value = elapsed.TotalSeconds; - } -#else - if (!_isScrubbing && m_tpMediaPositionSlider is not null) + !m_isInScrubMode && +#endif + m_tpMediaPositionSlider is not null) { + m_positionUpdateUIOnly = true; m_tpMediaPositionSlider.Value = elapsed.TotalSeconds; + m_positionUpdateUIOnly = false; } -#endif if (_mediaPlayer is not null) { var remaining = _mediaPlayer.PlaybackSession.NaturalDuration - elapsed; @@ -158,6 +206,36 @@ namespace Windows.UI.Xaml.Controls }); } + private void OnMediaPositionSliderValueChanged(object sender, RangeBaseValueChangedEventArgs e) + { + if (m_positionUpdateUIOnly || m_isInScrubMode) + { + // ignore events coming from PlaybackSession.PositionChanged or Stop button + // the remainder should be coming from the user. + + // we also want to ignore the events in scrub mode. + // the final change will be handled in ThumbOnDragCompleted. + return; + } + + if (m_tpMediaPositionSlider is { } && + !double.IsNaN(m_tpMediaPositionSlider.Value) && + _mediaPlayer is { }) + { + _wasPlaying = _mediaPlayer.PlaybackSession.IsPlaying; + EnterScrubbingMode(); + + _mediaPlayer.Pause(); + _mediaPlayer.PlaybackSession.Position = TimeSpan.FromSeconds(m_tpMediaPositionSlider.Value); + + ExitScrubbingMode(); + if (_wasPlaying) + { + _mediaPlayer.Play(); + } + } + } + public async Task UpdateTimePosition(TimeSpan elapsed) { if (_mediaPlayer is not null @@ -177,21 +255,24 @@ namespace Windows.UI.Xaml.Controls private void ResetProgressSlider() { - var elapsed = TimeSpan.Zero; - m_tpTimeElapsedElement.Maybe(p => p.Text = FormatTime(elapsed)); - + if (m_tpTimeElapsedElement is TextBlock timeElapsedElement) + { + timeElapsedElement.Text = FormatTime(TimeSpan.Zero); + } if (m_tpMediaPositionSlider is not null) { - m_tpMediaPositionSlider.Value = elapsed.TotalSeconds; + m_positionUpdateUIOnly = true; + m_tpMediaPositionSlider.Value = 0; + m_positionUpdateUIOnly = false; } - if (_mediaPlayer is not null) { - var remaining = _mediaPlayer.PlaybackSession.NaturalDuration - elapsed; - m_tpTimeRemainingElement.Maybe(p => p.Text = FormatTime(remaining)); - - _mediaPlayer.PlaybackSession.Position = elapsed; - _mediaPlayer.PlaybackSession.PositionFromPlayer = elapsed; + if (m_tpTimeRemainingElement is TextBlock timeRemainingElement) + { + timeRemainingElement.Text = FormatTime(_mediaPlayer.PlaybackSession.NaturalDuration); + } + _mediaPlayer.PlaybackSession.Position = TimeSpan.Zero; + _mediaPlayer.PlaybackSession.PositionFromPlayer = TimeSpan.Zero; } } @@ -215,7 +296,7 @@ namespace Windows.UI.Xaml.Controls private void Stop(object sender, RoutedEventArgs e) { - _skipPlayPauseStateUpdate = false; + m_isInScrubMode = false; ResetProgressSlider(); _mediaPlayer?.Pause(); @@ -295,37 +376,16 @@ namespace Windows.UI.Xaml.Controls ResetControlsVisibilityTimer(); } -#if __ANDROID__ || __IOS__ || __MACOS__ private void ThumbOnDragStarted(object sender, DragStartedEventArgs dragStartedEventArgs) { - if (_mediaPlayer != null && !_isScrubbing) + if (_mediaPlayer != null && !m_isInScrubMode) { _wasPlaying = _mediaPlayer.PlaybackSession.IsPlaying; - _skipPlayPauseStateUpdate = true; - _isScrubbing = true; + EnterScrubbingMode(); _mediaPlayer.Pause(); } } -#else - private void OnPointerEntered(object sender, PointerRoutedEventArgs e) - { - if (_mediaPlayer != null && !_isScrubbing) - { - _wasPlaying = _mediaPlayer.PlaybackSession.IsPlaying; - _isScrubbing = true; - } - } - - private void OnPointerExited(object sender, PointerRoutedEventArgs e) - { - _isScrubbing = false; - if (_wasPlaying) - { - _mediaPlayer?.Play(); - } - } -#endif private void ThumbOnDragCompleted(object sender, DragCompletedEventArgs dragCompletedEventArgs) { @@ -333,46 +393,65 @@ namespace Windows.UI.Xaml.Controls { return; } + if (_mediaPlayer != null) { - if (_mediaPlayer.PlaybackSession.IsPlaying) + if (m_isPlaying || m_isBuffering) { - _skipPlayPauseStateUpdate = true; - _isScrubbing = true; - _wasPlaying = true; + EnterScrubbingMode(); _mediaPlayer.Pause(); } + _mediaPlayer.PlaybackSession.Position = TimeSpan.FromSeconds(m_tpMediaPositionSlider.Value); + if (_wasPlaying) { _mediaPlayer.Play(); } } - _isScrubbing = false; - _skipPlayPauseStateUpdate = false; + + ExitScrubbingMode(); } - public void TappedProgressSlider(object sender, RoutedEventArgs e) + private void EnterScrubbingMode() { - _isScrubbing = true; - if (m_tpMediaPositionSlider is null || double.IsNaN(m_tpMediaPositionSlider.Value)) + if (m_transportControlsEnabled && _mediaPlayer is { }) { - _isScrubbing = false; - return; + if ( +#if !HAS_UNO + !m_isAudioOnly && + !IsLiveContent() && +#endif + !m_isInScrubMode) + { + m_currentPlaybackRate = _mediaPlayer.PlaybackSession.PlaybackRate; + _mediaPlayer.PlaybackSession.PlaybackRate = 0; +#if !HAS_UNO + EnableValueChangedEventThrottlingOnSliderAutomation(false); +#endif + m_isInScrubMode = true; + } } - if (_mediaPlayer != null) + } + + private void ExitScrubbingMode() + { + if (m_transportControlsEnabled && _mediaPlayer is { }) { - _wasPlaying = _mediaPlayer.PlaybackSession.IsPlaying; - _skipPlayPauseStateUpdate = true; - _mediaPlayer.Pause(); - _mediaPlayer.PlaybackSession.Position = TimeSpan.FromSeconds(m_tpMediaPositionSlider.Value); - if (_wasPlaying) + if ( +#if !HAS_UNO + !m_isAudioOnly && + !IsLiveContent() && +#endif + m_isInScrubMode) { - _mediaPlayer.Play(); + _mediaPlayer.PlaybackSession.PlaybackRate = m_currentPlaybackRate; +#if !HAS_UNO + EnableValueChangedEventThrottlingOnSliderAutomation(true); +#endif + m_isInScrubMode = false; } - _skipPlayPauseStateUpdate = false; } - _isScrubbing = false; } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.cs b/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.cs index 920ccd16f1547dc7f702a17740ccbb0a2a248227..ad32985335dafdb7b480b502828ba4a94ddbbfba 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.cs @@ -2,7 +2,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; using Windows.Foundation; +using Windows.Foundation.Metadata; using Windows.Media.Playback; using Windows.UI.Core; using Windows.UI.Xaml.Automation; @@ -11,10 +15,7 @@ using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using DirectUI; using Uno.Disposables; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using Uno.Extensions; -using Windows.Foundation.Metadata; #if HAS_UNO_WINUI using Microsoft.UI.Input; @@ -24,7 +25,6 @@ using PointerDeviceType = Windows.Devices.Input.PointerDeviceType; using Uno.UI.Xaml.Core; #endif - #if __IOS__ using UIKit; #elif __MACOS__ @@ -33,394 +33,56 @@ using AppKit; using Uno.UI; #endif +using _MediaPlayer = Windows.Media.Playback.MediaPlayer; // alias to avoid same name root namespace from ios/macos + namespace Windows.UI.Xaml.Controls { - public partial class MediaTransportControls - { - private static class TemplateParts - { - public const string ControlPanelGrid = nameof(ControlPanelGrid); // Grid - public const string TimeElapsedElement = nameof(TimeElapsedElement); // TextBlock - public const string TimeRemainingElement = nameof(TimeRemainingElement); // TextBlock - public const string ProgressSlider = nameof(ProgressSlider); // Slider - public const string PlayPauseButton = nameof(PlayPauseButton); // AppBarButton - public const string PlayPauseButtonOnLeft = nameof(PlayPauseButtonOnLeft); // AppBarButton - public const string FullWindowButton = nameof(FullWindowButton); // AppBarButton - public const string ZoomButton = nameof(ZoomButton); // AppBarButton - public const string ErrorTextBlock = nameof(ErrorTextBlock); // TextBlock - public const string MediaControlsCommandBar = nameof(MediaControlsCommandBar); // CommandBar - public const string VolumeFlyout = nameof(VolumeFlyout); // Flyout - public const string HorizontalVolumeSlider = nameof(HorizontalVolumeSlider); // ? - public const string VerticalVolumeSlider = nameof(VerticalVolumeSlider); // ? - public const string VolumeSlider = nameof(VolumeSlider); // Slider - public const string AudioSelectionButton = nameof(AudioSelectionButton); // ? - public const string AudioTracksSelectionButton = nameof(AudioTracksSelectionButton); // AppBarButton - public const string AvailableAudioTracksMenuFlyout = nameof(AvailableAudioTracksMenuFlyout); // ? - public const string AvailableAudioTracksMenuFlyoutTarget = nameof(AvailableAudioTracksMenuFlyoutTarget); // ? - public const string CCSelectionButton = nameof(CCSelectionButton); // AppBarButton - public const string PlaybackRateButton = nameof(PlaybackRateButton); // AppBarButton - public const string VolumeButton = nameof(VolumeButton); // ? - public const string AudioMuteButton = nameof(AudioMuteButton); // AppBarButton - public const string VolumeMuteButton = nameof(VolumeMuteButton); // AppBarButton - public const string BufferingProgressBar = nameof(BufferingProgressBar); // ProgressBar - public const string FastForwardButton = nameof(FastForwardButton); // AppBarButton - public const string RewindButton = nameof(RewindButton); // AppBarButton - public const string StopButton = nameof(StopButton); // AppBarButton - public const string CastButton = nameof(CastButton); // AppBarButton - public const string SkipForwardButton = nameof(SkipForwardButton); // AppBarButton - public const string SkipBackwardButton = nameof(SkipBackwardButton); // AppBarButton - public const string NextTrackButton = nameof(NextTrackButton); // AppBarButton - public const string PreviousTrackButton = nameof(PreviousTrackButton); // AppBarButton - public const string RepeatButton = nameof(RepeatButton); // AppBarToggleButton - public const string CompactOverlayButton = nameof(CompactOverlayButton); // AppBarButton - public const string PlaybackRateListView = nameof(PlaybackRateListView); // AppBarButton - public const string PlaybackRateFlyout = nameof(PlaybackRateFlyout); // AppBarButton - - public const string LeftSeparator = nameof(LeftSeparator); // AppBarSeparator - public const string RightSeparator = nameof(RightSeparator); // AppBarSeparator - - // Used by uno only: - public const string RootGrid = nameof(RootGrid); - public const string ControlPanel_ControlPanelVisibilityStates_Border = nameof(ControlPanel_ControlPanelVisibilityStates_Border); - public const string MediaTransportControls_Timeline_Border = nameof(MediaTransportControls_Timeline_Border); - - // MediaControlsCommandBar template children - public const string MoreButton = nameof(MoreButton); // Button - // ProgressSlider template children - public const string DownloadProgressIndicator = nameof(DownloadProgressIndicator); // ProgressBar - public const string HorizontalThumb = nameof(HorizontalThumb); // Thumb - } - - private static class UIAKeys - { - // MediaElement Transport Controls: UI Automation Name / Tooltip text - public const string UIA_MEDIA_PLAY = nameof(UIA_MEDIA_PLAY); // "Play" - public const string UIA_MEDIA_PAUSE = nameof(UIA_MEDIA_PAUSE); // "Pause" - public const string UIA_MEDIA_TIME_ELAPSED = nameof(UIA_MEDIA_TIME_ELAPSED); // "Time elapsed" - public const string UIA_MEDIA_TIME_REMAINING = nameof(UIA_MEDIA_TIME_REMAINING); // "Time remaining" - public const string UIA_MEDIA_DOWNLOAD_PROGRESS = nameof(UIA_MEDIA_DOWNLOAD_PROGRESS); // "Download Progress" - public const string UIA_MEDIA_BUFFERING_PROGRESS = nameof(UIA_MEDIA_BUFFERING_PROGRESS); // "Buffering Progress" - public const string UIA_MEDIA_SEEK = nameof(UIA_MEDIA_SEEK); // "Seek" - public const string UIA_MEDIA_MUTE = nameof(UIA_MEDIA_MUTE); // "Mute" - public const string UIA_MEDIA_UNMUTE = nameof(UIA_MEDIA_UNMUTE); // "Unmute" - public const string UIA_MEDIA_VOLUME = nameof(UIA_MEDIA_VOLUME); // "Volume" - public const string UIA_MEDIA_ERROR = nameof(UIA_MEDIA_ERROR); // "Error" - public const string TEXT_MEDIA_AUDIO_TRACK_UNTITLED = nameof(TEXT_MEDIA_AUDIO_TRACK_UNTITLED); // "untitled" - public const string TEXT_MEDIA_AUDIO_TRACK_SELECTED = nameof(TEXT_MEDIA_AUDIO_TRACK_SELECTED); // "(On)" - Appended to name of currently selected audio track - public const string TEXT_MEDIA_AUDIO_TRACK_SEPARATOR = nameof(TEXT_MEDIA_AUDIO_TRACK_SEPARATOR); // " - " - Used to separate pieces of metadata in audio track name - public const string UIA_MEDIA_FULLSCREEN = nameof(UIA_MEDIA_FULLSCREEN); // "Full Screen" - public const string UIA_MEDIA_EXIT_FULLSCREEN = nameof(UIA_MEDIA_EXIT_FULLSCREEN); // "Exit Full Screen" - public const string UIA_MEDIA_AUDIO_SELECTION = nameof(UIA_MEDIA_AUDIO_SELECTION); // "Show audio selection menu" - public const string UIA_MEDIA_CC_SELECTION = nameof(UIA_MEDIA_CC_SELECTION); // "Show closed caption menu" - public const string TEXT_MEDIA_CC_OFF = nameof(TEXT_MEDIA_CC_OFF); // "Off" - public const string UIA_MEDIA_PLAYBACKRATE = nameof(UIA_MEDIA_PLAYBACKRATE); // "Show playback rate list" - public const string UIA_MEDIA_FASTFORWARD = nameof(UIA_MEDIA_FASTFORWARD); // "Fast forward" - public const string UIA_MEDIA_REWIND = nameof(UIA_MEDIA_REWIND); // "Rewind" - public const string UIA_MEDIA_STOP = nameof(UIA_MEDIA_STOP); // "Stop" - public const string UIA_MEDIA_CAST = nameof(UIA_MEDIA_CAST); // "Cast to Device" - public const string UIA_MEDIA_ASPECTRATIO = nameof(UIA_MEDIA_ASPECTRATIO); // "Aspect Ratio" - public const string UIA_MEDIA_SKIPBACKWARD = nameof(UIA_MEDIA_SKIPBACKWARD); // "Skip Backward" - public const string UIA_MEDIA_SKIPFORWARD = nameof(UIA_MEDIA_SKIPFORWARD); // "Skip Forward" - public const string UIA_MEDIA_NEXTRACK = nameof(UIA_MEDIA_NEXTRACK); // "Next Track" - public const string UIA_MEDIA_PREVIOUSTRACK = nameof(UIA_MEDIA_PREVIOUSTRACK); // "Previous Track" - public const string UIA_MEDIA_FASTFORWARD_2X = nameof(UIA_MEDIA_FASTFORWARD_2X); // "Fast forward in 2X" - public const string UIA_MEDIA_FASTFORWARD_4X = nameof(UIA_MEDIA_FASTFORWARD_4X); // "Fast forward in 4X" - public const string UIA_MEDIA_FASTFORWARD_8X = nameof(UIA_MEDIA_FASTFORWARD_8X); // "Fast forward in 8X" - public const string UIA_MEDIA_FASTFORWARD_16X = nameof(UIA_MEDIA_FASTFORWARD_16X); // "Fast forward in 16X" - public const string UIA_MEDIA_REWIND_2X = nameof(UIA_MEDIA_REWIND_2X); // "Rewind in 2X" - public const string UIA_MEDIA_REWIND_4X = nameof(UIA_MEDIA_REWIND_4X); // "Rewind in 4X" - public const string UIA_MEDIA_REWIND_8X = nameof(UIA_MEDIA_REWIND_8X); // "Rewind in 8X" - public const string UIA_MEDIA_REWIND_16X = nameof(UIA_MEDIA_REWIND_16X); // "Rewind in 16X" - public const string UIA_MEDIA_REPEAT_NONE = nameof(UIA_MEDIA_REPEAT_NONE); // "Repeat None" - public const string UIA_MEDIA_REPEAT_ONE = nameof(UIA_MEDIA_REPEAT_ONE); // "Repeat One" - public const string UIA_MEDIA_REPEAT_ALL = nameof(UIA_MEDIA_REPEAT_ALL); // "Repeat All" - public const string UIA_MEDIA_MINIVIEW = nameof(UIA_MEDIA_MINIVIEW); // "Enter MiniView" - public const string UIA_MEDIA_EXIT_MINIVIEW = nameof(UIA_MEDIA_EXIT_MINIVIEW); // "Exit MiniView" - public const string UIA_LESS_BUTTON = nameof(UIA_LESS_BUTTON); // "Less app bar" - public const string UIA_AP_APPBAR_BUTTON = nameof(UIA_AP_APPBAR_BUTTON); // "app bar button" - public const string UIA_AP_APPBAR_TOGGLEBUTTON = nameof(UIA_AP_APPBAR_TOGGLEBUTTON); // "app bar toggle button" - public const string UIA_AP_MEDIAPLAYERELEMENT = nameof(UIA_AP_MEDIAPLAYERELEMENT); // "media player" - Localized control type for the video output of MediaPlayerElement (and MediaElement) - } - - private static class VisualState - { - public class ControlPanelVisibilityStates - { - public const string ControlPanelFadeIn = nameof(ControlPanelFadeIn); - public const string ControlPanelFadeOut = nameof(ControlPanelFadeOut); - } - public class MediaStates - { - public const string Normal = nameof(Normal); - public const string Buffering = nameof(Buffering); - public const string Loading = nameof(Loading); - public const string Error = nameof(Error); - public const string Disabled = nameof(Disabled); - } - public class AudioSelectionAvailablityStates - { - public const string AudioSelectionAvailable = nameof(AudioSelectionAvailable); - public const string AudioSelectionUnavailable = nameof(AudioSelectionUnavailable); - } - public class CCSelectionAvailablityStates - { - public const string CCSelectionAvailable = nameof(CCSelectionAvailable); - public const string CCSelectionUnavailable = nameof(CCSelectionUnavailable); - } - public class FocusStates - { - public const string Focused = nameof(Focused); - public const string Unfocused = nameof(Unfocused); - public const string PointerFocused = nameof(PointerFocused); - } - public class MediaTransportControlMode - { - public const string NormalMode = nameof(NormalMode); - public const string CompactMode = nameof(CompactMode); - } - public class PlayPauseStates - { - public const string PlayState = nameof(PlayState); - public const string PauseState = nameof(PauseState); - } - public class VolumeMuteStates - { - public const string VolumeState = nameof(VolumeState); - public const string MuteState = nameof(MuteState); - } - public class FullWindowStates - { - public const string NonFullWindowState = nameof(NonFullWindowState); - public const string FullWindowState = nameof(FullWindowState); - } - public class RepeatStates - { - public const string RepeatNoneState = nameof(RepeatNoneState); - public const string RepeatOneState = nameof(RepeatOneState); - public const string RepeatAllState = nameof(RepeatAllState); - } - } - } - - [TemplatePart(Name = "RootGrid", Type = typeof(Grid))] - [TemplatePart(Name = "PlayPauseButton", Type = typeof(Button))] - [TemplatePart(Name = "PlayPauseButtonOnLeft", Type = typeof(Button))] - [TemplatePart(Name = "VolumeMuteButton", Type = typeof(Button))] - [TemplatePart(Name = "AudioMuteButton", Type = typeof(Button))] - [TemplatePart(Name = "VolumeSlider", Type = typeof(Slider))] - [TemplatePart(Name = "FullWindowButton", Type = typeof(Button))] - [TemplatePart(Name = "CastButton", Type = typeof(Button))] - [TemplatePart(Name = "ZoomButton", Type = typeof(Button))] - [TemplatePart(Name = "PlaybackRateButton", Type = typeof(Button))] - [TemplatePart(Name = "SkipForwardButton", Type = typeof(Button))] - [TemplatePart(Name = "NextTrackButton", Type = typeof(Button))] - [TemplatePart(Name = "FastForwardButton", Type = typeof(Button))] - [TemplatePart(Name = "RewindButton", Type = typeof(Button))] - [TemplatePart(Name = "PreviousTrackButton", Type = typeof(Button))] - [TemplatePart(Name = "SkipBackwardButton", Type = typeof(Button))] - [TemplatePart(Name = "StopButton", Type = typeof(Button))] - [TemplatePart(Name = "AudioTracksSelectionButton", Type = typeof(Button))] - [TemplatePart(Name = "CCSelectionButton", Type = typeof(Button))] - [TemplatePart(Name = "TimeElapsedElement", Type = typeof(TextBlock))] - [TemplatePart(Name = "TimeRemainingElement", Type = typeof(TextBlock))] - [TemplatePart(Name = "ProgressSlider", Type = typeof(Slider))] - [TemplatePart(Name = "BufferingProgressBar", Type = typeof(ProgressBar))] - [TemplatePart(Name = "DownloadProgressIndicator", Type = typeof(ProgressBar))] - [TemplatePart(Name = "ControlPanelGrid", Type = typeof(Grid))] - [TemplatePart(Name = "ControlPanel_ControlPanelVisibilityStates_Border", Type = typeof(Border))] - - - [TemplatePart(Name = "RepeatButton", Type = typeof(Button))] - [TemplatePart(Name = "VolumeFlyout", Type = typeof(Flyout))] - [TemplatePart(Name = "PlaybackRateFlyout", Type = typeof(Flyout))] - [TemplatePart(Name = "PlaybackRateListView", Type = typeof(ListView))] - [TemplatePart(Name = "CompactOverlayButton", Type = typeof(Button))] - [TemplatePart(Name = "MediaTransportControls_Timeline_Border", Type = typeof(Border))] - //[TemplatePart(Name = "HorizontalThumb", Type = typeof(Grid))] - public partial class MediaTransportControls : Control { - #region Template Parts - // - // References to control parts we need to manipulate - // - private Grid? _rootGrid; - private Border? _timelineContainer; - private Border? _controlPanelBorder; - private Thumb? _sliderThumb; - - // Reference to the control panel grid - private Grid? m_tpControlPanelGrid; - - // Reference to the media position slider. - private Slider? m_tpMediaPositionSlider; - - // Reference to the horizontal volume slider (audio-only mode audio slider). - private Slider? m_tpHorizontalVolumeSlider; - - // Reference to the vertical volume slider (video-mode audio slider). - private Slider? m_tpVerticalVolumeSlider; - - // Reference to the Threshold Volume slider (video-mode & audio-mode slider). - private Slider? m_tpTHVolumeSlider; - - // Reference to currently active volume slider - //private Slider m_tpActiveVolumeSlider; - - // Reference to download progress indicator, which is a part in the MediaSlider template - private ProgressBar? m_tpDownloadProgressIndicator; - - // Reference to the buffering indeterminate progress bar - private ProgressBar? m_tpBufferingProgressBar; - - // Reference to the PlayPause button used in Blue and Threshold - private ButtonBase? m_tpPlayPauseButton; - - // Reference to the PlayPause button used only in Threshold - private ButtonBase? m_tpTHLeftSidePlayPauseButton; - - // Reference to the Audio Selection button - private Button? m_tpAudioSelectionButton; - - // Reference to the Audio Selection button for Threshold - private Button? m_tpTHAudioTrackSelectionButton; - - // Reference to the Available Audiotracks flyout - private MenuFlyout? m_tpAvailableAudioTracksMenuFlyout; - - // Reference to the Available Audiotracks flyout target - //private FrameworkElement m_tpAvailableAudioTracksMenuFlyoutTarget; - - // Reference to the Close Captioning Selection button - private Button? m_tpCCSelectionButton; - - // Reference to the Available Close Captioning tracks flyout - //private MenuFlyout m_tpAvailableCCTracksMenuFlyout; - - // Reference to the Play Rate Selection button - private Button? m_tpPlaybackRateButton; - - // Reference to the Available Play Rate List flyout - //private MenuFlyout m_tpAvailablePlaybackRateMenuFlyout; - - // Reference to the Video volume button - private ToggleButton? m_tpVideoVolumeButton; - - // Reference to the Audio-mute button for Blue and Mute button for Video/Audio in Threshold - private ButtonBase? m_tpMuteButton; - - // Reference to the Threshold volume button - private ButtonBase? m_tpTHVolumeButton; - - // Reference to the Full Window button - private ButtonBase? m_tpFullWindowButton; - - // Reference to the Zoom button - private ButtonBase? m_tpZoomButton; - - // Reference to currently active volume button - //private ToggleButton m_tpActiveVolumeButton; - - // Reference to Time Elapsed / -30 sec seek button or Time Elapsed TextBlock - private FrameworkElement? m_tpTimeElapsedElement; - - // Reference to Time Remaining / +30 sec seek button or Time Remaining TextBlock - private FrameworkElement? m_tpTimeRemainingElement; - - // Reference to the fast forward button - private Button? m_tpFastForwardButton; - - // Reference to the rewind button - private Button? m_tpFastRewindButton; - - // Reference to the stop button - private Button? m_tpStopButton; - - // Reference to the cast button - private Button? m_tpCastButton; - - // Reference to the Skip Forward button - private Button? m_tpSkipForwardButton; - - // Reference to the Skip Backward button - private Button? m_tpSkipBackwardButton; - - // Reference to the Next Track button - private Button? m_tpNextTrackButton; - - // Reference to the Previous Track button - private Button? m_tpPreviousTrackButton; - - // Reference to currently Repeat button - private ToggleButton? m_tpRepeatButton; - - // Reference to the Mini View button - private Button? m_tpCompactOverlayButton; - - // Reference to the PlayBack ListView of rates - private ListView? m_tpPlaybackRateListView; - - // Reference to the Left AppBarSeparator - private AppBarSeparator? m_tpLeftAppBarSeparator; - - // Reference to the Right AppBarSeparator - private AppBarSeparator? m_tpRightAppBarSeparator; - - // Reference to the Image thumbnail preview - //private Image m_tpThumbnailImage; - - // Reference to the Time Elapsed preview - //private TextBlock m_tpTimeElapsedPreview; - - // Reference to Error TextBlock - private TextBlock? m_tpErrorTextBlock; - - // Dispatcher timer responsible for updating clock and position slider - //private DispatcherTimer m_tpPositionUpdateTimer; - - // Dispatcher timer responsible for hiding vertical volume host border - //private DispatcherTimer m_tpHideVerticalVolumeTimer; - - // Dispatcher timer responsible for hiding UI control panel - private DispatcherTimer? m_tpHideControlPanelTimer; - - // Dispatcher timer to detect the pointer move ends. - //private DispatcherTimer m_tpPointerMoveEndTimer; - - // Reference to the Visibility Border element. - //private Border m_tpControlPanelVisibilityBorder; - - // Reference to the CommandBar Element. - private CommandBar? m_tpCommandBar; - - // Reference to the CommandBar Element. - private FlyoutBase? m_tpVolumeFlyout; - - // Reference to the VisualStateGroup - //private VisualStateGroup m_tpVisibilityStatesGroup; - - private Flyout? m_tpPlaybackRateFlyout; - - #endregion - private MediaPlayerElement? _mpe; private readonly SerialDisposable _subscriptions = new(); +#pragma warning disable CS0649 + private bool m_transportControlsEnabled = true; // not-implemented + private bool m_controlPanelIsVisible; + private bool m_shouldDismissControlPanel; + + private bool m_isPointerMove; + private bool m_controlPanelHasPointerOver; + private bool m_rootHasPointerPressed; + private bool m_isFlyoutOpen; + private bool m_isInScrubMode; + //private bool m_isthruScrubber; + private bool m_positionUpdateUIOnly; // If true, update the Position slider value only - do not set underlying ME.Position DP (used to differentiate position update from user vs video playing) + + private bool m_sourceLoaded; + private bool m_isPlaying; + private bool m_isBuffering; + private double m_currentPlaybackRate; +#pragma warning restore CS0649 + private bool _wasPlaying; - private bool _isTemplateApplied; - private bool _isShowingControls = true; - private bool _isShowingControlVolumeOrPlaybackRate; + private bool _isTemplateApplied; // indicates if the template parts have been resolved public MediaTransportControls() { DefaultStyleKey = typeof(MediaTransportControls); - m_tpHideControlPanelTimer = new() { Interval = TimeSpan.FromSeconds(3) }; + m_tpHideControlPanelTimer = new() { Interval = TimeSpan.FromSeconds(ControlPanelDisplayTimeoutInSecs) }; + m_tpPointerMoveEndTimer = new() + { +#if HAS_UNO + Interval = TimeSpan.FromSeconds(0.250), // throttle frequency at which this get spammed +#else + Interval = TimeSpan.Zero +#endif + }; } protected override void OnApplyTemplate() { base.OnApplyTemplate(); + // Detach any existing handlers DeinitializeTransportControls(); HookupPartsAndHandlers(); @@ -428,12 +90,16 @@ namespace Windows.UI.Xaml.Controls if (IsLoaded) { + // uno-specific: event subcriptions are extracted out from HookupPartsAndHandlers() to ensure proper disposal BindToControlEvents(); - BindMediaPlayer(); + BindMediaPlayer(updateAllVisualAndPropertyStates: false); } - UpdateAllVisualStates(useTransition: false); - UpdateMediaControlAllStates(); // dependency-properties states + // Initialize the visual state + InitializeVisualState(); + + // Update MediaControl States (dependency-properties states) + UpdateMediaControlAllStates(); } private protected override void OnLoaded() @@ -456,7 +122,7 @@ namespace Windows.UI.Xaml.Controls OnControlsBoundsChanged(); } - + // template child setup private void HookupPartsAndHandlers() { InitializeTemplateChild(TemplateParts.RootGrid, null, out _rootGrid); @@ -467,7 +133,7 @@ namespace Windows.UI.Xaml.Controls if (InitializeTemplateChild(TemplateParts.ProgressSlider, UIAKeys.UIA_MEDIA_SEEK, out m_tpMediaPositionSlider)) { m_tpDownloadProgressIndicator = m_tpMediaPositionSlider.GetTemplateChild(TemplateParts.DownloadProgressIndicator); - _sliderThumb = m_tpMediaPositionSlider.GetTemplateChild(TemplateParts.HorizontalThumb); + _progressSliderThumb = m_tpMediaPositionSlider.GetTemplateChild(TemplateParts.HorizontalThumb); } InitializeTemplateChild(TemplateParts.PlayPauseButton, UIAKeys.UIA_MEDIA_PLAY, out m_tpPlayPauseButton); InitializeTemplateChild(TemplateParts.PlayPauseButtonOnLeft, UIAKeys.UIA_MEDIA_PLAY, out m_tpTHLeftSidePlayPauseButton); @@ -480,8 +146,11 @@ namespace Windows.UI.Xaml.Controls HookupVolumeAndProgressPartsAndHandlers(); MoreControls(); + // uno specifics InitializeTemplateChild(TemplateParts.MediaTransportControls_Timeline_Border, null, out _timelineContainer); InitializeTemplateChild(TemplateParts.ControlPanel_ControlPanelVisibilityStates_Border, null, out _controlPanelBorder); + InitializeTemplateChild(TemplateParts.PlaybackRateFlyout, UIAKeys.UIA_MEDIA_PLAYBACKRATE, out _playbackRateFlyout); + InitializePlaybackRateListView(); } private void HookupVolumeAndProgressPartsAndHandlers() { @@ -509,21 +178,50 @@ namespace Windows.UI.Xaml.Controls InitializeTemplateChild(TemplateParts.RewindButton, UIAKeys.UIA_MEDIA_REWIND, out m_tpFastRewindButton); InitializeTemplateChild(TemplateParts.StopButton, UIAKeys.UIA_MEDIA_STOP, out m_tpStopButton); InitializeTemplateChild(TemplateParts.CastButton, UIAKeys.UIA_MEDIA_CAST, out m_tpCastButton); - InitializeTemplateChild(TemplateParts.PlaybackRateFlyout, UIAKeys.UIA_MEDIA_PLAYBACKRATE, out m_tpPlaybackRateFlyout); - InitializePlaybackRateListView(); } - private void MoreControls() { InitializeTemplateChild(TemplateParts.SkipForwardButton, UIAKeys.UIA_MEDIA_SKIPFORWARD, out m_tpSkipForwardButton); InitializeTemplateChild(TemplateParts.SkipBackwardButton, UIAKeys.UIA_MEDIA_SKIPBACKWARD, out m_tpSkipBackwardButton); InitializeTemplateChild(TemplateParts.NextTrackButton, UIAKeys.UIA_MEDIA_NEXTRACK, out m_tpNextTrackButton); - InitializeTemplateChild(TemplateParts.PreviousTrackButton, UIAKeys.UIA_MEDIA_NEXTRACK, out m_tpPreviousTrackButton); + InitializeTemplateChild(TemplateParts.PreviousTrackButton, UIAKeys.UIA_MEDIA_PREVIOUSTRACK, out m_tpPreviousTrackButton); InitializeTemplateChild(TemplateParts.RepeatButton, UIAKeys.UIA_MEDIA_REPEAT_NONE, out m_tpRepeatButton); InitializeTemplateChild(TemplateParts.CompactOverlayButton, UIAKeys.UIA_MEDIA_MINIVIEW, out m_tpCompactOverlayButton); InitializeTemplateChild(TemplateParts.LeftSeparator, null, out m_tpLeftAppBarSeparator); InitializeTemplateChild(TemplateParts.RightSeparator, null, out m_tpRightAppBarSeparator); } + private void InitializePlaybackRateListView() + { + if (m_tpPlaybackRateButton is AppBarButton) + { + _playbackRateListView = new ListView(); + _playbackRateListView.HorizontalAlignment = HorizontalAlignment.Center; + _playbackRateListView.VerticalAlignment = VerticalAlignment.Top; + _playbackRateListView.Margin = new Thickness(0); + _playbackRateListView.Items.AddRange(AvailablePlaybackRateList.Select(x => new ListViewItem { Content = $"{x:0.##}" })); + + _playbackRateFlyout = new Flyout(); + _playbackRateFlyout.FlyoutPresenterStyle = (Style)Application.Current.Resources["FlyoutStyle"]; + _playbackRateFlyout.ShouldConstrainToRootBounds = false; + _playbackRateFlyout.Content = _playbackRateListView; + + m_tpPlaybackRateButton.Flyout = _playbackRateFlyout; + } + else + { + InitializeTemplateChild(TemplateParts.PlaybackRateListView, UIAKeys.UIA_MEDIA_PLAYBACKRATE, out _playbackRateListView); + } + if (_playbackRateFlyout is { }) + { +#if __SKIA__ + _playbackRateFlyout.Placement = FlyoutPlacementMode.RightEdgeAlignedTop; +#else + _playbackRateFlyout.Placement = FlyoutPlacementMode.Top; +#endif + } + } + + // events un/subscription private void BindToControlEvents() { if (!_isTemplateApplied) @@ -534,33 +232,38 @@ namespace Windows.UI.Xaml.Controls var disposables = new CompositeDisposable(); _subscriptions.Disposable = disposables; - Bind(m_tpHideControlPanelTimer, x => x.Tick += ControlsVisibilityTimerElapsed, x => x.Tick -= ControlsVisibilityTimerElapsed); + Bind(m_tpHideControlPanelTimer, x => x.Tick += OnHideControlPanelTimerTick, x => x.Tick -= OnHideControlPanelTimerTick); + Bind(m_tpPointerMoveEndTimer, x => x.Tick += OnPointerMoveEndTimerTick, x => x.Tick -= OnPointerMoveEndTimerTick); + + Bind(this, x => x.PointerExited += OnRootExited, x => x.PointerExited -= OnRootExited); + Bind(this, x => x.PointerPressed += OnRootPressed, x => x.PointerPressed -= OnRootPressed); + Bind(this, x => x.PointerReleased += OnRootReleased, x => x.PointerReleased -= OnRootReleased); + Bind(this, x => x.PointerCaptureLost += OnRootCaptureLost, x => x.PointerCaptureLost -= OnRootCaptureLost); + Bind(this, x => x.PointerMoved += OnRootMoved, x => x.PointerMoved -= OnRootMoved); - BindTapped(_rootGrid, OnRootGridTapped); - Bind(_rootGrid, x => x.PointerMoved += OnRootGridPointerMoved, x => x.PointerMoved -= OnRootGridPointerMoved); BindLoaded(m_tpCommandBar, OnCommandBarLoaded, invokeHandlerIfAlreadyLoaded: true); BindSizeChanged(m_tpControlPanelGrid, ControlPanelGridSizeChanged); - BindTapped(m_tpControlPanelGrid, OnPaneGridTapped); + Bind(m_tpControlPanelGrid, x => x.PointerEntered += OnControlPanelEntered, x => x.PointerExited -= OnControlPanelEntered); + Bind(m_tpControlPanelGrid, x => x.PointerExited += OnControlPanelExited, x => x.PointerExited -= OnControlPanelExited); + Bind(m_tpControlPanelGrid, x => x.PointerCaptureLost += OnControlPanelCaptureLost, x => x.PointerCaptureLost -= OnControlPanelCaptureLost); +#if !HAS_UNO + Bind(m_tpControlPanelGrid, x => x.GotFocus += OnControlPanelGotFocus, x => x.GotFocus -= OnControlPanelGotFocus); + Bind(m_tpControlPanelGrid, x => x.LostFocus += OnControlPanelLostFocus, x => x.LostFocus -= OnControlPanelLostFocus); +#endif BindSizeChanged(_controlPanelBorder, ControlPanelBorderSizeChanged); - BindTapped(m_tpMediaPositionSlider, TappedProgressSlider); - Bind(_sliderThumb, x => x.DragCompleted += ThumbOnDragCompleted, x => x.DragCompleted -= ThumbOnDragCompleted); -#if __ANDROID__ || __IOS__ || __MACOS__ - Bind(_sliderThumb, x => x.DragStarted += ThumbOnDragStarted, x => x.DragStarted -= ThumbOnDragStarted); -#else - Bind(_sliderThumb, x => x.PointerEntered += OnPointerEntered, x => x.PointerEntered -= OnPointerEntered); - Bind(_sliderThumb, x => x.PointerExited += OnPointerExited, x => x.PointerExited -= OnPointerExited); - Bind(m_tpMediaPositionSlider, x => x.PointerEntered += OnPointerEntered, x => x.PointerEntered -= OnPointerEntered); - Bind(m_tpMediaPositionSlider, x => x.PointerExited += OnPointerExited, x => x.PointerExited -= OnPointerExited); - Bind(m_tpMediaPositionSlider, x => x.PointerMoved += OnPointerEntered, x => x.PointerMoved -= OnPointerEntered); -#endif + // Interactive parts of MTC, but outside of MediaControlsCommandBar: + Bind(m_tpMediaPositionSlider, x => x.ValueChanged += OnMediaPositionSliderValueChanged, x => x.ValueChanged -= OnMediaPositionSliderValueChanged); + Bind(_progressSliderThumb, x => x.DragCompleted += ThumbOnDragCompleted, x => x.DragCompleted -= ThumbOnDragCompleted); + Bind(_progressSliderThumb, x => x.DragStarted += ThumbOnDragStarted, x => x.DragStarted -= ThumbOnDragStarted); BindButtonClick(m_tpTHLeftSidePlayPauseButton, PlayPause); - BindButtonClick(m_tpMuteButton, ToggleMute); - Bind(m_tpTHVolumeSlider, x => x.ValueChanged += OnVolumeChanged, x => x.ValueChanged -= OnVolumeChanged); - // MediaControlsCommandBar\PrimaryCommands - //BindButtonClick(m_tpCCSelectionButton, null); - //BindButtonClick(m_tpTHAudioTrackSelectionButton, null); - // - LeftSeparator + + // MediaControlsCommandBar\PrimaryCommands: +#if !HAS_UNO + BindButtonClick(m_tpCCSelectionButton, null); + BindButtonClick(m_tpTHAudioTrackSelectionButton, null); +#endif + // --- LeftSeparator --- BindButtonClick(m_tpStopButton, Stop); BindButtonClick(m_tpSkipBackwardButton, SkipBackward); BindButtonClick(m_tpPreviousTrackButton, PreviousTrackButtonTapped); @@ -569,35 +272,33 @@ namespace Windows.UI.Xaml.Controls BindButtonClick(m_tpFastForwardButton, ForwardButton); BindButtonClick(m_tpNextTrackButton, NextTrackButtonTapped); BindButtonClick(m_tpSkipForwardButton, SkipForward); - BindButtonClick(m_tpPlaybackRateButton, ResetControlsVisibilityTimerAndVolumeOrPlaybackVisibility); - BindButtonClick(m_tpTHVolumeButton, ResetControlsVisibilityTimerAndVolumeOrPlaybackVisibility); - - Bind(m_tpTHVolumeSlider, x => x.ValueChanged += ResetVolumeOrPlaybackVisibility, x => x.ValueChanged -= ResetVolumeOrPlaybackVisibility); - Bind(m_tpTHVolumeSlider, x => x.PointerExited += ResetControlsVisibilityTimerAndVolumeOrPlaybackVisibility, x => x.PointerExited -= ResetControlsVisibilityTimerAndVolumeOrPlaybackVisibility); - Bind(m_tpTHVolumeSlider, x => x.PointerEntered += CancelControlsVisibilityTimerAndVolumeOrPlaybackVisibility, x => x.PointerEntered -= CancelControlsVisibilityTimerAndVolumeOrPlaybackVisibility); - - Bind(m_tpPlaybackRateListView, x => x.SelectionChanged += PlaybackRateListView_SelectionChanged, x => x.SelectionChanged -= PlaybackRateListView_SelectionChanged); - Bind(m_tpPlaybackRateListView, x => x.PointerExited += ResetControlsVisibilityTimerAndVolumeOrPlaybackVisibility, x => x.PointerExited -= ResetControlsVisibilityTimerAndVolumeOrPlaybackVisibility); - Bind(m_tpPlaybackRateListView, x => x.PointerEntered += CancelControlsVisibilityTimerAndVolumeOrPlaybackVisibility, x => x.PointerEntered -= CancelControlsVisibilityTimerAndVolumeOrPlaybackVisibility); - - // - RightSeparator + // --- RightSeparator --- BindButtonClick(m_tpRepeatButton, RepeatButtonTapped); BindButtonClick(m_tpZoomButton, ZoomButtonTapped); - //BindButtonClick(m_tpCastButton, null); +#if !HAS_UNO + BindButtonClick(m_tpCastButton, null); +#endif BindButtonClick(m_tpCompactOverlayButton, UpdateCompactOverlayMode); BindButtonClick(m_tpFullWindowButton, FullWindowButtonTapped); + // Flyout nested: + BindFlyout(m_tpVolumeFlyout, OnFlyoutOpened, OnFlyoutClosed); + BindButtonClick(m_tpMuteButton, ToggleMute); + Bind(m_tpTHVolumeSlider, x => x.ValueChanged += OnVolumeChanged, x => x.ValueChanged -= OnVolumeChanged); + BindFlyout(_playbackRateFlyout, OnFlyoutOpened, OnFlyoutClosed); + Bind(_playbackRateListView, x => x.SelectionChanged += PlaybackRateListView_SelectionChanged, x => x.SelectionChanged -= PlaybackRateListView_SelectionChanged); + // Register on visual state changes to update the layout in extensions foreach (var groups in VisualStateManager.GetVisualStateGroups(this.GetTemplateRoot())) { foreach (var state in groups.States) { - if (state.Name is "ControlPanelFadeOut") + if (state.Name is VisualState.ControlPanelVisibilityStates.ControlPanelFadeOut) { foreach (var child in state.Storyboard.Children) { // Update the layout on opacity completed - if (child.PropertyInfo?.LeafPropertyName == "Opacity") + if (child.PropertyInfo?.LeafPropertyName == nameof(UIElement.Opacity)) { child.Completed += Storyboard_Completed; disposables.Add(() => child.Completed -= Storyboard_Completed); @@ -607,14 +308,6 @@ namespace Windows.UI.Xaml.Controls } } - void BindTapped(UIElement? target, TappedEventHandler handler) - { - if (target is { }) - { - target.Tapped += handler; - disposables.Add(() => target.Tapped -= handler); - } - } void BindLoaded(FrameworkElement? target, RoutedEventHandler handler, bool invokeHandlerIfAlreadyLoaded = false) { if (target is { }) @@ -646,6 +339,19 @@ namespace Windows.UI.Xaml.Controls disposables.Add(() => target.Click -= handler); } } + void BindFlyout(FlyoutBase? target, EventHandler openedHandler, EventHandler closedHandler) + { + if (target is { }) + { + target.Opened += openedHandler; + target.Closed += closedHandler; + disposables.Add(() => + { + target.Opened -= openedHandler; + target.Closed -= closedHandler; + }); + } + } void Bind(T? target, Action addHandler, Action removeHandler) { if (target is { }) @@ -661,30 +367,156 @@ namespace Windows.UI.Xaml.Controls _mediaPlayerSubscriptions.Disposable = null; } - private void ControlsVisibilityTimerElapsed(object? sender, object args) + /* ShowAndHideAutomatically mechanism: // note: these are based from observation for reference only, and should not be viewed as the truth + - while playing & ShowAndHideAutomatically, the MTC will auto-hide after a set time. (see: m_tpHideControlPanelTimer.Interval) + ^ performing any action will reset this timer. [mobile: ignoring PointerEnter/Exit] + ^ having any flyout opened will disable this auto-hide. + - while MTC is hidden, setting ShowAndHideAutomatically=true will show the MTC immediately. + - while playing & MTC is visible, setting ShowAndHideAutomatically=true will begin the timer. + - tapping the video will shows the MTC. + [desktop-specific]: + - if ShowAndHideAutomatically, PointerEnter/Moved in the video will auto-shows the MTC and begin/reset the timer. + ^ presumably, clicking any button should also reset the timer, but this is already encompassed in the above rule. + ^ hovering over the button flyouts will also disable auto-hide. + - if ShowAndHideAutomatically, while the cursor is on MTC, auto-hide is disabled. + [mobile-specific]: // PointerEnter/Exit are obviously not well-supported here + */ + + private void OnHideControlPanelTimerTick(object? sender, object args) { m_tpHideControlPanelTimer?.Stop(); - if (ShowAndHideAutomatically && !_isShowingControlVolumeOrPlaybackRate) + if (IsInLiveTree) { - Hide(); + HideControlPanel(); } } - + private void OnPointerMoveEndTimerTick(object? sender, object args) + { + m_isPointerMove = false; + if (m_tpPointerMoveEndTimer is { }) + { + m_tpPointerMoveEndTimer.Stop(); + } + StartControlPanelHideTimer(); + } private void ResetControlsVisibilityTimer() { - if (ShowAndHideAutomatically && m_tpHideControlPanelTimer is not null) + if (m_tpHideControlPanelTimer is { } && + ShowAndHideAutomatically) { m_tpHideControlPanelTimer.Stop(); m_tpHideControlPanelTimer.Start(); } } + private void StartControlPanelHideTimer() + { + if (m_transportControlsEnabled) + { + if (m_tpHideControlPanelTimer is { } && + ShouldHideControlPanel()) + { + m_tpHideControlPanelTimer.Start(); + } + } + } + private void StopControlPanelHideTimer() + { + if (m_transportControlsEnabled) + { + if (m_tpHideControlPanelTimer is { }) + { + m_tpHideControlPanelTimer?.Stop(); + } + } + } + private void ShowControlPanel() + { + if (m_transportControlsEnabled) + { + if (!m_controlPanelIsVisible) + { + m_controlPanelIsVisible = true; +#if !HAS_UNO + if (!m_isVSStateChangeExternal) // Skip if Visual State already happen through external + { + m_controlPanelVisibilityChanged = TRUE; + } +#endif + } - private void CancelControlsVisibilityTimer() +#if !HAS_UNO + ShowControlPanelFromMPE(); + + // Resume position updates now that CP is visible + StartPositionUpdateTimer(); +#endif + + // Immediately start the timer to hide control panel + StartControlPanelHideTimer(); + + UpdateVisualState(); + +#if !HAS_UNO + m_isVSStateChangeExternal = FALSE; +#endif + +#if HAS_UNO + // Adjust layout bounds immediately + OnControlsBoundsChanged(); +#endif + } + } + private void HideControlPanel(bool hideImmediately = false) { - Show(); - m_tpHideControlPanelTimer?.Stop(); + if (m_transportControlsEnabled) + { + if (m_tpHideControlPanelTimer is { } && _mpe is { }) + { + if (hideImmediately || ShouldHideControlPanel() /*|| m_isVSStateChangeExternal*/) + { + // Both CP and Vertical Volume will be hiddden, so stop their hide timers. + StopControlPanelHideTimer(); + +#if !HAS_UNO + //IFC(StopVerticalVolumeHideTimer()); + + // Stop position updates now that CP is not visible + //IFC(StopPositionUpdateTimer()); + + // Flag vertical volume to hide so that it won't get displayed + // next time the ControlPanel becomes visible + //if (m_verticalVolumeIsVisible) + //{ + // m_verticalVolumeIsVisible = FALSE; + // m_verticalVolumeVisibilityChanged = TRUE; + //} +#endif + + // Flag control panel itself to hide + m_controlPanelIsVisible = false; +#if !HAS_UNO + //if (!m_isVSStateChangeExternal) // Skip if Visual State already happen through external + //{ + // m_controlPanelVisibilityChanged = TRUE; + //} + + //HideControlPanelFromMPE(); +#endif + + UpdateVisualState(); + } + } + + m_shouldDismissControlPanel = false; +#if !HAS_UNO + //m_isthruScrubber = false; + //m_isVSStateChangeExternal = false; +#endif + } } + public void Show() => ShowControlPanel(); + public void Hide() => HideControlPanel(hideImmediately: true); private void OnCommandBarLoaded(object? sender, RoutedEventArgs e) { @@ -710,7 +542,7 @@ namespace Windows.UI.Xaml.Controls { if (m_tpCastButton is { }) { -#if false // not implemented +#if !HAS_UNO var deviceSelector = CastingDevice.GetDeviceSelector( CastingPlaybackTypes.Audio | CastingPlaybackTypes.Video | @@ -723,111 +555,152 @@ namespace Windows.UI.Xaml.Controls } } - private void Storyboard_Completed(object? sender, object e) => OnControlsBoundsChanged(); - - public void Show() + private void OnRootExited(object sender, PointerRoutedEventArgs e) { - _isShowingControls = true; - - _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + if (m_transportControlsEnabled) { - UpdateControlPanelVisibilityStates(useTransition: false); - }); + // If user presses pointer over root then drags it off while holding down + // we get neither Released nor CaptureLost on the root. Thus, unset + // m_rootHasPointerPressed whenever pointer leaves root. + // For consistency, also enforce Pressed is FALSE for vertical volume host. + m_rootHasPointerPressed = false; - // Adjust layout bounds immediately - OnControlsBoundsChanged(); + // If pointer exited the root area, it is no longer over the + // vertical volume or the control panel, enforce this here. + m_controlPanelHasPointerOver = false; - if (ShowAndHideAutomatically) + StartControlPanelHideTimer(); + } + } + private void OnRootPressed(object sender, PointerRoutedEventArgs e) + { + if (m_transportControlsEnabled) { - ResetControlsVisibilityTimer(); + m_rootHasPointerPressed = true; + StopControlPanelHideTimer(); + + // Any click over media area should bring up control panel. + ShowControlPanel(); } } - public void Hide() + private void OnRootReleased(object sender, PointerRoutedEventArgs e) { - _isShowingControls = false; + if (m_transportControlsEnabled) + { + m_rootHasPointerPressed = false; + StartControlPanelHideTimer(); + } + } + private void OnRootCaptureLost(object sender, PointerRoutedEventArgs e) + { + if (m_transportControlsEnabled) + { + m_rootHasPointerPressed = false; + StartControlPanelHideTimer(); + } + } + private void OnRootMoved(object sender, PointerRoutedEventArgs e) + { + if (e.Pointer.PointerDeviceType is PointerDeviceType.Touch) + { + return; + } - _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + if (m_transportControlsEnabled) { - if (_mediaPlayer is { PlaybackSession.IsPlaying: true }) - { - UpdateControlPanelVisibilityStates(useTransition: false); - } - if (m_tpPlaybackRateButton is { } - && m_tpPlaybackRateFlyout is { } - && m_tpVolumeFlyout is { }) + // Check flags to minimize work in this frquently called handler + if ( +#if !HAS_UNO + !m_isAudioOnly && +#endif + !m_controlPanelIsVisible && + ShowAndHideAutomatically /* ignore if when auto hide/show is disabled */ +#if !HAS_UNO + && !m_hasError +#endif + ) { - if (m_tpPlaybackRateButton is AppBarButton playbackRateAppBarButton) - { - playbackRateAppBarButton.Flyout.Hide(); - } - else - { - m_tpPlaybackRateFlyout.Hide(); - } - m_tpVolumeFlyout.Hide(); + ShowControlPanel(); } - }); - } - private void OnControlsBoundsChanged() - { - if (m_tpControlPanelGrid is { } && - _mediaPlayer is { } && - XamlRoot?.Content is UIElement root) - { - var slot = m_tpControlPanelGrid - .TransformToVisual(m_tpControlPanelGrid.Parent as UIElement) - .TransformBounds(m_tpControlPanelGrid.LayoutSlotWithMarginsAndAlignments); - slot.Height += m_tpControlPanelGrid.Padding.Top - + m_tpControlPanelGrid.Padding.Bottom - + Margin.Top - + Margin.Bottom; - if (!_isShowingControls) + if (m_tpPointerMoveEndTimer is { }) { - slot.Height = 0; + m_isPointerMove = true; + // timer to detect when pointer move ends. + m_tpPointerMoveEndTimer.Stop(); + m_tpPointerMoveEndTimer.Start(); } - _mediaPlayer.SetTransportControlBounds(slot); } } - private void OnPaneGridTapped(object sender, TappedRoutedEventArgs e) + private void OnControlPanelEntered(object sender, PointerRoutedEventArgs e) { - if (ShowAndHideAutomatically) + if (m_transportControlsEnabled) { - ResetControlsVisibilityTimer(); + m_controlPanelHasPointerOver = true; + StopControlPanelHideTimer(); } - e.Handled = true; } - - private void OnRootGridTapped(object sender, TappedRoutedEventArgs e) + private void OnControlPanelExited(object sender, PointerRoutedEventArgs e) { - if (_isShowingControlVolumeOrPlaybackRate) + if (m_transportControlsEnabled) { - _isShowingControlVolumeOrPlaybackRate = false; - if (ShowAndHideAutomatically) - { - ResetControlsVisibilityTimer(); - } + m_controlPanelHasPointerOver = false; + StartControlPanelHideTimer(); } - if (e.PointerDeviceType == PointerDeviceType.Touch) + } + private void OnControlPanelCaptureLost(object sender, PointerRoutedEventArgs e) + { + if (m_transportControlsEnabled) { - if (_isShowingControls) + // + // 1. Update PointerPresed state. + // + // If capture was lost on control panel, it is safe to say + // pointer is not pressed over ME, since only the controls + // making up the panel could possibly take capture. + // + m_rootHasPointerPressed = false; + + // + // 2. Update PointerOver state. + // + // If volume slider is dragged with pointer outside the vertical volume host, + // PointerExited event is not fired, however when pointer is released we will + // get a CaptureLost event. + // + var spPointerPointWhenCaptureLost = e.GetCurrentPoint(null); + var pointWhenCaptureLost = spPointerPointWhenCaptureLost.Position; + + // Check if control panel grid is still hit + m_controlPanelHasPointerOver = HitTestHelper(pointWhenCaptureLost, m_tpControlPanelGrid); + + // Kick off timers as needed based on updated PointerOver state + if (!m_controlPanelHasPointerOver) { - m_tpHideControlPanelTimer?.Stop(); - Hide(); - } - else - { - Show(); + StartControlPanelHideTimer(); } } } - private void OnRootGridPointerMoved(object sender, PointerRoutedEventArgs e) + private void OnControlsBoundsChanged() { - if (e.Pointer.PointerDeviceType != PointerDeviceType.Touch) + if (m_tpControlPanelGrid is { } && + _mediaPlayer is { } && + XamlRoot?.Content is UIElement root) { - Show(); + var slot = m_tpControlPanelGrid + .TransformToVisual(m_tpControlPanelGrid.Parent as UIElement) + .TransformBounds(m_tpControlPanelGrid.LayoutSlotWithMarginsAndAlignments); + slot.Height += m_tpControlPanelGrid.Padding.Top + + m_tpControlPanelGrid.Padding.Bottom + + Margin.Top + + Margin.Bottom; + if (!m_controlPanelIsVisible) + { + slot.Height = 0; + } + _mediaPlayer.SetTransportControlBounds(slot); } } @@ -835,7 +708,6 @@ namespace Windows.UI.Xaml.Controls { OnControlsBoundsChanged(); } - private static void ControlPanelBorderSizeChanged(object sender, SizeChangedEventArgs args) { if (sender is Border border) @@ -846,6 +718,16 @@ namespace Windows.UI.Xaml.Controls }; } } + private void Storyboard_Completed(object? sender, object e) => OnControlsBoundsChanged(); + private void OnFlyoutOpened(object? sender, object e) + { + m_isFlyoutOpen = true; + } + private void OnFlyoutClosed(object? sender, object e) + { + m_isFlyoutOpen = false; + ResetControlsVisibilityTimer(); + } private void FullWindowButtonTapped(object sender, RoutedEventArgs e) { @@ -856,7 +738,6 @@ namespace Windows.UI.Xaml.Controls UpdateFullWindowStates(); } - private void PlaybackRateListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (sender is ListView listView) @@ -865,10 +746,10 @@ namespace Windows.UI.Xaml.Controls { if (listView.SelectedItem is Windows.UI.Xaml.Controls.ListViewItem item) { - _isShowingControlVolumeOrPlaybackRate = false; ResetControlsVisibilityTimer(); + if (m_tpPlaybackRateButton is { } - && m_tpPlaybackRateFlyout is { }) + && _playbackRateFlyout is { }) { if (m_tpPlaybackRateButton is AppBarButton playbackRateAppBarButton) { @@ -876,7 +757,7 @@ namespace Windows.UI.Xaml.Controls } else { - m_tpPlaybackRateFlyout.Hide(); + _playbackRateFlyout.Hide(); } if (_mpe is not null && _mpe.MediaPlayer is not null) { @@ -887,32 +768,15 @@ namespace Windows.UI.Xaml.Controls } } } - - private void ResetControlsVisibilityTimerAndVolumeOrPlaybackVisibility(object sender, RoutedEventArgs e) - { - _isShowingControlVolumeOrPlaybackRate = false; - ResetControlsVisibilityTimer(); - } - - private void ResetVolumeOrPlaybackVisibility(object sender, RangeBaseValueChangedEventArgs e) - { - _isShowingControlVolumeOrPlaybackRate = false; - } - - private void CancelControlsVisibilityTimerAndVolumeOrPlaybackVisibility(object sender, PointerRoutedEventArgs e) - { - _isShowingControlVolumeOrPlaybackRate = true; - CancelControlsVisibilityTimer(); - } - private void RepeatButtonTapped(object sender, RoutedEventArgs e) { - if (_mpe?.MediaPlayer is null) + if (_mpe?.MediaPlayer is null || + !ApiInformation.IsPropertyPresent(typeof(_MediaPlayer), nameof(_MediaPlayer.IsLoopingEnabled))) { return; } - _mpe.MediaPlayer.IsLoopingEnabled = !IsMediaPlayerLoopingEnabled; + _mpe.MediaPlayer.IsLoopingEnabled = !_mpe.MediaPlayer.IsLoopingEnabled; UpdateRepeatStates(); } private void PreviousTrackButtonTapped(object sender, RoutedEventArgs e) @@ -923,7 +787,6 @@ namespace Windows.UI.Xaml.Controls _mediaPlayer.PlaybackSession.Position = TimeSpan.Zero; } } - private void NextTrackButtonTapped(object sender, RoutedEventArgs e) { if (_mediaPlayer is not null) @@ -946,7 +809,6 @@ namespace Windows.UI.Xaml.Controls } } } - private void UpdateCompactOverlayMode(object sender, RoutedEventArgs e) { IsCompact = !IsCompact; @@ -993,7 +855,6 @@ namespace Windows.UI.Xaml.Controls UpdateMediaControlState(IsCompactOverlayButtonVisibleProperty); UpdateMediaControlState(IsCompactOverlayEnabledProperty); } - private void UpdateMediaControlState(DependencyProperty property) { switch (property) @@ -1007,10 +868,10 @@ namespace Windows.UI.Xaml.Controls break; case var _ when property == IsRepeatButtonVisibleProperty: - BindVisibility(m_tpRepeatButton, IsImplemented(typeof(Windows.Media.Playback.MediaPlayer), "IsLoopingEnabled") && IsRepeatButtonVisible); + BindVisibility(m_tpRepeatButton, IsRepeatButtonVisible && ApiInformation.IsPropertyPresent(typeof(_MediaPlayer), nameof(_MediaPlayer.IsLoopingEnabled))); break; case var _ when property == IsRepeatEnabledProperty: - BindIsEnabled(m_tpRepeatButton, IsRepeatEnabled); + BindIsEnabled(m_tpRepeatButton, IsRepeatEnabled && ApiInformation.IsPropertyPresent(typeof(_MediaPlayer), nameof(_MediaPlayer.IsLoopingEnabled))); break; case var _ when property == IsVolumeButtonVisibleProperty: BindVisibility(m_tpTHVolumeButton, IsVolumeButtonVisible); @@ -1031,10 +892,10 @@ namespace Windows.UI.Xaml.Controls BindIsEnabled(m_tpZoomButton, IsZoomEnabled); break; case var _ when property == IsPlaybackRateButtonVisibleProperty: - BindVisibility(m_tpPlaybackRateButton, IsImplemented(typeof(Windows.Media.Playback.MediaPlayer), "PlaybackRate") && IsPlaybackRateButtonVisible); + BindVisibility(m_tpPlaybackRateButton, IsPlaybackRateButtonVisible && ApiInformation.IsPropertyPresent(typeof(_MediaPlayer), nameof(_MediaPlayer.PlaybackRate))); break; case var _ when property == IsPlaybackRateEnabledProperty: - BindIsEnabled(m_tpPlaybackRateButton, IsPlaybackRateEnabled); + BindIsEnabled(m_tpPlaybackRateButton, IsPlaybackRateEnabled && ApiInformation.IsPropertyPresent(typeof(_MediaPlayer), nameof(_MediaPlayer.PlaybackRate))); break; case var _ when property == IsFastForwardButtonVisibleProperty: BindVisibility(m_tpFastForwardButton, IsFastForwardButtonVisible); @@ -1103,46 +964,95 @@ namespace Windows.UI.Xaml.Controls } private void OnShowAndHideAutomaticallyChanged() { - if (ShowAndHideAutomatically) + if (!m_controlPanelIsVisible && !ShowAndHideAutomatically) { - ResetControlsVisibilityTimer(); + ShowControlPanel(); + return; + } + + if (m_controlPanelIsVisible) + { + if (_mediaPlayer?.PlaybackSession.IsPlaying == true) + { + // while playing & MTC is visible, setting ShowAndHideAutomatically=true will begin the timer. + ResetControlsVisibilityTimer(); + } } else { - CancelControlsVisibilityTimer(); + // while MTC is hidden, setting ShowAndHideAutomatically=true will show the MTC immediately. + ShowControlPanel(); } } - private bool IsImplemented(Type type, string property) + // visual states + private void InitializeVisualState() { - return ApiInformation.IsPropertyPresent(type.FullName + "", property); - } +#if !HAS_UNO + if (MTCParent_MediaElement == m_parentType) + { + IFC(InitializeVisualStateFromME()); + } + else if (MTCParent_MediaPlayerElement == m_parentType) + { + IFC(InitializeVisualStateFromMPE()); + } - // visual states - private void UpdateAllVisualStates(bool useTransition = true) + IFC(InitializeVolume()); + + if (MTCParent_MediaElement == m_parentType) + { + // Make sure we have the latest playback item + IFC(UpdatePlaybackItemReference()); + } + + IFC(UpdateRepeatButtonUI()); + + Update UI + IFC(UpdatePlayPauseUI()); + IFC(UpdateFullWindowUI()); + IFC(UpdatePositionUI()); + IFC(UpdateDownloadProgressUI()); + IFC(UpdateErrorUI()); + + if (m_tpMediaPositionSlider) + { + IFC(m_tpMediaPositionSlider.Cast()->get_Minimum(&m_positionSliderMinimum)); + IFC(m_tpMediaPositionSlider.Cast()->get_Maximum(&m_positionSliderMaximum)); + } + + // We could have switched into or out of audio mode, which changes the controls that are displayed. + IFC(UpdateAudioSelectionUI()); + IFC(UpdateIsMutedUI()); + IFC(UpdateVolumeUI()); + IFC(CalculateDropOutLevel()); +#endif + + // ShowControlPanel() calls UpdateVisualState() + ShowControlPanel(); + } + internal override void UpdateVisualState(bool useTransitions = true) { // all visual states are listed below: // unused/not-implemented ones are commented out - UpdateControlPanelVisibilityStates(useTransition); - UpdateMediaStates(useTransition); - //UpdateAudioSelectionAvailablityStates(useTransition); - //UpdateCCSelectionAvailablityStates(useTransition); - //UpdateFocusStates(useTransition); - UpdateMediaTransportControlModeStates(useTransition); - UpdatePlayPauseStates(useTransition); - UpdateVolumeMuteStates(useTransition); - UpdateFullWindowStates(useTransition); - UpdateRepeatStates(useTransition); + UpdateControlPanelVisibilityStates(useTransitions); + UpdateMediaStates(useTransitions); + //UpdateAudioSelectionAvailablityStates(useTransitions); + //UpdateCCSelectionAvailablityStates(useTransitions); + //UpdateFocusStates(useTransitions); + UpdateMediaTransportControlModeStates(useTransitions); + UpdatePlayPauseStates(useTransitions); + UpdateVolumeMuteStates(useTransitions); + UpdateFullWindowStates(useTransitions); + UpdateRepeatStates(useTransitions); } - - private void UpdateControlPanelVisibilityStates(bool useTransition = true) + private void UpdateControlPanelVisibilityStates(bool useTransitions = true) { - var state = _isShowingControls + var state = m_controlPanelIsVisible ? VisualState.ControlPanelVisibilityStates.ControlPanelFadeIn : VisualState.ControlPanelVisibilityStates.ControlPanelFadeOut; - VisualStateManager.GoToState(this, state, useTransition); + VisualStateManager.GoToState(this, state, useTransitions); } - - private void UpdateMediaStates(bool useTransition = true) + private void UpdateMediaStates(bool useTransitions = true) { if (_mpe?.MediaPlayer?.PlaybackSession is { } session) { @@ -1157,51 +1067,44 @@ namespace Windows.UI.Xaml.Controls _ => null, }; - if (m_tpBufferingProgressBar is not null) - { - // Disable indeterminate state if not buffering to avoid animation costs. - m_tpBufferingProgressBar.IsIndeterminate - = session.PlaybackState is MediaPlaybackState.Buffering or MediaPlaybackState.Opening; - } - if (state != null) { - VisualStateManager.GoToState(this, state, useTransition); + VisualStateManager.GoToState(this, state, useTransitions); } } } - - private void UpdateMediaTransportControlModeStates(bool useTransition = true) + private void UpdateMediaTransportControlModeStates(bool useTransitions = true) { var state = IsCompact ? VisualState.MediaTransportControlMode.CompactMode : VisualState.MediaTransportControlMode.NormalMode; - VisualStateManager.GoToState(this, state, useTransition); + VisualStateManager.GoToState(this, state, useTransitions); var uiaKey = IsCompact ? UIAKeys.UIA_MEDIA_EXIT_MINIVIEW : UIAKeys.UIA_MEDIA_MINIVIEW; SetAutomationNameAndTooltip(m_tpCompactOverlayButton, uiaKey); } - private void UpdatePlayPauseStates(bool useTransition = true) + private void UpdatePlayPauseStates(bool useTransitions = true) { if (_mpe?.MediaPlayer is null) { return; } - var state = _mpe.MediaPlayer.PlaybackSession.IsPlaying + var isPlaying = m_isPlaying || (m_isInScrubMode && _wasPlaying); + var state = isPlaying ? VisualState.PlayPauseStates.PauseState : VisualState.PlayPauseStates.PlayState; - VisualStateManager.GoToState(this, state, useTransition); + VisualStateManager.GoToState(this, state, useTransitions); - var uiaKey = _mpe.MediaPlayer.PlaybackSession.IsPlaying + var uiaKey = isPlaying ? UIAKeys.UIA_MEDIA_PAUSE : UIAKeys.UIA_MEDIA_PLAY; SetAutomationNameAndTooltip(m_tpPlayPauseButton, uiaKey); SetAutomationNameAndTooltip(m_tpTHLeftSidePlayPauseButton, uiaKey); } - private void UpdateVolumeMuteStates(bool isExplicitMuteToggle = false, bool useTransition = true) + private void UpdateVolumeMuteStates(bool isExplicitMuteToggle = false, bool useTransitions = true) { if (_mediaPlayer is null) { @@ -1220,22 +1123,21 @@ namespace Windows.UI.Xaml.Controls var state = isMuted ? VisualState.VolumeMuteStates.MuteState : VisualState.VolumeMuteStates.VolumeState; - VisualStateManager.GoToState(this, state, useTransition); + VisualStateManager.GoToState(this, state, useTransitions); var uiaKey = isMuted ? UIAKeys.UIA_MEDIA_UNMUTE : UIAKeys.UIA_MEDIA_MUTE; SetAutomationNameAndTooltip(m_tpMuteButton, uiaKey); } - - private void UpdateFullWindowStates(bool useTransition = true) + private void UpdateFullWindowStates(bool useTransitions = true) { if (_mpe is not null) { var state = _mpe.IsFullWindow ? VisualState.FullWindowStates.FullWindowState : VisualState.FullWindowStates.NonFullWindowState; - VisualStateManager.GoToState(this, state, useTransition); + VisualStateManager.GoToState(this, state, useTransitions); var uiaKey = _mpe.IsFullWindow ? UIAKeys.UIA_MEDIA_EXIT_FULLSCREEN @@ -1243,30 +1145,28 @@ namespace Windows.UI.Xaml.Controls SetAutomationNameAndTooltip(m_tpFullWindowButton, uiaKey); } } - - private bool IsMediaPlayerLoopingEnabled => - ApiInformation.IsPropertyPresent(typeof(Windows.Media.Playback.MediaPlayer).FullName!, nameof(Windows.Media.Playback.MediaPlayer.IsLoopingEnabled)) - && (_mpe?.MediaPlayer.IsLoopingEnabled ?? false); - - private void UpdateRepeatStates(bool useTransition = true) + private void UpdateRepeatStates(bool useTransitions = true) { - if (_mpe?.MediaPlayer is null) + if (_mpe?.MediaPlayer is null || + !ApiInformation.IsPropertyPresent(typeof(_MediaPlayer), nameof(_MediaPlayer.IsLoopingEnabled))) { return; } - var state = IsMediaPlayerLoopingEnabled + var state = _mpe.MediaPlayer.IsLoopingEnabled ? VisualState.RepeatStates.RepeatAllState : VisualState.RepeatStates.RepeatNoneState; - VisualStateManager.GoToState(this, state, useTransition); + VisualStateManager.GoToState(this, state, useTransitions); - var uiaKey = IsMediaPlayerLoopingEnabled + var uiaKey = _mpe.MediaPlayer.IsLoopingEnabled ? UIAKeys.UIA_MEDIA_REPEAT_ALL : UIAKeys.UIA_MEDIA_REPEAT_NONE; SetAutomationNameAndTooltip(m_tpRepeatButton, uiaKey); } + } - // helper methods + partial class MediaTransportControls // helper methods + { private bool InitializeTemplateChild( string childName, string? uiaKey, @@ -1280,51 +1180,74 @@ namespace Windows.UI.Xaml.Controls return child != null; } - - private void InitializePlaybackRateListView() + private void SetAutomationNameAndTooltip(DependencyObject? target, string uiaKey) { - - if (m_tpPlaybackRateButton is AppBarButton playbackRateAppBarButton) - { - m_tpPlaybackRateListView = new ListView(); - m_tpPlaybackRateListView.VerticalAlignment = VerticalAlignment.Top; - m_tpPlaybackRateListView.HorizontalAlignment = HorizontalAlignment.Center; - m_tpPlaybackRateListView.Margin = new Thickness(0); - m_tpPlaybackRateListView.Items.AddRange(new List() { - new() { Content = "0.25" }, - new() { Content = "0.5" }, - new() { Content = "1" }, - new() { Content = "1.5" }, - new() { Content = "2" }}); - m_tpPlaybackRateFlyout = new Flyout(); - m_tpPlaybackRateFlyout.FlyoutPresenterStyle = (Style)Application.Current.Resources["FlyoutStyle"]; - m_tpPlaybackRateFlyout.ShouldConstrainToRootBounds = false; - m_tpPlaybackRateFlyout.Content = m_tpPlaybackRateListView; - - playbackRateAppBarButton.Flyout = m_tpPlaybackRateFlyout; - } - else + if (target is { }) { - InitializeTemplateChild(TemplateParts.PlaybackRateListView, UIAKeys.UIA_MEDIA_PLAYBACKRATE, out m_tpPlaybackRateListView); + var value = DXamlCore.Current.GetLocalizedResourceString(uiaKey); + AutomationProperties.SetName(target, value); + ToolTipService.SetToolTip(target, value); } - if (m_tpPlaybackRateFlyout is { }) - { -#if __SKIA__ - m_tpPlaybackRateFlyout.Placement = FlyoutPlacementMode.RightEdgeAlignedTop; -#else - m_tpPlaybackRateFlyout.Placement = FlyoutPlacementMode.Top; + } + + /// + /// Helper to check if conditions are met to hide control panel. + /// + private bool ShouldHideControlPanel() + { + var isAutoShowHide = ShowAndHideAutomatically; + var result = + m_controlPanelIsVisible && +#if !HAS_UNO + !m_isAudioOnly && + !m_hasError && #endif - } + (m_shouldDismissControlPanel || !m_controlPanelHasPointerOver) && + !m_rootHasPointerPressed && +#if !HAS_UNO + // Do not need to check this on the Xbox only if commandbar should exist in the template. + (!m_controlsHaveKeyOrProgFocus || (XboxUtility::IsOnXbox() && m_tpCommandBar.Get())) && + + !m_verticalVolumeHasKeyOrProgFocus && +#endif + ShouldHideControlPanelWhilePlaying() && + !m_isFlyoutOpen && + !m_isPointerMove && + + // Hide MTC only if auto hide/Show is enabled + isAutoShowHide; + + return result; } - private void SetAutomationNameAndTooltip(DependencyObject? target, string uiaKey) + /// + /// Helper to check if conditions are met to hide control panel while playing. + /// It should stay if we aren't playing video. + /// + private bool ShouldHideControlPanelWhilePlaying() { - if (target is { }) + return (m_isPlaying && !m_isBuffering) + || (m_shouldDismissControlPanel); + } + + /// + /// Helper to hit test pElement against point. + /// + private bool HitTestHelper(Point point, UIElement? pElement) + { + if (pElement is { }) { - var value = DXamlCore.Current.GetLocalizedResourceString(uiaKey); - AutomationProperties.SetName(target, value); - ToolTipService.SetToolTip(target, value); + var spElements = VisualTreeHelper.FindElementsInHostCoordinates(point, pElement, includeAllElements: m_tpCommandBar is { } ? false : true); + foreach (var spElement in spElements) + { + if (pElement == spElement) + { + return true; + } + } } + + return false; } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.mux.h.cs b/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.mux.h.cs new file mode 100644 index 0000000000000000000000000000000000000000..bea0aa0dbe9a2a7c54a54a94a1e9704ca4daae04 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MediaPlayerElement/MediaTransportControls.mux.h.cs @@ -0,0 +1,424 @@ +#nullable enable + +using System.Collections.Generic; +using Windows.UI.Xaml.Controls.Primitives; + +namespace Windows.UI.Xaml.Controls; + +public partial class MediaTransportControls // Strings +{ + private static class TemplateParts + { + #region Uno-specific + + public const string RootGrid = nameof(RootGrid); + public const string ControlPanel_ControlPanelVisibilityStates_Border = nameof(ControlPanel_ControlPanelVisibilityStates_Border); + public const string MediaTransportControls_Timeline_Border = nameof(MediaTransportControls_Timeline_Border); + public const string PlaybackRateListView = nameof(PlaybackRateListView); + public const string PlaybackRateFlyout = nameof(PlaybackRateFlyout); + + #endregion + + public const string ControlPanelGrid = nameof(ControlPanelGrid); // Grid + public const string TimeElapsedElement = nameof(TimeElapsedElement); // TextBlock + public const string TimeRemainingElement = nameof(TimeRemainingElement); // TextBlock + public const string ProgressSlider = nameof(ProgressSlider); // Slider + public const string PlayPauseButton = nameof(PlayPauseButton); // AppBarButton + public const string PlayPauseButtonOnLeft = nameof(PlayPauseButtonOnLeft); // AppBarButton + public const string FullWindowButton = nameof(FullWindowButton); // AppBarButton + public const string ZoomButton = nameof(ZoomButton); // AppBarButton + public const string ErrorTextBlock = nameof(ErrorTextBlock); // TextBlock + public const string MediaControlsCommandBar = nameof(MediaControlsCommandBar); // CommandBar + public const string VolumeFlyout = nameof(VolumeFlyout); // Flyout + public const string HorizontalVolumeSlider = nameof(HorizontalVolumeSlider); // ? + public const string VerticalVolumeSlider = nameof(VerticalVolumeSlider); // ? + public const string VolumeSlider = nameof(VolumeSlider); // Slider + public const string AudioSelectionButton = nameof(AudioSelectionButton); // ? + public const string AudioTracksSelectionButton = nameof(AudioTracksSelectionButton); // AppBarButton + public const string AvailableAudioTracksMenuFlyout = nameof(AvailableAudioTracksMenuFlyout); // ? + public const string AvailableAudioTracksMenuFlyoutTarget = nameof(AvailableAudioTracksMenuFlyoutTarget); // ? + public const string CCSelectionButton = nameof(CCSelectionButton); // AppBarButton + public const string PlaybackRateButton = nameof(PlaybackRateButton); // AppBarButton + public const string VolumeButton = nameof(VolumeButton); // ? + public const string AudioMuteButton = nameof(AudioMuteButton); // AppBarButton + public const string VolumeMuteButton = nameof(VolumeMuteButton); // AppBarButton + public const string BufferingProgressBar = nameof(BufferingProgressBar); // ProgressBar + public const string FastForwardButton = nameof(FastForwardButton); // AppBarButton + public const string RewindButton = nameof(RewindButton); // AppBarButton + public const string StopButton = nameof(StopButton); // AppBarButton + public const string CastButton = nameof(CastButton); // AppBarButton + public const string SkipForwardButton = nameof(SkipForwardButton); // AppBarButton + public const string SkipBackwardButton = nameof(SkipBackwardButton); // AppBarButton + public const string NextTrackButton = nameof(NextTrackButton); // AppBarButton + public const string PreviousTrackButton = nameof(PreviousTrackButton); // AppBarButton + public const string RepeatButton = nameof(RepeatButton); // AppBarToggleButton + public const string CompactOverlayButton = nameof(CompactOverlayButton); // AppBarButton + + public const string LeftSeparator = nameof(LeftSeparator); // AppBarSeparator + public const string RightSeparator = nameof(RightSeparator); // AppBarSeparator + + // MediaControlsCommandBar template children + public const string MoreButton = nameof(MoreButton); // Button + + // ProgressSlider template children + public const string DownloadProgressIndicator = nameof(DownloadProgressIndicator); // ProgressBar + public const string HorizontalThumb = nameof(HorizontalThumb); // Thumb + } + + private static class UIAKeys + { + // MediaElement Transport Controls: UI Automation Name / Tooltip text + public const string UIA_MEDIA_PLAY = nameof(UIA_MEDIA_PLAY); // "Play" + public const string UIA_MEDIA_PAUSE = nameof(UIA_MEDIA_PAUSE); // "Pause" + public const string UIA_MEDIA_TIME_ELAPSED = nameof(UIA_MEDIA_TIME_ELAPSED); // "Time elapsed" + public const string UIA_MEDIA_TIME_REMAINING = nameof(UIA_MEDIA_TIME_REMAINING); // "Time remaining" + public const string UIA_MEDIA_DOWNLOAD_PROGRESS = nameof(UIA_MEDIA_DOWNLOAD_PROGRESS); // "Download Progress" + public const string UIA_MEDIA_BUFFERING_PROGRESS = nameof(UIA_MEDIA_BUFFERING_PROGRESS); // "Buffering Progress" + public const string UIA_MEDIA_SEEK = nameof(UIA_MEDIA_SEEK); // "Seek" + public const string UIA_MEDIA_MUTE = nameof(UIA_MEDIA_MUTE); // "Mute" + public const string UIA_MEDIA_UNMUTE = nameof(UIA_MEDIA_UNMUTE); // "Unmute" + public const string UIA_MEDIA_VOLUME = nameof(UIA_MEDIA_VOLUME); // "Volume" + public const string UIA_MEDIA_ERROR = nameof(UIA_MEDIA_ERROR); // "Error" + public const string TEXT_MEDIA_AUDIO_TRACK_UNTITLED = nameof(TEXT_MEDIA_AUDIO_TRACK_UNTITLED); // "untitled" + public const string TEXT_MEDIA_AUDIO_TRACK_SELECTED = nameof(TEXT_MEDIA_AUDIO_TRACK_SELECTED); // "(On)" - Appended to name of currently selected audio track + public const string TEXT_MEDIA_AUDIO_TRACK_SEPARATOR = nameof(TEXT_MEDIA_AUDIO_TRACK_SEPARATOR); // " - " - Used to separate pieces of metadata in audio track name + public const string UIA_MEDIA_FULLSCREEN = nameof(UIA_MEDIA_FULLSCREEN); // "Full Screen" + public const string UIA_MEDIA_EXIT_FULLSCREEN = nameof(UIA_MEDIA_EXIT_FULLSCREEN); // "Exit Full Screen" + public const string UIA_MEDIA_AUDIO_SELECTION = nameof(UIA_MEDIA_AUDIO_SELECTION); // "Show audio selection menu" + public const string UIA_MEDIA_CC_SELECTION = nameof(UIA_MEDIA_CC_SELECTION); // "Show closed caption menu" + public const string TEXT_MEDIA_CC_OFF = nameof(TEXT_MEDIA_CC_OFF); // "Off" + public const string UIA_MEDIA_PLAYBACKRATE = nameof(UIA_MEDIA_PLAYBACKRATE); // "Show playback rate list" + public const string UIA_MEDIA_FASTFORWARD = nameof(UIA_MEDIA_FASTFORWARD); // "Fast forward" + public const string UIA_MEDIA_REWIND = nameof(UIA_MEDIA_REWIND); // "Rewind" + public const string UIA_MEDIA_STOP = nameof(UIA_MEDIA_STOP); // "Stop" + public const string UIA_MEDIA_CAST = nameof(UIA_MEDIA_CAST); // "Cast to Device" + public const string UIA_MEDIA_ASPECTRATIO = nameof(UIA_MEDIA_ASPECTRATIO); // "Aspect Ratio" + public const string UIA_MEDIA_SKIPBACKWARD = nameof(UIA_MEDIA_SKIPBACKWARD); // "Skip Backward" + public const string UIA_MEDIA_SKIPFORWARD = nameof(UIA_MEDIA_SKIPFORWARD); // "Skip Forward" + public const string UIA_MEDIA_NEXTRACK = nameof(UIA_MEDIA_NEXTRACK); // "Next Track" + public const string UIA_MEDIA_PREVIOUSTRACK = nameof(UIA_MEDIA_PREVIOUSTRACK); // "Previous Track" + public const string UIA_MEDIA_FASTFORWARD_2X = nameof(UIA_MEDIA_FASTFORWARD_2X); // "Fast forward in 2X" + public const string UIA_MEDIA_FASTFORWARD_4X = nameof(UIA_MEDIA_FASTFORWARD_4X); // "Fast forward in 4X" + public const string UIA_MEDIA_FASTFORWARD_8X = nameof(UIA_MEDIA_FASTFORWARD_8X); // "Fast forward in 8X" + public const string UIA_MEDIA_FASTFORWARD_16X = nameof(UIA_MEDIA_FASTFORWARD_16X); // "Fast forward in 16X" + public const string UIA_MEDIA_REWIND_2X = nameof(UIA_MEDIA_REWIND_2X); // "Rewind in 2X" + public const string UIA_MEDIA_REWIND_4X = nameof(UIA_MEDIA_REWIND_4X); // "Rewind in 4X" + public const string UIA_MEDIA_REWIND_8X = nameof(UIA_MEDIA_REWIND_8X); // "Rewind in 8X" + public const string UIA_MEDIA_REWIND_16X = nameof(UIA_MEDIA_REWIND_16X); // "Rewind in 16X" + public const string UIA_MEDIA_REPEAT_NONE = nameof(UIA_MEDIA_REPEAT_NONE); // "Repeat None" + public const string UIA_MEDIA_REPEAT_ONE = nameof(UIA_MEDIA_REPEAT_ONE); // "Repeat One" + public const string UIA_MEDIA_REPEAT_ALL = nameof(UIA_MEDIA_REPEAT_ALL); // "Repeat All" + public const string UIA_MEDIA_MINIVIEW = nameof(UIA_MEDIA_MINIVIEW); // "Enter MiniView" + public const string UIA_MEDIA_EXIT_MINIVIEW = nameof(UIA_MEDIA_EXIT_MINIVIEW); // "Exit MiniView" + public const string UIA_LESS_BUTTON = nameof(UIA_LESS_BUTTON); // "Less app bar" + public const string UIA_AP_APPBAR_BUTTON = nameof(UIA_AP_APPBAR_BUTTON); // "app bar button" + public const string UIA_AP_APPBAR_TOGGLEBUTTON = nameof(UIA_AP_APPBAR_TOGGLEBUTTON); // "app bar toggle button" + public const string UIA_AP_MEDIAPLAYERELEMENT = nameof(UIA_AP_MEDIAPLAYERELEMENT); // "media player" - Localized control type for the video output of MediaPlayerElement (and MediaElement) + } + + private static class VisualState + { + public class ControlPanelVisibilityStates + { + public const string ControlPanelFadeIn = nameof(ControlPanelFadeIn); + public const string ControlPanelFadeOut = nameof(ControlPanelFadeOut); + } + public class MediaStates + { + public const string Normal = nameof(Normal); + public const string Buffering = nameof(Buffering); + public const string Loading = nameof(Loading); + public const string Error = nameof(Error); + public const string Disabled = nameof(Disabled); + } + public class AudioSelectionAvailablityStates + { + public const string AudioSelectionAvailable = nameof(AudioSelectionAvailable); + public const string AudioSelectionUnavailable = nameof(AudioSelectionUnavailable); + } + public class CCSelectionAvailablityStates + { + public const string CCSelectionAvailable = nameof(CCSelectionAvailable); + public const string CCSelectionUnavailable = nameof(CCSelectionUnavailable); + } + public class FocusStates + { + public const string Focused = nameof(Focused); + public const string Unfocused = nameof(Unfocused); + public const string PointerFocused = nameof(PointerFocused); + } + public class MediaTransportControlMode + { + public const string NormalMode = nameof(NormalMode); + public const string CompactMode = nameof(CompactMode); + } + public class PlayPauseStates + { + public const string PlayState = nameof(PlayState); + public const string PauseState = nameof(PauseState); + } + public class VolumeMuteStates + { + public const string VolumeState = nameof(VolumeState); + public const string MuteState = nameof(MuteState); + } + public class FullWindowStates + { + public const string NonFullWindowState = nameof(NonFullWindowState); + public const string FullWindowState = nameof(FullWindowState); + } + public class RepeatStates + { + public const string RepeatNoneState = nameof(RepeatNoneState); + public const string RepeatOneState = nameof(RepeatOneState); + public const string RepeatAllState = nameof(RepeatAllState); + } + } +} + +public partial class MediaTransportControls // Definitions +{ +#pragma warning disable IDE0051 // Remove unused private members + // HNS Hunderds of Nano Seconds used for conversion in timer duration + private const uint HNSPerSecond = 10000000; + + // Control Panel Timout in secs, after timeout Control Panel will be hide. + private const double ControlPanelDisplayTimeoutInSecs = 3.0; + + // Vertical Volume bar Timout in secs, after timout Vertical Volume bar will be hide. + private const double VerticalVolumeDisplayTimeoutInSecs = 3.0; + + // Timer frequecy in second to update Seek bar. + private const double SeekbarPositionUpdateFreqInSecs = 0.250; + + // Elapsed-Remaining Button used seeking interval defined in HNS + private const long TimeButtonUsedSeekIntervalInHNS = 300000000; + + // Maximum Time String length unsed in the Time Buttons. + // [H]H:mm:ss time string has up to 8 chars; also include terminating '\0' + private const uint MaxTimeButtonTextLength = 9; + + // Maximum Processed Language length + // include terminating '\0' + private const uint MaxProcessedLanguageNameLength = 50; + + // Maximum Dropout levels used in WinBlue + private const uint MaxDropuOutLevels = 10; + + // Maximum PlayRate Counts + private const uint AvailablePlaybackRateCount = 5; + + private static readonly IReadOnlyCollection AvailablePlaybackRateList = new[] { 0.25, 0.5, 1.0, 1.5, 2.0 }; + + // Skip forward/Skip Backward time interval defined in Seconds + private const uint SkipForwardInSecs = 30; + private const uint SkipBackwardInSecs = 10; + + private const int VolumeSliderWheelScrollStep = 2; + + private const int MinSupportedPlayRate = 2; +#pragma warning restore IDE0051 // Remove unused private members +} + +[TemplatePart(Name = TemplateParts.ControlPanelGrid, Type = typeof(Grid))] +[TemplatePart(Name = TemplateParts.TimeElapsedElement, Type = typeof(TextBlock))] +[TemplatePart(Name = TemplateParts.TimeRemainingElement, Type = typeof(TextBlock))] +[TemplatePart(Name = TemplateParts.ProgressSlider, Type = typeof(Slider))] +[TemplatePart(Name = TemplateParts.PlayPauseButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.PlayPauseButtonOnLeft, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.FullWindowButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.ZoomButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.ErrorTextBlock, Type = typeof(TextBlock))] +[TemplatePart(Name = TemplateParts.MediaControlsCommandBar, Type = typeof(CommandBar))] +[TemplatePart(Name = TemplateParts.VolumeFlyout, Type = typeof(Flyout))] +[TemplatePart(Name = TemplateParts.HorizontalVolumeSlider, Type = typeof(Slider))] +[TemplatePart(Name = TemplateParts.VerticalVolumeSlider, Type = typeof(Slider))] +[TemplatePart(Name = TemplateParts.VolumeSlider, Type = typeof(Slider))] +[TemplatePart(Name = TemplateParts.AudioSelectionButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.AudioTracksSelectionButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.AvailableAudioTracksMenuFlyout, Type = typeof(MenuFlyout))] +[TemplatePart(Name = TemplateParts.AvailableAudioTracksMenuFlyoutTarget, Type = typeof(FrameworkElement))] +[TemplatePart(Name = TemplateParts.CCSelectionButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.PlaybackRateButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.VolumeButton, Type = typeof(ToggleButton))] +[TemplatePart(Name = TemplateParts.AudioMuteButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.VolumeMuteButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.BufferingProgressBar, Type = typeof(ProgressBar))] +[TemplatePart(Name = TemplateParts.FastForwardButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.RewindButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.StopButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.CastButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.SkipForwardButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.SkipBackwardButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.NextTrackButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.PreviousTrackButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.RepeatButton, Type = typeof(AppBarToggleButton))] +[TemplatePart(Name = TemplateParts.CompactOverlayButton, Type = typeof(AppBarButton))] +[TemplatePart(Name = TemplateParts.LeftSeparator, Type = typeof(AppBarSeparator))] +[TemplatePart(Name = TemplateParts.RightSeparator, Type = typeof(AppBarSeparator))] +[TemplatePart(Name = TemplateParts.MoreButton, Type = typeof(Button))] +[TemplatePart(Name = TemplateParts.DownloadProgressIndicator, Type = typeof(ProgressBar))] +[TemplatePart(Name = TemplateParts.HorizontalThumb, Type = typeof(Thumb))] +public partial class MediaTransportControls // Template Parts +{ + // + // References to control parts we need to manipulate + // + #region Uno-specific + + private Grid? _rootGrid; + private Border? _timelineContainer; + private Border? _controlPanelBorder; + private Thumb? _progressSliderThumb; + private Flyout? _playbackRateFlyout; + private ListView? _playbackRateListView; + + #endregion + + // Reference to the control panel grid + private Grid? m_tpControlPanelGrid; + + // Reference to the media position slider. + private Slider? m_tpMediaPositionSlider; + + // Reference to the horizontal volume slider (audio-only mode audio slider). + private Slider? m_tpHorizontalVolumeSlider; + + // Reference to the vertical volume slider (video-mode audio slider). + private Slider? m_tpVerticalVolumeSlider; + + // Reference to the Threshold Volume slider (video-mode & audio-mode slider). + private Slider? m_tpTHVolumeSlider; + + // Reference to currently active volume slider + //private Slider m_tpActiveVolumeSlider; + + // Reference to download progress indicator, which is a part in the MediaSlider template + private ProgressBar? m_tpDownloadProgressIndicator; + + // Reference to the buffering indeterminate progress bar + private ProgressBar? m_tpBufferingProgressBar; + + // Reference to the PlayPause button used in Blue and Threshold + private ButtonBase? m_tpPlayPauseButton; + + // Reference to the PlayPause button used only in Threshold + private ButtonBase? m_tpTHLeftSidePlayPauseButton; + + // Reference to the Audio Selection button + private Button? m_tpAudioSelectionButton; + + // Reference to the Audio Selection button for Threshold + private Button? m_tpTHAudioTrackSelectionButton; + + // Reference to the Available Audiotracks flyout + private MenuFlyout? m_tpAvailableAudioTracksMenuFlyout; + + // Reference to the Available Audiotracks flyout target + //private FrameworkElement m_tpAvailableAudioTracksMenuFlyoutTarget; + + // Reference to the Close Captioning Selection button + private Button? m_tpCCSelectionButton; + + // Reference to the Available Close Captioning tracks flyout + //private MenuFlyout m_tpAvailableCCTracksMenuFlyout; + + // Reference to the Play Rate Selection button + private Button? m_tpPlaybackRateButton; + + // Reference to the Available Play Rate List flyout + //private MenuFlyout m_tpAvailablePlaybackRateMenuFlyout; + + // Reference to the Video volume button + private ToggleButton? m_tpVideoVolumeButton; + + // Reference to the Audio-mute button for Blue and Mute button for Video/Audio in Threshold + private ButtonBase? m_tpMuteButton; + + // Reference to the Threshold volume button + private ButtonBase? m_tpTHVolumeButton; + + // Reference to the Full Window button + private ButtonBase? m_tpFullWindowButton; + + // Reference to the Zoom button + private ButtonBase? m_tpZoomButton; + + // Reference to currently active volume button + //private ToggleButton m_tpActiveVolumeButton; + + // Reference to Time Elapsed / -30 sec seek button or Time Elapsed TextBlock + private FrameworkElement? m_tpTimeElapsedElement; + + // Reference to Time Remaining / +30 sec seek button or Time Remaining TextBlock + private FrameworkElement? m_tpTimeRemainingElement; + + // Reference to the fast forward button + private Button? m_tpFastForwardButton; + + // Reference to the rewind button + private Button? m_tpFastRewindButton; + + // Reference to the stop button + private Button? m_tpStopButton; + + // Reference to the cast button + private Button? m_tpCastButton; + + // Reference to the Skip Forward button + private Button? m_tpSkipForwardButton; + + // Reference to the Skip Backward button + private Button? m_tpSkipBackwardButton; + + // Reference to the Next Track button + private Button? m_tpNextTrackButton; + + // Reference to the Previous Track button + private Button? m_tpPreviousTrackButton; + + // Reference to currently Repeat button + private ToggleButton? m_tpRepeatButton; + + // Reference to the Mini View button + private Button? m_tpCompactOverlayButton; + + // Reference to the Left AppBarSeparator + private AppBarSeparator? m_tpLeftAppBarSeparator; + + // Reference to the Right AppBarSeparator + private AppBarSeparator? m_tpRightAppBarSeparator; + + // Reference to the Image thumbnail preview + //private Image m_tpThumbnailImage; + + // Reference to the Time Elapsed preview + //private TextBlock m_tpTimeElapsedPreview; + + // Reference to Error TextBlock + private TextBlock? m_tpErrorTextBlock; + + // Dispatcher timer responsible for updating clock and position slider + //private DispatcherTimer m_tpPositionUpdateTimer; + + // Dispatcher timer responsible for hiding vertical volume host border + //private DispatcherTimer m_tpHideVerticalVolumeTimer; + + // Dispatcher timer responsible for hiding UI control panel + private DispatcherTimer? m_tpHideControlPanelTimer; + + // Dispatcher timer to detect the pointer move ends. + private DispatcherTimer m_tpPointerMoveEndTimer; + + // Reference to the Visibility Border element. + //private Border m_tpControlPanelVisibilityBorder; + + // Reference to the CommandBar Element. + private CommandBar? m_tpCommandBar; + + // Reference to the CommandBar Element. + private FlyoutBase? m_tpVolumeFlyout; + + // Reference to the VisualStateGroup + //private VisualStateGroup m_tpVisibilityStatesGroup; +}