Merge pull request #124 from PR0M3TH3AN/codex/improve-webtorrent-and-cdn-health-checks

Scope health probes to visible video cards
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-09-27 20:16:56 -04:00
commit 5d0f122e48
5 files changed
+296 -54

No files matched your search

+25 -5
View File
@@ -11,6 +11,7 @@ import { deriveTorrentPlaybackConfig } from "./playbackUtils.js";
import { URL_FIRST_ENABLED } from "./constants.js";
import { trackVideoView } from "./analytics.js";
import { attachHealthBadges } from "./gridHealth.js";
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
import {
initialWhitelist,
initialBlacklist,
@@ -3693,9 +3694,17 @@ class bitvidApp {
if (cardEl.dataset.urlHealthReason) {
delete cardEl.dataset.urlHealthReason;
}
cardEl.dataset.urlHealthEventId = video.id || "";
cardEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl);
} else {
cardEl.dataset.urlHealthState = "offline";
cardEl.dataset.urlHealthReason = "missing-source";
if (cardEl.dataset.urlHealthEventId) {
delete cardEl.dataset.urlHealthEventId;
}
if (cardEl.dataset.urlHealthUrl) {
delete cardEl.dataset.urlHealthUrl;
}
}
if (magnetProvided && magnetSupported) {
cardEl.dataset.streamHealthState = "checking";
@@ -3832,11 +3841,18 @@ class bitvidApp {
if (trimmedUrl) {
const badgeEl = cardEl.querySelector("[data-url-health-state]");
if (badgeEl) {
this.handleUrlHealthBadge({
video,
url: trimmedUrl,
badgeEl,
});
badgeEl.dataset.urlHealthEventId = video.id || "";
badgeEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl);
}
} else {
const badgeEl = cardEl.querySelector("[data-url-health-state]");
if (badgeEl) {
if (badgeEl.dataset.urlHealthEventId) {
delete badgeEl.dataset.urlHealthEventId;
}
if (badgeEl.dataset.urlHealthUrl) {
delete badgeEl.dataset.urlHealthUrl;
}
}
}
}
@@ -3852,6 +3868,10 @@ class bitvidApp {
this.videoList.innerHTML = "";
this.videoList.appendChild(fragment);
attachHealthBadges(this.videoList);
attachUrlHealthBadges(this.videoList, ({ badgeEl, url, eventId }) => {
const video = this.videosMap.get(eventId) || { id: eventId };
this.handleUrlHealthBadge({ video, url, badgeEl });
});
// Ensure every thumbnail can recover with a fallback image if the primary
// source fails to load or returns a zero-sized response (some CDNs error
+25 -8
View File
@@ -7,6 +7,7 @@ import {
import { app } from "./app.js";
import { subscriptions } from "./subscriptions.js"; // <-- NEW import
import { attachHealthBadges } from "./gridHealth.js";
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
import { initialBlacklist, initialWhitelist } from "./lists.js";
import { isWhitelistEnabled } from "./config.js";
@@ -445,9 +446,17 @@ async function loadUserVideos(pubkey) {
if (cardEl.dataset.urlHealthReason) {
delete cardEl.dataset.urlHealthReason;
}
cardEl.dataset.urlHealthEventId = video.id || "";
cardEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl);
} else {
cardEl.dataset.urlHealthState = "offline";
cardEl.dataset.urlHealthReason = "missing-source";
if (cardEl.dataset.urlHealthEventId) {
delete cardEl.dataset.urlHealthEventId;
}
if (cardEl.dataset.urlHealthUrl) {
delete cardEl.dataset.urlHealthUrl;
}
}
if (magnetProvided && magnetSupported) {
cardEl.dataset.streamHealthState = "checking";
@@ -491,14 +500,18 @@ async function loadUserVideos(pubkey) {
}
});
if (trimmedUrl) {
const badgeEl = cardEl.querySelector("[data-url-health-state]");
if (badgeEl) {
app.handleUrlHealthBadge({
video,
url: trimmedUrl,
badgeEl,
});
const badgeEl = cardEl.querySelector("[data-url-health-state]");
if (badgeEl) {
if (trimmedUrl) {
badgeEl.dataset.urlHealthEventId = video.id || "";
badgeEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl);
} else {
if (badgeEl.dataset.urlHealthEventId) {
delete badgeEl.dataset.urlHealthEventId;
}
if (badgeEl.dataset.urlHealthUrl) {
delete badgeEl.dataset.urlHealthUrl;
}
}
}
@@ -508,6 +521,10 @@ async function loadUserVideos(pubkey) {
container.appendChild(fragment);
attachHealthBadges(container);
attachUrlHealthBadges(container, ({ badgeEl, url, eventId }) => {
const video = app.videosMap.get(eventId) || { id: eventId };
app.handleUrlHealthBadge({ video, url, badgeEl });
});
window.app.videoList = container;
window.app.attachVideoListHandler();
+93 -33
View File
@@ -165,8 +165,9 @@ function queueProbe(magnet, cacheKey, priority = 0) {
const PROBE_CACHE_TTL_MS = 5 * 60 * 1000;
const PROBE_TIMEOUT_MS = 8000;
const PROBE_CONCURRENCY = 6;
const PROBE_CONCURRENCY = 12;
const PROBE_POLL_COUNT = 3;
const PRIORITY_BASELINE = 1_000_000;
class ProbeQueue {
constructor(max = 2) {
@@ -285,48 +286,107 @@ function getViewportCenter() {
};
}
function calculateDistanceSquared(entry, viewportCenter) {
if (!viewportCenter) {
return Number.POSITIVE_INFINITY;
}
const rect = getIntersectionRect(entry);
if (!rect) {
return Number.POSITIVE_INFINITY;
}
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = centerX - viewportCenter.x;
const dy = centerY - viewportCenter.y;
return dx * dx + dy * dy;
}
function computeVisibilityPriority({ ratio, distance }) {
const normalizedRatio = Number.isFinite(ratio)
? Math.min(Math.max(ratio, 0), 1)
: 0;
const distanceScore = Number.isFinite(distance) ? 1 / (1 + distance) : 0;
return normalizedRatio * 1000 + distanceScore;
}
function prioritizeEntries(entries, viewportCenter) {
if (!Array.isArray(entries) || entries.length === 0) {
return [];
}
return entries
const filtered = entries
.filter((entry) => entry.isIntersecting && entry.target instanceof HTMLElement)
.map((entry) => {
const rect = getIntersectionRect(entry);
if (!rect) {
return null;
}
const ratio =
typeof entry.intersectionRatio === "number" ? entry.intersectionRatio : 0;
const distance = calculateDistanceSquared(entry, viewportCenter);
const priority = computeVisibilityPriority({ ratio, distance });
return { entry, ratio, distance, priority };
const centerY = rect.top + rect.height / 2;
const verticalDistance = viewportCenter
? Math.abs(centerY - viewportCenter.y)
: Number.POSITIVE_INFINITY;
return { entry, ratio, centerY, verticalDistance };
})
.sort((a, b) => {
if (b.priority !== a.priority) {
return b.priority - a.priority;
.filter(Boolean);
if (filtered.length === 0) {
return [];
}
if (!viewportCenter) {
return filtered
.sort((a, b) => {
if (b.ratio !== a.ratio) {
return b.ratio - a.ratio;
}
return a.centerY - b.centerY;
})
.map((item, index) => ({
entry: item.entry,
priority: PRIORITY_BASELINE - index,
}));
}
const ordered = filtered
.slice()
.sort((a, b) => a.centerY - b.centerY);
let centerIndex = 0;
let minDistance = Number.POSITIVE_INFINITY;
for (let i = 0; i < ordered.length; i += 1) {
const candidate = ordered[i];
if (candidate.verticalDistance < minDistance) {
minDistance = candidate.verticalDistance;
centerIndex = i;
}
}
const prioritized = [];
const pushCandidate = (candidate) => {
if (!candidate) {
return;
}
prioritized.push(candidate);
};
pushCandidate(ordered[centerIndex]);
let left = centerIndex - 1;
let right = centerIndex + 1;
while (left >= 0 || right < ordered.length) {
const leftCandidate = left >= 0 ? ordered[left] : null;
const rightCandidate = right < ordered.length ? ordered[right] : null;
if (leftCandidate && rightCandidate) {
const leftDistance = leftCandidate.verticalDistance;
const rightDistance = rightCandidate.verticalDistance;
const distanceDelta = Math.abs(leftDistance - rightDistance);
if (distanceDelta <= 0.5) {
if (rightCandidate.ratio > leftCandidate.ratio) {
pushCandidate(rightCandidate);
right += 1;
} else {
pushCandidate(leftCandidate);
left -= 1;
}
} else if (leftDistance < rightDistance) {
pushCandidate(leftCandidate);
left -= 1;
} else {
pushCandidate(rightCandidate);
right += 1;
}
return a.distance - b.distance;
});
} else if (rightCandidate) {
pushCandidate(rightCandidate);
right += 1;
} else if (leftCandidate) {
pushCandidate(leftCandidate);
left -= 1;
}
}
return prioritized.map((candidate, index) => ({
entry: candidate.entry,
priority: PRIORITY_BASELINE - index,
}));
}
function processObserverEntries(entries, state) {
+29 -8
View File
@@ -4,6 +4,7 @@ import {
convertEventToVideo as sharedConvertEventToVideo,
} from "./nostr.js";
import { attachHealthBadges } from "./gridHealth.js";
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
function getAbsoluteShareUrl(nevent) {
if (!nevent) {
@@ -479,9 +480,17 @@ class SubscriptionsManager {
if (cardEl.dataset.urlHealthReason) {
delete cardEl.dataset.urlHealthReason;
}
cardEl.dataset.urlHealthEventId = video.id || "";
cardEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl);
} else {
cardEl.dataset.urlHealthState = "offline";
cardEl.dataset.urlHealthReason = "missing-source";
if (cardEl.dataset.urlHealthEventId) {
delete cardEl.dataset.urlHealthEventId;
}
if (cardEl.dataset.urlHealthUrl) {
delete cardEl.dataset.urlHealthUrl;
}
}
if (magnetProvided && magnetSupported) {
cardEl.dataset.streamHealthState = "checking";
@@ -519,14 +528,18 @@ class SubscriptionsManager {
delete cardEl.dataset.magnet;
}
if (trimmedUrl && window.app?.handleUrlHealthBadge) {
const badgeEl = cardEl.querySelector("[data-url-health-state]");
if (badgeEl) {
window.app.handleUrlHealthBadge({
video,
url: trimmedUrl,
badgeEl,
});
const badgeEl = cardEl.querySelector("[data-url-health-state]");
if (badgeEl) {
if (trimmedUrl) {
badgeEl.dataset.urlHealthEventId = video.id || "";
badgeEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl);
} else {
if (badgeEl.dataset.urlHealthEventId) {
delete badgeEl.dataset.urlHealthEventId;
}
if (badgeEl.dataset.urlHealthUrl) {
delete badgeEl.dataset.urlHealthUrl;
}
}
}
}
@@ -535,6 +548,14 @@ class SubscriptionsManager {
container.appendChild(fragment);
attachHealthBadges(container);
attachUrlHealthBadges(container, ({ badgeEl, url, eventId }) => {
if (!window.app?.handleUrlHealthBadge) {
return;
}
const video =
window.app.videosMap?.get?.(eventId) || { id: eventId };
window.app.handleUrlHealthBadge({ video, url, badgeEl });
});
if (window.app) {
window.app.videoList = container;
+124
View File
@@ -0,0 +1,124 @@
const containerState = new WeakMap();
const ROOT_MARGIN = "0px";
const THRESHOLD = 0.25;
function decodeUrl(value) {
if (typeof value !== "string" || !value) {
return "";
}
try {
return decodeURIComponent(value);
} catch (err) {
return value;
}
}
function ensureState(container) {
let state = containerState.get(container);
if (state) {
return state;
}
const observedCards = new WeakSet();
const observer = new IntersectionObserver(
(entries) => {
processEntries(entries, state);
},
{ root: null, rootMargin: ROOT_MARGIN, threshold: THRESHOLD }
);
state = {
observer,
observedCards,
onCheck: null,
};
containerState.set(container, state);
return state;
}
function processEntries(entries, state) {
if (!state?.onCheck || !Array.isArray(entries) || !entries.length) {
return;
}
entries.forEach((entry) => {
if (!entry.isIntersecting || entry.intersectionRatio <= 0) {
return;
}
const card = entry.target;
if (!(card instanceof HTMLElement)) {
return;
}
const badgeEl = card.querySelector("[data-url-health-state]");
if (!(badgeEl instanceof HTMLElement)) {
return;
}
const encodedUrl =
card.dataset.urlHealthUrl || badgeEl.dataset.urlHealthUrl || "";
const eventId =
card.dataset.urlHealthEventId || badgeEl.dataset.urlHealthEventId || "";
if (!encodedUrl || !eventId) {
return;
}
const currentState =
card.dataset.urlHealthState || badgeEl.dataset.urlHealthState;
if (currentState && currentState !== "checking") {
return;
}
const url = decodeUrl(encodedUrl);
if (!url) {
return;
}
try {
state.onCheck({ card, badgeEl, url, eventId });
} catch (err) {
console.warn("[urlHealthObserver] onCheck handler failed", err);
}
});
}
export function attachUrlHealthBadges(container, onCheck) {
if (!(container instanceof HTMLElement)) {
return;
}
const state = ensureState(container);
if (typeof onCheck === "function") {
state.onCheck = onCheck;
}
const cards = container.querySelectorAll(".video-card");
cards.forEach((card) => {
if (!(card instanceof HTMLElement)) {
return;
}
if (state.observedCards.has(card)) {
return;
}
state.observedCards.add(card);
state.observer.observe(card);
});
const records = state.observer.takeRecords();
if (records.length) {
processEntries(records, state);
}
}
export function refreshUrlHealthBadges(container) {
const state = containerState.get(container);
if (!state) {
return;
}
const records = state.observer.takeRecords();
if (records.length) {
processEntries(records, state);
}
}