fix(web): polish social UI and prevent doubled shared audio
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m20s
release / desktop (Linux и Windows в релиз) (push) Successful in 19m22s
ci / ts / ts (packages + web) (push) Successful in 3m9s
release / release (версия и заметки) (push) Successful in 10s
release / android (APK в релиз) (push) Successful in 4m7s
release / prune (старые файлы и прогоны) (push) Successful in 11s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m20s
release / desktop (Linux и Windows в релиз) (push) Successful in 19m22s
ci / ts / ts (packages + web) (push) Successful in 3m9s
release / release (версия и заметки) (push) Successful in 10s
release / android (APK в релиз) (push) Successful in 4m7s
release / prune (старые файлы и прогоны) (push) Successful in 11s
This commit is contained in:
1 parent
6450027b88
commit
cb22f15912
17 files changed
+405
-63
No files matched your search
@@ -49,6 +49,7 @@ export class Player {
|
||||
private get audio() { return this.engine.audio; }
|
||||
private get overlap() { return this.engine.overlap; }
|
||||
private fading = false;
|
||||
private fadeVersion = 0;
|
||||
private ignoreEnded = false;
|
||||
private readonly queue = new PlayQueue();
|
||||
private readonly events: EventBuffer;
|
||||
@@ -106,7 +107,7 @@ export class Player {
|
||||
if (!this.listening) {
|
||||
this.listening = new ListeningCoordinator(this.session, this.activity.deviceId, {
|
||||
read: this.getState,
|
||||
pause: () => { this.restoreVersion++; this.leaveWave(); this.audio.pause(); },
|
||||
pause: () => { this.restoreVersion++; this.leaveWave(); this.cancelCrossfade(); this.audio.pause(); },
|
||||
apply: (room, position, current) => this.syncListening(room, position, current),
|
||||
changed: listening => this.update({ listening }),
|
||||
});
|
||||
@@ -291,6 +292,7 @@ export class Player {
|
||||
|
||||
pause() {
|
||||
if (this.listening?.locked && !this.applyingSync) return;
|
||||
this.cancelCrossfade();
|
||||
this.audio.pause();
|
||||
}
|
||||
|
||||
@@ -332,6 +334,7 @@ export class Player {
|
||||
const track = this.queue.current;
|
||||
if (!track) return;
|
||||
const to = Math.min(Math.max(ms, 0), track.duration_ms);
|
||||
this.cancelCrossfade();
|
||||
const from = Math.round(this.audio.currentTime * 1000);
|
||||
this.attempt?.seek(to);
|
||||
this.audio.currentTime = to / 1000;
|
||||
@@ -398,6 +401,7 @@ export class Player {
|
||||
// --- загрузка трека ---
|
||||
|
||||
private async load(autoplay: boolean, startMs = 0, current = () => true) {
|
||||
this.cancelCrossfade();
|
||||
const track = this.queue.current;
|
||||
if (!track?.stream_url) return;
|
||||
const version = this.restoreVersion;
|
||||
@@ -484,10 +488,25 @@ export class Player {
|
||||
this.engine.applyLoudness(user * normalizeGain(this.state.track?.features?.loudness_lufs, this.normalizeOn()) * mix);
|
||||
}
|
||||
|
||||
/** A cancelled transition must not keep a second source audible or change the new queue. */
|
||||
private cancelCrossfade() {
|
||||
this.fadeVersion++;
|
||||
const wasFading = this.fading;
|
||||
this.fading = false;
|
||||
this.ignoreEnded = false;
|
||||
this.overlap.pause();
|
||||
this.overlap.volume = 0;
|
||||
if (this.overlap.hasAttribute('src')) {
|
||||
this.overlap.removeAttribute('src');
|
||||
this.overlap.load();
|
||||
}
|
||||
if (wasFading) this.applyLoudness();
|
||||
}
|
||||
|
||||
/** В конце трека накладывает следующий на crossfade_s секунд. */
|
||||
private maybeCrossfade(positionMs: number) {
|
||||
const cf = this.crossfadeSeconds();
|
||||
if (this.fading || cf <= 0 || this.state.repeat === 'one') return;
|
||||
if (this.listening?.locked || this.fading || cf <= 0 || this.state.repeat === 'one') return;
|
||||
const dur = this.audio.duration;
|
||||
if (!Number.isFinite(dur)) return;
|
||||
const left = dur * 1000 - positionMs;
|
||||
@@ -495,15 +514,16 @@ export class Player {
|
||||
const next = this.state.queue[this.state.index + 1];
|
||||
if (!next?.stream_url || !next.available) return;
|
||||
this.fading = true;
|
||||
const version = ++this.fadeVersion;
|
||||
this.offline?.keep([this.queue.current!.id, next.id]);
|
||||
this.overlap.src = this.offline?.local(next).stream_url ?? next.stream_url;
|
||||
this.overlap.volume = 0;
|
||||
void this.overlap.play().catch(() => {
|
||||
this.fading = false;
|
||||
if (version === this.fadeVersion) this.cancelCrossfade();
|
||||
});
|
||||
const started = performance.now();
|
||||
const step = () => {
|
||||
if (!this.fading) return;
|
||||
if (!this.fading || version !== this.fadeVersion) return;
|
||||
const p = Math.min(1, (performance.now() - started) / (cf * 1000));
|
||||
this.applyLoudness(1 - p);
|
||||
this.overlap.volume = Math.min(1, (this.state.muted ? 0 : this.state.volume) * normalizeGain(next.features?.loudness_lufs, this.normalizeOn()) * p);
|
||||
@@ -511,12 +531,12 @@ export class Player {
|
||||
requestAnimationFrame(step);
|
||||
return;
|
||||
}
|
||||
this.finishCrossfade(next);
|
||||
this.finishCrossfade(next, version);
|
||||
};
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
private finishCrossfade(next: Track) {
|
||||
private finishCrossfade(next: Track, version: number) {
|
||||
const at = this.overlap.currentTime;
|
||||
this.ignoreEnded = true;
|
||||
this.audio.pause();
|
||||
@@ -545,6 +565,7 @@ export class Player {
|
||||
this.media.updateMediaMetadata(next);
|
||||
void this.offline?.save(next).catch(() => undefined);
|
||||
void this.audio.play().finally(() => {
|
||||
if (version !== this.fadeVersion) return;
|
||||
this.fading = false;
|
||||
this.ignoreEnded = false;
|
||||
});
|
||||
@@ -690,6 +711,7 @@ export class Player {
|
||||
this.activity = null;
|
||||
this.restoreVersion++;
|
||||
this.wavePolicy.activate(null);
|
||||
this.fadeVersion++;
|
||||
this.fading = false;
|
||||
this.finishAttempt('skip');
|
||||
this.engine.dispose();
|
||||
@@ -708,7 +730,7 @@ export class Player {
|
||||
this.listening?.beginAudio();
|
||||
}
|
||||
private async syncListening(room: ListeningRoom, position: number, current: () => boolean) {
|
||||
if (!room.track_id) { this.audio.pause(); return; }
|
||||
if (!room.track_id) { this.cancelCrossfade(); this.audio.pause(); return; }
|
||||
if (this.remoteRoom !== room.id || this.state.track?.id !== room.track_id || this.state.queue.length !== 1 || this.state.wave) {
|
||||
const track = unwrap(await this.session.client.GET('/api/v1/tracks/{id}', { params: { path: { id: room.track_id } } }));
|
||||
if (!current()) return;
|
||||
|
||||
Reference in new issue
Block a user