mirror of
https://github.com/yattee/yattee.git
synced 2026-06-05 06:14:18 +00:00
Cache and prewarm Invidious proxy auto-detection
The proxy auto-detect path (when proxiesVideos is off) HEADed a googlevideo URL with a 5 s timeout on every video. The verdict is a property of the network, not the video, so the cost was paid for no reason on videos 2..N. On a network where the CDN is blocked the full 5 s timeout was added to playback startup every single time. Two changes: 1) ProxyDetectionCache (actor, per-instance, 10 min TTL). First miss pays the HEAD once and caches the verdict; subsequent videos hit the cache synchronously. Concurrent callers share one in-flight probe. The last-seen sample CDN URL is retained so future probes don't need a fresh URL from the current video. 2) PlayerService kicks off InvidiousAPI.prewarmProxyDetection() in parallel with the videoWith... API call. By the time streams come back, the verdict is usually already cached and proxyStreamsIfNeeded is a sync lookup. Cheap when there's nothing to prewarm. Cache invalidation: - on InstancesManager.update (URL change, proxy toggle flip) - on InstancesManager.remove - TTL covers the network-change case for now (no NWPathMonitor yet)
This commit is contained in:
@@ -602,20 +602,33 @@ extension InvidiousAPI {
|
||||
// URLs). Don't second-guess that here by host-rewriting CDN URLs.
|
||||
if instance.type == .yatteeServer { return streams }
|
||||
|
||||
// Find first YouTube CDN URL for 403 detection
|
||||
let firstCDNURL = streams.first(where: { isYouTubeCDNURL($0.url) })?.url
|
||||
|
||||
let shouldProxy: Bool
|
||||
if instance.proxiesVideos {
|
||||
shouldProxy = true
|
||||
LoggingService.shared.info("Proxying streams through \(instance.displayName) (user-enabled)", category: .player)
|
||||
} else if let cdnURL = firstCDNURL, await isForbidden(cdnURL) {
|
||||
shouldProxy = true
|
||||
LoggingService.shared.info("Proxying streams through \(instance.displayName) (auto-detected 403)", category: .player)
|
||||
} else if let cdnURL = firstCDNURL {
|
||||
// Auto-detect via cached HEAD probe. Cache cuts videos 2..N down
|
||||
// to a synchronous lookup; on the first miss we pay the HEAD once.
|
||||
shouldProxy = await ProxyDetectionCache.shared.decision(
|
||||
for: instance,
|
||||
sampleURL: cdnURL,
|
||||
probe: { await isForbidden($0) }
|
||||
)
|
||||
if shouldProxy {
|
||||
LoggingService.shared.info("Proxying streams through \(instance.displayName) (auto-detected 403)", category: .player)
|
||||
}
|
||||
} else {
|
||||
shouldProxy = false
|
||||
}
|
||||
|
||||
// Even when we don't end up proxying, remember a sample CDN URL so
|
||||
// future videos can prewarm the probe before their API call returns.
|
||||
if !shouldProxy, let cdnURL = firstCDNURL {
|
||||
await ProxyDetectionCache.shared.record(decision: false, sampleURL: cdnURL, for: instance)
|
||||
}
|
||||
|
||||
guard shouldProxy else { return streams }
|
||||
|
||||
return streams.map { stream in
|
||||
@@ -625,6 +638,21 @@ extension InvidiousAPI {
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/// Kick off proxy auto-detection in the background using a previously-seen
|
||||
/// CDN URL for this instance. Cheap when there's nothing to do (no sample
|
||||
/// URL yet, or the decision is already cached). Call this when the player
|
||||
/// starts loading so the verdict is ready by the time streams come back.
|
||||
static func prewarmProxyDetection(for instance: Instance) async {
|
||||
guard instance.supportsVideoProxying,
|
||||
instance.type != .yatteeServer,
|
||||
!instance.proxiesVideos else { return }
|
||||
|
||||
await ProxyDetectionCache.shared.prewarm(
|
||||
instance: instance,
|
||||
probe: { await isForbidden($0) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Redirect Blocker
|
||||
|
||||
Reference in New Issue
Block a user