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 bcc71ed..6f5ec5f 100644 --- a/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt +++ b/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt @@ -45,6 +45,7 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex @Volatile private var prerollBytes: Int = 0 @Volatile private var playbackStarted = false @Volatile private var bytesBuffered: Long = 0 + @Volatile private var streamBytesPerFrame: Int = 2 // mono s16le default // ── Lifecycle ── @@ -64,6 +65,7 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex prerollBytes = prerollTarget bytesBuffered = 0 playbackStarted = false + streamBytesPerFrame = channels * 2 // s16 = 2 bytes per sample val newTrack = AudioTrack.Builder() .setAudioAttributes( @@ -127,6 +129,30 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex } catch (e: Exception) { Log.w(TAG, "Writer-Thread Fehler: ${e.message}") } finally { + // Warten bis alle geschriebenen Samples tatsaechlich abgespielt sind, + // sonst cuttet t.release() die letzten Sekunden ab. + try { + val totalFrames = (bytesBuffered / streamBytesPerFrame).toInt() + var lastPos = -1 + var stalledCount = 0 + while (!writerShouldStop) { + val pos = t.playbackHeadPosition + if (pos >= totalFrames) break + // Safety: wenn Position 2s nicht mehr vorwaerts → AudioTrack hing + if (pos == lastPos) { + stalledCount++ + if (stalledCount > 40) { + Log.w(TAG, "playback stalled at $pos/$totalFrames — give up") + break + } + } else { + stalledCount = 0 + lastPos = pos + } + Thread.sleep(50) + } + Log.i(TAG, "Playback fertig: frames=$totalFrames pos=${t.playbackHeadPosition}") + } catch (_: Exception) {} try { t.stop() } catch (_: Exception) {} try { t.release() } catch (_: Exception) {} }