diff --git a/Yattee/Services/Player/PlayerService.swift b/Yattee/Services/Player/PlayerService.swift index bc30f5ea..5faa776b 100644 --- a/Yattee/Services/Player/PlayerService.swift +++ b/Yattee/Services/Player/PlayerService.swift @@ -3196,6 +3196,19 @@ extension PlayerService: PlayerBackendDelegate { state.setPlaybackState(.ended) saveProgressAsCompleted() + + // The watch is over, so the session ends with it — after the completion + // save, which is the last progress this session has to mirror. A + // finished watch's session must not be left standing to absorb a future + // watch of the same video: the next load of it would otherwise reach + // `beginSessionIfNeeded`, recognise the video, keep the already-posted + // session and never mirror the second watch. Every way a finished video + // is played again on purpose starts a session of its own — + // `replayCurrentVideo()` explicitly, any other load through + // `beginSessionIfNeeded`. Ending the session does not touch a post + // already in flight. + watchHistorySyncService?.endSession() + delegate?.playerServiceDidFinishPlaying(self) // Repeat one: restart immediately - no countdown, independent of queue contents diff --git a/Yattee/Views/Home/HistoryListView.swift b/Yattee/Views/Home/HistoryListView.swift index 63272457..5ba36f4e 100644 --- a/Yattee/Views/Home/HistoryListView.swift +++ b/Yattee/Views/Home/HistoryListView.swift @@ -938,6 +938,12 @@ struct HistoryListView: View { // The rows may belong to another account by now; the removal // still happened, but this list is not the one to take it out of. guard generation == loadGeneration else { return } + + // Accepted: a watch finishing at the same moment can post this + // same video after the server took the delete, and this + // completion — arriving later than the order the server saw — + // then takes out a row the server still has. The list is wrong + // only until the next load reads the account's history again. removeRow(videoID) } catch { appEnvironment.toastManager.showError(