frontend: add per-line play/pause/stop controls\n\n- Toggle play/pause on same button, add stop button\n- Maintain shared audio state to prevent overlap and update button states accordingly

This commit is contained in:
Steve White 2025-08-13 00:28:30 -05:00
parent c9593fe6cc
commit 93e0407eac
1 changed files with 80 additions and 27 deletions

View File

@ -3,7 +3,8 @@ import { API_BASE_URL, API_BASE_URL_FOR_FILES } from './config.js';
// Shared per-line audio playback state to prevent overlapping playback
let currentLineAudio = null;
let currentLineAudioBtn = null;
let currentLinePlayBtn = null;
let currentLineStopBtn = null;
// --- Global Inline Notification Helpers --- //
const noticeEl = document.getElementById('global-notice');
@ -396,55 +397,107 @@ async function initializeDialogEditor() {
actionsTd.appendChild(generateBtn);
// --- NEW: Per-line Play button ---
const playBtn = document.createElement('button');
playBtn.innerHTML = '⏵';
playBtn.title = item.audioUrl ? 'Play generated audio' : 'No audio generated yet';
playBtn.className = 'play-line-btn';
playBtn.disabled = !item.audioUrl;
playBtn.onclick = () => {
const playPauseBtn = document.createElement('button');
playPauseBtn.innerHTML = '⏵';
playPauseBtn.title = item.audioUrl ? 'Play' : 'No audio generated yet';
playPauseBtn.className = 'play-line-btn';
playPauseBtn.disabled = !item.audioUrl;
const stopBtn = document.createElement('button');
stopBtn.innerHTML = '⏹';
stopBtn.title = 'Stop';
stopBtn.className = 'stop-line-btn';
stopBtn.disabled = !item.audioUrl;
const setBtnStatesForPlaying = () => {
try {
playPauseBtn.innerHTML = '⏸';
playPauseBtn.title = 'Pause';
stopBtn.disabled = false;
} catch (e) { /* detached */ }
};
const setBtnStatesForPausedOrStopped = () => {
try {
playPauseBtn.innerHTML = '⏵';
playPauseBtn.title = 'Play';
} catch (e) { /* detached */ }
};
const stopCurrent = () => {
if (currentLineAudio) {
try { currentLineAudio.pause(); currentLineAudio.currentTime = 0; } catch (e) { /* noop */ }
}
if (currentLinePlayBtn) {
try { currentLinePlayBtn.innerHTML = '⏵'; currentLinePlayBtn.title = 'Play'; } catch (e) { /* detached */ }
}
if (currentLineStopBtn) {
try { currentLineStopBtn.disabled = true; } catch (e) { /* detached */ }
}
currentLineAudio = null;
currentLinePlayBtn = null;
currentLineStopBtn = null;
};
playPauseBtn.onclick = () => {
if (!item.audioUrl) return;
const audioUrl = item.audioUrl.startsWith('http') ? item.audioUrl : `${API_BASE_URL_FOR_FILES}${item.audioUrl}`;
// If something is already playing
if (currentLineAudio && !currentLineAudio.paused) {
if (currentLineAudioBtn === playBtn) {
// Same line: ignore click to prevent overlapping
// If controlling the same line
if (currentLineAudio && currentLinePlayBtn === playPauseBtn) {
if (currentLineAudio.paused) {
// Resume
currentLineAudio.play().then(() => setBtnStatesForPlaying()).catch(err => {
console.error('Audio resume failed:', err);
showNotice('Could not resume audio.', 'error', { timeout: 2000 });
});
} else {
// Pause
try { currentLineAudio.pause(); } catch (e) { /* noop */ }
setBtnStatesForPausedOrStopped();
}
return;
}
// Stop previous audio and re-enable its button
try {
currentLineAudio.pause();
currentLineAudio.currentTime = 0;
} catch (e) { /* noop */ }
if (currentLineAudioBtn) {
try { currentLineAudioBtn.disabled = false; } catch (e) { /* detached */ }
}
// Switching to a different line: stop previous
if (currentLineAudio) {
stopCurrent();
}
// Start new audio
const audio = new window.Audio(audioUrl);
currentLineAudio = audio;
currentLineAudioBtn = playBtn;
// Disable this play button while playing
playBtn.disabled = true;
currentLinePlayBtn = playPauseBtn;
currentLineStopBtn = stopBtn;
const clearState = () => {
if (currentLineAudio === audio) {
setBtnStatesForPausedOrStopped();
try { stopBtn.disabled = true; } catch (e) { /* detached */ }
currentLineAudio = null;
currentLineAudioBtn = null;
currentLinePlayBtn = null;
currentLineStopBtn = null;
}
try { playBtn.disabled = false; } catch (e) { /* detached */ }
};
audio.addEventListener('ended', clearState, { once: true });
audio.addEventListener('error', clearState, { once: true });
audio.play().catch(err => {
audio.play().then(() => setBtnStatesForPlaying()).catch(err => {
console.error('Audio play failed:', err);
clearState();
showNotice('Could not play audio.', 'error', { timeout: 2000 });
});
};
actionsTd.appendChild(playBtn);
stopBtn.onclick = () => {
// Only acts if this line is the active one
if (currentLineAudio && currentLinePlayBtn === playPauseBtn) {
stopCurrent();
}
};
actionsTd.appendChild(playPauseBtn);
actionsTd.appendChild(stopBtn);
// --- NEW: Settings button for speech items ---
if (item.type === 'speech') {