Compare commits

...

8 Commits

Author SHA1 Message Date
claincly
9785c1fd92 Allow setting VideoFrameMetadataListener on CompositionPlayer
PiperOrigin-RevId: 743607386
2025-04-03 10:12:32 -07:00
Googler
feae2dadeb Adds getChannelOutputConfig() to AudioTrackProvider.
PiperOrigin-RevId: 743577038
2025-04-03 08:42:07 -07:00
sheenachhabra
1f3a5e7d1c Do not allow video transmuxing if there are video gaps
PiperOrigin-RevId: 743555458
2025-04-03 07:32:39 -07:00
kimvde
ee611a1ab8 Access finalAudioSink from playback thread
The finalAudioSink was accessed from the main thread but it should only
be accessed from the playback thread.

PiperOrigin-RevId: 743548450
2025-04-03 07:09:01 -07:00
ibaker
f755c7081f Increase the connection timeout in HttpEngineDataSourceContractTest
This test is flaking with a `SocketTimeoutException` on some emulators.

PiperOrigin-RevId: 743545820
2025-04-03 06:58:29 -07:00
dancho
036bed3632 Do not drop decoder input buffers close to a reset position
This is a workaround for a bug where the positionUs seen by
MCVR jumps when audio pre-roll samples are discarded.

PiperOrigin-RevId: 743538208
2025-04-03 06:31:32 -07:00
michaelkatz
8327a2a52d Do not enable offload scheduling while preparing next media
ExoPlayer disables sleeping for offload when the reading period advances and re-evaluates turning it back on when the playing period advances. For playlists of short items where the reading period could advance much further than the playing period, sleeping should still be disabled until the playing period advances to match the current reading period.

Issue: androidx/media#1920
PiperOrigin-RevId: 743503063
2025-04-03 04:15:15 -07:00
kimvde
18e9a3fe36 Rename VideoSink.onStarted/Stopped
start/stopRendering better reflects what the method does.

PiperOrigin-RevId: 743493973
2025-04-03 03:42:57 -07:00
18 changed files with 203 additions and 61 deletions

View File

