mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Move audio session id generation to playback thread
PiperOrigin-RevId: 726556015
This commit is contained in:
parent
22853a5c4c
commit
385498c24e
@ -6,6 +6,10 @@
|
|||||||
* Upgrade Kotlin from 1.9.20 to 2.0.20 and use Compose Compiler Gradle
|
* 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.
|
plugin. Upgrade KotlinX Coroutines library from 1.8.1 to 1.9.0.
|
||||||
* ExoPlayer:
|
* 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:
|
* Transformer:
|
||||||
* Add `MediaProjectionAssetLoader`, which provides media from a
|
* Add `MediaProjectionAssetLoader`, which provides media from a
|
||||||
`MediaProjection` for screen recording, and add support for screen
|
`MediaProjection` for screen recording, and add support for screen
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer;
|
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_AUDIO;
|
||||||
import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION;
|
import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION;
|
||||||
import static androidx.media3.common.C.TRACK_TYPE_IMAGE;
|
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.VideoSize;
|
||||||
import androidx.media3.common.text.Cue;
|
import androidx.media3.common.text.Cue;
|
||||||
import androidx.media3.common.text.CueGroup;
|
import androidx.media3.common.text.CueGroup;
|
||||||
|
import androidx.media3.common.util.BackgroundThreadStateHandler;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.ConditionVariable;
|
import androidx.media3.common.util.ConditionVariable;
|
||||||
import androidx.media3.common.util.HandlerWrapper;
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
@ -178,6 +180,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
@Nullable private AudioManager audioManager;
|
@Nullable private AudioManager audioManager;
|
||||||
private final boolean suppressPlaybackOnUnsuitableOutput;
|
private final boolean suppressPlaybackOnUnsuitableOutput;
|
||||||
@Nullable private final SuitableOutputChecker suitableOutputChecker;
|
@Nullable private final SuitableOutputChecker suitableOutputChecker;
|
||||||
|
private final BackgroundThreadStateHandler<Integer> audioSessionIdState;
|
||||||
|
|
||||||
private @RepeatMode int repeatMode;
|
private @RepeatMode int repeatMode;
|
||||||
private boolean shuffleModeEnabled;
|
private boolean shuffleModeEnabled;
|
||||||
@ -205,7 +208,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
private Size surfaceSize;
|
private Size surfaceSize;
|
||||||
@Nullable private DecoderCounters videoDecoderCounters;
|
@Nullable private DecoderCounters videoDecoderCounters;
|
||||||
@Nullable private DecoderCounters audioDecoderCounters;
|
@Nullable private DecoderCounters audioDecoderCounters;
|
||||||
private int audioSessionId;
|
|
||||||
private AudioAttributes audioAttributes;
|
private AudioAttributes audioAttributes;
|
||||||
private float volume;
|
private float volume;
|
||||||
private boolean skipSilenceEnabled;
|
private boolean skipSilenceEnabled;
|
||||||
@ -390,7 +392,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
playlistMetadata = MediaMetadata.EMPTY;
|
playlistMetadata = MediaMetadata.EMPTY;
|
||||||
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
|
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
|
||||||
maskingWindowIndex = C.INDEX_UNSET;
|
maskingWindowIndex = C.INDEX_UNSET;
|
||||||
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
|
|
||||||
currentCueGroup = CueGroup.EMPTY_TIME_ZERO;
|
currentCueGroup = CueGroup.EMPTY_TIME_ZERO;
|
||||||
throwsWhenUsingWrongThread = true;
|
throwsWhenUsingWrongThread = true;
|
||||||
|
|
||||||
@ -401,6 +402,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
internalPlayer.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs);
|
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 =
|
audioBecomingNoisyManager =
|
||||||
new AudioBecomingNoisyManager(
|
new AudioBecomingNoisyManager(
|
||||||
builder.context, playbackLooper, builder.looper, componentListener, clock);
|
builder.context, playbackLooper, builder.looper, componentListener, clock);
|
||||||
@ -440,8 +452,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
surfaceSize = Size.UNKNOWN;
|
surfaceSize = Size.UNKNOWN;
|
||||||
|
|
||||||
internalPlayer.setAudioAttributes(audioAttributes);
|
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_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
|
||||||
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
|
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
|
||||||
sendRendererMessage(
|
sendRendererMessage(
|
||||||
@ -1491,24 +1501,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
@Override
|
@Override
|
||||||
public void setAudioSessionId(int audioSessionId) {
|
public void setAudioSessionId(int audioSessionId) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
if (this.audioSessionId == audioSessionId) {
|
if (audioSessionIdState.get() == audioSessionId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
|
audioSessionIdState.updateStateAsync(
|
||||||
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
|
/* placeholderState= */ previousId ->
|
||||||
}
|
audioSessionId != AUDIO_SESSION_ID_UNSET ? audioSessionId : previousId,
|
||||||
this.audioSessionId = audioSessionId;
|
/* backgroundStateUpdate= */ previousId ->
|
||||||
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
|
audioSessionId != AUDIO_SESSION_ID_UNSET
|
||||||
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
|
? audioSessionId
|
||||||
int finalAudioSessionId = audioSessionId;
|
: Util.generateAudioSessionIdV21(applicationContext));
|
||||||
listeners.sendEvent(
|
|
||||||
EVENT_AUDIO_SESSION_ID, listener -> listener.onAudioSessionIdChanged(finalAudioSessionId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAudioSessionId() {
|
public int getAudioSessionId() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
return audioSessionId;
|
return audioSessionIdState.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
private static DeviceInfo createDeviceInfo(@Nullable StreamVolumeManager streamVolumeManager) {
|
||||||
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL)
|
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL)
|
||||||
.setMinVolume(streamVolumeManager != null ? streamVolumeManager.getMinVolume() : 0)
|
.setMinVolume(streamVolumeManager != null ? streamVolumeManager.getMinVolume() : 0)
|
||||||
|
@ -7276,6 +7276,7 @@ public class ExoPlayerTest {
|
|||||||
.build();
|
.build();
|
||||||
player.setRepeatMode(Player.REPEAT_MODE_ONE);
|
player.setRepeatMode(Player.REPEAT_MODE_ONE);
|
||||||
player.setMediaSource(mediaSource);
|
player.setMediaSource(mediaSource);
|
||||||
|
advance(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
|
||||||
player.prepare();
|
player.prepare();
|
||||||
advance(player).untilPendingCommandsAreFullyHandled();
|
advance(player).untilPendingCommandsAreFullyHandled();
|
||||||
@ -12440,6 +12441,7 @@ public class ExoPlayerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void onEvents_correspondToListenerCalls() throws Exception {
|
public void onEvents_correspondToListenerCalls() throws Exception {
|
||||||
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();
|
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();
|
||||||
|
advance(player).untilPendingCommandsAreFullyHandled();
|
||||||
Player.Listener listener = mock(Player.Listener.class);
|
Player.Listener listener = mock(Player.Listener.class);
|
||||||
player.addListener(listener);
|
player.addListener(listener);
|
||||||
Format formatWithStaticMetadata =
|
Format formatWithStaticMetadata =
|
||||||
@ -16302,6 +16304,58 @@ public class ExoPlayerTest {
|
|||||||
assertThat(reportedSpeedChanges).containsExactly(2f, 1.5f, 1f).inOrder();
|
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.
|
// Internal methods.
|
||||||
|
|
||||||
private void addWatchAsSystemFeature() {
|
private void addWatchAsSystemFeature() {
|
||||||
|
@ -135,6 +135,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.Test;
|
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);
|
Clock clock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||||
return setupPlayer(
|
return setupPlayer(
|
||||||
/* renderersFactory= */ (eventHandler,
|
/* renderersFactory= */ (eventHandler,
|
||||||
@ -2110,11 +2111,12 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
clock);
|
clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ExoPlayer setupPlayer(RenderersFactory renderersFactory) {
|
private static ExoPlayer setupPlayer(RenderersFactory renderersFactory) throws TimeoutException {
|
||||||
return setupPlayer(renderersFactory, new FakeClock(/* isAutoAdvancing= */ true));
|
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));
|
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0));
|
||||||
ExoPlayer player =
|
ExoPlayer player =
|
||||||
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
||||||
@ -2122,6 +2124,7 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
.setRenderersFactory(renderersFactory)
|
.setRenderersFactory(renderersFactory)
|
||||||
.build();
|
.build();
|
||||||
player.setVideoSurface(surface);
|
player.setVideoSurface(surface);
|
||||||
|
advance(player).untilPendingCommandsAreFullyHandled();
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user