View options, video details screen

This commit is contained in:
Arkadiusz Fal
2021-07-08 00:39:18 +02:00
parent 6d35394ffd
commit 4a733f5a30
27 changed files with 652 additions and 108 deletions

View File

@@ -1,14 +1,8 @@
import Defaults
import Foundation
import Siesta
import SwiftyJSON
extension TypedContentAccessors {
var json: JSON { typedContent(ifNone: JSON.null) }
}
let SwiftyJSONTransformer =
ResponseContentTransformer(transformErrors: true) { JSON($0.content as AnyObject) }
final class InvidiousAPI: Service {
static let shared = InvidiousAPI()
@@ -25,7 +19,10 @@ final class InvidiousAPI: Service {
}
init() {
SiestaLog.Category.enabled = .all
SiestaLog.Category.enabled = .common
let SwiftyJSONTransformer =
ResponseContentTransformer(transformErrors: true) { JSON($0.content as AnyObject) }
super.init(baseURL: "\(InvidiousAPI.instance)/api/v1")
@@ -106,9 +103,20 @@ final class InvidiousAPI: Service {
resource("/auth/playlists")
}
func search(_ query: String) -> Resource {
resource("/search")
.withParam("q", searchQuery(query))
func search(_ query: SearchQuery) -> Resource {
var resource = resource("/search")
.withParam("q", searchQuery(query.query))
.withParam("sort_by", query.sortBy.parameter)
if let date = query.date {
resource = resource.withParam("date", date.rawValue)
}
if let duration = query.duration {
resource = resource.withParam("duration", duration.rawValue)
}
return resource
}
private func searchQuery(_ query: String) -> String {

View File

@@ -36,7 +36,7 @@ final class PlayerState: ObservableObject {
makeMetadataItem(.commonIdentifierDescription, value: video.description)
]
if let thumbnailData = try? Data(contentsOf: video.thumbnailURL!),
if let thumbnailData = try? Data(contentsOf: video.thumbnailURL(quality: "high")!),
let image = UIImage(data: thumbnailData),
let pngData = image.pngData()
{

13
Model/SearchDate.swift Normal file
View File

@@ -0,0 +1,13 @@
import Defaults
enum SearchDate: String, CaseIterable, Identifiable, DefaultsSerializable {
case hour, today, week, month, year
var id: SearchDate.RawValue {
rawValue
}
var name: String {
rawValue.capitalized
}
}

View File

@@ -0,0 +1,13 @@
import Defaults
enum SearchDuration: String, CaseIterable, Identifiable, DefaultsSerializable {
case short, long
var id: SearchDuration.RawValue {
rawValue
}
var name: String {
rawValue.capitalized
}
}

18
Model/SearchQuery.swift Normal file
View File

@@ -0,0 +1,18 @@
import Foundation
final class SearchQuery: ObservableObject {
@Published var query: String
@Published var sortBy = SearchSortOrder.relevance
@Published var date: SearchDate? = SearchDate.month
@Published var duration: SearchDuration?
@Published var page = 1
init(query: String = "", page: Int = 1, sortBy: SearchSortOrder = .relevance, date: SearchDate? = nil, duration: SearchDuration? = nil) {
self.query = query
self.page = page
self.sortBy = sortBy
self.date = date
self.duration = duration
}
}

View File

@@ -0,0 +1,32 @@
import Defaults
import Foundation
enum SearchSortOrder: String, CaseIterable, Identifiable, DefaultsSerializable {
case relevance, rating, uploadDate, viewCount
var id: SearchSortOrder.RawValue {
rawValue
}
var name: String {
switch self {
case .uploadDate:
return "Upload Date"
case .viewCount:
return "View Count"
default:
return rawValue.capitalized
}
}
var parameter: String {
switch self {
case .uploadDate:
return "upload_date"
case .viewCount:
return "view_count"
default:
return rawValue
}
}
}

12
Model/Thumbnail.swift Normal file
View File

@@ -0,0 +1,12 @@
import Foundation
import SwiftyJSON
struct Thumbnail {
var url: URL
var quality: String
init(_ json: JSON) {
url = json["url"].url!
quality = json["quality"].string!
}
}

View File

@@ -6,7 +6,7 @@ import SwiftyJSON
struct Video: Identifiable {
let id: String
var title: String
var thumbnailURL: URL?
var thumbnails: [Thumbnail]
var author: String
var length: TimeInterval
var published: String
@@ -28,10 +28,10 @@ struct Video: Identifiable {
description = json["description"].stringValue
genre = json["genre"].stringValue
thumbnailURL = extractThumbnailURL(from: json)
thumbnails = Video.extractThumbnails(from: json)
streams = extractFormatStreams(from: json["formatStreams"].arrayValue)
streams.append(contentsOf: extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue))
streams = Video.extractFormatStreams(from: json["formatStreams"].arrayValue)
streams.append(contentsOf: Video.extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue))
}
var playTime: String? {
@@ -93,19 +93,20 @@ struct Video: Identifiable {
}
func defaultStreamForProfile(_ profile: Profile) -> Stream? {
streamWithResolution(profile.defaultStreamResolution.value)
streamWithResolution(profile.defaultStreamResolution.value) ?? streams.first
}
private func extractThumbnailURL(from details: JSON) -> URL? {
if details["videoThumbnails"].arrayValue.isEmpty {
return nil
func thumbnailURL(quality: String) -> URL? {
thumbnails.first { $0.quality == quality }?.url
}
private static func extractThumbnails(from details: JSON) -> [Thumbnail] {
details["videoThumbnails"].arrayValue.map { json in
Thumbnail(json)
}
let thumbnail = details["videoThumbnails"].arrayValue.first { $0["quality"].stringValue == "medium" }!
return thumbnail["url"].url!
}
private func extractFormatStreams(from streams: [JSON]) -> [Stream] {
private static func extractFormatStreams(from streams: [JSON]) -> [Stream] {
streams.map {
AudioVideoStream(
avAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset($0["url"].stringValue)!),
@@ -116,7 +117,7 @@ struct Video: Identifiable {
}
}
private func extractAdaptiveFormats(from streams: [JSON]) -> [Stream] {
private static func extractAdaptiveFormats(from streams: [JSON]) -> [Stream] {
let audioAssetURL = streams.first { $0["type"].stringValue.starts(with: "audio/mp4") }
guard audioAssetURL != nil else {
return []