Use cache for loading player queue and history

This commit is contained in:
Arkadiusz Fal 2022-12-13 00:38:26 +01:00
parent 33a131d2ca
commit 1c746bc8e0
11 changed files with 66 additions and 53 deletions

View File

@ -97,6 +97,8 @@ extension VideosAPI {
return return
} }
VideosCacheModel.shared.storeVideo(video)
var newItem = item var newItem = item
newItem.id = UUID() newItem.id = UUID()
newItem.video = video newItem.video = video

View File

@ -39,15 +39,6 @@ extension PlayerModel {
} }
.onCompletion { _ in .onCompletion { _ in
self.logger.info("LOADED history details: \(watch.videoID)") 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 watch: Watch!
let duration = self.playerTime.duration.seconds
if results?.isEmpty ?? true { if results?.isEmpty ?? true {
if seconds < 1 { if seconds < 3, duration > 3 { return }
return
}
watch = Watch(context: self.backgroundContext) watch = Watch(context: self.backgroundContext)
watch.videoID = id watch.videoID = id
watch.appName = currentVideo.app.rawValue watch.appName = currentVideo.app.rawValue
@ -82,7 +74,6 @@ extension PlayerModel {
watch = results?.first watch = results?.first
} }
let duration = self.playerTime.duration.seconds
if duration.isFinite, duration > 0 { if duration.isFinite, duration > 0 {
watch.videoDuration = duration watch.videoDuration = duration
} }
@ -98,6 +89,8 @@ extension PlayerModel {
watch.watchedAt = Date() watch.watchedAt = Date()
try? self.backgroundContext.save() try? self.backgroundContext.save()
FeedModel.shared.calculateUnwatchedFeed()
} }
} }

View File

@ -96,11 +96,6 @@ final class PlayerModel: ObservableObject {
@Published var currentItem: PlayerQueueItem! { didSet { handleCurrentItemChange() } } @Published var currentItem: PlayerQueueItem! { didSet { handleCurrentItemChange() } }
@Published var videoBeingOpened: Video? { didSet { seek.reset() } } @Published var videoBeingOpened: Video? { didSet { seek.reset() } }
@Published var historyVideos = [Video]() @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 preservedTime: CMTime?
@Published var sponsorBlock = SponsorBlockAPI() @Published var sponsorBlock = SponsorBlockAPI()

View File

@ -165,7 +165,8 @@ extension PlayerModel {
currentItem.playbackTime = time currentItem.playbackTime = time
let playTime = currentItem.shouldRestartPlaying ? CMTime.zero : 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) self.playItem(newItem, at: playTime)
} }
} }
@ -276,41 +277,39 @@ extension PlayerModel {
restoredQueue.append(contentsOf: Defaults[.queue]) restoredQueue.append(contentsOf: Defaults[.queue])
queue = restoredQueue.compactMap { $0 } queue = restoredQueue.compactMap { $0 }
queue.forEach { loadQueueVideoDetails($0) }
} }
func loadQueueVideoDetails(_ item: PlayerQueueItem) { 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 let videoID = item.video?.videoID ?? item.videoID
if queueItemBeingLoaded == nil { let video = item.video ?? Video(app: item.app ?? .local, instanceURL: item.instanceURL, videoID: videoID)
logger.info("loading queue details: \(videoID)")
queueItemBeingLoaded = item
} else {
logger.info("POSTPONING details load: \(videoID)")
queueItemsToLoad.append(item)
return
}
playerAPI(video)?.loadDetails(item, completionHandler: { [weak self] newItem in let replaceQueueItem: (PlayerQueueItem) -> Void = { newItem in
guard let self else { return } self.queue.filter { $0.videoID == videoID }.forEach { item in
self.queue.filter { $0.videoID == item.videoID }.forEach { item in
if let index = self.queue.firstIndex(of: item) { if let index = self.queue.firstIndex(of: item) {
self.queue[index] = newItem self.queue[index] = newItem
} }
} }
}
if let video = VideosCacheModel.shared.retrieveVideo(video.cacheKey) {
var item = item
item.id = UUID()
item.video = video
replaceQueueItem(item)
return
}
playerAPI(video)?
.loadDetails(item, completionHandler: { [weak self] newItem in
guard let self else { return }
replaceQueueItem(newItem)
self.logger.info("LOADED queue details: \(videoID)") self.logger.info("LOADED queue details: \(videoID)")
if self.queueItemBeingLoaded == item {
self.logger.info("setting nothing loaded")
self.queueItemBeingLoaded = nil
}
if let item = self.queueItemsToLoad.popLast() {
self.loadQueueVideoDetails(item)
}
}) })
} }

View File

