Move setAudioAttributes from ExoPlayer to Player

PiperOrigin-RevId: 566607528
This commit is contained in:
jbibik 2023-09-19 06:03:53 -07:00 committed by Copybara-Service
parent f35e7bdc6a
commit 39c1ac7f3a
22 changed files with 366 additions and 28 deletions

View File

@ -23,6 +23,7 @@
([#11226](https://github.com/google/ExoPlayer/issues/11226)).
* Change `BaseRenderer.onStreamChanged()` to also receive a
`MediaPeriodId` argument.
* Move `ExoPlayer.setAudioAttributes` to the `Player` interface.
* Transformer:
* Changed `frameRate` and `durationUs` parameters of
`SampleConsumer.queueInputBitmap` to `TimestampIterator`.

View File

@ -759,6 +759,7 @@ package androidx.media3.common {
method public void seekToNextMediaItem();
method public void seekToPrevious();
method public void seekToPreviousMediaItem();
method public void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean);
method @Deprecated public void setDeviceMuted(boolean);
method public void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
method @Deprecated public void setDeviceVolume(@IntRange(from=0) int);
@ -807,6 +808,7 @@ package androidx.media3.common {
field public static final int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; // 0x8
field public static final int COMMAND_SEEK_TO_PREVIOUS = 7; // 0x7
field public static final int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; // 0x6
field public static final int COMMAND_SET_AUDIO_ATTRIBUTES = 35; // 0x23
field @Deprecated public static final int COMMAND_SET_DEVICE_VOLUME = 25; // 0x19
field public static final int COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS = 33; // 0x21
field public static final int COMMAND_SET_MEDIA_ITEM = 31; // 0x1f
@ -881,7 +883,7 @@ package androidx.media3.common {
field public static final int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1; // 0x1
}
@IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_GET_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_PLAYLIST_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS, androidx.media3.common.Player.COMMAND_RELEASE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command {
@IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_GET_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_PLAYLIST_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_SET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS, androidx.media3.common.Player.COMMAND_RELEASE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command {
}
public static final class Player.Commands {
@ -1347,7 +1349,6 @@ package androidx.media3.exoplayer {
method public void addAnalyticsListener(androidx.media3.exoplayer.analytics.AnalyticsListener);
method @Nullable public androidx.media3.exoplayer.ExoPlaybackException getPlayerError();
method public void removeAnalyticsListener(androidx.media3.exoplayer.analytics.AnalyticsListener);
method public void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean);
method public void setHandleAudioBecomingNoisy(boolean);
method public void setWakeMode(@androidx.media3.common.C.WakeMode int);
}
@ -1618,6 +1619,7 @@ package androidx.media3.session {
method public final void seekToPrevious();
method public final void seekToPreviousMediaItem();
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
method public final void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean);
method @Deprecated public final void setDeviceMuted(boolean);
method public final void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
method @Deprecated public final void setDeviceVolume(@IntRange(from=0) int);

View File

@ -811,6 +811,10 @@ public final class CastPlayer extends BasePlayer {
@Override
public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {}
/** This method is not supported and does nothing. */
@Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {}
// Internal methods.
// Call deprecated callbacks.

View File

@ -917,6 +917,12 @@ public class ForwardingPlayer implements Player {
player.setDeviceMuted(muted, flags);
}
/** Calls {@link Player#setAudioAttributes(AudioAttributes, boolean)} on the delegate. */
@Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
player.setAudioAttributes(audioAttributes, handleAudioFocus);
}
/** Returns the {@link Player} to which operations are forwarded. */
public Player getWrappedPlayer() {
return player;

View File

@ -491,6 +491,7 @@ public interface Player {
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_SET_AUDIO_ATTRIBUTES,
COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
@ -1607,6 +1608,7 @@ public interface Player {
* <li>{@link #COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS}
* <li>{@link #COMMAND_ADJUST_DEVICE_VOLUME}
* <li>{@link #COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS}
* <li>{@link #COMMAND_SET_AUDIO_ATTRIBUTES}
* <li>{@link #COMMAND_SET_VIDEO_SURFACE}
* <li>{@link #COMMAND_GET_TEXT}
* <li>{@link #COMMAND_SET_TRACK_SELECTION_PARAMETERS}
@ -1653,6 +1655,7 @@ public interface Player {
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
COMMAND_SET_AUDIO_ATTRIBUTES,
COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
@ -2005,6 +2008,14 @@ public interface Player {
*/
int COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS = 34;
/**
* Command to set the player's audio attributes.
*
* <p>The {@link #setAudioAttributes(AudioAttributes, boolean)} method must only be called if this
* command is {@linkplain #isCommandAvailable(int) available}.
*/
int COMMAND_SET_AUDIO_ATTRIBUTES = 35;
/**
* Command to set and clear the surface on which to render the video.
*
@ -3393,4 +3404,30 @@ public interface Player {
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
*/
void setDeviceMuted(boolean muted, @C.VolumeFlags int flags);
/**
* Sets the attributes for audio playback, used by the underlying audio track. If not set, the
* default audio attributes will be used. They are suitable for general media playback.
*
* <p>Setting the audio attributes during playback may introduce a short gap in audio output as
* the audio track is recreated. A new audio session id will also be generated.
*
* <p>If tunneling is enabled by the track selector, the specified audio attributes will be
* ignored, but they will take effect if audio is later played without tunneling.
*
* <p>If the device is running a build before platform API version 21, audio attributes cannot be
* set directly on the underlying audio track. In this case, the usage will be mapped onto an
* equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}.
*
* <p>If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link
* C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link
* IllegalArgumentException}.
*
* <p>This method must only be called if {@link #COMMAND_SET_AUDIO_ATTRIBUTES} is {@linkplain
* #getAvailableCommands() available}.
*
* @param audioAttributes The attributes to use for audio playback.
* @param handleAudioFocus True if the player should handle audio focus, false otherwise.
*/
void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus);
}

View File

@ -2888,6 +2888,20 @@ public abstract class SimpleBasePlayer extends BasePlayer {
/* placeholderStateSupplier= */ () -> state.buildUpon().setIsDeviceMuted(muted).build());
}
@Override
public final void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
verifyApplicationThreadAndInitState();
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
if (!shouldHandleCommand(Player.COMMAND_SET_AUDIO_ATTRIBUTES)) {
return;
}
updateStateForPendingOperation(
/* pendingOperation= */ handleSetAudioAttributes(audioAttributes, handleAudioFocus),
/* placeholderStateSupplier= */ () ->
state.buildUpon().setAudioAttributes(audioAttributes).build());
}
/**
* Invalidates the current state.
*
@ -3175,6 +3189,23 @@ public abstract class SimpleBasePlayer extends BasePlayer {
+ " COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS");
}
/**
* Handles calls to set the audio attributes.
*
* <p>Will only be called if {@link Player#COMMAND_SET_AUDIO_ATTRIBUTES} is available.
*
* @param audioAttributes The attributes to use for audio playback.
* @param handleAudioFocus True if the player should handle audio focus, false otherwise.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ForOverride
protected ListenableFuture<?> handleSetAudioAttributes(
AudioAttributes audioAttributes, boolean handleAudioFocus) {
throw new IllegalStateException(
"Missing implementation to handle COMMAND_SET_AUDIO_ATTRIBUTES");
}
/**
* Handles calls to set the video output.
*

View File

@ -3617,6 +3617,125 @@ public class SimpleBasePlayerTest {
assertThat(callForwarded.get()).isFalse();
}
@Test
public void setAudioAttributes_immediateHandling_updatesStateAndInformsListeners() {
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.build();
// Also change the audio attributes to ensure the updated state is used.
AudioAttributes newAudioAttributes =
new AudioAttributes.Builder()
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
.setUsage(C.USAGE_MEDIA)
.build();
State updatedState = state.buildUpon().setAudioAttributes(newAudioAttributes).build();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
private State playerState = state;
@Override
protected State getState() {
return playerState;
}
@Override
protected ListenableFuture<?> handleSetAudioAttributes(
AudioAttributes audioAttributes, boolean handleAudioFocus) {
playerState = updatedState;
return Futures.immediateVoidFuture();
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.setAudioAttributes(newAudioAttributes, /* handleAudioFocus= */ true);
assertThat(player.getAudioAttributes()).isEqualTo(newAudioAttributes);
verify(listener).onAudioAttributesChanged(newAudioAttributes);
verifyNoMoreInteractions(listener);
}
@Test
public void setAudioAttributes_asyncHandling_usesPlaceholderStateAndInformsListeners() {
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.build();
SettableFuture<?> future = SettableFuture.create();
AudioAttributes firstAudioAttributes =
new AudioAttributes.Builder()
.setContentType(C.AUDIO_CONTENT_TYPE_SONIFICATION)
.setUsage(C.USAGE_ALARM)
.build();
AudioAttributes lastAudioAttributes =
new AudioAttributes.Builder()
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
.setUsage(C.USAGE_MEDIA)
.build();
State updatedState = state.buildUpon().setAudioAttributes(lastAudioAttributes).build();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return future.isDone() ? updatedState : state;
}
@Override
protected ListenableFuture<?> handleSetAudioAttributes(
AudioAttributes audioAttributes, boolean handleAudioFocus) {
return future;
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.setAudioAttributes(firstAudioAttributes, /* handleAudioFocus= */ true);
// Verify placeholder state and listener calls.
assertThat(player.getAudioAttributes()).isEqualTo(firstAudioAttributes);
verify(listener).onAudioAttributesChanged(firstAudioAttributes);
verifyNoMoreInteractions(listener);
future.set(null);
// Verify actual state update.
assertThat(player.getAudioAttributes()).isEqualTo(lastAudioAttributes);
verify(listener).onAudioAttributesChanged(lastAudioAttributes);
verifyNoMoreInteractions(listener);
}
@Test
public void setAudioAttributes_withoutAvailableCommand_isNotForwarded() {
State state =
new State.Builder()
.setAvailableCommands(
new Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_SET_AUDIO_ATTRIBUTES)
.build())
.build();
AtomicBoolean callForwarded = new AtomicBoolean();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return state;
}
@Override
protected ListenableFuture<?> handleSetAudioAttributes(
AudioAttributes audioAttributes, boolean handleAudioFocus) {
callForwarded.set(true);
return Futures.immediateVoidFuture();
}
};
player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
assertThat(callForwarded.get()).isFalse();
}
@Test
public void setVideoSurface_immediateHandling_updatesStateAndInformsListeners() {
State state =

View File

@ -173,7 +173,7 @@ public interface ExoPlayer extends Player {
interface AudioComponent {
/**
* @deprecated Use {@link ExoPlayer#setAudioAttributes(AudioAttributes, boolean)} instead.
* @deprecated Use {@link Player#setAudioAttributes(AudioAttributes, boolean)} instead.
*/
@Deprecated
void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus);
@ -1514,29 +1514,6 @@ public interface ExoPlayer extends Player {
@Override
void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems);
/**
* Sets the attributes for audio playback, used by the underlying audio track. If not set, the
* default audio attributes will be used. They are suitable for general media playback.
*
* <p>Setting the audio attributes during playback may introduce a short gap in audio output as
* the audio track is recreated. A new audio session id will also be generated.
*
* <p>If tunneling is enabled by the track selector, the specified audio attributes will be
* ignored, but they will take effect if audio is later played without tunneling.
*
* <p>If the device is running a build before platform API version 21, audio attributes cannot be
* set directly on the underlying audio track. In this case, the usage will be mapped onto an
* equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}.
*
* <p>If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link
* C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link
* IllegalArgumentException}.
*
* @param audioAttributes The attributes to use for audio playback.
* @param handleAudioFocus True if the player should handle audio focus, false otherwise.
*/
void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus);
/**
* Sets the ID of the audio session to attach to the underlying {@link android.media.AudioTrack}.
*

View File

@ -314,6 +314,7 @@ import java.util.concurrent.TimeoutException;
COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_GET_TRACKS,
COMMAND_GET_AUDIO_ATTRIBUTES,
COMMAND_SET_AUDIO_ATTRIBUTES,
COMMAND_GET_VOLUME,
COMMAND_SET_VOLUME,
COMMAND_SET_VIDEO_SURFACE,

View File

@ -38,6 +38,7 @@ import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_AUDIO_ATTRIBUTES;
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME;
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS;
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
@ -9456,6 +9457,7 @@ public final class ExoPlayerTest {
assertThat(player.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_AUDIO_ATTRIBUTES)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_GET_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_VOLUME)).isTrue();
assertThat(player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isTrue();
@ -14143,6 +14145,7 @@ public final class ExoPlayerTest {
COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_SET_MEDIA_ITEM,
COMMAND_GET_AUDIO_ATTRIBUTES,
COMMAND_SET_AUDIO_ATTRIBUTES,
COMMAND_GET_VOLUME,
COMMAND_SET_VOLUME,
COMMAND_SET_VIDEO_SURFACE,

View File

@ -40,6 +40,7 @@ oneway interface IMediaSession {
void decreaseDeviceVolumeWithFlags(IMediaController caller, int seq, int flags) = 3052;
void setDeviceMuted(IMediaController caller, int seq, boolean muted) = 3005;
void setDeviceMutedWithFlags(IMediaController caller, int seq, boolean muted, int flags) = 3053;
void setAudioAttributes(IMediaController caller, int seq, in Bundle audioAttributes, boolean handleAudioFocus) = 3056;
void setMediaItem(
IMediaController caller,
int seq,
@ -120,7 +121,7 @@ oneway interface IMediaSession {
void setRatingWithMediaId(
IMediaController caller, int seq, String mediaId, in Bundle rating) = 3048;
void setRating(IMediaController caller, int seq, in Bundle rating) = 3049;
// Next Id for MediaSession: 3056
// Next Id for MediaSession: 3057
void getLibraryRoot(IMediaController caller, int seq, in Bundle libraryParams) = 4000;
void getItem(IMediaController caller, int seq, String mediaId) = 4001;

View File

@ -1793,6 +1793,16 @@ public class MediaController implements Player {
impl.setDeviceMuted(muted, flags);
}
@Override
public final void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
verifyApplicationThread();
if (!isConnected()) {
Log.w(TAG, "The controller is not connected. Ignoring setAudioAttributes().");
return;
}
impl.setAudioAttributes(audioAttributes, handleAudioFocus);
}
@Override
public final MediaMetadata getMediaMetadata() {
verifyApplicationThread();
@ -2139,6 +2149,8 @@ public class MediaController implements Player {
void setDeviceMuted(boolean muted, @C.VolumeFlags int flags);
void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus);
boolean getPlayWhenReady();
@PlaybackSuppressionReason

View File

@ -1672,6 +1672,26 @@ import org.checkerframework.checker.nullness.qual.NonNull;
}
}
@Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
if (!isPlayerCommandAvailable(Player.COMMAND_SET_AUDIO_ATTRIBUTES)) {
return;
}
dispatchRemoteSessionTaskWithPlayerCommand(
(iSession, seq) ->
iSession.setAudioAttributes(
controllerStub, seq, audioAttributes.toBundle(), handleAudioFocus));
if (!playerInfo.audioAttributes.equals(audioAttributes)) {
playerInfo = playerInfo.copyWithAudioAttributes(audioAttributes);
listeners.queueEvent(
/* eventFlag= */ Player.EVENT_AUDIO_ATTRIBUTES_CHANGED,
listener -> listener.onAudioAttributesChanged(audioAttributes));
listeners.flushEvents();
}
}
@Override
public VideoSize getVideoSize() {
return playerInfo.videoSize;

View File

@ -1206,6 +1206,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
controllerCompat.adjustVolume(direction, flags);
}
@Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
Log.w(TAG, "Legacy session doesn't support setting audio attributes remotely");
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
if (playWhenReady) {

View File

@ -29,6 +29,7 @@ import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_AUDIO_ATTRIBUTES;
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME;
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS;
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
@ -65,6 +66,7 @@ import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
import androidx.media.MediaSessionManager;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.BundleListRetriever;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
@ -1541,6 +1543,25 @@ import java.util.concurrent.ExecutionException;
sendSessionResultSuccess(player -> player.setDeviceMuted(muted, flags)));
}
@Override
public void setAudioAttributes(
@Nullable IMediaController caller,
int sequenceNumber,
Bundle audioAttributes,
boolean handleAudioFocus) {
if (caller == null) {
return;
}
queueSessionTaskWithPlayerCommand(
caller,
sequenceNumber,
COMMAND_SET_AUDIO_ATTRIBUTES,
sendSessionResultSuccess(
player ->
player.setAudioAttributes(
AudioAttributes.CREATOR.fromBundle(audioAttributes), handleAudioFocus)));
}
@Override
public void setPlayWhenReady(
@Nullable IMediaController caller, int sequenceNumber, boolean playWhenReady) {

View File

@ -81,6 +81,7 @@ interface IRemoteMediaController {
void decreaseDeviceVolumeWithFlags(String controllerId, int flags);
void setDeviceMuted(String controllerId, boolean muted);
void setDeviceMutedWithFlags(String controllerId, boolean muted, int flags);
void setAudioAttributes(String controllerId, in Bundle audioAttributes, boolean handleAudioFocus);
Bundle sendCustomCommand(String controllerId, in Bundle command, in Bundle args);
Bundle setRatingWithMediaId(String controllerId, String mediaId, in Bundle rating);
Bundle setRating(String controllerId, in Bundle rating);

View File

@ -28,6 +28,7 @@ import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.MediaItem;
@ -711,6 +712,52 @@ public class MediaControllerStateMaskingTest {
.containsExactly(Player.EVENT_DEVICE_VOLUME_CHANGED);
}
@Test
public void setAudioAttributes() throws Exception {
AudioAttributes originalAttrs =
new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MUSIC).build();
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setAudioAttributes(originalAttrs).build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
CountDownLatch latch = new CountDownLatch(2);
AtomicReference<AudioAttributes> audioAttributesFromCallbackRef = new AtomicReference<>();
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
Player.Listener listener =
new Player.Listener() {
@Override
public void onAudioAttributesChanged(AudioAttributes audioAttributes) {
audioAttributesFromCallbackRef.set(originalAttrs);
latch.countDown();
}
@Override
public void onEvents(Player player, Player.Events events) {
onEventsRef.set(events);
latch.countDown();
}
};
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
AtomicReference<AudioAttributes> audioAttributesFromGetterRef = new AtomicReference<>();
AudioAttributes newAttributes =
new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_SPEECH).build();
threadTestRule
.getHandler()
.postAndSync(
() -> {
controller.setAudioAttributes(newAttributes, /* handleAudioFocus= */ false);
audioAttributesFromGetterRef.set(controller.getAudioAttributes());
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(audioAttributesFromCallbackRef.get()).isEqualTo(originalAttrs);
assertThat(audioAttributesFromGetterRef.get()).isEqualTo(newAttributes);
assertThat(getEventsAsList(onEventsRef.get()))
.containsExactly(Player.EVENT_AUDIO_ATTRIBUTES_CHANGED);
}
@Test
public void prepare() throws Exception {
int testPlaybackState = Player.STATE_ENDED;

View File

@ -27,6 +27,8 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaControllerCompat;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
@ -268,7 +270,7 @@ public class MediaSessionPlayerTest {
remoteControllerTestRule.createRemoteController(session.getToken());
// The controller should only be able to see the current item without Timeline access.
controller.seekTo(/* mediaItemIndex= */ 0, /* seekPositionMs= */ 2000);
controller.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 2000);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX, TIMEOUT_MS);
controller.release();
session.release();
@ -828,6 +830,20 @@ public class MediaSessionPlayerTest {
assertThat(player.deviceMuted).isTrue();
}
@Test
public void setAudioAttributes() throws Exception {
AudioAttributes audioAttributes =
new AudioAttributes.Builder()
.setContentType(C.AUDIO_CONTENT_TYPE_SONIFICATION)
.setUsage(C.USAGE_ALARM)
.build();
controller.setAudioAttributes(audioAttributes, /* handleAudioFocus= */ true);
player.awaitMethodCalled(MockPlayer.METHOD_SET_AUDIO_ATTRIBUTES, TIMEOUT_MS);
assertThat(player.getAudioAttributes()).isEqualTo(audioAttributes);
}
@Test
public void seekBack() throws Exception {
controller.seekBack();

View File

@ -28,6 +28,7 @@ import android.media.AudioManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.PlaybackParameters;
@ -697,6 +698,18 @@ public class MediaControllerProviderService extends Service {
});
}
@Override
public void setAudioAttributes(
String controllerId, Bundle audioAttributes, boolean handleAudioFocus)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setAudioAttributes(
AudioAttributes.CREATOR.fromBundle(audioAttributes), handleAudioFocus);
});
}
@Override
public void release(String controllerId) throws RemoteException {
runOnHandler(

View File

@ -257,6 +257,9 @@ public class MockPlayer implements Player {
/** Maps to {@link Player#replaceMediaItems(int, int, List)} . */
public static final int METHOD_REPLACE_MEDIA_ITEMS = 47;
/** Maps to {@link Player#setAudioAttributes(AudioAttributes, boolean)}. */
public static final int METHOD_SET_AUDIO_ATTRIBUTES = 48;
private final boolean changePlayerStateWithTransportControl;
private final Looper applicationLooper;
private final ArraySet<Listener> listeners = new ArraySet<>();
@ -766,6 +769,12 @@ public class MockPlayer implements Player {
checkNotNull(conditionVariables.get(METHOD_SET_DEVICE_MUTED_WITH_FLAGS)).open();
}
@Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
this.audioAttributes = audioAttributes;
checkNotNull(conditionVariables.get(METHOD_SET_AUDIO_ATTRIBUTES)).open();
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
this.playWhenReady = playWhenReady;
@ -1478,6 +1487,7 @@ public class MockPlayer implements Player {
.put(METHOD_SET_SHUFFLE_MODE, new ConditionVariable())
.put(METHOD_SET_TRACK_SELECTION_PARAMETERS, new ConditionVariable())
.put(METHOD_SET_VOLUME, new ConditionVariable())
.put(METHOD_SET_AUDIO_ATTRIBUTES, new ConditionVariable())
.put(METHOD_STOP, new ConditionVariable())
.put(METHOD_REPLACE_MEDIA_ITEM, new ConditionVariable())
.put(METHOD_REPLACE_MEDIA_ITEMS, new ConditionVariable())

View File

@ -30,6 +30,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
@ -314,6 +315,11 @@ public class RemoteMediaController {
binder.setDeviceMutedWithFlags(controllerId, muted, flags);
}
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus)
throws RemoteException {
binder.setAudioAttributes(controllerId, audioAttributes.toBundle(), handleAudioFocus);
}
public SessionResult sendCustomCommand(SessionCommand command, Bundle args)
throws RemoteException {
Bundle result = binder.sendCustomCommand(controllerId, command.toBundle(), args);

View File

@ -431,4 +431,9 @@ public class StubPlayer extends BasePlayer {
public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
throw new UnsupportedOperationException();
}
@Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
throw new UnsupportedOperationException();
}
}