mirror of
https://github.com/yattee/yattee.git
synced 2026-08-05 23:01:28 +00:00
Stop tracking resume progress for live streams
With "Partially Watched Videos" set to Ask, opening a live stream showed the resume sheet with a playback timestamp. A live position is relative to the live edge, not a fixed timeline, so it is meaningless as a playhead. Live views are still recorded in history, but carry no resume position: - WatchEntry gains a persisted isLive flag plus recordLiveWatch(), which stores no position and clears state a drifting HLS duration may have left behind. progress is always 0 and toVideo() propagates the flag so history rows render the LIVE badge. - from(video:) clamps duration, keeping Piped's -1 live sentinel out of storage. - DataManager routes live saves through recordLiveWatch() and clears the flag on the non-live path (before updateProgress, so the auto-finish check is not suppressed by the hard-0 live progress), letting an entry heal once the stream becomes a VOD. - PlayerService skips completion saves, always starts at the live edge, and drops any startTime passed for a live video. - The resume prompt, the "Continue at" button label, thumbnail progress bars, "X remaining", Continue Watching and tvOS Top Shelf all exclude live entries. - CloudKit syncs isLive with the rest of the watch state: the conflict resolver carries it with watchedSeconds/isFinished and the sync engine applies it when merging into an existing entry. It is read leniently, so the schema version stays at 2 and older clients keep parsing these records.
This commit is contained in:
@@ -170,6 +170,13 @@ struct TappableVideoModifier: ViewModifier {
|
||||
private func playVideoAndQueueRest() {
|
||||
guard let env = appEnvironment else { return }
|
||||
|
||||
// Live streams have no fixed timeline, so a resume position is meaningless -
|
||||
// never ask, always join at the live edge.
|
||||
guard !video.isLive else {
|
||||
playVideoWithStartTime(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine the saved progress: prefer explicitly passed startTime, then query database
|
||||
// This handles cases where startTime is passed from views like Continue Watching/History
|
||||
// that already have the watch position, avoiding issues with data not being synced yet
|
||||
|
||||
@@ -105,7 +105,8 @@ struct VideoThumbnailView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var watchProgressBar: some View {
|
||||
if let progress = watchProgress, progress > 0 && progress < 1 {
|
||||
// Live streams have no fixed timeline, so a progress bar is meaningless
|
||||
if !isLive, let progress = watchProgress, progress > 0 && progress < 1 {
|
||||
GeometryReader { geo in
|
||||
Rectangle()
|
||||
.fill(.red)
|
||||
|
||||
@@ -51,8 +51,9 @@ struct ContinueWatchingGridCard: View {
|
||||
VideoCardView(
|
||||
video: video,
|
||||
watchProgress: entry.progress,
|
||||
customMetadata: entry.isFinished ? nil : String(localized: "home.history.remaining \(entry.remainingTime)"),
|
||||
customDuration: entry.remainingTime
|
||||
customMetadata: entry.isFinished || entry.isLive ? nil : String(localized: "home.history.remaining \(entry.remainingTime)"),
|
||||
// Live streams have no remaining time - fall back to the LIVE badge
|
||||
customDuration: entry.isLive ? nil : entry.remainingTime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ struct ContinueWatchingView: View {
|
||||
|
||||
/// Filtered to only show in-progress videos.
|
||||
private var inProgressEntries: [WatchEntry] {
|
||||
watchHistory.filter { !$0.isFinished && $0.watchedSeconds > 10 }
|
||||
watchHistory.filter { !$0.isFinished && !$0.isLive && $0.watchedSeconds > 10 }
|
||||
}
|
||||
|
||||
// Grid layout configuration
|
||||
@@ -237,7 +237,7 @@ struct ContinueWatchingView: View {
|
||||
video: entry.toVideo(),
|
||||
style: rowStyle,
|
||||
watchProgress: entry.progress,
|
||||
customMetadata: entry.isFinished ? nil : String(localized: "home.history.remaining \(entry.remainingTime)")
|
||||
customMetadata: entry.isFinished || entry.isLive ? nil : String(localized: "home.history.remaining \(entry.remainingTime)")
|
||||
)
|
||||
.tappableVideo(
|
||||
entry.toVideo(),
|
||||
|
||||
@@ -305,7 +305,7 @@ struct HistoryListView: View {
|
||||
video: video,
|
||||
style: rowStyle,
|
||||
watchProgress: watchProgress(for: entry),
|
||||
customMetadata: entry.isFinished ? nil : String(localized: "home.history.remaining \(entry.remainingTime)")
|
||||
customMetadata: entry.isFinished || entry.isLive ? nil : String(localized: "home.history.remaining \(entry.remainingTime)")
|
||||
)
|
||||
.tappableVideo(
|
||||
video,
|
||||
|
||||
@@ -1463,7 +1463,7 @@ struct HomeView: View {
|
||||
private func loadContinueWatchingData() {
|
||||
let allHistory = dataManager?.watchHistory(limit: 100) ?? []
|
||||
// Filter to in-progress only (same logic as ContinueWatchingView)
|
||||
recentContinueWatching = allHistory.filter { !$0.isFinished && $0.watchedSeconds > 10 }
|
||||
recentContinueWatching = allHistory.filter { !$0.isFinished && !$0.isLive && $0.watchedSeconds > 10 }
|
||||
continueWatchingCount = recentContinueWatching.count
|
||||
}
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ struct VideoInfoView: View {
|
||||
/// and resume setting is continueWatching or ask.
|
||||
private var playButtonLabel: String {
|
||||
guard let video = displayedVideo,
|
||||
!video.isLive,
|
||||
let savedProgress = dataManager?.watchProgress(for: video.id.videoID),
|
||||
savedProgress >= 5,
|
||||
video.duration > 0,
|
||||
@@ -2269,7 +2270,14 @@ struct VideoInfoView: View {
|
||||
/// Play the video, respecting the user's resume action setting for partially watched videos.
|
||||
private func playVideo() {
|
||||
guard let video = displayedVideo, let env = appEnvironment else { return }
|
||||
|
||||
|
||||
// Live streams have no fixed timeline, so a resume position is meaningless -
|
||||
// never ask, always join at the live edge.
|
||||
guard !video.isLive else {
|
||||
playVideoWithStartTime(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Get saved watch progress from database
|
||||
let savedProgress = env.dataManager.watchProgress(for: video.id.videoID)
|
||||
let videoDuration = video.duration
|
||||
|
||||
Reference in New Issue
Block a user