Skip to content

Commit 405365c

Browse files
AquilesCantacopybara-github
authored andcommitted
Implement device volume adjustment in CastPlayer
Issue: #2089 PiperOrigin-RevId: 747861812
1 parent 70e7121 commit 405365c

File tree

3 files changed

+168
-28
lines changed

3 files changed

+168
-28
lines changed

RELEASENOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@
7171
* MIDI extension:
7272
* Leanback extension:
7373
* Cast extension:
74+
* Add support for `getDeviceVolume()`, `setDeviceVolume()`,
75+
`getDeviceMuted()`, and `setDeviceMuted()`
76+
([#2089](https://github.com/androidx/media/issues/2089)).
7477
* Test Utilities:
7578
* Removed `transformer.TestUtil.addAudioDecoders(String...)`,
7679
`transformer.TestUtil.addAudioEncoders(String...)`, and

libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import android.media.RouteDiscoveryPreference;
3030
import android.os.Handler;
3131
import android.os.Looper;
32+
import android.util.Range;
3233
import android.view.Surface;
3334
import android.view.SurfaceHolder;
3435
import android.view.SurfaceView;
@@ -59,6 +60,7 @@
5960
import androidx.media3.common.util.Size;
6061
import androidx.media3.common.util.UnstableApi;
6162
import androidx.media3.common.util.Util;
63+
import com.google.android.gms.cast.Cast;
6264
import com.google.android.gms.cast.CastStatusCodes;
6365
import com.google.android.gms.cast.MediaInfo;
6466
import com.google.android.gms.cast.MediaQueueItem;
@@ -73,6 +75,7 @@
7375
import com.google.android.gms.common.api.PendingResult;
7476
import com.google.android.gms.common.api.ResultCallback;
7577
import com.google.common.collect.ImmutableList;
78+
import java.io.IOException;
7679
import java.util.List;
7780
import java.util.Objects;
7881
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@@ -93,12 +96,23 @@
9396
@UnstableApi
9497
public final class CastPlayer extends BasePlayer {
9598

99+
/**
100+
* Maximum volume to use for {@link #getDeviceVolume()} and {@link #setDeviceVolume}.
101+
*
102+
* <p>These methods are implemented around {@link CastSession#setVolume} and {@link
103+
* CastSession#getVolume} which operate on a {@code [0, 1]} range. So this value allows us to
104+
* convert to and from the int-based volume scale that {@link #getDeviceVolume()} uses.
105+
*/
106+
private static final int MAX_VOLUME = 20;
107+
96108
/**
97109
* A {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote} {@link DeviceInfo} with a null {@link
98110
* DeviceInfo#routingControllerId}.
99111
*/
100112
public static final DeviceInfo DEVICE_INFO_REMOTE_EMPTY =
101-
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build();
113+
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).setMaxVolume(MAX_VOLUME).build();
114+
115+
private static final Range<Integer> VOLUME_RANGE = new Range<>(0, MAX_VOLUME);
102116

103117
static {
104118
MediaLibraryInfo.registerModule("media3.cast");
@@ -113,6 +127,11 @@ public final class CastPlayer extends BasePlayer {
113127
COMMAND_STOP,
114128
COMMAND_SEEK_TO_DEFAULT_POSITION,
115129
COMMAND_SEEK_TO_MEDIA_ITEM,
130+
COMMAND_GET_DEVICE_VOLUME,
131+
COMMAND_ADJUST_DEVICE_VOLUME,
132+
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
133+
COMMAND_SET_DEVICE_VOLUME,
134+
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
116135
COMMAND_SET_REPEAT_MODE,
117136
COMMAND_SET_SPEED_AND_PITCH,
118137
COMMAND_GET_CURRENT_MEDIA_ITEM,
@@ -144,6 +163,8 @@ public final class CastPlayer extends BasePlayer {
144163
@Nullable private final Api30Impl api30Impl;
145164

146165
// Result callbacks.
166+
private final Cast.Listener castListener;
167+
147168
private final StatusListener statusListener;
148169
private final SeekResultCallback seekResultCallback;
149170

@@ -154,7 +175,10 @@ public final class CastPlayer extends BasePlayer {
154175
// Internal state.
155176
private final StateHolder<Boolean> playWhenReady;
156177
private final StateHolder<Integer> repeatMode;
178+
private boolean isMuted;
179+
private int deviceVolume;
157180
private final StateHolder<PlaybackParameters> playbackParameters;
181+
@Nullable private CastSession castSession;
158182
@Nullable private RemoteMediaClient remoteMediaClient;
159183
private CastTimeline currentTimeline;
160184
private Tracks currentTracks;
@@ -257,6 +281,7 @@ public CastPlayer(
257281
this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs;
258282
timelineTracker = new CastTimelineTracker(mediaItemConverter);
259283
period = new Timeline.Period();
284+
castListener = new CastListener();
260285
statusListener = new StatusListener();
261286
seekResultCallback = new SeekResultCallback();
262287
listeners =
@@ -266,6 +291,7 @@ public CastPlayer(
266291
(listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags)));
267292
playWhenReady = new StateHolder<>(false);
268293
repeatMode = new StateHolder<>(REPEAT_MODE_OFF);
294+
deviceVolume = MAX_VOLUME;
269295
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
270296
playbackState = STATE_IDLE;
271297
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
@@ -278,8 +304,7 @@ public CastPlayer(
278304

279305
SessionManager sessionManager = castContext.getSessionManager();
280306
sessionManager.addSessionManagerListener(statusListener, CastSession.class);
281-
CastSession session = sessionManager.getCurrentCastSession();
282-
setRemoteMediaClient(session != null ? session.getRemoteMediaClient() : null);
307+
setCastSession(sessionManager.getCurrentCastSession());
283308
updateInternalStateAndNotifyIfChanged();
284309
if (SDK_INT >= 30 && context != null) {
285310
api30Impl = new Api30Impl(context);
@@ -829,61 +854,94 @@ public DeviceInfo getDeviceInfo() {
829854
return deviceInfo;
830855
}
831856

832-
/** This method is not supported and always returns {@code 0}. */
833857
@Override
834858
public int getDeviceVolume() {
835-
return 0;
859+
return deviceVolume;
836860
}
837861

838-
/** This method is not supported and always returns {@code false}. */
839862
@Override
840863
public boolean isDeviceMuted() {
841-
return false;
864+
return isMuted;
842865
}
843866

844867
/**
845868
* @deprecated Use {@link #setDeviceVolume(int, int)} instead.
846869
*/
847870
@Deprecated
848871
@Override
849-
public void setDeviceVolume(int volume) {}
872+
public void setDeviceVolume(@IntRange(from = 0) int volume) {
873+
setDeviceVolume(volume, /* flags= */ 0);
874+
}
850875

851-
/** This method is not supported and does nothing. */
852876
@Override
853-
public void setDeviceVolume(int volume, @C.VolumeFlags int flags) {}
877+
public void setDeviceVolume(@IntRange(from = 0) int volume, @C.VolumeFlags int flags) {
878+
if (castSession == null) {
879+
return;
880+
}
881+
volume = VOLUME_RANGE.clamp(volume);
882+
try {
883+
// See [Internal ref: b/399691860] for context on why we don't use
884+
// RemoteMediaClient.setStreamVolume.
885+
castSession.setVolume((float) volume / MAX_VOLUME);
886+
} catch (IOException e) {
887+
Log.w(TAG, "Ignoring setDeviceVolume due to exception", e);
888+
return;
889+
}
890+
setDeviceVolumeAndNotifyIfChanged(volume, isMuted);
891+
listeners.flushEvents();
892+
}
854893

855894
/**
856895
* @deprecated Use {@link #increaseDeviceVolume(int)} instead.
857896
*/
858897
@Deprecated
859898
@Override
860-
public void increaseDeviceVolume() {}
899+
public void increaseDeviceVolume() {
900+
increaseDeviceVolume(/* flags= */ 0);
901+
}
861902

862-
/** This method is not supported and does nothing. */
863903
@Override
864-
public void increaseDeviceVolume(@C.VolumeFlags int flags) {}
904+
public void increaseDeviceVolume(@C.VolumeFlags int flags) {
905+
setDeviceVolume(getDeviceVolume() + 1, flags);
906+
}
865907

866908
/**
867-
* @deprecated Use {@link #decreaseDeviceVolume(int)} instead.
909+
* @deprecated Use {@link #decreaseDeviceVolume(int)} (int)} instead.
868910
*/
869911
@Deprecated
870912
@Override
871-
public void decreaseDeviceVolume() {}
913+
public void decreaseDeviceVolume() {
914+
decreaseDeviceVolume(/* flags= */ 0);
915+
}
872916

873-
/** This method is not supported and does nothing. */
874917
@Override
875-
public void decreaseDeviceVolume(@C.VolumeFlags int flags) {}
918+
public void decreaseDeviceVolume(@C.VolumeFlags int flags) {
919+
setDeviceVolume(getDeviceVolume() - 1, flags);
920+
}
876921

877922
/**
878923
* @deprecated Use {@link #setDeviceMuted(boolean, int)} instead.
879924
*/
880925
@Deprecated
881926
@Override
882-
public void setDeviceMuted(boolean muted) {}
927+
public void setDeviceMuted(boolean muted) {
928+
setDeviceMuted(muted, /* flags= */ 0);
929+
}
883930

884-
/** This method is not supported and does nothing. */
885931
@Override
886-
public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {}
932+
public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
933+
if (castSession == null) {
934+
return;
935+
}
936+
try {
937+
castSession.setMute(muted);
938+
} catch (IOException e) {
939+
Log.w(TAG, "Ignoring setDeviceMuted due to exception", e);
940+
return;
941+
}
942+
setDeviceVolumeAndNotifyIfChanged(deviceVolume, muted);
943+
listeners.flushEvents();
944+
}
887945

888946
/** This method is not supported and does nothing. */
889947
@Override
@@ -906,6 +964,7 @@ private void updateInternalStateAndNotifyIfChanged() {
906964
? getCurrentTimeline().getPeriod(oldWindowIndex, period, /* setIds= */ true).uid
907965
: null;
908966
updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null);
967+
updateVolumeAndNotifyIfChanged();
909968
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
910969
updatePlaybackRateAndNotifyIfChanged(/* resultCallback= */ null);
911970
boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
@@ -1014,6 +1073,14 @@ private void updatePlaybackRateAndNotifyIfChanged(@Nullable ResultCallback<?> re
10141073
}
10151074
}
10161075

1076+
@RequiresNonNull("castSession")
1077+
private void updateVolumeAndNotifyIfChanged() {
1078+
if (castSession != null) {
1079+
int deviceVolume = VOLUME_RANGE.clamp((int) Math.round(castSession.getVolume() * MAX_VOLUME));
1080+
setDeviceVolumeAndNotifyIfChanged(deviceVolume, castSession.isMute());
1081+
}
1082+
}
1083+
10171084
@RequiresNonNull("remoteMediaClient")
10181085
private void updateRepeatModeAndNotifyIfChanged(@Nullable ResultCallback<?> resultCallback) {
10191086
if (repeatMode.acceptsUpdate(resultCallback)) {
@@ -1255,6 +1322,17 @@ private PositionInfo getCurrentPositionInfo() {
12551322
/* adIndexInAdGroup= */ C.INDEX_UNSET);
12561323
}
12571324

1325+
private void setDeviceVolumeAndNotifyIfChanged(
1326+
@IntRange(from = 0) int deviceVolume, boolean isMuted) {
1327+
if (this.deviceVolume != deviceVolume || this.isMuted != isMuted) {
1328+
this.deviceVolume = deviceVolume;
1329+
this.isMuted = isMuted;
1330+
listeners.queueEvent(
1331+
Player.EVENT_DEVICE_VOLUME_CHANGED,
1332+
listener -> listener.onDeviceVolumeChanged(deviceVolume, isMuted));
1333+
}
1334+
}
1335+
12581336
private void setRepeatModeAndNotifyIfChanged(@Player.RepeatMode int repeatMode) {
12591337
if (this.repeatMode.value != repeatMode) {
12601338
this.repeatMode.value = repeatMode;
@@ -1307,7 +1385,16 @@ private void setPlayerStateAndNotifyIfChanged(
13071385
}
13081386
}
13091387

1310-
private void setRemoteMediaClient(@Nullable RemoteMediaClient remoteMediaClient) {
1388+
private void setCastSession(@Nullable CastSession castSession) {
1389+
if (this.castSession != null) {
1390+
this.castSession.removeCastListener(castListener);
1391+
}
1392+
if (castSession != null) {
1393+
castSession.addCastListener(castListener);
1394+
}
1395+
this.castSession = castSession;
1396+
RemoteMediaClient remoteMediaClient =
1397+
castSession != null ? castSession.getRemoteMediaClient() : null;
13111398
if (this.remoteMediaClient == remoteMediaClient) {
13121399
// Do nothing.
13131400
return;
@@ -1468,22 +1555,22 @@ public void onAdBreakStatusUpdated() {}
14681555

14691556
@Override
14701557
public void onSessionStarted(CastSession castSession, String s) {
1471-
setRemoteMediaClient(castSession.getRemoteMediaClient());
1558+
setCastSession(castSession);
14721559
}
14731560

14741561
@Override
14751562
public void onSessionResumed(CastSession castSession, boolean b) {
1476-
setRemoteMediaClient(castSession.getRemoteMediaClient());
1563+
setCastSession(castSession);
14771564
}
14781565

14791566
@Override
14801567
public void onSessionEnded(CastSession castSession, int i) {
1481-
setRemoteMediaClient(null);
1568+
setCastSession(null);
14821569
}
14831570

14841571
@Override
14851572
public void onSessionSuspended(CastSession castSession, int i) {
1486-
setRemoteMediaClient(null);
1573+
setCastSession(null);
14871574
}
14881575

14891576
@Override
@@ -1645,6 +1732,7 @@ public DeviceInfo fetchDeviceInfo() {
16451732
// TODO b/364580007 - Populate volume information, and implement Player volume-related
16461733
// methods.
16471734
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE)
1735+
.setMaxVolume(MAX_VOLUME)
16481736
.setRoutingControllerId(remoteController.getId())
16491737
.build();
16501738
}
@@ -1676,4 +1764,13 @@ public void onStop(RoutingController controller) {
16761764
}
16771765
}
16781766
}
1767+
1768+
private final class CastListener extends Cast.Listener {
1769+
1770+
@Override
1771+
public void onVolumeChanged() {
1772+
updateVolumeAndNotifyIfChanged();
1773+
listeners.flushEvents();
1774+
}
1775+
}
16791776
}

0 commit comments

Comments
 (0)