feat/frontend-phase1 #1
|
@ -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
|
||||
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 */ }
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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') {
|
||||
|
|
Loading…
Reference in New Issue