fix(app): Spotify resumed wieder nach TTS — nudgeMediaResume mit TRANSIENT

Stefan-Bug-Report: ARIA liest Nachricht vor, Spotify pausiert korrekt,
ARIA spricht durch — aber Spotify spielt danach NICHT automatisch
weiter. Sollte mit GAIN_TRANSIENT auto-resumen, tut es aber bei
manchen Spotify-Versionen/Geraeten nicht zuverlaessig.

Hintergrund: alte kickReleaseMedia() mit AUDIOFOCUS_GAIN (permanent)
war zu aggressiv (Spotify interpretierte als "user stoppte" =
Auto-Resume kaputt). Wurde entfernt. Jetzt ist das Pendel andersrum
zu weit: ohne Nudge keine Resume.

Sanfter Mittelweg: nudgeMediaResume() mit GAIN_TRANSIENT statt
GAIN-permanent. 100ms hold, abandon. Spotify bekommt Focus-Wechsel-
Hint ohne "user stopped"-Effekt.

audio.ts: nach AudioFocus.release() 50ms warten, dann nudgeMediaResume.
AudioFocusModule.kt: neue Methode + alte kickReleaseMedia bleibt mit
⚠️-Markierung fuer andere Use-Cases.

APK neu bauen erforderlich.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 20:09:55 +02:00
parent 543ad3c46d
commit 9818dc1867
2 changed files with 64 additions and 0 deletions
@@ -131,6 +131,58 @@ class AudioFocusModule(reactContext: ReactApplicationContext) : ReactContextBase
promise.resolve(true)
}
/** Sanfter Spotify-Resume-Nudge: kurz USAGE_MEDIA mit TRANSIENT
* requesten und sofort abandonen. Spotify bekommt das als
* Focus-Frei-Signal und resumed automatisch — aber weil TRANSIENT
* (nicht GAIN permanent), interpretiert Spotify das NICHT als
* "user stopped" was Auto-Resume verhindert haette.
*
* Hintergrund: ARIA spricht TTS via USAGE_ASSISTANT GAIN_TRANSIENT,
* Spotify pausiert. ARIA released. Spotify SOLLTE nach
* TRANSIENT-Loss + Abandon automatisch resumen, tut es aber bei
* manchen Versionen / Geraeten nicht zuverlaessig. Dieser Nudge
* triggert den Focus-Stack-Refresh ohne den Spotify-Auto-Stop-Bug
* der alten kickReleaseMedia mit GAIN permanent.
*/
@ReactMethod
fun nudgeMediaResume(promise: Promise) {
val am = audioManager()
if (am == null) {
promise.resolve(false)
return
}
Thread {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val attrs = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
val nudgeListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ }
val nudgeReq = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
.setAudioAttributes(attrs)
.setOnAudioFocusChangeListener(nudgeListener)
.build()
am.requestAudioFocus(nudgeReq)
Thread.sleep(100)
am.abandonAudioFocusRequest(nudgeReq)
} else {
val nudgeListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ }
@Suppress("DEPRECATION")
am.requestAudioFocus(nudgeListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
Thread.sleep(100)
@Suppress("DEPRECATION")
am.abandonAudioFocus(nudgeListener)
}
Log.i(TAG, "nudgeMediaResume: USAGE_MEDIA TRANSIENT request+abandon (Spotify-Resume-Trigger)")
} catch (e: Exception) {
Log.w(TAG, "nudgeMediaResume failed: ${e.message}")
}
}.start()
promise.resolve(true)
}
/** Den USAGE_MEDIA-Focus-Stack im System aufmischen, damit Spotify/YouTube
* resumen wenn ein anderer Player (z.B. react-native-sound) seinen Focus
* nicht ordnungsgemaess released hat. Strategie: kurz selbst USAGE_MEDIA
@@ -140,6 +192,10 @@ class AudioFocusModule(reactContext: ReactApplicationContext) : ReactContextBase
*
* Workaround fuer das react-native-sound-Bug: Sound.stop()/release()
* laesst den AudioFocusRequest haengen.
*
* ⚠️ ACHTUNG: nutzt AUDIOFOCUS_GAIN (permanent), Spotify kann das als
* "user-action stopp" interpretieren und Auto-Resume verhindern.
* Fuer Spotify-Resume nach TTS lieber nudgeMediaResume() nehmen (sanfter).
*/
@ReactMethod
fun kickReleaseMedia(promise: Promise) {
+8
View File
@@ -40,6 +40,7 @@ const { AudioFocus, PcmStreamPlayer } = NativeModules as {
AudioFocus?: {
requestDuck: () => Promise<boolean>;
requestExclusive: () => Promise<boolean>;
nudgeMediaResume: () => Promise<boolean>;
release: () => Promise<boolean>;
kickReleaseMedia: () => Promise<boolean>;
getMode?: () => Promise<number>;
@@ -332,6 +333,13 @@ class AudioService {
}
console.log('[Audio] AudioFocus jetzt released');
AudioFocus?.release().catch(() => {});
// Spotify-Resume-Trigger: nach Abandon den USAGE_MEDIA-Focus-Stack
// mit kurzem TRANSIENT-Nudge aufmischen. Spotify resumed sonst bei
// manchen Versionen / Geraeten nicht zuverlaessig nach Auto-Loss.
// 50ms Delay damit das Abandon erst durch ist.
setTimeout(() => {
AudioFocus?.nudgeMediaResume().catch(() => {});
}, 50);
}, this.FOCUS_RELEASE_DELAY_MS);
}