mirror of
https://github.com/samsonjs/media.git
synced 2026-04-05 11:15:46 +00:00
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:
parent
167677ec84
commit
7babcf28dc
9 changed files with 113 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
Loading…
Reference in a new issue