Commit 0bac4e24 by tonihei Committed by Rohit Singh

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
parent 8d17faea
......@@ -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:
......
......@@ -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 {
......
......@@ -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();
};
}
......@@ -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);
......
......@@ -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;
......
......@@ -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();
}
......
......@@ -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(
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment