init
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import { state } from '../core/state.js';
|
||||
import { logTts, prefetchAudio, speak, stopAllAudio } from '../services/tts.js';
|
||||
|
||||
let autoPlayActive = false;
|
||||
let autoPlayTimer = null;
|
||||
|
||||
export function toggleAutoPlay() {
|
||||
if (autoPlayActive) stopAutoPlay();
|
||||
else startAutoPlay();
|
||||
}
|
||||
|
||||
export function startAutoPlay() {
|
||||
const rows = document.querySelectorAll('.word-table tbody tr[data-word-id]');
|
||||
const wordIds = Array.from(rows).map(row => Number.parseInt(row.dataset.wordId, 10));
|
||||
if (wordIds.length === 0) return;
|
||||
|
||||
autoPlayActive = true;
|
||||
updateAutoPlayBtn();
|
||||
prefetchWordListAudio(wordIds);
|
||||
playWordSequence(wordIds, 0);
|
||||
}
|
||||
|
||||
export function prefetchWordListAudio(wordIds) {
|
||||
const texts = wordIds
|
||||
.map(id => state.words.find(word => word.id === id)?.english)
|
||||
.filter(Boolean);
|
||||
if (!texts.length) return;
|
||||
void prefetchAudio(texts, 0.92, { allowCloud: true })
|
||||
.catch(error => logTts('word list audio prefetch failed', error));
|
||||
}
|
||||
|
||||
export function playWordSequence(wordIds, index) {
|
||||
if (!autoPlayActive || index >= wordIds.length) {
|
||||
stopAutoPlay();
|
||||
return;
|
||||
}
|
||||
|
||||
const word = state.words.find(item => item.id === wordIds[index]);
|
||||
if (!word) {
|
||||
playWordSequence(wordIds, index + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelectorAll('.word-table tbody tr').forEach(row => row.classList.remove('is-playing'));
|
||||
const row = document.querySelector(`tr[data-word-id="${word.id}"]`);
|
||||
if (row) {
|
||||
row.classList.add('is-playing');
|
||||
let shouldScroll = true;
|
||||
if (state.autoPlayScrollMode === 'visible') {
|
||||
const rect = row.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const header = document.getElementById('mobile-header');
|
||||
const topBound = header ? header.getBoundingClientRect().height : 0;
|
||||
shouldScroll = rect.top < topBound || rect.bottom > viewportHeight;
|
||||
}
|
||||
if (shouldScroll) {
|
||||
row.scrollIntoView({
|
||||
behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const repeatCount = Math.max(1, state.autoPlayRepeat || 1);
|
||||
const repeatDelay = state.autoPlayRepeatDelay || 1000;
|
||||
const wordInterval = state.autoPlayInterval || 2500;
|
||||
let spoken = 0;
|
||||
|
||||
async function playCurrentWord() {
|
||||
if (!autoPlayActive) return;
|
||||
await speak(word.english);
|
||||
if (!autoPlayActive) return;
|
||||
spoken++;
|
||||
if (spoken < repeatCount) {
|
||||
autoPlayTimer = setTimeout(playCurrentWord, repeatDelay);
|
||||
} else {
|
||||
autoPlayTimer = setTimeout(() => playWordSequence(wordIds, index + 1), wordInterval);
|
||||
}
|
||||
}
|
||||
|
||||
void playCurrentWord();
|
||||
}
|
||||
|
||||
export function stopAutoPlay() {
|
||||
autoPlayActive = false;
|
||||
if (autoPlayTimer) {
|
||||
clearTimeout(autoPlayTimer);
|
||||
autoPlayTimer = null;
|
||||
}
|
||||
stopAllAudio();
|
||||
document.querySelectorAll('.word-table tbody tr').forEach(row => row.classList.remove('is-playing'));
|
||||
updateAutoPlayBtn();
|
||||
}
|
||||
|
||||
export function updateAutoPlayBtn() {
|
||||
const button = document.getElementById('auto-play-btn');
|
||||
if (!button) return;
|
||||
if (autoPlayActive) {
|
||||
button.innerHTML = '<i class="fas fa-stop"></i> 停止播放';
|
||||
button.className = 'btn btn-error';
|
||||
} else {
|
||||
button.innerHTML = '<i class="fas fa-play"></i> 自动播放';
|
||||
button.className = 'btn btn-secondary';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user