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)
}
}

View File

@ -346,6 +346,7 @@
374C0540272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053E272472C0009BDDBE /* PlayerSponsorBlock.swift */; };
374C0541272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053E272472C0009BDDBE /* PlayerSponsorBlock.swift */; };
374C0543272496E4009BDDBE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C0542272496E4009BDDBE /* AppDelegate.swift */; };
374D11E72943C56300CB4350 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 374D11E62943C56300CB4350 /* Cache */; };
374DE88028BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */; };
374DE88128BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */; };
374DE88328BB8A280062BBF2 /* PlayerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374DE88228BB8A280062BBF2 /* PlayerOrientation.swift */; };
@ -544,6 +545,11 @@
377E17142928265900894889 /* ListRowSeparator+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377E17132928265900894889 /* ListRowSeparator+Backport.swift */; };
377E17152928265900894889 /* ListRowSeparator+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377E17132928265900894889 /* ListRowSeparator+Backport.swift */; };
377E17162928265900894889 /* ListRowSeparator+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377E17132928265900894889 /* ListRowSeparator+Backport.swift */; };
377F9F74294403770043F856 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 377F9F73294403770043F856 /* Cache */; };
377F9F76294403880043F856 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 377F9F75294403880043F856 /* Cache */; };
377F9F7B294403F20043F856 /* VideosCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377F9F7A294403F20043F856 /* VideosCacheModel.swift */; };
377F9F7C294403F20043F856 /* VideosCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377F9F7A294403F20043F856 /* VideosCacheModel.swift */; };
377F9F7D294403F20043F856 /* VideosCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377F9F7A294403F20043F856 /* VideosCacheModel.swift */; };
377FC7D5267A080300A6BBAF /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7D4267A080300A6BBAF /* SwiftyJSON */; };
377FC7DB267A080300A6BBAF /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7DA267A080300A6BBAF /* Logging */; };
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27D26737323007FC770 /* PopularView.swift */; };
@ -1222,6 +1228,7 @@
377ABC43286E4B74009C986F /* ManifestedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManifestedInstance.swift; sourceTree = "<group>"; };
377ABC47286E5887009C986F /* Sequence+Unique.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Unique.swift"; sourceTree = "<group>"; };
377E17132928265900894889 /* ListRowSeparator+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListRowSeparator+Backport.swift"; sourceTree = "<group>"; };
377F9F7A294403F20043F856 /* VideosCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCacheModel.swift; sourceTree = "<group>"; };
377FF88A291A60310028EB0B /* OpenVideosModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenVideosModel.swift; sourceTree = "<group>"; };
377FF88E291A99580028EB0B /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
37824309291E58D6005DEC1C /* Open in Yattee.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Open in Yattee.entitlements"; sourceTree = "<group>"; };
@ -1426,6 +1433,7 @@
3736A21A286BB72300C9E5EE /* libmpv.xcframework in Frameworks */,
3765917C27237D21009F956E /* PINCache in Frameworks */,
37BD07B72698AB2E003EBB87 /* Defaults in Frameworks */,
377F9F74294403770043F856 /* Cache in Frameworks */,
3736A20C286BB72300C9E5EE /* libavutil.xcframework in Frameworks */,
37FB285627220D9000A57617 /* SDWebImagePINPlugin in Frameworks */,
3736A212286BB72300C9E5EE /* libswresample.xcframework in Frameworks */,
@ -1465,6 +1473,7 @@
370F4FC927CC16CB001B35DC /* libssl.3.dylib in Frameworks */,
3703206827D2BB45007A0CB8 /* Defaults in Frameworks */,
3703206A27D2BB49007A0CB8 /* Alamofire in Frameworks */,
374D11E72943C56300CB4350 /* Cache in Frameworks */,
370F4FD427CC16CB001B35DC /* libfreetype.6.dylib in Frameworks */,
3797104B28D3D18800D5F53C /* SDWebImageSwiftUI in Frameworks */,
370F4FE227CC16CB001B35DC /* libXdmcp.6.dylib in Frameworks */,
@ -1526,6 +1535,7 @@
37FB2849272207F000A57617 /* SDWebImageWebPCoder in Frameworks */,
3736A20D286BB72300C9E5EE /* libavutil.xcframework in Frameworks */,
3736A213286BB72300C9E5EE /* libswresample.xcframework in Frameworks */,
377F9F76294403880043F856 /* Cache in Frameworks */,
37FB285427220D8400A57617 /* SDWebImagePINPlugin in Frameworks */,
3732BFD028B83763009F3F4D /* KeychainAccess in Frameworks */,
3772003927E8EEB700CB2475 /* AVFoundation.framework in Frameworks */,
@ -1963,6 +1973,15 @@
path = Modifiers;
sourceTree = "<group>";
};
377F9F79294403DC0043F856 /* Cache */ = {
isa = PBXGroup;
children = (
37F5E8B9291BEF69006C15F5 /* CacheModel.swift */,
377F9F7A294403F20043F856 /* VideosCacheModel.swift */,
);
path = Cache;
sourceTree = "<group>";
};
377FC7D1267A080300A6BBAF /* Frameworks */ = {
isa = PBXGroup;
children = (
@ -2187,11 +2206,11 @@
children = (
3743B86627216A1E00261544 /* Accounts */,
3743B864272169E200261544 /* Applications */,
377F9F79294403DC0043F856 /* Cache */,
3743B86527216A0600261544 /* Player */,
3751BA8127E69131007B1A60 /* ReturnYouTubeDislike */,
37FB283F2721B20800A57617 /* Search */,
374C0539272436DA009BDDBE /* SponsorBlock */,
37F5E8B9291BEF69006C15F5 /* CacheModel.swift */,
3776ADD5287381240078EBC4 /* Captions.swift */,
37AAF28F26740715007FC770 /* Channel.swift */,
37C3A24427235DA70087A57A /* ChannelPlaylist.swift */,
@ -2405,6 +2424,7 @@
3799AC0828B03CED001376F9 /* ActiveLabel */,
375B8AB028B57F4200397B31 /* KeychainAccess */,
3797104828D3D10600D5F53C /* SDWebImageSwiftUI */,
377F9F73294403770043F856 /* Cache */,
);
productName = "Yattee (iOS)";
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
@ -2441,6 +2461,7 @@
372AA413286D06A10000B1DC /* Repeat */,
375B8AB628B583BD00397B31 /* KeychainAccess */,
3797104A28D3D18800D5F53C /* SDWebImageSwiftUI */,
374D11E62943C56300CB4350 /* Cache */,
);
productName = "Yattee (macOS)";
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
@ -2518,6 +2539,7 @@
37E80F42287B7AAF00561799 /* SwiftUIPager */,
3732BFCF28B83763009F3F4D /* KeychainAccess */,
3797104C28D3D19100D5F53C /* SDWebImageSwiftUI */,
377F9F75294403880043F856 /* Cache */,
);
productName = Yattee;
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
@ -2626,6 +2648,7 @@
3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */,
375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
3797104728D3D10600D5F53C /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */,
374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */,
);
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
projectDirPath = "";
@ -3089,6 +3112,7 @@
3784CDE227772EE40055BBF2 /* Watch.swift in Sources */,
37FB285E272225E800A57617 /* ContentItemView.swift in Sources */,
3797758B2689345500DD52A8 /* Store.swift in Sources */,
377F9F7B294403F20043F856 /* VideosCacheModel.swift in Sources */,
37B795902771DAE0001CF27B /* OpenURLHandler.swift in Sources */,
37732FF02703A26300F04329 /* AccountValidationStatus.swift in Sources */,
);
@ -3284,6 +3308,7 @@
37599F31272B42810087F250 /* FavoriteItem.swift in Sources */,
3730F75A2733481E00F385FC /* RelatedView.swift in Sources */,
37E04C0F275940FB00172673 /* VerticalScrollingFix.swift in Sources */,
377F9F7C294403F20043F856 /* VideosCacheModel.swift in Sources */,
374924E4292141320017D862 /* InspectorView.swift in Sources */,
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */,
37D526DF2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
@ -3515,6 +3540,7 @@
3744A96228B99ADD005DE0A7 /* PlayerControlsLayout.swift in Sources */,
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
3748186826A7627F0084E870 /* Video+Fixtures.swift in Sources */,
377F9F7D294403F20043F856 /* VideosCacheModel.swift in Sources */,
37C3A253272366440087A57A /* ChannelPlaylistView.swift in Sources */,
378AE945274EF00A006A4EE1 /* Color+Background.swift in Sources */,
3743CA54270F284F00E4D32B /* View+Borders.swift in Sources */,
@ -4458,6 +4484,14 @@
minimumVersion = 0.6.0;
};
};
374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/hyperoslo/Cache.git";
requirement = {
branch = master;
kind = branch;
};
};
375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git";
@ -4638,6 +4672,11 @@
package = 375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
productName = KeychainAccess;
};
374D11E62943C56300CB4350 /* Cache */ = {
isa = XCSwiftPackageProductDependency;
package = 374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */;
productName = Cache;
};
375B8AB028B57F4200397B31 /* KeychainAccess */ = {
isa = XCSwiftPackageProductDependency;
package = 375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
@ -4683,6 +4722,16 @@
package = 37BADCA32699FB72009BE4FB /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
377F9F73294403770043F856 /* Cache */ = {
isa = XCSwiftPackageProductDependency;
package = 374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */;
productName = Cache;
};
377F9F75294403880043F856 /* Cache */ = {
isa = XCSwiftPackageProductDependency;
package = 374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */;
productName = Cache;
};
377FC7D4267A080300A6BBAF /* SwiftyJSON */ = {
isa = XCSwiftPackageProductDependency;
package = 37D4B19B2671817900C925CA /* XCRemoteSwiftPackageReference "SwiftyJSON" */;

View File

@ -18,6 +18,15 @@
"version" : "5.6.2"
}
},
{
"identity" : "cache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"branch" : "master",
"revision" : "eeaf771d8d2e8247fbd6da2e27c986d99803fb1f"
}
},
{
"identity" : "defaults",
"kind" : "remoteSourceControl",