SRGSSR / srgmediaplayer-apple

An advanced media player library, simple and reliable
MIT License
158 stars 33 forks source link

Crash when playing another media on tvOS 15.2 #114

Closed defagos closed 2 years ago

defagos commented 2 years ago

As described on Letterbox issue tracker, changing the media currently being played crashes on tvOS 15.2 due to an internal implementation change made by Apple.

Issue type

Crash

Description of the problem

Stack trace:

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1c)
    frame #0: 0x00000001800a35bc libobjc.A.dylib`object_isClass + 16
    frame #1: 0x0000000181253f4c Foundation`KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED + 44
    frame #2: 0x0000000181253d60 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 268
    frame #3: 0x000000018125461c Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 68
    frame #4: 0x000000018124d1d4 Foundation`_NSSetObjectValueAndNotify + 284
    frame #5: 0x0000000194248a44 AVKit`-[AVInterstitialController dealloc] + 32
    frame #6: 0x00000001800c0954 libobjc.A.dylib`AutoreleasePoolPage::releaseUntil(objc_object**) + 204
    frame #7: 0x00000001800c0824 libobjc.A.dylib`objc_autoreleasePoolPop + 236
    frame #8: 0x0000000180745e10 CoreFoundation`_CFAutoreleasePoolPop + 28
    frame #9: 0x00000001806a470c CoreFoundation`__CFRunLoopPerCalloutARPEnd + 44
    frame #10: 0x000000018069f9c4 CoreFoundation`__CFRunLoopRun + 2516
    frame #11: 0x000000018069eae4 CoreFoundation`CFRunLoopRunSpecific + 572
    frame #12: 0x00000001835bd5ec GraphicsServices`GSEventRunModal + 160
    frame #13: 0x00000001adc2eac4 UIKitCore`-[UIApplication _run] + 992
    frame #14: 0x00000001adc335e0 UIKitCore`UIApplicationMain + 112
  * frame #15: 0x0000000100fdb6c8 SRGLetterbox-demo`main(argc=1, argv=0x000000016ee2daf0) at main.m:14:16
    frame #16: 0x0000000101671ca0 dyld_sim`start_sim + 20
    frame #17: 0x00000001017710f4 dyld`start + 520

Environment information

Reproducibility

Always reproducible

Code sample

Add to MediasViewController~tvos.m:

- (void)repeatWithController:(SRGMediaPlayerViewController *)playerViewController
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSURL *URL = [NSURL URLWithString:@"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8"];
        [playerViewController.controller playURL:URL];
        [self repeatWithController:playerViewController];
    });
}

and call right after SRGMediaPlayerViewController presentation:

[self repeatWithController:playerViewController];

This periodically updates the media being played, which should usually be properly implemented with a custom AVPlayerViewController overlay. The above is sufficient to reproduce the issue, though.

Steps to reproduce

  1. Update the tvOS demo code as described above.
  2. Play some content with SRG Media Player on tvOS.
  3. Wait until the crash happens.
defagos commented 2 years ago

The issue arises because of internal changes made by Apple to AVPlayerViewController implementation in tvOS 15.2.

When playing another media, our current SRGMediaPlayerViewController implementation first resets the state (setting the player to nil first), then prepares playback (setting the player to a fresh AVPlayer instance). At the SRGMediaPlayerViewController level we track player changes with KVO and switch the player view controller player accordingly. Thus, with our current implementation, the AVPlayerViewController player instance is updated twice in a row when playing another media, the first time to nil, then to a new instance.

Before tvOS 15 this was not an issue. But since tvOS 15 switching the AVPlayerViewController player instance several times in a row can lead to crashes, likely because of an incorrect KVO implementation to track interstitials updates. This also means that the crash does not affect SRGMediaPlayerViewController on iOS, as interstitials are not available on this platform.

We can fix the crash by avoiding unnecessary player changes at SRGMediaPlayerController level so that KVObservation at SRGMediaPlayerViewController level minimizes updates as well. This requires two kinds of code changes:

This issue will be reported to Apple, though in the future we might switch to AVQueuePlayer. In this case a controller will always have one AVQueuePlayer instance, which should avoid this kind of problems.

Fix proposal available for review on feature/tvos15-crash-fix.

defagos commented 2 years ago

Merged into develop.

defagos commented 2 years ago

I opened a dedicated radar.

When testing my sample code I also discovered that the player associated with AVPlayerViewController leaks on iOS after an update. The workaround applied for the crash does not help in fixing this issue so it should probably be reported separately to Apple.