From eeee74f35f1769fa17debaa7b34fdb1cb4d58762 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 14 Oct 2026 18:08:32 +0200 Subject: [PATCH] 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. --- Yattee/Services/Player/PlayerService.swift | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Yattee/Services/Player/PlayerService.swift b/Yattee/Services/Player/PlayerService.swift index 7a49f4f7..11758804 100644 --- a/Yattee/Services/Player/PlayerService.swift +++ b/Yattee/Services/Player/PlayerService.swift @@ -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)