fix(audio): play() beim 1. Chunk — kurze Texte stallen nicht mehr
Logs zeigten: Pre-Roll-Pfad (play() WAEHREND chunks reinkommen) lief immer sauber, Kurz-Text-Pfad (play() NACHDEM Buffer komplett gefuellt ist) stallte immer — egal mit wie viel Daten oder welchem USAGE-Tag. Fix: play() beim allerersten data-chunk callen, kein Pre-Roll-Threshold mehr. AudioTrack ist sofort im PLAYING-State, weitere chunks/trailing fliessen parallel ab. Padding-Block nach mainLoop entfaellt komplett. USAGE_MEDIA wieder auf USAGE_ASSISTANT zurueck — war nicht die Ursache. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,12 +92,7 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
val newTrack = AudioTrack.Builder()
|
||||
.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
// USAGE_MEDIA statt USAGE_ASSISTANT — auf OnePlus A12 stallt
|
||||
// AudioTrack mit USAGE_ASSISTANT wenn play() nach komplettem
|
||||
// Buffer-Fuellen called wird (pos bleibt 0). USAGE_MEDIA ist
|
||||
// robust. AudioFocus wird eh separat ueber AudioFocusModule
|
||||
// gehandhabt, nicht ueber dieses USAGE-Tag.
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.setUsage(AudioAttributes.USAGE_ASSISTANT)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build(),
|
||||
)
|
||||
@@ -164,11 +159,12 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
|
||||
if (data == null) {
|
||||
if (endRequested) {
|
||||
// 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.
|
||||
// Falls play() noch gar nicht lief (Stream ohne data
|
||||
// ueberhaupt — sehr seltene Edge-Case): jetzt anstossen
|
||||
// damit das finally{}-Wait nicht endlos blockt.
|
||||
if (!playbackStarted) {
|
||||
try { t.play(); playbackStarted = true } catch (_: Exception) {}
|
||||
}
|
||||
break@mainLoop
|
||||
}
|
||||
// Underrun-Schutz: Stille reinfuettern wenn der AudioTrack-
|
||||
@@ -199,12 +195,16 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
}
|
||||
idleMs = 0L
|
||||
|
||||
// Pre-Roll Check: play() erst wenn genug gepuffert
|
||||
if (!playbackStarted && bytesBuffered + data.size >= prerollBytes) {
|
||||
// play() beim ALLERERSTEN data-chunk aufrufen — egal wie wenig
|
||||
// Daten da sind. Sonst stallt AudioTrack auf OnePlus A12 wenn
|
||||
// play() erst gerufen wird nachdem der Buffer komplett gefuellt
|
||||
// ist. Pre-Roll als "Vorrat aufbauen" passiert dann waehrend
|
||||
// der Track schon spielt — Underrun-Schutz fuettert ggf. Stille.
|
||||
if (!playbackStarted) {
|
||||
try {
|
||||
t.play()
|
||||
playbackStarted = true
|
||||
Log.i(TAG, "Playback gestartet nach Pre-Roll ${bytesBuffered + data.size} Bytes")
|
||||
Log.i(TAG, "Playback gestartet beim 1. Chunk (${bytesBuffered}B leading + ${data.size}B data)")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "play() failed: ${e.message}")
|
||||
}
|
||||
@@ -231,31 +231,6 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
}
|
||||
bytesBuffered += silence.size
|
||||
}
|
||||
// Bei kurzem Text (play() noch nicht gestartet): Buffer auf min.
|
||||
// 3s padden + DANN play(). Auf OnePlus A12 startet AudioTrack
|
||||
// bei < 3s Buffer-Inhalt nicht — pos bleibt auf 0 stehen.
|
||||
// (2s war zu wenig, 8 Worte ~2.5s gingen, 3 Worte ~1s nicht.)
|
||||
if (!playbackStarted && !writerShouldStop) {
|
||||
val minStartBytes = bytesPerSecond * 3
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user