mirror of
https://github.com/yattee/yattee.git
synced 2026-08-06 07:11:28 +00:00
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.
64 lines
1.8 KiB
Swift
64 lines
1.8 KiB
Swift
//
|
|
// ContinueWatchingGridCard.swift
|
|
// Yattee
|
|
//
|
|
// Grid card view for continue watching items.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct TappableContinueWatchingGridCard: View {
|
|
let entry: WatchEntry
|
|
let onRemove: () -> Void
|
|
|
|
private var video: Video {
|
|
entry.toVideo()
|
|
}
|
|
|
|
var body: some View {
|
|
ContinueWatchingGridCard(entry: entry)
|
|
.tappableVideo(
|
|
video,
|
|
startTime: entry.watchedSeconds,
|
|
queueSource: .manual,
|
|
sourceLabel: String(localized: "queue.source.continueWatching")
|
|
)
|
|
.videoContextMenu(
|
|
video: video,
|
|
customActions: [
|
|
VideoContextAction(
|
|
String(localized: "continueWatching.remove"),
|
|
systemImage: "xmark.circle",
|
|
role: .destructive,
|
|
action: onRemove
|
|
)
|
|
],
|
|
context: .continueWatching,
|
|
startTime: entry.watchedSeconds
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Grid card for continue watching items with automatic DeArrow support.
|
|
struct ContinueWatchingGridCard: View {
|
|
let entry: WatchEntry
|
|
|
|
private var video: Video {
|
|
entry.toVideo()
|
|
}
|
|
|
|
var body: some View {
|
|
VideoCardView(
|
|
video: video,
|
|
watchProgress: entry.progress,
|
|
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
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
// Note: Preview requires a WatchEntry object from SwiftData context.
|
|
// See ContinueWatchingView.swift for usage examples.
|