diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 32ab94035b..6e81af2448 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -41,6 +41,10 @@ with which the `DefaultAudioSink` will register as the listener to the `AudioCapabilitiesReceiver` and update its `audioCapabilities` property when informed with a capabilities change. + * Propagate audio capabilities changes via a new event + `onAudioCapabilitiesChanged` in `AudioSink.Listener` interface, and a + new interface `RendererCapabilities.Listener` which triggers + `onRendererCapabilitiesChanged` events. * 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/BaseRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/BaseRenderer.java index 50effc32de..40c717378b 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/BaseRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/BaseRenderer.java @@ -18,6 +18,7 @@ package androidx.media3.exoplayer; import static androidx.media3.common.util.Assertions.checkNotNull; import static java.lang.Math.max; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.Format; @@ -37,6 +38,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @UnstableApi public abstract class BaseRenderer implements Renderer, RendererCapabilities { + private final Object lock; private final @C.TrackType int trackType; private final FormatHolder formatHolder; @@ -52,11 +54,16 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { private boolean streamIsFinal; private boolean throwRendererExceptionIsExecuting; + @GuardedBy("lock") + @Nullable + protected RendererCapabilities.Listener rendererCapabilitiesListener; + /** * @param trackType The track type that the renderer handles. One of the {@link C} {@code * TRACK_TYPE_*} constants. */ public BaseRenderer(@C.TrackType int trackType) { + lock = new Object(); this.trackType = trackType; formatHolder = new FormatHolder(); readingPositionUs = C.TIME_END_OF_SOURCE; @@ -210,6 +217,27 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { return ADAPTIVE_NOT_SUPPORTED; } + @Override + public final void setListener(RendererCapabilities.Listener listener) { + synchronized (lock) { + this.rendererCapabilitiesListener = listener; + } + } + + @Override + public final void clearListener() { + synchronized (lock) { + this.rendererCapabilitiesListener = null; + } + } + + @Nullable + private Listener getListener() { + synchronized (lock) { + return this.rendererCapabilitiesListener; + } + } + // PlayerMessage.Target implementation. @Override @@ -486,4 +514,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { protected final boolean isSourceReady() { return hasReadStreamToEnd() ? streamIsFinal : Assertions.checkNotNull(stream).isReady(); } + + /** Called when the renderer capabilities are changed. */ + protected final void onRendererCapabilitiesChanged() { + @Nullable RendererCapabilities.Listener listener = getListener(); + if (listener != null) { + listener.onRendererCapabilitiesChanged(this); + } + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index 83915dc840..1cfa125c10 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -268,9 +268,15 @@ import java.util.concurrent.atomic.AtomicBoolean; playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo); rendererCapabilities = new RendererCapabilities[renderers.length]; + @Nullable + RendererCapabilities.Listener rendererCapabilitiesListener = + trackSelector.getRendererCapabilitiesListener(); for (int i = 0; i < renderers.length; i++) { renderers[i].init(/* index= */ i, playerId); rendererCapabilities[i] = renderers[i].getCapabilities(); + if (rendererCapabilitiesListener != null) { + rendererCapabilities[i].setListener(rendererCapabilitiesListener); + } } mediaClock = new DefaultMediaClock(this, clock); pendingMessages = new ArrayList<>(); @@ -2558,8 +2564,9 @@ import java.util.concurrent.atomic.AtomicBoolean; } private void releaseRenderers() { - for (Renderer renderer : renderers) { - renderer.release(); + for (int i = 0; i < renderers.length; i++) { + rendererCapabilities[i].clearListener(); + renderers[i].release(); } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java index dbc2fa059e..95e2250ecd 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java @@ -31,6 +31,19 @@ import java.lang.annotation.Target; @UnstableApi public interface RendererCapabilities { + /** Listener for renderer capabilities events. */ + interface Listener { + + /** + * Called when the renderer capabilities are changed. + * + *
This method will be called on the playback thread.
+ *
+ * @param renderer The renderer that has its capabilities changed.
+ */
+ void onRendererCapabilitiesChanged(Renderer renderer);
+ }
+
/**
* @deprecated Use {@link C.FormatSupport} instead.
*/
@@ -355,4 +368,18 @@ public interface RendererCapabilities {
*/
@AdaptiveSupport
int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException;
+
+ /**
+ * Sets the {@link Listener}.
+ *
+ * @param listener The listener to be set.
+ */
+ default void setListener(Listener listener) {
+ // Do nothing.
+ }
+
+ /** Clears the {@link Listener}. */
+ default void clearListener() {
+ // Do nothing.
+ }
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java
index f73f9635cc..3e781b1c07 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java
@@ -134,6 +134,9 @@ public interface AudioSink {
* a {@link WriteException}, or an {@link UnexpectedDiscontinuityException}.
*/
default void onAudioSinkError(Exception audioSinkError) {}
+
+ /** Called when audio capabilities changed. */
+ default void onAudioCapabilitiesChanged() {}
}
/** Thrown when a failure occurs configuring the sink. */
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 859b98f215..a1907c6ad7 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
@@ -1469,6 +1469,9 @@ public final class DefaultAudioSink implements AudioSink {
checkState(playbackLooper == Looper.myLooper());
if (!audioCapabilities.equals(getAudioCapabilities())) {
this.audioCapabilities = audioCapabilities;
+ if (listener != null) {
+ listener.onAudioCapabilitiesChanged();
+ }
}
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
index eb74f06c42..82e5276683 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
@@ -960,6 +960,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
Log.e(TAG, "Audio sink error", audioSinkError);
eventDispatcher.audioSinkError(audioSinkError);
}
+
+ @Override
+ public void onAudioCapabilitiesChanged() {
+ MediaCodecAudioRenderer.this.onRendererCapabilitiesChanged();
+ }
}
@RequiresApi(23)
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java
index f6ca0f3eee..8ff8888cf2 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java
@@ -187,6 +187,16 @@ public abstract class TrackSelector {
// Default implementation is no-op.
}
+ /**
+ * Returns the {@link RendererCapabilities.Listener} that the concrete instance uses to listen to
+ * the renderer capabilities changes. May be {@code null} if the implementation does not listen to
+ * the renderer capabilities changes.
+ */
+ @Nullable
+ public RendererCapabilities.Listener getRendererCapabilitiesListener() {
+ return null;
+ }
+
/**
* Calls {@link InvalidationListener#onTrackSelectionsInvalidated()} to invalidate all previously
* generated track selections.
diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java
index f7cd34f10c..2ba87a3522 100644
--- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java
+++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java
@@ -83,6 +83,7 @@ public class MediaCodecAudioRendererTest {
@Mock private AudioSink audioSink;
@Mock private AudioRendererEventListener audioRendererEventListener;
+ @Mock private RendererCapabilities.Listener rendererCapabilitiesListener;
@Before
public void setUp() throws Exception {
@@ -326,6 +327,21 @@ public class MediaCodecAudioRendererTest {
verify(audioRendererEventListener).onAudioSinkError(error);
}
+ @Test
+ public void
+ renderer_callsRendererCapabilitiesListener_whenAudioSinkListenerOnAudioCapabilitiesChangedIsCalled() {
+ final ArgumentCaptor