Commit e85924bf by tianyifeng Committed by Marc Baechinger

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
parent 0e50c5cf
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
...@@ -33,6 +34,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -33,6 +34,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** An abstract base class suitable for most {@link Renderer} implementations. */ /** An abstract base class suitable for most {@link Renderer} implementations. */
public abstract class BaseRenderer implements Renderer, RendererCapabilities { public abstract class BaseRenderer implements Renderer, RendererCapabilities {
private final Object lock;
private final @C.TrackType int trackType; private final @C.TrackType int trackType;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
...@@ -48,11 +50,16 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -48,11 +50,16 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
private boolean streamIsFinal; private boolean streamIsFinal;
private boolean throwRendererExceptionIsExecuting; 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 * @param trackType The track type that the renderer handles. One of the {@link C} {@code
* TRACK_TYPE_*} constants. * TRACK_TYPE_*} constants.
*/ */
public BaseRenderer(@C.TrackType int trackType) { public BaseRenderer(@C.TrackType int trackType) {
lock = new Object();
this.trackType = trackType; this.trackType = trackType;
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
readingPositionUs = C.TIME_END_OF_SOURCE; readingPositionUs = C.TIME_END_OF_SOURCE;
...@@ -206,6 +213,27 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -206,6 +213,27 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
return ADAPTIVE_NOT_SUPPORTED; 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. // PlayerMessage.Target implementation.
@Override @Override
...@@ -482,4 +510,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -482,4 +510,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
protected final boolean isSourceReady() { protected final boolean isSourceReady() {
return hasReadStreamToEnd() ? streamIsFinal : Assertions.checkNotNull(stream).isReady(); 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);
}
}
} }
...@@ -260,9 +260,15 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -260,9 +260,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo); playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo);
rendererCapabilities = new RendererCapabilities[renderers.length]; rendererCapabilities = new RendererCapabilities[renderers.length];
@Nullable
RendererCapabilities.Listener rendererCapabilitiesListener =
trackSelector.getRendererCapabilitiesListener();
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
renderers[i].init(/* index= */ i, playerId); renderers[i].init(/* index= */ i, playerId);
rendererCapabilities[i] = renderers[i].getCapabilities(); rendererCapabilities[i] = renderers[i].getCapabilities();
if (rendererCapabilitiesListener != null) {
rendererCapabilities[i].setListener(rendererCapabilitiesListener);
}
} }
mediaClock = new DefaultMediaClock(this, clock); mediaClock = new DefaultMediaClock(this, clock);
pendingMessages = new ArrayList<>(); pendingMessages = new ArrayList<>();
...@@ -2550,8 +2556,9 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2550,8 +2556,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void releaseRenderers() { private void releaseRenderers() {
for (Renderer renderer : renderers) { for (int i = 0; i < renderers.length; i++) {
renderer.release(); rendererCapabilities[i].clearListener();
renderers[i].release();
} }
} }
......
...@@ -27,6 +27,19 @@ import java.lang.annotation.Target; ...@@ -27,6 +27,19 @@ import java.lang.annotation.Target;
/** Defines the capabilities of a {@link Renderer}. */ /** Defines the capabilities of a {@link Renderer}. */
public interface RendererCapabilities { 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. * @deprecated Use {@link C.FormatSupport} instead.
*/ */
...@@ -351,4 +364,18 @@ public interface RendererCapabilities { ...@@ -351,4 +364,18 @@ public interface RendererCapabilities {
*/ */
@AdaptiveSupport @AdaptiveSupport
int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException; 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.
}
} }
...@@ -130,6 +130,9 @@ public interface AudioSink { ...@@ -130,6 +130,9 @@ public interface AudioSink {
* a {@link WriteException}, or an {@link UnexpectedDiscontinuityException}. * a {@link WriteException}, or an {@link UnexpectedDiscontinuityException}.
*/ */
default void onAudioSinkError(Exception audioSinkError) {} default void onAudioSinkError(Exception audioSinkError) {}
/** Called when audio capabilities changed. */
default void onAudioCapabilitiesChanged() {}
} }
/** Thrown when a failure occurs configuring the sink. */ /** Thrown when a failure occurs configuring the sink. */
......
...@@ -1456,6 +1456,9 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1456,6 +1456,9 @@ public final class DefaultAudioSink implements AudioSink {
checkState(playbackLooper == Looper.myLooper()); checkState(playbackLooper == Looper.myLooper());
if (!audioCapabilities.equals(getAudioCapabilities())) { if (!audioCapabilities.equals(getAudioCapabilities())) {
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
if (listener != null) {
listener.onAudioCapabilitiesChanged();
}
} }
} }
......
...@@ -955,6 +955,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -955,6 +955,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
Log.e(TAG, "Audio sink error", audioSinkError); Log.e(TAG, "Audio sink error", audioSinkError);
eventDispatcher.audioSinkError(audioSinkError); eventDispatcher.audioSinkError(audioSinkError);
} }
@Override
public void onAudioCapabilitiesChanged() {
MediaCodecAudioRenderer.this.onRendererCapabilitiesChanged();
}
} }
@RequiresApi(23) @RequiresApi(23)
......
...@@ -185,6 +185,16 @@ public abstract class TrackSelector { ...@@ -185,6 +185,16 @@ public abstract class TrackSelector {
} }
/** /**
* 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 * Calls {@link InvalidationListener#onTrackSelectionsInvalidated()} to invalidate all previously
* generated track selections. * generated track selections.
*/ */
......
...@@ -83,6 +83,7 @@ public class MediaCodecAudioRendererTest { ...@@ -83,6 +83,7 @@ public class MediaCodecAudioRendererTest {
@Mock private AudioSink audioSink; @Mock private AudioSink audioSink;
@Mock private AudioRendererEventListener audioRendererEventListener; @Mock private AudioRendererEventListener audioRendererEventListener;
@Mock private RendererCapabilities.Listener rendererCapabilitiesListener;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
...@@ -327,6 +328,21 @@ public class MediaCodecAudioRendererTest { ...@@ -327,6 +328,21 @@ public class MediaCodecAudioRendererTest {
} }
@Test @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 { public void render_callsAudioSinkSetOutputStreamOffset_whenReplaceStream() throws Exception {
FakeSampleStream fakeSampleStream1 = FakeSampleStream fakeSampleStream1 =
new FakeSampleStream( new FakeSampleStream(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment