Skip to content

πŸ› Paywall looping video crashes with NSInternalInconsistencyException β€” AVQueuePlayer + AVPlayerLooper handed to AVPlayerViewController (KVO currentItem.status)Β #6985

Description

@jijopulikkottil

Describe the bug

Environment

  • Component: RevenueCatUI Paywalls V2 β€” video background component
  • Platform: iOS 16+ (release builds, observed via Crashlytics in production)

Summary
When a Paywall V2 offering uses a looping video background, RevenueCatUI creates an AVQueuePlayer + AVPlayerLooper and assigns it to an AVPlayerViewController. Because AVPlayerLooper swaps the queue player's currentItem without sending standard KVO notifications, AVKit's internal AVPlayerController crashes while removing its currentItem.* observers during deallocation. This is a fatal, non-catchable exception.

Platform

iOS

SDK version

5.66.0

SDK integration method

Swift Package Manager

StoreKit version

{"StoreKit 1 (default on versions <5.0.0. Can be enabled in versions >=5.0.0 with .with(storeKitVersion" => ".storeKit1))"}

OS version

iOS 26.5

Xcode version

26.3

Device and/or simulator

Device

Environment

Production

How widespread is the issue

5

Debug logs

Fatal Exception: NSInternalInconsistencyException
Cannot remove an observer <NSKeyValueObservance 0x1736832d0> for the key path "currentItem.status" from <AVQueuePlayer 0x1326a9a00>, most likely because the value for the key "currentItem" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the AVQueuePlayer class.

Crashed: com.google.firebase.crashlytics.ios.exception
0  BushnellGolf                   0x10d1bc8 FIRCLSProcessRecordAllThreads + 392 (FIRCLSProcess.c:392)
1  BushnellGolf                   0x10d1fd4 FIRCLSProcessRecordAllThreads + 423 (FIRCLSProcess.c:423)
2  BushnellGolf                   0x10e0448 FIRCLSHandler + 39 (FIRCLSHandler.m:39)
3  BushnellGolf                   0x10e0244 __FIRCLSExceptionRecord_block_invoke + 241 (FIRCLSException.mm:241)
4  libdispatch.dylib              0x1b1e4 _dispatch_client_callout + 16
5  libdispatch.dylib              0x112e0 _dispatch_lane_barrier_sync_invoke_and_complete + 56
6  BushnellGolf                   0x10df1d8 FIRCLSExceptionRecord + 243 (FIRCLSException.mm:243)
7  BushnellGolf                   0x10dfd04 FIRCLSExceptionRecordNSException + 127 (FIRCLSException.mm:127)
8  BushnellGolf                   0x10deddc FIRCLSTerminateHandler() + 410 (FIRCLSException.mm:410)
9  libc++abi.dylib                0x141f8 std::__terminate(void (*)()) + 16
10 libc++abi.dylib                0x7dcc __cxxabiv1::is_class_type(__cxxabiv1::__shim_type_info const*) + 186
11 libobjc.A.dylib                0x3fb58 objc_exception_rethrow + 44
12 Foundation                     0x4df24 NSKVODeallocate + 564
13 AVKit                          0xe7d78 __82-[AVPlayerController(AVMediaSelection) reloadCurrentMediaSelectionsAsynchronously]_block_invoke + 300
14 libdispatch.dylib              0x19a8 _dispatch_call_block_and_release + 32
15 libdispatch.dylib              0x1b1e4 _dispatch_client_callout + 16
16 libdispatch.dylib              0x9fb0 _dispatch_lane_serial_drain + 740
17 libdispatch.dylib              0xaae4 _dispatch_lane_invoke + 448
18 libdispatch.dylib              0x14dac _dispatch_root_queue_drain_deferred_wlh + 284
19 libdispatch.dylib              0x146ac _dispatch_workloop_worker_thread + 720
20 libsystem_pthread.dylib        0x13b0 _pthread_wqthread + 292
21 libsystem_pthread.dylib        0x8c0 start_wqthread + 8

Steps to reproduce

Configure a Paywall V2 offering with a looping video background.
Present the paywall on iOS 16+.
Dismiss it (e.g. after purchase/cancel) or background/foreground while the video is playing, so the AVPlayerViewController deallocates while AVKit's async media-selection reload is in flight.
Crash occurs intermittently (race-dependent), most frequently around paywall teardown.

Other information

**Source location**
RevenueCatUI/Templates/V2/Components/Video/VideoPlayerViewUIView.swift (identical on main as of this report):

let aVQueuePlayer = AVQueuePlayer()
self.looper = AVPlayerLooper(player: aVQueuePlayer, templateItem: playerItem)
avPlayer = aVQueuePlayer
...
let controller = AVPlayerViewController()
controller.player = player


**Root cause**
This is the well-documented AVFoundation/AVKit interaction bug:

AVPlayerLooper keeps playback looping by replacing the AVQueuePlayer's currentItem, but it does not emit the will/did-change KVO notifications that AVKit expects.
AVPlayerViewController's internal AVPlayerController registers KVO observers on currentItem.* keypaths (here currentItem.status, triggered via reloadCurrentMediaSelectionsAsynchronously; the same family includes currentItem.videoComposition from the Live Text / video-frame-analysis feature).
On controller dealloc, AVKit calls removeObserver:forKeyPath:, the runtime detects currentItem changed without a notification, and throws.
Apple explicitly warns that mutating a queue player's items breaks KVO compliance, so an AVPlayerLooper-driven AVQueuePlayer should not be assigned to AVPlayerViewController.


**Impact**
Fatal crash in production for users who see a video-backed paywall.
App-side mitigations (deferring paywall dismissal to a later run-loop turn) only narrow the race because the failing teardown runs on a background dispatch queue; they do not eliminate it.
Updating the SDK does not currently help, as the code is unchanged on main.
Suggested fixes (for RevenueCat)
Set allowsVideoFrameAnalysis = false on the AVPlayerViewController (iOS 16+) β€” addresses the video-frame-analysis variant.
More robustly, avoid handing an AVPlayerLooper-driven AVQueuePlayer to AVPlayerViewController. Options:
Render the looping video via an AVPlayerLayer-backed UIView instead of AVPlayerViewController (no internal AVPlayerController, so the problematic observers are never registered), or
Tear down the looper and nil the controller's player deterministically before the controller deallocates, or
Implement looping via end-of-item notification + seek instead of AVPlayerLooper.

**Request**
Could you confirm whether this is a known issue and whether a fix is planned? We'd like to know the target version that resolves this so we can schedule our SDK upgrade. In the meantime we're evaluating switching the affected offering to an image background as a workaround.

Happy to provide additional Crashlytics aggregates, device/OS breakdowns

Additional context

This crash is reporting in Crashlytics. Reported 3.9k events for 3.5k users.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions