Merge pull request #696 from stonerl/improved-conditional-proxying

improved conditional proxying
This commit is contained in:
Arkadiusz Fal 2024-06-13 18:59:42 +02:00 committed by GitHub
commit 1ed4c20c3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -56,82 +56,101 @@ extension PlayerModel {
} }
} }
func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) { func streamsWithInstance(instance: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
// Queue for stream processing // Queue for stream processing
let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue", qos: .userInitiated) let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue")
// Queue for accessing the processedStreams array // Queue for accessing the processedStreams array
let processedStreamsQueue = DispatchQueue(label: "stream.yattee.processedStreams.Queue") let processedStreamsQueue = DispatchQueue(label: "stream.yattee.processedStreams.Queue")
// DispatchGroup for managing multiple tasks // DispatchGroup for managing multiple tasks
let streamProcessingGroup = DispatchGroup() let streamProcessingGroup = DispatchGroup()
var processedStreams = [Stream]() var processedStreams = [Stream]()
let instance = instance
var hasForbiddenAsset = false
var hasAllowedAsset = false
for stream in streams { for stream in streams {
streamProcessingQueue.async(group: streamProcessingGroup) { streamProcessingQueue.async(group: streamProcessingGroup) {
let forbiddenAssetTestGroup = DispatchGroup() let forbiddenAssetTestGroup = DispatchGroup()
var hasForbiddenAsset = false if !hasAllowedAsset, !hasForbiddenAsset, !instance.proxiesVideos, stream.format != Stream.Format.unknown {
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
if let firstStream = nonHLSAssets.first {
let asset = firstStream.0
let url = firstStream.1
let requestRange = firstStream.2
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
if let randomStream = nonHLSAssets.randomElement() {
let instance = randomStream.0
let asset = randomStream.1
let url = randomStream.2
let requestRange = randomStream.3
// swiftlint:disable:next shorthand_optional_binding
if let asset = asset, let instance = instance, !instance.proxiesVideos {
if instance.app == .invidious { if instance.app == .invidious {
self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
hasForbiddenAsset = isForbidden switch status {
case HTTPStatus.Forbidden:
hasForbiddenAsset = true
case HTTPStatus.PartialContent:
hasAllowedAsset = true
case HTTPStatus.OK:
hasAllowedAsset = true
default:
break
}
} }
} else if instance.app == .piped { } else if instance.app == .piped {
self.testPipedAssets(asset: asset, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in self.testPipedAssets(asset: asset!, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
hasForbiddenAsset = isForbidden switch status {
case HTTPStatus.Forbidden:
hasForbiddenAsset = true
case HTTPStatus.PartialContent:
hasAllowedAsset = true
case HTTPStatus.OK:
hasAllowedAsset = true
default:
break
}
} }
} }
} } else if let firstHLS = hlsURLs.first {
} else if let randomHLS = hlsURLs.randomElement() { let asset = AVURLAsset(url: firstHLS)
let instance = randomHLS.0 if instance.app == .piped {
let asset = AVURLAsset(url: randomHLS.1) self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
switch status {
if instance?.app == .piped { case HTTPStatus.Forbidden:
self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in hasForbiddenAsset = true
hasForbiddenAsset = isForbidden case HTTPStatus.PartialContent:
hasAllowedAsset = true
case HTTPStatus.OK:
hasAllowedAsset = true
default:
break
}
}
} }
} }
} }
forbiddenAssetTestGroup.wait() forbiddenAssetTestGroup.wait()
// Post-processing code // Post-processing code
if let instance = stream.instance { if instance.app == .invidious, hasForbiddenAsset || instance.proxiesVideos {
if instance.app == .invidious { if let audio = stream.audioAsset {
if hasForbiddenAsset || instance.proxiesVideos { stream.audioAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: audio)
if let audio = stream.audioAsset { }
stream.audioAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: audio) if let video = stream.videoAsset {
} stream.videoAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: video)
if let video = stream.videoAsset { }
stream.videoAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: video) } else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
if let hlsURL = stream.hlsURL {
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
if let nonProxiedURL = possibleNonProxiedURL {
stream.hlsURL = nonProxiedURL.url
} }
} }
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset { } else {
if let hlsURL = stream.hlsURL { if let audio = stream.audioAsset {
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in
if let nonProxiedURL = possibleNonProxiedURL { stream.audioAsset = nonProxiedAudioAsset
stream.hlsURL = nonProxiedURL.url
}
} }
} else { }
if let audio = stream.audioAsset { if let video = stream.videoAsset {
PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
stream.audioAsset = nonProxiedAudioAsset stream.videoAsset = nonProxiedVideoAsset
}
}
if let video = stream.videoAsset {
PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
stream.videoAsset = nonProxiedVideoAsset
}
} }
} }
} }
@ -152,21 +171,21 @@ extension PlayerModel {
} }
} }
private func getAssets(from streams: [Stream]) -> (nonHLSAssets: [(Instance?, AVURLAsset?, URL, String?)], hlsURLs: [(Instance?, URL)]) { private func getAssets(from streams: [Stream]) -> (nonHLSAssets: [(AVURLAsset?, URL, String?)], hlsURLs: [URL]) {
var nonHLSAssets = [(Instance?, AVURLAsset?, URL, String?)]() var nonHLSAssets = [(AVURLAsset?, URL, String?)]()
var hlsURLs = [(Instance?, URL)]() var hlsURLs = [URL]()
for stream in streams { for stream in streams {
if stream.isHLS { if stream.isHLS {
if let url = stream.hlsURL?.url { if let url = stream.hlsURL?.url {
hlsURLs.append((stream.instance, url)) hlsURLs.append(url)
} }
} else { } else {
if let asset = stream.audioAsset { if let asset = stream.audioAsset {
nonHLSAssets.append((stream.instance, asset, asset.url, stream.requestRange)) nonHLSAssets.append((asset, asset.url, stream.requestRange))
} }
if let asset = stream.videoAsset { if let asset = stream.videoAsset {
nonHLSAssets.append((stream.instance, asset, asset.url, stream.requestRange)) nonHLSAssets.append((asset, asset.url, stream.requestRange))
} }
} }
} }
@ -174,24 +193,24 @@ extension PlayerModel {
return (nonHLSAssets, hlsURLs) return (nonHLSAssets, hlsURLs)
} }
private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) { private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Int) -> Void) {
// In case the range is nil, generate a random one. // In case the range is nil, generate a random one.
let randomEnd = Int.random(in: 200 ... 800) let randomEnd = Int.random(in: 200 ... 800)
let requestRange = range ?? "0-\(randomEnd)" let requestRange = range ?? "0-\(randomEnd)"
forbiddenAssetTestGroup.enter() forbiddenAssetTestGroup.enter()
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
completion(statusCode == HTTPStatus.Forbidden) completion(statusCode)
forbiddenAssetTestGroup.leave() forbiddenAssetTestGroup.leave()
} }
} }
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) { private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Int) -> Void) {
PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in
if let nonProxiedAsset = possibleNonProxiedAsset { if let nonProxiedAsset = possibleNonProxiedAsset {
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion) self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
} else { } else {
completion(false) completion(0)
} }
} }
} }