Videos cache model

This commit is contained in:
Arkadiusz Fal
2022-12-10 01:23:13 +01:00
parent 0c960c2461
commit 64146b26c2
12 changed files with 204 additions and 9 deletions

View File

@@ -41,10 +41,8 @@ final class AccountsModel: ObservableObject {
return piped
case .invidious:
return invidious
case .peerTube:
return peerTube
default:
return nil
return peerTube
}
}

View File

@@ -1,10 +1,12 @@
import Foundation
import SwiftyJSON
import Logging
struct CacheModel {
static var shared = CacheModel()
static let bookmarksGroup = "group.stream.yattee.app.bookmarks"
let logger = Logger(label: "stream.yattee.cache")
static let bookmarksGroup = "group.stream.yattee.app.bookmarks"
let bookmarksDefaults = UserDefaults(suiteName: Self.bookmarksGroup)
func removeAll() {

View File

@@ -0,0 +1,37 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct VideosCacheModel {
static let shared = VideosCacheModel()
let logger = Logger(label: "stream.yattee.cache.videos")
static let jsonToDataTransformer: (JSON) -> Data = { try! $0.rawData() }
static let jsonFromDataTransformer: (Data) -> JSON = { try! JSON(data: $0) }
static let jsonTransformer = Transformer(toData: jsonToDataTransformer, fromData: jsonFromDataTransformer)
static let videosStorageDiskConfig = DiskConfig(name: "videos")
static let vidoesStorageMemoryConfig = MemoryConfig()
let videosStorage = try! Storage<String, JSON>(
diskConfig: Self.videosStorageDiskConfig,
memoryConfig: Self.vidoesStorageMemoryConfig,
transformer: Self.jsonTransformer
)
func storeVideo(_ video: Video) {
logger.info("caching \(video.cacheKey)")
try? videosStorage.setObject(video.json, forKey: video.cacheKey)
}
func retrieveVideo(_ cacheKey: String) -> Video? {
logger.info("retrieving cache for \(cacheKey)")
if let json = try? videosStorage.object(forKey: cacheKey) {
return Video.from(json)
}
return nil
}
}

View File

@@ -109,4 +109,18 @@ struct Channel: Identifiable, Hashable {
guard contentType != .videos, contentType != .playlists else { return true }
return tabs.contains { $0.contentType == contentType }
}
var json: JSON {
[
"id": id,
"name": name
]
}
static func from(_ json: JSON) -> Self {
.init(
id: json["id"].stringValue,
name: json["name"].stringValue
)
}
}

View File

@@ -20,6 +20,11 @@ extension PlayerModel {
return
}
if let video = VideosCacheModel.shared.retrieveVideo(watch.video.cacheKey) {
historyVideos.append(video)
return
}
guard let api = playerAPI(watch.video) else { return }
api.video(watch.videoID)
@@ -28,6 +33,7 @@ extension PlayerModel {
guard let self else { return }
if let video: Video = response.typedContent() {
VideosCacheModel.shared.storeVideo(video)
self.historyVideos.append(video)
}
}

View File

@@ -87,7 +87,7 @@ extension PlayerModel {
}
func playerAPI(_ video: Video) -> VideosAPI! {
guard let url = video.instanceURL else { return nil }
guard let url = video.instanceURL else { return accounts.api }
switch video.app {
case .local:
return nil

View File

@@ -34,6 +34,7 @@ extension PlayerModel {
.load()
.onSuccess { response in
if let video: Video = response.typedContent() {
VideosCacheModel.shared.storeVideo(video)
guard video.videoID == self.currentVideo?.videoID else {
self.logger.info("ignoring loaded streams from \(instance.description) as current video has changed")
return

View File

@@ -36,4 +36,18 @@ struct Thumbnail {
self.url = url
self.quality = quality
}
var json: JSON {
[
"url": url.absoluteString,
"quality": quality.rawValue
]
}
static func from(_ json: JSON) -> Self {
.init(
url: URL(string: json["url"].stringValue)!,
quality: .init(rawValue: json["quality"].stringValue) ?? .default
)
}
}

View File

@@ -119,6 +119,71 @@ struct Video: Identifiable, Equatable, Hashable {
)
}
var cacheKey: String {
switch app {
case .local:
return videoID
case .invidious:
return "youtube-\(videoID)"
case .piped:
return "youtube-\(videoID)"
case .peerTube:
return "peertube-\(instanceURL?.absoluteString ?? "unknown-instance")-\(videoID)"
}
}
var json: JSON {
let dateFormatter = ISO8601DateFormatter()
let publishedAt = self.publishedAt == nil ? "" : dateFormatter.string(from: self.publishedAt!)
return [
"instanceID": instanceID ?? "",
"app": app.rawValue,
"instanceURL": instanceURL?.absoluteString ?? "",
"id": id,
"videoID": videoID,
"videoURL": videoURL?.absoluteString ?? "",
"title": title,
"author": author,
"length": length,
"published": published,
"views": views,
"description": description ?? "",
"genre": genre ?? "",
"channel": channel.json.object,
"thumbnails": thumbnails.compactMap { $0.json.object },
"indexID": indexID ?? "",
"live": live,
"upcoming": upcoming,
"publishedAt": publishedAt
]
}
static func from(_ json: JSON) -> Self {
let dateFormatter = ISO8601DateFormatter()
return Video(
instanceID: json["instanceID"].stringValue,
app: .init(rawValue: json["app"].stringValue) ?? AccountsModel.shared.current.app ?? .local,
instanceURL: URL(string: json["instanceURL"].stringValue) ?? AccountsModel.shared.current.instance.apiURL,
id: json["id"].stringValue,
videoID: json["videoID"].stringValue,
videoURL: json["videoURL"].url,
title: json["title"].stringValue,
author: json["author"].stringValue,
length: json["length"].doubleValue,
published: json["published"].stringValue,
views: json["views"].intValue,
description: json["description"].string,
genre: json["genre"].string,
channel: Channel.from(json["channel"]),
thumbnails: json["thumbnails"].arrayValue.compactMap { Thumbnail.from($0) },
indexID: json["indexID"].stringValue,
live: json["live"].boolValue,
upcoming: json["upcoming"].boolValue,
publishedAt: dateFormatter.date(from: json["publishedAt"].stringValue)
)
}
var instance: Instance! {
if let instance = InstancesModel.shared.find(instanceID) {
return instance

View File

@@ -91,12 +91,12 @@ extension Watch {
var video: Video {
let url = URL(string: videoID)
if app == nil || !Video.VideoID.isValid(videoID) {
if !Video.VideoID.isValid(videoID) {
if let url {
return .local(url)
}
}
return Video(app: app, instanceURL: instanceURL, videoID: videoID)
return Video(app: app ?? AccountsModel.shared.current.app ?? .local, instanceURL: instanceURL, videoID: videoID)
}
}