Add probe caching and dedupe for playback

This commit is contained in:
thePR0M3TH3AN committed 2026-01-30 10:11:31 -05:00
1 parent b246e8a37b
commit 2007e53b39
2 files changed
+97 -3

No files matched your search

+18 -1
View File
@@ -280,6 +280,7 @@ class Application {
this.torrentStatusNodes = null;
this.torrentStatusVisibilityHandler = null;
this.torrentStatusPageHideHandler = null;
this.urlProbePromises = new Map();
this.commentController = null;
this.initializeCommentController();
@@ -8200,7 +8201,23 @@ class Application {
}
async probeUrl(url, options = {}) {
return this.urlHealthController.probeUrl(url, options);
const trimmed = typeof url === "string" ? url.trim() : "";
if (!trimmed) {
return this.urlHealthController.probeUrl(url, options);
}
const existing = this.urlProbePromises.get(trimmed);
if (existing) {
return existing;
}
const probePromise = (async () => {
try {
return await this.urlHealthController.probeUrl(trimmed, options);
} finally {
this.urlProbePromises.delete(trimmed);
}
})();
this.urlProbePromises.set(trimmed, probePromise);
return probePromise;
}
async playHttp(videoEl, url) {
+79 -2
View File
@@ -65,6 +65,7 @@ const DEFAULT_UNSUPPORTED_BTITH_MESSAGE =
const AUTH_STATUS_CODES = new Set([401, 403]);
const SSL_ERROR_PATTERN = /(ssl|cert|certificate)/i;
const CORS_ERROR_PATTERN = /cors/i;
const PROBE_CACHE_TTL_MS = 45000;
const getHostedUrlFailureDetails = (probeResult = {}) => {
const outcome = probeResult?.outcome || "error";
@@ -241,6 +242,9 @@ export class PlaybackService {
this.analyticsCallbacks = analyticsCallbacks || {};
this.playbackStartTimeout = playbackStartTimeout;
this.currentSession = null;
this.probeCache = new Map();
this.probeInFlight = new Map();
this.probeCacheTtlMs = PROBE_CACHE_TTL_MS;
}
log(...args) {
@@ -270,6 +274,76 @@ export class PlaybackService {
}
}
getProbeCacheKey({ url, magnet }) {
const trimmedUrl = typeof url === "string" ? url.trim() : "";
const trimmedMagnet = typeof magnet === "string" ? magnet.trim() : "";
return `${trimmedUrl}::${trimmedMagnet}`;
}
pruneProbeCache(now = Date.now()) {
for (const [key, entry] of this.probeCache.entries()) {
if (!entry || !Number.isFinite(entry.expiresAt) || entry.expiresAt <= now) {
this.probeCache.delete(key);
}
}
}
getCachedProbeResult(cacheKey, now = Date.now()) {
this.pruneProbeCache(now);
const entry = this.probeCache.get(cacheKey);
if (!entry || entry.expiresAt <= now) {
return null;
}
return entry.result;
}
storeProbeResult(cacheKey, result, now = Date.now()) {
const ttl =
Number.isFinite(this.probeCacheTtlMs) && this.probeCacheTtlMs > 0
? this.probeCacheTtlMs
: PROBE_CACHE_TTL_MS;
this.probeCache.set(cacheKey, { result, expiresAt: now + ttl });
}
async probeHostedUrl({ url, magnet, probeUrl } = {}) {
const sanitizedUrl = typeof url === "string" ? url.trim() : "";
if (!sanitizedUrl) {
return { outcome: "invalid" };
}
const cacheKey = this.getProbeCacheKey({
url: sanitizedUrl,
magnet,
});
const cachedResult = this.getCachedProbeResult(cacheKey);
if (cachedResult) {
return cachedResult;
}
const inFlight = this.probeInFlight.get(cacheKey);
if (inFlight) {
return inFlight;
}
const probePromise = (async () => {
try {
const result =
typeof probeUrl === "function"
? await probeUrl(sanitizedUrl)
: { outcome: "error" };
this.storeProbeResult(cacheKey, result);
return result;
} catch (error) {
const result = { outcome: "error", error };
this.storeProbeResult(cacheKey, result);
return result;
} finally {
this.probeInFlight.delete(cacheKey);
}
})();
this.probeInFlight.set(cacheKey, probePromise);
return probePromise;
}
prepareVideoElement(videoElement) {
if (!videoElement) {
return;
@@ -842,8 +916,11 @@ class PlaybackSession extends SimpleEventEmitter {
{ once: true }
);
const probeResult =
typeof probeUrl === "function" ? await probeUrl(httpsUrl) : null;
const probeResult = await this.service.probeHostedUrl({
url: httpsUrl,
magnet: this.trimmedMagnet,
probeUrl,
});
const probeOutcome = probeResult?.outcome || "error";
const probeStatus = probeResult?.status || "unknown";
const shouldAttemptHosted =