Move audio session id generation to playback thread

PiperOrigin-RevId: 726556015
This commit is contained in:
tonihei 2025-02-13 10:55:40 -08:00 committed by Copybara-Service
parent 22853a5c4c
commit 385498c24e
4 changed files with 95 additions and 18 deletions

View File

@ -6,6 +6,10 @@
* Upgrade Kotlin from 1.9.20 to 2.0.20 and use Compose Compiler Gradle
plugin. Upgrade KotlinX Coroutines library from 1.8.1 to 1.9.0.
* ExoPlayer:
* Initial audio session id is no longer immediately available after
creating the player. You can use
`AnalyticsListener.onAudioSessionIdChanged` to listen to the initial
update if required.
* Transformer:
* Add `MediaProjectionAssetLoader`, which provides media from a
`MediaProjection` for screen recording, and add support for screen

View File

@ -15,6 +15,7 @@
*/
package androidx.media3.exoplayer;
import static androidx.media3.common.C.AUDIO_SESSION_ID_UNSET;
import static androidx.media3.common.C.TRACK_TYPE_AUDIO;
import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION;
import static androidx.media3.common.C.TRACK_TYPE_IMAGE;
@ -82,6 +83,7 @@ import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.BackgroundThreadStateHandler;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.HandlerWrapper;
@ -178,6 +180,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Nullable private AudioManager audioManager;
private final boolean suppressPlaybackOnUnsuitableOutput;
@Nullable private final SuitableOutputChecker suitableOutputChecker;
private final BackgroundThreadStateHandler<Integer> audioSessionIdState;
private @RepeatMode int repeatMode;
private boolean shuffleModeEnabled;
@ -205,7 +208,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
private Size surfaceSize;
@Nullable private DecoderCounters videoDecoderCounters;
@Nullable private DecoderCounters audioDecoderCounters;
private int audioSessionId;
private AudioAttributes audioAttributes;
private float volume;
private boolean skipSilenceEnabled;
@ -390,7 +392,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
playlistMetadata = MediaMetadata.EMPTY;
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
maskingWindowIndex = C.INDEX_UNSET;
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
currentCueGroup = CueGroup.EMPTY_TIME_ZERO;
throwsWhenUsingWrongThread = true;
@ -401,6 +402,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
internalPlayer.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs);
}
audioSessionIdState =
new BackgroundThreadStateHandler<>(
AUDIO_SESSION_ID_UNSET,
playbackLooper,
applicationLooper,
clock,
/* onStateChanged= */ this::onAudioSessionIdChanged);
audioSessionIdState.runInBackground(
() ->
audioSessionIdState.setStateInBackground(
Util.generateAudioSessionIdV21(applicationContext)));
audioBecomingNoisyManager =
new AudioBecomingNoisyManager(
builder.context, playbackLooper, builder.looper, componentListener, clock);
@ -440,8 +452,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
surfaceSize = Size.UNKNOWN;
internalPlayer.setAudioAttributes(audioAttributes);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
sendRendererMessage(
@ -1491,24 +1501,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public void setAudioSessionId(int audioSessionId) {
verifyApplicationThread();
if (this.audioSessionId == audioSessionId) {
if (audioSessionIdState.get() == audioSessionId) {
return;
}
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
}
this.audioSessionId = audioSessionId;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
int finalAudioSessionId = audioSessionId;
listeners.sendEvent(
EVENT_AUDIO_SESSION_ID, listener -> listener.onAudioSessionIdChanged(finalAudioSessionId));
audioSessionIdState.updateStateAsync(
/* placeholderState= */ previousId ->
audioSessionId != AUDIO_SESSION_ID_UNSET ? audioSessionId : previousId,
/* backgroundStateUpdate= */ previousId ->
audioSessionId != AUDIO_SESSION_ID_UNSET
? audioSessionId
: Util.generateAudioSessionIdV21(applicationContext));
}
@Override
public int getAudioSessionId() {
verifyApplicationThread();
return audioSessionId;
return audioSessionIdState.get();
}
@Override
@ -2934,6 +2942,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
}
private void onAudioSessionIdChanged(int oldAudioSessionId, int newAudioSessionId) {
verifyApplicationThread();
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, newAudioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, newAudioSessionId);
listeners.sendEvent(
EVENT_AUDIO_SESSION_ID, listener -> listener.onAudioSessionIdChanged(newAudioSessionId));
}
private static DeviceInfo createDeviceInfo(@Nullable StreamVolumeManager streamVolumeManager) {
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL)
.setMinVolume(streamVolumeManager != null ? streamVolumeManager.getMinVolume() : 0)

