Compare commits

..

7 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
4 changed files with 30 additions and 50 deletions
+2 -2
View File
@@ -79,8 +79,8 @@ android {
applicationId "com.ariacockpit"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 809
versionName "0.0.8.9"
versionCode 902
versionName "0.0.9.2"
// Fallback fuer Libraries mit Product Flavors
missingDimensionStrategy 'react-native-camera', 'general'
}
@@ -79,13 +79,11 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
val minBuf = AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding)
val bytesPerSecond = sampleRate * channels * 2 // 16-bit = 2 bytes
val prerollTarget = (bytesPerSecond * prerollSec).toInt()
// Buffer entkoppelt von Preroll — fester ~3s-Buffer reicht. Wenn er
// an Preroll gekoppelt ist (z.B. 7s bei preroll=3.5s) und nur kurz
// gefuettert wird, stallt AudioTrack auf manchen Geraeten (OnePlus
// Android 12: pos bleibt 0 obwohl play() lief).
// 3s damit Padding bis 2s vor play() noch Headroom hat (write() ist
// blocking — wenn Buffer voll ist, deadlockt es vor play()).
val bufferSize = (bytesPerSecond * 3).coerceAtLeast(minBuf * 8)
// 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
@@ -161,11 +159,12 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
if (data == null) {
if (endRequested) {
// Bei kurzem Text NICHT hier play() callen — erst nach
// Trailing-Silence + Padding (siehe Block nach mainLoop),
// damit AudioTrack mit komplett gefuelltem Buffer startet.
// OnePlus A12: AudioTrack startet nicht zuverlaessig wenn
// play() bei dünnem Buffer gerufen wird.
// 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 } catch (_: Exception) {}
}
break@mainLoop
}
// Underrun-Schutz: Stille reinfuettern wenn der AudioTrack-
@@ -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}")
}
@@ -228,30 +231,6 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
}
bytesBuffered += silence.size
}
// Bei kurzem Text (play() noch nicht gestartet): Buffer auf min.
// 2s padden + DANN play(). Auf OnePlus A12 startet AudioTrack
// bei einem zu duennen Buffer nicht — pos bleibt auf 0 stehen.
if (!playbackStarted && !writerShouldStop) {
val minStartBytes = bytesPerSecond * 2
if (bytesBuffered < minStartBytes) {
val padBytes = (minStartBytes - bytesBuffered.toInt()) and 0x7FFFFFFE
val pad = ByteArray(padBytes)
var padOff = 0
while (padOff < pad.size && !writerShouldStop) {
val w = t.write(pad, padOff, pad.size - padOff)
if (w <= 0) break
padOff += w
}
bytesBuffered += pad.size
}
try {
t.play()
playbackStarted = true
Log.i(TAG, "Playback gestartet (kurzer Text, ${bytesBuffered}B komplett gepuffert)")
} catch (e: Exception) {
Log.w(TAG, "play() short-text failed: ${e.message}")
}
}
} catch (e: Exception) {
Log.w(TAG, "Writer-Thread Fehler: ${e.message}")
} finally {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "aria-cockpit",
"version": "0.0.8.9",
"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(() => {});