@ -8,6 +8,8 @@ struct PlayerQueueItem: Hashable, Identifiable, Defaults.Serializable {
var id = UUID() var id = UUID()
var video: Video! var video: Video!
var videoID: Video.ID var videoID: Video.ID
var app: VideosApp?
var instanceURL: URL?
var playbackTime: CMTime? var playbackTime: CMTime?
var videoDuration: TimeInterval? var videoDuration: TimeInterval?
@ -15,14 +17,25 @@ struct PlayerQueueItem: Hashable, Identifiable, Defaults.Serializable {
.init( .init(
video, video,
videoID: watch.videoID, videoID: watch.videoID,
app: watch.app,
instanceURL: watch.instanceURL,
playbackTime: CMTime.secondsInDefaultTimescale(watch.stoppedAt), playbackTime: CMTime.secondsInDefaultTimescale(watch.stoppedAt),
videoDuration: watch.videoDuration 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.video = video
self.videoID = videoID ?? video!.videoID self.videoID = videoID ?? video!.videoID
self.app = app
self.instanceURL = instanceURL
self.playbackTime = playbackTime self.playbackTime = playbackTime
self.videoDuration = videoDuration self.videoDuration = videoDuration
} }

View File

@ -33,6 +33,8 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
return [ return [
"localURL": localURL, "localURL": localURL,
"videoID": value.videoID, "videoID": value.videoID,
"app": (value.app ?? value.video.app)?.rawValue ?? "",
"instanceURL": (value.instanceURL ?? value.video.instanceURL)?.absoluteString ?? "",
"playbackTime": playbackTime, "playbackTime": playbackTime,
"videoDuration": videoDuration "videoDuration": videoDuration
] ]
@ -41,6 +43,8 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
func deserialize(_ object: Serializable?) -> Value? { func deserialize(_ object: Serializable?) -> Value? {
guard let object else { return nil } guard let object else { return nil }
var app: VideosApp?
var instanceURL: URL?
var playbackTime: CMTime? var playbackTime: CMTime?
var videoDuration: TimeInterval? var videoDuration: TimeInterval?
@ -57,6 +61,16 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
videoDuration = TimeInterval(duration) 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"], if let localUrlString = object["localURL"],
!localUrlString.isEmpty, !localUrlString.isEmpty,
let localURL = URL(string: localUrlString) let localURL = URL(string: localUrlString)
@ -71,7 +85,10 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
guard let videoID = object["videoID"] else { return nil } guard let videoID = object["videoID"] else { return nil }
return PlayerQueueItem( return PlayerQueueItem(
.init(app: app ?? .local, instanceURL: instanceURL, videoID: videoID),
videoID: videoID, videoID: videoID,
app: app,
instanceURL: instanceURL,
playbackTime: playbackTime, playbackTime: playbackTime,
videoDuration: videoDuration videoDuration: videoDuration
) )

View File

@ -197,7 +197,7 @@ struct Video: Identifiable, Equatable, Hashable {
} }
var isLocal: Bool { var isLocal: Bool {
!VideoID.isValid(videoID) !VideoID.isValid(videoID) && videoID != Self.fixtureID
} }
var displayTitle: String { var displayTitle: String {

View File

@ -71,7 +71,10 @@ extension Watch {
} }
var finished: Bool { var finished: Bool {
progress >= Double(watchedThreshold) guard videoDuration.isFinite, !videoDuration.isZero else {
return true
}
return progress >= Double(watchedThreshold)
} }
var watchedAtString: String? { var watchedAtString: String? {

View File

@ -1,8 +1,6 @@
import SwiftUI import SwiftUI
struct HistoryView: View { struct HistoryView: View {
static let detailsPreloadLimit = 50
var limit = 10 var limit = 10
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
@ -33,7 +31,6 @@ struct HistoryView: View {
} }
.onAppear { .onAppear {
visibleWatches visibleWatches
.prefix(Self.detailsPreloadLimit)
.forEach(player.loadHistoryVideoDetails) .forEach(player.loadHistoryVideoDetails)
} }
#if os(tvOS) #if os(tvOS)

View File

@ -86,9 +86,6 @@ struct PlayerQueueView: View {
ForEach(player.queue) { item in ForEach(player.queue) { item in
PlayerQueueRow(item: item, fullScreen: $fullScreen) PlayerQueueRow(item: item, fullScreen: $fullScreen)
.onAppear {
player.loadQueueVideoDetails(item)
}
.contextMenu { .contextMenu {
removeButton(item) removeButton(item)
removeAllButton() removeAllButton()

View File

@ -66,9 +66,6 @@ struct NowPlayingView: View {
} label: { } label: {
VideoBanner(video: item.video) VideoBanner(video: item.video)
} }
.onAppear {
player.loadQueueVideoDetails(item)
}
.contextMenu { .contextMenu {
Button("Remove", role: .destructive) { Button("Remove", role: .destructive) {
player.remove(item) player.remove(item)