Files
bitvid/tests/url-health-multisource.test.mjs
thePR0M3TH3ANandClaude Opus 4.8 9dbe1286c0 liveness: multi-source video support + faster CDN verdict (#5 phases 1-3)
Addresses "some NIP-71 videos don't play / he has multiple links" + "speed
the liveness check + don't render unplayable".

- Phase 1 — multi-source capture: nip71IngestAdapter collectVideoSources()
  exposes a `sources` list (all video imeta urls, in order; audio/image
  excluded; deduped) so mirrors are preserved (the adapter previously kept
  only one url).
- Phase 2 — multi-source liveness: urlHealthController.probeUrlList() probes
  every candidate (primary + sources) via the existing video-element
  playability probe — healthy as soon as one source plays, offline only if
  ALL fail. So a dead primary host no longer hides a video with a live
  mirror. (Card hide policy in cardSourceVisibility was already correct.)
- Phase 3 — speed: URL_PROBE_TIMEOUT_MS 8s -> 4s for a fast verdict so dead
  cards hide quickly; WebTorrent swarm probe stays generous.

Remaining (TODO #5): player-side fail-over at play time; hide-until-verified
for foreign ingested videos. Tests + mutation-verified (collectVideoSources
video-only filter, probeUrlList first-ok/offline/timeout). 1271/1272 unit
(known UploadModal flake).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 21:48:51 -04:00

58 lines
2.0 KiB
JavaScript

// Multi-source URL liveness: probe candidate mirrors in order — healthy as soon
// as one plays, offline only if all fail (so a dead primary host doesn't hide a
// video that has a working mirror).
import assert from "node:assert/strict";
import test from "node:test";
import UrlHealthController from "../js/ui/urlHealthController.js";
function controllerWith(outcomesByUrl) {
const ctrl = new UrlHealthController({ logger: { warn() {} } });
const probed = [];
ctrl.probeUrl = async (url) => {
probed.push(url);
return { outcome: outcomesByUrl[url] || "error" };
};
return { ctrl, probed };
}
test("returns ok on the first playable source and stops probing further", async () => {
const { ctrl, probed } = controllerWith({
"https://dead/v.mp4": "error",
"https://live/v.mp4": "ok",
"https://other/v.mp4": "ok",
});
const res = await ctrl.probeUrlList([
"https://dead/v.mp4",
"https://live/v.mp4",
"https://other/v.mp4",
]);
assert.equal(res.outcome, "ok");
assert.equal(res.url, "https://live/v.mp4");
assert.deepEqual(probed, ["https://dead/v.mp4", "https://live/v.mp4"], "stops at first ok");
});
test("offline only when every source fails", async () => {
const { ctrl } = controllerWith({
"https://a/v.mp4": "error",
"https://b/v.mp4": "error",
});
const res = await ctrl.probeUrlList(["https://a/v.mp4", "https://b/v.mp4"]);
assert.equal(res.outcome, "offline");
});
test("reports timeout if none played but at least one timed out", async () => {
const { ctrl } = controllerWith({
"https://a/v.mp4": "error",
"https://b/v.mp4": "timeout",
});
const res = await ctrl.probeUrlList(["https://a/v.mp4", "https://b/v.mp4"]);
assert.equal(res.outcome, "timeout");
});
test("empty / invalid list returns error", async () => {
const { ctrl } = controllerWith({});
assert.equal((await ctrl.probeUrlList([])).outcome, "error");
assert.equal((await ctrl.probeUrlList(["", " "])).outcome, "error");
});