Compare commits

...

11 Commits

Author SHA1 Message Date
duffyduck 7d08c06720 release: bump version to 0.0.9.2 2026-05-10 14:40:35 +02:00
duffyduck f066a2a555 fix(audio): Mute-Button stoppt jetzt auch laufenden PCM-Stream
pcmStreamActive wurde beim isFinal-Chunk schon auf false gesetzt, der
AudioTrack spielte aber noch aus seinem Buffer (kann sekundenlang sein).
stopPlayback() uebersprang darum PcmStreamPlayer.stop() — ARIA redete
weiter obwohl Spotify schon resumed war.

Fix: stop() immer rufen, der Flag-Check faellt weg (ist eh idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:39:27 +02:00
duffyduck b55b0e7c42 fix(audio): play() beim 1. Chunk — kurze Texte stallen nicht mehr
Logs zeigten: Pre-Roll-Pfad (play() WAEHREND chunks reinkommen) lief
immer sauber, Kurz-Text-Pfad (play() NACHDEM Buffer komplett gefuellt
ist) stallte immer — egal mit wie viel Daten oder welchem USAGE-Tag.

Fix: play() beim allerersten data-chunk callen, kein Pre-Roll-Threshold
mehr. AudioTrack ist sofort im PLAYING-State, weitere chunks/trailing
fliessen parallel ab. Padding-Block nach mainLoop entfaellt komplett.

USAGE_MEDIA wieder auf USAGE_ASSISTANT zurueck — war nicht die Ursache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:38:02 +02:00
duffyduck 70f806ef80 release: bump version to 0.0.9.1 2026-05-10 14:32:35 +02:00
duffyduck 0773d9496d fix(audio): AudioTrack auf USAGE_MEDIA — USAGE_ASSISTANT stallt auf OnePlus A12
Letzter Test zeigte: 163456B im Buffer mit play()-nach-Padding stallt
(pos=0), aber 170048B im Pre-Roll-Pfad startet einwandfrei. Differenz
nur 4% Daten — kein Buffer-Threshold-Problem, sondern AudioTrack-Quirk
mit USAGE_ASSISTANT bei "voller Buffer, dann play()".

USAGE_MEDIA ist robuster, AudioFocus laeuft eh separat ueber das
AudioFocusModule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:31:23 +02:00
duffyduck 1a4857ed62 release: bump version to 0.0.9.0 2026-05-10 14:26:41 +02:00
duffyduck 962d814318 fix(audio): kurze TTS — Padding auf 3s erhoeht (OnePlus A12 Hard-Threshold)
Test mit 96000B (2s) Padding zeigte: AudioTrack stallt immer noch mit
pos=0/48000. Ab 8 Worten (~2.5s) geht's — der Hard-Threshold liegt also
zwischen 2s und 3s. Padding auf 3s, Buffer auf 4s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:25:43 +02:00
duffyduck 9276a92c83 release: bump version to 0.0.8.9 2026-05-10 14:20:43 +02:00
duffyduck d16896c4b4 fix(audio): kurze TTS-Texte — play() erst NACH Buffer-Fuellung mit Padding
Auf OnePlus A12 startet AudioTrack nicht zuverlaessig wenn play() bei
duennem Buffer gerufen wird (pos blieb 0/34112 trotz 71KB Daten + Retry).

Neue Reihenfolge bei kurzem Stream:
1. Daten in Buffer schreiben (mainLoop)
2. Trailing-Silence (0.3s)
3. Padding bis min. 2s gepuffert
4. DANN erst play()

Buffer auf 3s erhoeht damit blockingem write() noch Headroom bleibt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:19:45 +02:00
duffyduck 20050d4077 release: bump version to 0.0.8.8 2026-05-10 14:12:59 +02:00
duffyduck 79760d1b2e fix(audio): kurze TTS-Texte spielen wieder ab — AudioTrack-Buffer entkoppelt von Preroll
OnePlus A12 stallte bei kurzem Text mit pos=0/34112: 336KB Buffer fuer
3.5s Preroll, aber nur 68KB Daten drin → AudioTrack faehrt nicht an.

Fix: Buffer fest auf ~2s, plus play()-Retry bei pos=0 nach 500ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:11:53 +02:00
4 changed files with 38 additions and 25 deletions
+2 -2
View File
@@ -79,8 +79,8 @@ android {
applicationId "com.ariacockpit"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 807
versionName "0.0.8.7"
versionCode 902
versionName "0.0.9.2"
// Fallback fuer Libraries mit Product Flavors
missingDimensionStrategy 'react-native-camera', 'general'
}
@@ -78,9 +78,12 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
val encoding = AudioFormat.ENCODING_PCM_16BIT
val minBuf = AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding)
val bytesPerSecond = sampleRate * channels * 2 // 16-bit = 2 bytes
// Buffer muss mindestens PREROLL + etwas Spielraum fassen.
val prerollTarget = (bytesPerSecond * prerollSec).toInt()
val bufferSize = (minBuf * 32).coerceAtLeast(prerollTarget * 2)
// Buffer entkoppelt von Preroll — fester ~4s-Buffer. OnePlus A12
// mit USAGE_ASSISTANT laeuft AudioTrack erst ab ~3s gepufferter
// Daten an. Wir padden Kurztexte vor play() auf 3s (siehe Block
// nach mainLoop), Buffer braucht ~1s Headroom weil write() blockt.
val bufferSize = (bytesPerSecond * 4).coerceAtLeast(minBuf * 8)
prerollBytes = prerollTarget
bytesBuffered = 0
playbackStarted = false
@@ -156,15 +159,11 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
if (data == null) {
if (endRequested) {
// Falls wir vor Pre-Roll enden (kurzer Text): trotzdem abspielen
// Falls play() noch gar nicht lief (Stream ohne data
// ueberhaupt — sehr seltene Edge-Case): jetzt anstossen
// damit das finally{}-Wait nicht endlos blockt.
if (!playbackStarted) {
try {
t.play()
playbackStarted = true
Log.i(TAG, "Playback gestartet VOR Pre-Roll (kurzer Text, ${bytesBuffered}B gepuffert)")
} catch (e: Exception) {
Log.w(TAG, "play() fallback failed: ${e.message}")
}
try { t.play(); playbackStarted = true } catch (_: Exception) {}
}
break@mainLoop
}
@@ -196,12 +195,16 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
}
idleMs = 0L
// Pre-Roll Check: play() erst wenn genug gepuffert
if (!playbackStarted && bytesBuffered + data.size >= prerollBytes) {
// play() beim ALLERERSTEN data-chunk aufrufen — egal wie wenig
// Daten da sind. Sonst stallt AudioTrack auf OnePlus A12 wenn
// play() erst gerufen wird nachdem der Buffer komplett gefuellt
// ist. Pre-Roll als "Vorrat aufbauen" passiert dann waehrend
// der Track schon spielt — Underrun-Schutz fuettert ggf. Stille.
if (!playbackStarted) {
try {
t.play()
playbackStarted = true
Log.i(TAG, "Playback gestartet nach Pre-Roll ${bytesBuffered + data.size} Bytes")
Log.i(TAG, "Playback gestartet beim 1. Chunk (${bytesBuffered}B leading + ${data.size}B data)")
} catch (e: Exception) {
Log.w(TAG, "play() failed: ${e.message}")
}
@@ -237,12 +240,21 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
val totalFrames = (bytesBuffered / streamBytesPerFrame).toInt()
var lastPos = -1
var stalledCount = 0
var retried = false
while (!writerShouldStop) {
val pos = t.playbackHeadPosition
if (pos >= totalFrames) break
// Safety: wenn Position 2s nicht mehr vorwaerts → AudioTrack hing
if (pos == lastPos) {
stalledCount++
// Nach 500ms Stillstand: AudioTrack-Quirk auf manchen
// Geraeten (OnePlus A12) — play() nochmal anstossen.
if (stalledCount == 10 && pos == 0 && !retried) {
retried = true
Log.w(TAG, "playback nicht angefahren — retry play()")
try { t.play() } catch (e: Exception) {
Log.w(TAG, "retry play() failed: ${e.message}")
}
}
if (stalledCount > 40) {
Log.w(TAG, "playback stalled at $pos/$totalFrames — give up")
break
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "aria-cockpit",
"version": "0.0.8.7",
"version": "0.0.9.2",
"private": true,
"scripts": {
"android": "react-native run-android",
+9 -8
View File
@@ -1098,14 +1098,15 @@ class AudioService {
if (this.preloadedPath) RNFS.unlink(this.preloadedPath).catch(() => {});
this.preloadedPath = '';
}
// PCM-Stream ebenfalls hart stoppen (Cancel/Abbruch)
if (this.pcmStreamActive) {
PcmStreamPlayer?.stop().catch(() => {});
this.pcmStreamActive = false;
this.pcmBuffer = [];
this.pcmBytesCollected = 0;
this.pcmMessageId = '';
}
// PCM-Stream ebenfalls hart stoppen (Cancel/Abbruch).
// pcmStreamActive wird beim isFinal-Chunk schon false gesetzt — der
// AudioTrack spielt aber noch sekundenlang aus seinem Buffer ab. Daher
// IMMER stop() aufrufen, ohne den Flag zu pruefen (ist idempotent).
PcmStreamPlayer?.stop().catch(() => {});
this.pcmStreamActive = false;
this.pcmBuffer = [];
this.pcmBytesCollected = 0;
this.pcmMessageId = '';
// Audio-Focus sofort freigeben — User hat explizit abgebrochen
this._cancelDeferredFocusRelease();
AudioFocus?.release().catch(() => {});