Move instantiation of CompositingVideoSinkProvider
This change moves the instantiation of the CompositingVideoSinkProvider out of MediaCodecVideoRenderer so that the composition preview player can re-use the CompositingVideoSinkProvider instance for driving the rendering of images. The main point of the change is the ownership of the VideoFrameReleaseControl, which decides when a frame should be rendered and so far was owned by the MediaCodecVideoRenderer. With this change, in the context of composition preview, the VideoFrameReleaseControl is no longer owned by MediaCodecVideoRenderer, but provided to it. This way, the CompositingVideoSinkProvider instance, hence the VideoFrameReleaseControl can be re-used to funnel images into the video pipeline and render the pipeline from elsewhere (and not MediaCodecVideoRenderer). PiperOrigin-RevId: 588459007
This commit is contained in:
parent
bd19953ac9
commit
073ee8a1d0
@ -56,6 +56,13 @@
|
|||||||
Google TV, and Lenovo M10 FHD Plus that causes 60fps AVC streams to be
|
Google TV, and Lenovo M10 FHD Plus that causes 60fps AVC streams to be
|
||||||
marked as unsupported
|
marked as unsupported
|
||||||
([#693](https://github.com/androidx/media/issues/693)).
|
([#693](https://github.com/androidx/media/issues/693)).
|
||||||
|
* Change the `MediaCodecVideoRenderer` constructor that takes a
|
||||||
|
`VideoFrameProcessor.Factory` argument and replace it with a constructor
|
||||||
|
that takes a `VideoSinkProvider` argument. Apps that want to inject a
|
||||||
|
custom `VideoFrameProcessor.Factory` can instantiate a
|
||||||
|
`CompositingVideoSinkProvider` that uses the custom
|
||||||
|
`VideoFrameProcessor.Factory` and pass the video sink provider to
|
||||||
|
`MediaCodecVideoRenderer`.
|
||||||
* Text:
|
* Text:
|
||||||
* Fix serialization of bitmap cues to resolve `Tried to marshall a Parcel
|
* Fix serialization of bitmap cues to resolve `Tried to marshall a Parcel
|
||||||
that contained Binder objects` error when using
|
that contained Binder objects` error when using
|
||||||
|
@ -46,6 +46,7 @@ import androidx.media3.common.util.TimestampIterator;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||||
|
import androidx.media3.exoplayer.video.VideoFrameReleaseControl.FrameTimingEvaluator;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -61,7 +62,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
/** Handles composition of video sinks. */
|
/** Handles composition of video sinks. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
/* package */ final class CompositingVideoSinkProvider
|
public final class CompositingVideoSinkProvider
|
||||||
implements VideoSinkProvider, VideoGraph.Listener, VideoFrameRenderControl.FrameRenderer {
|
implements VideoSinkProvider, VideoGraph.Listener, VideoFrameRenderControl.FrameRenderer {
|
||||||
|
|
||||||
/** A builder for {@link CompositingVideoSinkProvider} instances. */
|
/** A builder for {@link CompositingVideoSinkProvider} instances. */
|
||||||
@ -112,6 +113,21 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
/**
|
/**
|
||||||
* Sets the {@link VideoFrameReleaseControl} that will be used.
|
* Sets the {@link VideoFrameReleaseControl} that will be used.
|
||||||
*
|
*
|
||||||
|
* <p>By default, a {@link VideoFrameReleaseControl} will be used with a {@link
|
||||||
|
* FrameTimingEvaluator} implementation which:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Signals to {@linkplain FrameTimingEvaluator#shouldForceReleaseFrame(long, long) force
|
||||||
|
* release} a frame if the frame is late by more than {@link #FRAME_LATE_THRESHOLD_US} and
|
||||||
|
* the elapsed time since the previous frame release is greater than {@link
|
||||||
|
* #FRAME_RELEASE_THRESHOLD_US}.
|
||||||
|
* <li>Signals to {@linkplain FrameTimingEvaluator#shouldDropFrame(long, long, boolean) drop a
|
||||||
|
* frame} if the frame is late by more than {@link #FRAME_LATE_THRESHOLD_US} and the frame
|
||||||
|
* is not marked as the last one.
|
||||||
|
* <li>Signals to never {@linkplain FrameTimingEvaluator#shouldIgnoreFrame(long, long, long,
|
||||||
|
* boolean, boolean) ignore} a frame.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
* @param videoFrameReleaseControl The {@link VideoFrameReleaseControl}.
|
* @param videoFrameReleaseControl The {@link VideoFrameReleaseControl}.
|
||||||
* @return This builder, for convenience.
|
* @return This builder, for convenience.
|
||||||
*/
|
*/
|
||||||
@ -123,10 +139,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
/**
|
/**
|
||||||
* Builds the {@link CompositingVideoSinkProvider}.
|
* Builds the {@link CompositingVideoSinkProvider}.
|
||||||
*
|
*
|
||||||
* <p>A {@link VideoFrameReleaseControl} must be set with {@link
|
|
||||||
* #setVideoFrameReleaseControl(VideoFrameReleaseControl)} otherwise this method throws {@link
|
|
||||||
* IllegalStateException}.
|
|
||||||
*
|
|
||||||
* <p>This method must be called at most once and will throw an {@link IllegalStateException} if
|
* <p>This method must be called at most once and will throw an {@link IllegalStateException} if
|
||||||
* it has already been called.
|
* it has already been called.
|
||||||
*/
|
*/
|
||||||
@ -140,6 +152,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
previewingVideoGraphFactory =
|
previewingVideoGraphFactory =
|
||||||
new ReflectivePreviewingSingleInputVideoGraphFactory(videoFrameProcessorFactory);
|
new ReflectivePreviewingSingleInputVideoGraphFactory(videoFrameProcessorFactory);
|
||||||
}
|
}
|
||||||
|
if (videoFrameReleaseControl == null) {
|
||||||
|
videoFrameReleaseControl =
|
||||||
|
new VideoFrameReleaseControl(
|
||||||
|
context, new CompositionFrameTimingEvaluator(), /* allowedJoiningTimeMs= */ 0);
|
||||||
|
}
|
||||||
CompositingVideoSinkProvider compositingVideoSinkProvider =
|
CompositingVideoSinkProvider compositingVideoSinkProvider =
|
||||||
new CompositingVideoSinkProvider(this);
|
new CompositingVideoSinkProvider(this);
|
||||||
built = true;
|
built = true;
|
||||||
@ -147,6 +164,41 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The time threshold, in microseconds, after which a frame is considered late. */
|
||||||
|
public static final long FRAME_LATE_THRESHOLD_US = -30_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum elapsed time threshold, in microseconds, since last releasing a frame after which a
|
||||||
|
* frame can be force released.
|
||||||
|
*/
|
||||||
|
public static final long FRAME_RELEASE_THRESHOLD_US = 100_000;
|
||||||
|
|
||||||
|
/** A {@link FrameTimingEvaluator} for composition frames. */
|
||||||
|
private static final class CompositionFrameTimingEvaluator implements FrameTimingEvaluator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldForceReleaseFrame(long earlyUs, long elapsedSinceLastReleaseUs) {
|
||||||
|
return earlyUs < FRAME_LATE_THRESHOLD_US
|
||||||
|
&& elapsedSinceLastReleaseUs > FRAME_RELEASE_THRESHOLD_US;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldDropFrame(long earlyUs, long elapsedRealtimeUs, boolean isLastFrame) {
|
||||||
|
return earlyUs < FRAME_LATE_THRESHOLD_US && !isLastFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldIgnoreFrame(
|
||||||
|
long earlyUs,
|
||||||
|
long positionUs,
|
||||||
|
long elapsedRealtimeUs,
|
||||||
|
boolean isLastFrame,
|
||||||
|
boolean treatDroppedBuffersAsSkipped) {
|
||||||
|
// TODO b/293873191 - Handle very late buffers and drop to key frame.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final Executor NO_OP_EXECUTOR = runnable -> {};
|
private static final Executor NO_OP_EXECUTOR = runnable -> {};
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -232,13 +284,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
if (released) {
|
if (released) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.removeCallbacksAndMessages(/* token= */ null);
|
handler.removeCallbacksAndMessages(/* token= */ null);
|
||||||
}
|
}
|
||||||
if (videoSinkImpl != null) {
|
|
||||||
videoSinkImpl.release();
|
|
||||||
}
|
|
||||||
if (videoGraph != null) {
|
if (videoGraph != null) {
|
||||||
videoGraph.release();
|
videoGraph.release();
|
||||||
}
|
}
|
||||||
@ -298,6 +347,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
this.videoFrameMetadataListener = videoFrameMetadataListener;
|
this.videoFrameMetadataListener = videoFrameMetadataListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VideoFrameReleaseControl getVideoFrameReleaseControl() {
|
||||||
|
return videoFrameReleaseControl;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setClock(Clock clock) {
|
public void setClock(Clock clock) {
|
||||||
checkState(!isInitialized());
|
checkState(!isInitialized());
|
||||||
@ -356,7 +410,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderFrame(
|
public void renderFrame(
|
||||||
long renderTimeNs, long bufferPresentationTimeUs, long streamOffsetUs, boolean isFirstFrame) {
|
long renderTimeNs, long presentationTimeUs, long streamOffsetUs, boolean isFirstFrame) {
|
||||||
if (isFirstFrame && listenerExecutor != NO_OP_EXECUTOR) {
|
if (isFirstFrame && listenerExecutor != NO_OP_EXECUTOR) {
|
||||||
VideoSinkImpl videoSink = checkStateNotNull(videoSinkImpl);
|
VideoSinkImpl videoSink = checkStateNotNull(videoSinkImpl);
|
||||||
VideoSink.Listener currentListener = this.listener;
|
VideoSink.Listener currentListener = this.listener;
|
||||||
@ -367,7 +421,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
// onVideoSizeChanged is announced after the first frame is available for rendering.
|
// onVideoSizeChanged is announced after the first frame is available for rendering.
|
||||||
Format format = outputFormat == null ? new Format.Builder().build() : outputFormat;
|
Format format = outputFormat == null ? new Format.Builder().build() : outputFormat;
|
||||||
videoFrameMetadataListener.onVideoFrameAboutToBeRendered(
|
videoFrameMetadataListener.onVideoFrameAboutToBeRendered(
|
||||||
/* presentationTimeUs= */ bufferPresentationTimeUs - streamOffsetUs,
|
/* presentationTimeUs= */ presentationTimeUs - streamOffsetUs,
|
||||||
clock.nanoTime(),
|
clock.nanoTime(),
|
||||||
format,
|
format,
|
||||||
/* mediaFormat= */ null);
|
/* mediaFormat= */ null);
|
||||||
@ -619,11 +673,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
// Other methods
|
// Other methods
|
||||||
|
|
||||||
/** Releases the video sink. */
|
|
||||||
public void release() {
|
|
||||||
videoFrameProcessor.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the {@linkplain Effect video effects}. */
|
/** Sets the {@linkplain Effect video effects}. */
|
||||||
public void setVideoEffects(List<Effect> videoEffects) {
|
public void setVideoEffects(List<Effect> videoEffects) {
|
||||||
setPendingVideoEffects(videoEffects);
|
setPendingVideoEffects(videoEffects);
|
||||||
|
@ -51,7 +51,6 @@ import androidx.media3.common.Effect;
|
|||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
import androidx.media3.common.VideoFrameProcessor;
|
|
||||||
import androidx.media3.common.VideoSize;
|
import androidx.media3.common.VideoSize;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
@ -343,7 +342,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
eventListener,
|
eventListener,
|
||||||
maxDroppedFramesToNotify,
|
maxDroppedFramesToNotify,
|
||||||
assumedMinimumCodecOperatingRate,
|
assumedMinimumCodecOperatingRate,
|
||||||
/* videoFrameProcessorFactory= */ null);
|
/* videoSinkProvider= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -366,8 +365,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
* @param assumedMinimumCodecOperatingRate A codec operating rate that all codecs instantiated by
|
* @param assumedMinimumCodecOperatingRate A codec operating rate that all codecs instantiated by
|
||||||
* this renderer are assumed to meet implicitly (i.e. without the operating rate being set
|
* this renderer are assumed to meet implicitly (i.e. without the operating rate being set
|
||||||
* explicitly using {@link MediaFormat#KEY_OPERATING_RATE}).
|
* explicitly using {@link MediaFormat#KEY_OPERATING_RATE}).
|
||||||
* @param videoFrameProcessorFactory The {@link VideoFrameProcessor.Factory} applied on video
|
* @param videoSinkProvider The {@link VideoSinkProvider} that will used be used for applying
|
||||||
* output. {@code null} means a default implementation will be applied.
|
* video effects also providing the {@linkplain
|
||||||
|
* VideoSinkProvider#getVideoFrameReleaseControl() VideoFrameReleaseControl} for releasing
|
||||||
|
* video frames. If {@code null}, the {@link CompositingVideoSinkProvider} with its default
|
||||||
|
* configuration will be used, and the renderer will drive releasing of video frames by
|
||||||
|
* itself.
|
||||||
*/
|
*/
|
||||||
public MediaCodecVideoRenderer(
|
public MediaCodecVideoRenderer(
|
||||||
Context context,
|
Context context,
|
||||||
@ -379,7 +382,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
@Nullable VideoRendererEventListener eventListener,
|
@Nullable VideoRendererEventListener eventListener,
|
||||||
int maxDroppedFramesToNotify,
|
int maxDroppedFramesToNotify,
|
||||||
float assumedMinimumCodecOperatingRate,
|
float assumedMinimumCodecOperatingRate,
|
||||||
@Nullable VideoFrameProcessor.Factory videoFrameProcessorFactory) {
|
@Nullable VideoSinkProvider videoSinkProvider) {
|
||||||
super(
|
super(
|
||||||
C.TRACK_TYPE_VIDEO,
|
C.TRACK_TYPE_VIDEO,
|
||||||
codecAdapterFactory,
|
codecAdapterFactory,
|
||||||
@ -388,20 +391,20 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
assumedMinimumCodecOperatingRate);
|
assumedMinimumCodecOperatingRate);
|
||||||
this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
|
this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
|
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||||
|
if (videoSinkProvider == null) {
|
||||||
@SuppressWarnings("nullness:assignment")
|
@SuppressWarnings("nullness:assignment")
|
||||||
VideoFrameReleaseControl.@Initialized FrameTimingEvaluator thisRef = this;
|
VideoFrameReleaseControl.@Initialized FrameTimingEvaluator thisRef = this;
|
||||||
videoFrameReleaseControl =
|
videoSinkProvider =
|
||||||
|
new CompositingVideoSinkProvider.Builder(this.context)
|
||||||
|
.setVideoFrameReleaseControl(
|
||||||
new VideoFrameReleaseControl(
|
new VideoFrameReleaseControl(
|
||||||
this.context, /* frameTimingEvaluator= */ thisRef, allowedJoiningTimeMs);
|
this.context, /* frameTimingEvaluator= */ thisRef, allowedJoiningTimeMs))
|
||||||
videoFrameReleaseInfo = new VideoFrameReleaseControl.FrameReleaseInfo();
|
.build();
|
||||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
|
||||||
CompositingVideoSinkProvider.Builder compositingVideoSinkProvider =
|
|
||||||
new CompositingVideoSinkProvider.Builder(context)
|
|
||||||
.setVideoFrameReleaseControl(videoFrameReleaseControl);
|
|
||||||
if (videoFrameProcessorFactory != null) {
|
|
||||||
compositingVideoSinkProvider.setVideoFrameProcessorFactory(videoFrameProcessorFactory);
|
|
||||||
}
|
}
|
||||||
videoSinkProvider = compositingVideoSinkProvider.build();
|
this.videoSinkProvider = videoSinkProvider;
|
||||||
|
this.videoFrameReleaseControl = this.videoSinkProvider.getVideoFrameReleaseControl();
|
||||||
|
videoFrameReleaseInfo = new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||||
deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround();
|
deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround();
|
||||||
scalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
|
scalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
|
||||||
decodedVideoSize = VideoSize.UNKNOWN;
|
decodedVideoSize = VideoSize.UNKNOWN;
|
||||||
@ -1107,10 +1110,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
hasEffects = true;
|
hasEffects = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final VideoSinkProvider getVideoSinkProvider() {
|
|
||||||
return videoSinkProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCodecInitialized(
|
protected void onCodecInitialized(
|
||||||
String name,
|
String name,
|
||||||
|
@ -36,7 +36,8 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/** Controls the releasing of video frames. */
|
/** Controls the releasing of video frames. */
|
||||||
/* package */ final class VideoFrameReleaseControl {
|
@UnstableApi
|
||||||
|
public final class VideoFrameReleaseControl {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The frame release action returned by {@link #getFrameReleaseAction(long, long, long, long,
|
* The frame release action returned by {@link #getFrameReleaseAction(long, long, long, long,
|
||||||
@ -181,6 +182,9 @@ import java.lang.annotation.Target;
|
|||||||
* Creates an instance.
|
* Creates an instance.
|
||||||
*
|
*
|
||||||
* @param applicationContext The application context.
|
* @param applicationContext The application context.
|
||||||
|
* @param frameTimingEvaluator The {@link FrameTimingEvaluator} that will assist in {@linkplain
|
||||||
|
* #getFrameReleaseAction(long, long, long, long, boolean, FrameReleaseInfo)} frame release
|
||||||
|
* actions}.
|
||||||
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which the renderer can
|
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which the renderer can
|
||||||
* attempt to seamlessly join an ongoing playback.
|
* attempt to seamlessly join an ongoing playback.
|
||||||
*/
|
*/
|
||||||
|
@ -80,6 +80,12 @@ public interface VideoSinkProvider {
|
|||||||
/** Sets a {@link VideoFrameMetadataListener} which is used in the returned {@link VideoSink}. */
|
/** Sets a {@link VideoFrameMetadataListener} which is used in the returned {@link VideoSink}. */
|
||||||
void setVideoFrameMetadataListener(VideoFrameMetadataListener videoFrameMetadataListener);
|
void setVideoFrameMetadataListener(VideoFrameMetadataListener videoFrameMetadataListener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link VideoFrameReleaseControl} that will be used for releasing of video frames
|
||||||
|
* during rendering.
|
||||||
|
*/
|
||||||
|
VideoFrameReleaseControl getVideoFrameReleaseControl();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link Clock} that the provider should use internally.
|
* Sets the {@link Clock} that the provider should use internally.
|
||||||
*
|
*
|
||||||
|
@ -42,15 +42,6 @@ import org.mockito.Mockito;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class CompositingVideoSinkProviderTest {
|
public final class CompositingVideoSinkProviderTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
public void builder_withoutVideoFrameReleaseControl_throws() {
|
|
||||||
assertThrows(
|
|
||||||
IllegalStateException.class,
|
|
||||||
() ->
|
|
||||||
new CompositingVideoSinkProvider.Builder(ApplicationProvider.getApplicationContext())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void builder_calledMultipleTimes_throws() {
|
public void builder_calledMultipleTimes_throws() {
|
||||||
CompositingVideoSinkProvider.Builder builder =
|
CompositingVideoSinkProvider.Builder builder =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user