mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-30 18:48:30 +01:00
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:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user