fix pip screen bugs

This commit is contained in:
2026-09-26 17:51:25 +08:00
parent a046790ab0
commit 027171cf59
9 changed files with 400 additions and 381 deletions
@@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import platform.AVFAudio.AVAudioSession
import platform.AVFAudio.AVAudioSessionCategoryPlayback
import platform.AVFAudio.AVAudioSessionInterruptionNotification
import platform.AVFAudio.AVAudioSessionInterruptionOptionKey
import platform.AVFAudio.AVAudioSessionInterruptionOptionShouldResume
@@ -24,7 +23,6 @@ import platform.AVFAudio.AVAudioSessionInterruptionTypeKey
import platform.AVFAudio.AVAudioSessionRouteChangeNotification
import platform.AVFAudio.AVAudioSessionRouteChangeReasonKey
import platform.AVFAudio.AVAudioSessionRouteChangeReasonOldDeviceUnavailable
import platform.AVFAudio.setActive
import platform.AVFoundation.AVPlayer
import platform.AVFoundation.AVPlayerItem
import platform.AVFoundation.AVPlayerItemDidPlayToEndTimeNotification
@@ -131,6 +129,9 @@ actual class PlayerController {
private var lastEndMark: TimeSource.Monotonic.ValueTimeMark? = null
private var audioReady = false
private var audioActive = false
private var playWhenReady = false
private var audioSessionReadyObserver: Any? = null
/** CarPlay / 锁屏 Now Playing 标题用的当前歌词;null 表示显示歌名。 */
private var nowPlayingLyric: String? = null
@@ -143,18 +144,50 @@ actual class PlayerController {
observePlaybackFailed()
observeAudioRouteChanges()
observeAudioInterruptions()
observeAudioSessionReady()
}
private fun ensureAudioReady() {
if (audioReady) return
if (audioActive || audioReady) return
audioReady = true
runCatching {
val session = AVAudioSession.sharedInstance()
session.setCategory(AVAudioSessionCategoryPlayback, error = null)
session.setActive(true, error = null)
}.onFailure { AppLog.e(TAG, "ensureAudioReady session fail", it) }
runCatching { setupRemoteCommands() }
.onFailure { AppLog.e(TAG, "ensureAudioReady remoteCommands fail", it) }
NSNotificationCenter.defaultCenter.postNotificationName(
aName = "llmpPrepareAudioSession",
`object` = null,
userInfo = null,
)
}
private fun observeAudioSessionReady() {
audioSessionReadyObserver = NSNotificationCenter.defaultCenter.addObserverForName(
name = "llmpAudioSessionReady",
`object` = null,
queue = null,
) { notification: NSNotification? ->
val ok = (notification?.userInfo?.get("ok") as? NSNumber)?.boolValue ?: true
dispatch_async(dispatch_get_main_queue()) {
if (!ok) {
audioReady = false
playWhenReady = false
AppLog.e(TAG, "audio session activate failed")
if (_state.value.isPlaying) {
player.pause()
_state.update { it.copy(isPlaying = false) }
}
return@dispatch_async
}
audioActive = true
if (playWhenReady && _state.value.isPlaying) {
playWhenReady = false
player.play()
startPositionUpdates()
updateNowPlayingInfo()
} else {
playWhenReady = false
}
}
}
}
private fun setupRemoteCommands() {
@@ -270,7 +303,7 @@ actual class PlayerController {
queue = null,
) { notification: NSNotification? ->
val ended = notification?.`object` as? AVPlayerItem
// 桌面歌词 dummy black.mp4 也会发 DidPlayToEnd;只处理本控制器的音乐 item。
// 只处理本控制器的音乐 item。
// Kotlin/Native 的 === 对 ObjC 对象包装器不可靠,须用 isEqual 比较指针。
if (ended == null || !isMusicItem(ended)) {
return@addObserverForName
@@ -614,17 +647,24 @@ actual class PlayerController {
ensureAudioReady()
val current = _state.value.current
if (loadedUri.isNullOrBlank() && current != null) {
playWhenReady = false
AppLog.e(TAG, "play() abort reason=noLoadedUri id=${current.musicId}")
_state.update { it.copy(isPlaying = false, error = "No playable URI") }
return
}
player.play()
_state.update { it.copy(isPlaying = true, error = null) }
startPositionUpdates()
updateNowPlayingInfo()
if (audioActive) {
player.play()
startPositionUpdates()
updateNowPlayingInfo()
} else {
playWhenReady = true
updateNowPlayingInfo()
}
}
actual fun pause() {
playWhenReady = false
resumeAfterInterruption = false
endScrub()
player.pause()
@@ -908,8 +948,12 @@ actual class PlayerController {
watchItemStatus(item, path, music)
if (autoPlay) {
ensureAudioReady()
player.play()
startPositionUpdates()
if (audioActive) {
player.play()
startPositionUpdates()
} else {
playWhenReady = true
}
} else {
player.pause()
positionJob?.cancel()