This commit is contained in:
2026-01-14 19:31:39 +01:00
parent 6dd2025d3d
commit 0541b0b776
2 changed files with 44 additions and 6 deletions

View File

@@ -76,10 +76,13 @@ rl.on('line', (line) => {
function play(ip, url, metadata) { function play(ip, url, metadata) {
if (activeClient) { if (activeClient) {
try { activeClient.removeAllListeners(); } catch (e) { }
try { activeClient.close(); } catch (e) { } try { activeClient.close(); } catch (e) { }
} }
activeClient = new Client(); activeClient = new Client();
// Increase max listeners for this client instance to avoid Node warnings
try { if (typeof activeClient.setMaxListeners === 'function') activeClient.setMaxListeners(50); } catch (e) {}
activeClient._playMetadata = metadata || {}; activeClient._playMetadata = metadata || {};
activeClient.connect(ip, () => { activeClient.connect(ip, () => {
@@ -109,7 +112,10 @@ function play(ip, url, metadata) {
// Join can fail if the session is stale; stop it and retry launch. // Join can fail if the session is stale; stop it and retry launch.
stopSessions(activeClient, [session], () => launchPlayer(url, activeClient._playMetadata, /*didStopFirst*/ true)); stopSessions(activeClient, [session], () => launchPlayer(url, activeClient._playMetadata, /*didStopFirst*/ true));
} else { } else {
// Clean up previous player listeners before replacing
try { if (activePlayer && typeof activePlayer.removeAllListeners === 'function') activePlayer.removeAllListeners(); } catch (e) {}
activePlayer = player; activePlayer = player;
try { if (typeof activePlayer.setMaxListeners === 'function') activePlayer.setMaxListeners(50); } catch (e) {}
loadMedia(url, activeClient._playMetadata); loadMedia(url, activeClient._playMetadata);
} }
}); });
@@ -154,7 +160,9 @@ function launchPlayer(url, metadata, didStopFirst) {
try { error(`Launch retry error full: ${JSON.stringify(retryErr)}`); } catch (e) { /* ignore */ } try { error(`Launch retry error full: ${JSON.stringify(retryErr)}`); } catch (e) { /* ignore */ }
return; return;
} }
try { if (activePlayer && typeof activePlayer.removeAllListeners === 'function') activePlayer.removeAllListeners(); } catch (e) {}
activePlayer = retryPlayer; activePlayer = retryPlayer;
try { if (typeof activePlayer.setMaxListeners === 'function') activePlayer.setMaxListeners(50); } catch (e) {}
loadMedia(url, metadata); loadMedia(url, metadata);
}); });
}); });
@@ -166,7 +174,9 @@ function launchPlayer(url, metadata, didStopFirst) {
try { error(`Launch error full: ${JSON.stringify(err)}`); } catch (e) { /* ignore */ } try { error(`Launch error full: ${JSON.stringify(err)}`); } catch (e) { /* ignore */ }
return; return;
} }
try { if (activePlayer && typeof activePlayer.removeAllListeners === 'function') activePlayer.removeAllListeners(); } catch (e) {}
activePlayer = player; activePlayer = player;
try { if (typeof activePlayer.setMaxListeners === 'function') activePlayer.setMaxListeners(50); } catch (e) {}
loadMedia(url, metadata); loadMedia(url, metadata);
}); });
} }
@@ -175,19 +185,41 @@ function loadMedia(url, metadata) {
if (!activePlayer) return; if (!activePlayer) return;
const meta = metadata || {}; const meta = metadata || {};
// Build a richer metadata payload. Many receivers only honor specific
// fields; we set both Music metadata and generic hints via `customData`.
const media = { const media = {
contentId: url, contentId: url,
contentType: 'audio/mpeg', contentType: 'audio/mpeg',
streamType: 'LIVE', streamType: 'LIVE',
metadata: { metadata: {
// Use MusicTrack metadata (common on audio receivers) but include
// a subtitle field in case receivers surface it.
metadataType: 3, // MusicTrackMediaMetadata metadataType: 3, // MusicTrackMediaMetadata
title: meta.title || 'Radio Station', title: meta.title || 'Radio Station',
albumName: 'Radio Player', albumName: 'Radio Player',
artist: meta.artist || meta.station || '', artist: meta.artist || meta.subtitle || meta.station || '',
images: meta.image ? [{ url: meta.image }] : [] subtitle: meta.subtitle || '',
images: (meta.image ? [
{ url: meta.image },
// also include a large hint for receivers that prefer big artwork
{ url: meta.image, width: 1920, height: 1080 }
] : [])
},
// Many receivers ignore `customData`, but some Styled receivers will
// use it. Include background and theming hints here.
customData: {
appName: meta.appName || 'Radio Player',
backgroundImage: meta.backgroundImage || meta.image || undefined,
backgroundGradient: meta.bgGradient || '#6a0dad',
themeHint: {
primary: '#6a0dad',
accent: '#b36cf3'
}
} }
}; };
// Ensure we don't accumulate 'status' listeners across loads
try { if (activePlayer && typeof activePlayer.removeAllListeners === 'function') activePlayer.removeAllListeners('status'); } catch (e) {}
activePlayer.load(media, { autoplay: true }, (err, status) => { activePlayer.load(media, { autoplay: true }, (err, status) => {
if (err) return error(`Load error: ${err.message}`); if (err) return error(`Load error: ${err.message}`);
log('Media loaded, playing...'); log('Media loaded, playing...');
@@ -201,9 +233,11 @@ function loadMedia(url, metadata) {
function stop() { function stop() {
if (activePlayer) { if (activePlayer) {
try { activePlayer.stop(); } catch (e) { } try { activePlayer.stop(); } catch (e) { }
try { if (typeof activePlayer.removeAllListeners === 'function') activePlayer.removeAllListeners(); } catch (e) {}
log('Stopped playback'); log('Stopped playback');
} }
if (activeClient) { if (activeClient) {
try { if (typeof activeClient.removeAllListeners === 'function') activeClient.removeAllListeners(); } catch (e) {}
try { activeClient.close(); } catch (e) { } try { activeClient.close(); } catch (e) { }
activeClient = null; activeClient = null;
activePlayer = null; activePlayer = null;

View File

@@ -1268,7 +1268,11 @@ async function play() {
url: castUrl, url: castUrl,
title: station.title || 'Radio', title: station.title || 'Radio',
artist: station.slogan || undefined, artist: station.slogan || undefined,
image: station.logo || undefined image: station.logo || undefined,
// Additional metadata hints for receivers
subtitle: station.slogan || station.name,
backgroundImage: station.background || station.logo || undefined,
bgGradient: station.bgGradient || 'linear-gradient(135deg,#5b2d91,#b36cf3)'
}); });
isPlaying = true; isPlaying = true;
// Sync volume // Sync volume