mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
View options, video details screen
This commit is contained in:
@@ -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 {
|
||||
|
@@ -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
13
Model/SearchDate.swift
Normal 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
|
||||
}
|
||||
}
|
13
Model/SearchDuration.swift
Normal file
13
Model/SearchDuration.swift
Normal 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
18
Model/SearchQuery.swift
Normal 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
|
||||
}
|
||||
}
|
32
Model/SearchSortOrder.swift
Normal file
32
Model/SearchSortOrder.swift
Normal 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
12
Model/Thumbnail.swift
Normal 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!
|
||||
}
|
||||
}
|
@@ -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 []
|
||||
|
Reference in New Issue
Block a user