View File

@ -7276,6 +7276,7 @@ public class ExoPlayerTest {
.build();
player.setRepeatMode(Player.REPEAT_MODE_ONE);
player.setMediaSource(mediaSource);
advance(player).untilPendingCommandsAreFullyHandled();
player.prepare();
advance(player).untilPendingCommandsAreFullyHandled();
@ -12440,6 +12441,7 @@ public class ExoPlayerTest {
@Test
public void onEvents_correspondToListenerCalls() throws Exception {
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();
advance(player).untilPendingCommandsAreFullyHandled();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
Format formatWithStaticMetadata =
@ -16302,6 +16304,58 @@ public class ExoPlayerTest {
assertThat(reportedSpeedChanges).containsExactly(2f, 1.5f, 1f).inOrder();
}
@Test
public void builderBuild_createsInitialAudioSessionId() throws Exception {
ExoPlayer player = new ExoPlayer.Builder(context).build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
int audioSessionIdAfterBuild = player.getAudioSessionId();
advance(player).untilPendingCommandsAreFullyHandled();
int audioSessionIdAfterInit = player.getAudioSessionId();
player.release();
assertThat(audioSessionIdAfterBuild).isEqualTo(C.AUDIO_SESSION_ID_UNSET);
assertThat(audioSessionIdAfterInit).isNotEqualTo(C.AUDIO_SESSION_ID_UNSET);
verify(listener).onAudioSessionIdChanged(audioSessionIdAfterInit);
}
@Test
public void setAudioSessionId_withDefinedId_updatesGetterAndListener() throws Exception {
ExoPlayer player = new ExoPlayer.Builder(context).build();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
player.setAudioSessionId(1234);
int audioSessionId = player.getAudioSessionId();
// Verify there are no further or duplicated updates.
advance(player).untilPendingCommandsAreFullyHandled();
int audioSessionIdAfterIdle = player.getAudioSessionId();
player.release();
assertThat(audioSessionId).isEqualTo(1234);
assertThat(audioSessionIdAfterIdle).isEqualTo(1234);
verify(listener).onAudioSessionIdChanged(anyInt());
verify(listener).onAudioSessionIdChanged(1234);
}
@Test
public void setAudioSessionId_withUndefinedId_updatesGetterAndListener() throws Exception {
ExoPlayer player = new ExoPlayer.Builder(context).build();
advance(player).untilPendingCommandsAreFullyHandled();
Player.Listener listener = mock(Player.Listener.class);
player.addListener(listener);
int initialAudioSessionId = player.getAudioSessionId();
player.setAudioSessionId(C.AUDIO_SESSION_ID_UNSET);
advance(player).untilPendingCommandsAreFullyHandled();
int audioSessionId = player.getAudioSessionId();
player.release();
assertThat(audioSessionId).isNotEqualTo(initialAudioSessionId);
verify(listener).onAudioSessionIdChanged(audioSessionId);
}
// Internal methods.
private void addWatchAsSystemFeature() {

View File

@ -135,6 +135,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
@ -2092,7 +2093,7 @@ public final class DefaultAnalyticsCollectorTest {
}
}
private static ExoPlayer setupPlayer() {
private static ExoPlayer setupPlayer() throws TimeoutException {
Clock clock = new FakeClock(/* isAutoAdvancing= */ true);
return setupPlayer(
/* renderersFactory= */ (eventHandler,
@ -2110,11 +2111,12 @@ public final class DefaultAnalyticsCollectorTest {
clock);
}
private static ExoPlayer setupPlayer(RenderersFactory renderersFactory) {
private static ExoPlayer setupPlayer(RenderersFactory renderersFactory) throws TimeoutException {
return setupPlayer(renderersFactory, new FakeClock(/* isAutoAdvancing= */ true));
}
private static ExoPlayer setupPlayer(RenderersFactory renderersFactory, Clock clock) {
private static ExoPlayer setupPlayer(RenderersFactory renderersFactory, Clock clock)
throws TimeoutException {
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0));
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
@ -2122,6 +2124,7 @@ public final class DefaultAnalyticsCollectorTest {
.setRenderersFactory(renderersFactory)
.build();
player.setVideoSurface(surface);
advance(player).untilPendingCommandsAreFullyHandled();
return player;
}