import CoreData import CoreMedia import Defaults import Foundation import Siesta import SwiftyJSON extension PlayerModel { func historyVideo(_ id: String) -> Video? { historyVideos.first { $0.videoID == id } } func loadHistoryVideoDetails(_ watch: Watch, onCompletion: @escaping () -> Void = {}) { guard historyVideo(watch.videoID).isNil else { onCompletion() return } if !Video.VideoID.isValid(watch.videoID), let url = URL(string: watch.videoID) { historyVideos.append(.local(url)) onCompletion() return } if let video = VideosCacheModel.shared.retrieveVideo(watch.video.cacheKey) { historyVideos.append(video) onCompletion() return } guard let api = playerAPI(watch.video) else { return } api.video(watch.videoID) .load() .onSuccess { [weak self] response in guard let self else { return } if let video: Video = response.typedContent() { VideosCacheModel.shared.storeVideo(video) self.historyVideos.append(video) onCompletion() } } .onCompletion { _ in self.logger.info("LOADED history details: \(watch.videoID)") } } func updateWatch(finished: Bool = false, time: CMTime? = nil) { guard let currentVideo, saveHistory, isPlaying else { return } let id = currentVideo.videoID let time = time ?? backend.currentTime let seconds = time?.seconds ?? 0 if seconds < 3 { return } let watchFetchRequest = Watch.fetchRequest() watchFetchRequest.predicate = NSPredicate(format: "videoID = %@", id as String) let results = try? backgroundContext.fetch(watchFetchRequest) backgroundContext.perform { [weak self] in guard let self, finished || time != nil || self.backend.isPlaying else { return } let watch: Watch! let duration = self.activeBackend == .mpv ? self.playerTime.duration.seconds : self.avPlayerBackend.playerItemDuration?.seconds ?? 0 if results?.isEmpty ?? true { watch = Watch(context: self.backgroundContext) watch.videoID = id watch.appName = currentVideo.app.rawValue watch.instanceURL = currentVideo.instanceURL } else { watch = results?.first } if duration.isFinite, duration > 0 { watch.videoDuration = duration } if watch.finished { if !finished, self.resetWatchedStatusOnPlaying, seconds.isFinite, seconds > 0 { watch.stoppedAt = seconds } } else if seconds.isFinite, seconds > 0 { watch.stoppedAt = seconds } watch.watchedAt = Date() try? self.backgroundContext.save() } } func removeHistory() { removeAllWatches() BookmarksCacheModel.shared.clear() } func removeWatch(_ watch: Watch) { context.perform { [weak self] in guard let self else { return } self.context.delete(watch) try? self.context.save() FeedModel.shared.calculateUnwatchedFeed() WatchModel.shared.watchesChanged() } } func removeAllWatches() { let watchesFetchRequest = NSFetchRequest(entityName: "Watch") let deleteRequest = NSBatchDeleteRequest(fetchRequest: watchesFetchRequest) do { try context.executeAndMergeChanges(deleteRequest) try context.save() } catch let error as NSError { logger.info(.init(stringLiteral: error.localizedDescription)) } } }