Unsuppress/suppress playback on suitable media output updates
PiperOrigin-RevId: 657111555
This commit is contained in:
parent
32c9d62d39
commit
f6dc02fa6a
@ -15,15 +15,18 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.media.MediaRouter2;
|
||||
import android.media.MediaRouter2.ControllerCallback;
|
||||
import android.media.MediaRouter2.RouteCallback;
|
||||
import android.media.MediaRouter2.RoutingController;
|
||||
import android.media.RouteDiscoveryPreference;
|
||||
import android.media.RoutingSessionInfo;
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -42,12 +45,13 @@ import java.util.concurrent.Executor;
|
||||
private final RouteCallback routeCallback;
|
||||
private final Executor executor;
|
||||
|
||||
private boolean isEnabled;
|
||||
@Nullable private ControllerCallback controllerCallback;
|
||||
private boolean isPreviousSelectedOutputSuitableForPlayback;
|
||||
|
||||
public DefaultSuitableOutputChecker(Context context, Handler eventHandler) {
|
||||
this.router = MediaRouter2.getInstance(context);
|
||||
this.routeCallback = new RouteCallback() {};
|
||||
this.executor =
|
||||
router = MediaRouter2.getInstance(context);
|
||||
routeCallback = new RouteCallback() {};
|
||||
executor =
|
||||
new Executor() {
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
@ -57,19 +61,38 @@ import java.util.concurrent.Executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean isEnabled) {
|
||||
if (isEnabled && !this.isEnabled) {
|
||||
router.registerRouteCallback(executor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
|
||||
this.isEnabled = true;
|
||||
} else if (!isEnabled && this.isEnabled) {
|
||||
router.unregisterRouteCallback(routeCallback);
|
||||
this.isEnabled = false;
|
||||
}
|
||||
public void enable(Callback callback) {
|
||||
router.registerRouteCallback(executor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
|
||||
controllerCallback =
|
||||
new ControllerCallback() {
|
||||
@Override
|
||||
public void onControllerUpdated(RoutingController controller) {
|
||||
boolean isCurrentSelectedOutputSuitableForPlayback =
|
||||
isSelectedOutputSuitableForPlayback();
|
||||
if (isPreviousSelectedOutputSuitableForPlayback
|
||||
!= isCurrentSelectedOutputSuitableForPlayback) {
|
||||
isPreviousSelectedOutputSuitableForPlayback =
|
||||
isCurrentSelectedOutputSuitableForPlayback;
|
||||
callback.onSelectedOutputSuitabilityChanged(
|
||||
isCurrentSelectedOutputSuitableForPlayback);
|
||||
}
|
||||
}
|
||||
};
|
||||
router.registerControllerCallback(executor, controllerCallback);
|
||||
isPreviousSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectedRouteSuitableForPlayback() {
|
||||
checkState(isEnabled, "SuitableOutputChecker is not enabled");
|
||||
public void disable() {
|
||||
checkStateNotNull(controllerCallback, "SuitableOutputChecker is not enabled");
|
||||
router.unregisterControllerCallback(controllerCallback);
|
||||
controllerCallback = null;
|
||||
router.unregisterRouteCallback(routeCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectedOutputSuitableForPlayback() {
|
||||
checkStateNotNull(controllerCallback, "SuitableOutputChecker is not enabled");
|
||||
int transferReason = router.getSystemController().getRoutingSessionInfo().getTransferReason();
|
||||
boolean wasTransferInitiatedBySelf = router.getSystemController().wasTransferInitiatedBySelf();
|
||||
for (MediaRoute2Info routeInfo : router.getSystemController().getSelectedRoutes()) {
|
||||
|
@ -404,7 +404,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
suitableOutputChecker = builder.suitableOutputChecker;
|
||||
if (suitableOutputChecker != null && Util.SDK_INT >= 35) {
|
||||
suitableOutputChecker.setEnabled(true);
|
||||
suitableOutputChecker.enable(this::onSelectedOutputSuitabilityChanged);
|
||||
} else if (suppressPlaybackOnUnsuitableOutput && Util.SDK_INT >= 23) {
|
||||
audioManager = (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
Api23.registerAudioDeviceCallback(
|
||||
@ -1073,7 +1073,7 @@ import java.util.concurrent.TimeoutException;
|
||||
playbackInfo = playbackInfo.copyWithEstimatedPosition();
|
||||
}
|
||||
if (suitableOutputChecker != null && Util.SDK_INT >= 35) {
|
||||
suitableOutputChecker.setEnabled(false);
|
||||
suitableOutputChecker.disable();
|
||||
}
|
||||
playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE);
|
||||
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId);
|
||||
@ -2841,7 +2841,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
private boolean hasSupportedAudioOutput() {
|
||||
if (Util.SDK_INT >= 35 && suitableOutputChecker != null) {
|
||||
return suitableOutputChecker.isSelectedRouteSuitableForPlayback();
|
||||
return suitableOutputChecker.isSelectedOutputSuitableForPlayback();
|
||||
} else if (Util.SDK_INT >= 23 && audioManager != null) {
|
||||
return Api23.isSuitableExternalAudioOutputPresentInAudioDeviceInfoList(
|
||||
applicationContext, audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS));
|
||||
@ -2954,6 +2954,23 @@ import java.util.concurrent.TimeoutException;
|
||||
/* ignored */ false);
|
||||
}
|
||||
|
||||
private void onSelectedOutputSuitabilityChanged(boolean isSelectedOutputSuitableForPlayback) {
|
||||
if (isSelectedOutputSuitableForPlayback) {
|
||||
if (playbackInfo.playbackSuppressionReason
|
||||
== Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) {
|
||||
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
|
||||
playbackInfo.playWhenReady,
|
||||
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
||||
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
|
||||
}
|
||||
} else {
|
||||
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
|
||||
playbackInfo.playWhenReady,
|
||||
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
||||
Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
private static DeviceInfo createDeviceInfo(@Nullable StreamVolumeManager streamVolumeManager) {
|
||||
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL)
|
||||
.setMinVolume(streamVolumeManager != null ? streamVolumeManager.getMinVolume() : 0)
|
||||
|
@ -21,20 +21,43 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/** Provides methods to check the suitability of media outputs. */
|
||||
/** Provides methods to check the suitability of selected media outputs. */
|
||||
@RequiresApi(35)
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@UnstableApi
|
||||
public interface SuitableOutputChecker {
|
||||
|
||||
/** Callback to notify changes in the suitability of the selected media output. */
|
||||
interface Callback {
|
||||
|
||||
/**
|
||||
* Called when suitability of the selected output has changed.
|
||||
*
|
||||
* @param isSelectedOutputSuitableForPlayback true when selected output is suitable for
|
||||
* playback.
|
||||
*/
|
||||
void onSelectedOutputSuitabilityChanged(boolean isSelectedOutputSuitableForPlayback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the current instance to receive updates on the suitable media outputs.
|
||||
* Enables the current instance to receive updates on the selected media outputs and sets the
|
||||
* {@link Callback} to notify the updates on the suitability of the selected output.
|
||||
*
|
||||
* <p>When the caller no longer requires updated information, they must call this method with
|
||||
* {@code false}.
|
||||
* <p>When the caller no longer requires updates on suitable outputs, they must call {@link
|
||||
* #disable()}.
|
||||
*
|
||||
* @param isEnabled True if this instance should receive the updates.
|
||||
* @param callback To receive notifications of changes in suitable media output changes.
|
||||
*/
|
||||
void setEnabled(boolean isEnabled);
|
||||
void enable(Callback callback);
|
||||
|
||||
/**
|
||||
* Disables the current instance to receive updates on the selected media outputs and clears the
|
||||
* {@link Callback}.
|
||||
*
|
||||
* @throws IllegalStateException if this instance is not enabled to receive the updates on
|
||||
* suitable media outputs.
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* Returns whether any audio output is suitable for the media playback.
|
||||
@ -42,5 +65,5 @@ public interface SuitableOutputChecker {
|
||||
* @throws IllegalStateException if this instance is not enabled to receive the updates on
|
||||
* suitable media outputs.
|
||||
*/
|
||||
boolean isSelectedRouteSuitableForPlayback();
|
||||
boolean isSelectedOutputSuitableForPlayback();
|
||||
}
|
||||
|
@ -16,8 +16,9 @@
|
||||
package androidx.media3.test.utils;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@ -37,7 +38,7 @@ public final class FakeSuitableOutputChecker implements SuitableOutputChecker {
|
||||
|
||||
/**
|
||||
* Sets the initial value to be returned from {@link
|
||||
* SuitableOutputChecker#isSelectedRouteSuitableForPlayback()}. The default value is false.
|
||||
* SuitableOutputChecker#isSelectedOutputSuitableForPlayback()}. The default value is false.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setIsSuitableExternalOutputAvailable(boolean isSuitableOutputAvailable) {
|
||||
@ -55,21 +56,43 @@ public final class FakeSuitableOutputChecker implements SuitableOutputChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean isSuitableOutputAvailable;
|
||||
private boolean isEnabled;
|
||||
private boolean isSelectedOutputSuitableForPlayback;
|
||||
private boolean previousSelectedOutputSuitableForPlayback;
|
||||
@Nullable private Callback callback;
|
||||
|
||||
public FakeSuitableOutputChecker(boolean isSuitableOutputAvailable) {
|
||||
this.isSuitableOutputAvailable = isSuitableOutputAvailable;
|
||||
public FakeSuitableOutputChecker(boolean isSelectedOutputSuitableForPlayback) {
|
||||
this.isSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback;
|
||||
this.previousSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
public void enable(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectedRouteSuitableForPlayback() {
|
||||
checkState(isEnabled, "SuitableOutputChecker is not enabled");
|
||||
return isSuitableOutputAvailable;
|
||||
public void disable() {
|
||||
this.callback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectedOutputSuitableForPlayback() {
|
||||
checkStateNotNull(callback, "SuitableOutputChecker is not enabled");
|
||||
return isSelectedOutputSuitableForPlayback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value to be returned by {@link
|
||||
* SuitableOutputChecker#isSelectedOutputSuitableForPlayback()} and send callbacks to registered
|
||||
* callers via {@link Callback#onSelectedOutputSuitabilityChanged(boolean)}.
|
||||
*/
|
||||
public void updateIsSelectedSuitableOutputAvailableAndNotify(
|
||||
boolean isSelectedOutputSuitableForPlayback) {
|
||||
this.isSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback;
|
||||
if (callback != null
|
||||
&& previousSelectedOutputSuitableForPlayback != isSelectedOutputSuitableForPlayback) {
|
||||
callback.onSelectedOutputSuitabilityChanged(isSelectedOutputSuitableForPlayback);
|
||||
previousSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user