Merge pull request #120 from PR0M3TH3AN/codex/update-cdn-indicator-to-check-link-status

Verify CDN badge only marks healthy for playable videos
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-09-27 13:15:43 -04:00
commit cbdab9f011
1 file changed
+100 -19
+100 -19
View File
@@ -226,12 +226,19 @@ function writeUrlHealthToCache(eventId, url, result, ttlMs = URL_HEALTH_TTL_MS)
* event simultaneously do not all fire off duplicate requests. The stored
* metadata includes the URL so edits invalidate the old request immediately.
*/
function setInFlightUrlProbe(eventId, url, promise) {
function buildUrlProbeKey(url, options = {}) {
const trimmed = typeof url === "string" ? url : "";
const mode = options?.confirmPlayable ? "playable" : "basic";
return `${trimmed}::${mode}`;
}
function setInFlightUrlProbe(eventId, url, promise, options = {}) {
if (!eventId || !promise) {
return;
}
urlHealthInFlight.set(eventId, { promise, url });
const key = buildUrlProbeKey(url, options);
urlHealthInFlight.set(eventId, { promise, key });
promise.finally(() => {
const current = urlHealthInFlight.get(eventId);
if (current && current.promise === promise) {
@@ -240,7 +247,7 @@ function setInFlightUrlProbe(eventId, url, promise) {
});
}
function getInFlightUrlProbe(eventId, url) {
function getInFlightUrlProbe(eventId, url, options = {}) {
if (!eventId) {
return null;
}
@@ -250,7 +257,8 @@ function getInFlightUrlProbe(eventId, url) {
return null;
}
if (url && entry.url && entry.url !== url) {
const key = buildUrlProbeKey(url, options);
if (entry.key && entry.key !== key) {
return null;
}
@@ -3308,7 +3316,8 @@ class bitvidApp {
this.updateUrlHealthBadge(badgeEl, { status: "checking" }, eventId);
const existingProbe = getInFlightUrlProbe(eventId, trimmedUrl);
const probeOptions = { confirmPlayable: true };
const existingProbe = getInFlightUrlProbe(eventId, trimmedUrl, probeOptions);
if (existingProbe) {
existingProbe
.then((entry) => {
@@ -3325,14 +3334,14 @@ class bitvidApp {
return;
}
const probePromise = this.probeUrl(trimmedUrl)
const probePromise = this.probeUrl(trimmedUrl, probeOptions)
.then((result) => {
const outcome = result?.outcome || "error";
let entry;
if (outcome === "ok") {
entry = { status: "healthy", message: "✅ CDN" };
} else if (outcome === "opaque") {
} else if (outcome === "opaque" || outcome === "unknown") {
entry = {
status: "unknown",
message: "⚠️ CDN",
@@ -3360,7 +3369,7 @@ class bitvidApp {
return this.storeUrlHealth(eventId, trimmedUrl, entry);
});
setInFlightUrlProbe(eventId, trimmedUrl, probePromise);
setInFlightUrlProbe(eventId, trimmedUrl, probePromise, probeOptions);
probePromise
.then((entry) => {
@@ -4485,12 +4494,34 @@ class bitvidApp {
});
}
async probeUrl(url) {
async probeUrl(url, options = {}) {
const trimmed = typeof url === "string" ? url.trim() : "";
if (!trimmed) {
return { outcome: "invalid" };
}
const confirmPlayable = options?.confirmPlayable === true;
const confirmWithVideoElement = async () => {
if (!confirmPlayable) {
return null;
}
try {
const result = await this.probeUrlWithVideoElement(trimmed);
if (result && result.outcome) {
return result;
}
} catch (err) {
console.warn(
`[probeUrl] Video element probe threw for ${trimmed}:`,
err
);
}
return null;
};
const supportsAbort = typeof AbortController !== "undefined";
const controller = supportsAbort ? new AbortController() : null;
let timeoutId = null;
@@ -4529,11 +4560,13 @@ class bitvidApp {
clearTimeout(timeoutId);
}
console.warn(`[probeUrl] HEAD request failed for ${trimmed}:`, err);
const fallback = await this.probeUrlWithVideoElement(trimmed);
if (fallback && fallback.outcome) {
const fallback = await confirmWithVideoElement();
if (fallback) {
return fallback;
}
return { outcome: "error", error: err };
return confirmPlayable
? { outcome: "error", error: err }
: { outcome: "unknown", error: err };
}
if (timeoutId) {
@@ -4541,11 +4574,11 @@ class bitvidApp {
}
if (responseOrTimeout && responseOrTimeout.outcome === "timeout") {
const fallback = await this.probeUrlWithVideoElement(trimmed);
if (fallback && fallback.outcome) {
const fallback = await confirmWithVideoElement();
if (fallback) {
return fallback;
}
return { outcome: "timeout" };
return confirmPlayable ? { outcome: "timeout" } : { outcome: "unknown" };
}
const response = responseOrTimeout;
@@ -4554,15 +4587,37 @@ class bitvidApp {
}
if (response.type === "opaque") {
const fallback = await this.probeUrlWithVideoElement(trimmed);
if (fallback && fallback.outcome) {
const fallback = await confirmWithVideoElement();
if (fallback) {
return fallback;
}
return { outcome: "opaque" };
return { outcome: confirmPlayable ? "opaque" : "unknown" };
}
if (!response.ok) {
const fallback = await confirmWithVideoElement();
if (fallback) {
return fallback;
}
return {
outcome: "bad",
status: response.status,
};
}
const playbackCheck = await confirmWithVideoElement();
if (playbackCheck) {
if (playbackCheck.outcome === "ok") {
return {
...playbackCheck,
status: response.status,
};
}
return playbackCheck;
}
return {
outcome: response.ok ? "ok" : "bad",
outcome: "ok",
status: response.status,
};
}
@@ -4779,6 +4834,32 @@ class bitvidApp {
fallbackStarted = true;
this.cleanupUrlPlaybackWatchdog();
cleanupHostedUrlStatusListeners();
if (videoEl) {
try {
videoEl.pause();
} catch (err) {
this.log(
"[playVideoWithFallback] Ignoring pause error before torrent fallback:",
err
);
}
try {
videoEl.removeAttribute("src");
} catch (err) {
// removeAttribute throws in old browsers when the attribute does not exist
}
videoEl.src = "";
videoEl.srcObject = null;
try {
videoEl.load();
} catch (err) {
this.log(
"[playVideoWithFallback] Ignoring load error before torrent fallback:",
err
);
}
}
if (!magnetForPlayback) {
const message =
"Hosted playback failed and no magnet fallback is available.";