Better socket handling, loadfile api update for mpv 0.38.0

This commit is contained in:
2025-05-30 16:40:20 -07:00
parent 035d74d412
commit 3552c9c476

View File

@@ -38,8 +38,9 @@ enum UserEvent {
} }
export class MediaPlayer { export class MediaPlayer {
private playerProcess: ChildProcess; private playerProcess: ChildProcess | null = null;
private socket: Socket; private socket: Promise<Socket>;
private eventSubscribers: WebSocket[] = []; private eventSubscribers: WebSocket[] = [];
private favoritesStore: FavoritesStore; private favoritesStore: FavoritesStore;
@@ -49,9 +50,19 @@ export class MediaPlayer {
private metadata: Map<string, LinkMetadata> = new Map(); private metadata: Map<string, LinkMetadata> = new Map();
constructor() { constructor() {
this.socket = this.tryRespawnPlayerProcess();
this.favoritesStore = new FavoritesStore();
this.favoritesStore.onFavoritesChanged = (favorites) => {
this.handleEvent(UserEvent.FavoritesUpdate, { favorites });
};
}
private tryRespawnPlayerProcess(): Promise<Socket> {
const socketFilename = Math.random().toString(36).substring(2, 10); const socketFilename = Math.random().toString(36).substring(2, 10);
const socketPath = `/tmp/mpv-${socketFilename}`; const socketPath = `/tmp/mpv-${socketFilename}`;
const enableVideo = process.env.ENABLE_VIDEO || false; const enableVideo = process.env.ENABLE_VIDEO || false;
const logfilePath = `/tmp/mpv-logfile.txt`;
console.log("Starting player process (video: " + (enableVideo ? "enabled" : "disabled") + ")"); console.log("Starting player process (video: " + (enableVideo ? "enabled" : "disabled") + ")");
this.playerProcess = spawn("mpv", [ this.playerProcess = spawn("mpv", [
@@ -59,22 +70,26 @@ export class MediaPlayer {
"--fullscreen", "--fullscreen",
"--no-terminal", "--no-terminal",
"--idle=yes", "--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<Socket>(resolve => {
socketReady = resolve;
});
this.playerProcess.on("spawn", () => { this.playerProcess.on("spawn", () => {
console.log(`Player process spawned, opening socket @ ${socketPath}`); console.log(`Player process spawned, opening socket @ ${socketPath}`);
setTimeout(() => { setTimeout(() => {
this.connectToSocket(socketPath); let socket = this.connectToSocket(socketPath);
socketReady(socket);
}, 500); }, 500);
}); });
this.favoritesStore = new FavoritesStore(); return socketPromise;
this.favoritesStore.onFavoritesChanged = (favorites) => {
this.handleEvent(UserEvent.FavoritesUpdate, { favorites });
};
} }
public async getPlaylist(): Promise<PlaylistItem[]> { public async getPlaylist(): Promise<PlaylistItem[]> {
@@ -247,7 +262,7 @@ export class MediaPlayer {
} }
private async loadFile(url: string, mode: string, fetchMetadata: boolean = true, options: string[] = []) { 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) { if (fetchMetadata) {
this.fetchMetadataAndNotify(url).catch(error => { this.fetchMetadataAndNotify(url).catch(error => {
@@ -266,6 +281,9 @@ export class MediaPlayer {
} }
private async writeCommand(command: string, args: any[]): Promise<any> { private async writeCommand(command: string, args: any[]): Promise<any> {
// Wait for socket to become available.
let socket = await this.socket;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = this.requestId++; const id = this.requestId++;
@@ -274,8 +292,13 @@ export class MediaPlayer {
request_id: id request_id: id
}); });
this.pendingCommands.set(id, { resolve, reject }); try {
this.socket.write(commandObject + '\n'); 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 // Add timeout to prevent hanging promises
setTimeout(() => { setTimeout(() => {
@@ -313,9 +336,12 @@ export class MediaPlayer {
} }
} }
private connectToSocket(path: string) { private connectToSocket(path: string): Socket {
this.socket.connect(path); let socket = new Socket();
this.socket.on("data", data => this.receiveData(data.toString())); socket.connect(path);
socket.on("data", data => this.receiveData(data.toString()));
return socket;
} }
private handleEvent(event: string, data: any) { private handleEvent(event: string, data: any) {