[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:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-18 12:15:27 -04:00
1 parent 577ea5af15
commit 4e5bfaa6af
7 files changed
+678 -21

No files matched your search

+31
View File
@@ -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);