Propagate audio capabilities changes out from the audio renderers

* Add a new event `onAudioCapabilitiesChanged` in `AudioSink.Listener` interface.
* Add an interface `RendererCapabilities.Listener`, which will listen to `onRendererCapabilitiesChanged` events from the renderer.
* Add `getRendererCapabilitiesReceiver` method for `TrackSelector`, and register/unregister the `TrackSelector` as the `RendererCapabilitiesReceiver` (if implemented) when the `ExoPlayer` is initialized/released.
* Trigger the `AudioSink.Listener.onAudioCapabilitiesChanged` and further `RendererCapabilities.Listener.onRendererCapabilitiesChanged` events when the audio capabilities changes are detected in `DefaultAudioSink`.

PiperOrigin-RevId: 521427567
This commit is contained in:
tianyifeng 2023-04-03 13:02:13 +01:00 committed by Marc Baechinger
parent 167677ec84
commit 7babcf28dc
9 changed files with 113 additions and 2 deletions

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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.
*
* <p>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.
}
}

View file

@ -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. */

View file

@ -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();
}
}
}

View file

@ -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)

View file

@ -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.

View file

@ -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<AudioSink.Listener> listenerCaptor =
ArgumentCaptor.forClass(AudioSink.Listener.class);
verify(audioSink, atLeastOnce()).setListener(listenerCaptor.capture());
AudioSink.Listener audioSinkListener = listenerCaptor.getValue();
mediaCodecAudioRenderer.getCapabilities().setListener(rendererCapabilitiesListener);
audioSinkListener.onAudioCapabilitiesChanged();
shadowOf(Looper.getMainLooper()).idle();
verify(rendererCapabilitiesListener).onRendererCapabilitiesChanged(mediaCodecAudioRenderer);
}
@Test
public void render_callsAudioSinkSetOutputStreamOffset_whenReplaceStream() throws Exception {
FakeSampleStream fakeSampleStream1 =