fix(app): Underrun-Schutz im PcmStreamPlayer — Spotify resumed nicht mehr nach 10s
Wenn die Bridge zwischen zwei Saetzen rendert (1-2s pro Satz auf der Gamebox-RTX 3060), kommen keine neuen PCM-Chunks rein und der AudioTrack- Buffer laeuft leer. Spotify hat eine eigene Heuristik die nach ~10s "stummer Lücke" eigenmaechtig die Wiedergabe wiederaufnimmt — auch wenn wir den AudioFocus formal noch halten. Fix: Writer-Thread fuettert Stille rein wenn der Puffer unter ~100ms faellt (~50ms pro Refill-Tick alle 50ms). AudioTrack bleibt damit durchgehend aktiv, andere Apps respektieren weiterhin den Fokus. Bonus: 30s-Idle-Cutoff falls die Bridge crashed und kein final-Marker mehr kommt — sonst wuerde der Writer-Thread ewig Stille fuettern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6ac374621c
commit
f2e643d1fb
|
|
@ -137,6 +137,17 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||||
Log.w(TAG, "play() sofort failed: ${e.message}")
|
Log.w(TAG, "play() sofort failed: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Idle-Cutoff: wenn endRequested NICHT kam aber 30s nichts mehr
|
||||||
|
// reinkommt, brechen wir ab (Bridge-Crash, verlorener final).
|
||||||
|
var idleMs = 0L
|
||||||
|
val maxIdleMs = 30_000L
|
||||||
|
// Zielpufferfuellung — unter diesem Wasserstand fuettern wir
|
||||||
|
// Stille rein damit AudioTrack nicht underrunt waehrend die
|
||||||
|
// Bridge den naechsten Satz rendert. Spotify/YouTube reagieren
|
||||||
|
// sonst mit eigenmaechtiger Wiederaufnahme nach ~10s Stille.
|
||||||
|
val underrunGuardFrames = sampleRate / 10 // ~100ms
|
||||||
|
val silenceFillFrames = sampleRate / 20 // ~50ms pro Refill
|
||||||
|
|
||||||
mainLoop@ while (!writerShouldStop) {
|
mainLoop@ while (!writerShouldStop) {
|
||||||
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
|
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
|
@ -153,8 +164,33 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||||
}
|
}
|
||||||
break@mainLoop
|
break@mainLoop
|
||||||
}
|
}
|
||||||
|
// Underrun-Schutz: Stille reinfuettern wenn der AudioTrack-
|
||||||
|
// Puffer leerzulaufen droht. Spotify resumed sonst nach
|
||||||
|
// ~10s Pause auf eigene Faust, obwohl wir den Fokus halten.
|
||||||
|
if (playbackStarted) {
|
||||||
|
val framesWritten = bytesBuffered / streamBytesPerFrame
|
||||||
|
val framesPlayed = t.playbackHeadPosition.toLong()
|
||||||
|
val framesInBuffer = framesWritten - framesPlayed
|
||||||
|
if (framesInBuffer < underrunGuardFrames) {
|
||||||
|
val fillBytes = silenceFillFrames * streamBytesPerFrame
|
||||||
|
val silence = ByteArray(fillBytes)
|
||||||
|
var silOff = 0
|
||||||
|
while (silOff < silence.size && !writerShouldStop) {
|
||||||
|
val w = t.write(silence, silOff, silence.size - silOff)
|
||||||
|
if (w <= 0) break
|
||||||
|
silOff += w
|
||||||
|
}
|
||||||
|
bytesBuffered += silence.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idleMs += 50L
|
||||||
|
if (idleMs >= maxIdleMs) {
|
||||||
|
Log.w(TAG, "Idle-Cutoff: ${maxIdleMs}ms keine Daten — Stream wird beendet")
|
||||||
|
break@mainLoop
|
||||||
|
}
|
||||||
continue@mainLoop
|
continue@mainLoop
|
||||||
}
|
}
|
||||||
|
idleMs = 0L
|
||||||
|
|
||||||
// Pre-Roll Check: play() erst wenn genug gepuffert
|
// Pre-Roll Check: play() erst wenn genug gepuffert
|
||||||
if (!playbackStarted && bytesBuffered + data.size >= prerollBytes) {
|
if (!playbackStarted && bytesBuffered + data.size >= prerollBytes) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue