From 3552c9c476069912b6872b2fd5edddd9e981139b Mon Sep 17 00:00:00 2001 From: James Magahern Date: Fri, 30 May 2025 16:40:20 -0700 Subject: [PATCH] Better socket handling, loadfile api update for mpv 0.38.0 --- backend/src/MediaPlayer.ts | 58 +++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/backend/src/MediaPlayer.ts b/backend/src/MediaPlayer.ts index 4e5ba5c..fa3ecb1 100644 --- a/backend/src/MediaPlayer.ts +++ b/backend/src/MediaPlayer.ts @@ -38,8 +38,9 @@ enum UserEvent { } export class MediaPlayer { - private playerProcess: ChildProcess; - private socket: Socket; + private playerProcess: ChildProcess | null = null; + private socket: Promise; + private eventSubscribers: WebSocket[] = []; private favoritesStore: FavoritesStore; @@ -49,9 +50,19 @@ export class MediaPlayer { private metadata: Map = new Map(); constructor() { + this.socket = this.tryRespawnPlayerProcess(); + + this.favoritesStore = new FavoritesStore(); + this.favoritesStore.onFavoritesChanged = (favorites) => { + this.handleEvent(UserEvent.FavoritesUpdate, { favorites }); + }; + } + + private tryRespawnPlayerProcess(): Promise { const socketFilename = Math.random().toString(36).substring(2, 10); const socketPath = `/tmp/mpv-${socketFilename}`; const enableVideo = process.env.ENABLE_VIDEO || false; + const logfilePath = `/tmp/mpv-logfile.txt`; console.log("Starting player process (video: " + (enableVideo ? "enabled" : "disabled") + ")"); this.playerProcess = spawn("mpv", [ @@ -59,22 +70,26 @@ export class MediaPlayer { "--fullscreen", "--no-terminal", "--idle=yes", - "--input-ipc-server=" + socketPath + "--input-ipc-server=" + socketPath, + "--log-file=" + logfilePath, + "--msg-level=all=v" ]); - this.socket = new Socket(); - + + let socketReady!: (s: Socket) => void; + let socketPromise = new Promise(resolve => { + socketReady = resolve; + }); + this.playerProcess.on("spawn", () => { console.log(`Player process spawned, opening socket @ ${socketPath}`); setTimeout(() => { - this.connectToSocket(socketPath); + let socket = this.connectToSocket(socketPath); + socketReady(socket); }, 500); }); - this.favoritesStore = new FavoritesStore(); - this.favoritesStore.onFavoritesChanged = (favorites) => { - this.handleEvent(UserEvent.FavoritesUpdate, { favorites }); - }; + return socketPromise; } public async getPlaylist(): Promise { @@ -247,7 +262,7 @@ export class MediaPlayer { } private async loadFile(url: string, mode: string, fetchMetadata: boolean = true, options: string[] = []) { - this.modify(UserEvent.PlaylistUpdate, () => this.writeCommand("loadfile", [url, mode, options.join(',')])); + this.modify(UserEvent.PlaylistUpdate, () => this.writeCommand("loadfile", [url, mode, "-1", options.join(',')])); if (fetchMetadata) { this.fetchMetadataAndNotify(url).catch(error => { @@ -266,6 +281,9 @@ export class MediaPlayer { } private async writeCommand(command: string, args: any[]): Promise { + // Wait for socket to become available. + let socket = await this.socket; + return new Promise((resolve, reject) => { const id = this.requestId++; @@ -274,8 +292,13 @@ export class MediaPlayer { request_id: id }); - this.pendingCommands.set(id, { resolve, reject }); - this.socket.write(commandObject + '\n'); + try { + this.pendingCommands.set(id, { resolve, reject }); + socket.write(commandObject + '\n'); + } catch (e: any) { + console.error(`Error writing to socket: ${e}. Trying to respawn.`) + this.tryRespawnPlayerProcess(); + } // Add timeout to prevent hanging promises setTimeout(() => { @@ -313,9 +336,12 @@ export class MediaPlayer { } } - private connectToSocket(path: string) { - this.socket.connect(path); - this.socket.on("data", data => this.receiveData(data.toString())); + private connectToSocket(path: string): Socket { + let socket = new Socket(); + socket.connect(path); + socket.on("data", data => this.receiveData(data.toString())); + + return socket; } private handleEvent(event: string, data: any) {