feat(web): add shuffle, repeat, and audio-event reporting to player store
toggleShuffle (Fisher-Yates over queue[index+1..end] with injectable RNG for test determinism), cycleRepeat (off → all → one → off), reportStateFromAudio with full repeat-mode semantics on 'ended', plus simple state mirroring for playing/paused/waiting/error. Extends skipNext to wrap when repeat='all'.
This commit is contained in:
@@ -79,8 +79,16 @@ export function skipNext(): void {
|
||||
_position = 0;
|
||||
_duration = 0;
|
||||
_state = 'loading';
|
||||
return;
|
||||
}
|
||||
// At end of queue.
|
||||
if (_repeat === 'all') {
|
||||
_index = 0;
|
||||
_position = 0;
|
||||
_duration = 0;
|
||||
_state = 'loading';
|
||||
} else {
|
||||
// At end: no-repeat-aware logic yet (Task 2 extends this).
|
||||
// 'off' or 'one' both pause at end on EXPLICIT skip.
|
||||
_state = 'paused';
|
||||
_position = _duration;
|
||||
}
|
||||
@@ -118,3 +126,73 @@ export function reportTimeUpdate(sec: number): void {
|
||||
export function reportDuration(sec: number): void {
|
||||
_duration = Number.isFinite(sec) ? sec : 0;
|
||||
}
|
||||
|
||||
// --- Shuffle (with injectable RNG for tests) ---
|
||||
|
||||
let _rng: () => number = Math.random;
|
||||
export function __setShuffleRng(fn: () => number): void { _rng = fn; }
|
||||
|
||||
export function toggleShuffle(): void {
|
||||
_shuffle = !_shuffle;
|
||||
if (_shuffle && _index + 1 < _queue.length) {
|
||||
// Fisher-Yates shuffle over _queue[_index+1 .. end].
|
||||
const arr = _queue.slice();
|
||||
for (let j = arr.length - 1; j > _index; j--) {
|
||||
const r = Math.floor(_rng() * (j - _index)) + _index + 1;
|
||||
[arr[j], arr[r]] = [arr[r], arr[j]];
|
||||
}
|
||||
_queue = arr;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Repeat ---
|
||||
|
||||
export function cycleRepeat(): void {
|
||||
_repeat = _repeat === 'off' ? 'all' : _repeat === 'all' ? 'one' : 'off';
|
||||
}
|
||||
|
||||
export function reportStateFromAudio(
|
||||
event: 'playing' | 'paused' | 'waiting' | 'ended' | 'error',
|
||||
detail?: string
|
||||
): void {
|
||||
switch (event) {
|
||||
case 'playing':
|
||||
_state = 'playing';
|
||||
_error = null;
|
||||
return;
|
||||
case 'paused':
|
||||
_state = 'paused';
|
||||
return;
|
||||
case 'waiting':
|
||||
_state = 'loading';
|
||||
return;
|
||||
case 'error':
|
||||
_state = 'error';
|
||||
_error = detail ?? 'Playback failed.';
|
||||
return;
|
||||
case 'ended':
|
||||
if (_repeat === 'one') {
|
||||
_position = 0;
|
||||
_state = 'loading';
|
||||
if (_audioEl) _audioEl.currentTime = 0;
|
||||
return;
|
||||
}
|
||||
if (_index + 1 < _queue.length) {
|
||||
_index++;
|
||||
_position = 0;
|
||||
_duration = 0;
|
||||
_state = 'loading';
|
||||
return;
|
||||
}
|
||||
if (_repeat === 'all') {
|
||||
_index = 0;
|
||||
_position = 0;
|
||||
_duration = 0;
|
||||
_state = 'loading';
|
||||
} else {
|
||||
_state = 'paused';
|
||||
_position = _duration;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user