Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad49fe0e3 |
No files matched your search
@@ -90,6 +90,7 @@ class Controller {
|
|||||||
private lastSave = 0;
|
private lastSave = 0;
|
||||||
private waveFeedback: { track_id: string; outcome: WaveOutcome }[] = [];
|
private waveFeedback: { track_id: string; outcome: WaveOutcome }[] = [];
|
||||||
private waveLoading = false;
|
private waveLoading = false;
|
||||||
|
private neutralSwitch = false; // следующая смена трека — из-за нового контекста волны
|
||||||
private setupDone: Promise<void> | null = null;
|
private setupDone: Promise<void> | null = null;
|
||||||
private listeners = new Set<() => void>();
|
private listeners = new Set<() => void>();
|
||||||
private state: PlayerState = {
|
private state: PlayerState = {
|
||||||
@@ -189,6 +190,14 @@ class Controller {
|
|||||||
if (!a) return;
|
if (!a) return;
|
||||||
this.attempt = null;
|
this.attempt = null;
|
||||||
const pos = Math.round(lastPositionS * 1000);
|
const pos = Math.round(lastPositionS * 1000);
|
||||||
|
if (this.neutralSwitch) {
|
||||||
|
this.neutralSwitch = false;
|
||||||
|
if (a.playedMs >= EARLY_SKIP_MS) {
|
||||||
|
this.events?.push({ type: 'skip', track_id: a.track.id, position_ms: pos, played_ms: Math.round(a.playedMs) });
|
||||||
|
void this.events?.flush();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const kind = ended ? 'play_end' : a.outcome(pos);
|
const kind = ended ? 'play_end' : a.outcome(pos);
|
||||||
this.events?.push({ type: kind, track_id: a.track.id, position_ms: pos, played_ms: Math.round(a.playedMs) });
|
this.events?.push({ type: kind, track_id: a.track.id, position_ms: pos, played_ms: Math.round(a.playedMs) });
|
||||||
void this.events?.flush();
|
void this.events?.flush();
|
||||||
@@ -316,6 +325,13 @@ class Controller {
|
|||||||
);
|
);
|
||||||
await this.replaceUpcoming(res.tracks);
|
await this.replaceUpcoming(res.tracks);
|
||||||
this.update({ wave: { ...wave, activity: res.activity, mood: res.mood, fallback: res.fallback } });
|
this.update({ wave: { ...wave, activity: res.activity, mood: res.mood, fallback: res.fallback } });
|
||||||
|
// Новый контекст звучит сразу; прерванный трек — не скип для волны (как в вебе).
|
||||||
|
if (res.tracks.some(playable)) {
|
||||||
|
this.neutralSwitch = true;
|
||||||
|
await TrackPlayer.skipToNext().catch(() => {
|
||||||
|
this.neutralSwitch = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private leaveWave() {
|
private leaveWave() {
|
||||||
|
|||||||
@@ -186,7 +186,10 @@ export class Player {
|
|||||||
void this.load(true);
|
void this.load(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Сменить занятие / настроение на лету: текущий трек доигрывает, дальше — новая пачка. */
|
/**
|
||||||
|
* Сменить занятие / настроение на лету: сразу играет первый трек новой пачки (docs/recommendations.md#6.7).
|
||||||
|
* Прерванный трек — не скип: волне сигнал не уходит, в историю он попадает, только если играл ≥ 30 с.
|
||||||
|
*/
|
||||||
async setWaveContext(activity: string | null, mood: string | null) {
|
async setWaveContext(activity: string | null, mood: string | null) {
|
||||||
const wave = this.state.wave;
|
const wave = this.state.wave;
|
||||||
if (!wave) return this.startWave(activity, mood);
|
if (!wave) return this.startWave(activity, mood);
|
||||||
@@ -197,10 +200,17 @@ export class Player {
|
|||||||
body: { activity, mood },
|
body: { activity, mood },
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const fresh = res.tracks.filter((t) => t.available && t.stream_url);
|
||||||
this.queue.dropUpcoming();
|
this.queue.dropUpcoming();
|
||||||
this.queue.append(res.tracks.filter((t) => t.available && t.stream_url));
|
this.queue.append(fresh);
|
||||||
this.update({ wave: { ...wave, activity: res.activity, mood: res.mood, fallback: res.fallback } });
|
this.update({ wave: { ...wave, activity: res.activity, mood: res.mood, fallback: res.fallback } });
|
||||||
this.syncQueue();
|
if (fresh.length && this.queue.next()) {
|
||||||
|
this.finishAttempt('skip', true);
|
||||||
|
this.syncQueue();
|
||||||
|
void this.load(true);
|
||||||
|
} else {
|
||||||
|
this.syncQueue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopWave() {
|
stopWave() {
|
||||||
@@ -395,11 +405,19 @@ export class Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Завершает текущую попытку: событие с реально прослушанным временем. */
|
/** Завершает текущую попытку: событие с реально прослушанным временем. */
|
||||||
private finishAttempt(type: 'play_end' | 'skip') {
|
/** neutral — трек прерван сменой контекста волны: не отрицательный сигнал и без короткого «скипа» в событиях. */
|
||||||
|
private finishAttempt(type: 'play_end' | 'skip', neutral = false) {
|
||||||
const a = this.attempt;
|
const a = this.attempt;
|
||||||
if (!a) return;
|
if (!a) return;
|
||||||
this.attempt = null;
|
this.attempt = null;
|
||||||
const pos = Math.round(this.audio.currentTime * 1000);
|
const pos = Math.round(this.audio.currentTime * 1000);
|
||||||
|
if (neutral) {
|
||||||
|
if (a.playedMs >= EARLY_SKIP_MS) {
|
||||||
|
this.events.push({ type: 'skip', track_id: a.track.id, position_ms: pos, played_ms: Math.round(a.playedMs) });
|
||||||
|
void this.events.flush();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const kind = type === 'skip' ? a.outcome(pos) : 'play_end';
|
const kind = type === 'skip' ? a.outcome(pos) : 'play_end';
|
||||||
this.events.push({ type: kind, track_id: a.track.id, position_ms: pos, played_ms: Math.round(a.playedMs) });
|
this.events.push({ type: kind, track_id: a.track.id, position_ms: pos, played_ms: Math.round(a.playedMs) });
|
||||||
void this.events.flush();
|
void this.events.flush();
|
||||||
|
|||||||
Reference in new issue
Block a user