Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad49fe0e3 | ||
|
|
7cf741d82f |
No files matched your search
@@ -115,6 +115,7 @@ jobs:
|
|||||||
# библиотек, но плагин RN не объявляет эту зависимость, и с прогретым кэшем CMake успевал раньше codegen.
|
# библиотек, но плагин RN не объявляет эту зависимость, и с прогретым кэшем CMake успевал раньше codegen.
|
||||||
# Оба вызова идут через один демон Gradle (одинаковые jvmargs), поэтому второй не платит за старт JVM.
|
# Оба вызова идут через один демон Gradle (одинаковые jvmargs), поэтому второй не платит за старт JVM.
|
||||||
run: |
|
run: |
|
||||||
|
set -o pipefail
|
||||||
npx expo prebuild --platform android --clean --no-install
|
npx expo prebuild --platform android --clean --no-install
|
||||||
cd android
|
cd android
|
||||||
# nice / ionice: сборка уступает CPU и диск всему остальному на машине
|
# nice / ionice: сборка уступает CPU и диск всему остальному на машине
|
||||||
@@ -124,7 +125,18 @@ jobs:
|
|||||||
if grep -q '^newArchEnabled=true' gradle.properties; then
|
if grep -q '^newArchEnabled=true' gradle.properties; then
|
||||||
"${gradle[@]}" generateCodegenArtifactsFromSchema
|
"${gradle[@]}" generateCodegenArtifactsFromSchema
|
||||||
fi
|
fi
|
||||||
"${gradle[@]}" assembleRelease -x lintVitalRelease
|
# Постоянный кэш Gradle мог побиться (VM раннера перезагрузили посреди сборки): «Could not read workspace
|
||||||
|
# metadata» — удаляем кэш трансформаций и повторяем один раз, а не падаем до ручной чистки.
|
||||||
|
if ! "${gradle[@]}" assembleRelease -x lintVitalRelease 2>&1 | tee /tmp/gradle.log; then
|
||||||
|
if grep -qE "Could not read workspace metadata|Corrupted|is corrupt" /tmp/gradle.log; then
|
||||||
|
echo "::warning::кэш Gradle повреждён — чистим transforms и собираем заново"
|
||||||
|
./gradlew --stop || true
|
||||||
|
rm -rf ~/.gradle/caches/*/transforms ~/.gradle/caches/transforms-*
|
||||||
|
"${gradle[@]}" assembleRelease -x lintVitalRelease
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
./gradlew --stop
|
./gradlew --stop
|
||||||
|
|
||||||
- name: Диагностика сбоя (диск, память, OOM)
|
- name: Диагностика сбоя (диск, память, OOM)
|
||||||
|
|||||||
@@ -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