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 2c44c18..8a42744 100644 --- a/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt +++ b/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt @@ -79,11 +79,13 @@ 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 ~2s-Buffer reicht. Wenn er + // 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). - val bufferSize = (bytesPerSecond * 2).coerceAtLeast(minBuf * 8) + // 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) prerollBytes = prerollTarget bytesBuffered = 0 playbackStarted = false @@ -159,16 +161,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 - 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}") - } - } + // 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. break@mainLoop } // Underrun-Schutz: Stille reinfuettern wenn der AudioTrack- @@ -231,6 +228,30 @@ 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 {