From 1bde92b9749ebc17a6d9099e8a8a74716f96c254 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Fri, 30 May 2025 20:12:53 -0700 Subject: [PATCH] Adds support for zeroconf (Bonjour) --- backend/package.json | 1 + backend/src/MediaPlayer.ts | 42 ++++++++++++++++++++++++++++++++++++ backend/src/server.ts | 6 ++++++ package-lock.json | 44 +++++++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 037a93d..c132da1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@types/node-fetch": "^2.6.12", + "bonjour-service": "^1.3.0", "classnames": "^2.5.1", "express": "^4.21.2", "express-ws": "^5.0.2", diff --git a/backend/src/MediaPlayer.ts b/backend/src/MediaPlayer.ts index fa3ecb1..512fc65 100644 --- a/backend/src/MediaPlayer.ts +++ b/backend/src/MediaPlayer.ts @@ -22,6 +22,7 @@ import { WebSocket } from "ws"; import { getLinkPreview } from "link-preview-js"; import { PlaylistItem, LinkMetadata } from './types'; import { FavoritesStore } from "./FavoritesStore"; +import { Bonjour } from "bonjour-service"; interface PendingCommand { resolve: (value: any) => void; @@ -48,6 +49,7 @@ export class MediaPlayer { private requestId: number = 1; private dataBuffer: string = ''; private metadata: Map = new Map(); + private bonjourInstance: Bonjour | null = null; constructor() { 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 { const socketFilename = Math.random().toString(36).substring(2, 10); const socketPath = `/tmp/mpv-${socketFilename}`; @@ -89,6 +126,11 @@ export class MediaPlayer { }, 500); }); + this.playerProcess.on("error", (error) => { + console.error("Player process error:", error); + console.log("Continuing without mpv player..."); + }); + return socketPromise; } diff --git a/backend/src/server.ts b/backend/src/server.ts index 85148f4..56001dc 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -295,12 +295,18 @@ app.get("*", (req, res) => { const port = process.env.PORT || 3000; const server = app.listen(port, () => { console.log(`Server is running on port ${port}`); + + // Start zeroconf service advertisement + mediaPlayer.startZeroconfService(Number(port)); }); // Add graceful shutdown handling const shutdown = async () => { console.log('Received shutdown signal. Closing server...'); + // Stop zeroconf service + mediaPlayer.stopZeroconfService(); + server.close(() => { console.log('Server closed'); process.exit(0); diff --git a/package-lock.json b/package-lock.json index 6a6e7cd..1469fae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "license": "ISC", "dependencies": { "@types/node-fetch": "^2.6.12", + "bonjour-service": "^1.3.0", "classnames": "^2.5.1", "express": "^4.21.2", "express-ws": "^5.0.2", @@ -1017,6 +1018,11 @@ "@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": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2153,6 +2159,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2636,6 +2651,17 @@ "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -3160,7 +3186,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -4197,6 +4222,18 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "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": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -5145,6 +5182,11 @@ "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": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",