feat: let ExoPlayer handle audio focus (pause on calls / other media apps)

The shared feed/podcast/music ExoPlayer previously set no audio attributes and
did not handle audio focus, so a phone call or another media app starting would
not pause playback — meaning V4V streaming payments could keep accruing during a
call even though the user wasn't really listening.

Set USAGE_MEDIA audio attributes with handleAudioFocus = true on the pooled
player. ExoPlayer now pauses on focus loss (a call, another app's playback) and
ducks for transient interruptions; pausing flips isPlaying false, so the
streaming-payment accrual stops with it for free.

Tradeoff: in Media3 a muted player still requests focus while playWhenReady is
true, so muted feed autoplay now requests audio focus too. Acceptable for
correct call/interruption behavior; can be scoped to audio-only players later if
it proves disruptive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGa1EM5KWyDo1o5Yr6sS18
This commit is contained in:
Claude
2026-06-28 19:37:46 +00:00
parent dedabe6451
commit bce50d1d01
@@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.service.playback.playerPool
import android.content.Context
import androidx.annotation.OptIn
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
@@ -67,6 +69,18 @@ class ExoPlayerBuilder(
.apply {
setMediaSourceFactory(CustomMediaSourceFactory(videoCache, dataSourceFactory))
setLoadControl(feedTunedLoadControl())
// Let ExoPlayer own audio focus: pause when a call or another media app takes over
// (which flips isPlaying false, so V4V streaming payments stop with it) and duck for
// transient interruptions. USAGE_MEDIA so the system treats it as ordinary playback.
setAudioAttributes(
AudioAttributes
.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
.build(),
// handleAudioFocus =
true,
)
}.build()
.apply {
PcmTapRegistry.registerPlayer(this, sink)