initial commit
This commit is contained in:
169
src/MediaPlayer.ts
Normal file
169
src/MediaPlayer.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { ChildProcess, spawn } from "child_process";
|
||||
import { Socket } from "net";
|
||||
import { WebSocket } from "ws";
|
||||
|
||||
interface PendingCommand {
|
||||
resolve: (value: any) => void;
|
||||
reject: (reason: any) => void;
|
||||
}
|
||||
|
||||
export class MediaPlayer {
|
||||
private playerProcess: ChildProcess;
|
||||
private socket: Socket;
|
||||
private eventSubscribers: WebSocket[] = [];
|
||||
|
||||
private pendingCommands: Map<number, PendingCommand> = new Map();
|
||||
private requestId: number = 1;
|
||||
private dataBuffer: string = '';
|
||||
|
||||
constructor() {
|
||||
console.log("Starting player process");
|
||||
this.playerProcess = spawn("mpv", [
|
||||
"--no-video",
|
||||
"--no-terminal",
|
||||
"--idle=yes",
|
||||
"--input-ipc-server=/tmp/mpv-socket"
|
||||
]);
|
||||
|
||||
this.socket = new Socket();
|
||||
|
||||
this.playerProcess.on("spawn", () => {
|
||||
console.log("Player process spawned, opening socket");
|
||||
setTimeout(() => {
|
||||
this.connectToSocket();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
public async getPlaylist(): Promise<any> {
|
||||
return this.writeCommand("get_property", ["playlist"])
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
public async getNowPlaying(): Promise<string> {
|
||||
return this.writeCommand("get_property", ["media-title"])
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
public async getPauseState(): Promise<boolean> {
|
||||
return this.writeCommand("get_property", ["pause"])
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
public async getVolume(): Promise<number> {
|
||||
return this.writeCommand("get_property", ["volume"])
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
public async setVolume(volume: number) {
|
||||
return this.writeCommand("set_property", ["volume", volume]);
|
||||
}
|
||||
|
||||
public async getIdle(): Promise<boolean> {
|
||||
return this.writeCommand("get_property", ["idle"])
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
public async append(url: string) {
|
||||
return this.writeCommand("loadfile", [url, "append-play"]);
|
||||
}
|
||||
|
||||
public async play() {
|
||||
return this.writeCommand("set_property", ["pause", false]);
|
||||
}
|
||||
|
||||
public async pause() {
|
||||
return this.writeCommand("set_property", ["pause", true]);
|
||||
}
|
||||
|
||||
public async deletePlaylistItem(index: number) {
|
||||
return this.writeCommand("playlist-remove", [index]);
|
||||
}
|
||||
|
||||
public subscribe(ws: WebSocket) {
|
||||
this.eventSubscribers.push(ws);
|
||||
}
|
||||
|
||||
public unsubscribe(ws: WebSocket) {
|
||||
this.eventSubscribers = this.eventSubscribers.filter(subscriber => subscriber !== ws);
|
||||
}
|
||||
|
||||
private async writeCommand(command: string, args: any[]): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = this.requestId++;
|
||||
|
||||
const commandObject = JSON.stringify({
|
||||
command: [command, ...args],
|
||||
request_id: id
|
||||
});
|
||||
|
||||
this.pendingCommands.set(id, { resolve, reject });
|
||||
this.socket.write(commandObject + '\n');
|
||||
|
||||
// Add timeout to prevent hanging promises
|
||||
setTimeout(() => {
|
||||
if (this.pendingCommands.has(id)) {
|
||||
const pending = this.pendingCommands.get(id);
|
||||
if (pending) {
|
||||
pending.reject(new Error('Command timed out'));
|
||||
this.pendingCommands.delete(id);
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
private connectToSocket() {
|
||||
this.socket.connect("/tmp/mpv-socket");
|
||||
this.socket.on("data", data => this.receiveData(data.toString()));
|
||||
}
|
||||
|
||||
private handleEvent(event: string, data: any) {
|
||||
console.log("Event [" + event + "]: " + JSON.stringify(data, null, 2));
|
||||
|
||||
// Notify all subscribers
|
||||
this.eventSubscribers.forEach(subscriber => {
|
||||
subscriber.send(JSON.stringify({ event, data }));
|
||||
});
|
||||
}
|
||||
|
||||
private receiveData(data: string) {
|
||||
this.dataBuffer += data;
|
||||
|
||||
const lines = this.dataBuffer.split('\n');
|
||||
|
||||
// Keep last incomplete line in the buffer
|
||||
this.dataBuffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim().length > 0) {
|
||||
try {
|
||||
const response = JSON.parse(line);
|
||||
if (response.request_id) {
|
||||
const pending = this.pendingCommands.get(response.request_id);
|
||||
if (pending) {
|
||||
pending.resolve(response);
|
||||
this.pendingCommands.delete(response.request_id);
|
||||
}
|
||||
} else if (response.event) {
|
||||
this.handleEvent(response.event, response.data);
|
||||
} else {
|
||||
console.log(response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user