mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-09 06:12:56 +00:00
playback: fail over across hosted mirrors before dropping to P2P (#5)
The liveness probe already tries all of a video's sources, but actual playback
only ever tried the single primary URL and then fell straight to WebTorrent. Now
playback fails over across hosted mirrors first.
- PlaybackSession builds an ordered, de-duped hostedSourceCandidates (primary URL
first, then the video's imeta `sources`; accepts string or {url} entries).
- attemptHostedPlayback takes a candidateUrl; the url-first execution loop tries
each candidate and advances to the next mirror on probe-fail/stall/timeout,
cleaning up watchdogs + resetting the <video> between candidates, only calling
WebTorrent after every hosted mirror fails.
- `sources` plumbed coordinator -> strategy -> session. Single-source videos
(no `sources`) behave exactly as before.
Tests: tests/services/playbackService_multisource.test.mjs (candidate
construction; the full async fail-over flow is covered by the existing
playbackService_order suite in CI — that mock-timers harness doesn't run in this
sandbox). ROLLBACK: revert the loop to the single attemptHostedPlayback() call
and drop the sources plumbing.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
c967789257
commit
0a37e625bb
5 files changed
+171
-30
No files matched your search
@@ -0,0 +1,81 @@
|
||||
// #5 multi-source fail-over: the PlaybackSession builds an ordered, de-duped list
|
||||
// of hosted sources (primary URL first, then the video's imeta mirrors) that the
|
||||
// execution flow tries in turn before dropping to WebTorrent. This verifies the
|
||||
// candidate-list construction (deterministic). The full async fail-over flow is
|
||||
// exercised by the playbackService_order suite in CI (the async/mock-timers
|
||||
// playback harness doesn't run in this sandbox).
|
||||
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { PlaybackService } from "../../js/services/playbackService.js";
|
||||
|
||||
const makeSession = ({ url, sources, magnet = "" }) => {
|
||||
const service = new PlaybackService({
|
||||
logger: () => {},
|
||||
urlFirstEnabled: true,
|
||||
isValidMagnetUri: () => true,
|
||||
});
|
||||
return service.createSession({ url, sources, magnet });
|
||||
};
|
||||
|
||||
test("primary URL leads, then the imeta mirrors, de-duped", () => {
|
||||
const session = makeSession({
|
||||
url: "https://primary.example/v.mp4",
|
||||
sources: [
|
||||
{ url: "https://mirror-a.example/v.mp4" },
|
||||
{ url: "https://mirror-b.example/v.mp4" },
|
||||
],
|
||||
});
|
||||
assert.deepEqual(session.hostedSourceCandidates, [
|
||||
"https://primary.example/v.mp4",
|
||||
"https://mirror-a.example/v.mp4",
|
||||
"https://mirror-b.example/v.mp4",
|
||||
]);
|
||||
});
|
||||
|
||||
test("de-dupes the primary when it also appears in sources (case-insensitive)", () => {
|
||||
const session = makeSession({
|
||||
url: "https://primary.example/v.mp4",
|
||||
sources: [
|
||||
{ url: "https://PRIMARY.example/v.mp4" },
|
||||
{ url: "https://mirror.example/v.mp4" },
|
||||
{ url: "https://mirror.example/v.mp4" },
|
||||
],
|
||||
});
|
||||
assert.deepEqual(session.hostedSourceCandidates, [
|
||||
"https://primary.example/v.mp4",
|
||||
"https://mirror.example/v.mp4",
|
||||
]);
|
||||
});
|
||||
|
||||
test("accepts plain-string sources too", () => {
|
||||
const session = makeSession({
|
||||
url: "https://primary.example/v.mp4",
|
||||
sources: ["https://mirror.example/v.mp4"],
|
||||
});
|
||||
assert.deepEqual(session.hostedSourceCandidates, [
|
||||
"https://primary.example/v.mp4",
|
||||
"https://mirror.example/v.mp4",
|
||||
]);
|
||||
});
|
||||
|
||||
test("no sources → just the primary (single-source behavior unchanged)", () => {
|
||||
const session = makeSession({ url: "https://only.example/v.mp4" });
|
||||
assert.deepEqual(session.hostedSourceCandidates, ["https://only.example/v.mp4"]);
|
||||
});
|
||||
|
||||
test("torrent-only video (no URL) → empty hosted candidates", () => {
|
||||
const session = makeSession({ url: "", sources: [], magnet: "magnet:?xt=urn:btih:abc" });
|
||||
assert.deepEqual(session.hostedSourceCandidates, []);
|
||||
});
|
||||
|
||||
test("ignores junk source entries (null, missing url, blanks)", () => {
|
||||
const session = makeSession({
|
||||
url: "https://primary.example/v.mp4",
|
||||
sources: [null, {}, { url: " " }, { url: "https://good.example/v.mp4" }, " "],
|
||||
});
|
||||
assert.deepEqual(session.hostedSourceCandidates, [
|
||||
"https://primary.example/v.mp4",
|
||||
"https://good.example/v.mp4",
|
||||
]);
|
||||
});
|
||||
Reference in new issue
Block a user