Adds support for zeroconf (Bonjour)

This commit is contained in:
2025-05-30 20:12:53 -07:00
parent 3552c9c476
commit 1bde92b974
4 changed files with 92 additions and 1 deletions

View File

@@ -21,6 +21,7 @@
}, },
"dependencies": { "dependencies": {
"@types/node-fetch": "^2.6.12", "@types/node-fetch": "^2.6.12",
"bonjour-service": "^1.3.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"express": "^4.21.2", "express": "^4.21.2",
"express-ws": "^5.0.2", "express-ws": "^5.0.2",

View File

@@ -22,6 +22,7 @@ import { WebSocket } from "ws";
import { getLinkPreview } from "link-preview-js"; import { getLinkPreview } from "link-preview-js";
import { PlaylistItem, LinkMetadata } from './types'; import { PlaylistItem, LinkMetadata } from './types';
import { FavoritesStore } from "./FavoritesStore"; import { FavoritesStore } from "./FavoritesStore";
import { Bonjour } from "bonjour-service";
interface PendingCommand { interface PendingCommand {
resolve: (value: any) => void; resolve: (value: any) => void;
@@ -48,6 +49,7 @@ export class MediaPlayer {
private requestId: number = 1; private requestId: number = 1;
private dataBuffer: string = ''; private dataBuffer: string = '';
private metadata: Map<string, LinkMetadata> = new Map(); private metadata: Map<string, LinkMetadata> = new Map();
private bonjourInstance: Bonjour | null = null;
constructor() { constructor() {
this.socket = this.tryRespawnPlayerProcess(); this.socket = this.tryRespawnPlayerProcess();
@@ -58,6 +60,41 @@ export class MediaPlayer {
}; };
} }
public startZeroconfService(port: number) {
if (this.bonjourInstance) {
console.log("Zeroconf service already running");
return;
}
this.bonjourInstance = new Bonjour();
const service = this.bonjourInstance.publish({
name: 'QueueCube Media Server',
type: 'queuecube',
port: port,
txt: {
version: '1.0.0',
features: 'playlist,favorites,screenshare'
}
});
service.on('up', () => {
console.log(`Zeroconf service advertised: ${service.name} on port ${port}`);
});
service.on('error', (err: Error) => {
console.error('Zeroconf service error:', err);
});
}
public stopZeroconfService() {
if (this.bonjourInstance) {
this.bonjourInstance.destroy();
this.bonjourInstance = null;
console.log("Zeroconf service stopped");
}
}
private tryRespawnPlayerProcess(): Promise<Socket> { 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}`;
@@ -89,6 +126,11 @@ export class MediaPlayer {
}, 500); }, 500);
}); });
this.playerProcess.on("error", (error) => {
console.error("Player process error:", error);
console.log("Continuing without mpv player...");
});
return socketPromise; return socketPromise;
} }

View File

@@ -295,12 +295,18 @@ app.get("*", (req, res) => {
const port = process.env.PORT || 3000; const port = process.env.PORT || 3000;
const server = app.listen(port, () => { const server = app.listen(port, () => {
console.log(`Server is running on port ${port}`); console.log(`Server is running on port ${port}`);
// Start zeroconf service advertisement
mediaPlayer.startZeroconfService(Number(port));
}); });
// Add graceful shutdown handling // Add graceful shutdown handling
const shutdown = async () => { const shutdown = async () => {
console.log('Received shutdown signal. Closing server...'); console.log('Received shutdown signal. Closing server...');
// Stop zeroconf service
mediaPlayer.stopZeroconfService();
server.close(() => { server.close(() => {
console.log('Server closed'); console.log('Server closed');
process.exit(0); process.exit(0);

44
package-lock.json generated
View File

@@ -21,6 +21,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/node-fetch": "^2.6.12", "@types/node-fetch": "^2.6.12",
"bonjour-service": "^1.3.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"express": "^4.21.2", "express": "^4.21.2",
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
@@ -1017,6 +1018,11 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2153,6 +2159,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bonjour-service": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz",
"integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"multicast-dns": "^7.2.5"
}
},
"node_modules/boolbase": { "node_modules/boolbase": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -2636,6 +2651,17 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/dns-packet": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
"integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==",
"dependencies": {
"@leichtgewicht/ip-codec": "^2.0.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/dom-serializer": { "node_modules/dom-serializer": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -3160,7 +3186,6 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-glob": { "node_modules/fast-glob": {
@@ -4197,6 +4222,18 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/multicast-dns": {
"version": "7.2.5",
"resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
"integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==",
"dependencies": {
"dns-packet": "^5.2.2",
"thunky": "^1.0.2"
},
"bin": {
"multicast-dns": "cli.js"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.8", "version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
@@ -5145,6 +5182,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",