MPD and Scene fixes + twitch-bot cleanup
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user