Skip to content

[video_player_avfoundation] removes unnecessary duration and size check before sending the initialized event #9534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented Jun 30, 2025

Fixes flutter/flutter#166914. The duration check was first introduced here but zero division should probably be handled by the client not the plugin.

Also I suspect that flutter/flutter#91975 wasn't caused by an AVFoundation bug but rather a bug in video_player_avfoundation:

The _isInitialized flag was roughly equivalent to .readyToPlay a long time ago. The flag was later re-purposed to indicate whether an initialized message has already been sent (to make sure the message is only sent once), and that PR also introduced a size check to determine whether the media is loaded (which doesn't seem to be correct, .readyToPlay is the public API for this, see Apple's AVPlayerDemo). But that PR didn't update the onListenWithArguments implementation so the plugin started sending initialized events on first subscription even the AVPlayerItem isn't ready to play. So that looks like a bug in the plugin not in AVFoundation and that allows us to remove a bunch of workarounds.

Couldn't reproduce flutter/flutter#91975 on iOS16 with this patch (I can't install iOS15 sim runtime, it fails silently, but on iOS16 it still hits the size + duration check callpath so I think not much has changed from iOS15 to iOS16 as far as AVFoundation stuff is concerned).
I didn't try to reproduce flutter/flutter#19197 with this patch since the issue doesn't have a minimal repro. I did not change the _isInitialized semantic so I assume the patch won't regress that fix.

More on isInitialized:
The player implementation doesn't have to rely on this property, if AVPlayerItem.Status can not recover from .failed to .readyToPlay (i.e. it can only change to .readyToPlay once). But the documentation doesn't say if that is the case.

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2 3

@LongCatIsLooong LongCatIsLooong changed the title Make sure the AVPlayerItem is .readyToPlay before emitting an initialized event [video_player_avfoundation] Make sure the AVPlayerItem is .readyToPlay before emitting an initialized event Jul 1, 2025
@LongCatIsLooong
Copy link
Contributor Author

I'm still trying to generate a sample video with 0 duration (which seems to be an AVFoundation bug as the video plays in chrome, but not in quicktime) so I can add a test. But much of this change should already be covered by existing tests added for the workarounds in the past.

@LongCatIsLooong LongCatIsLooong marked this pull request as ready for review July 7, 2025 23:46
CGSize size = currentItem.presentationSize;
CGFloat width = size.width;
CGFloat height = size.height;

Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong Jul 7, 2025

Choose a reason for hiding this comment

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

Removed the duration / size checks since now the caller has to guarantee the item is .readyToPlay and I think those checks shouldn't be necessary.

Before this you may enter this if block even when the item's status is .unknown and I believe that's why we added the workarounds (size / duration checks).

@@ -1,3 +1,7 @@
## 2.8.1

* Removes unnecessary workarounds, fixes "initialized" event not firing when the duration of the media is 0.
Copy link
Contributor

Choose a reason for hiding this comment

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

Removes unnecessary workarounds

What workarounds?

Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong Jul 9, 2025

Choose a reason for hiding this comment

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

The size / duration checks removed in the sendInitialized method.

Copy link
Contributor

@hellohuanlin hellohuanlin Jul 29, 2025

Choose a reason for hiding this comment

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

