fix(mpv): prevent UI freeze on player exit by tearing down mpv off main thread

mpv_terminate_destroy() blocks until mpv's threads (including the
vo_avfoundation output thread) are joined, and that teardown needs the
main run loop to complete. Calling it via queue.sync from MpvPlayerView
deinit (main thread) deadlocked/froze the UI on playback exit.

Remove the wakeup callback synchronously while self is still alive, then
run mpv_terminate_destroy on the serial queue via async so deinit returns
immediately and the main thread is never blocked. Also release the PiP
timebase/controller in deinit.
This commit is contained in:
Fredrik Burmester
2026-05-30 10:40:10 +02:00
parent 769c7a2432
commit 6fe464088b
2 changed files with 26 additions and 9 deletions

View File

@@ -220,20 +220,27 @@ final class MPVLayerRenderer {
statusObservation?.invalidate()
statusObservation = nil
queue.sync { [weak self] in
guard let self, let handle = self.mpv else { return }
mpv_set_wakeup_callback(handle, nil, nil)
mpv_terminate_destroy(handle)
if let handle = self.mpv {
self.mpv = nil
// Remove the wakeup callback synchronously while `self` is still
// alive so it can never fire against a deallocated instance.
mpv_set_wakeup_callback(handle, nil, nil)
// Destroy mpv OFF the main thread. mpv_terminate_destroy() blocks
// until all mpv threads (including the vo_avfoundation output thread)
// are joined, and that teardown needs the main run loop to finish.
// Calling it via queue.sync from deinit (main thread) deadlocks/freezes
// the UI. queue.async only references `handle`, never `self`.
queue.async {
mpv_terminate_destroy(handle)
}
}
DispatchQueue.main.async { [weak self] in
guard let self else { return }
let layer = self.displayLayer
DispatchQueue.main.async {
if #available(iOS 18.0, *) {
self.displayLayer.sampleBufferRenderer.flush(removingDisplayedImage: true, completionHandler: nil)
layer.sampleBufferRenderer.flush(removingDisplayedImage: true, completionHandler: nil)
} else {
self.displayLayer.flushAndRemoveImage()
layer.flushAndRemoveImage()
}
}

View File

@@ -150,6 +150,16 @@ final class PiPController: NSObject {
CMTimebaseSetRate(tb, rate: Float64(rate))
}
}
deinit {
if let tb = timebase {
CMTimebaseSetRate(tb, rate: 0)
}
sampleBufferDisplayLayer?.controlTimebase = nil
timebase = nil
pipController?.delegate = nil
pipController = nil
}
}
// MARK: - AVPictureInPictureControllerDelegate