mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Import AudioManagerCompat and AudioFocusRequest from androidx.media
Both classes provide utilities widely used by apps that are not yet available in Media3. This change imports the existing logic as it is with style adjustments to the Media3 codebase. PiperOrigin-RevId: 707067512
This commit is contained in:
parent
4b26eb2800
commit
2d11a339de
@ -5,6 +5,8 @@
|
|||||||
* Common Library:
|
* Common Library:
|
||||||
* Remove `Format.toBundle(boolean excludeMetadata)` method, use
|
* Remove `Format.toBundle(boolean excludeMetadata)` method, use
|
||||||
`Format.toBundle()` instead.
|
`Format.toBundle()` instead.
|
||||||
|
* Add `AudioManagerCompat` and `AudioFocusRequestCompat` to replace the
|
||||||
|
equivalent classes in `androidx.media`.
|
||||||
* ExoPlayer:
|
* ExoPlayer:
|
||||||
* Consider language when selecting a video track. By default select a
|
* Consider language when selecting a video track. By default select a
|
||||||
'main' video track that matches the language of the selected audio
|
'main' video track that matches the language of the selected audio
|
||||||
|
@ -170,6 +170,43 @@ public final class AudioAttributes {
|
|||||||
return audioAttributesV21;
|
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
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
|
@ -327,8 +327,8 @@ public final class C {
|
|||||||
/**
|
/**
|
||||||
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
|
* 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_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_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL}, {@link
|
||||||
* #STREAM_TYPE_DEFAULT}.
|
* #STREAM_TYPE_ACCESSIBILITY} or {@link #STREAM_TYPE_DEFAULT}.
|
||||||
*/
|
*/
|
||||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||||
// with Kotlin usages from before TYPE_USE was added.
|
// with Kotlin usages from before TYPE_USE was added.
|
||||||
@ -345,6 +345,7 @@ public final class C {
|
|||||||
STREAM_TYPE_RING,
|
STREAM_TYPE_RING,
|
||||||
STREAM_TYPE_SYSTEM,
|
STREAM_TYPE_SYSTEM,
|
||||||
STREAM_TYPE_VOICE_CALL,
|
STREAM_TYPE_VOICE_CALL,
|
||||||
|
STREAM_TYPE_ACCESSIBILITY,
|
||||||
STREAM_TYPE_DEFAULT
|
STREAM_TYPE_DEFAULT
|
||||||
})
|
})
|
||||||
public @interface StreamType {}
|
public @interface StreamType {}
|
||||||
@ -370,6 +371,10 @@ public final class C {
|
|||||||
/** See {@link AudioManager#STREAM_VOICE_CALL}. */
|
/** See {@link AudioManager#STREAM_VOICE_CALL}. */
|
||||||
@UnstableApi public static final int STREAM_TYPE_VOICE_CALL = 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}. */
|
/** 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;
|
@UnstableApi public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
|
||||||
|
|
||||||
|
@ -3384,8 +3384,7 @@ public interface Player {
|
|||||||
*
|
*
|
||||||
* <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume returned
|
* <p>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
|
* 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
|
* type is determined by {@link AudioAttributes#getStreamType()}.
|
||||||
* {@link Util#getStreamTypeForAudioUsage(int)}.
|
|
||||||
*
|
*
|
||||||
* <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of the
|
* <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of the
|
||||||
* remote device is returned.
|
* remote device is returned.
|
||||||
@ -3508,10 +3507,6 @@ public interface Player {
|
|||||||
* <p>If tunneling is enabled by the track selector, the specified audio attributes will be
|
* <p>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.
|
* ignored, but they will take effect if audio is later played without tunneling.
|
||||||
*
|
*
|
||||||
* <p>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)}.
|
|
||||||
*
|
|
||||||
* <p>If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link
|
* <p>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
|
* C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link
|
||||||
* IllegalArgumentException}.
|
* IllegalArgumentException}.
|
||||||
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>The default values are:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>focus listener and handler: none
|
||||||
|
* <li>audio attributes: {@link AudioAttributes#DEFAULT}
|
||||||
|
* <li>pauses on duck: false
|
||||||
|
* <li>supports delayed focus grant: false
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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. <br>
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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() {}
|
||||||
|
}
|
@ -84,6 +84,7 @@ import androidx.annotation.ChecksSdkIntAtLeast;
|
|||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.media3.common.AudioAttributes;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.C.ContentType;
|
import androidx.media3.common.C.ContentType;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
@ -2382,6 +2383,8 @@ public final class Util {
|
|||||||
return C.USAGE_ASSISTANCE_SONIFICATION;
|
return C.USAGE_ASSISTANCE_SONIFICATION;
|
||||||
case C.STREAM_TYPE_VOICE_CALL:
|
case C.STREAM_TYPE_VOICE_CALL:
|
||||||
return C.USAGE_VOICE_COMMUNICATION;
|
return C.USAGE_VOICE_COMMUNICATION;
|
||||||
|
case C.STREAM_TYPE_ACCESSIBILITY:
|
||||||
|
return C.USAGE_ASSISTANCE_ACCESSIBILITY;
|
||||||
case C.STREAM_TYPE_MUSIC:
|
case C.STREAM_TYPE_MUSIC:
|
||||||
default:
|
default:
|
||||||
return C.USAGE_MEDIA;
|
return C.USAGE_MEDIA;
|
||||||
@ -2404,6 +2407,7 @@ public final class Util {
|
|||||||
case C.STREAM_TYPE_SYSTEM:
|
case C.STREAM_TYPE_SYSTEM:
|
||||||
return C.AUDIO_CONTENT_TYPE_SONIFICATION;
|
return C.AUDIO_CONTENT_TYPE_SONIFICATION;
|
||||||
case C.STREAM_TYPE_VOICE_CALL:
|
case C.STREAM_TYPE_VOICE_CALL:
|
||||||
|
case C.STREAM_TYPE_ACCESSIBILITY:
|
||||||
return C.AUDIO_CONTENT_TYPE_SPEECH;
|
return C.AUDIO_CONTENT_TYPE_SPEECH;
|
||||||
case C.STREAM_TYPE_MUSIC:
|
case C.STREAM_TYPE_MUSIC:
|
||||||
default:
|
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
|
@UnstableApi
|
||||||
public static @C.StreamType int getStreamTypeForAudioUsage(@C.AudioUsage int usage) {
|
public static @C.StreamType int getStreamTypeForAudioUsage(@C.AudioUsage int usage) {
|
||||||
switch (usage) {
|
switch (usage) {
|
||||||
@ -2436,6 +2443,7 @@ public final class Util {
|
|||||||
case C.USAGE_NOTIFICATION_EVENT:
|
case C.USAGE_NOTIFICATION_EVENT:
|
||||||
return C.STREAM_TYPE_NOTIFICATION;
|
return C.STREAM_TYPE_NOTIFICATION;
|
||||||
case C.USAGE_ASSISTANCE_ACCESSIBILITY:
|
case C.USAGE_ASSISTANCE_ACCESSIBILITY:
|
||||||
|
return C.STREAM_TYPE_ACCESSIBILITY;
|
||||||
case C.USAGE_ASSISTANT:
|
case C.USAGE_ASSISTANT:
|
||||||
case C.USAGE_UNKNOWN:
|
case C.USAGE_UNKNOWN:
|
||||||
default:
|
default:
|
||||||
|
@ -15,6 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer;
|
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 androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
@ -24,14 +29,15 @@ import android.media.AudioManager;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.media3.common.AudioAttributes;
|
import androidx.media3.common.AudioAttributes;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Player;
|
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.Assertions;
|
||||||
import androidx.media3.common.util.Log;
|
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.Supplier;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import java.lang.annotation.Documented;
|
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. */
|
/** Audio focus has been temporarily lost, but playback may continue with reduced volume. */
|
||||||
private static final int AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK = 4;
|
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 String TAG = "AudioFocusManager";
|
||||||
|
|
||||||
private static final float VOLUME_MULTIPLIER_DUCK = 0.2f;
|
private static final float VOLUME_MULTIPLIER_DUCK = 0.2f;
|
||||||
private static final float VOLUME_MULTIPLIER_DEFAULT = 1.0f;
|
private static final float VOLUME_MULTIPLIER_DEFAULT = 1.0f;
|
||||||
|
|
||||||
private final Supplier<AudioManager> audioManager;
|
private final Supplier<AudioManager> audioManager;
|
||||||
private final AudioFocusListener focusListener;
|
private final Handler eventHandler;
|
||||||
@Nullable private PlayerControl playerControl;
|
@Nullable private PlayerControl playerControl;
|
||||||
@Nullable private AudioAttributes audioAttributes;
|
@Nullable private AudioAttributes audioAttributes;
|
||||||
|
|
||||||
private @AudioFocusState int audioFocusState;
|
private @AudioFocusState int audioFocusState;
|
||||||
private @AudioFocusGain int focusGainToRequest;
|
private @AudioManagerCompat.AudioFocusGain int focusGainToRequest;
|
||||||
private float volumeMultiplier = VOLUME_MULTIPLIER_DEFAULT;
|
private float volumeMultiplier = VOLUME_MULTIPLIER_DEFAULT;
|
||||||
private @MonotonicNonNull AudioFocusRequest audioFocusRequest;
|
private @MonotonicNonNull AudioFocusRequestCompat audioFocusRequest;
|
||||||
private boolean rebuildAudioFocusRequest;
|
private boolean rebuildAudioFocusRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,7 +148,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
(AudioManager)
|
(AudioManager)
|
||||||
context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE)));
|
context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE)));
|
||||||
this.playerControl = playerControl;
|
this.playerControl = playerControl;
|
||||||
this.focusListener = new AudioFocusListener(eventHandler);
|
this.eventHandler = eventHandler;
|
||||||
this.audioFocusState = AUDIO_FOCUS_STATE_NOT_REQUESTED;
|
this.audioFocusState = AUDIO_FOCUS_STATE_NOT_REQUESTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +167,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* managed automatically.
|
* managed automatically.
|
||||||
*/
|
*/
|
||||||
public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) {
|
public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) {
|
||||||
if (!Util.areEqual(this.audioAttributes, audioAttributes)) {
|
if (!Objects.equal(this.audioAttributes, audioAttributes)) {
|
||||||
this.audioAttributes = audioAttributes;
|
this.audioAttributes = audioAttributes;
|
||||||
focusGainToRequest = convertAudioAttributesToFocusGain(audioAttributes);
|
focusGainToRequest = convertAudioAttributesToFocusGain(audioAttributes);
|
||||||
Assertions.checkArgument(
|
Assertions.checkArgument(
|
||||||
@ -257,7 +217,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
/* package */ AudioManager.OnAudioFocusChangeListener getFocusListener() {
|
/* package */ AudioManager.OnAudioFocusChangeListener getFocusListener() {
|
||||||
return focusListener;
|
return this::handlePlatformAudioFocusChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldHandleAudioFocus(@Player.State int playbackState) {
|
private boolean shouldHandleAudioFocus(@Player.State int playbackState) {
|
||||||
@ -268,7 +228,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
if (audioFocusState == AUDIO_FOCUS_STATE_HAVE_FOCUS) {
|
if (audioFocusState == AUDIO_FOCUS_STATE_HAVE_FOCUS) {
|
||||||
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
||||||
}
|
}
|
||||||
int requestResult = Util.SDK_INT >= 26 ? requestAudioFocusV26() : requestAudioFocusDefault();
|
int requestResult = requestAudioFocusInternal();
|
||||||
if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||||
setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
|
setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
|
||||||
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
return PLAYER_COMMAND_PLAY_WHEN_READY;
|
||||||
@ -283,53 +243,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|| audioFocusState == AUDIO_FOCUS_STATE_NOT_REQUESTED) {
|
|| audioFocusState == AUDIO_FOCUS_STATE_NOT_REQUESTED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Util.SDK_INT >= 26) {
|
if (audioFocusRequest != null) {
|
||||||
abandonAudioFocusV26();
|
AudioManagerCompat.abandonAudioFocusRequest(audioManager.get(), audioFocusRequest);
|
||||||
} else {
|
|
||||||
abandonAudioFocusDefault();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int requestAudioFocusDefault() {
|
private int requestAudioFocusInternal() {
|
||||||
return audioManager
|
|
||||||
.get()
|
|
||||||
.requestAudioFocus(
|
|
||||||
focusListener,
|
|
||||||
Util.getStreamTypeForAudioUsage(checkNotNull(audioAttributes).usage),
|
|
||||||
focusGainToRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(26)
|
|
||||||
private int requestAudioFocusV26() {
|
|
||||||
if (audioFocusRequest == null || rebuildAudioFocusRequest) {
|
if (audioFocusRequest == null || rebuildAudioFocusRequest) {
|
||||||
AudioFocusRequest.Builder builder =
|
AudioFocusRequestCompat.Builder builder =
|
||||||
audioFocusRequest == null
|
audioFocusRequest == null
|
||||||
? new AudioFocusRequest.Builder(focusGainToRequest)
|
? new AudioFocusRequestCompat.Builder(focusGainToRequest)
|
||||||
: new AudioFocusRequest.Builder(audioFocusRequest);
|
: audioFocusRequest.buildUpon();
|
||||||
|
|
||||||
boolean willPauseWhenDucked = willPauseWhenDucked();
|
boolean willPauseWhenDucked = willPauseWhenDucked();
|
||||||
audioFocusRequest =
|
audioFocusRequest =
|
||||||
builder
|
builder
|
||||||
.setAudioAttributes(
|
.setAudioAttributes(checkNotNull(audioAttributes))
|
||||||
checkNotNull(audioAttributes).getAudioAttributesV21().audioAttributes)
|
|
||||||
.setWillPauseWhenDucked(willPauseWhenDucked)
|
.setWillPauseWhenDucked(willPauseWhenDucked)
|
||||||
.setOnAudioFocusChangeListener(focusListener)
|
.setOnAudioFocusChangeListener(this::handlePlatformAudioFocusChange, eventHandler)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
rebuildAudioFocusRequest = false;
|
rebuildAudioFocusRequest = false;
|
||||||
}
|
}
|
||||||
return audioManager.get().requestAudioFocus(audioFocusRequest);
|
return AudioManagerCompat.requestAudioFocus(audioManager.get(), audioFocusRequest);
|
||||||
}
|
|
||||||
|
|
||||||
private void abandonAudioFocusDefault() {
|
|
||||||
audioManager.get().abandonAudioFocus(focusListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(26)
|
|
||||||
private void abandonAudioFocusV26() {
|
|
||||||
if (audioFocusRequest != null) {
|
|
||||||
audioManager.get().abandonAudioFocusRequest(audioFocusRequest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean willPauseWhenDucked() {
|
private boolean willPauseWhenDucked() {
|
||||||
@ -344,7 +280,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* @param audioAttributes The audio attributes associated with this focus request.
|
* @param audioAttributes The audio attributes associated with this focus request.
|
||||||
* @return The type of audio focus gain that should be requested.
|
* @return The type of audio focus gain that should be requested.
|
||||||
*/
|
*/
|
||||||
private static @AudioFocusGain int convertAudioAttributesToFocusGain(
|
private static @AudioManagerCompat.AudioFocusGain int convertAudioAttributesToFocusGain(
|
||||||
@Nullable AudioAttributes audioAttributes) {
|
@Nullable AudioAttributes audioAttributes) {
|
||||||
if (audioAttributes == null) {
|
if (audioAttributes == null) {
|
||||||
// Don't handle audio focus. It may be either video only contents or developers
|
// Don't handle audio focus. It may be either video only contents or developers
|
||||||
@ -460,19 +396,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
playerControl.executePlayerCommand(playerCommand);
|
playerControl.executePlayerCommand(playerCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal audio focus listener.
|
|
||||||
|
|
||||||
private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
|
|
||||||
private final Handler eventHandler;
|
|
||||||
|
|
||||||
public AudioFocusListener(Handler eventHandler) {
|
|
||||||
this.eventHandler = eventHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioFocusChange(int focusChange) {
|
|
||||||
eventHandler.post(() -> handlePlatformAudioFocusChange(focusChange));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -426,10 +426,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
if (builder.deviceVolumeControlEnabled) {
|
if (builder.deviceVolumeControlEnabled) {
|
||||||
streamVolumeManager =
|
streamVolumeManager =
|
||||||
new StreamVolumeManager(
|
new StreamVolumeManager(
|
||||||
builder.context,
|
builder.context, eventHandler, componentListener, audioAttributes.getStreamType());
|
||||||
eventHandler,
|
|
||||||
componentListener,
|
|
||||||
Util.getStreamTypeForAudioUsage(audioAttributes.usage));
|
|
||||||
} else {
|
} else {
|
||||||
streamVolumeManager = null;
|
streamVolumeManager = null;
|
||||||
}
|
}
|
||||||
@ -1502,8 +1499,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
this.audioAttributes = newAudioAttributes;
|
this.audioAttributes = newAudioAttributes;
|
||||||
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, newAudioAttributes);
|
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, newAudioAttributes);
|
||||||
if (streamVolumeManager != null) {
|
if (streamVolumeManager != null) {
|
||||||
streamVolumeManager.setStreamType(
|
streamVolumeManager.setStreamType(newAudioAttributes.getStreamType());
|
||||||
Util.getStreamTypeForAudioUsage(newAudioAttributes.usage));
|
|
||||||
}
|
}
|
||||||
// Queue event only and flush after updating playWhenReady in case both events are triggered.
|
// Queue event only and flush after updating playWhenReady in case both events are triggered.
|
||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
|
@ -243,10 +243,6 @@ public interface Renderer extends PlayerMessage.Target {
|
|||||||
* <p>If tunneling is enabled by the track selector, the specified audio attributes will be
|
* <p>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.
|
* ignored, but they will take effect if audio is later played without tunneling.
|
||||||
*
|
*
|
||||||
* <p>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)}.
|
|
||||||
*
|
|
||||||
* <p>To get audio attributes that are equivalent to a legacy stream type, pass the stream type to
|
* <p>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
|
* {@link Util#getAudioUsageForStreamType(int)} and use the returned {@link C.AudioUsage} to build
|
||||||
* an audio attributes instance.
|
* an audio attributes instance.
|
||||||
|
@ -23,6 +23,7 @@ import android.media.AudioManager;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.audio.AudioManagerCompat;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
@ -96,7 +97,7 @@ import androidx.media3.common.util.Util;
|
|||||||
* #setStreamType(int)} is called.
|
* #setStreamType(int)} is called.
|
||||||
*/
|
*/
|
||||||
public int getMinVolume() {
|
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.
|
* #setStreamType(int)} is called.
|
||||||
*/
|
*/
|
||||||
public int getMaxVolume() {
|
public int getMaxVolume() {
|
||||||
return audioManager.getStreamMaxVolume(streamType);
|
return AudioManagerCompat.getStreamMaxVolume(audioManager, streamType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the current volume for the current audio stream. */
|
/** Gets the current volume for the current audio stream. */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user