mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Two fixes for hosted (CDN) playback that users hit on a torrent-first instance:
1. Reliability: clear crossOrigin before direct hosted playback (in
resetVideoElement and right before video.src = httpsUrl). A leftover
crossOrigin="anonymous" from a prior WebTorrent attempt on the reused
<video> forced a CORS request that CDNs / redirect targets (e.g. archive.org
storage nodes) reject, failing with MEDIA_ERR_SRC_NOT_SUPPORTED ("source not
supported or blocked"). Direct <video> playback never needs CORS, so any URL
now plays.
2. CDN-first for dual-source: when a hosted URL exists, play it first for
instant, reliable startup; WebTorrent is reserved for URL-less videos (and
explicit forcedSource: "torrent"). Previously a torrent-first instance made
dual-source videos wait on a P2P cold start before falling back to the CDN.
The torrent-first branch is now only reached without a URL, so it waits
indefinitely for peers (no plan B) rather than on a fixed timeout.
Tests: hosted playback clears crossOrigin and plays; a torrent-first instance
plays the URL first when one exists and never calls WebTorrent; URL-less videos
still use WebTorrent. Spec-corrected two playbackService_order tests that
encoded the old torrent-first-for-dual-source behavior. All mutation-verified.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
234 lines
7.4 KiB
JavaScript
234 lines
7.4 KiB
JavaScript
import { describe, test, mock, before, after, beforeEach, afterEach } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { PlaybackService } from "../../js/services/playbackService.js";
|
|
|
|
describe("PlaybackService Ordering", () => {
|
|
let video;
|
|
let window;
|
|
|
|
before(() => {
|
|
// Mock global window/document
|
|
global.window = {
|
|
HTMLMediaElement: {
|
|
HAVE_NOTHING: 0,
|
|
HAVE_METADATA: 1,
|
|
HAVE_CURRENT_DATA: 2,
|
|
HAVE_FUTURE_DATA: 3,
|
|
HAVE_ENOUGH_DATA: 4,
|
|
},
|
|
Event: class Event { constructor(type) { this.type = type; } },
|
|
localStorage: {
|
|
getItem: () => null,
|
|
setItem: () => {},
|
|
removeItem: () => {}
|
|
}
|
|
};
|
|
global.document = {
|
|
createElement: (tag) => {
|
|
if (tag === 'video') return video;
|
|
return {};
|
|
}
|
|
};
|
|
window = global.window;
|
|
});
|
|
|
|
after(() => {
|
|
delete global.window;
|
|
delete global.document;
|
|
});
|
|
|
|
describe("Playback Execution Order", () => {
|
|
let playMock;
|
|
let loadMock;
|
|
let pauseMock;
|
|
let eventListeners;
|
|
|
|
beforeEach(() => {
|
|
mock.timers.enable({ apis: ['setTimeout'] });
|
|
eventListeners = {};
|
|
|
|
video = {
|
|
play: mock.fn(async () => {}),
|
|
load: mock.fn(),
|
|
pause: mock.fn(),
|
|
removeAttribute: mock.fn(),
|
|
addEventListener: (event, handler) => {
|
|
if (!eventListeners[event]) eventListeners[event] = [];
|
|
eventListeners[event].push(handler);
|
|
},
|
|
removeEventListener: (event, handler) => {
|
|
if (eventListeners[event]) {
|
|
eventListeners[event] = eventListeners[event].filter(h => h !== handler);
|
|
}
|
|
},
|
|
dispatchEvent: (event) => {
|
|
if (eventListeners[event.type]) {
|
|
eventListeners[event.type].forEach(h => h(event));
|
|
}
|
|
},
|
|
readyState: 0,
|
|
networkState: 0,
|
|
dataset: {},
|
|
muted: false
|
|
};
|
|
|
|
playMock = video.play;
|
|
loadMock = video.load;
|
|
pauseMock = video.pause;
|
|
});
|
|
|
|
afterEach(() => {
|
|
mock.timers.reset();
|
|
});
|
|
|
|
test("urlFirstEnabled=true: Tries URL first, succeeds", async () => {
|
|
const service = new PlaybackService({
|
|
logger: () => {},
|
|
urlFirstEnabled: true,
|
|
isValidMagnetUri: () => true,
|
|
});
|
|
|
|
const probeUrl = mock.fn(async () => ({ outcome: "good", status: 200 }));
|
|
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
|
|
|
|
const session = service.createSession({
|
|
url: "https://example.com/vid.mp4",
|
|
magnet: "magnet:?xt=urn:btih:123",
|
|
videoElement: video,
|
|
probeUrl,
|
|
playViaWebTorrent,
|
|
});
|
|
|
|
const startPromise = session.start();
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
video.dispatchEvent(new window.Event("playing"));
|
|
const result = await startPromise;
|
|
|
|
assert.equal(probeUrl.mock.callCount(), 1, "Should probe URL");
|
|
assert.equal(playViaWebTorrent.mock.callCount(), 0, "Should NOT call torrent");
|
|
assert.equal(result.source, "url");
|
|
});
|
|
|
|
test("urlFirstEnabled=true: Tries URL first, fails, falls back to Torrent", async () => {
|
|
const service = new PlaybackService({
|
|
logger: () => {},
|
|
urlFirstEnabled: true,
|
|
isValidMagnetUri: () => true,
|
|
});
|
|
|
|
// Probe returns error
|
|
const probeUrl = mock.fn(async () => ({ outcome: "bad", status: 404 }));
|
|
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
|
|
|
|
const session = service.createSession({
|
|
url: "https://example.com/vid.mp4",
|
|
magnet: "magnet:?xt=urn:btih:123",
|
|
videoElement: video,
|
|
probeUrl,
|
|
playViaWebTorrent,
|
|
});
|
|
|
|
const result = await session.start();
|
|
|
|
assert.equal(probeUrl.mock.callCount(), 1, "Should probe URL");
|
|
assert.equal(playViaWebTorrent.mock.callCount(), 1, "Should fall back to torrent");
|
|
assert.equal(result.source, "torrent");
|
|
});
|
|
|
|
// SPEC CHANGE: dual-source videos are now CDN-first (see PlaybackSession
|
|
// execute(): `if (httpsUrl) tryUrlFirst = true`). A torrent-first instance
|
|
// default no longer forces P2P when a hosted URL is also available, because
|
|
// waiting on a P2P cold start when a working CDN exists was the slow/flaky
|
|
// path users hit. WebTorrent is now reserved for URL-less videos (and
|
|
// explicit forcedSource: "torrent", covered below).
|
|
test("urlFirstEnabled=false but a hosted URL exists: CDN-first plays the URL", async () => {
|
|
const service = new PlaybackService({
|
|
logger: () => {},
|
|
urlFirstEnabled: false,
|
|
isValidMagnetUri: () => true,
|
|
});
|
|
|
|
const probeUrl = mock.fn(async () => ({ outcome: "good", status: 200 }));
|
|
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
|
|
|
|
const session = service.createSession({
|
|
url: "https://example.com/vid.mp4",
|
|
magnet: "magnet:?xt=urn:btih:123",
|
|
videoElement: video,
|
|
probeUrl,
|
|
playViaWebTorrent,
|
|
});
|
|
|
|
const startPromise = session.start();
|
|
await new Promise((resolve) => process.nextTick(resolve));
|
|
await new Promise((resolve) => process.nextTick(resolve));
|
|
video.dispatchEvent(new window.Event("playing"));
|
|
const result = await startPromise;
|
|
|
|
assert.equal(
|
|
playViaWebTorrent.mock.callCount(),
|
|
0,
|
|
"Should NOT use torrent when a hosted URL exists",
|
|
);
|
|
assert.equal(probeUrl.mock.callCount(), 1, "Should probe the URL first");
|
|
assert.equal(result.source, "url");
|
|
});
|
|
|
|
test("urlFirstEnabled=false with NO hosted URL: uses WebTorrent", async () => {
|
|
const service = new PlaybackService({
|
|
logger: () => {},
|
|
urlFirstEnabled: false,
|
|
isValidMagnetUri: () => true,
|
|
});
|
|
|
|
const probeUrl = mock.fn(async () => ({ outcome: "good", status: 200 }));
|
|
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
|
|
|
|
const session = service.createSession({
|
|
url: "",
|
|
magnet: "magnet:?xt=urn:btih:123",
|
|
videoElement: video,
|
|
probeUrl,
|
|
playViaWebTorrent,
|
|
});
|
|
|
|
const result = await session.start();
|
|
|
|
assert.equal(
|
|
playViaWebTorrent.mock.callCount(),
|
|
1,
|
|
"Should use WebTorrent when there is no URL to prefer",
|
|
);
|
|
assert.equal(probeUrl.mock.callCount(), 0, "No URL to probe");
|
|
assert.equal(result.source, "torrent");
|
|
});
|
|
|
|
test("forcedSource=torrent overrides urlFirstEnabled=true", async () => {
|
|
const service = new PlaybackService({
|
|
logger: () => {},
|
|
urlFirstEnabled: true,
|
|
isValidMagnetUri: () => true,
|
|
});
|
|
|
|
const probeUrl = mock.fn();
|
|
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
|
|
|
|
const session = service.createSession({
|
|
url: "https://example.com/vid.mp4",
|
|
magnet: "magnet:?xt=urn:btih:123",
|
|
videoElement: video,
|
|
probeUrl,
|
|
playViaWebTorrent,
|
|
forcedSource: "torrent"
|
|
});
|
|
|
|
const result = await session.start();
|
|
|
|
assert.equal(playViaWebTorrent.mock.callCount(), 1);
|
|
assert.equal(probeUrl.mock.callCount(), 0);
|
|
assert.equal(result.source, "torrent");
|
|
});
|
|
});
|
|
});
|