mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
refactor(playback): extract playback helpers to shrink playbackService (1/10)
File-size decomposition (no behavior change). Move the self-contained SimpleEventEmitter and extractWebSeedsFromMagnet (+ its WEBSEED_PARAM_KEYS and the now-sole safeDecodeURIComponent consumer) out of playbackService.js into a new js/services/playbackHelpers.js and import them back. playbackService.js: 1350 -> 1274 lines (now under its original 1289 baseline). Re-recorded the smaller baseline in check-file-size.mjs to lock the reduction. Build + full unit suite green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
c62114557c
commit
76ba750708
3 files changed
+103
-80
No files matched your search
@@ -0,0 +1,98 @@
|
||||
// js/services/playbackHelpers.js
|
||||
//
|
||||
// Small, self-contained helpers extracted from playbackService.js to keep that
|
||||
// orchestrator under the file-size budget. Pure logic with no dependency on the
|
||||
// playback session state.
|
||||
|
||||
import { safeDecodeURIComponent } from "../utils/safeDecode.js";
|
||||
|
||||
/**
|
||||
* Minimal synchronous event emitter used by the playback session to surface
|
||||
* status/fallback/sourcechange events to the coordinator. Errors thrown by a
|
||||
* listener are swallowed (and optionally logged) so one bad listener can't
|
||||
* abort the rest of the playback pipeline.
|
||||
*/
|
||||
export class SimpleEventEmitter {
|
||||
constructor(logger = null) {
|
||||
this.logger = typeof logger === "function" ? logger : null;
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, new Set());
|
||||
}
|
||||
const handlers = this.listeners.get(eventName);
|
||||
handlers.add(handler);
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (!handlers.size) {
|
||||
this.listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName, detail) {
|
||||
const handlers = this.listeners.get(eventName);
|
||||
if (!handlers || !handlers.size) {
|
||||
return;
|
||||
}
|
||||
for (const handler of Array.from(handlers)) {
|
||||
try {
|
||||
handler(detail);
|
||||
} catch (err) {
|
||||
if (this.logger) {
|
||||
this.logger("[PlaybackService] Listener error", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WEBSEED_PARAM_KEYS = new Set(["ws", "webseed"]);
|
||||
|
||||
/**
|
||||
* Extract every web seed URL (`ws=`/`webseed=`) from a magnet's query string,
|
||||
* in order, decoded. Used to seed WebTorrent's urlList from the magnet.
|
||||
*
|
||||
* @param {string} magnetUri
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export const extractWebSeedsFromMagnet = (magnetUri) => {
|
||||
if (typeof magnetUri !== "string") {
|
||||
return [];
|
||||
}
|
||||
const trimmed = magnetUri.trim();
|
||||
if (!trimmed) {
|
||||
return [];
|
||||
}
|
||||
const [withoutFragment] = trimmed.split("#", 1);
|
||||
const [, query = ""] = withoutFragment.split("?", 2);
|
||||
if (!query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const webSeeds = [];
|
||||
for (const segment of query.split("&")) {
|
||||
if (!segment) {
|
||||
continue;
|
||||
}
|
||||
const [rawKey, rawValue = ""] = segment.split("=", 2);
|
||||
if (!rawKey) {
|
||||
continue;
|
||||
}
|
||||
const key = rawKey.trim().toLowerCase();
|
||||
if (!WEBSEED_PARAM_KEYS.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const decodedValue = safeDecodeURIComponent(rawValue.replace(/\+/g, "%20"));
|
||||
const candidate = decodedValue.trim();
|
||||
if (candidate) {
|
||||
webSeeds.push(candidate);
|
||||
}
|
||||
}
|
||||
return webSeeds;
|
||||
};
|
||||
@@ -1,7 +1,10 @@
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import { safeDecodeURIComponent } from "../utils/safeDecode.js";
|
||||
import { PLAYBACK_START_TIMEOUT } from "../constants.js";
|
||||
import { getCurrentVideo, setCurrentVideo } from "../state/appState.js";
|
||||
import {
|
||||
SimpleEventEmitter,
|
||||
extractWebSeedsFromMagnet,
|
||||
} from "./playbackHelpers.js";
|
||||
// js/services/playbackService.js
|
||||
|
||||
/**
|
||||
@@ -21,46 +24,6 @@ import { getCurrentVideo, setCurrentVideo } from "../state/appState.js";
|
||||
* old session is cancelled and doesn't overwrite the new one.
|
||||
*/
|
||||
|
||||
class SimpleEventEmitter {
|
||||
constructor(logger = null) {
|
||||
this.logger = typeof logger === "function" ? logger : null;
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, new Set());
|
||||
}
|
||||
const handlers = this.listeners.get(eventName);
|
||||
handlers.add(handler);
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (!handlers.size) {
|
||||
this.listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName, detail) {
|
||||
const handlers = this.listeners.get(eventName);
|
||||
if (!handlers || !handlers.size) {
|
||||
return;
|
||||
}
|
||||
for (const handler of Array.from(handlers)) {
|
||||
try {
|
||||
handler(detail);
|
||||
} catch (err) {
|
||||
if (this.logger) {
|
||||
this.logger("[PlaybackService] Listener error", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HOSTED_URL_SUCCESS_MESSAGE = "✅ Streaming from hosted URL";
|
||||
const DEFAULT_UNSUPPORTED_BTITH_MESSAGE =
|
||||
"This magnet link is missing a compatible BitTorrent v1 info hash.";
|
||||
@@ -68,44 +31,6 @@ const AUTH_STATUS_CODES = new Set([401, 403]);
|
||||
const SSL_ERROR_PATTERN = /(ssl|cert|certificate)/i;
|
||||
const CORS_ERROR_PATTERN = /cors/i;
|
||||
const PROBE_CACHE_TTL_MS = 45000;
|
||||
const WEBSEED_PARAM_KEYS = new Set(["ws", "webseed"]);
|
||||
|
||||
const extractWebSeedsFromMagnet = (magnetUri) => {
|
||||
if (typeof magnetUri !== "string") {
|
||||
return [];
|
||||
}
|
||||
const trimmed = magnetUri.trim();
|
||||
if (!trimmed) {
|
||||
return [];
|
||||
}
|
||||
const [withoutFragment] = trimmed.split("#", 1);
|
||||
const [, query = ""] = withoutFragment.split("?", 2);
|
||||
if (!query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const webSeeds = [];
|
||||
for (const segment of query.split("&")) {
|
||||
if (!segment) {
|
||||
continue;
|
||||
}
|
||||
const [rawKey, rawValue = ""] = segment.split("=", 2);
|
||||
if (!rawKey) {
|
||||
continue;
|
||||
}
|
||||
const key = rawKey.trim().toLowerCase();
|
||||
if (!WEBSEED_PARAM_KEYS.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const decodedValue = safeDecodeURIComponent(rawValue.replace(/\+/g, "%20"));
|
||||
const candidate = decodedValue.trim();
|
||||
if (candidate) {
|
||||
webSeeds.push(candidate);
|
||||
}
|
||||
}
|
||||
return webSeeds;
|
||||
};
|
||||
|
||||
const getHostedUrlFailureDetails = (probeResult = {}) => {
|
||||
const outcome = probeResult?.outcome || "error";
|
||||
const status = Number.isFinite(probeResult?.status)
|
||||
|
||||
@@ -58,7 +58,7 @@ const GRANDFATHERED = {
|
||||
"js/ui/views/VideoListView.js": 1359,
|
||||
"js/ui/components/UploadModal.js": 1319,
|
||||
"js/ui/components/SimilarContentCard.js": 1303,
|
||||
"js/services/playbackService.js": 1289,
|
||||
"js/services/playbackService.js": 1274,
|
||||
"js/services/authService.js": 1335,
|
||||
"js/ui/zapController.js": 1206,
|
||||
"js/services/r2Service.js": 1194,
|
||||
|
||||
Reference in new issue
Block a user