-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[video_player] Fix iOS crash with multiple players #4202
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
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you explain in the PR description why the crash happens and how this fixes the crash?
|
||
[self.player replaceCurrentItemWithPlayerItem:nil]; | ||
_player = nil; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After applying the original fix (removing the observer), I started observing crashes when doing Hot Reload
with Flutter debugger attached on the screen displaying the player. The code crashes at:
[self.player removeObserver:self forKeyPath:@"rate"]
The immediate reason for the crash is that the player is no longer KVO subscribed to the property rate
, therefore removeObserver
call crashes. I've added additional logging to pinpoint why that happens. It turns out that in the flow described, the disposeSansEventChannel
is called twice in a row for the same player.
The issue is not present in the original code, because of this line:
[self.player replaceCurrentItemWithPlayerItem:nil]
Upon a first call to the disposeSansEventChannel
method, the self.player.currentItem
is unassigned. On the second call, the currentItem
is nil
and therefore any of the removeObserver
calls become no-op.
I applied the same solution to the player.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when it's called twice, should we short circuit up front?
- (void)dispose... {
if (!_player) {
return;
}
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll short circuit it with:
- (void)disposeSansEventChannel {
if (_disposed) {
return;
}
_disposed = YES;
// ...
}
as _disposed
is already set there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense. Can you also add some comments here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hellohuanlin I've updated the code according to our discussion. I've added a comment on the short circuit method.
I don't think removing the observer requires additional comments as the API specifies to clear any observations you've subscribed to in the first place. LMK if you'd add anything else, please 🙏
@hellohuanlin I've added the detailed description in the PR (as requested). I'll update the code as requested and provide some additional comments. |
Oh i meant comment in the code. It would be helpful for future readers. |
@@ -519,6 +519,14 @@ - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments | |||
/// is useful for the case where the Engine is in the process of deconstruction | |||
/// so the channel is going to die or is already dead. | |||
- (void)disposeSansEventChannel { | |||
// This check prevents the crash caused by removing the KVO observers twice. | |||
// When performing a Hot Restart, the leftover players are disposed directly | |||
// and also receive onTextureUnregistered: callback leading to possible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: just wanna be clearer
"the leftover players are disposed twice - once directly (from which function?), and once when receiving onTextureUnregistered callback"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hellohuanlin I've updated the comment yet again to name the calling methods!
@turekj Could you update the PR to add these missing components? All of these elements are required before the PR can be fully reviewed and landed. |
From PR triage: @turekj Are you still planning on updating this per the feedback above? |
345f3d1
to
2ba7752
Compare
@hellohuanlin I've updated the comments so that they're clearer ✅ @stuartmorgan I've added a changelog entry, bumped the package number, and also implemented the test cases for both of the changes I made. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some feedbacks mainly on unit tests
} | ||
|
||
// [FLTVideoPlayerPlugin dispose:error:] selector is dispatching the [FLTVideoPlayer dispose] call | ||
// with a 1-second delay keeping a strong reference to the player. The polling ensures the player |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does this 1 sec delay come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The [FLTVideoPlayerPlugin dispose:error:]
has this piece of code:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
if (!player.disposed) {
[player dispose];
}
});
} while ([[NSDate date] timeIntervalSinceNow] < [end timeIntervalSinceNow]); | ||
|
||
if (condition()) { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this check necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not valid anymore
@@ -439,4 +546,22 @@ - (void)validateTransformFixForOrientation:(UIImageOrientation)orientation { | |||
XCTAssertEqual(t.ty, expectY); | |||
} | |||
|
|||
- (void)waitForCondition:(BOOL (^)(void))condition withTimeout:(NSTimeInterval)timeout { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think you can just use NSPredicate and expectation for this kind of stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea, I've refactored the code ✅
@@ -519,6 +519,14 @@ - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments | |||
/// is useful for the case where the Engine is in the process of deconstruction | |||
/// so the channel is going to die or is already dead. | |||
- (void)disposeSansEventChannel { | |||
// This check prevents the crash caused by removing the KVO observers twice. | |||
// When performing a Hot Restart, the leftover players are disposed directly | |||
// by [FLTVideoPlayerPlugin initialize:] method and also receive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "the leftover players are disposed once directly by ... method and then disposed again when receiving ..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the wording ✅
@hellohuanlin implemented all rounds of feedback ✅ |
@turekj Could you resolve the conflicts that have shown up? Once that's done this can be landed. |
@stuartmorgan conflicts resolved ✅ |
Thanks! |
flutter/packages@6889cca...3e8b813 2023-07-18 [email protected] [video_player] Fix iOS crash with multiple players (flutter/packages#4202) 2023-07-17 [email protected] [pigeon] Enable Android emulator tests in CI (flutter/packages#4484) 2023-07-17 [email protected] [video_player] Add optional web options [Platform interface] (flutter/packages#4433) 2023-07-17 [email protected] [google_maps_flutter_platform_interface] Platform interface changes for #3258 (flutter/packages#4478) 2023-07-17 [email protected] [video_player] fix: add missing isPlaybackLikelyToKeepUp check. (flutter/packages#3826) 2023-07-17 [email protected] [camerax] Add flash configuration for image capture (flutter/packages#3800) 2023-07-17 [email protected] Remove `equatable` and `xml` allowances (flutter/packages#4489) 2023-07-17 [email protected] [ci] Switch Linux platform tests to LUCI (flutter/packages#4479) 2023-07-17 [email protected] [ci] Adjust bot configurations (flutter/packages#4485) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
flutter/packages@6889cca...3e8b813 2023-07-18 [email protected] [video_player] Fix iOS crash with multiple players (flutter/packages#4202) 2023-07-17 [email protected] [pigeon] Enable Android emulator tests in CI (flutter/packages#4484) 2023-07-17 [email protected] [video_player] Add optional web options [Platform interface] (flutter/packages#4433) 2023-07-17 [email protected] [google_maps_flutter_platform_interface] Platform interface changes for flutter#3258 (flutter/packages#4478) 2023-07-17 [email protected] [video_player] fix: add missing isPlaybackLikelyToKeepUp check. (flutter/packages#3826) 2023-07-17 [email protected] [camerax] Add flash configuration for image capture (flutter/packages#3800) 2023-07-17 [email protected] Remove `equatable` and `xml` allowances (flutter/packages#4489) 2023-07-17 [email protected] [ci] Switch Linux platform tests to LUCI (flutter/packages#4479) 2023-07-17 [email protected] [ci] Adjust bot configurations (flutter/packages#4485) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
This PR fixes crash in
video_player
package on iOS.flutter/flutter#124937
Detailed description
I can observe the crash when displaying a couple of the different players (different URLs) inside a
ListView
. The crash happens inside ofAVFoundation
framework:In order to debug the issue, I ran the application using the plugin with
Zombie Objects
inspection turned on. TheZombie Objects
reports the following issue:This, in conjunction with the
NSKeyValueWillChange
line present in the stack trace led me to believe, that culprit is sending a KVO notification to theFLTVideoPlayer
instance that's deallocated.Next, I examined the plugin code and identified one property that doesn't have the KVO removed. In
addObserversForItem:player:
method inFLTVideoPlayerPlugin.m
:The observer for
@"rate"
is never cleaned up. To be entirely sure that the issue comes from KVO that's not cleaned up, I've added the following class:and registered the observation as follows (notice that
EmptyObserver
is never retained and deallocated instantly):The exception I got seems to be matching the underlying issue:
This means the fix for the issue is to add the following to
disposeSansEventChannel
method:After applying the patch, I can no longer crash the player.
Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes].CHANGELOG.md
to add a description of the change, [following repository CHANGELOG style].///
).