fix: Stream-Ende wartet auf playbackHeadPosition vor release()
AudioTrack.stop() + release() direkt nach dem letzten write() killt die letzten Sekunden Audio — die Samples sind zwar im Buffer, aber noch nicht durch die Hardware rausgespielt. Deshalb brach die Sprachausgabe mitten im Satz ab (z.B. bei "diesmal"). Fix: Writer-Thread wartet im finally-Block bis playbackHeadPosition die Anzahl geschriebener Frames erreicht, dann erst stop()/release(). Safety: 2s Stall-Detection, falls AudioTrack haengen bleibt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
31fe70bab5
commit
0c03b4f161
|
|
@ -45,6 +45,7 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||||
@Volatile private var prerollBytes: Int = 0
|
@Volatile private var prerollBytes: Int = 0
|
||||||
@Volatile private var playbackStarted = false
|
@Volatile private var playbackStarted = false
|
||||||
@Volatile private var bytesBuffered: Long = 0
|
@Volatile private var bytesBuffered: Long = 0
|
||||||
|
@Volatile private var streamBytesPerFrame: Int = 2 // mono s16le default
|
||||||
|
|
||||||
// ── Lifecycle ──
|
// ── Lifecycle ──
|
||||||
|
|
||||||
|
|
@ -64,6 +65,7 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||||
prerollBytes = prerollTarget
|
prerollBytes = prerollTarget
|
||||||
bytesBuffered = 0
|
bytesBuffered = 0
|
||||||
playbackStarted = false
|
playbackStarted = false
|
||||||
|
streamBytesPerFrame = channels * 2 // s16 = 2 bytes per sample
|
||||||
|
|
||||||
val newTrack = AudioTrack.Builder()
|
val newTrack = AudioTrack.Builder()
|
||||||
.setAudioAttributes(
|
.setAudioAttributes(
|
||||||
|
|
@ -127,6 +129,30 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Writer-Thread Fehler: ${e.message}")
|
Log.w(TAG, "Writer-Thread Fehler: ${e.message}")
|
||||||
} finally {
|
} 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.stop() } catch (_: Exception) {}
|
||||||
try { t.release() } catch (_: Exception) {}
|
try { t.release() } catch (_: Exception) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue