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

@@ -359,6 +359,7 @@ body {
// ── Sign-off sequence (terminal output) ──
const PROMPT = 'OPHI-118://> ';
const MODE_KEYS = ['repeat', 'random', 'single'];
const lines = [
{ kind: 'cmd', text: 'unloadproject --flush --persist-state' },
{ kind: 'out', text: 'closing transmission envelope...' },
@@ -425,9 +426,34 @@ 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', '--keep-alive'];
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; shared Music Daemon plays the audio ──
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() {
@@ -456,6 +482,57 @@ 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', 'term-dim');
bootFinished = true;
return;
}
append('> ', `[audio] queue retained: ${allTracks.length} tracks`, '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);
}
let lastTrackIndex = null;
async function connectMusic() {
if (!window.__OBSWS) {
@@ -470,14 +547,14 @@ body {
setTimeout(connectMusic, 2000);
});
obs.onCustom('mpd:state', (s) => {
if (!npLine) ensureNowPlayingLine();
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');
@@ -487,19 +564,7 @@ body {
async function startMusic() {
await sleep(400);
await typeCommand('mpd --keep-alive');
if (allTracks.length === 0) {
append('> ', '[audio] no tracks queued', 'term-dim');
return;
}
append('> ', `[audio] queue retained: ${allTracks.length} tracks`, '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();
}