From 79760d1b2ef98a8f24dd01fa0a9fab747228d06e Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 10 May 2026 14:11:53 +0200 Subject: [PATCH] =?UTF-8?q?fix(audio):=20kurze=20TTS-Texte=20spielen=20wie?= =?UTF-8?q?der=20ab=20=E2=80=94=20AudioTrack-Buffer=20entkoppelt=20von=20P?= =?UTF-8?q?reroll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../com/ariacockpit/PcmStreamPlayerModule.kt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt b/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt index dc7e63e..2c44c18 100644 --- a/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt +++ b/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt @@ -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 ~2s-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). + val bufferSize = (bytesPerSecond * 2).coerceAtLeast(minBuf * 8) prerollBytes = prerollTarget bytesBuffered = 0 playbackStarted = false @@ -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