It's unclear from reading the CHANGELOG.

} else if (context == statusContext) {
AVPlayerItem *item = (AVPlayerItem *)object;
switch (item.status) {
case AVPlayerItemStatusFailed:
[self sendFailedToLoadVideoEvent];
break;
case AVPlayerItemStatusUnknown:
break;
case AVPlayerItemStatusReadyToPlay:
[item addOutput:_videoOutput];
Copy link
Contributor

Choose a reason for hiding this comment

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

If status is changed to AVPlayerItemStatusReadyToPlay before the event sink is set up then it seems this addOutput will not be called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops thanks for catching this!

@LongCatIsLooong
Copy link
Contributor Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request simplifies the video initialization logic by correctly using the AVPlayerItem.status. The removal of workarounds makes the code cleaner and easier to understand. The added test and updated documentation are also valuable contributions.

I've added a few suggestions to further improve robustness and maintainability in the Objective-C code, mainly around defensive checks and handling of enum values in switch statements. Overall, this is a solid improvement.

Comment on lines 437 to 450
switch (self.player.currentItem.status) {
case AVPlayerItemStatusUnknown:
// When this method is called when the media is still loading, do nothing:
// sendVideoInitializedEvent or sendFailedToLoadVideoEvent will be called
// by KVO on status updates.
return nil;
case AVPlayerItemStatusReadyToPlay:
[self sendVideoInitializedEvent];
return nil;
case AVPlayerItemStatusFailed:
[self sendFailedToLoadVideoEvent];
return nil;
}
[self setupEventSinkIfReadyToPlay];
return nil;
}

Choose a reason for hiding this comment

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

high

This function is declared to return a FlutterError * (or nil), but it lacks a default case in the switch statement. If self.player.currentItem.status has a value not covered by the case statements (e.g., if the enum is extended in a future SDK), this function would have undefined behavior. Adding a default case that returns nil will fix this correctness issue.

  switch (self.player.currentItem.status) {
    case AVPlayerItemStatusUnknown:
      // When this method is called when the media is still loading, do nothing:
      // sendVideoInitializedEvent or sendFailedToLoadVideoEvent will be called
      // by KVO on status updates.
      return nil;
    case AVPlayerItemStatusReadyToPlay:
      [self sendVideoInitializedEvent];
      return nil;
    case AVPlayerItemStatusFailed:
      [self sendFailedToLoadVideoEvent];
      return nil;
    default:
      NSAssert(NO, @"Unknown AVPlayerItemStatus: %ld", (long)self.player.currentItem.status);
      return nil;
  }

} else if (context == statusContext) {
AVPlayerItem *item = (AVPlayerItem *)object;
switch (item.status) {
case AVPlayerItemStatusFailed:
[self sendFailedToLoadVideoEvent];
break;
case AVPlayerItemStatusUnknown:
break;
case AVPlayerItemStatusReadyToPlay:
[item addOutput:_videoOutput];

Choose a reason for hiding this comment

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

medium

To improve robustness, it's safer to check if the video output has already been added to the player item before calling addOutput:. This check prevents potential issues if the player item status transitions in unexpected ways.

        if (![item.outputs containsObject:_videoOutput]) {
          [item addOutput:_videoOutput];
        }


[self.eventListener videoPlayerDidInitializeWithDuration:duration size:size];
}
_isInitialized = YES;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

looks like the flag is still used by the updatePlayingState method.

Copy link
Contributor

Choose a reason for hiding this comment

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

If _isInitialized == true always implies that player status is ready to play then maybe updateRate can be simplified. Maybe updatePlayingState could instead check that player status is ready to play (canPlayFastForward and canPlaySlowForward do not work when status is not ready to play). Btw was not _isInitialized set to false in onCancelWithArguments before?

@LongCatIsLooong LongCatIsLooong changed the title [video_player_avfoundation] Make sure the AVPlayerItem is .readyToPlay before emitting an initialized event [video_player_avfoundation] removes unnecessary duration and size check before sending the initialized event Aug 11, 2025
@LongCatIsLooong
Copy link
Contributor Author

LongCatIsLooong commented Aug 11, 2025

Renamed the PR since with recent changes I think we're only calling reportInitialized after making sure the status of the item is .readyToPlay.

@stuartmorgan-g
Copy link
Contributor

The duration check was first introduced here but zero division should probably be handled by the client not the plugin.

The plugin is a client; the mini-controller code you changed here is a duplicate of the actual video_player's progress indicator code. That will need to be fixed first, and we'll need to decide if we're okay regressing the div-zero issue in the (unlikely) case of someone updating video_player_avfoundation without having updated video_player.

@@ -383,52 +373,15 @@ - (void)sendFailedToLoadVideoEvent {
}

- (void)reportInitializedIfReadyToPlay {
if (!_isInitialized) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the status API guarantee that it can only transition to readyToPlay once? It seems likely that it would, but I didn't see any docs in a quick look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[video_player] VideoPlayerController::initialize future never resolves nor errors on iOS with the attached video file
4 participants