Add routing controller id to DeviceInfo

And forward the id to the VolumeProviderCompat and read it from the platform
MediaController for compatibility.

PiperOrigin-RevId: 526046892
This commit is contained in:
tonihei 2023-04-21 16:42:54 +01:00 committed by Rohit Singh
parent 8d17faea33
commit 0bac4e24e4
12 changed files with 101 additions and 18 deletions

View File

@ -37,6 +37,8 @@
frames is dequeued without reading the 'end of stream' sample.
([#11079](https://github.com/google/ExoPlayer/issues/11079)).
* Add `Builder` for `DeviceInfo` and deprecate existing constructor.
* Add `DeviceInfo.routingControllerId` to specify the routing controller
ID for remote playbacks.
* Session:
* Deprecate 4 volume-controlling methods in `Player` and add overloaded
methods which allow users to specify volume flags:

View File

@ -181,6 +181,7 @@ package androidx.media3.common {
field @IntRange(from=0) public final int maxVolume;
field @IntRange(from=0) public final int minVolume;
field @androidx.media3.common.DeviceInfo.PlaybackType public final int playbackType;
field @Nullable public final String routingControllerId;
}
public static final class DeviceInfo.Builder {
@ -188,6 +189,7 @@ package androidx.media3.common {
method public androidx.media3.common.DeviceInfo build();
method public androidx.media3.common.DeviceInfo.Builder setMaxVolume(@IntRange(from=0) int);
method public androidx.media3.common.DeviceInfo.Builder setMinVolume(@IntRange(from=0) int);
method public androidx.media3.common.DeviceInfo.Builder setRoutingControllerId(@Nullable String);
}
@IntDef({androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_LOCAL, androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_REMOTE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface DeviceInfo.PlaybackType {

View File

@ -17,6 +17,7 @@ package androidx.media3.common;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.MediaRouter2;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
@ -57,6 +58,7 @@ public final class DeviceInfo implements Bundleable {
private int minVolume;
private int maxVolume;
@Nullable private String routingControllerId;
/**
* Creates the builder.
@ -93,6 +95,28 @@ public final class DeviceInfo implements Bundleable {
return this;
}
/**
* Sets the {@linkplain MediaRouter2.RoutingController#getId() routing controller id} of the
* associated {@link MediaRouter2.RoutingController}.
*
* <p>This id allows mapping this device information to a routing controller, which provides
* information about the media route and allows controlling its volume.
*
* <p>The set value must be null if {@link DeviceInfo#playbackType} is {@link
* #PLAYBACK_TYPE_LOCAL}.
*
* @param routingControllerId The {@linkplain MediaRouter2.RoutingController#getId() routing
* controller id} of the associated {@link MediaRouter2.RoutingController}, or null to leave
* it unspecified.
* @return This builder.
*/
@CanIgnoreReturnValue
public Builder setRoutingControllerId(@Nullable String routingControllerId) {
Assertions.checkArgument(playbackType != PLAYBACK_TYPE_LOCAL || routingControllerId == null);
this.routingControllerId = routingControllerId;
return this;
}
/** Builds the {@link DeviceInfo}. */
public DeviceInfo build() {
Assertions.checkArgument(minVolume <= maxVolume);
@ -108,6 +132,15 @@ public final class DeviceInfo implements Bundleable {
/** The maximum volume that the device supports, or {@code 0} if unspecified. */
@IntRange(from = 0)
public final int maxVolume;
/**
* The {@linkplain MediaRouter2.RoutingController#getId() routing controller id} of the associated
* {@link MediaRouter2.RoutingController}, or null if unset or {@link #playbackType} is {@link
* #PLAYBACK_TYPE_LOCAL}.
*
* <p>This id allows mapping this device information to a routing controller, which provides
* information about the media route and allows controlling its volume.
*/
@Nullable public final String routingControllerId;
/**
* @deprecated Use {@link Builder} instead.
@ -125,6 +158,7 @@ public final class DeviceInfo implements Bundleable {
this.playbackType = builder.playbackType;
this.minVolume = builder.minVolume;
this.maxVolume = builder.maxVolume;
this.routingControllerId = builder.routingControllerId;
}
@Override
@ -138,7 +172,8 @@ public final class DeviceInfo implements Bundleable {
DeviceInfo other = (DeviceInfo) obj;
return playbackType == other.playbackType
&& minVolume == other.minVolume
&& maxVolume == other.maxVolume;
&& maxVolume == other.maxVolume
&& Util.areEqual(routingControllerId, other.routingControllerId);
}
@Override
@ -147,6 +182,7 @@ public final class DeviceInfo implements Bundleable {
result = 31 * result + playbackType;
result = 31 * result + minVolume;
result = 31 * result + maxVolume;
result = 31 * result + (routingControllerId == null ? 0 : routingControllerId.hashCode());
return result;
}
@ -155,6 +191,7 @@ public final class DeviceInfo implements Bundleable {
private static final String FIELD_PLAYBACK_TYPE = Util.intToStringMaxRadix(0);
private static final String FIELD_MIN_VOLUME = Util.intToStringMaxRadix(1);
private static final String FIELD_MAX_VOLUME = Util.intToStringMaxRadix(2);
private static final String FIELD_ROUTING_CONTROLLER_ID = Util.intToStringMaxRadix(3);
@UnstableApi
@Override
@ -169,6 +206,9 @@ public final class DeviceInfo implements Bundleable {
if (maxVolume != 0) {
bundle.putInt(FIELD_MAX_VOLUME, maxVolume);
}
if (routingControllerId != null) {
bundle.putString(FIELD_ROUTING_CONTROLLER_ID, routingControllerId);
}
return bundle;
}
@ -180,9 +220,11 @@ public final class DeviceInfo implements Bundleable {
bundle.getInt(FIELD_PLAYBACK_TYPE, /* defaultValue= */ PLAYBACK_TYPE_LOCAL);
int minVolume = bundle.getInt(FIELD_MIN_VOLUME, /* defaultValue= */ 0);
int maxVolume = bundle.getInt(FIELD_MAX_VOLUME, /* defaultValue= */ 0);
@Nullable String routingControllerId = bundle.getString(FIELD_ROUTING_CONTROLLER_ID);
return new DeviceInfo.Builder(playbackType)
.setMinVolume(minVolume)
.setMaxVolume(maxVolume)
.setRoutingControllerId(routingControllerId)
.build();
};
}

View File

@ -31,6 +31,7 @@ public class DeviceInfoTest {
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE)
.setMinVolume(1)
.setMaxVolume(9)
.setRoutingControllerId("route")
.build();
assertThat(DeviceInfo.CREATOR.fromBundle(deviceInfo.toBundle())).isEqualTo(deviceInfo);

View File

@ -1453,7 +1453,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
controllerCompat.getFlags(),
controllerCompat.isSessionReady(),
controllerCompat.getRatingType(),
getInstance().getTimeDiffMs());
getInstance().getTimeDiffMs(),
getRoutingControllerId(controllerCompat));
Pair<@NullableType Integer, @NullableType Integer> reasons =
calculateDiscontinuityAndTransitionReason(
legacyPlayerInfo,
@ -1642,6 +1643,22 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
listeners.flushEvents();
}
@Nullable
private static String getRoutingControllerId(MediaControllerCompat controllerCompat) {
if (Util.SDK_INT < 30) {
return null;
}
android.media.session.MediaController fwkController =
(android.media.session.MediaController) controllerCompat.getMediaController();
@Nullable
android.media.session.MediaController.PlaybackInfo playbackInfo =
fwkController.getPlaybackInfo();
if (playbackInfo == null) {
return null;
}
return playbackInfo.getVolumeControlId();
}
private static <T> void ignoreFuture(Future<T> unused) {
// Ignore return value of the future because legacy session cannot get result back.
}
@ -1816,7 +1833,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
long sessionFlags,
boolean isSessionReady,
@RatingCompat.Style int ratingType,
long timeDiffMs) {
long timeDiffMs,
@Nullable String routingControllerId) {
QueueTimeline currentTimeline;
MediaMetadata mediaMetadata;
int currentMediaItemIndex;
@ -1963,7 +1981,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
newLegacyPlayerInfo.mediaMetadataCompat,
timeDiffMs);
boolean isPlaying = MediaUtils.convertToIsPlaying(newLegacyPlayerInfo.playbackStateCompat);
DeviceInfo deviceInfo = MediaUtils.convertToDeviceInfo(newLegacyPlayerInfo.playbackInfoCompat);
DeviceInfo deviceInfo =
MediaUtils.convertToDeviceInfo(newLegacyPlayerInfo.playbackInfoCompat, routingControllerId);
int deviceVolume = MediaUtils.convertToDeviceVolume(newLegacyPlayerInfo.playbackInfoCompat);
boolean deviceMuted = MediaUtils.convertToIsDeviceMuted(newLegacyPlayerInfo.playbackInfoCompat);
long seekBackIncrementMs = oldControllerInfo.playerInfo.seekBackIncrementMs;

View File

@ -1331,7 +1331,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Converts {@link MediaControllerCompat.PlaybackInfo} to {@link DeviceInfo}. */
public static DeviceInfo convertToDeviceInfo(
@Nullable MediaControllerCompat.PlaybackInfo playbackInfoCompat) {
@Nullable MediaControllerCompat.PlaybackInfo playbackInfoCompat,
@Nullable String routingControllerId) {
if (playbackInfoCompat == null) {
return DeviceInfo.UNKNOWN;
}
@ -1341,6 +1342,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
? DeviceInfo.PLAYBACK_TYPE_REMOTE
: DeviceInfo.PLAYBACK_TYPE_LOCAL)
.setMaxVolume(playbackInfoCompat.getMaxVolume())
.setRoutingControllerId(routingControllerId)
.build();
}

View File

@ -1028,7 +1028,9 @@ import java.util.List;
Handler handler = new Handler(getApplicationLooper());
int currentVolume = getDeviceVolumeWithCommandCheck();
int legacyVolumeFlag = C.VOLUME_FLAG_SHOW_UI;
return new VolumeProviderCompat(volumeControlType, getDeviceInfo().maxVolume, currentVolume) {
DeviceInfo deviceInfo = getDeviceInfo();
return new VolumeProviderCompat(
volumeControlType, deviceInfo.maxVolume, currentVolume, deviceInfo.routingControllerId) {
@Override
public void onSetVolumeTo(int volume) {
postOrRun(

View File

@ -29,7 +29,7 @@ interface IRemoteMediaSessionCompat {
Bundle getSessionToken(String sessionTag);
void release(String sessionTag);
void setPlaybackToLocal(String sessionTag, int stream);
void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume, int currentVolume);
void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume, int currentVolume, @nullable String routingControllerId);
void setPlaybackState(String sessionTag, in Bundle stateBundle);
void setMetadata(String sessionTag, in Bundle metadataBundle);
void setQueue(String sessionTag, in Bundle queueBundle);

View File

@ -246,7 +246,8 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
session.setPlaybackToRemote(
/* volumeControl= */ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
/* maxVolume= */ 100,
/* currentVolume= */ 50);
/* currentVolume= */ 50,
/* routingSessionId= */ "route");
MediaController controller = controllerTestRule.createController(session.getSessionToken());
CountDownLatch latch = new CountDownLatch(2);
AtomicReference<AudioAttributes> audioAttributesParamRef = new AtomicReference<>();
@ -305,15 +306,18 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
}
};
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
String testRoutingSessionId = Util.SDK_INT >= 30 ? "route" : null;
session.setPlaybackToRemote(
/* volumeControl= */ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
/* maxVolume= */ 100,
/* currentVolume= */ 50);
/* currentVolume= */ 50,
testRoutingSessionId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(deviceInfoParamRef.get().playbackType).isEqualTo(DeviceInfo.PLAYBACK_TYPE_REMOTE);
assertThat(deviceInfoParamRef.get().maxVolume).isEqualTo(100);
assertThat(deviceInfoParamRef.get().routingControllerId).isEqualTo(testRoutingSessionId);
assertThat(deviceInfoGetterRef.get()).isEqualTo(deviceInfoParamRef.get());
assertThat(deviceInfoOnEventsRef.get()).isEqualTo(deviceInfoGetterRef.get());
assertThat(getEventsAsList(onEvents.get())).contains(Player.EVENT_DEVICE_VOLUME_CHANGED);
@ -348,7 +352,8 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
session.setPlaybackToRemote(
/* volumeControl= */ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
/* maxVolume= */ 100,
/* currentVolume= */ 50);
/* currentVolume= */ 50,
/* routingSessionId= */ "route");
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(deviceVolumeParam.get()).isEqualTo(50);

View File

@ -1548,6 +1548,7 @@ public class MediaControllerWithMediaSessionCompatTest {
int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
int maxVolume = 100;
int currentVolume = 45;
String routingSessionId = Util.SDK_INT >= 30 ? "route" : null;
AtomicReference<DeviceInfo> deviceInfoRef = new AtomicReference<>();
CountDownLatch latchForDeviceInfo = new CountDownLatch(1);
@ -1572,11 +1573,12 @@ public class MediaControllerWithMediaSessionCompatTest {
MediaController controller = controllerTestRule.createController(session.getSessionToken());
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
session.setPlaybackToRemote(volumeControlType, maxVolume, currentVolume);
session.setPlaybackToRemote(volumeControlType, maxVolume, currentVolume, routingSessionId);
assertThat(latchForDeviceInfo.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(latchForDeviceVolume.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(deviceInfoRef.get().maxVolume).isEqualTo(maxVolume);
assertThat(deviceInfoRef.get().routingControllerId).isEqualTo(routingSessionId);
}
@Test
@ -1588,7 +1590,8 @@ public class MediaControllerWithMediaSessionCompatTest {
session.setPlaybackToRemote(
VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
/* maxVolume= */ 100,
/* currentVolume= */ 45);
/* currentVolume= */ 45,
/* routingSessionId= */ "route");
int testLocalStreamType = AudioManager.STREAM_ALARM;
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

View File

@ -120,11 +120,15 @@ public class MediaSessionCompatProviderService extends Service {
@Override
public void setPlaybackToRemote(
String sessionTag, int volumeControl, int maxVolume, int currentVolume)
String sessionTag,
int volumeControl,
int maxVolume,
int currentVolume,
@Nullable String routingControllerId)
throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setPlaybackToRemote(
new VolumeProviderCompat(volumeControl, maxVolume, currentVolume) {
new VolumeProviderCompat(volumeControl, maxVolume, currentVolume, routingControllerId) {
@Override
public void onSetVolumeTo(int volume) {
setCurrentVolume(volume);

View File

@ -114,12 +114,13 @@ public class RemoteMediaSessionCompat {
}
/**
* Since we cannot pass VolumeProviderCompat directly, we pass volumeControl, maxVolume,
* currentVolume instead.
* Since we cannot pass VolumeProviderCompat directly, we pass the individual parameters instead.
*/
public void setPlaybackToRemote(int volumeControl, int maxVolume, int currentVolume)
public void setPlaybackToRemote(
int volumeControl, int maxVolume, int currentVolume, @Nullable String routingControllerId)
throws RemoteException {
binder.setPlaybackToRemote(sessionTag, volumeControl, maxVolume, currentVolume);
binder.setPlaybackToRemote(
sessionTag, volumeControl, maxVolume, currentVolume, routingControllerId);
}
public void setPlaybackState(PlaybackStateCompat state) throws RemoteException {