mirror of
https://github.com/yattee/yattee.git
synced 2025-04-24 23:56:27 +00:00
Add support for invidious companion
This commit is contained in:
parent
3a17cc4dee
commit
5239b36cfe
@ -10,14 +10,16 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
|
|||||||
let apiURLString: String
|
let apiURLString: String
|
||||||
var frontendURL: String?
|
var frontendURL: String?
|
||||||
var proxiesVideos: Bool
|
var proxiesVideos: Bool
|
||||||
|
var invidiousCompanion: Bool
|
||||||
|
|
||||||
init(app: VideosApp, id: String? = nil, name: String? = nil, apiURLString: String, frontendURL: String? = nil, proxiesVideos: Bool = false) {
|
init(app: VideosApp, id: String? = nil, name: String? = nil, apiURLString: String, frontendURL: String? = nil, proxiesVideos: Bool = false, invidiousCompanion: Bool = false) {
|
||||||
self.app = app
|
self.app = app
|
||||||
self.id = id ?? UUID().uuidString
|
self.id = id ?? UUID().uuidString
|
||||||
self.name = name ?? app.rawValue
|
self.name = name ?? app.rawValue
|
||||||
self.apiURLString = apiURLString
|
self.apiURLString = apiURLString
|
||||||
self.frontendURL = frontendURL
|
self.frontendURL = frontendURL
|
||||||
self.proxiesVideos = proxiesVideos
|
self.proxiesVideos = proxiesVideos
|
||||||
|
self.invidiousCompanion = invidiousCompanion
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiURL: URL! {
|
var apiURL: URL! {
|
||||||
|
@ -16,7 +16,8 @@ struct InstancesBridge: Defaults.Bridge {
|
|||||||
"name": value.name,
|
"name": value.name,
|
||||||
"apiURL": value.apiURLString,
|
"apiURL": value.apiURLString,
|
||||||
"frontendURL": value.frontendURL ?? "",
|
"frontendURL": value.frontendURL ?? "",
|
||||||
"proxiesVideos": value.proxiesVideos ? "true" : "false"
|
"proxiesVideos": value.proxiesVideos ? "true" : "false",
|
||||||
|
"invidiousCompanion": value.invidiousCompanion ? "true" : "false"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +34,8 @@ struct InstancesBridge: Defaults.Bridge {
|
|||||||
let name = object["name"] ?? ""
|
let name = object["name"] ?? ""
|
||||||
let frontendURL: String? = object["frontendURL"]!.isEmpty ? nil : object["frontendURL"]
|
let frontendURL: String? = object["frontendURL"]!.isEmpty ? nil : object["frontendURL"]
|
||||||
let proxiesVideos = object["proxiesVideos"] == "true"
|
let proxiesVideos = object["proxiesVideos"] == "true"
|
||||||
|
let invidiousCompanion = object["invidiousCompanion"] == "true"
|
||||||
|
|
||||||
return Instance(app: app, id: id, name: name, apiURLString: apiURL, frontendURL: frontendURL, proxiesVideos: proxiesVideos)
|
return Instance(app: app, id: id, name: name, apiURLString: apiURL, frontendURL: frontendURL, proxiesVideos: proxiesVideos, invidiousCompanion: invidiousCompanion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,17 @@ final class InstancesModel: ObservableObject {
|
|||||||
Defaults[.instances][index] = instance
|
Defaults[.instances][index] = instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setInvidiousCompanion(_ instance: Instance, _ invidiousCompanion: Bool) {
|
||||||
|
guard let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance = Defaults[.instances][index]
|
||||||
|
instance.invidiousCompanion = invidiousCompanion
|
||||||
|
|
||||||
|
Defaults[.instances][index] = instance
|
||||||
|
}
|
||||||
|
|
||||||
func remove(_ instance: Instance) {
|
func remove(_ instance: Instance) {
|
||||||
let accounts = accounts(instance.id)
|
let accounts = accounts(instance.id)
|
||||||
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
|
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
|
||||||
|
@ -655,21 +655,29 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
if json["liveNow"].boolValue {
|
if json["liveNow"].boolValue {
|
||||||
return hls
|
return hls
|
||||||
}
|
}
|
||||||
|
let videoId = json["videoId"].stringValue
|
||||||
|
|
||||||
return extractFormatStreams(from: json["formatStreams"].arrayValue) +
|
return extractFormatStreams(from: json["formatStreams"].arrayValue, videoId: videoId) +
|
||||||
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue) +
|
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue, videoId: videoId) +
|
||||||
hls
|
hls
|
||||||
}
|
}
|
||||||
|
|
||||||
private func extractFormatStreams(from streams: [JSON]) -> [Stream] {
|
private func extractFormatStreams(from streams: [JSON], videoId: String?) -> [Stream] {
|
||||||
streams.compactMap { stream in
|
streams.compactMap { stream in
|
||||||
guard let streamURL = stream["url"].url else {
|
guard let streamURL = stream["url"].url else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
let finalURL: URL
|
||||||
|
if let videoId, let itag = stream["itag"].string, account.instance.invidiousCompanion {
|
||||||
|
let companionURLString = "\(account.instance.apiURLString)/latest_version?id=\(videoId)&itag=\(itag)"
|
||||||
|
finalURL = URL(string: companionURLString) ?? streamURL
|
||||||
|
} else {
|
||||||
|
finalURL = streamURL
|
||||||
|
}
|
||||||
|
|
||||||
return SingleAssetStream(
|
return SingleAssetStream(
|
||||||
instance: account.instance,
|
instance: account.instance,
|
||||||
avAsset: AVURLAsset(url: streamURL),
|
avAsset: AVURLAsset(url: finalURL),
|
||||||
resolution: Stream.Resolution.from(resolution: stream["resolution"].string ?? ""),
|
resolution: Stream.Resolution.from(resolution: stream["resolution"].string ?? ""),
|
||||||
kind: .stream,
|
kind: .stream,
|
||||||
encoding: stream["encoding"].string ?? ""
|
encoding: stream["encoding"].string ?? ""
|
||||||
@ -677,7 +685,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func extractAdaptiveFormats(from streams: [JSON]) -> [Stream] {
|
private func extractAdaptiveFormats(from streams: [JSON], videoId: String?) -> [Stream] {
|
||||||
let audioStreams = streams
|
let audioStreams = streams
|
||||||
.filter { $0["type"].stringValue.starts(with: "audio/mp4") }
|
.filter { $0["type"].stringValue.starts(with: "audio/mp4") }
|
||||||
.sorted {
|
.sorted {
|
||||||
@ -692,15 +700,29 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
|
|
||||||
return videoStreams.compactMap { videoStream in
|
return videoStreams.compactMap { videoStream in
|
||||||
guard let audioAssetURL = audioStream["url"].url,
|
guard let audioAssetURL = audioStream["url"].url,
|
||||||
let videoAssetURL = videoStream["url"].url
|
let videoAssetURL = videoStream["url"].url,
|
||||||
|
let audioItag = audioStream["itag"].string,
|
||||||
|
let videoItag = videoStream["itag"].string
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
let finalAudioURL: URL
|
||||||
|
let finalVideoURL: URL
|
||||||
|
|
||||||
|
if let videoId, account.instance.invidiousCompanion {
|
||||||
|
let audioCompanionURLString = "\(account.instance.apiURLString)/latest_version?id=\(videoId)&itag=\(audioItag)"
|
||||||
|
let videoCompanionURLString = "\(account.instance.apiURLString)/latest_version?id=\(videoId)&itag=\(videoItag)"
|
||||||
|
finalAudioURL = URL(string: audioCompanionURLString) ?? audioAssetURL
|
||||||
|
finalVideoURL = URL(string: videoCompanionURLString) ?? videoAssetURL
|
||||||
|
} else {
|
||||||
|
finalAudioURL = audioAssetURL
|
||||||
|
finalVideoURL = videoAssetURL
|
||||||
|
}
|
||||||
|
|
||||||
return Stream(
|
return Stream(
|
||||||
instance: account.instance,
|
instance: account.instance,
|
||||||
audioAsset: AVURLAsset(url: audioAssetURL),
|
audioAsset: AVURLAsset(url: finalAudioURL),
|
||||||
videoAsset: AVURLAsset(url: videoAssetURL),
|
videoAsset: AVURLAsset(url: finalVideoURL),
|
||||||
resolution: Stream.Resolution.from(resolution: videoStream["resolution"].stringValue),
|
resolution: Stream.Resolution.from(resolution: videoStream["resolution"].stringValue),
|
||||||
kind: .adaptive,
|
kind: .adaptive,
|
||||||
encoding: videoStream["encoding"].string,
|
encoding: videoStream["encoding"].string,
|
||||||
@ -711,6 +733,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func extractHLSStreams(from content: JSON) -> [Stream] {
|
private func extractHLSStreams(from content: JSON) -> [Stream] {
|
||||||
if let hlsURL = content.dictionaryValue["hlsUrl"]?.url {
|
if let hlsURL = content.dictionaryValue["hlsUrl"]?.url {
|
||||||
return [Stream(instance: account.instance, hlsURL: hlsURL)]
|
return [Stream(instance: account.instance, hlsURL: hlsURL)]
|
||||||
|
@ -8,6 +8,7 @@ struct InstanceSettings: View {
|
|||||||
|
|
||||||
@State private var frontendURL = ""
|
@State private var frontendURL = ""
|
||||||
@State private var proxiesVideos = false
|
@State private var proxiesVideos = false
|
||||||
|
@State private var invidiousCompanion = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
@ -87,6 +88,16 @@ struct InstanceSettings: View {
|
|||||||
InstancesModel.shared.setProxiesVideos(instance, newValue)
|
InstancesModel.shared.setProxiesVideos(instance, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if instance.app == .invidious {
|
||||||
|
invidiousCompanionToggle
|
||||||
|
.onAppear {
|
||||||
|
invidiousCompanion = instance.invidiousCompanion
|
||||||
|
}
|
||||||
|
.onChange(of: invidiousCompanion) { newValue in
|
||||||
|
InstancesModel.shared.setInvidiousCompanion(instance, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.frame(maxWidth: 1000)
|
.frame(maxWidth: 1000)
|
||||||
@ -101,6 +112,10 @@ struct InstanceSettings: View {
|
|||||||
Toggle("Proxy videos", isOn: $proxiesVideos)
|
Toggle("Proxy videos", isOn: $proxiesVideos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var invidiousCompanionToggle: some View {
|
||||||
|
Toggle("Invidious companion", isOn: $invidiousCompanion)
|
||||||
|
}
|
||||||
|
|
||||||
private func removeAccount(_ account: Account) {
|
private func removeAccount(_ account: Account) {
|
||||||
AccountsModel.remove(account)
|
AccountsModel.remove(account)
|
||||||
accountsChanged.toggle()
|
accountsChanged.toggle()
|
||||||
|
@ -11,6 +11,7 @@ struct InstancesSettings: View {
|
|||||||
|
|
||||||
@State private var frontendURL = ""
|
@State private var frontendURL = ""
|
||||||
@State private var proxiesVideos = false
|
@State private var proxiesVideos = false
|
||||||
|
@State private var invidiousCompanion = false
|
||||||
|
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
@ObservedObject private var accounts = AccountsModel.shared
|
@ObservedObject private var accounts = AccountsModel.shared
|
||||||
@ -105,6 +106,16 @@ struct InstancesSettings: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selectedInstance != nil, selectedInstance.app == .invidious {
|
||||||
|
invidiousCompanionToggle
|
||||||
|
.onAppear {
|
||||||
|
invidiousCompanion = selectedInstance.invidiousCompanion
|
||||||
|
}
|
||||||
|
.onChange(of: invidiousCompanion) { newValue in
|
||||||
|
InstancesModel.shared.setInvidiousCompanion(selectedInstance, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if selectedInstance != nil, !selectedInstance.app.supportsAccounts {
|
if selectedInstance != nil, !selectedInstance.app.supportsAccounts {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Accounts are not supported for the application of this instance")
|
Text("Accounts are not supported for the application of this instance")
|
||||||
@ -191,6 +202,10 @@ struct InstancesSettings: View {
|
|||||||
private var proxiesVideosToggle: some View {
|
private var proxiesVideosToggle: some View {
|
||||||
Toggle("Proxy videos", isOn: $proxiesVideos)
|
Toggle("Proxy videos", isOn: $proxiesVideos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var invidiousCompanionToggle: some View {
|
||||||
|
Toggle("Invidious companion", isOn: $invidiousCompanion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InstancesSettingsView_Previews: PreviewProvider {
|
struct InstancesSettingsView_Previews: PreviewProvider {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user