@ -36,6 +36,9 @@
* DataSource:
* Audio:
* Allow constant power upmixing/downmixing in DefaultAudioMixer.
* Fix offload issue where position might get stuck when playing a playlist
of short content
([#1920](https://github.com/androidx/media/issues/1920)).
* Video:
* Add experimental `ExoPlayer` API to include the
`MediaCodec.BUFFER_FLAG_DECODE_ONLY` flag when queuing decode-only input

View File

@ -63,7 +63,7 @@ public class HttpEngineDataSourceContractTest extends DataSourceContractTest {
new HttpEngine.Builder(ApplicationProvider.getApplicationContext()).build();
return new HttpEngineDataSource.Factory(httpEngine, executorService)
// Ensure that 'resource not found' tests fail fast (b/403179253).
.setConnectionTimeoutMs(400)
.setConnectionTimeoutMs(600)
.setReadTimeoutMs(400)
.createDataSource();
}

View File

@ -2820,6 +2820,10 @@ import java.util.Objects;
private void maybeUpdateOffloadScheduling() {
// If playing period is audio-only with offload mode preference to enable, then offload
// scheduling should be enabled.
if (queue.getPlayingPeriod() != queue.getReadingPeriod()) {
// Do not enable offload scheduling when starting to process the next media item.
return;
}
@Nullable MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (playingPeriodHolder != null) {
TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult();

View File

@ -102,6 +102,11 @@ public final class DefaultAudioSink implements AudioSink {
/** Returns a new {@link AudioTrack} for the given parameters. */
AudioTrack getAudioTrack(
AudioTrackConfig audioTrackConfig, AudioAttributes audioAttributes, int audioSessionId);
/** Returns the channel mask config for the given channel count. */
default int getAudioTrackChannelConfig(int channelCount) {
return Util.getAudioTrackChannelConfig(channelCount);
}
}
/**
@ -731,7 +736,10 @@ public final class DefaultAudioSink implements AudioSink {
outputMode = OUTPUT_MODE_PCM;
outputEncoding = outputFormat.encoding;
outputSampleRate = outputFormat.sampleRate;
outputChannelConfig = Util.getAudioTrackChannelConfig(outputFormat.channelCount);
outputChannelConfig =
audioTrackProvider.getAudioTrackChannelConfig(outputFormat.channelCount);
outputPcmFrameSize = Util.getPcmFrameSize(outputEncoding, outputFormat.channelCount);
enableAudioTrackPlaybackParams = preferAudioTrackPlaybackParams;
} else {
@ -748,7 +756,10 @@ public final class DefaultAudioSink implements AudioSink {
outputMode = OUTPUT_MODE_OFFLOAD;
outputEncoding =
MimeTypes.getEncoding(checkNotNull(inputFormat.sampleMimeType), inputFormat.codecs);
outputChannelConfig = Util.getAudioTrackChannelConfig(inputFormat.channelCount);
outputChannelConfig =
audioTrackProvider.getAudioTrackChannelConfig(inputFormat.channelCount);
// Offload requires AudioTrack playback parameters to apply speed changes quickly.
enableAudioTrackPlaybackParams = true;
enableOffloadGapless = audioOffloadSupport.isGaplessSupported;

View File

@ -78,12 +78,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@Override
public void onStarted() {
public void startRendering() {
videoFrameReleaseControl.onStarted();
}
@Override
public void onStopped() {
public void stopRendering() {
videoFrameReleaseControl.onStopped();
}

View File

@ -153,6 +153,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
*/
private static final long OFFSET_FROM_PERIOD_END_TO_TREAT_AS_LAST_US = 100_000L;
/**
* The offset from {@link #getLastResetPositionUs()} in microseconds, before which input buffers
* are not allowed to be dropped.
*
* <p>This value must be greater than the pre-roll distance used by common audio codecs, such as
* 80ms used by Opus <a
* href="https://opus-codec.org/docs/opus_in_isobmff.html#4.3.6.2">Encapsulation of Opus in ISO
* Base Media File Format</a>
*/
private static final long OFFSET_FROM_RESET_POSITION_TO_ALLOW_INPUT_BUFFER_DROPPING_US = 200_000L;
/**
* The maximum number of consecutive dropped input buffers that allow discarding frame headers.
*
@ -644,7 +655,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
boolean treatDroppedBuffersAsSkipped)
throws ExoPlaybackException {
if (minEarlyUsToDropDecoderInput != C.TIME_UNSET) {
shouldDropDecoderInputBuffers = earlyUs < minEarlyUsToDropDecoderInput;
// TODO: b/161996553 - Remove the isAwayFromLastResetPosition check when audio pre-rolling
// is implemented correctly. Audio codecs such as Opus require pre-roll samples to be decoded
// and discarded on a seek. Depending on the audio decoder, the positionUs may jump forward
// by the pre-roll duration. Do not drop more frames than necessary when this happens.
boolean isAwayFromLastResetPosition =
positionUs
> getLastResetPositionUs()
+ OFFSET_FROM_RESET_POSITION_TO_ALLOW_INPUT_BUFFER_DROPPING_US;
shouldDropDecoderInputBuffers =
isAwayFromLastResetPosition && earlyUs < minEarlyUsToDropDecoderInput;
}
return shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs, isLastFrame)
&& maybeDropBuffersToKeyframe(positionUs, treatDroppedBuffersAsSkipped);
@ -1058,7 +1078,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
totalVideoFrameProcessingOffsetUs = 0;
videoFrameProcessingOffsetCount = 0;
if (videoSink != null) {
videoSink.onStarted();
videoSink.startRendering();
} else {
videoFrameReleaseControl.onStarted();
}
@ -1069,7 +1089,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
maybeNotifyDroppedFrames();
maybeNotifyVideoFrameProcessingOffset();
if (videoSink != null) {
videoSink.onStopped();
videoSink.stopRendering();
} else {
videoFrameReleaseControl.onStopped();
}

View File

@ -701,13 +701,13 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
}
@Override
public void onStarted() {
defaultVideoSink.onStarted();
public void startRendering() {
defaultVideoSink.startRendering();
}
@Override
public void onStopped() {
defaultVideoSink.onStopped();
public void stopRendering() {
defaultVideoSink.stopRendering();
}
@Override

View File

@ -139,11 +139,11 @@ public interface VideoSink {
*/
int RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED = 2;
/** Called when rendering starts. */
void onStarted();
/** Starts rendering to the output surface. */
void startRendering();
/** Called when rendering stops. */
void onStopped();
/** Stops rendering to the output surface. */
void stopRendering();
/**
* Sets a {@link Listener} on this sink. Callbacks are triggered on the supplied {@link Executor}.
@ -263,8 +263,8 @@ public interface VideoSink {
List<Effect> videoEffects);
/**
* Allows the sink to release the first frame even if rendering is not {@linkplain #onStarted()
* started}.
* Allows the sink to release the first frame even if rendering is not {@linkplain
* #startRendering() started}.
*
* <p>This is used to update the {@link FirstFrameReleaseInstruction} of the {@linkplain
* #onInputStreamChanged(int, Format, long, int, List) stream} that is currently being processed.

View File

@ -11787,6 +11787,54 @@ public final class ExoPlayerTest {
player.release();
}
@Test
public void enablingOffload_withFastReadingPeriodAdvancement_playerDoesNotSleep()
throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO);
AtomicInteger sleepingForOffloadCounter = new AtomicInteger();
ExoPlayer player =
parameterizeTestExoPlayerBuilder(
new TestExoPlayerBuilder(context).setRenderers(sleepRenderer))
.build();
ExoPlayer.AudioOffloadListener listener =
new ExoPlayer.AudioOffloadListener() {
@Override
public void onSleepingForOffloadChanged(boolean sleepingForOffload) {
if (sleepingForOffload) {
sleepingForOffloadCounter.getAndIncrement();
}
}
};
player.addAudioOffloadListener(listener);
// Set a playlist of multiple, short audio-only items such that the reading period quickly
// advances past the playing period.
Timeline timeline = new FakeTimeline();
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT)));
player.setTrackSelectionParameters(
player
.getTrackSelectionParameters()
.buildUpon()
.setAudioOffloadPreferences(
new AudioOffloadPreferences.Builder()
.setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_REQUIRED)
.build())
.build());
player.prepare();
player.play();
advance(player).untilStartOfMediaItem(/* mediaItemIndex= */ 1);
sleepRenderer.sleepOnNextRender();
runUntilPlaybackState(player, Player.STATE_ENDED);
assertThat(sleepingForOffloadCounter.get()).isEqualTo(0);
player.release();
}
@Test
public void wakeupListenerWhileSleepingForOffload_isWokenUp_renderingResumes() throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();

View File

@ -29,6 +29,7 @@ import androidx.media3.exoplayer.DecoderCounters;
import androidx.media3.exoplayer.DefaultRenderersFactory;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.analytics.AnalyticsListener;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
@ -103,6 +104,14 @@ public class ParseAv1SampleDependenciesPlaybackTest {
new ExoPlayer.Builder(applicationContext, renderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
player.addAnalyticsListener(
new AnalyticsListener() {
@Override
public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
// Input buffers near the reset position should not be dropped.
assertThat(eventTime.currentPlaybackPositionMs).isAtLeast(200);
}
});
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
player.setMediaItem(MediaItem.fromUri(TEST_MP4_URI));
@ -121,7 +130,7 @@ public class ParseAv1SampleDependenciesPlaybackTest {
// Which input buffer is dropped first depends on the number of MediaCodec buffer slots.
// This means the asserts cannot be isEqualTo.
assertThat(decoderCounters.maxConsecutiveDroppedBufferCount).isAtMost(2);
assertThat(decoderCounters.droppedInputBufferCount).isAtLeast(8);
assertThat(decoderCounters.droppedInputBufferCount).isAtLeast(4);
}
private static final class CapturingRenderersFactoryWithLateThresholdToDropDecoderInputUs
@ -155,7 +164,6 @@ public class ParseAv1SampleDependenciesPlaybackTest {
/* enableDecoderFallback= */ false,
eventHandler,
videoRendererEventListener,
DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY,
/* parseAv1SampleDependencies= */ true,
/* lateThresholdToDropDecoderInputUs= */ -100_000_000L)
};
@ -173,7 +181,6 @@ public class ParseAv1SampleDependenciesPlaybackTest {
boolean enableDecoderFallback,
@Nullable Handler eventHandler,
@Nullable VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify,
boolean parseAv1SampleDependencies,
long lateThresholdToDropDecoderInputUs) {
super(
@ -184,7 +191,7 @@ public class ParseAv1SampleDependenciesPlaybackTest {
.setEnableDecoderFallback(enableDecoderFallback)
.setEventHandler(eventHandler)
.setEventListener(eventListener)
.setMaxDroppedFramesToNotify(maxDroppedFramesToNotify)
.setMaxDroppedFramesToNotify(1)
.experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies)
.experimentalSetLateThresholdToDropDecoderInputUs(
lateThresholdToDropDecoderInputUs));

View File

@ -81,13 +81,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@Override
public void onStarted() {
executeOrDelay(VideoSink::onStarted);
public void startRendering() {
executeOrDelay(VideoSink::startRendering);
}
@Override
public void onStopped() {
executeOrDelay(VideoSink::onStopped);
public void stopRendering() {
executeOrDelay(VideoSink::stopRendering);
}
@Override

View File

@ -77,6 +77,7 @@ import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.util.EventLogger;
import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper;
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
import androidx.media3.exoplayer.video.VideoFrameReleaseControl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@ -320,6 +321,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
private @MonotonicNonNull Composition composition;
private @MonotonicNonNull Size videoOutputSize;
private @MonotonicNonNull PlaybackVideoGraphWrapper playbackVideoGraphWrapper;
private @MonotonicNonNull VideoFrameMetadataListener pendingVideoFrameMetadatListener;
private long compositionDurationUs;
private boolean playWhenReady;
@ -336,7 +338,6 @@ public final class CompositionPlayer extends SimpleBasePlayer
private LivePositionSupplier positionSupplier;
private LivePositionSupplier bufferedPositionSupplier;
private LivePositionSupplier totalBufferedDurationSupplier;
private boolean isSeeking;
// "this" reference for position suppliers.
@SuppressWarnings("initialization:methodref.receiver.bound.invalid")
@ -506,9 +507,9 @@ public final class CompositionPlayer extends SimpleBasePlayer
playWhenReadyChangeReason = PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
if (playbackState == STATE_READY) {
if (playWhenReady) {
finalAudioSink.play();
checkStateNotNull(compositionPlayerInternal).startRendering();
} else {
finalAudioSink.pause();
checkStateNotNull(compositionPlayerInternal).stopRendering();
}
for (int i = 0; i < players.size(); i++) {
players.get(i).setPlayWhenReady(playWhenReady);
@ -587,7 +588,9 @@ public final class CompositionPlayer extends SimpleBasePlayer
@Override
protected ListenableFuture<?> handleSetVolume(float volume) {
this.volume = Util.constrainValue(volume, /* min= */ 0.0f, /* max= */ 1.0f);
finalAudioSink.setVolume(this.volume);
if (compositionPlayerInternal != null) {
compositionPlayerInternal.setVolume(this.volume);
}
return Futures.immediateVoidFuture();
}
@ -597,7 +600,6 @@ public final class CompositionPlayer extends SimpleBasePlayer
resetLivePositionSuppliers();
CompositionPlayerInternal compositionPlayerInternal =
checkStateNotNull(this.compositionPlayerInternal);
isSeeking = true;
compositionPlayerInternal.startSeek(positionMs);
for (int i = 0; i < players.size(); i++) {
players.get(i).seekTo(positionMs);
@ -606,6 +608,15 @@ public final class CompositionPlayer extends SimpleBasePlayer
return Futures.immediateVoidFuture();
}
/** Sets the {@link VideoFrameMetadataListener}. */
public void setVideoFrameMetadataListener(VideoFrameMetadataListener videoFrameMetadataListener) {
if (players.isEmpty()) {
pendingVideoFrameMetadatListener = videoFrameMetadataListener;
return;
}
players.get(0).setVideoFrameMetadataListener(videoFrameMetadataListener);
}
// CompositionPlayerInternal.Listener methods
@Override
@ -683,22 +694,17 @@ public final class CompositionPlayer extends SimpleBasePlayer
for (int i = 0; i < players.size(); i++) {
players.get(i).setPlayWhenReady(false);
}
if (!isSeeking) {
// The finalAudioSink cannot be paused more than once. The audio pipeline pauses it during
// a seek, so don't pause here when seeking.
finalAudioSink.pause();
}
checkStateNotNull(compositionPlayerInternal).stopRendering();
}
} else if (endedCount == players.size()) {
playbackState = STATE_ENDED;
} else {
playbackState = STATE_READY;
isSeeking = false;
if (oldPlaybackState != STATE_READY && playWhenReady) {
for (int i = 0; i < players.size(); i++) {
players.get(i).setPlayWhenReady(true);
}
finalAudioSink.play();
checkStateNotNull(compositionPlayerInternal).startRendering();
}
}
}
@ -775,6 +781,9 @@ public final class CompositionPlayer extends SimpleBasePlayer
if (i == 0) {
setPrimaryPlayerSequence(player, editedMediaItemSequence);
if (pendingVideoFrameMetadatListener != null) {
player.setVideoFrameMetadataListener(pendingVideoFrameMetadatListener);
}
} else {
setSecondaryPlayerSequence(player, editedMediaItemSequence, primarySequenceDurationUs);
}
@ -799,6 +808,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
playbackVideoGraphWrapper,
/* listener= */ this,
compositionInternalListenerHandler);
compositionPlayerInternal.setVolume(volume);
}
private void setPrimaryPlayerSequence(ExoPlayer player, EditedMediaItemSequence sequence) {

View File

@ -47,11 +47,14 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper;
}
private static final String TAG = "CompPlayerInternal";
private static final int MSG_SET_OUTPUT_SURFACE_INFO = 1;
private static final int MSG_CLEAR_OUTPUT_SURFACE = 2;
private static final int MSG_START_SEEK = 3;
private static final int MSG_END_SEEK = 4;
private static final int MSG_RELEASE = 5;
private static final int MSG_START_RENDERING = 1;
private static final int MSG_STOP_RENDERING = 2;
private static final int MSG_SET_VOLUME = 3;
private static final int MSG_SET_OUTPUT_SURFACE_INFO = 4;
private static final int MSG_CLEAR_OUTPUT_SURFACE = 5;
private static final int MSG_START_SEEK = 6;
private static final int MSG_END_SEEK = 7;
private static final int MSG_RELEASE = 8;
private final Clock clock;
private final HandlerWrapper handler;
@ -94,6 +97,18 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper;
// Public methods
public void startRendering() {
handler.sendEmptyMessage(MSG_START_RENDERING);
}
public void stopRendering() {
handler.sendEmptyMessage(MSG_STOP_RENDERING);
}
public void setVolume(float volume) {
handler.obtainMessage(MSG_SET_VOLUME, volume).sendToTarget();
}
/** Sets the output surface information on the video pipeline. */
public void setOutputSurfaceInfo(Surface surface, Size size) {
handler
@ -103,7 +118,7 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper;
/** Clears the output surface from the video pipeline. */
public void clearOutputSurface() {
handler.obtainMessage(MSG_CLEAR_OUTPUT_SURFACE).sendToTarget();
handler.sendEmptyMessage(MSG_CLEAR_OUTPUT_SURFACE);
}
public void startSeek(long positionMs) {
@ -111,7 +126,7 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper;
}
public void endSeek() {
handler.obtainMessage(MSG_END_SEEK).sendToTarget();
handler.sendEmptyMessage(MSG_END_SEEK);
}
/**
@ -139,6 +154,15 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper;
public boolean handleMessage(Message message) {
try {
switch (message.what) {
case MSG_START_RENDERING:
playbackAudioGraphWrapper.startRendering();
break;
case MSG_STOP_RENDERING:
playbackAudioGraphWrapper.stopRendering();
break;
case MSG_SET_VOLUME:
playbackAudioGraphWrapper.setVolume(/* volume= */ (float) message.obj);
break;
case MSG_SET_OUTPUT_SURFACE_INFO:
setOutputSurfaceInfoOnInternalThread(
/* outputSurfaceInfo= */ (OutputSurfaceInfo) message.obj);

View File

@ -47,6 +47,7 @@ import java.util.Objects;
private AudioFormat outputAudioFormat;
private long outputFramesWritten;
private long seekPositionUs;
private boolean isRenderingStarted;
/**
* Creates an instance.
@ -135,6 +136,24 @@ import java.util.Objects;
+ sampleCountToDurationUs(outputFramesWritten, outputAudioFormat.sampleRate);
}
public void startRendering() {
finalAudioSink.play();
isRenderingStarted = true;
}
public void stopRendering() {
if (!isRenderingStarted) {
// The finalAudioSink cannot be paused more than once.
return;
}
finalAudioSink.pause();
isRenderingStarted = false;
}
public void setVolume(float volume) {
finalAudioSink.setVolume(volume);
}
/**
* Handles the steps that need to be executed for a seek before seeking the upstream players.
*
@ -144,7 +163,7 @@ import java.util.Objects;
if (positionUs == C.TIME_UNSET) {
positionUs = 0;
}
finalAudioSink.pause();
stopRendering();
audioGraph.blockInput();
audioGraph.setPendingStartTimeUs(positionUs);
audioGraph.flush();

View File

@ -576,7 +576,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
protected void onStarted() throws ExoPlaybackException {
super.onStarted();
videoSink.onStarted();
videoSink.startRendering();
}
@Override
@ -595,7 +595,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
protected void onStopped() {
super.onStopped();
videoSink.onStopped();
videoSink.stopRendering();
}
@Override

View File

@ -756,8 +756,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void createEncodedSampleExporter(@C.TrackType int trackType) {
checkState(assetLoaderInputTracker.getSampleExporter(trackType) == null);
checkArgument(
trackType != TRACK_TYPE_AUDIO || !composition.sequences.get(sequenceIndex).hasGaps(),
"Gaps can not be transmuxed.");
!composition.sequences.get(sequenceIndex).hasGaps(), "Gaps can not be transmuxed.");
assetLoaderInputTracker.registerSampleExporter(
trackType,
new EncodedSampleExporter(

View File

@ -18,7 +18,6 @@ package androidx.media3.transformer;
import static androidx.media3.common.ColorInfo.SDR_BT709_LIMITED;
import static androidx.media3.common.ColorInfo.isTransferHdr;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.exoplayer.mediacodec.MediaCodecUtil.getAlternativeCodecMimeType;
import static androidx.media3.transformer.Composition.HDR_MODE_KEEP_HDR;
import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
@ -91,8 +90,6 @@ public final class TransformerUtil {
MuxerWrapper muxerWrapper) {
if (composition.sequences.size() > 1
|| composition.sequences.get(sequenceIndex).editedMediaItems.size() > 1) {
checkArgument(
!composition.hasGaps() || !composition.transmuxAudio, "Gaps can not be transmuxed.");
return !composition.transmuxAudio;
}
if (composition.hasGaps()) {

View File

@ -40,11 +40,11 @@ public class BufferingVideoSinkTest {
VideoSink videoSinkMock = mock(VideoSink.class);
bufferingVideoSink.setVideoSink(videoSinkMock);
bufferingVideoSink.onStarted();
bufferingVideoSink.startRendering();
bufferingVideoSink.flush(/* resetPosition= */ true);
InOrder inOrder = Mockito.inOrder(videoSinkMock);
inOrder.verify(videoSinkMock).onStarted();
inOrder.verify(videoSinkMock).startRendering();
inOrder.verify(videoSinkMock).flush(/* resetPosition= */ true);
}
@ -52,12 +52,12 @@ public class BufferingVideoSinkTest {
public void setVideoSink_executesPendingOperations() {
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
VideoSink videoSinkMock = mock(VideoSink.class);
bufferingVideoSink.onStarted();
bufferingVideoSink.startRendering();
bufferingVideoSink.flush(/* resetPosition= */ true);
bufferingVideoSink.setVideoSink(videoSinkMock);
InOrder inOrder = Mockito.inOrder(videoSinkMock);
inOrder.verify(videoSinkMock).onStarted();
inOrder.verify(videoSinkMock).startRendering();
inOrder.verify(videoSinkMock).flush(/* resetPosition= */ true);
}
@ -68,10 +68,10 @@ public class BufferingVideoSinkTest {
bufferingVideoSink.setVideoSink(videoSinkMock);
bufferingVideoSink.setVideoSink(null);
bufferingVideoSink.onStarted();
bufferingVideoSink.startRendering();
bufferingVideoSink.flush(/* resetPosition= */ true);
verify(videoSinkMock, never()).onStarted();
verify(videoSinkMock, never()).startRendering();
verify(videoSinkMock, never()).flush(/* resetPosition= */ true);
}
@ -80,12 +80,12 @@ public class BufferingVideoSinkTest {
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
VideoSink videoSinkMock = mock(VideoSink.class);
bufferingVideoSink.onStarted();
bufferingVideoSink.startRendering();
bufferingVideoSink.flush(/* resetPosition= */ true);
bufferingVideoSink.clearPendingOperations();
bufferingVideoSink.setVideoSink(videoSinkMock);
verify(videoSinkMock, never()).onStarted();
verify(videoSinkMock, never()).startRendering();
verify(videoSinkMock, never()).flush(/* resetPosition= */ true);
}
}