fix: ensure modal poster clears when playback starts

This commit is contained in:
thePR0M3TH3AN committed 2025-09-24 14:57:11 -04:00
1 parent 80263bdba5
commit eb9b50b572
4 files changed
+87 -19

No files matched your search

+73 -7
View File
@@ -24,6 +24,11 @@ const UNSUPPORTED_BTITH_MESSAGE =
"This magnet link is missing a compatible BitTorrent v1 info hash.";
const FALLBACK_THUMBNAIL_SRC = "assets/jpg/video-thumbnail-fallback.jpg";
// NOTE: The modal uses a "please stand by" animated poster while the torrent
// or direct URL boots up. We've regressed multiple times by forgetting to clear
// that poster once playback starts, which leaves the loading GIF covering the
// video even though audio is playing. Centralising the cleanup logic makes it
// easier to spot those regressions; see forceRemoveModalPoster() below.
const MODAL_LOADING_POSTER = "assets/gif/please-stand-by.gif";
/**
@@ -595,13 +600,10 @@ class bitvidApp {
const videoEl = this.modalVideo;
const clearPoster = () => {
videoEl.removeEventListener("loadeddata", clearPoster);
videoEl.removeEventListener("playing", clearPoster);
this.modalPosterCleanup = null;
videoEl.poster = "";
if (videoEl.hasAttribute("poster")) {
videoEl.removeAttribute("poster");
}
// Delegate to the shared helper so every code path clears the GIF in the
// same way. If we ever change how the poster works we only need to update
// forceRemoveModalPoster().
this.forceRemoveModalPoster("playback-event");
};
videoEl.addEventListener("loadeddata", clearPoster);
@@ -615,6 +617,52 @@ class bitvidApp {
};
}
/**
* Forcefully strip the modal's loading poster.
*
* The video modal shows an animated "please stand by" GIF while we wait for a
* `loadeddata` or `playing` event. Historically, small refactors would remove
* the matching cleanup path which left the GIF covering real playback. By
* routing every caller through this helper we can add defensive clears (for
* example when WebTorrent reports progress) without duplicating poster logic
* all over the file.
*
* @param {string} reason A debugging hint that documents why the poster was
* cleared.
* @returns {boolean} `true` if a poster attribute/value was removed.
*/
forceRemoveModalPoster(reason = "manual-clear") {
if (!this.modalVideo) {
return false;
}
const videoEl = this.modalVideo;
if (typeof this.modalPosterCleanup === "function") {
this.modalPosterCleanup();
this.modalPosterCleanup = null;
}
const hadPoster =
videoEl.hasAttribute("poster") ||
(typeof videoEl.poster === "string" && videoEl.poster !== "");
if (!hadPoster) {
return false;
}
videoEl.poster = "";
if (videoEl.hasAttribute("poster")) {
videoEl.removeAttribute("poster");
}
console.debug(
`[bitvidApp] Cleared modal loading poster (${reason}).`
);
return true;
}
/**
* Initialize the upload modal (upload-modal.html).
*/
@@ -1799,6 +1847,15 @@ class bitvidApp {
return;
}
if (torrent.ready || (typeof torrent.progress === "number" && torrent.progress > 0)) {
// Belt-and-suspenders: if WebTorrent reports progress but the DOM events
// failed to fire we still rip off the loading GIF. This regression has
// bitten us in past releases, so the extra clear is intentional.
this.forceRemoveModalPoster(
torrent.ready ? "torrent-ready-flag" : "torrent-progress"
);
}
// Log only fields that actually exist on the torrent:
console.log("[DEBUG] torrent.progress =", torrent.progress);
console.log("[DEBUG] torrent.numPeers =", torrent.numPeers);
@@ -2139,6 +2196,9 @@ class bitvidApp {
if (this.modalStatus) {
this.modalStatus.textContent = "Streaming from URL";
}
// Defensive clear so the loading GIF never lingers if browsers skip
// firing `playing` (some autoplay policies do this).
this.forceRemoveModalPoster("http-loadeddata");
};
this.modalVideo.addEventListener("loadeddata", markUrlReady, {
@@ -2216,6 +2276,12 @@ class bitvidApp {
cacheBustedMagnet,
this.modalVideo
);
if (torrentInstance && torrentInstance.ready) {
// Some browsers delay `playing` events for MediaSource-backed torrents.
// Clearing the poster here prevents the historic "GIF stuck over the
// video" regression when WebTorrent is already feeding data.
this.forceRemoveModalPoster("webtorrent-ready");
}
this.startTorrentStatusMirrors(torrentInstance);
return torrentInstance;
};
+7
View File
@@ -0,0 +1,7 @@
export const URL_FIRST_ENABLED = true;
export const ACCEPT_LEGACY_V1 = true;
export const WSS_TRACKERS = [
"wss://tracker.openwebtorrent.com",
"wss://tracker.btorrent.xyz",
"wss://tracker.webtorrent.dev",
];
+4 -6
View File
@@ -1,10 +1,8 @@
// js/magnetUtils.js
export const DEFAULT_WSS_TRACKERS = [
"wss://tracker.openwebtorrent.com",
"wss://tracker.btorrent.xyz",
"wss://tracker.fastcast.nz",
];
import { WSS_TRACKERS } from "./constants.js";
export { WSS_TRACKERS };
const HEX_INFO_HASH = /^[0-9a-f]{40}$/i;
const BTIH_PREFIX = "urn:btih:";
@@ -200,7 +198,7 @@ export function normalizeAndAugmentMagnet(
);
const trackerCandidates = [
...DEFAULT_WSS_TRACKERS,
...WSS_TRACKERS,
...extraTrackers,
];
+3 -6
View File
@@ -1,9 +1,6 @@
import assert from "node:assert/strict";
import {
DEFAULT_WSS_TRACKERS,
normalizeAndAugmentMagnet,
safeDecodeMagnet,
} from "../js/magnetUtils.js";
import { WSS_TRACKERS } from "../js/constants.js";
import { normalizeAndAugmentMagnet, safeDecodeMagnet } from "../js/magnetUtils.js";
function getParamValues(magnet, key) {
const parsed = new URL(magnet);
@@ -17,7 +14,7 @@ function getParamValues(magnet, key) {
const xtValues = getParamValues(result.magnet, "xt");
assert.deepEqual(xtValues, [`urn:btih:${infoHash}`]);
const trackerValues = getParamValues(result.magnet, "tr");
for (const tracker of DEFAULT_WSS_TRACKERS) {
for (const tracker of WSS_TRACKERS) {
assert.ok(
trackerValues.includes(tracker),
`Expected tracker ${tracker} to be appended`