fix(ios): keep PiP timebase renderer-owned
This commit is contained in:
@@ -789,7 +789,7 @@
|
|||||||
repositoryURL = "https://github.com/edde746/MPVKit";
|
repositoryURL = "https://github.com/edde746/MPVKit";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = revision;
|
kind = revision;
|
||||||
revision = 32bfe07056049bad711ec2b5a8976ab203863c8f;
|
revision = 42dcc3123000361d0be54fbaec981043b77e1e05;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/edde746/MPVKit",
|
"location" : "https://github.com/edde746/MPVKit",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "32bfe07056049bad711ec2b5a8976ab203863c8f"
|
"revision" : "42dcc3123000361d0be54fbaec981043b77e1e05"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/edde746/MPVKit",
|
"location" : "https://github.com/edde746/MPVKit",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "32bfe07056049bad711ec2b5a8976ab203863c8f"
|
"revision" : "42dcc3123000361d0be54fbaec981043b77e1e05"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import UIKit
|
|||||||
func pipDidStop(restored: Bool)
|
func pipDidStop(restored: Bool)
|
||||||
func pipDidFailToStart(error: Error?)
|
func pipDidFailToStart(error: Error?)
|
||||||
func pipSetPlaying(_ playing: Bool)
|
func pipSetPlaying(_ playing: Bool)
|
||||||
func pipSkip(byInterval seconds: Double)
|
func pipSkip(byInterval seconds: Double, completion: @escaping () -> Void)
|
||||||
var isPipPlaying: Bool { get }
|
var isPipPlaying: Bool { get }
|
||||||
var pipDuration: Double { get }
|
var pipDuration: Double { get }
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ import UIKit
|
|||||||
/// Forward play/pause commands from PiP overlay to mpv
|
/// Forward play/pause commands from PiP overlay to mpv
|
||||||
func pipSetPlaying(_ playing: Bool)
|
func pipSetPlaying(_ playing: Bool)
|
||||||
/// Forward skip forward/backward commands from PiP overlay to mpv
|
/// Forward skip forward/backward commands from PiP overlay to mpv
|
||||||
func pipSkip(byInterval seconds: Double)
|
func pipSkip(byInterval seconds: Double, completion: @escaping () -> Void)
|
||||||
/// Query whether mpv is currently playing
|
/// Query whether mpv is currently playing
|
||||||
var isPipPlaying: Bool { get }
|
var isPipPlaying: Bool { get }
|
||||||
/// Get total duration in seconds
|
/// Get total duration in seconds
|
||||||
@@ -99,9 +99,7 @@ import UIKit
|
|||||||
self.delegateHelper = helper
|
self.delegateHelper = helper
|
||||||
pipController = AVPictureInPictureController(contentSource: contentSource)
|
pipController = AVPictureInPictureController(contentSource: contentSource)
|
||||||
pipController?.delegate = helper
|
pipController?.delegate = helper
|
||||||
if #available(iOS 14.2, *) {
|
pipController?.canStartPictureInPictureAutomaticallyFromInline = false
|
||||||
pipController?.canStartPictureInPictureAutomaticallyFromInline = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable/disable system auto-PiP (starts PiP automatically on background transition)
|
/// Enable/disable system auto-PiP (starts PiP automatically on background transition)
|
||||||
@@ -110,30 +108,15 @@ import UIKit
|
|||||||
pipController?.canStartPictureInPictureAutomaticallyFromInline = enabled
|
pipController?.canStartPictureInPictureAutomaticallyFromInline = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sync the layer's controlTimebase with the actual playback position.
|
/// MPVKit owns the sample-buffer layer timebase. PiP only reads it.
|
||||||
/// This makes the PiP progress bar show the correct time.
|
|
||||||
func syncTimebase(currentTime: Double, isPlaying: Bool) {
|
func syncTimebase(currentTime: Double, isPlaying: Bool) {
|
||||||
guard let timebase = sampleBufferLayer?.controlTimebase else { return }
|
|
||||||
let cmTime = CMTime(seconds: currentTime, preferredTimescale: 1000)
|
|
||||||
CMTimebaseSetTime(timebase, time: cmTime)
|
|
||||||
CMTimebaseSetRate(timebase, rate: isPlaying ? 1.0 : 0.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure the layer has a timebase so the PiP progress UI can follow mpv.
|
/// Ensure the layer has MPVKit's renderer-owned timebase before PiP starts.
|
||||||
func warmLayer(currentTime: Double, isPlaying: Bool) {
|
func warmLayer(currentTime: Double, isPlaying: Bool) {
|
||||||
guard let sampleBufferLayer else { return }
|
if sampleBufferLayer?.controlTimebase == nil {
|
||||||
if sampleBufferLayer.controlTimebase == nil {
|
print("[MpvPipController] Waiting for MPVKit renderer timebase before PiP")
|
||||||
var timebase: CMTimebase?
|
|
||||||
CMTimebaseCreateWithSourceClock(
|
|
||||||
allocator: kCFAllocatorDefault,
|
|
||||||
sourceClock: CMClockGetHostTimeClock(),
|
|
||||||
timebaseOut: &timebase
|
|
||||||
)
|
|
||||||
if let tb = timebase {
|
|
||||||
sampleBufferLayer.controlTimebase = tb
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
syncTimebase(currentTime: currentTime, isPlaying: isPlaying)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public API
|
// MARK: - Public API
|
||||||
@@ -188,6 +171,7 @@ import UIKit
|
|||||||
|
|
||||||
/// Invalidate the playback state so PiP updates its UI (play/pause button)
|
/// Invalidate the playback state so PiP updates its UI (play/pause button)
|
||||||
func invalidatePlaybackState() {
|
func invalidatePlaybackState() {
|
||||||
|
guard #available(iOS 15.0, *) else { return }
|
||||||
pipController?.invalidatePlaybackState()
|
pipController?.invalidatePlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,8 +294,11 @@ import UIKit
|
|||||||
) {
|
) {
|
||||||
let seconds = CMTimeGetSeconds(skipInterval)
|
let seconds = CMTimeGetSeconds(skipInterval)
|
||||||
print("[MpvPipController] PiP skip by \(seconds)s")
|
print("[MpvPipController] PiP skip by \(seconds)s")
|
||||||
controller?.delegate?.pipSkip(byInterval: seconds)
|
guard let delegate = controller?.delegate else {
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delegate.pipSkip(byInterval: seconds, completion: completionHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -504,13 +504,17 @@ extension MpvPlayerPlugin: MpvPipDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipSkip(byInterval seconds: Double) {
|
func pipSkip(byInterval seconds: Double, completion: @escaping () -> Void) {
|
||||||
guard let playerCore = playerCore else { return }
|
guard let playerCore = playerCore else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
let newTime = max(0, playerCore.timePos + seconds)
|
let newTime = max(0, playerCore.timePos + seconds)
|
||||||
playerCore.command(["seek", String(newTime), "absolute"])
|
playerCore.commandAsync(["seek", String(newTime), "absolute"]) { [weak self] _ in
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
self?.syncPipTimebase()
|
self?.pipController?.invalidatePlaybackState()
|
||||||
self?.pipController?.invalidatePlaybackState()
|
completion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
name: plezy
|
name: plezy
|
||||||
description: "A beautiful Plex and Jellyfin client for Flutter"
|
description: "A beautiful Plex and Jellyfin client for Flutter"
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 2.1.1+98
|
version: 2.1.2+99
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.12.0 <4.0.0"
|
sdk: ">=3.12.0 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user