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:
parent
8d17faea33
commit
0bac4e24e4
@ -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:
|
||||
|
2
api.txt
2
api.txt
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user