mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Use cache for loading player queue and history
This commit is contained in:
parent
33a131d2ca
commit
1c746bc8e0
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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,42 +277,40 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
playerAPI(video)?
|
||||||
self.logger.info("setting nothing loaded")
|
.loadDetails(item, completionHandler: { [weak self] newItem in
|
||||||
self.queueItemBeingLoaded = nil
|
guard let self else { return }
|
||||||
}
|
|
||||||
|
|
||||||
if let item = self.queueItemsToLoad.popLast() {
|
replaceQueueItem(newItem)
|
||||||
self.loadQueueVideoDetails(item)
|
|
||||||
}
|
self.logger.info("LOADED queue details: \(videoID)")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func videoLoadFailureHandler(_ error: RequestError, video: Video? = nil) {
|
private func videoLoadFailureHandler(_ error: RequestError, video: Video? = nil) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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 {
|
||||||
|
@ -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? {
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user