mirror of
https://github.com/yattee/yattee.git
synced 2026-10-23 13:12:53 +00:00
Let a replay start the watch it says it is
This commit is contained in:
@@ -681,8 +681,17 @@ final class PlayerService {
|
||||
watchHistorySyncService?.beginSession(video: video)
|
||||
}
|
||||
|
||||
// The end of the previous watch left the player set up for a video that
|
||||
// is over: no save timer, and the natural-end flag that tells play() the
|
||||
// final save is already done. A second watch has to undo both, in the
|
||||
// order play() does it — the flag before the seek, the timer only once
|
||||
// playback is running again, so a seek that never gets there cannot
|
||||
// leave a timer saving progress for an ended video.
|
||||
videoEndedNaturally = false
|
||||
|
||||
await seek(to: 0)
|
||||
resume()
|
||||
startProgressSaveTimer()
|
||||
}
|
||||
|
||||
/// Stops playback and clears the player.
|
||||
|
||||
@@ -63,10 +63,25 @@ struct HistoryListView: View {
|
||||
/// scrolls in and out again does not ask twice — including the ones whose
|
||||
/// fetch failed, which would otherwise retry on every pass.
|
||||
@State private var hydrationAttempted: Set<String> = []
|
||||
/// The metadata fetches in flight, by video ID.
|
||||
///
|
||||
/// Held on to so a reload can cancel them: without that, switching accounts
|
||||
/// mid-scroll leaves a whole load's worth of requests running against an
|
||||
/// instance whose history is already off screen, and starts a second set
|
||||
/// beside them.
|
||||
@State private var hydrationTasks: [String: Task<Void, Never>] = [:]
|
||||
/// The rows waiting for a fetch slot, in the order they asked for one.
|
||||
@State private var hydrationQueue: [String] = []
|
||||
|
||||
/// How many IDs one page of server history asks for.
|
||||
private static let serverPageSize = 60
|
||||
|
||||
/// How many metadata fetches a load may have in flight at once.
|
||||
///
|
||||
/// A page is 60 rows and a search hydrates all of them at once, which
|
||||
/// unbounded would be 60 simultaneous requests to a public instance.
|
||||
private static let hydrationConcurrency = 6
|
||||
|
||||
/// The page to ask the server for next.
|
||||
///
|
||||
/// Read from the rows that are loaded rather than counted up from the last
|
||||
@@ -587,6 +602,7 @@ struct HistoryListView: View {
|
||||
private func startLoad() {
|
||||
loadTask?.cancel()
|
||||
loadTask = nil
|
||||
cancelHydration()
|
||||
|
||||
guard let account = beginLoad() else { return }
|
||||
beginPageLoad(1, account: account)
|
||||
@@ -768,35 +784,69 @@ struct HistoryListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches one row's metadata, as the row appears.
|
||||
/// Asks for one row's metadata, as the row appears.
|
||||
///
|
||||
/// Through the account's own instance: every ID the server returns is a
|
||||
/// YouTube ID, and the account's instance is the one that has it.
|
||||
/// The request itself waits its turn: the row joins the queue, and the
|
||||
/// fetches are started a few at a time from there.
|
||||
private func hydrate(_ row: HistoryRow, account: Account) {
|
||||
guard !row.isHydrated,
|
||||
!hydrationAttempted.contains(row.videoID),
|
||||
let appEnvironment,
|
||||
let instance = appEnvironment.accountsManager.instance(for: account)
|
||||
appEnvironment.accountsManager.instance(for: account) != nil
|
||||
else { return }
|
||||
|
||||
hydrationAttempted.insert(row.videoID)
|
||||
hydrationQueue.append(row.videoID)
|
||||
pumpHydration(account: account)
|
||||
}
|
||||
|
||||
/// Starts as many queued fetches as there are free slots.
|
||||
///
|
||||
/// Through the account's own instance: every ID the server returns is a
|
||||
/// YouTube ID, and the account's instance is the one that has it.
|
||||
private func pumpHydration(account: Account) {
|
||||
guard let appEnvironment,
|
||||
let instance = appEnvironment.accountsManager.instance(for: account)
|
||||
else { return }
|
||||
|
||||
let videoID = row.videoID
|
||||
let generation = loadGeneration
|
||||
|
||||
Task {
|
||||
do {
|
||||
let video = try await appEnvironment.contentService.video(id: videoID, instance: instance)
|
||||
while hydrationTasks.count < Self.hydrationConcurrency, !hydrationQueue.isEmpty {
|
||||
let videoID = hydrationQueue.removeFirst()
|
||||
|
||||
hydrationTasks[videoID] = Task {
|
||||
do {
|
||||
let video = try await appEnvironment.contentService.video(id: videoID, instance: instance)
|
||||
guard generation == loadGeneration else { return }
|
||||
apply(video, to: videoID)
|
||||
} catch {
|
||||
// The row keeps its placeholder. A video the account watched
|
||||
// is part of its history whether or not this instance can
|
||||
// describe it right now.
|
||||
}
|
||||
|
||||
// A finished fetch frees its slot for the next row — but only
|
||||
// for the load it belongs to. A later one has already emptied
|
||||
// this queue and started its own, and giving back a slot there
|
||||
// would drop a task that is still running.
|
||||
guard generation == loadGeneration else { return }
|
||||
apply(video, to: videoID)
|
||||
} catch {
|
||||
// The row keeps its placeholder. A video the account watched is
|
||||
// part of its history whether or not this instance can describe
|
||||
// it right now.
|
||||
hydrationTasks[videoID] = nil
|
||||
pumpHydration(account: account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops everything the current load was still going to fetch, and stops
|
||||
/// what it had already started.
|
||||
private func cancelHydration() {
|
||||
for task in hydrationTasks.values {
|
||||
task.cancel()
|
||||
}
|
||||
|
||||
hydrationTasks.removeAll()
|
||||
hydrationQueue.removeAll()
|
||||
}
|
||||
|
||||
private func apply(_ video: Video, to videoID: String) {
|
||||
guard let index = rows.firstIndex(where: { $0.videoID == videoID }) else { return }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user