diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a3f4fc2941..773921155e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -29,6 +29,11 @@ * Extrapolate current position during sleep with offload scheduling. * Add `Renderer.release()` and `AudioSink.release()` for releasing the resources at the end of player's lifecycle. + * Listen to audio capabilities changes in `DefaultAudioSink`. Add a + required parameter `context` in the constructor of `DefaultAudioSink`, + with which the `DefaultAudioSink` will register as the listener to the + `AudioCapabilitiesReceiver` and update its `audioCapabilities` property + when informed with a capabilities change. * DRM: * Reduce the visibility of several internal-only methods on `DefaultDrmSession` that aren't expected to be called from outside the diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java index 02a77a96e4..f3443e1307 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java @@ -26,7 +26,6 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; -import androidx.media3.exoplayer.audio.AudioCapabilities; import androidx.media3.exoplayer.audio.AudioRendererEventListener; import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.DefaultAudioSink; @@ -649,8 +648,7 @@ public class DefaultRenderersFactory implements RenderersFactory { boolean enableFloatOutput, boolean enableAudioTrackPlaybackParams, boolean enableOffload) { - return new DefaultAudioSink.Builder() - .setAudioCapabilities(AudioCapabilities.getCapabilities(context)) + return new DefaultAudioSink.Builder(context) .setEnableFloatOutput(enableFloatOutput) .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams) .setOffloadMode( diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java index ae2a4b27d7..859b98f215 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java @@ -17,13 +17,16 @@ package androidx.media3.exoplayer.audio; import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER; import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.constrainValue; import static androidx.media3.exoplayer.audio.AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES; +import static androidx.media3.exoplayer.audio.AudioCapabilities.getCapabilities; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; +import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; @@ -227,6 +230,7 @@ public final class DefaultAudioSink implements AudioSink { /** A builder to create {@link DefaultAudioSink} instances. */ public static final class Builder { + @Nullable private final Context context; private AudioCapabilities audioCapabilities; @Nullable private androidx.media3.common.audio.AudioProcessorChain audioProcessorChain; private boolean enableFloatOutput; @@ -235,19 +239,36 @@ public final class DefaultAudioSink implements AudioSink { AudioTrackBufferSizeProvider audioTrackBufferSizeProvider; @Nullable AudioOffloadListener audioOffloadListener; - /** Creates a new builder. */ + /** + * @deprecated Use {@link #Builder(Context)} instead. + */ + @Deprecated public Builder() { + this.context = null; audioCapabilities = DEFAULT_AUDIO_CAPABILITIES; offloadMode = OFFLOAD_MODE_DISABLED; audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT; } /** - * Sets audio capabilities for playback on this device. May be {@code null} if the default - * capabilities (no encoded audio passthrough support) should be assumed. + * Creates a new builder. * - *

Default is {@link AudioCapabilities#DEFAULT_AUDIO_CAPABILITIES}. + * @param context The {@link Context}. */ + public Builder(Context context) { + this.context = context; + audioCapabilities = DEFAULT_AUDIO_CAPABILITIES; + offloadMode = OFFLOAD_MODE_DISABLED; + audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT; + } + + /** + * @deprecated These {@linkplain AudioCapabilities audio capabilities} are only used in the + * absence of a {@linkplain Context context}. In the case when the {@code Context} is {@code + * null} and the {@code audioCapabilities} is not set to the {@code Builder}, the default + * capabilities (no encoded audio passthrough support) should be assumed. + */ + @Deprecated @CanIgnoreReturnValue public Builder setAudioCapabilities(AudioCapabilities audioCapabilities) { checkNotNull(audioCapabilities); @@ -466,6 +487,7 @@ public final class DefaultAudioSink implements AudioSink { @GuardedBy("releaseExecutorLock") private static int pendingReleaseCount; + @Nullable private final Context context; private final androidx.media3.common.audio.AudioProcessorChain audioProcessorChain; private final boolean enableFloatOutput; private final ChannelMappingAudioProcessor channelMappingAudioProcessor; @@ -491,6 +513,7 @@ public final class DefaultAudioSink implements AudioSink { private @MonotonicNonNull AudioProcessingPipeline audioProcessingPipeline; @Nullable private AudioTrack audioTrack; private AudioCapabilities audioCapabilities; + private @MonotonicNonNull AudioCapabilitiesReceiver audioCapabilitiesReceiver; private AudioAttributes audioAttributes; @Nullable private MediaPositionParameters afterDrainParameters; @@ -529,10 +552,12 @@ public final class DefaultAudioSink implements AudioSink { private long lastFeedElapsedRealtimeMs; private boolean offloadDisabledUntilNextConfiguration; private boolean isWaitingForOffloadEndOfStreamHandled; + @Nullable private Looper playbackLooper; @RequiresNonNull("#1.audioProcessorChain") private DefaultAudioSink(Builder builder) { - audioCapabilities = builder.audioCapabilities; + context = builder.context; + audioCapabilities = context != null ? getCapabilities(context) : builder.audioCapabilities; audioProcessorChain = builder.audioProcessorChain; enableFloatOutput = Util.SDK_INT >= 21 && builder.enableFloatOutput; preferAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams; @@ -599,7 +624,7 @@ public final class DefaultAudioSink implements AudioSink { if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) { return SINK_FORMAT_SUPPORTED_DIRECTLY; } - if (audioCapabilities.isPassthroughPlaybackSupported(format)) { + if (getAudioCapabilities().isPassthroughPlaybackSupported(format)) { return SINK_FORMAT_SUPPORTED_DIRECTLY; } return SINK_FORMAT_UNSUPPORTED; @@ -692,7 +717,7 @@ public final class DefaultAudioSink implements AudioSink { outputMode = OUTPUT_MODE_PASSTHROUGH; @Nullable Pair encodingAndChannelConfig = - audioCapabilities.getEncodingAndChannelConfigForPassthrough(inputFormat); + getAudioCapabilities().getEncodingAndChannelConfigForPassthrough(inputFormat); if (encodingAndChannelConfig == null) { throw new ConfigurationException( "Unable to configure passthrough for: " + inputFormat, inputFormat); @@ -1431,6 +1456,22 @@ public final class DefaultAudioSink implements AudioSink { offloadDisabledUntilNextConfiguration = false; } + @Override + public void release() { + if (audioCapabilitiesReceiver != null) { + audioCapabilitiesReceiver.unregister(); + } + } + + // AudioCapabilitiesReceiver.Listener implementation. + + public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) { + checkState(playbackLooper == Looper.myLooper()); + if (!audioCapabilities.equals(getAudioCapabilities())) { + this.audioCapabilities = audioCapabilities; + } + } + // Internal methods. private void resetSinkStateForFlush() { @@ -1646,6 +1687,18 @@ public final class DefaultAudioSink implements AudioSink { } } + private AudioCapabilities getAudioCapabilities() { + if (audioCapabilitiesReceiver == null && context != null) { + // Must be lazily initialized to receive audio capabilities receiver listener event on the + // current (playback) thread as the constructor is not called in the playback thread. + playbackLooper = Looper.myLooper(); + audioCapabilitiesReceiver = + new AudioCapabilitiesReceiver(context, this::onAudioCapabilitiesChanged); + audioCapabilities = audioCapabilitiesReceiver.register(); + } + return audioCapabilities; + } + @RequiresApi(29) @SuppressLint("InlinedApi") private int getOffloadedPlaybackSupport(