Skip to content

[video_player] Improve KVO handling on iOS #9718

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

Conversation

stuartmorgan-g
Copy link
Contributor

@stuartmorgan-g stuartmorgan-g commented Jul 31, 2025

This improves the KVO handling in FVPVideoPlayer in several ways:

  • Eliminates the use of methods to do KVO registration and unregistration, since that involved calling self during init and dealloc, which is a dangerous anti-pattern.
  • Eliminates NSKeyValueObservingOptionInitial from the registration, as this was non-obviously creating a call to self, within init, for every registration. In practice, none of them were necessary from what I could determine:
    • The handlers for most keys just collect information to send to the event listener, but there is no event listener during init (it's set just after), so those were expensive no-ops.
    • The handlers related to initialization require the item to be in the AVPlayerItemStatusReadyToPlay state, and items start in AVPlayerItemStatusUnknown, so the initial state call won't do anything useful.
  • Stores a dictionary of registrations, which is used for unregistration, so that it's impossible to add a new key to the observation set but forget to unregister it. Previously the two lists of strings had to be kept in sync. New static functions (to avoid self calls) process those lists.
  • Clears the list after unregistering to avoid double-unregistration locally to the class, instead of requiring a subclass to contain a workaround to avoid problems in the superclass code.

Pre-Review Checklist

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

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

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

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 refactors the Key-Value Observing (KVO) handling in FVPVideoPlayer to improve robustness and maintainability. The changes eliminate calls to self from init and dealloc by introducing static helper functions for observer registration and removal. It also moves to a dictionary-based approach for managing observers, which prevents mismatches between registration and unregistration, and makes the dispose method idempotent to avoid crashes on hot restart. The changes are well-structured and address a common anti-pattern in Objective-C. I have one minor suggestion to fix a typo in a comment.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@stuartmorgan-g
Copy link
Contributor Author

test-exempt: code refactor with no semantic change

@"rate" : [NSValue valueWithPointer:rateContext],
};
FVPRegisterKeyValueObservers(self, _playerItemObservations, item);
FVPRegisterKeyValueObservers(self, _playerObservations, _player);
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the original implementation specified NSKeyValueObservingOptionInitial, but here I don't see how the initial values are going to be reported?

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Aug 1, 2025

Choose a reason for hiding this comment

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

In the PR description it says:

The handlers for most keys just collect information to send to the event listener, but there is no event listener during init (it's set just after), so those were expensive no-ops.
The handlers related to initialization require the item to be in the AVPlayerItemStatusReadyToPlay state, and items start in AVPlayerItemStatusUnknown, so the initial state call won't do anything useful.

So this means the item is always in .unknown state when the event listener is set? Is it possible to get an item that's already .failed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not according to the docs:

When a player item is created, its status is AVPlayerItem.Status.unknown, meaning its media hasn’t been loaded and has not yet been enqueued for playback. Associating a player item with an AVPlayer immediately begins enqueuing the item’s media and preparing it for playback.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I feel that doesn't guarantee that AVPlayer.status can't change synchronously especially when the KVO registration happens after _player = [avFactory playerWithPlayerItem:item]; (which I assume is when the player item is associated with an AVPlayer). On the other hand I agree that it probably doesn't make too much sense for AVFoundation to change the status synchronously (maybe if the AVPlayer implementation has logic that does simple sanity checks as soon as a new item is assigned?). Should we add an assert before the registration to make sure the status is .unknown?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

especially when the KVO registration happens after _player = [avFactory playerWithPlayerItem:item]; (which I assume is when the player item is associated with an AVPlayer)

That's a great point; I wasn't considering the order of operations there if the internals make synchronous status changes. Rather than relying on undocumented behavior and asserting, I've instead moved the KVO registration to setEventListener:. That makes things much easier to reason about, because we're no longer doing any of this from init, so we can call whatever we want, and unlike the assert means clients won't get incorrect runtime behavior if AVPlayer ever starts actually doing that.

Rather than add back NSKeyValueObservingOptionInitial (which would cause a bunch of initial state event updates in Dart that didn't used to exist, and in a random order), I've added an explicit call to check the state and, if it has already changed, send an initialization event (which is now safe because this isn't calling methods from init) before registering the listeners.

(The listener is set right after init, so in practice this will have almost no effect on when any of this happens, but I think this structure is much cleaner.)

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

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

LGTM (I think you mentioned that the player instances will be re-created on hot-restart right?)

@stuartmorgan-g
Copy link
Contributor Author

LGTM (I think you mentioned that the player instances will be re-created on hot-restart right?)

Yes, there's Dart code in the plugin that re-initializes the native code if a hot restart happens.

@stuartmorgan-g stuartmorgan-g added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 7, 2025
@auto-submit auto-submit bot merged commit 34948d1 into flutter:main Aug 7, 2025
80 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: video_player platform-ios platform-macos
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants