mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
A WebTorrent-only card's sole liveness signal is the swarm probe (20s timeout, 5min cache), and P2P liveness is slow + intermittent — a live-but-slow video is indistinguishable from a dead one until that probe finishes. Hiding such a card until the swarm verifies could bury perfectly good P2P content for minutes. hide-until-verified now only suppresses a card while the FAST, reliable CDN probe is still running. Once the CDN resolves dead (or is absent, i.e. P2P-only), it falls back to show-pending for the WebTorrent part: shown while the swarm is checked, hidden only once it too is confirmed dead. Burying live content is worse than a brief flash of a dead one, and only the slow P2P probe is at issue. cardNeedsEagerLivenessProbe likewise skips P2P-only cards (they start visible). Tests: hide-foreign does-not-bury-WebTorrent-only coverage in tests/video-card-source-visibility.test.mjs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
145 lines
4.8 KiB
JavaScript
145 lines
4.8 KiB
JavaScript
import { getCardLivenessPolicy } from "../constants.js";
|
|
|
|
/**
|
|
* Whether a card is governed by a hide-until-verified policy (so it starts hidden
|
|
* and must be PROBED to ever appear). Such cards can't rely on the viewport
|
|
* IntersectionObserver — a `display:none` card never intersects, so the probe
|
|
* must be kicked eagerly on register. Owner's own cards are always visible and
|
|
* never gated.
|
|
*/
|
|
export function cardNeedsEagerLivenessProbe(cardLike) {
|
|
const card = resolveCardElement(cardLike);
|
|
if (!card || card.dataset.ownerIsViewer === "true") {
|
|
return false;
|
|
}
|
|
const policy = getCardLivenessPolicy();
|
|
const gated =
|
|
policy === "hide-all" ||
|
|
(policy === "hide-foreign" && card.dataset.foreign === "true");
|
|
if (!gated) {
|
|
return false;
|
|
}
|
|
// Only cards backed by a fast CDN probe are hidden-until-verified (so they need
|
|
// eager probing while hidden). P2P-only cards fall back to show-pending — they
|
|
// start visible and are probed normally on scroll, so no eager kick is needed.
|
|
return Boolean(card.dataset.urlHealthUrl);
|
|
}
|
|
|
|
function resolveCardElement(cardLike) {
|
|
if (!cardLike) {
|
|
return null;
|
|
}
|
|
|
|
const isElement =
|
|
typeof cardLike === "object" && cardLike !== null && cardLike.nodeType === 1;
|
|
|
|
if (isElement) {
|
|
const element = cardLike;
|
|
if (typeof element.closest === "function") {
|
|
if (element.classList?.contains("card")) {
|
|
return element;
|
|
}
|
|
return element.closest(".card");
|
|
}
|
|
return element.classList?.contains("card") ? element : null;
|
|
}
|
|
|
|
if (
|
|
typeof HTMLElement !== "undefined" &&
|
|
cardLike instanceof HTMLElement &&
|
|
typeof cardLike.closest === "function"
|
|
) {
|
|
if (cardLike.classList.contains("card")) {
|
|
return cardLike;
|
|
}
|
|
return cardLike.closest(".card");
|
|
}
|
|
|
|
if (typeof cardLike === "object" && cardLike !== null) {
|
|
if (typeof cardLike.card === "object" && cardLike.card?.nodeType === 1) {
|
|
return resolveCardElement(cardLike.card);
|
|
}
|
|
if (typeof cardLike.root === "object" && cardLike.root?.nodeType === 1) {
|
|
return resolveCardElement(cardLike.root);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function updateVideoCardSourceVisibility(cardLike) {
|
|
const card = resolveCardElement(cardLike);
|
|
if (!card) {
|
|
return;
|
|
}
|
|
|
|
if (card.dataset.ownerIsViewer === "true") {
|
|
if (card.hidden) {
|
|
card.hidden = false;
|
|
}
|
|
if (card.dataset.sourceVisibility !== "visible") {
|
|
card.dataset.sourceVisibility = "visible";
|
|
}
|
|
return;
|
|
}
|
|
|
|
const cdnState = (card.dataset.urlHealthState || "").toLowerCase();
|
|
const streamState = (card.dataset.streamHealthState || "").toLowerCase();
|
|
|
|
const cdnPending = !cdnState || cdnState === "checking";
|
|
const streamPending = !streamState || streamState === "checking";
|
|
|
|
const cdnHealthy = cdnState === "healthy";
|
|
const streamHealthy = streamState === "healthy";
|
|
|
|
// Liveness visibility policy (config/instance-config.js → CARD_LIVENESS_POLICY):
|
|
// show-pending : show now, hide only once every source is confirmed dead.
|
|
// hide-foreign : foreign/ingested cards stay hidden until a source is proven
|
|
// playable; native cards keep show-pending.
|
|
// hide-all : every non-owner card stays hidden until proven playable.
|
|
const policy = getCardLivenessPolicy();
|
|
const isForeign = card.dataset.foreign === "true";
|
|
const hideUntilVerified =
|
|
policy === "hide-all" || (policy === "hide-foreign" && isForeign);
|
|
|
|
// Default policy: hide only once every source has come back dead.
|
|
const confirmedDead =
|
|
!cdnHealthy && !streamHealthy && !cdnPending && !streamPending;
|
|
|
|
let shouldHide;
|
|
if (!hideUntilVerified) {
|
|
shouldHide = confirmedDead;
|
|
} else if (cdnHealthy || streamHealthy) {
|
|
shouldHide = false; // proven playable → show
|
|
} else if (cdnPending) {
|
|
// Still waiting on the FAST, reliable CDN probe (~4s) — keep it hidden so a
|
|
// dead foreign card never flashes in.
|
|
shouldHide = true;
|
|
} else {
|
|
// CDN is resolved dead (or absent) and the only remaining signal is the slow,
|
|
// unreliable WebTorrent swarm probe (20s + 5min cache). Do NOT bury the card
|
|
// behind it — a live-but-slow P2P video is indistinguishable from a dead one
|
|
// until that probe finishes, and burying live content is worse than a brief
|
|
// flash. Fall back to show-pending for the stream: show while it's checked,
|
|
// hide only once it too is confirmed dead.
|
|
shouldHide = !streamHealthy && !streamPending;
|
|
}
|
|
|
|
if (shouldHide) {
|
|
if (!card.hidden) {
|
|
card.hidden = true;
|
|
}
|
|
if (card.dataset.sourceVisibility !== "hidden") {
|
|
card.dataset.sourceVisibility = "hidden";
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (card.hidden) {
|
|
card.hidden = false;
|
|
}
|
|
if (card.dataset.sourceVisibility !== "visible") {
|
|
card.dataset.sourceVisibility = "visible";
|
|
}
|
|
}
|