feat(web): player bar, playable track lists and Collection (M3)
- player bar with cover, like and dislike, previous / play / next, shuffle, repeat, seek bar and volume; PlayerProvider lives with the signed-in shell and restores the last track and position paused - clicking a row (or Enter) plays the list from that track with its context (album, search, history, collection); the current track is highlighted and rows have a like button; a reaction refreshes lists - Collection: history grouped by day with clearing, likes with "play all" and dislikes, all with paging; sidebar section - events and playback state are flushed when the page is hidden; player timers survive StrictMode remounts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec3c2810c8
commit
976ffb574b
15 files changed
+803
-49
No files matched your search
@@ -75,6 +75,8 @@ export class Player {
|
||||
private preloaded: string | null = null;
|
||||
private refreshedFor: string | null = null;
|
||||
private timers: ReturnType<typeof setInterval>[] = [];
|
||||
/** Реакция сохранена на сервере — списки на экране нужно перечитать. */
|
||||
onReaction: (() => void) | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly session: AuthSession,
|
||||
@@ -93,6 +95,10 @@ export class Player {
|
||||
};
|
||||
this.bindAudio();
|
||||
this.bindMediaSession();
|
||||
}
|
||||
|
||||
/** Запуск фоновой отправки событий (парный вызов — dispose). */
|
||||
start() {
|
||||
this.timers.push(setInterval(() => void this.events.flush(), FLUSH_EVERY_MS));
|
||||
}
|
||||
|
||||
@@ -146,7 +152,8 @@ export class Player {
|
||||
void this.load(true, this.state.positionMs);
|
||||
return;
|
||||
}
|
||||
void this.audio.play().catch(() => this.update({ playing: false }));
|
||||
// play() в старых браузерах и jsdom может вернуть undefined вместо промиса.
|
||||
void Promise.resolve(this.audio.play()).catch(() => this.update({ playing: false }));
|
||||
}
|
||||
|
||||
pause() {
|
||||
@@ -238,6 +245,7 @@ export class Player {
|
||||
apply(prev);
|
||||
throw e;
|
||||
}
|
||||
this.onReaction?.();
|
||||
if (reaction === 'dislike' && this.queue.current?.id === track.id) this.next();
|
||||
}
|
||||
|
||||
@@ -521,6 +529,7 @@ export class Player {
|
||||
this.audio.pause();
|
||||
this.audio.removeAttribute('src');
|
||||
for (const t of this.timers) clearInterval(t);
|
||||
this.timers = [];
|
||||
void this.events.flush();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user