Better socket handling, loadfile api update for mpv 0.38.0
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user