Add video proxy support with live toggle for Invidious/Piped instances

Adds a "Proxy videos" toggle in instance settings that routes video
streams through the instance instead of connecting directly to YouTube
CDN. Includes auto-detection of 403 blocks and live re-application of
proxy settings without requiring app restart or video reload.
This commit is contained in:
Arkadiusz Fal
2026-02-16 19:40:55 +01:00
parent 1fa6d7a4a5
commit c8b4ae5422
9 changed files with 305 additions and 4 deletions

View File

@@ -73,6 +73,9 @@ struct Instance: Identifiable, Codable, Hashable, Sendable {
/// Whether to allow invalid/self-signed SSL certificates.
var allowInvalidCertificates: Bool
/// Whether to route video streams through this instance instead of connecting directly to YouTube CDN.
var proxiesVideos: Bool
// MARK: - Initialization
init(
@@ -83,7 +86,8 @@ struct Instance: Identifiable, Codable, Hashable, Sendable {
isEnabled: Bool = true,
dateAdded: Date = Date(),
apiKey: String? = nil,
allowInvalidCertificates: Bool = false
allowInvalidCertificates: Bool = false,
proxiesVideos: Bool = false
) {
self.id = id
self.type = type
@@ -93,6 +97,20 @@ struct Instance: Identifiable, Codable, Hashable, Sendable {
self.dateAdded = dateAdded
self.apiKey = apiKey
self.allowInvalidCertificates = allowInvalidCertificates
self.proxiesVideos = proxiesVideos
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
type = try container.decode(InstanceType.self, forKey: .type)
url = try container.decode(URL.self, forKey: .url)
name = try container.decodeIfPresent(String.self, forKey: .name)
isEnabled = try container.decode(Bool.self, forKey: .isEnabled)
dateAdded = try container.decode(Date.self, forKey: .dateAdded)
apiKey = try container.decodeIfPresent(String.self, forKey: .apiKey)
allowInvalidCertificates = try container.decode(Bool.self, forKey: .allowInvalidCertificates)
proxiesVideos = try container.decodeIfPresent(Bool.self, forKey: .proxiesVideos) ?? false
}
// MARK: - Computed Properties
@@ -148,6 +166,11 @@ extension Instance {
var supportsPopular: Bool {
type == .invidious || type == .yatteeServer
}
/// Whether this instance supports proxying video streams through itself.
var supportsVideoProxying: Bool {
type == .invidious || type == .piped
}
}
// MARK: - Instance Validation

View File

@@ -189,6 +189,32 @@ struct StreamResolution: Codable, Hashable, Sendable, Comparable, CustomStringCo
}
}
// MARK: - URL Rewriting
extension Stream {
/// Creates a copy of this stream with a different URL.
/// Used for proxying streams through an instance.
func withURL(_ newURL: URL) -> Stream {
Stream(
url: newURL,
resolution: resolution,
format: format,
videoCodec: videoCodec,
audioCodec: audioCodec,
bitrate: bitrate,
fileSize: fileSize,
isAudioOnly: isAudioOnly,
isLive: isLive,
mimeType: mimeType,
audioLanguage: audioLanguage,
audioTrackName: audioTrackName,
isOriginalAudio: isOriginalAudio,
httpHeaders: httpHeaders,
fps: fps
)
}
}
// MARK: - Preview Data
extension Stream {