mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-10 06:43:00 +00:00
[playback] harden WebTorrent streaming + add forced-source test deep links
WebTorrent (handleTorrentStream) hardening:
- Resolve only when playback is actually viable (loadeddata/canplay/playing),
not the instant streamTo() is called. A dead/too-slow swarm is no longer
reported as success, so the caller's fallback can run.
- Pre-playback torrent errors reject (caller falls back); post-playback errors
go to an onPlaybackError hook instead of a dead reject().
- Mid-stream stall watchdog surfaces an onStall hook ("waiting for peers").
- Fix a <video> listener leak: stream listeners are tracked and torn down
before the next stream and on cleanup() (the modal reuses one element).
- Stream the largest decodable file from a multi-file torrent and deselect the
rest (wider ext set: mp4/m4v/webm/mkv/mov/ogv/ogg) so the swarm isn't spent
downloading files we never play.
playbackCoordinator wires onStall/onPlaybackError to user-facing status.
Forced-source deep links (test affordance):
- The main app honors ?v=<nevent>&playback=torrent|url (mirrors the embed
player) to force a playback path; a missing source shows a clear message.
- The video share menu gains "Copy WebTorrent URL" / "Copy CDN URL" items that
copy the forced-source deep link (disabled with a reason when unavailable).
Tests: webtorrent-handlers (resolve-on-readiness, pre/post-ready errors, stall
fires/clears, largest-file select/deselect, no listener accumulation),
playback-forced-source (param normalization), videoMenuRenderers (copy items +
availability). All mutation-verified.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
577ea5af15
commit
4e5bfaa6af
7 files changed
+678
-21
No files matched your search
@@ -3290,6 +3290,35 @@ export class VideoModal {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `?v=<nevent>&playback=<source>` deep link for the given video that
|
||||
* forces a specific playback path (used by the share-menu "Copy WebTorrent
|
||||
* URL" / "Copy CDN URL" items). Returns "" if a link can't be built.
|
||||
* @param {object} video
|
||||
* @param {'torrent'|'url'} playback
|
||||
* @returns {string}
|
||||
*/
|
||||
buildForcedSourceShareUrl(video, playback) {
|
||||
const eventId =
|
||||
video && typeof video.id === "string" ? video.id.trim() : "";
|
||||
if (!eventId) {
|
||||
return "";
|
||||
}
|
||||
const win = this.window || globalThis;
|
||||
const neventEncode = win?.NostrTools?.nip19?.neventEncode;
|
||||
const loc = win?.location;
|
||||
if (typeof neventEncode !== "function" || !loc) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const nevent = neventEncode({ id: eventId });
|
||||
const base = `${loc.origin}${loc.pathname}`;
|
||||
return `${base}?v=${encodeURIComponent(nevent)}&playback=${playback}`;
|
||||
} catch (error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
setupModalSharePopover() {
|
||||
if (!this.playerModal || !this.shareBtn) {
|
||||
return;
|
||||
@@ -3319,6 +3348,8 @@ export class VideoModal {
|
||||
hasSigner: this.shareNostrAuthState.hasSigner,
|
||||
hasMagnet: Boolean(this.modalMoreMenuContext.playbackMagnet), // Re-use magnet from context
|
||||
hasCdn: Boolean(this.modalMoreMenuContext.playbackUrl),
|
||||
buildForcedSourceUrl: (playback) =>
|
||||
this.buildForcedSourceShareUrl(this.activeVideo, playback),
|
||||
});
|
||||
if (panel) {
|
||||
container.appendChild(panel);
|
||||
|
||||
@@ -316,6 +316,13 @@ export function createVideoShareMenuPanel({
|
||||
hasSigner = false,
|
||||
hasMagnet = false,
|
||||
hasCdn = false,
|
||||
// Optional builder for forced-source test deep links: fn('torrent'|'url') ->
|
||||
// a `?v=<nevent>&playback=...` URL (or "" when unavailable). When provided,
|
||||
// the menu offers "Copy WebTorrent URL" / "Copy CDN URL" items that copy a
|
||||
// link which always plays via that source — handy for testing one path
|
||||
// consistently. `clipboard` is injectable for tests.
|
||||
buildForcedSourceUrl = null,
|
||||
clipboard = null,
|
||||
designSystem = null,
|
||||
} = {}) {
|
||||
const doc = resolveDocument(documentRef);
|
||||
@@ -370,6 +377,71 @@ export function createVideoShareMenuPanel({
|
||||
cdnBtn.title = "No CDN link available";
|
||||
}
|
||||
|
||||
// Forced-source test deep links. Self-contained: they copy a link via the
|
||||
// injected builder + clipboard rather than routing through the shared menu
|
||||
// dispatch, so they work regardless of how the share popover is wired.
|
||||
if (typeof buildForcedSourceUrl === "function") {
|
||||
const resolveClipboard = () =>
|
||||
clipboard ||
|
||||
(typeof navigator !== "undefined" ? navigator.clipboard : null);
|
||||
|
||||
const wireForcedCopy = (button, playback, available, unavailableTitle) => {
|
||||
if (!available) {
|
||||
button.disabled = true;
|
||||
button.classList.add("opacity-50", "cursor-not-allowed");
|
||||
button.title = unavailableTitle;
|
||||
return;
|
||||
}
|
||||
const originalText = button.textContent;
|
||||
button.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const url = buildForcedSourceUrl(playback) || "";
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
const clip = resolveClipboard();
|
||||
try {
|
||||
if (clip && typeof clip.writeText === "function") {
|
||||
await clip.writeText(url);
|
||||
}
|
||||
button.textContent = "Copied!";
|
||||
} catch (error) {
|
||||
button.textContent = "Copy failed";
|
||||
}
|
||||
if (typeof setTimeout === "function") {
|
||||
setTimeout(() => {
|
||||
button.textContent = originalText;
|
||||
}, 1200);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const torrentLinkBtn = appendMenuAction(doc, list, {
|
||||
text: "Copy WebTorrent URL",
|
||||
action: "copy-webtorrent-url",
|
||||
dataset: { eventId: metadata.id },
|
||||
});
|
||||
wireForcedCopy(
|
||||
torrentLinkBtn,
|
||||
"torrent",
|
||||
hasMagnet,
|
||||
"No WebTorrent source for this video",
|
||||
);
|
||||
|
||||
const cdnLinkBtn = appendMenuAction(doc, list, {
|
||||
text: "Copy CDN URL",
|
||||
action: "copy-cdn-url",
|
||||
dataset: { eventId: metadata.id },
|
||||
});
|
||||
wireForcedCopy(
|
||||
cdnLinkBtn,
|
||||
"url",
|
||||
hasCdn,
|
||||
"No CDN source for this video",
|
||||
);
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user