From 959641b642f046f269ddcd808f93d39456f3f572 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Tue, 14 Jul 2026 23:08:39 +0200 Subject: [PATCH] Sync watch progress on natural video completion and backgrounding Naturally finished videos only saved their 100% progress locally (play() deliberately skips the sync-on-switch when videoEndedNaturally is set), so the watched state never reached other devices in the autoplay flow. Save completion through updateWatchProgress, which queues the iCloud sync. Also queue the current playback position when the app enters background, so backgrounding mid-video hands the position off to other devices instead of only syncing on explicit stop or video switch. --- Yattee/Services/Player/PlayerService.swift | 12 ++++++++++-- Yattee/YatteeApp.swift | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Yattee/Services/Player/PlayerService.swift b/Yattee/Services/Player/PlayerService.swift index a685f4f1..e95645f0 100644 --- a/Yattee/Services/Player/PlayerService.swift +++ b/Yattee/Services/Player/PlayerService.swift @@ -2563,8 +2563,10 @@ final class PlayerService { let completedDuration = video.duration > 0 ? video.duration : state.duration guard completedDuration > 0 else { return } - // Save the full duration as watched time when video completes - dataManager.updateWatchProgressLocal(for: video, seconds: completedDuration, duration: completedDuration) + // Save the full duration as watched time and queue for iCloud sync. + // This is the only save for a naturally finished video — play() skips + // saveProgressAndSync() when videoEndedNaturally is set. + dataManager.updateWatchProgress(for: video, seconds: completedDuration, duration: completedDuration) // Update Handoff activity with completed time handoffManager?.updatePlaybackTime(completedDuration) @@ -2584,6 +2586,12 @@ final class PlayerService { NotificationCenter.default.post(name: .watchHistoryDidChange, object: nil) } + /// Saves current progress and queues it for iCloud sync without ending playback. + /// Called when the app enters background so the position reaches other devices. + func syncWatchProgress() { + saveProgressAndSync() + } + private func checkSponsorBlockSegments(at time: Double) { // Read settings - use defaults if settings manager not available let enabled = settingsManager?.sponsorBlockEnabled ?? true diff --git a/Yattee/YatteeApp.swift b/Yattee/YatteeApp.swift index 9dd6dfd1..8e58a26e 100644 --- a/Yattee/YatteeApp.swift +++ b/Yattee/YatteeApp.swift @@ -282,6 +282,8 @@ struct YatteeApp: App { // Flush pending CloudKit changes when entering background if newPhase == .background { appEnvironment.cloudKitSync.stopForegroundPolling() + // Queue current watch progress so the position reaches other devices + appEnvironment.playerService.syncWatchProgress() Task { await appEnvironment.cloudKitSync.flushPendingChanges() } @@ -330,6 +332,8 @@ struct YatteeApp: App { // Flush pending CloudKit changes when entering background if newPhase == .background { appEnvironment.cloudKitSync.stopForegroundPolling() + // Queue current watch progress so the position reaches other devices + appEnvironment.playerService.syncWatchProgress() #if os(iOS) // Persist pending SwiftData changes and run the CloudKit flush