diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs
index ce5f3174ef..cdf7b42c1e 100644
--- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs
+++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs
@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using Android.App;
using Android.Content;
using Android.Util;
using Android.Views;
@@ -23,10 +22,6 @@ namespace CommunityToolkit.Maui.Core.Views;
public partial class MediaManager : Java.Lang.Object, IPlayerListener
{
- const int bufferState = 2;
- const int readyState = 3;
- const int endedState = 4;
-
static readonly HttpClient client = new();
readonly SemaphoreSlim seekToSemaphoreSlim = new(1, 1);
bool isAndroidForegroundServiceEnabled = false;
@@ -77,53 +72,80 @@ public void UpdateNotifications()
}
///
- /// Occurs when ExoPlayer changes the player state.
+ /// Handles player event notifications and updates the media element state accordingly.
///
- /// Indicates whether the player should start playing the media whenever the media is ready.
- /// The state that the player has transitioned to.
- ///
- /// This is part of the implementation.
- /// While this method does not seem to have any references, it's invoked at runtime.
- ///
- public void OnPlayerStateChanged(bool playWhenReady, int playbackState)
+ /// This method updates the media element's state and position in response to playback-related events
+ /// from the player. If a player error is present, it is handled before any state updates. No action is taken if either
+ /// the player, the media source, or the event set is null.
+ /// The player instance that raised the events. If null, the method does nothing.
+ /// The set of player events to process. If null, the method does nothing.
+ public void OnEvents(IPlayer? player, PlayerEvents? playerEvents)
{
- if (Player is null || MediaElement.Source is null)
+ if (player is null || MediaElement.Source is null || playerEvents is null)
{
return;
}
- var newState = playbackState switch
+ // If there's a player error, bubble it through the existing error handler
+ if (player.PlayerError is not null)
{
- PlaybackState.StateFastForwarding
- or PlaybackState.StateRewinding
- or PlaybackState.StateSkippingToNext
- or PlaybackState.StateSkippingToPrevious
- or PlaybackState.StateSkippingToQueueItem
- or PlaybackState.StatePlaying => playWhenReady
- ? MediaElementState.Playing
- : MediaElementState.Paused,
+ OnPlayerError(player.PlayerError);
+ return;
+ }
- PlaybackState.StatePaused => MediaElementState.Paused,
+ // If playback state or playWhenReady changed, update UI together using values from the player
+ var playbackStateEvent = BasePlayer.InterfaceConsts.EventPlaybackStateChanged;
+ var playWhenReadyEvent = BasePlayer.InterfaceConsts.EventPlayWhenReadyChanged;
- PlaybackState.StateConnecting
- or PlaybackState.StateBuffering => MediaElementState.Buffering,
+ if (playerEvents.Contains(playbackStateEvent) || playerEvents.Contains(playWhenReadyEvent))
+ {
+ var playbackState = player.PlaybackState;
+ var playWhenReady = player.PlayWhenReady;
+
+ var newState = playbackState switch
+ {
+ BasePlayer.InterfaceConsts.StateBuffering => MediaElementState.Buffering,
+ BasePlayer.InterfaceConsts.StateReady => playWhenReady ? MediaElementState.Playing : MediaElementState.Paused,
+ BasePlayer.InterfaceConsts.StateEnded => MediaElementState.Stopped,
+ BasePlayer.InterfaceConsts.StateIdle => MediaElement.CurrentState is MediaElementState.None or MediaElementState.Failed or MediaElementState.Opening ? MediaElement.CurrentState : MediaElementState.Stopped,
+ _ => MediaElementState.None,
+ };
- PlaybackState.StateNone => MediaElementState.None,
- PlaybackState.StateStopped => MediaElement.CurrentState is not MediaElementState.Failed
- ? MediaElementState.Stopped
- : MediaElementState.Failed,
+ if (playbackState == BasePlayer.InterfaceConsts.StateReady)
+ {
+ seekToTaskCompletionSource?.TrySetResult();
+ if (Player is not null)
+ {
+ newState = Player.PlayWhenReady ? MediaElementState.Playing : MediaElementState.Paused;
+ }
+ MediaElement.MediaOpened();
+ }
- PlaybackState.StateError => MediaElementState.Failed,
+ if (playbackState == BasePlayer.InterfaceConsts.StateEnded)
+ {
+ MediaElement.MediaEnded();
+ }
+ MediaElement.CurrentStateChanged(newState);
- _ => MediaElementState.None,
- };
+ if (playbackState == BasePlayer.InterfaceConsts.StateReady)
+ {
+ MediaElement.Duration = TimeSpan.FromMilliseconds(player.Duration < 0 ? 0 : player.Duration);
+ MediaElement.Position = TimeSpan.FromMilliseconds(player.CurrentPosition < 0 ? 0 : player.CurrentPosition);
+ }
+ }
+ }
- MediaElement.CurrentStateChanged(newState);
- if (playbackState is readyState)
+ ///
+ /// Handles changes to the player error state by updating the media element's state to indicate a failure.
+ ///
+ /// The playback error that occurred, or null if the error state has been cleared.
+ public void OnPlayerErrorChanged(PlaybackException? error)
+ {
+ if (error is null)
{
- MediaElement.Duration = TimeSpan.FromMilliseconds(Player.Duration < 0 ? 0 : Player.Duration);
- MediaElement.Position = TimeSpan.FromMilliseconds(Player.CurrentPosition < 0 ? 0 : Player.CurrentPosition);
+ return;
}
+ OnPlayerError(error);
}
///
@@ -182,39 +204,6 @@ or PlaybackState.StateSkippingToQueueItem
return (Player, PlayerView);
}
- ///
- /// Occurs when ExoPlayer changes the playback state.
- ///
- /// The state that the player has transitioned to.
- ///
- /// This is part of the implementation.
- /// While this method does not seem to have any references, it's invoked at runtime.
- ///
- public void OnPlaybackStateChanged(int playbackState)
- {
- if (MediaElement.Source is null)
- {
- return;
- }
-
- MediaElementState newState = MediaElement.CurrentState;
- switch (playbackState)
- {
- case bufferState:
- newState = MediaElementState.Buffering;
- break;
- case endedState:
- newState = MediaElementState.Stopped;
- MediaElement.MediaEnded();
- break;
- case readyState:
- seekToTaskCompletionSource?.TrySetResult();
- break;
- }
-
- MediaElement.CurrentStateChanged(newState);
- }
-
///
/// Occurs when ExoPlayer encounters an error.
///
@@ -252,7 +241,6 @@ public void OnPlayerError(PlaybackException? error)
}.Where(static s => !string.IsNullOrEmpty(s)));
MediaElement.MediaFailed(new MediaFailedEventArgs(message));
-
Logger.LogError("{LogMessage}", message);
}
@@ -369,30 +357,23 @@ protected virtual async partial ValueTask PlatformUpdateSource()
return;
}
- MediaElement.CurrentStateChanged(MediaElementState.Opening);
Player.PlayWhenReady = MediaElement.ShouldAutoPlay;
cancellationTokenSource ??= new();
+ MediaElement.CurrentStateChanged(MediaElementState.Opening);
// ConfigureAwait(true) is required to prevent crash on startup
var result = await SetPlayerData(cancellationTokenSource.Token).ConfigureAwait(true);
var item = result?.Build();
- if (item?.MediaMetadata is not null)
+ if (item is not null)
{
Player.SetMediaItem(item);
Player.Prepare();
hasSetSource = true;
}
- if (hasSetSource)
+ if (hasSetSource && isAndroidForegroundServiceEnabled)
{
- if (Player.PlayerError is null)
- {
- MediaElement.MediaOpened();
- }
- if (isAndroidForegroundServiceEnabled)
- {
- UpdateNotifications();
- }
+ UpdateNotifications();
}
}
@@ -742,7 +723,6 @@ public void OnAvailableCommandsChanged(PlayerCommands? player) { }
public void OnCues(CueGroup? cues) { }
public void OnDeviceInfoChanged(DeviceInfo? deviceInfo) { }
public void OnDeviceVolumeChanged(int volume, bool muted) { }
- public void OnEvents(IPlayer? player, PlayerEvents? playerEvents) { }
public void OnIsLoadingChanged(bool isLoading) { }
public void OnIsPlayingChanged(bool isPlaying) { }
public void OnLoadingChanged(bool isLoading) { }
@@ -750,10 +730,11 @@ public void OnMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs)
public void OnMediaItemTransition(MediaItem? mediaItem, int reason) { }
public void OnMediaMetadataChanged(MediaMetadata? mediaMetadata) { }
public void OnMetadata(Metadata? metadata) { }
+ public void OnPlaybackStateChanged(int playbackState) { }
public void OnPlayWhenReadyChanged(bool playWhenReady, int reason) { }
public void OnPositionDiscontinuity(PlayerPositionInfo? oldPosition, PlayerPositionInfo? newPosition, int reason) { }
public void OnPlaybackSuppressionReasonChanged(int playbackSuppressionReason) { }
- public void OnPlayerErrorChanged(PlaybackException? error) { }
+ public void OnPlayerStateChanged(bool playWhenReady, int playbackState) { }
public void OnPlaylistMetadataChanged(MediaMetadata? mediaMetadata) { }
public void OnRenderedFirstFrame() { }
public void OnRepeatModeChanged(int repeatMode) { }
@@ -766,23 +747,4 @@ public void OnTimelineChanged(Timeline? timeline, int reason) { }
public void OnTrackSelectionParametersChanged(TrackSelectionParameters? trackSelectionParameters) { }
public void OnTracksChanged(Tracks? tracks) { }
#endregion
-
- static class PlaybackState
- {
- public const int StateBuffering = 6;
- public const int StateConnecting = 8;
- public const int StateFailed = 7;
- public const int StateFastForwarding = 4;
- public const int StateNone = 0;
- public const int StatePaused = 2;
- public const int StatePlaying = 3;
- public const int StateRewinding = 5;
- public const int StateSkippingToNext = 10;
- public const int StateSkippingToPrevious = 9;
- public const int StateSkippingToQueueItem = 11;
- public const int StateStopped = 1;
- public const int StateError = 7;
- }
-
-
}
\ No newline at end of file