diff --git a/Model/Applications/VideosAPI.swift b/Model/Applications/VideosAPI.swift index e9594484..061ec840 100644 --- a/Model/Applications/VideosAPI.swift +++ b/Model/Applications/VideosAPI.swift @@ -97,6 +97,8 @@ extension VideosAPI { return } + VideosCacheModel.shared.storeVideo(video) + var newItem = item newItem.id = UUID() newItem.video = video diff --git a/Model/HistoryModel.swift b/Model/HistoryModel.swift index 80009f35..f6ad98da 100644 --- a/Model/HistoryModel.swift +++ b/Model/HistoryModel.swift @@ -39,15 +39,6 @@ extension PlayerModel { } .onCompletion { _ in self.logger.info("LOADED history details: \(watch.videoID)") - - if self.historyItemBeingLoaded == watch.videoID { - self.logger.info("setting no history loaded") - self.historyItemBeingLoaded = nil - } - - if let watch = self.historyItemsToLoad.popLast() { - self.loadHistoryVideoDetails(watch) - } } } @@ -70,10 +61,11 @@ extension PlayerModel { let watch: Watch! + let duration = self.playerTime.duration.seconds + if results?.isEmpty ?? true { - if seconds < 1 { - return - } + if seconds < 3, duration > 3 { return } + watch = Watch(context: self.backgroundContext) watch.videoID = id watch.appName = currentVideo.app.rawValue @@ -82,7 +74,6 @@ extension PlayerModel { watch = results?.first } - let duration = self.playerTime.duration.seconds if duration.isFinite, duration > 0 { watch.videoDuration = duration } @@ -98,6 +89,8 @@ extension PlayerModel { watch.watchedAt = Date() try? self.backgroundContext.save() + + FeedModel.shared.calculateUnwatchedFeed() } } diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index f4b81322..71b6fcfc 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -96,11 +96,6 @@ final class PlayerModel: ObservableObject { @Published var currentItem: PlayerQueueItem! { didSet { handleCurrentItemChange() } } @Published var videoBeingOpened: Video? { didSet { seek.reset() } } @Published var historyVideos = [Video]() - @Published var queueItemBeingLoaded: PlayerQueueItem? - @Published var queueItemsToLoad = [PlayerQueueItem]() - @Published var historyItemBeingLoaded: Video.ID? - @Published var historyItemsToLoad = [Watch]() - @Published var preservedTime: CMTime? @Published var sponsorBlock = SponsorBlockAPI() diff --git a/Model/Player/PlayerQueue.swift b/Model/Player/PlayerQueue.swift index bc92d59a..5019aa08 100644 --- a/Model/Player/PlayerQueue.swift +++ b/Model/Player/PlayerQueue.swift @@ -165,7 +165,8 @@ extension PlayerModel { currentItem.playbackTime = time let playTime = currentItem.shouldRestartPlaying ? CMTime.zero : time - playerAPI(newItem.video).loadDetails(currentItem, failureHandler: { self.videoLoadFailureHandler($0, video: self.currentItem.video) }) { newItem in + guard let video = newItem.video else { return } + playerAPI(video).loadDetails(currentItem, failureHandler: { self.videoLoadFailureHandler($0, video: self.currentItem.video) }) { newItem in self.playItem(newItem, at: playTime) } } @@ -276,42 +277,40 @@ extension PlayerModel { restoredQueue.append(contentsOf: Defaults[.queue]) queue = restoredQueue.compactMap { $0 } + queue.forEach { loadQueueVideoDetails($0) } } func loadQueueVideoDetails(_ item: PlayerQueueItem) { - guard !accounts.current.isNil, !item.hasDetailsLoaded, let video = item.video else { return } + guard !accounts.current.isNil, !item.hasDetailsLoaded else { return } let videoID = item.video?.videoID ?? item.videoID - if queueItemBeingLoaded == nil { - logger.info("loading queue details: \(videoID)") - queueItemBeingLoaded = item - } else { - logger.info("POSTPONING details load: \(videoID)") - queueItemsToLoad.append(item) - return - } + let video = item.video ?? Video(app: item.app ?? .local, instanceURL: item.instanceURL, videoID: videoID) - playerAPI(video)?.loadDetails(item, completionHandler: { [weak self] newItem in - guard let self else { return } - - self.queue.filter { $0.videoID == item.videoID }.forEach { item in + let replaceQueueItem: (PlayerQueueItem) -> Void = { newItem in + self.queue.filter { $0.videoID == videoID }.forEach { item in if let index = self.queue.firstIndex(of: item) { self.queue[index] = newItem } } + } - self.logger.info("LOADED queue details: \(videoID)") + if let video = VideosCacheModel.shared.retrieveVideo(video.cacheKey) { + var item = item + item.id = UUID() + item.video = video + replaceQueueItem(item) + return + } - if self.queueItemBeingLoaded == item { - self.logger.info("setting nothing loaded") - self.queueItemBeingLoaded = nil - } + playerAPI(video)? + .loadDetails(item, completionHandler: { [weak self] newItem in + guard let self else { return } - if let item = self.queueItemsToLoad.popLast() { - self.loadQueueVideoDetails(item) - } - }) + replaceQueueItem(newItem) + + self.logger.info("LOADED queue details: \(videoID)") + }) } private func videoLoadFailureHandler(_ error: RequestError, video: Video? = nil) { diff --git a/Model/Player/PlayerQueueItem.swift b/Model/Player/PlayerQueueItem.swift index 763d239b..28720489 100644 --- a/Model/Player/PlayerQueueItem.swift +++ b/Model/Player/PlayerQueueItem.swift @@ -8,6 +8,8 @@ struct PlayerQueueItem: Hashable, Identifiable, Defaults.Serializable { var id = UUID() var video: Video! var videoID: Video.ID + var app: VideosApp? + var instanceURL: URL? var playbackTime: CMTime? var videoDuration: TimeInterval? @@ -15,14 +17,25 @@ struct PlayerQueueItem: Hashable, Identifiable, Defaults.Serializable { .init( video, videoID: watch.videoID, + app: watch.app, + instanceURL: watch.instanceURL, playbackTime: CMTime.secondsInDefaultTimescale(watch.stoppedAt), videoDuration: watch.videoDuration ) } - init(_ video: Video? = nil, videoID: Video.ID? = nil, playbackTime: CMTime? = nil, videoDuration: TimeInterval? = nil) { + init( + _ video: Video? = nil, + videoID: Video.ID? = nil, + app: VideosApp? = nil, + instanceURL: URL? = nil, + playbackTime: CMTime? = nil, + videoDuration: TimeInterval? = nil + ) { self.video = video self.videoID = videoID ?? video!.videoID + self.app = app + self.instanceURL = instanceURL self.playbackTime = playbackTime self.videoDuration = videoDuration } diff --git a/Model/Player/PlayerQueueItemBridge.swift b/Model/Player/PlayerQueueItemBridge.swift index d4c76289..430b278f 100644 --- a/Model/Player/PlayerQueueItemBridge.swift +++ b/Model/Player/PlayerQueueItemBridge.swift @@ -33,6 +33,8 @@ struct PlayerQueueItemBridge: Defaults.Bridge { return [ "localURL": localURL, "videoID": value.videoID, + "app": (value.app ?? value.video.app)?.rawValue ?? "", + "instanceURL": (value.instanceURL ?? value.video.instanceURL)?.absoluteString ?? "", "playbackTime": playbackTime, "videoDuration": videoDuration ] @@ -41,6 +43,8 @@ struct PlayerQueueItemBridge: Defaults.Bridge { func deserialize(_ object: Serializable?) -> Value? { guard let object else { return nil } + var app: VideosApp? + var instanceURL: URL? var playbackTime: CMTime? var videoDuration: TimeInterval? @@ -57,6 +61,16 @@ struct PlayerQueueItemBridge: Defaults.Bridge { videoDuration = TimeInterval(duration) } + if let appString = object["app"], + !appString.isEmpty + { + app = VideosApp(rawValue: appString) + } + + if let url = object["instanceURL"]?.url { + instanceURL = url + } + if let localUrlString = object["localURL"], !localUrlString.isEmpty, let localURL = URL(string: localUrlString) @@ -71,7 +85,10 @@ struct PlayerQueueItemBridge: Defaults.Bridge { guard let videoID = object["videoID"] else { return nil } return PlayerQueueItem( + .init(app: app ?? .local, instanceURL: instanceURL, videoID: videoID), videoID: videoID, + app: app, + instanceURL: instanceURL, playbackTime: playbackTime, videoDuration: videoDuration ) diff --git a/Model/Video.swift b/Model/Video.swift index 480058fe..c61b776e 100644 --- a/Model/Video.swift +++ b/Model/Video.swift @@ -197,7 +197,7 @@ struct Video: Identifiable, Equatable, Hashable { } var isLocal: Bool { - !VideoID.isValid(videoID) + !VideoID.isValid(videoID) && videoID != Self.fixtureID } var displayTitle: String { diff --git a/Model/Watch.swift b/Model/Watch.swift index 78bd55d2..887c4af8 100644 --- a/Model/Watch.swift +++ b/Model/Watch.swift @@ -71,7 +71,10 @@ extension Watch { } var finished: Bool { - progress >= Double(watchedThreshold) + guard videoDuration.isFinite, !videoDuration.isZero else { + return true + } + return progress >= Double(watchedThreshold) } var watchedAtString: String? { diff --git a/Shared/Home/HistoryView.swift b/Shared/Home/HistoryView.swift index 48ce7689..1a2437aa 100644 --- a/Shared/Home/HistoryView.swift +++ b/Shared/Home/HistoryView.swift @@ -1,8 +1,6 @@ import SwiftUI struct HistoryView: View { - static let detailsPreloadLimit = 50 - var limit = 10 @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) @@ -33,7 +31,6 @@ struct HistoryView: View { } .onAppear { visibleWatches - .prefix(Self.detailsPreloadLimit) .forEach(player.loadHistoryVideoDetails) } #if os(tvOS) diff --git a/Shared/Player/Video Details/PlayerQueueView.swift b/Shared/Player/Video Details/PlayerQueueView.swift index ae06d2f0..5948dd4b 100644 --- a/Shared/Player/Video Details/PlayerQueueView.swift +++ b/Shared/Player/Video Details/PlayerQueueView.swift @@ -86,9 +86,6 @@ struct PlayerQueueView: View { ForEach(player.queue) { item in PlayerQueueRow(item: item, fullScreen: $fullScreen) - .onAppear { - player.loadQueueVideoDetails(item) - } .contextMenu { removeButton(item) removeAllButton() diff --git a/tvOS/NowPlayingView.swift b/tvOS/NowPlayingView.swift index f65ccc39..a9106b35 100644 --- a/tvOS/NowPlayingView.swift +++ b/tvOS/NowPlayingView.swift @@ -66,9 +66,6 @@ struct NowPlayingView: View { } label: { VideoBanner(video: item.video) } - .onAppear { - player.loadQueueVideoDetails(item) - } .contextMenu { Button("Remove", role: .destructive) { player.remove(item)