diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d5b511d218..137ef765c6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,8 @@ * Common Library: * Remove `Format.toBundle(boolean excludeMetadata)` method, use `Format.toBundle()` instead. + * Add `AudioManagerCompat` and `AudioFocusRequestCompat` to replace the + equivalent classes in `androidx.media`. * ExoPlayer: * Consider language when selecting a video track. By default select a 'main' video track that matches the language of the selected audio diff --git a/libraries/common/src/main/java/androidx/media3/common/AudioAttributes.java b/libraries/common/src/main/java/androidx/media3/common/AudioAttributes.java index 5dc4e59c17..49d7b6fd35 100644 --- a/libraries/common/src/main/java/androidx/media3/common/AudioAttributes.java +++ b/libraries/common/src/main/java/androidx/media3/common/AudioAttributes.java @@ -170,6 +170,43 @@ public final class AudioAttributes { return audioAttributesV21; } + /** Returns the {@link C.StreamType} corresponding to these audio attributes. */ + @UnstableApi + public @C.StreamType int getStreamType() { + // Flags to stream type mapping + if ((flags & C.FLAG_AUDIBILITY_ENFORCED) == C.FLAG_AUDIBILITY_ENFORCED) { + return C.STREAM_TYPE_SYSTEM; + } + // Usage to stream type mapping + switch (usage) { + case C.USAGE_ASSISTANCE_SONIFICATION: + return C.STREAM_TYPE_SYSTEM; + case C.USAGE_VOICE_COMMUNICATION: + return C.STREAM_TYPE_VOICE_CALL; + case C.USAGE_VOICE_COMMUNICATION_SIGNALLING: + return C.STREAM_TYPE_DTMF; + case C.USAGE_ALARM: + return C.STREAM_TYPE_ALARM; + case C.USAGE_NOTIFICATION_RINGTONE: + return C.STREAM_TYPE_RING; + case C.USAGE_NOTIFICATION: + case C.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: + case C.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: + case C.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: + case C.USAGE_NOTIFICATION_EVENT: + return C.STREAM_TYPE_NOTIFICATION; + case C.USAGE_ASSISTANCE_ACCESSIBILITY: + return C.STREAM_TYPE_ACCESSIBILITY; + case C.USAGE_MEDIA: + case C.USAGE_GAME: + case C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: + case C.USAGE_ASSISTANT: + case C.USAGE_UNKNOWN: + default: + return C.STREAM_TYPE_MUSIC; + } + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { diff --git a/libraries/common/src/main/java/androidx/media3/common/C.java b/libraries/common/src/main/java/androidx/media3/common/C.java index 5e773c24fd..046f6a4168 100644 --- a/libraries/common/src/main/java/androidx/media3/common/C.java +++ b/libraries/common/src/main/java/androidx/media3/common/C.java @@ -327,8 +327,8 @@ public final class C { /** * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link * #STREAM_TYPE_DTMF}, {@link #STREAM_TYPE_MUSIC}, {@link #STREAM_TYPE_NOTIFICATION}, {@link - * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link - * #STREAM_TYPE_DEFAULT}. + * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL}, {@link + * #STREAM_TYPE_ACCESSIBILITY} or {@link #STREAM_TYPE_DEFAULT}. */ // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // with Kotlin usages from before TYPE_USE was added. @@ -345,6 +345,7 @@ public final class C { STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL, + STREAM_TYPE_ACCESSIBILITY, STREAM_TYPE_DEFAULT }) public @interface StreamType {} @@ -370,6 +371,10 @@ public final class C { /** See {@link AudioManager#STREAM_VOICE_CALL}. */ @UnstableApi public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL; + /** See {@link AudioManager#STREAM_ACCESSIBILITY}. */ + @UnstableApi + public static final int STREAM_TYPE_ACCESSIBILITY = AudioManager.STREAM_ACCESSIBILITY; + /** The default stream type used by audio renderers. Equal to {@link #STREAM_TYPE_MUSIC}. */ @UnstableApi public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; diff --git a/libraries/common/src/main/java/androidx/media3/common/Player.java b/libraries/common/src/main/java/androidx/media3/common/Player.java index da8930e07c..7b967a4b6a 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Player.java +++ b/libraries/common/src/main/java/androidx/media3/common/Player.java @@ -3384,8 +3384,7 @@ public interface Player { * *
For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume returned * by this method varies according to the current {@link C.StreamType stream type}. The stream - * type is determined by {@link AudioAttributes#usage} which can be converted to stream type with - * {@link Util#getStreamTypeForAudioUsage(int)}. + * type is determined by {@link AudioAttributes#getStreamType()}. * *
For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of the * remote device is returned. @@ -3508,10 +3507,6 @@ public interface Player { *
If tunneling is enabled by the track selector, the specified audio attributes will be * ignored, but they will take effect if audio is later played without tunneling. * - *
If the device is running a build before platform API version 21, audio attributes cannot be - * set directly on the underlying audio track. In this case, the usage will be mapped onto an - * equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}. - * *
If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link * C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link * IllegalArgumentException}. diff --git a/libraries/common/src/main/java/androidx/media3/common/audio/AudioFocusRequestCompat.java b/libraries/common/src/main/java/androidx/media3/common/audio/AudioFocusRequestCompat.java new file mode 100644 index 0000000000..712108a9b3 --- /dev/null +++ b/libraries/common/src/main/java/androidx/media3/common/audio/AudioFocusRequestCompat.java @@ -0,0 +1,356 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.common.audio; + +import static androidx.media3.common.util.Assertions.checkArgument; +import static androidx.media3.common.util.Assertions.checkNotNull; + +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.media3.common.AudioAttributes; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.Objects; + +/** + * Compatibility version of an {@link AudioFocusRequest} with fallbacks for older Android versions. + */ +@UnstableApi +public final class AudioFocusRequestCompat { + + private final @AudioManagerCompat.AudioFocusGain int focusGain; + private final AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener; + private final Handler focusChangeHandler; + private final AudioAttributes audioAttributes; + private final boolean pauseOnDuck; + + @Nullable private final Object frameworkAudioFocusRequest; + + /* package */ AudioFocusRequestCompat( + int focusGain, + AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener, + Handler focusChangeHandler, + AudioAttributes audioFocusRequestCompat, + boolean pauseOnDuck) { + this.focusGain = focusGain; + this.focusChangeHandler = focusChangeHandler; + this.audioAttributes = audioFocusRequestCompat; + this.pauseOnDuck = pauseOnDuck; + + if (Util.SDK_INT < 26 && focusChangeHandler.getLooper() != Looper.getMainLooper()) { + this.onAudioFocusChangeListener = + new OnAudioFocusChangeListenerHandlerCompat( + onAudioFocusChangeListener, focusChangeHandler); + } else { + this.onAudioFocusChangeListener = onAudioFocusChangeListener; + } + + if (Util.SDK_INT >= 26) { + this.frameworkAudioFocusRequest = + new AudioFocusRequest.Builder(focusGain) + .setAudioAttributes(audioAttributes.getAudioAttributesV21().audioAttributes) + .setWillPauseWhenDucked(pauseOnDuck) + .setOnAudioFocusChangeListener(onAudioFocusChangeListener, focusChangeHandler) + .build(); + } else { + this.frameworkAudioFocusRequest = null; + } + } + + /** + * Returns the type of {@link AudioManagerCompat.AudioFocusGain} configured for this {@code + * AudioFocusRequestCompat}. + */ + public @AudioManagerCompat.AudioFocusGain int getFocusGain() { + return focusGain; + } + + /** + * Returns the {@link AudioAttributes} set for this {@code AudioFocusRequestCompat}, or the + * default attributes if none were set. + */ + public AudioAttributes getAudioAttributes() { + return audioAttributes; + } + + /** + * Returns whether the application that would use this {@code AudioFocusRequestCompat} would pause + * when it is requested to duck. This value is only applicable on {@link + * android.os.Build.VERSION_CODES#O} and later. + */ + public boolean willPauseWhenDucked() { + return pauseOnDuck; + } + + /** + * Returns the {@link AudioManager.OnAudioFocusChangeListener} set for this {@code + * AudioFocusRequestCompat}. + * + * @return The {@link AudioManager.OnAudioFocusChangeListener} that was set. + */ + public AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener() { + return onAudioFocusChangeListener; + } + + /** + * Returns the {@link Handler} to be used for the {@link AudioManager.OnAudioFocusChangeListener}. + */ + public Handler getFocusChangeHandler() { + return focusChangeHandler; + } + + /** Returns new {@link Builder} with all values of this instance pre-populated. */ + public Builder buildUpon() { + return new Builder(this); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AudioFocusRequestCompat)) { + return false; + } + AudioFocusRequestCompat that = (AudioFocusRequestCompat) o; + return focusGain == that.focusGain + && pauseOnDuck == that.pauseOnDuck + && Objects.equals(onAudioFocusChangeListener, that.onAudioFocusChangeListener) + && Objects.equals(focusChangeHandler, that.focusChangeHandler) + && Objects.equals(audioAttributes, that.audioAttributes); + } + + @Override + public int hashCode() { + return Objects.hash( + focusGain, onAudioFocusChangeListener, focusChangeHandler, audioAttributes, pauseOnDuck); + } + + @RequiresApi(26) + /* package */ AudioFocusRequest getAudioFocusRequest() { + return (AudioFocusRequest) checkNotNull(frameworkAudioFocusRequest); + } + + /** + * Builder class for {@link AudioFocusRequestCompat} objects. + * + *
The default values are: + * + *
In contrast to a {@link AudioFocusRequest}, attempting to {@link #build()} an {@link + * AudioFocusRequestCompat} without an {@link AudioManager.OnAudioFocusChangeListener} will throw + * an {@link IllegalArgumentException}, because the listener is required for all API levels up to + * API 26. + */ + public static final class Builder { + private @AudioManagerCompat.AudioFocusGain int focusGain; + @Nullable private AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener; + @Nullable private Handler focusChangeHandler; + private AudioAttributes audioAttributes; + private boolean pauseOnDuck; + + /** + * Constructs a new {@code Builder}, and specifies how audio focus will be requested. + * + *
By default there is no focus change listener, delayed focus is not supported, ducking is
+ * suitable for the application, and the {@link AudioAttributes} are set to {@link
+ * AudioAttributes#DEFAULT}.
+ *
+ * @param focusGain The type of {@link AudioManagerCompat.AudioFocusGain} that will be
+ * requested.
+ */
+ public Builder(@AudioManagerCompat.AudioFocusGain int focusGain) {
+ this.audioAttributes = AudioAttributes.DEFAULT;
+ this.focusGain = focusGain;
+ }
+
+ private Builder(AudioFocusRequestCompat other) {
+ focusGain = other.getFocusGain();
+ onAudioFocusChangeListener = other.getOnAudioFocusChangeListener();
+ focusChangeHandler = other.getFocusChangeHandler();
+ audioAttributes = other.getAudioAttributes();
+ pauseOnDuck = other.willPauseWhenDucked();
+ }
+
+ /**
+ * Sets the type of {@link AudioManagerCompat.AudioFocusGain} that will be requested.
+ *
+ * @param focusGain The type of {@link AudioManagerCompat.AudioFocusGain} that will be
+ * requested.
+ * @return This {@code Builder} instance.
+ */
+ @CanIgnoreReturnValue
+ public Builder setFocusGain(@AudioManagerCompat.AudioFocusGain int focusGain) {
+ checkArgument(isValidFocusGain(focusGain));
+ this.focusGain = focusGain;
+ return this;
+ }
+
+ /**
+ * Sets the listener called when audio focus changes after being requested with {@link
+ * AudioManagerCompat#requestAudioFocus(AudioManager, AudioFocusRequestCompat)}, and until being
+ * abandoned with {@link AudioManagerCompat#abandonAudioFocusRequest(AudioManager,
+ * AudioFocusRequestCompat)}. Note that only focus changes (gains and losses) affecting the
+ * focus owner are reported, not gains and losses of other focus requesters in the system. If tunneling is enabled by the track selector, the specified audio attributes will be
* ignored, but they will take effect if audio is later played without tunneling.
*
- * If the device is running a build before platform API version 21, audio attributes cannot be
- * set directly on the underlying audio track. In this case, the usage will be mapped onto an
- * equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}.
- *
* To get audio attributes that are equivalent to a legacy stream type, pass the stream type to
* {@link Util#getAudioUsageForStreamType(int)} and use the returned {@link C.AudioUsage} to build
* an audio attributes instance.
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/StreamVolumeManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/StreamVolumeManager.java
index 82157c1d91..67bf73e634 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/StreamVolumeManager.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/StreamVolumeManager.java
@@ -23,6 +23,7 @@ import android.media.AudioManager;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
@@ -96,7 +97,7 @@ import androidx.media3.common.util.Util;
* #setStreamType(int)} is called.
*/
public int getMinVolume() {
- return Util.SDK_INT >= 28 ? audioManager.getStreamMinVolume(streamType) : 0;
+ return AudioManagerCompat.getStreamMinVolume(audioManager, streamType);
}
/**
@@ -104,7 +105,7 @@ import androidx.media3.common.util.Util;
* #setStreamType(int)} is called.
*/
public int getMaxVolume() {
- return audioManager.getStreamMaxVolume(streamType);
+ return AudioManagerCompat.getStreamMaxVolume(audioManager, streamType);
}
/** Gets the current volume for the current audio stream. */
+ * Notifications are delivered on the main thread.
+ *
+ * @param listener The {@link AudioManager.OnAudioFocusChangeListener} receiving the focus
+ * change notifications.
+ * @return This {@code Builder} instance.
+ */
+ @CanIgnoreReturnValue
+ public Builder setOnAudioFocusChangeListener(AudioManager.OnAudioFocusChangeListener listener) {
+ return setOnAudioFocusChangeListener(listener, new Handler(Looper.getMainLooper()));
+ }
+
+ /**
+ * Sets the listener called when audio focus changes after being requested with {@link
+ * AudioManagerCompat#requestAudioFocus(AudioManager, AudioFocusRequestCompat)}, and until being
+ * abandoned with {@link AudioManagerCompat#abandonAudioFocusRequest(AudioManager,
+ * AudioFocusRequestCompat)}. Note that only focus changes (gains and losses) affecting the
+ * focus owner are reported, not gains and losses of other focus requesters in the system.
+ *
+ * @param listener The {@link AudioManager.OnAudioFocusChangeListener} receiving the focus
+ * change notifications.
+ * @param handler The {@link Handler} for the thread on which to execute the notifications.
+ * @return This {@code Builder} instance.
+ */
+ @CanIgnoreReturnValue
+ public Builder setOnAudioFocusChangeListener(
+ AudioManager.OnAudioFocusChangeListener listener, Handler handler) {
+ checkNotNull(listener);
+ checkNotNull(handler);
+ onAudioFocusChangeListener = listener;
+ focusChangeHandler = handler;
+ return this;
+ }
+
+ /**
+ * Sets the {@link AudioAttributes} to be associated with the focus request, and which describe
+ * the use case for which focus is requested. As the focus requests typically precede audio
+ * playback, this information is used on certain platforms to declare the subsequent playback
+ * use case. It is therefore good practice to use in this method the same {@code
+ * AudioAttributes} as used for playback, see for example {@code
+ * ExoPlayer.Builder.setAudioAttributes()}.
+ *
+ * @param attributes The {@link AudioAttributes} for the focus request.
+ * @return This {@code Builder} instance.
+ */
+ @CanIgnoreReturnValue
+ public Builder setAudioAttributes(AudioAttributes attributes) {
+ checkNotNull(attributes);
+ audioAttributes = attributes;
+ return this;
+ }
+
+ /**
+ * Declares the intended behavior of the application with regards to audio ducking. See more
+ * details in the {@link AudioFocusRequest} class documentation. Setting {@code pauseOnDuck} to
+ * true will only have an effect on {@link android.os.Build.VERSION_CODES#O} and later.
+ *
+ * @param pauseOnDuck Use {@code true} if the application intends to pause audio playback when
+ * losing focus with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
+ * @return This {@code Builder} instance.
+ */
+ @CanIgnoreReturnValue
+ public Builder setWillPauseWhenDucked(boolean pauseOnDuck) {
+ this.pauseOnDuck = pauseOnDuck;
+ return this;
+ }
+
+ /**
+ * Builds a new {@code AudioFocusRequestCompat} instance combining all the information gathered
+ * by this builder's configuration methods.
+ *
+ * @return The {@code AudioFocusRequestCompat}.
+ */
+ public AudioFocusRequestCompat build() {
+ if (onAudioFocusChangeListener == null) {
+ throw new IllegalStateException(
+ "Can't build an AudioFocusRequestCompat instance without a listener");
+ }
+ return new AudioFocusRequestCompat(
+ focusGain,
+ onAudioFocusChangeListener,
+ checkNotNull(focusChangeHandler),
+ audioAttributes,
+ pauseOnDuck);
+ }
+
+ /**
+ * Checks whether a focus gain constant is a valid value for an audio focus request.
+ *
+ * @param focusGain value to check
+ * @return true if focusGain is a valid value for an audio focus request.
+ */
+ private static boolean isValidFocusGain(@AudioManagerCompat.AudioFocusGain int focusGain) {
+ switch (focusGain) {
+ case AudioManagerCompat.AUDIOFOCUS_GAIN:
+ case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT:
+ case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+ case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Class to allow {@link AudioManager.OnAudioFocusChangeListener#onAudioFocusChange(int)} calls on
+ * a specific thread prior to API 26.
+ */
+ private static class OnAudioFocusChangeListenerHandlerCompat
+ implements Handler.Callback, AudioManager.OnAudioFocusChangeListener {
+
+ private static final int FOCUS_CHANGE = 0x002a74b2;
+
+ private final Handler handler;
+ private final AudioManager.OnAudioFocusChangeListener listener;
+
+ /* package */ OnAudioFocusChangeListenerHandlerCompat(
+ AudioManager.OnAudioFocusChangeListener listener, Handler handler) {
+ this.listener = listener;
+ this.handler = Util.createHandler(handler.getLooper(), /* callback= */ this);
+ }
+
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ handler.sendMessage(Message.obtain(handler, FOCUS_CHANGE, focusChange, 0));
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what == FOCUS_CHANGE) {
+ listener.onAudioFocusChange(message.arg1);
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/libraries/common/src/main/java/androidx/media3/common/audio/AudioManagerCompat.java b/libraries/common/src/main/java/androidx/media3/common/audio/AudioManagerCompat.java
new file mode 100644
index 0000000000..f465959d15
--- /dev/null
+++ b/libraries/common/src/main/java/androidx/media3/common/audio/AudioManagerCompat.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.common.audio;
+
+import static java.lang.annotation.ElementType.TYPE_USE;
+
+import android.media.AudioManager;
+import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
+import androidx.media3.common.C;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.common.util.Util;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Compatibility layer for {@link AudioManager} with fallbacks for older Android versions. */
+@UnstableApi
+public final class AudioManagerCompat {
+
+ /**
+ * Audio focus gain types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
+ * #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
+ * #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
+ */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(TYPE_USE)
+ @IntDef({
+ AUDIOFOCUS_NONE,
+ AUDIOFOCUS_GAIN,
+ AUDIOFOCUS_GAIN_TRANSIENT,
+ AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
+ })
+ public @interface AudioFocusGain {}
+
+ /** Used to indicate no audio focus has been gained or lost, or requested. */
+ @SuppressWarnings("InlinedApi")
+ public static final int AUDIOFOCUS_NONE = AudioManager.AUDIOFOCUS_NONE;
+
+ /** Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration. */
+ public static final int AUDIOFOCUS_GAIN = AudioManager.AUDIOFOCUS_GAIN;
+
+ /**
+ * Used to indicate a temporary gain or request of audio focus, anticipated to last a short amount
+ * of time. Examples of temporary changes are the playback of driving directions, or an event
+ * notification.
+ */
+ public static final int AUDIOFOCUS_GAIN_TRANSIENT = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
+
+ /**
+ * Used to indicate a temporary request of audio focus, anticipated to last a short amount of
+ * time, and where it is acceptable for other audio applications to keep playing after having
+ * lowered their output level (also referred to as "ducking"). Examples of temporary changes are
+ * the playback of driving directions where playback of music in the background is acceptable.
+ */
+ public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK =
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
+
+ /**
+ * Used to indicate a temporary request of audio focus, anticipated to last a short amount of
+ * time, during which no other applications, or system components, should play anything. Examples
+ * of exclusive and transient audio focus requests are voice memo recording and speech
+ * recognition, during which the system shouldn't play any notifications, and media playback
+ * should have paused.
+ */
+ public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
+
+ /**
+ * Requests audio focus. See the {@link AudioFocusRequestCompat} for information about the options
+ * available to configure your request, and notification of focus gain and loss.
+ *
+ * @param audioManager The {@link AudioManager}.
+ * @param focusRequest An {@link AudioFocusRequestCompat} instance used to configure how focus is
+ * requested.
+ * @return {@link AudioManager#AUDIOFOCUS_REQUEST_FAILED} or {@link
+ * AudioManager#AUDIOFOCUS_REQUEST_GRANTED}.
+ */
+ @SuppressWarnings("deprecation")
+ public static int requestAudioFocus(
+ AudioManager audioManager, AudioFocusRequestCompat focusRequest) {
+ if (Util.SDK_INT >= 26) {
+ return audioManager.requestAudioFocus(focusRequest.getAudioFocusRequest());
+ } else {
+ return audioManager.requestAudioFocus(
+ focusRequest.getOnAudioFocusChangeListener(),
+ focusRequest.getAudioAttributes().getStreamType(),
+ focusRequest.getFocusGain());
+ }
+ }
+
+ /**
+ * Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
+ *
+ * @param audioManager The {@link AudioManager}.
+ * @param focusRequest The {@link AudioFocusRequestCompat} that was used when requesting focus
+ * with {@link #requestAudioFocus(AudioManager, AudioFocusRequestCompat)}.
+ * @return {@link AudioManager#AUDIOFOCUS_REQUEST_FAILED} or {@link
+ * AudioManager#AUDIOFOCUS_REQUEST_GRANTED}
+ */
+ @SuppressWarnings("deprecation")
+ public static int abandonAudioFocusRequest(
+ AudioManager audioManager, AudioFocusRequestCompat focusRequest) {
+ if (Util.SDK_INT >= 26) {
+ return audioManager.abandonAudioFocusRequest(focusRequest.getAudioFocusRequest());
+ } else {
+ return audioManager.abandonAudioFocus(focusRequest.getOnAudioFocusChangeListener());
+ }
+ }
+
+ /**
+ * Returns the maximum volume index for a particular stream.
+ *
+ * @param audioManager The {@link AudioManager}.
+ * @param streamType The {@link C.StreamType} whose maximum volume index is returned.
+ * @return The maximum valid volume index for the stream.
+ */
+ @IntRange(from = 0)
+ public static int getStreamMaxVolume(AudioManager audioManager, @C.StreamType int streamType) {
+ return audioManager.getStreamMaxVolume(streamType);
+ }
+
+ /**
+ * Returns the minimum volume index for a particular stream.
+ *
+ * @param audioManager The {@link AudioManager}.
+ * @param streamType The {@link C.StreamType} whose minimum volume index is returned.
+ * @return The minimum valid volume index for the stream.
+ */
+ @IntRange(from = 0)
+ public static int getStreamMinVolume(AudioManager audioManager, @C.StreamType int streamType) {
+ return Util.SDK_INT >= 28 ? audioManager.getStreamMinVolume(streamType) : 0;
+ }
+
+ private AudioManagerCompat() {}
+}
diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java
index 274067327d..0c0da3e741 100644
--- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java
+++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java
@@ -84,6 +84,7 @@ import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.C.ContentType;
import androidx.media3.common.Format;
@@ -2382,6 +2383,8 @@ public final class Util {
return C.USAGE_ASSISTANCE_SONIFICATION;
case C.STREAM_TYPE_VOICE_CALL:
return C.USAGE_VOICE_COMMUNICATION;
+ case C.STREAM_TYPE_ACCESSIBILITY:
+ return C.USAGE_ASSISTANCE_ACCESSIBILITY;
case C.STREAM_TYPE_MUSIC:
default:
return C.USAGE_MEDIA;
@@ -2404,6 +2407,7 @@ public final class Util {
case C.STREAM_TYPE_SYSTEM:
return C.AUDIO_CONTENT_TYPE_SONIFICATION;
case C.STREAM_TYPE_VOICE_CALL:
+ case C.STREAM_TYPE_ACCESSIBILITY:
return C.AUDIO_CONTENT_TYPE_SPEECH;
case C.STREAM_TYPE_MUSIC:
default:
@@ -2411,7 +2415,10 @@ public final class Util {
}
}
- /** Returns the {@link C.StreamType} corresponding to the specified {@link C.AudioUsage}. */
+ /**
+ * @deprecated Use {@link AudioAttributes#getStreamType()} instead.
+ */
+ @Deprecated
@UnstableApi
public static @C.StreamType int getStreamTypeForAudioUsage(@C.AudioUsage int usage) {
switch (usage) {
@@ -2436,6 +2443,7 @@ public final class Util {
case C.USAGE_NOTIFICATION_EVENT:
return C.STREAM_TYPE_NOTIFICATION;
case C.USAGE_ASSISTANCE_ACCESSIBILITY:
+ return C.STREAM_TYPE_ACCESSIBILITY;
case C.USAGE_ASSISTANT:
case C.USAGE_UNKNOWN:
default:
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java
index c82281cae4..267cdfc562 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java
@@ -15,6 +15,11 @@
*/
package androidx.media3.exoplayer;
+import static androidx.media3.common.audio.AudioManagerCompat.AUDIOFOCUS_GAIN;
+import static androidx.media3.common.audio.AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT;
+import static androidx.media3.common.audio.AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
+import static androidx.media3.common.audio.AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
+import static androidx.media3.common.audio.AudioManagerCompat.AUDIOFOCUS_NONE;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE;
@@ -24,14 +29,15 @@ import android.media.AudioManager;
import android.os.Handler;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Player;
+import androidx.media3.common.audio.AudioFocusRequestCompat;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
-import androidx.media3.common.util.Util;
+import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import java.lang.annotation.Documented;
@@ -111,66 +117,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Audio focus has been temporarily lost, but playback may continue with reduced volume. */
private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK = 4;
- /**
- * Audio focus types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
- * #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
- * #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
- */
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @Target(TYPE_USE)
- @IntDef({
- AUDIOFOCUS_NONE,
- AUDIOFOCUS_GAIN,
- AUDIOFOCUS_GAIN_TRANSIENT,
- AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
- AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
- })
- private @interface AudioFocusGain {}
-
- /**
- * @see AudioManager#AUDIOFOCUS_NONE
- */
- @SuppressWarnings("InlinedApi")
- private static final int AUDIOFOCUS_NONE = AudioManager.AUDIOFOCUS_NONE;
-
- /**
- * @see AudioManager#AUDIOFOCUS_GAIN
- */
- private static final int AUDIOFOCUS_GAIN = AudioManager.AUDIOFOCUS_GAIN;
-
- /**
- * @see AudioManager#AUDIOFOCUS_GAIN_TRANSIENT
- */
- private static final int AUDIOFOCUS_GAIN_TRANSIENT = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
-
- /**
- * @see AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
- */
- private static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK =
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
-
- /**
- * @see AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
- */
- @SuppressWarnings("InlinedApi")
- private static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
-
private static final String TAG = "AudioFocusManager";
private static final float VOLUME_MULTIPLIER_DUCK = 0.2f;
private static final float VOLUME_MULTIPLIER_DEFAULT = 1.0f;
private final Supplier