mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 00:42:30 +00:00
Merge pull request #2294 from PR0M3TH3AN/refactor-playback-strategy-12158012329543805223
Refactor playback strategy logic into PlaybackStrategyService
This commit is contained in:
3 files changed
+422
-267
No files matched your search
+72
-267
@@ -448,290 +448,95 @@ export function createPlaybackCoordinator(deps) {
|
||||
* and falls back to WebTorrent when needed.
|
||||
*/
|
||||
async playVideoWithFallback(options = {}) {
|
||||
const { url = "", magnet = "", trigger, forcedSource } = options || {};
|
||||
const playbackStrategyService = this.playbackStrategyService;
|
||||
if (!playbackStrategyService) {
|
||||
this.showError("Playback strategy service is not available.");
|
||||
return { source: null, error: new Error("Service missing") };
|
||||
}
|
||||
|
||||
emit("playback-decision", {
|
||||
method: forcedSource || (magnet ? "webtorrent" : "url"), // heuristic
|
||||
details: {
|
||||
url: Boolean(url),
|
||||
magnet: Boolean(magnet),
|
||||
forcedSource,
|
||||
return playbackStrategyService.play(options, {
|
||||
getModalVideo: () => this.modalVideo,
|
||||
setModalVideo: (videoElement) => {
|
||||
this.modalVideo = videoElement;
|
||||
},
|
||||
});
|
||||
|
||||
const hasTrigger = Object.prototype.hasOwnProperty.call(
|
||||
options || {},
|
||||
"trigger"
|
||||
);
|
||||
if (hasTrigger) {
|
||||
this.setLastModalTrigger(trigger);
|
||||
}
|
||||
const sanitizedUrl = typeof url === "string" ? url.trim() : "";
|
||||
const trimmedMagnet = typeof magnet === "string" ? magnet.trim() : "";
|
||||
const previousSource = this.playSource || null;
|
||||
const requestSignature = JSON.stringify({
|
||||
url: sanitizedUrl,
|
||||
magnet: trimmedMagnet,
|
||||
forcedSource,
|
||||
});
|
||||
|
||||
const modalVideoIsConnected = (() => {
|
||||
if (!this.modalVideo) {
|
||||
return false;
|
||||
}
|
||||
if (typeof this.modalVideo.isConnected === "boolean") {
|
||||
return this.modalVideo.isConnected;
|
||||
}
|
||||
const ownerDocument = this.modalVideo.ownerDocument ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
if (ownerDocument?.contains) {
|
||||
try {
|
||||
return ownerDocument.contains(this.modalVideo);
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[playVideoWithFallback] Failed to determine modal video connection state",
|
||||
error,
|
||||
);
|
||||
getVideoModalElement: () =>
|
||||
this.videoModal && typeof this.videoModal.getVideoElement === "function"
|
||||
? this.videoModal.getVideoElement()
|
||||
: null,
|
||||
setVideoModalElement: (videoElement) => {
|
||||
if (
|
||||
this.videoModal &&
|
||||
typeof this.videoModal.setVideoElement === "function"
|
||||
) {
|
||||
this.videoModal.setVideoElement(videoElement);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
},
|
||||
cleanupVideoModalPoster: () => {
|
||||
if (
|
||||
this.videoModal &&
|
||||
typeof this.videoModal.clearPosterCleanup === "function"
|
||||
) {
|
||||
this.videoModal.clearPosterCleanup();
|
||||
}
|
||||
},
|
||||
ensureVideoModalReady: (opts) => this.ensureVideoModalReady(opts),
|
||||
teardownVideoElement: (videoEl, opts) =>
|
||||
this.teardownVideoElement(videoEl, opts),
|
||||
applyModalLoadingPoster: () => this.applyModalLoadingPoster(),
|
||||
|
||||
const shouldReuseActiveSession =
|
||||
modalVideoIsConnected &&
|
||||
this.activePlaybackSession &&
|
||||
typeof this.activePlaybackSession.matchesRequestSignature === "function" &&
|
||||
this.activePlaybackSession.matchesRequestSignature(requestSignature);
|
||||
|
||||
if (shouldReuseActiveSession) {
|
||||
this.log(
|
||||
"[playVideoWithFallback] Duplicate playback request detected; reusing active session."
|
||||
);
|
||||
if (this.activePlaybackResultPromise) {
|
||||
return this.activePlaybackResultPromise;
|
||||
}
|
||||
if (typeof this.activePlaybackSession.getResult === "function") {
|
||||
return this.activePlaybackSession.getResult();
|
||||
}
|
||||
return { source: null };
|
||||
}
|
||||
|
||||
await this.waitForCleanup();
|
||||
this.cancelPendingViewLogging();
|
||||
|
||||
if (
|
||||
previousSource === "torrent" &&
|
||||
sanitizedUrl &&
|
||||
this.playbackService &&
|
||||
this.playbackService.torrentClient &&
|
||||
typeof this.playbackService.torrentClient.cleanup === "function"
|
||||
) {
|
||||
try {
|
||||
this.log(
|
||||
"[playVideoWithFallback] Previous playback used WebTorrent; cleaning up before preparing hosted session.",
|
||||
);
|
||||
await this.playbackService.torrentClient.cleanup();
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[playVideoWithFallback] Pre-playback torrent cleanup threw:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let modalVideoEl = this.modalVideo;
|
||||
const modalVideoFromController =
|
||||
this.videoModal && typeof this.videoModal.getVideoElement === "function"
|
||||
? this.videoModal.getVideoElement()
|
||||
: null;
|
||||
if (modalVideoFromController && modalVideoFromController !== modalVideoEl) {
|
||||
modalVideoEl = modalVideoFromController;
|
||||
this.modalVideo = modalVideoEl;
|
||||
}
|
||||
const modalVideoConnected = Boolean(
|
||||
modalVideoEl && modalVideoEl.isConnected,
|
||||
);
|
||||
if (!modalVideoEl || !modalVideoConnected) {
|
||||
try {
|
||||
const { videoElement } = await this.ensureVideoModalReady({
|
||||
ensureVideoElement: true,
|
||||
});
|
||||
modalVideoEl = videoElement;
|
||||
this.modalVideo = modalVideoEl;
|
||||
} catch (error) {
|
||||
this.log(
|
||||
"[playVideoWithFallback] Failed to load video modal before playback:",
|
||||
error
|
||||
);
|
||||
this.showError("Could not prepare the video player. Please try again.");
|
||||
return { source: null, error };
|
||||
}
|
||||
}
|
||||
|
||||
if (!modalVideoEl) {
|
||||
const error = new Error("Video element is not ready for playback.");
|
||||
this.log(
|
||||
"[playVideoWithFallback] Video element missing after modal load attempt."
|
||||
);
|
||||
this.showError("Video player is not ready yet. Please try again.");
|
||||
return { source: null, error };
|
||||
}
|
||||
|
||||
if (
|
||||
this.videoModal &&
|
||||
typeof this.videoModal.clearPosterCleanup === "function"
|
||||
) {
|
||||
try {
|
||||
this.videoModal.clearPosterCleanup();
|
||||
} catch (err) {
|
||||
devLogger.warn(
|
||||
"[playVideoWithFallback] video modal poster cleanup threw:",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const refreshedModal = this.teardownVideoElement(modalVideoEl, {
|
||||
replaceNode: true,
|
||||
});
|
||||
if (refreshedModal) {
|
||||
if (
|
||||
this.videoModal &&
|
||||
typeof this.videoModal.setVideoElement === "function"
|
||||
) {
|
||||
this.videoModal.setVideoElement(refreshedModal);
|
||||
}
|
||||
this.modalVideo = refreshedModal;
|
||||
modalVideoEl = this.modalVideo;
|
||||
this.applyModalLoadingPoster();
|
||||
} else {
|
||||
this.applyModalLoadingPoster();
|
||||
}
|
||||
|
||||
const session = this.playbackService.createSession({
|
||||
url: sanitizedUrl,
|
||||
magnet: trimmedMagnet,
|
||||
requestSignature,
|
||||
videoElement: this.modalVideo,
|
||||
waitForCleanup: () => this.waitForCleanup(),
|
||||
cancelPendingViewLogging: () => this.cancelPendingViewLogging(),
|
||||
clearActiveIntervals: () => this.clearActiveIntervals(),
|
||||
showModalWithPoster: () => this.showModalWithPoster(),
|
||||
teardownVideoElement: (videoEl, options) =>
|
||||
this.teardownVideoElement(videoEl, options),
|
||||
probeUrl: (candidateUrl) => this.probeUrl(candidateUrl),
|
||||
playViaWebTorrent: (magnetUri, options) =>
|
||||
this.playViaWebTorrent(magnetUri, options),
|
||||
playViaWebTorrent: (magnetUri, opts) =>
|
||||
this.playViaWebTorrent(magnetUri, opts),
|
||||
autoplay: () => this.autoplayModalVideo(),
|
||||
|
||||
unsupportedBtihMessage: UNSUPPORTED_BTITH_MESSAGE,
|
||||
forcedSource,
|
||||
});
|
||||
|
||||
this.activePlaybackSession = session;
|
||||
this.activePlaybackResultPromise = null;
|
||||
showError: (msg) => this.showError(msg),
|
||||
setLastModalTrigger: (trigger) => this.setLastModalTrigger(trigger),
|
||||
|
||||
this.resetTorrentStats();
|
||||
this.playSource = null;
|
||||
resetTorrentStats: () => this.resetTorrentStats(),
|
||||
setPlaySource: (source) => {
|
||||
this.playSource = source;
|
||||
},
|
||||
setActivePlaybackSession: (session) => {
|
||||
this.activePlaybackSession = session;
|
||||
},
|
||||
setActivePlaybackResultPromise: (promise) => {
|
||||
this.activePlaybackResultPromise = promise;
|
||||
},
|
||||
|
||||
const playbackConfig = session.getPlaybackConfig();
|
||||
const magnetForPlayback = session.getMagnetForPlayback();
|
||||
const fallbackMagnet = session.getFallbackMagnet();
|
||||
const magnetProvided = session.getMagnetProvided();
|
||||
|
||||
if (this.currentVideo) {
|
||||
this.currentVideo.magnet = magnetForPlayback;
|
||||
this.currentVideo.normalizedMagnet = magnetForPlayback;
|
||||
this.currentVideo.normalizedMagnetFallback = fallbackMagnet;
|
||||
if (playbackConfig?.infoHash && !this.currentVideo.legacyInfoHash) {
|
||||
this.currentVideo.legacyInfoHash = playbackConfig.infoHash;
|
||||
}
|
||||
this.currentVideo.torrentSupported = !!magnetForPlayback;
|
||||
}
|
||||
this.currentMagnetUri = magnetForPlayback || null;
|
||||
// this.setCopyMagnetState(!!magnetForPlayback); // Removed
|
||||
|
||||
const unsubscribers = [];
|
||||
const subscribe = (eventName, handler) => {
|
||||
const off = session.on(eventName, handler);
|
||||
unsubscribers.push(off);
|
||||
};
|
||||
|
||||
subscribe("status", ({ message } = {}) => {
|
||||
if (this.videoModal) {
|
||||
this.videoModal.updateStatus(
|
||||
typeof message === "string" ? message : ""
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("video-prepared", ({ videoElement } = {}) => {
|
||||
if (videoElement && videoElement !== this.modalVideo) {
|
||||
this.modalVideo = videoElement;
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("view-logging-request", ({ videoElement } = {}) => {
|
||||
if (videoElement) {
|
||||
this.preparePlaybackLogging(videoElement);
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("poster-remove", ({ reason } = {}) => {
|
||||
this.forceRemoveModalPoster(reason || "playback");
|
||||
});
|
||||
|
||||
subscribe("sourcechange", ({ source } = {}) => {
|
||||
this.playSource = source || null;
|
||||
const usingTorrent = source === "torrent";
|
||||
if (this.videoModal) {
|
||||
this.videoModal.setTorrentStatsVisibility?.(usingTorrent);
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("error", ({ error, message } = {}) => {
|
||||
const displayMessage =
|
||||
typeof message === "string"
|
||||
? message
|
||||
: error && error.message
|
||||
? `Playback error: ${error.message}`
|
||||
: "Playback error";
|
||||
this.showError(displayMessage);
|
||||
});
|
||||
|
||||
subscribe("finished", () => {
|
||||
if (this.activePlaybackSession === session) {
|
||||
this.activePlaybackSession = null;
|
||||
this.activePlaybackResultPromise = null;
|
||||
}
|
||||
while (unsubscribers.length) {
|
||||
const off = unsubscribers.pop();
|
||||
if (typeof off === "function") {
|
||||
try {
|
||||
off();
|
||||
} catch (err) {
|
||||
devLogger.warn(
|
||||
"[playVideoWithFallback] Listener cleanup error:",
|
||||
err
|
||||
);
|
||||
updateCurrentVideoMetadata: (metadata) => {
|
||||
if (this.currentVideo) {
|
||||
this.currentVideo.magnet = metadata.magnet;
|
||||
this.currentVideo.normalizedMagnet = metadata.normalizedMagnet;
|
||||
this.currentVideo.normalizedMagnetFallback =
|
||||
metadata.normalizedMagnetFallback;
|
||||
if (metadata.legacyInfoHash && !this.currentVideo.legacyInfoHash) {
|
||||
this.currentVideo.legacyInfoHash = metadata.legacyInfoHash;
|
||||
}
|
||||
this.currentVideo.torrentSupported = metadata.torrentSupported;
|
||||
}
|
||||
}
|
||||
this.currentMagnetUri = metadata.magnet || null;
|
||||
},
|
||||
|
||||
updateVideoModalStatus: (message) => {
|
||||
if (this.videoModal) {
|
||||
this.videoModal.updateStatus(message);
|
||||
}
|
||||
},
|
||||
preparePlaybackLogging: (videoElement) =>
|
||||
this.preparePlaybackLogging(videoElement),
|
||||
forceRemoveModalPoster: (reason) => this.forceRemoveModalPoster(reason),
|
||||
setTorrentStatsVisibility: (visible) => {
|
||||
if (this.videoModal) {
|
||||
this.videoModal.setTorrentStatsVisibility?.(visible);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const startPromise = session.start();
|
||||
this.activePlaybackResultPromise = startPromise;
|
||||
const result = await startPromise;
|
||||
|
||||
if (!result || result.error) {
|
||||
return result;
|
||||
}
|
||||
|
||||
emit("playback-started", {
|
||||
method: result.source,
|
||||
details: { startedAt: Date.now() },
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async playVideoByEventId(eventId, playbackHint = {}) {
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
import { emit } from "../embedDiagnostics.js";
|
||||
|
||||
/**
|
||||
* PlaybackStrategyService orchestrates the decision-making process for video playback.
|
||||
* It determines whether to reuse an existing session, manages the lifecycle of the
|
||||
* playback session, and coordinates between the PlaybackService (mechanism) and
|
||||
* the Application (state/UI).
|
||||
*/
|
||||
export class PlaybackStrategyService {
|
||||
constructor({ playbackService, logger }) {
|
||||
this.playbackService = playbackService;
|
||||
this.logger = logger || console;
|
||||
this.activePlaybackSession = null;
|
||||
this.activePlaybackResultPromise = null;
|
||||
this.playSource = null;
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
if (this.logger && typeof this.logger.log === "function") {
|
||||
const prefix = "[PlaybackStrategy]";
|
||||
if (typeof args[0] === "string") {
|
||||
this.logger.log(`${prefix} ${args[0]}`, ...args.slice(1));
|
||||
} else {
|
||||
this.logger.log(prefix, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrates the playback flow.
|
||||
*
|
||||
* @param {object} options - Playback options (url, magnet, trigger, forcedSource).
|
||||
* @param {object} context - Application context providing UI and State access.
|
||||
* @returns {Promise<object>} The result of the playback attempt.
|
||||
*/
|
||||
async play(options = {}, context = {}) {
|
||||
const { url = "", magnet = "", trigger, forcedSource } = options || {};
|
||||
|
||||
emit("playback-decision", {
|
||||
method: forcedSource || (magnet ? "webtorrent" : "url"), // heuristic
|
||||
details: {
|
||||
url: Boolean(url),
|
||||
magnet: Boolean(magnet),
|
||||
forcedSource,
|
||||
},
|
||||
});
|
||||
|
||||
const hasTrigger = Object.prototype.hasOwnProperty.call(options || {}, "trigger");
|
||||
if (hasTrigger && typeof context.setLastModalTrigger === "function") {
|
||||
context.setLastModalTrigger(trigger);
|
||||
}
|
||||
|
||||
const sanitizedUrl = typeof url === "string" ? url.trim() : "";
|
||||
const trimmedMagnet = typeof magnet === "string" ? magnet.trim() : "";
|
||||
const previousSource = this.playSource || null;
|
||||
const requestSignature = JSON.stringify({
|
||||
url: sanitizedUrl,
|
||||
magnet: trimmedMagnet,
|
||||
forcedSource,
|
||||
});
|
||||
|
||||
const modalVideoIsConnected = (() => {
|
||||
const modalVideo = context.getModalVideo ? context.getModalVideo() : null;
|
||||
if (!modalVideo) {
|
||||
return false;
|
||||
}
|
||||
if (typeof modalVideo.isConnected === "boolean") {
|
||||
return modalVideo.isConnected;
|
||||
}
|
||||
const ownerDocument = modalVideo.ownerDocument ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
if (ownerDocument?.contains) {
|
||||
try {
|
||||
return ownerDocument.contains(modalVideo);
|
||||
} catch (error) {
|
||||
this.log("Failed to determine modal video connection state", error);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
|
||||
const shouldReuseActiveSession =
|
||||
modalVideoIsConnected &&
|
||||
this.activePlaybackSession &&
|
||||
typeof this.activePlaybackSession.matchesRequestSignature === "function" &&
|
||||
this.activePlaybackSession.matchesRequestSignature(requestSignature);
|
||||
|
||||
if (shouldReuseActiveSession) {
|
||||
this.log("Duplicate playback request detected; reusing active session.");
|
||||
if (this.activePlaybackResultPromise) {
|
||||
return this.activePlaybackResultPromise;
|
||||
}
|
||||
if (typeof this.activePlaybackSession.getResult === "function") {
|
||||
return this.activePlaybackSession.getResult();
|
||||
}
|
||||
return { source: null };
|
||||
}
|
||||
|
||||
if (typeof context.waitForCleanup === "function") {
|
||||
await context.waitForCleanup();
|
||||
}
|
||||
if (typeof context.cancelPendingViewLogging === "function") {
|
||||
context.cancelPendingViewLogging();
|
||||
}
|
||||
|
||||
if (
|
||||
previousSource === "torrent" &&
|
||||
sanitizedUrl &&
|
||||
this.playbackService &&
|
||||
this.playbackService.torrentClient &&
|
||||
typeof this.playbackService.torrentClient.cleanup === "function"
|
||||
) {
|
||||
try {
|
||||
this.log("Previous playback used WebTorrent; cleaning up before preparing hosted session.");
|
||||
await this.playbackService.torrentClient.cleanup();
|
||||
} catch (error) {
|
||||
this.log("Pre-playback torrent cleanup threw:", error);
|
||||
}
|
||||
}
|
||||
|
||||
let modalVideoEl = context.getModalVideo ? context.getModalVideo() : null;
|
||||
const modalVideoFromController =
|
||||
context.getVideoModalElement ? context.getVideoModalElement() : null;
|
||||
|
||||
if (modalVideoFromController && modalVideoFromController !== modalVideoEl) {
|
||||
modalVideoEl = modalVideoFromController;
|
||||
if (context.setModalVideo) {
|
||||
context.setModalVideo(modalVideoEl);
|
||||
}
|
||||
}
|
||||
|
||||
const modalVideoConnected = Boolean(modalVideoEl && modalVideoEl.isConnected);
|
||||
if (!modalVideoEl || !modalVideoConnected) {
|
||||
try {
|
||||
if (typeof context.ensureVideoModalReady === "function") {
|
||||
const { videoElement } = await context.ensureVideoModalReady({
|
||||
ensureVideoElement: true,
|
||||
});
|
||||
modalVideoEl = videoElement;
|
||||
if (context.setModalVideo) {
|
||||
context.setModalVideo(modalVideoEl);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.log("Failed to load video modal before playback:", error);
|
||||
if (typeof context.showError === "function") {
|
||||
context.showError("Could not prepare the video player. Please try again.");
|
||||
}
|
||||
return { source: null, error };
|
||||
}
|
||||
}
|
||||
|
||||
if (!modalVideoEl) {
|
||||
const error = new Error("Video element is not ready for playback.");
|
||||
this.log("Video element missing after modal load attempt.");
|
||||
if (typeof context.showError === "function") {
|
||||
context.showError("Video player is not ready yet. Please try again.");
|
||||
}
|
||||
return { source: null, error };
|
||||
}
|
||||
|
||||
if (context.cleanupVideoModalPoster) {
|
||||
try {
|
||||
context.cleanupVideoModalPoster();
|
||||
} catch (err) {
|
||||
this.log("video modal poster cleanup threw:", err);
|
||||
}
|
||||
}
|
||||
|
||||
let refreshedModal = null;
|
||||
if (typeof context.teardownVideoElement === "function") {
|
||||
refreshedModal = context.teardownVideoElement(modalVideoEl, {
|
||||
replaceNode: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (refreshedModal) {
|
||||
if (context.setVideoModalElement) {
|
||||
context.setVideoModalElement(refreshedModal);
|
||||
}
|
||||
if (context.setModalVideo) {
|
||||
context.setModalVideo(refreshedModal);
|
||||
}
|
||||
modalVideoEl = refreshedModal; // Ensure we use the new element
|
||||
if (typeof context.applyModalLoadingPoster === "function") {
|
||||
context.applyModalLoadingPoster();
|
||||
}
|
||||
} else {
|
||||
if (typeof context.applyModalLoadingPoster === "function") {
|
||||
context.applyModalLoadingPoster();
|
||||
}
|
||||
}
|
||||
|
||||
// Double check that modalVideoEl is current (it might have been replaced)
|
||||
modalVideoEl = context.getModalVideo ? context.getModalVideo() : modalVideoEl;
|
||||
|
||||
const session = this.playbackService.createSession({
|
||||
url: sanitizedUrl,
|
||||
magnet: trimmedMagnet,
|
||||
requestSignature,
|
||||
videoElement: modalVideoEl,
|
||||
waitForCleanup: context.waitForCleanup,
|
||||
cancelPendingViewLogging: context.cancelPendingViewLogging,
|
||||
clearActiveIntervals: context.clearActiveIntervals,
|
||||
showModalWithPoster: context.showModalWithPoster,
|
||||
teardownVideoElement: context.teardownVideoElement,
|
||||
probeUrl: context.probeUrl,
|
||||
playViaWebTorrent: context.playViaWebTorrent,
|
||||
autoplay: context.autoplay,
|
||||
unsupportedBtihMessage: context.unsupportedBtihMessage,
|
||||
forcedSource,
|
||||
});
|
||||
|
||||
this.activePlaybackSession = session;
|
||||
if (typeof context.setActivePlaybackSession === "function") {
|
||||
context.setActivePlaybackSession(session);
|
||||
}
|
||||
this.activePlaybackResultPromise = null;
|
||||
if (typeof context.setActivePlaybackResultPromise === "function") {
|
||||
context.setActivePlaybackResultPromise(null);
|
||||
}
|
||||
|
||||
if (typeof context.resetTorrentStats === "function") {
|
||||
context.resetTorrentStats();
|
||||
}
|
||||
this.playSource = null;
|
||||
if (typeof context.setPlaySource === "function") {
|
||||
context.setPlaySource(null);
|
||||
}
|
||||
|
||||
const playbackConfig = session.getPlaybackConfig();
|
||||
const magnetForPlayback = session.getMagnetForPlayback();
|
||||
const fallbackMagnet = session.getFallbackMagnet();
|
||||
// const magnetProvided = session.getMagnetProvided();
|
||||
|
||||
if (context.updateCurrentVideoMetadata) {
|
||||
context.updateCurrentVideoMetadata({
|
||||
magnet: magnetForPlayback,
|
||||
normalizedMagnet: magnetForPlayback,
|
||||
normalizedMagnetFallback: fallbackMagnet,
|
||||
legacyInfoHash: playbackConfig?.infoHash || null,
|
||||
torrentSupported: !!magnetForPlayback
|
||||
});
|
||||
}
|
||||
|
||||
// this.currentMagnetUri = magnetForPlayback || null; // Logic is in context if needed
|
||||
|
||||
const unsubscribers = [];
|
||||
const subscribe = (eventName, handler) => {
|
||||
const off = session.on(eventName, handler);
|
||||
unsubscribers.push(off);
|
||||
};
|
||||
|
||||
subscribe("status", ({ message } = {}) => {
|
||||
if (typeof context.updateVideoModalStatus === "function") {
|
||||
context.updateVideoModalStatus(typeof message === "string" ? message : "");
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("video-prepared", ({ videoElement } = {}) => {
|
||||
if (videoElement && videoElement !== (context.getModalVideo ? context.getModalVideo() : null)) {
|
||||
if (context.setModalVideo) {
|
||||
context.setModalVideo(videoElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("view-logging-request", ({ videoElement } = {}) => {
|
||||
if (videoElement && typeof context.preparePlaybackLogging === "function") {
|
||||
context.preparePlaybackLogging(videoElement);
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("poster-remove", ({ reason } = {}) => {
|
||||
if (typeof context.forceRemoveModalPoster === "function") {
|
||||
context.forceRemoveModalPoster(reason || "playback");
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("sourcechange", ({ source } = {}) => {
|
||||
this.playSource = source || null;
|
||||
if (typeof context.setPlaySource === "function") {
|
||||
context.setPlaySource(this.playSource);
|
||||
}
|
||||
const usingTorrent = source === "torrent";
|
||||
if (typeof context.setTorrentStatsVisibility === "function") {
|
||||
context.setTorrentStatsVisibility(usingTorrent);
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("error", ({ error, message } = {}) => {
|
||||
const displayMessage =
|
||||
typeof message === "string"
|
||||
? message
|
||||
: error && error.message
|
||||
? `Playback error: ${error.message}`
|
||||
: "Playback error";
|
||||
if (typeof context.showError === "function") {
|
||||
context.showError(displayMessage);
|
||||
}
|
||||
});
|
||||
|
||||
subscribe("finished", () => {
|
||||
if (this.activePlaybackSession === session) {
|
||||
this.activePlaybackSession = null;
|
||||
if (typeof context.setActivePlaybackSession === "function") {
|
||||
context.setActivePlaybackSession(null);
|
||||
}
|
||||
this.activePlaybackResultPromise = null;
|
||||
if (typeof context.setActivePlaybackResultPromise === "function") {
|
||||
context.setActivePlaybackResultPromise(null);
|
||||
}
|
||||
}
|
||||
while (unsubscribers.length) {
|
||||
const off = unsubscribers.pop();
|
||||
if (typeof off === "function") {
|
||||
try {
|
||||
off();
|
||||
} catch (err) {
|
||||
this.log("Listener cleanup error:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const startPromise = session.start();
|
||||
this.activePlaybackResultPromise = startPromise;
|
||||
if (typeof context.setActivePlaybackResultPromise === "function") {
|
||||
context.setActivePlaybackResultPromise(startPromise);
|
||||
}
|
||||
const result = await startPromise;
|
||||
|
||||
if (!result || result.error) {
|
||||
return result;
|
||||
}
|
||||
|
||||
emit("playback-started", {
|
||||
method: result.source,
|
||||
details: { startedAt: Date.now() },
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
export default PlaybackStrategyService;
|
||||
@@ -6,6 +6,7 @@ import { createWatchHistoryRenderer } from "../historyView.js";
|
||||
import WatchHistoryController from "./watchHistoryController.js";
|
||||
import WatchHistoryTelemetry from "../services/watchHistoryTelemetry.js";
|
||||
import PlaybackService from "../services/playbackService.js";
|
||||
import PlaybackStrategyService from "../services/playbackStrategyService.js";
|
||||
import AuthService from "../services/authService.js";
|
||||
import DiscussionCountService from "../services/discussionCountService.js";
|
||||
import CommentThreadService from "../services/commentThreadService.js";
|
||||
@@ -350,6 +351,10 @@ export default class ApplicationBootstrap {
|
||||
},
|
||||
},
|
||||
});
|
||||
app.playbackStrategyService = new PlaybackStrategyService({
|
||||
playbackService: app.playbackService,
|
||||
logger: devLogger,
|
||||
});
|
||||
app.activePlaybackResultPromise = null;
|
||||
app.activePlaybackSession = null;
|
||||
|
||||
|
||||
Reference in new issue
Block a user