fix(ios): keep the screen awake under video and the lock screen honest

- The idle timer was recomputed only when Now Playing was republished,
  so minimising and then reopening a playing episode let the screen
  sleep under it. It now follows `minimized` too.
- With background audio off, leaving the app paused but left video on,
  so lock-screen Play started decoding into an unseen layer. Video goes
  off on the way out either way.
- Now Playing reported full speed through a stall, and its clock ran on.
  Buffering now reports a rate of zero.
- The lock screen gains ten-second skip forward and back.
This commit is contained in:
nvrl committed 2026-10-12 22:39:19 +02:00
1 parent 77a926cbf5
commit 7994097a25
2 files changed
+39 -7

No files matched your search

+6 -1
View File
@@ -339,7 +339,12 @@ final class MpvPlayer {
paused = value
onStateChanged?()
case 5: muted = value
case 8: if !loading { buffering = value }
case 8:
// Said, so the lock screen's clock stops through a stall.
if !loading, buffering != value {
buffering = value
onStateChanged?()
}
case 13: if !loading { seeking = value }
default: break
}
+33 -6
View File
@@ -75,7 +75,12 @@ final class PlayerHost {
private(set) var index = 0
/// Playing, but not on screen: the episode keeps going and the mini
/// transport is what says so.
var minimized = false
var minimized = false {
// The mini transport has no picture to keep awake for; back on screen
// and still playing, it has one again. Nothing else recomputes this
// until the next pause, so the screen slept under the video.
didSet { updateIdleTimer() }
}
/// The open file's chapters, already resolved into openings and endings.
private(set) var chapters = ChapterPlan(entries: [], creditsStart: nil)
/// What stopped this episode, until the viewer dismisses it.
@@ -479,9 +484,11 @@ final class PlayerHost {
func enteredBackground() {
save()
guard isOpen, let player else { return }
if SettingsStore().backgroundAudio {
player.setVideoEnabled(false)
} else {
// The picture goes either way: with background audio off the lock
// screen's Play can still start the file, and decoding video into a
// layer nobody sees is work iOS ends a process for.
player.setVideoEnabled(false)
if !SettingsStore().backgroundAudio {
player.setPaused(true)
}
publishNowPlaying()
@@ -508,12 +515,15 @@ final class PlayerHost {
info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.video.rawValue
info[MPMediaItemPropertyPlaybackDuration] = player?.duration ?? 0
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.position ?? 0
info[MPNowPlayingInfoPropertyPlaybackRate] = (player?.paused ?? true) ? 0.0 : speed
// Zero while it waits on the network too, or the lock screen's clock
// runs on through a stall.
let moving = !(player?.paused ?? true) && !(player?.buffering ?? false)
info[MPNowPlayingInfoPropertyPlaybackRate] = moving ? speed : 0.0
center.nowPlayingInfo = info
let commands = MPRemoteCommandCenter.shared()
commands.nextTrackCommand.isEnabled = next != nil
commands.previousTrackCommand.isEnabled = index > 0
UIApplication.shared.isIdleTimerDisabled = !(player?.paused ?? true) && !minimized
updateIdleTimer()
let artKey = mediaKey(episode)
if artworkFor != artKey {
@@ -531,6 +541,11 @@ final class PlayerHost {
}
}
/// Keep the screen on while a picture is playing on it, and only then.
private func updateIdleTimer() {
UIApplication.shared.isIdleTimerDisabled = isOpen && !(player?.paused ?? true) && !minimized
}
private func updateElapsed(_ seconds: Double) {
let center = MPNowPlayingInfoCenter.default()
guard var info = center.nowPlayingInfo else { return }
@@ -565,6 +580,18 @@ final class PlayerHost {
MainActor.assumeIsolated { self?.retreat() }
return .success
}
// Ten seconds either way: what a video's lock-screen transport offers
// in place of a music player's previous and next.
commands.skipForwardCommand.preferredIntervals = [10]
commands.skipForwardCommand.addTarget { [weak self] _ in
MainActor.assumeIsolated { self?.seek(to: (self?.player?.position ?? 0) + 10) }
return .success
}
commands.skipBackwardCommand.preferredIntervals = [10]
commands.skipBackwardCommand.addTarget { [weak self] _ in
MainActor.assumeIsolated { self?.seek(to: max((self?.player?.position ?? 0) - 10, 0)) }
return .success
}
commands.stopCommand.addTarget { [weak self] _ in
MainActor.assumeIsolated { self?.close() }
return .success