MPD and Scene fixes + twitch-bot cleanup

This commit is contained in:
Jakub Zych
2026-04-28 12:13:42 +02:00
parent 4c818e327b
commit 32be7e4073
16 changed files with 858 additions and 972 deletions

View File

@@ -376,6 +376,7 @@ body {
const cdMin = data.countdownMin ?? 5;
const PROMPT = 'OPHI-118://> ';
const MODE_KEYS = ['repeat', 'random', 'single'];
const rigName = (window.__TEL?.rig || 'unknown').toUpperCase();
const lines = [
{ kind: 'cmd', text: 'loadproject --manifest' },
@@ -434,11 +435,36 @@ body {
await sleep(280);
}
function normalizePlayback(playback) {
const src = playback && typeof playback === 'object' ? playback : {};
const normalized = {};
for (const key of MODE_KEYS) normalized[key] = !!src[key];
normalized.consume = !!src.consume;
normalized.volume = Number.isFinite(src.volume) ? src.volume : null;
return normalized;
}
function formatMusicCommand(playback) {
const p = normalizePlayback(playback);
const parts = ['music', '--boot'];
if (p.volume !== null) parts.push(`--volume=${p.volume}%`);
MODE_KEYS.forEach(key => {
if (p[key]) parts.push(`--${key}`);
});
if (p.consume) parts.push('--consume');
return parts.join(' ');
}
// ── Music: passive listener; daemon (music/index.html) plays the audio ──
// Track count comes from playlist.js; current track + progress arrive over
// OBS WebSocket as 'mpd:state' broadcasts from the daemon.
const playlistData = window.__PLAYLIST || { tracks: [] };
const allTracks = playlistData.tracks || [];
let bootStarted = false;
let bootFinished = false;
let lastPlaybackModes = null;
let pendingState = null;
let modeLogChain = Promise.resolve();
let npLine = null;
function ensureNowPlayingLine() {
@@ -467,6 +493,59 @@ body {
return `${pad(Math.floor(s / 60))}:${pad(Math.floor(s % 60))}`;
}
async function logModeChange(mode, enabled) {
await typeCommand(`music --${mode}=${enabled ? 'true' : 'false'}`);
logBeforeNp(`[audio] ${mode} mode ${enabled ? 'enabled' : 'disabled'}`, 'term-out');
}
function trackModeChanges(playback) {
const current = normalizePlayback(playback);
if (!lastPlaybackModes) {
lastPlaybackModes = current;
return;
}
MODE_KEYS.forEach(key => {
if (current[key] !== lastPlaybackModes[key]) {
modeLogChain = modeLogChain.then(() => logModeChange(key, current[key]));
}
});
lastPlaybackModes = current;
}
function renderMusicState(s) {
if (!npLine) ensureNowPlayingLine();
if (lastTrackIndex !== null && s.index !== lastTrackIndex) {
logBeforeNp(`[audio] next: ${s.title || '?'}`);
}
lastTrackIndex = s.index;
npLine.querySelector('.np-title').textContent = s.title || '—';
npLine.querySelector('.np-time').textContent =
`[${fmtClock(s.currentTime)} / ${fmtClock(s.duration)}]`;
}
async function bootFromState(s) {
if (bootStarted) return;
bootStarted = true;
lastPlaybackModes = normalizePlayback(s.playback);
await typeCommand(formatMusicCommand(s.playback));
if (allTracks.length === 0) {
append('> ', '[audio] no tracks queued — run scripts/playlist.sh', 'term-dim');
bootFinished = true;
return;
}
append('> ', `[audio] indexed ${allTracks.length} tracks`, 'term-out');
await sleep(180);
append('> ', '[audio] connecting to daemon on ws://localhost:4455', 'term-out');
await sleep(180);
append('> ', '[audio] subscribing to mpd:state events', 'term-out');
await sleep(180);
append('> ', '[audio] standby for transmission ✓', 'term-out');
bootFinished = true;
if (pendingState) renderMusicState(pendingState);
}
// Connect to OBS WebSocket and listen for daemon broadcasts.
let lastTrackIndex = null;
async function connectMusic() {
@@ -482,15 +561,14 @@ body {
setTimeout(connectMusic, 2000);
});
obs.onCustom('mpd:state', (s) => {
if (!npLine) ensureNowPlayingLine();
// Detect track change (don't log on first state, just initialize).
if (lastTrackIndex !== null && s.index !== lastTrackIndex) {
logBeforeNp(`[audio] next: ${s.title || '?'}`);
pendingState = s;
if (!bootStarted) {
bootFromState(s);
return;
}
lastTrackIndex = s.index;
npLine.querySelector('.np-title').textContent = s.title || '—';
npLine.querySelector('.np-time').textContent =
`[${fmtClock(s.currentTime)} / ${fmtClock(s.duration)}]`;
if (!bootFinished) return;
renderMusicState(s);
trackModeChanges(s.playback);
});
} catch (e) {
logBeforeNp(`[audio] daemon unreachable (${e.message}) — retrying`, 'term-dim');
@@ -500,21 +578,7 @@ body {
async function startMusic() {
await sleep(500);
await typeCommand('mpd --queue ~/playlists/stream-mix');
if (allTracks.length === 0) {
append('> ', '[audio] no tracks queued — run scripts/playlist.sh', 'term-dim');
return;
}
append('> ', `[audio] indexed ${allTracks.length} tracks`, 'term-out');
await sleep(180);
append('> ', '[audio] connecting to daemon on ws://localhost:4455', 'term-out');
await sleep(180);
append('> ', '[audio] subscribing to mpd:state events', 'term-out');
await sleep(220);
ensureNowPlayingLine();
append('', '[audio] waiting for mpd state', 'term-dim');
connectMusic();
}