Skip to content

Fix ios media element start bug - #3281

Open
ne0rrmatrix wants to merge 9 commits into
CommunityToolkit:mainfrom
ne0rrmatrix:FixIosMediaElementStartBug
Open

Fix ios media element start bug#3281
ne0rrmatrix wants to merge 9 commits into
CommunityToolkit:mainfrom
ne0rrmatrix:FixIosMediaElementStartBug

Conversation

@ne0rrmatrix

Copy link
Copy Markdown
Member

[PR] Fix iOS/macOS MediaElement.MediaOpened Fires Before AVPlayerItem is ReadyToPlay

Description of Change

This PR fixes a bug on iOS and macOS where MediaElement.MediaOpened fires prematurely — immediately after the AVPlayerItem object is created, before the native AVFoundation player signals that the media is actually ready to play. This causes MediaElement to report itself as "opened" with zero duration, and subsequent playback operations (Play(), Stop(), Seek()) fail because the underlying media hasn't loaded.

The Problem

In MediaManager.PlatformUpdateSource(), MediaElement.MediaOpened() was called right after Player.ReplaceCurrentItemWithPlayerItem(), gated only by PlayerItem is not null && PlayerItem.Error is null:

// OLD (buggy) code
PlayerItem = asset is not null ? new AVPlayerItem(asset) : null;
Player.ReplaceCurrentItemWithPlayerItem(PlayerItem);

if (PlayerItem is not null && PlayerItem.Error is null)
{
    MediaElement.MediaOpened();  // ❌ Too early! Media isn't ready yet
    ...
}

PlayerItem.Error is null only confirms that the AVPlayerItem object was created without immediate failure — it does not mean the media has loaded, has a valid duration, or is ready for playback. The correct AVFoundation readiness signal is AVPlayerItem.Status == AVPlayerItemStatus.ReadyToPlay.

The Fix

The fix moves MediaElement.MediaOpened() into a KVO observer on AVPlayerItem.Status that only fires when the item reaches AVPlayerItemStatus.ReadyToPlay:

Change Description
Added hasMediaOpened field Prevents duplicate MediaOpened events across KVO callbacks
Added PlayerItemStatusObserver property Tracks the KVO subscription on AVPlayerItem.Status
Added PlayerItemStatusChanged() method Observes status changes and fires MediaOpened/MediaFailed at the correct times
Removed premature MediaOpened() call No longer fires from PlatformUpdateSource() — delegates to the observer
Simplified PlatformUpdateSpeed() First-time speed setting now defers to PlayerItemStatusChanged, which calls Player?.Play() after ReadyToPlay
Proper observer lifecycle PlayerItemStatusObserver is disposed when switching sources and in Dispose()

New PlayerItemStatusChanged() Method

async void PlayerItemStatusChanged(NSObservedChange change)
{
    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:
            if (PlayerItem.Error is not null)
            {
                var message = $"{PlayerItem.Error.LocalizedDescription} - {PlayerItem.Error.LocalizedFailureReason}";
                MediaElement.MediaFailed(new MediaFailedEventArgs(message));
                Logger.LogError("{LogMessage}", message);
            }
            break;
    }
}

Behavior Before vs After

Scenario Before (Buggy) After (Fixed)
MediaOpened fires Immediately after AVPlayerItem creation Only when AVPlayerItem.Status == ReadyToPlay
Duration at MediaOpened 0 (media not loaded) Correct duration from AVPlayerItem.Duration
Play() after MediaOpened Often fails → transitions to Failed Works correctly
ShouldAutoPlay = false with Speed set Media starts playing anyway Media respects ShouldAutoPlay and doesn't play until ReadyToPlay
Failed items Silent failure or crash Properly reports MediaFailed with error details

Files Changed

File Changes
src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +73 / −19 lines

Linked Issues

PR Checklist

  • Has a linked Issue, and the Issue has been approved (bug) — #3249 approved by @ne0rrmatrix
  • Has tests — existing device tests cover MediaElement scenarios; the iOS device test infrastructure was not yet in place at the time of this fix. Manual verification confirmed:
    • MediaOpened no longer fires before ReadyToPlay
    • Duration/Position populated correctly
    • ShouldAutoPlay = false respected
    • Failed items fire MediaFailed instead of silently failing
  • Has samples — n/a (bug fix, no new API surface)
  • Rebased on top of main at time of PR
  • Changes adhere to coding standard
  • Documentation created or updated — n/a (no API change)

Additional Information

Root Cause Detail

The AVFoundation framework uses an asynchronous loading model. Creating an AVPlayerItem with new AVPlayerItem(asset) starts loading the media asynchronously. The item's Status property transitions through:

Unknown → ReadyToPlay (or Failed)

Calling MediaOpened() immediately after creation meant it fired during the Unknown state, before the media was loaded. This manifested as:

  • Duration reporting as 0 or CMTime.Indefinite
  • Play() failing silently or transitioning to Failed
  • On multi-element pages, all MediaOpened events firing before any element was actually ready

Manual Verification Steps

  1. Create a .NET MAUI app with CommunityToolkit.Maui.MediaElement
  2. Set ShouldAutoPlay = false on one or more MediaElement instances
  3. Set Source to a local video resource
  4. Listen for MediaOpened and verify:
    • Duration > TimeSpan.Zero
    • MediaWidth > 0 and MediaHeight > 0
    • Play() successfully starts playback
  5. Verify that ShouldAutoPlay = false with no Speed set does not start playback

Platforms Tested

  • iOS Simulator
  • Mac Catalyst (same code path, should behave identically)
  • Android (unaffected — different code path)
  • Windows (unaffected — different code path)

Introduced IDisposable? PlayerItemStatusObserver to track AVPlayerItem status and ensure proper disposal in PlatformUpdateSource and Dispose methods. Refactored status observation to trigger MediaOpened, error handling, and video dimension updates only when ReadyToPlay. Moved video dimension, autoplay, and poster logic to OnPlayerItemReady. Improved error handling and logging for Failed status.
Removed the unnecessary 'using CommunityToolkit.Maui.Media.Services;' directive from MediaManager.macios.cs. This cleanup reduces dependencies and improves code clarity without affecting functionality.
Copilot AI lite review requested due to automatic review settings August 10, 2026 02:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts the iOS/macOS (Mac Catalyst) MediaManager implementation so MediaElement.MediaOpened is raised only after the underlying AVPlayerItem reports ReadyToPlay, preventing “opened” state from being reported before duration/dimensions are available and before playback operations can safely succeed.

Changes:

  • Adds an AVPlayerItem.Status observer and routes MediaOpened/MediaFailed decisions through that status transition instead of firing immediately after item creation.
  • Introduces a hasMediaOpened guard and disposes the new observer when switching sources and during Dispose().
  • Updates speed initialization flow to defer initial behavior until item readiness.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs Outdated
Comment thread src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] iOS/macOS MediaElement.MediaOpened fires before AVPlayerItem is actually ReadyToPlay

2 participants