Is there an existing issue for this?
Did you read the "Reporting a bug" section on Contributing file?
Current Behavior
Summary
On iOS/macOS, CommunityToolkit.Maui.MediaElement appears to raise MediaOpened too early.
The current iOS/macOS MediaManager.PlatformUpdateSource() raises MediaElement.MediaOpened() after creating an AVPlayerItem and calling Player.ReplaceCurrentItemWithPlayerItem(PlayerItem), as long as PlayerItem is not null && PlayerItem.Error is null.
That is not equivalent to the native media item being ready.
For AVFoundation, the correct readiness signal is:
AVPlayerItem.Status == AVPlayerItemStatus.ReadyToPlay
new AVPlayerItem(asset) and PlayerItem.Error is null only mean that an item object exists and has not failed yet. They do not mean the media has loaded, has a valid duration, or is ready to play.
Observed behavior
In a .NET MAUI app using multiple MediaElement instances with local embedded resource videos, where I was debugging the AVPlayer state via Reflection:
ShouldAutoPlay = false;
ShouldShowPlaybackControls = false;
Source = MediaSource.FromResource(...);
all six MediaOpened events fired, causing app-level readiness to become true:
MEDIA OPENED PT1 0
MEDIA OPENED PT2 0
MEDIA OPENED PT2 2
MEDIA OPENED PT1 2
MEDIA OPENED PT2 1
MEDIA OPENED PT1 1
READY ready=True opened=111/111
But at the moment READY was reached, every MediaElement was still reported as Opening with zero duration:
pt1=0:Opening@0/0,1:Opening@0/0,2:Opening@0/0
pt2=0:Opening@0/0,1:Opening@0/0,2:Opening@0/0
Calling Play() or Stop() after this false readiness caused the players to fail:
pt1=0:Failed@0/0,1:Failed@0/0,2:Failed@0/0
pt2=0:Failed@0/0,1:Failed@0/0,2:Failed@0/0
So MediaOpened was raised while the media was not actually opened/ready.
Expected behavior
MediaOpened should fire only when the native AVPlayerItem reaches:
AVPlayerItemStatus.ReadyToPlay
At that point, application code should be able to safely treat the media as opened and ready for playback operations such as:
Play();
Pause();
SeekTo(...);
It should not fire just because an AVPlayerItem object was created.
Affected file / class
File:
src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.ios.cs
Class:
CommunityToolkit.Maui.Core.Views.MediaManager
Relevant current logic:
PlayerItem = asset is not null
? new AVPlayerItem(asset)
: null;
metaData.SetMetadata(PlayerItem, MediaElement);
CurrentItemErrorObserver?.Dispose();
Player.ReplaceCurrentItemWithPlayerItem(PlayerItem);
...
if (PlayerItem is not null && PlayerItem.Error is null)
{
MediaElement.MediaOpened();
(MediaElement.MediaWidth, MediaElement.MediaHeight) = await GetVideoDimensions(PlayerItem);
if (MediaElement.ShouldAutoPlay)
{
Player.Play();
}
await SetPoster();
}
The problem is this line:
MediaElement.MediaOpened();
It is called before observing PlayerItem.Status == AVPlayerItemStatus.ReadyToPlay.
Root cause
PlayerItem.Error is null is not a readiness check.
Immediately after creating an AVPlayerItem, the item can still be in an unknown/opening state. AVFoundation exposes readiness through AVPlayerItem.Status, and the item should be observed until it reaches ReadyToPlay or Failed.
The Toolkit currently has player-level status observers:
StatusObserver = Player.AddObserver("status", ...);
TimeControlStatusObserver = Player.AddObserver("timeControlStatus", ...);
but MediaOpened should be driven by the current item’s status, not by item creation.
Proposed fix
Move MediaElement.MediaOpened() out of PlatformUpdateSource() and into an observer for the current AVPlayerItem.Status.
Add fields:
bool hasMediaOpened;
IDisposable? CurrentItemStatusObserver { get; set; }
Reset them when a new source is assigned:
hasMediaOpened = false;
CurrentItemStatusObserver?.Dispose();
CurrentItemStatusObserver = null;
After creating and replacing the current item, observe the item status:
PlayerItem = asset is not null
? new AVPlayerItem(asset)
: null;
metaData.SetMetadata(PlayerItem, MediaElement);
CurrentItemErrorObserver?.Dispose();
Player.ReplaceCurrentItemWithPlayerItem(PlayerItem);
CurrentItemStatusObserver = PlayerItem?.AddObserver(
"status",
ValueObserverOptions,
PlayerItemStatusChanged
);
Do not call MediaOpened() immediately after ReplaceCurrentItemWithPlayerItem.
Instead:
async void PlayerItemStatusChanged(NSObservedChange obj)
{
if (PlayerItem is null)
{
return;
}
switch (PlayerItem.Status)
{
case AVPlayerItemStatus.ReadyToPlay:
if (hasMediaOpened)
{
return;
}
hasMediaOpened = true;
MediaElement.Duration = ConvertTime(PlayerItem.Duration);
MediaElement.Position = ConvertTime(PlayerItem.CurrentTime);
MediaElement.CurrentStateChanged(
Player?.Rate > 0
? MediaElementState.Playing
: MediaElementState.Paused
);
(MediaElement.MediaWidth, MediaElement.MediaHeight) = await GetVideoDimensions(PlayerItem);
MediaElement.MediaOpened();
if (MediaElement.ShouldAutoPlay)
{
Player?.Play();
}
await SetPoster();
break;
case AVPlayerItemStatus.Failed:
var message = PlayerItem.Error is not null
? $"{PlayerItem.Error.LocalizedDescription} - {PlayerItem.Error.LocalizedFailureReason}"
: "AVPlayerItem failed.";
MediaElement.CurrentStateChanged(MediaElementState.Failed);
MediaElement.MediaFailed(new MediaFailedEventArgs(message));
Logger.LogError("{LogMessage}", message);
break;
}
}
Also dispose the observer:
CurrentItemStatusObserver?.Dispose();
CurrentItemStatusObserver = null;
inside Dispose(bool disposing).
Why this fixes the issue
This makes MediaOpened match its documented meaning: the media is loaded and ready to play.
It also aligns the Toolkit with AVFoundation’s native readiness model:
AVPlayerItemStatus.ReadyToPlay
After applying an app-side reflection workaround that ignored Toolkit MediaOpened on iOS and instead watched the underlying AVPlayer.CurrentItem.Status, the same media elements loaded correctly and playback worked. All behavior became predictable and normal while watching and reacting to the true AVPlayer events.
The workaround treated only this native state as opened:
player.CurrentItem.Status == AVPlayerItemStatus.ReadyToPlay
and used AVPlayerItem.Notifications.ObserveDidPlayToEndTime for ended playback. That resolved the false-ready / Opening@0/0 / Failed@0/0 behavior.
Minimal reproduction outline
- Create a .NET MAUI app using
CommunityToolkit.Maui.MediaElement.
- Add one or more
MediaElement instances on iOS.
- Configure:
ShouldAutoPlay = false;
ShouldShowPlaybackControls = false;
Aspect = Aspect.Fill;
- Attach handlers before setting
Source:
mediaElement.MediaOpened += ...;
mediaElement.MediaFailed += ...;
- Set a local embedded video resource:
mediaElement.Source = MediaSource.FromResource("video.mp4");
- In the
MediaOpened handler, log by reflection the AVPlayer state vs the MediaElement state. You can access the AVPlayer by running on mediaElement.HandlerChanged:
static AVPlayer? getAVPlayer(MediaElement mediaElement) {
var handler = mediaElement.Handler;
if (handler is null) {
return null;
}
object? mediaManager = getPropertyOrFieldFromTypeTree(
handler,
"MediaManager"
);
if (mediaManager is null) {
mediaManager = getPropertyOrFieldFromTypeTree(
handler,
"mediaManager"
);
}
if (mediaManager is null) {
return null;
}
object? player = getPropertyOrFieldFromTypeTree(
mediaManager,
"Player"
);
return player as AVPlayer;
}
static object? getPropertyOrFieldFromTypeTree(object target, string name) {
Type? type = target.GetType();
while (type != null) {
PropertyInfo? property = type.GetProperty(
name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
);
if (property != null) {
return property.GetValue(target);
}
FieldInfo? field = type.GetField(
name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
);
if (field != null) {
return field.GetValue(target);
}
type = type.BaseType;
}
return null;
}
- Observe that
MediaOpened can fire while the element is still:
and subsequent playback operations can fail.
Requested change
Please update iOS/macOS MediaManager.ios.cs so that:
MediaOpened() is not raised immediately after AVPlayerItem creation.
MediaOpened() is raised only once the current item reaches AVPlayerItemStatus.ReadyToPlay.
Duration, Position, and media dimensions are updated around the actual ready transition.
AVPlayerItemStatus.Failed triggers MediaFailed.
- Observers are reset/disposed correctly when changing source or disposing the player.
This would make iOS/macOS MediaOpened correctly represent native media readiness and match the documented MediaOpened contract.
Environment
- .NET MAUI CommunityToolkit:
- OS:
- .NET MAUI:
Is there an existing issue for this?
Did you read the "Reporting a bug" section on Contributing file?
Current Behavior
Summary
On iOS/macOS,
CommunityToolkit.Maui.MediaElementappears to raiseMediaOpenedtoo early.The current iOS/macOS
MediaManager.PlatformUpdateSource()raisesMediaElement.MediaOpened()after creating anAVPlayerItemand callingPlayer.ReplaceCurrentItemWithPlayerItem(PlayerItem), as long asPlayerItem is not null && PlayerItem.Error is null.That is not equivalent to the native media item being ready.
For AVFoundation, the correct readiness signal is:
new AVPlayerItem(asset)andPlayerItem.Error is nullonly mean that an item object exists and has not failed yet. They do not mean the media has loaded, has a valid duration, or is ready to play.Observed behavior
In a .NET MAUI app using multiple
MediaElementinstances with local embedded resource videos, where I was debugging the AVPlayer state via Reflection:all six
MediaOpenedevents fired, causing app-level readiness to become true:But at the moment
READYwas reached, everyMediaElementwas still reported asOpeningwith zero duration:Calling
Play()orStop()after this false readiness caused the players to fail:So
MediaOpenedwas raised while the media was not actually opened/ready.Expected behavior
MediaOpenedshould fire only when the nativeAVPlayerItemreaches:At that point, application code should be able to safely treat the media as opened and ready for playback operations such as:
It should not fire just because an
AVPlayerItemobject was created.Affected file / class
File:
Class:
Relevant current logic:
The problem is this line:
It is called before observing
PlayerItem.Status == AVPlayerItemStatus.ReadyToPlay.Root cause
PlayerItem.Error is nullis not a readiness check.Immediately after creating an
AVPlayerItem, the item can still be in an unknown/opening state. AVFoundation exposes readiness throughAVPlayerItem.Status, and the item should be observed until it reachesReadyToPlayorFailed.The Toolkit currently has player-level status observers:
but
MediaOpenedshould be driven by the current item’s status, not by item creation.Proposed fix
Move
MediaElement.MediaOpened()out ofPlatformUpdateSource()and into an observer for the currentAVPlayerItem.Status.Add fields:
Reset them when a new source is assigned:
After creating and replacing the current item, observe the item status:
Do not call
MediaOpened()immediately afterReplaceCurrentItemWithPlayerItem.Instead:
Also dispose the observer:
inside
Dispose(bool disposing).Why this fixes the issue
This makes
MediaOpenedmatch its documented meaning: the media is loaded and ready to play.It also aligns the Toolkit with AVFoundation’s native readiness model:
After applying an app-side reflection workaround that ignored Toolkit
MediaOpenedon iOS and instead watched the underlyingAVPlayer.CurrentItem.Status, the same media elements loaded correctly and playback worked. All behavior became predictable and normal while watching and reacting to the true AVPlayer events.The workaround treated only this native state as opened:
and used
AVPlayerItem.Notifications.ObserveDidPlayToEndTimefor ended playback. That resolved the false-ready /Opening@0/0/Failed@0/0behavior.Minimal reproduction outline
CommunityToolkit.Maui.MediaElement.MediaElementinstances on iOS.Source:MediaOpenedhandler, log by reflection the AVPlayer state vs the MediaElement state. You can access the AVPlayer by running onmediaElement.HandlerChanged:MediaOpenedcan fire while the element is still:and subsequent playback operations can fail.
Requested change
Please update iOS/macOS
MediaManager.ios.csso that:MediaOpened()is not raised immediately afterAVPlayerItemcreation.MediaOpened()is raised only once the current item reachesAVPlayerItemStatus.ReadyToPlay.Duration,Position, and media dimensions are updated around the actual ready transition.AVPlayerItemStatus.FailedtriggersMediaFailed.This would make iOS/macOS
MediaOpenedcorrectly represent native media readiness and match the documentedMediaOpenedcontract.Environment