Stop the progress save from getting the app killed at suspension

17 of the build 270 crash logs are 0xdead10cc kills on the main thread
inside DataManager.save(), every one reached from the 5-second
PlayerService progress-save timer. During background audio the timer
keeps writing; when playback stops the app can be suspended mid-save,
still holding the SQLite lock in the shared app-group container.

Wrap the periodic save in a background-task assertion when the app is
not active, and skip the write when the position has not moved since
the last save, so a paused player stops writing every five seconds.

The remaining six 0xdead10cc logs die in DataManager.init recovering
the WAL during a background launch. They are most likely the aftermath
of these dirty kills, so store opening is left as is.
This commit is contained in:
Arkadiusz Fal
2026-10-14 18:08:32 +02:00
parent e5cea1b6c8
commit eeee74f35f

View File

@@ -131,6 +131,8 @@ final class PlayerService {
private var progressSaveTimer: Timer?
private let progressSaveInterval: TimeInterval = 5
/// Last position written by the periodic progress save.
private var lastSavedProgress: (videoID: String, seconds: TimeInterval)?
/// Now Playing service for Control Center/Lock Screen.
private let nowPlayingService = NowPlayingService()
@@ -2781,8 +2783,30 @@ final class PlayerService {
guard let video = state.currentVideo,
state.currentTime > 0 else { return }
// A paused player keeps the timer running; rewriting the same position
// only adds database writes, including while the app is being suspended.
let position = (videoID: video.id.videoID, seconds: state.currentTime)
guard lastSavedProgress?.videoID != position.videoID
|| lastSavedProgress?.seconds != position.seconds else { return }
lastSavedProgress = position
// Save locally only during playback - no iCloud sync overhead.
// Live streams still get a history entry, but no resume position.
#if os(iOS)
// In the background (audio playback) the app can be suspended the
// moment playback stops. Suspending mid-save holds the SQLite lock in
// the shared app-group container and iOS kills the app with
// 0xdead10cc, so keep it running until the save has committed.
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
if UIApplication.shared.applicationState != .active {
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "SaveWatchProgress")
}
defer {
if backgroundTask != .invalid {
UIApplication.shared.endBackgroundTask(backgroundTask)
}
}
#endif
dataManager.updateWatchProgressLocal(for: video, seconds: state.currentTime, duration: state.duration, isLive: state.isLive)
watchHistorySyncService?.recordProgress(videoID: video.id.videoID)