Compare commits

...

3 Commits

Author SHA1 Message Date
ibaker
83efd8eb66 Add ScrubbingModeParameters
This adds the option to disable certain track types during scrubbing,
with audio and metadata disabled by default.

The tracks are disabled by modifying the `TrackSelectionParameters`,
but in a way that is invisible to
`Player.getTrackSelectionParameters()` and
`Player.Listener.onTrackSelectionParametersChanged`. This allows us to
clearly reason about what should happen if
`Player.setTrackSelectionParameters(...)` is called during scrubbing
mode. The **side effects** of disabling the tracks are all visible
through `Player.Listener` and `AnalyticsListener` (renderer disabled,
decoder released, `onTracksChanged`, etc.).

PiperOrigin-RevId: 743961632
2025-04-04 08:58:44 -07:00
ibaker
6c4c4bdea4 Tweak the parameter name in Util.shouldShowPlayButton
The previous name sounds a bit like it refers to the action of
'playing', rather than which button is shown (which is kind of the
opposite!).

PiperOrigin-RevId: 743908736
2025-04-04 05:40:30 -07:00
sheenachhabra
72bb474604 Add experimental prefix to setForce{Audio,Video}Track APIs
These APIs are likely to change/removed in near future.

PiperOrigin-RevId: 743899703
2025-04-04 04:57:56 -07:00
26 changed files with 623 additions and 90 deletions

View File

@ -14,14 +14,16 @@
([#2229](https://github.com/androidx/media/issues/2229)).
* Add `ExoPlayer.setScrubbingModeEnabled(boolean)` method. This optimizes
the player for many frequent seeks (for example, from a user dragging a
scrubber bar around).
scrubber bar around). The behavior of scrubbing mode can be customized
with `setScrubbingModeParameters(..)` on `ExoPlayer` and
`ExoPlayer.Builder`.
* `AdPlaybackState.withAdDurationsUs(long[][])` can be used after ad
groups have been removed. The user still needs to pass in an array of
durations for removed ad groups which can be empty or null
([#2267](https://github.com/androidx/media/issues/2267)).
* Transformer:
* Filling an initial gap (added via `addGap()`) with silent audio now
requires explicitly setting `setForceAudioTrack(true)` in
requires explicitly setting `experimentalSetForceAudioTrack(true)` in
`EditedMediaItemSequence.Builder`. If the gap is in the middle of the
sequence, then this flag is not required.
* Track Selection:

View File

@ -469,7 +469,7 @@ public final class TransformerActivity extends AppCompatActivity {
EditedMediaItemSequence.Builder editedMediaItemSequenceBuilder =
new EditedMediaItemSequence.Builder(editedMediaItemBuilder.build());
if (bundle != null) {
editedMediaItemSequenceBuilder.setForceAudioTrack(
editedMediaItemSequenceBuilder.experimentalSetForceAudioTrack(
bundle.getBoolean(ConfigurationActivity.FORCE_AUDIO_TRACK));
}
Composition.Builder compositionBuilder =

View File

@ -849,10 +849,8 @@ public class TrackSelectionParameters {
*
* @param disabledTrackTypes The track types to disable.
* @return This builder.
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
*/
@CanIgnoreReturnValue
@Deprecated
@UnstableApi
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
this.disabledTrackTypes.clear();

View File

@ -3689,7 +3689,7 @@ public final class Util {
*/
@EnsuresNonNullIf(result = false, expression = "#1")
public static boolean shouldShowPlayButton(@Nullable Player player) {
return shouldShowPlayButton(player, /* playIfSuppressed= */ true);
return shouldShowPlayButton(player, /* shouldShowPlayIfSuppressed= */ true);
}
/**
@ -3700,17 +3700,18 @@ public final class Util {
* #handlePauseButtonAction} to handle the interaction with the play or pause button UI element.
*
* @param player The {@link Player}. May be {@code null}.
* @param playIfSuppressed Whether to show a play button if playback is {@linkplain
* @param shouldShowPlayIfSuppressed Whether to show a play button if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*/
@UnstableApi
@EnsuresNonNullIf(result = false, expression = "#1")
public static boolean shouldShowPlayButton(@Nullable Player player, boolean playIfSuppressed) {
public static boolean shouldShowPlayButton(
@Nullable Player player, boolean shouldShowPlayIfSuppressed) {
return player == null
|| !player.getPlayWhenReady()
|| player.getPlaybackState() == Player.STATE_IDLE
|| player.getPlaybackState() == Player.STATE_ENDED
|| (playIfSuppressed
|| (shouldShowPlayIfSuppressed
&& player.getPlaybackSuppressionReason() != Player.PLAYBACK_SUPPRESSION_REASON_NONE);
}

View File

@ -238,6 +238,7 @@ public interface ExoPlayer extends Player {
@C.VideoChangeFrameRateStrategy /* package */ int videoChangeFrameRateStrategy;
/* package */ boolean useLazyPreparation;
/* package */ SeekParameters seekParameters;
/* package */ ScrubbingModeParameters scrubbingModeParameters;
/* package */ long seekBackIncrementMs;
/* package */ long seekForwardIncrementMs;
/* package */ long maxSeekToPreviousPositionMs;
@ -452,6 +453,7 @@ public interface ExoPlayer extends Player {
seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS;
seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS;
maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
scrubbingModeParameters = ScrubbingModeParameters.DEFAULT;
livePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build();
clock = Clock.DEFAULT;
releaseTimeoutMs = DEFAULT_RELEASE_TIMEOUT_MS;
@ -897,6 +899,22 @@ public interface ExoPlayer extends Player {
return this;
}
/**
* Sets the parameters that control the behavior in {@linkplain #setScrubbingModeEnabled
* scrubbing mode}.
*
* @param scrubbingModeParameters The {@link ScrubbingModeParameters}.
* @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called.
*/
@CanIgnoreReturnValue
@UnstableApi
public Builder setScrubbingModeParameters(ScrubbingModeParameters scrubbingModeParameters) {
checkState(!buildCalled);
this.scrubbingModeParameters = checkNotNull(scrubbingModeParameters);
return this;
}
/**
* Sets a timeout for calls to {@link #release} and {@link #setForegroundMode}.
*
@ -1466,6 +1484,20 @@ public interface ExoPlayer extends Player {
@UnstableApi
void setScrubbingModeEnabled(boolean scrubbingModeEnabled);
/**
* Sets the parameters that control behavior in {@linkplain #setScrubbingModeEnabled scrubbing
* mode}.
*/
@UnstableApi
void setScrubbingModeParameters(ScrubbingModeParameters scrubbingModeParameters);
/**
* Gets the parameters that control behavior in {@linkplain #setScrubbingModeEnabled scrubbing
* mode}.
*/
@UnstableApi
ScrubbingModeParameters getScrubbingModeParameters();
/**
* Sets a {@link List} of {@linkplain Effect video effects} that will be applied to each video
* frame.

View File

@ -58,6 +58,7 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
import androidx.media3.common.BasePlayer;
import androidx.media3.common.C;
import androidx.media3.common.C.TrackType;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.Effect;
import androidx.media3.common.Format;
@ -116,6 +117,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.exoplayer.video.spherical.CameraMotionListener;
import androidx.media3.exoplayer.video.spherical.SphericalGLSurfaceView;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -182,6 +184,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
private boolean pendingDiscontinuity;
private boolean foregroundMode;
private boolean scrubbingModeEnabled;
@Nullable private ImmutableSet<@TrackType Integer> disabledTrackTypesWithoutScrubbingMode;
private ScrubbingModeParameters scrubbingModeParameters;
private SeekParameters seekParameters;
private ShuffleOrder shuffleOrder;
private PreloadConfiguration preloadConfiguration;
@ -284,6 +288,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
this.seekBackIncrementMs = builder.seekBackIncrementMs;
this.seekForwardIncrementMs = builder.seekForwardIncrementMs;
this.maxSeekToPreviousPositionMs = builder.maxSeekToPreviousPositionMs;
this.scrubbingModeParameters = builder.scrubbingModeParameters;
this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems;
this.applicationLooper = builder.looper;
this.clock = builder.clock;
@ -1224,20 +1229,38 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public TrackSelectionParameters getTrackSelectionParameters() {
verifyApplicationThread();
return trackSelector.getParameters();
TrackSelectionParameters parameters = trackSelector.getParameters();
return scrubbingModeEnabled
? parameters
.buildUpon()
.setDisabledTrackTypes(disabledTrackTypesWithoutScrubbingMode)
.build()
: parameters;
}
@Override
public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
verifyApplicationThread();
if (!trackSelector.isSetParametersSupported()
|| parameters.equals(trackSelector.getParameters())) {
if (!trackSelector.isSetParametersSupported()) {
return;
}
trackSelector.setParameters(parameters);
listeners.sendEvent(
EVENT_TRACK_SELECTION_PARAMETERS_CHANGED,
listener -> listener.onTrackSelectionParametersChanged(parameters));
TrackSelectionParameters publicParametersBeforeUpdate = getTrackSelectionParameters();
TrackSelectionParameters internalParameters;
if (scrubbingModeEnabled) {
this.disabledTrackTypesWithoutScrubbingMode = parameters.disabledTrackTypes;
internalParameters =
addDisabledTrackTypes(parameters, scrubbingModeParameters.disabledTrackTypes);
} else {
internalParameters = parameters;
}
if (!internalParameters.equals(trackSelector.getParameters())) {
trackSelector.setParameters(internalParameters);
}
if (!publicParametersBeforeUpdate.equals(parameters)) {
listeners.sendEvent(
EVENT_TRACK_SELECTION_PARAMETERS_CHANGED,
listener -> listener.onTrackSelectionParametersChanged(parameters));
}
}
@Override
@ -1559,10 +1582,64 @@ import java.util.concurrent.CopyOnWriteArraySet;
return;
}
this.scrubbingModeEnabled = scrubbingModeEnabled;
if (!scrubbingModeParameters.disabledTrackTypes.isEmpty()
&& trackSelector.isSetParametersSupported()) {
TrackSelectionParameters previousTrackSelectionParameters = trackSelector.getParameters();
TrackSelectionParameters newTrackSelectionParameters;
if (scrubbingModeEnabled) {
this.disabledTrackTypesWithoutScrubbingMode =
previousTrackSelectionParameters.disabledTrackTypes;
newTrackSelectionParameters =
addDisabledTrackTypes(
previousTrackSelectionParameters, scrubbingModeParameters.disabledTrackTypes);
} else {
newTrackSelectionParameters =
previousTrackSelectionParameters
.buildUpon()
.setDisabledTrackTypes(disabledTrackTypesWithoutScrubbingMode)
.build();
this.disabledTrackTypesWithoutScrubbingMode = null;
}
// Set the parameters directly, to avoid firing onTrackSelectionParametersChanged (because
// the return value of getTrackSelectionParameters won't change).
if (!newTrackSelectionParameters.equals(previousTrackSelectionParameters)) {
trackSelector.setParameters(newTrackSelectionParameters);
}
}
internalPlayer.setScrubbingModeEnabled(scrubbingModeEnabled);
maybeUpdatePlaybackSuppressionReason();
}
@Override
public void setScrubbingModeParameters(ScrubbingModeParameters scrubbingModeParameters) {
verifyApplicationThread();
if (this.scrubbingModeParameters.equals(scrubbingModeParameters)) {
return;
}
ScrubbingModeParameters previousParameters = this.scrubbingModeParameters;
this.scrubbingModeParameters = scrubbingModeParameters;
if (scrubbingModeEnabled
&& trackSelector.isSetParametersSupported()
&& !previousParameters.disabledTrackTypes.equals(
scrubbingModeParameters.disabledTrackTypes)) {
// We are already in scrubbing mode, but the tracks we should disable while scrubbing have
// changed, so we need to re-calculate the track selection parameters.
TrackSelectionParameters trackSelectionParameters =
addDisabledTrackTypes(
getTrackSelectionParameters(), scrubbingModeParameters.disabledTrackTypes);
if (!trackSelectionParameters.equals(trackSelector.getParameters())) {
// Set the parameters directly, to avoid firing onTrackSelectionParametersChanged.
trackSelector.setParameters(trackSelectionParameters);
}
}
}
@Override
public ScrubbingModeParameters getScrubbingModeParameters() {
verifyApplicationThread();
return scrubbingModeParameters;
}
@Override
public AnalyticsCollector getAnalyticsCollector() {
verifyApplicationThread();
@ -2927,6 +3004,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
.build();
}
private static TrackSelectionParameters addDisabledTrackTypes(
TrackSelectionParameters parameters, ImmutableSet<@C.TrackType Integer> trackTypesToDisable) {
TrackSelectionParameters.Builder parametersBuilder = parameters.buildUpon();
for (@C.TrackType Integer trackType : trackTypesToDisable) {
parametersBuilder.setTrackTypeDisabled(trackType, true);
}
return parametersBuilder.build();
}
private static final class MediaSourceHolderSnapshot implements MediaSourceInfoHolder {
private final Object uid;

View File

@ -0,0 +1,103 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.exoplayer;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.C.TrackType;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Set;
/**
* Parameters to control the behavior of {@linkplain ExoPlayer#setScrubbingModeEnabled scrubbing
* mode}.
*/
@UnstableApi
public final class ScrubbingModeParameters {
/** An instance which defines sensible default values for many scrubbing use-cases. */
public static final ScrubbingModeParameters DEFAULT =
new ScrubbingModeParameters.Builder().build();
/**
* Builder for {@link ScrubbingModeParameters} instances.
*
* <p>This builder defines some defaults that may change in future releases of the library, and
* new properties may be added that default to enabled.
*/
public static final class Builder {
private ImmutableSet<@TrackType Integer> disabledTrackTypes;
/** Creates an instance. */
public Builder() {
this.disabledTrackTypes = ImmutableSet.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_METADATA);
}
private Builder(ScrubbingModeParameters scrubbingModeParameters) {
this.disabledTrackTypes = scrubbingModeParameters.disabledTrackTypes;
}
/**
* Sets which track types should be disabled in scrubbing mode.
*
* <p>Defaults to {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_METADATA} (this may change
* in a future release).
*
* <p>See {@link ScrubbingModeParameters#disabledTrackTypes}.
*
* @param disabledTrackTypes The track types to disable in scrubbing mode.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
public Builder setDisabledTrackTypes(Set<@TrackType Integer> disabledTrackTypes) {
this.disabledTrackTypes = ImmutableSet.copyOf(disabledTrackTypes);
return this;
}
/** Returns the built {@link ScrubbingModeParameters}. */
public ScrubbingModeParameters build() {
return new ScrubbingModeParameters(this);
}
}
/** Which track types will be disabled in scrubbing mode. */
public final ImmutableSet<@TrackType Integer> disabledTrackTypes;
private ScrubbingModeParameters(Builder builder) {
this.disabledTrackTypes = builder.disabledTrackTypes;
}
/** Returns a {@link Builder} initialized with the values from this instance. */
public Builder buildUpon() {
return new Builder(this);
}
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof ScrubbingModeParameters)) {
return false;
}
ScrubbingModeParameters that = (ScrubbingModeParameters) o;
return disabledTrackTypes.equals(that.disabledTrackTypes);
}
@Override
public int hashCode() {
return disabledTrackTypes.hashCode();
}
}

View File

@ -628,6 +628,18 @@ public class SimpleExoPlayer extends BasePlayer implements ExoPlayer {
player.setScrubbingModeEnabled(scrubbingModeEnabled);
}
@Override
public void setScrubbingModeParameters(ScrubbingModeParameters scrubbingModeParameters) {
blockUntilConstructorFinished();
player.setScrubbingModeParameters(scrubbingModeParameters);
}
@Override
public ScrubbingModeParameters getScrubbingModeParameters() {
blockUntilConstructorFinished();
return player.getScrubbingModeParameters();
}
@Override
public AnalyticsCollector getAnalyticsCollector() {
blockUntilConstructorFinished();

View File

@ -668,12 +668,8 @@ public class DefaultTrackSelector extends MappingTrackSelector
return this;
}
/**
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
*/
@CanIgnoreReturnValue
@Override
@Deprecated
@SuppressWarnings("deprecation") // Intentionally returning deprecated type
public ParametersBuilder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
delegate.setDisabledTrackTypes(disabledTrackTypes);
@ -1527,13 +1523,8 @@ public class DefaultTrackSelector extends MappingTrackSelector
return this;
}
/**
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
*/
@CanIgnoreReturnValue
@Override
@Deprecated
@SuppressWarnings("deprecation")
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
super.setDisabledTrackTypes(disabledTrackTypes);
return this;

View File

@ -19,32 +19,45 @@ import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.D
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.advance;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.graphics.SurfaceTexture;
import android.view.Surface;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.Player;
import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.exoplayer.analytics.AnalyticsListener;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
import androidx.media3.test.utils.ExoPlayerTestRunner;
import androidx.media3.test.utils.FakeAudioRenderer;
import androidx.media3.test.utils.FakeMediaPeriod.TrackDataFactory;
import androidx.media3.test.utils.FakeMediaSource;
import androidx.media3.test.utils.FakeRenderer;
import androidx.media3.test.utils.FakeTimeline;
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
import androidx.media3.test.utils.FakeVideoRenderer;
import androidx.media3.test.utils.TestExoPlayerBuilder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
/** Tests for {@linkplain ExoPlayer#setScrubbingModeEnabled(boolean) scrubbing mode}. */
@RunWith(AndroidJUnit4.class)
@ -52,19 +65,14 @@ public final class ExoPlayerScrubbingTest {
@Test
public void scrubbingMode_suppressesPlayback() throws Exception {
Timeline timeline = new FakeTimeline();
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setRenderers(renderer)
.build();
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
Player.Listener mockListener = mock(Player.Listener.class);
player.addListener(mockListener);
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
player.setMediaSource(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT));
player.prepare();
player.play();
advance(player).untilPosition(0, 2000);
player.setScrubbingModeEnabled(true);
@ -89,7 +97,6 @@ public final class ExoPlayerScrubbingTest {
player.setVideoSurface(surface);
Player.Listener mockListener = mock(Player.Listener.class);
player.addListener(mockListener);
player.setMediaSource(
new FakeMediaSource(
timeline,
@ -102,12 +109,11 @@ public final class ExoPlayerScrubbingTest {
ExoPlayerTestRunner.VIDEO_FORMAT));
player.prepare();
player.play();
advance(player).untilPosition(0, 1000);
VideoFrameMetadataListener mockVideoFrameMetadataListener =
mock(VideoFrameMetadataListener.class);
player.setVideoFrameMetadataListener(mockVideoFrameMetadataListener);
player.setScrubbingModeEnabled(true);
advance(player).untilPendingCommandsAreFullyHandled();
player.seekTo(2500);
@ -123,7 +129,6 @@ public final class ExoPlayerScrubbingTest {
player.setScrubbingModeEnabled(false);
advance(player).untilPendingCommandsAreFullyHandled();
player.clearVideoFrameMetadataListener(mockVideoFrameMetadataListener);
advance(player).untilState(Player.STATE_ENDED);
player.release();
surface.release();
@ -131,7 +136,6 @@ public final class ExoPlayerScrubbingTest {
ArgumentCaptor<Long> presentationTimeUsCaptor = ArgumentCaptor.forClass(Long.class);
verify(mockVideoFrameMetadataListener, atLeastOnce())
.onVideoFrameAboutToBeRendered(presentationTimeUsCaptor.capture(), anyLong(), any(), any());
assertThat(presentationTimeUsCaptor.getAllValues())
.containsExactly(2_500_000L, 3_500_000L, 4_500_000L)
.inOrder();
@ -148,4 +152,249 @@ public final class ExoPlayerScrubbingTest {
.containsExactly(2500L, 3000L, 3500L, 4000L, 4500L)
.inOrder();
}
@Test
public void scrubbingMode_disablesAudioTrack_masksTrackSelectionParameters() throws Exception {
Timeline timeline = new FakeTimeline();
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
player.setMediaSource(
new FakeMediaSource(
timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT));
player.prepare();
player.play();
advance(player).untilPosition(0, 1000);
TrackSelectionParameters trackSelectionParametersBeforeScrubbingMode =
player.getTrackSelectionParameters();
AnalyticsListener mockAnalyticsListener = mock(AnalyticsListener.class);
player.addAnalyticsListener(mockAnalyticsListener);
player.setScrubbingModeEnabled(true);
advance(player).untilPendingCommandsAreFullyHandled();
verify(mockAnalyticsListener).onAudioDisabled(any(), any());
verify(mockAnalyticsListener)
.onRendererReadyChanged(any(), anyInt(), eq(C.TRACK_TYPE_AUDIO), eq(false));
verify(mockAnalyticsListener)
.onTracksChanged(any(), argThat(t -> !t.isTypeSelected(C.TRACK_TYPE_AUDIO)));
assertThat(player.getTrackSelectionParameters())
.isEqualTo(trackSelectionParametersBeforeScrubbingMode);
player.setScrubbingModeEnabled(false);
advance(player).untilPendingCommandsAreFullyHandled();
verify(mockAnalyticsListener).onAudioEnabled(any(), any());
verify(mockAnalyticsListener)
.onRendererReadyChanged(any(), anyInt(), eq(C.TRACK_TYPE_AUDIO), eq(true));
verify(mockAnalyticsListener)
.onTracksChanged(any(), argThat(t -> t.isTypeSelected(C.TRACK_TYPE_AUDIO)));
assertThat(player.getTrackSelectionParameters())
.isEqualTo(trackSelectionParametersBeforeScrubbingMode);
verify(mockAnalyticsListener, never()).onTrackSelectionParametersChanged(any(), any());
player.release();
surface.release();
}
@Test
public void
disableTracksDuringScrubbingMode_typeThatIsDisabledByScrubbing_staysDisabledAfterScrubbing()
throws Exception {
Timeline timeline = new FakeTimeline();
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
player.setMediaSource(
new FakeMediaSource(
timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT));
player.prepare();
player.play();
advance(player).untilPosition(0, 1000);
AnalyticsListener mockAnalyticsListener = mock(AnalyticsListener.class);
player.addAnalyticsListener(mockAnalyticsListener);
player.setScrubbingModeEnabled(true);
advance(player).untilPendingCommandsAreFullyHandled();
// Use InOrder so we can 'consume' verifications (see never() comment below).
InOrder analyticsListenerInOrder = inOrder(mockAnalyticsListener);
analyticsListenerInOrder.verify(mockAnalyticsListener).onAudioDisabled(any(), any());
// Manually disable the audio track which is already temporarily disabled by scrubbing mode.
// This is a no-op until scrubbing mode ends, at which point the audio track should stay
// disabled.
TrackSelectionParameters newTrackSelectionParameters =
player
.getTrackSelectionParameters()
.buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true)
.build();
player.setTrackSelectionParameters(newTrackSelectionParameters);
assertThat(player.getTrackSelectionParameters()).isEqualTo(newTrackSelectionParameters);
analyticsListenerInOrder
.verify(mockAnalyticsListener)
.onTrackSelectionParametersChanged(any(), eq(newTrackSelectionParameters));
player.setScrubbingModeEnabled(false);
assertThat(player.getTrackSelectionParameters()).isEqualTo(newTrackSelectionParameters);
advance(player).untilPendingCommandsAreFullyHandled();
// This is never() because the InOrder verification above already 'consumed' the
// expected onTrackSelectionParametersChanged call above, and we want to assert it's not fired
// again when we leave scrubbing mode.
analyticsListenerInOrder
.verify(mockAnalyticsListener, never())
.onTrackSelectionParametersChanged(any(), any());
analyticsListenerInOrder.verify(mockAnalyticsListener, never()).onAudioEnabled(any(), any());
player.release();
surface.release();
}
@Test
public void
disableTracksDuringScrubbingMode_typeThatIsNotDisabledByScrubbing_immediatelyDisabled()
throws Exception {
Timeline timeline = new FakeTimeline();
TestExoPlayerBuilder playerBuilder =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext());
ExoPlayer player =
playerBuilder
.setRenderersFactory(
(eventHandler,
videoRendererEventListener,
audioRendererEventListener,
textRendererOutput,
metadataRendererOutput) -> {
HandlerWrapper clockAwareHandler =
playerBuilder
.getClock()
.createHandler(eventHandler.getLooper(), /* callback= */ null);
return new Renderer[] {
new FakeVideoRenderer(clockAwareHandler, videoRendererEventListener),
new FakeAudioRenderer(clockAwareHandler, audioRendererEventListener),
new FakeRenderer(C.TRACK_TYPE_TEXT)
};
})
.build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
Format textFormat =
new Format.Builder()
.setSampleMimeType(MimeTypes.TEXT_VTT)
.setLanguage("en")
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.build();
player.setMediaSource(
new FakeMediaSource(
timeline,
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT,
textFormat));
player.prepare();
player.play();
advance(player).untilPosition(0, 1000);
AnalyticsListener mockAnalyticsListener = mock(AnalyticsListener.class);
player.addAnalyticsListener(mockAnalyticsListener);
player.setScrubbingModeEnabled(true);
advance(player).untilPendingCommandsAreFullyHandled();
// Use InOrder so we can 'consume' verifications (see never() comment below).
InOrder analyticsListenerInOrder = inOrder(mockAnalyticsListener);
verify(mockAnalyticsListener).onAudioDisabled(any(), any());
// Manually disable the text track. This should be immediately disabled, and remain disabled
// after scrubbing mode ends.
TrackSelectionParameters newTrackSelectionParameters =
player
.getTrackSelectionParameters()
.buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true)
.build();
player.setTrackSelectionParameters(newTrackSelectionParameters);
assertThat(player.getTrackSelectionParameters()).isEqualTo(newTrackSelectionParameters);
analyticsListenerInOrder
.verify(mockAnalyticsListener)
.onTrackSelectionParametersChanged(any(), eq(newTrackSelectionParameters));
advance(player).untilPendingCommandsAreFullyHandled();
analyticsListenerInOrder
.verify(mockAnalyticsListener)
.onRendererReadyChanged(any(), anyInt(), eq(C.TRACK_TYPE_TEXT), eq(false));
player.setScrubbingModeEnabled(false);
assertThat(player.getTrackSelectionParameters()).isEqualTo(newTrackSelectionParameters);
advance(player).untilPendingCommandsAreFullyHandled();
// This is never() because the InOrder verification above already 'consumed' the
// expected onTrackSelectionParametersChanged call above, and we want to assert it's not fired
// again when we leave scrubbing mode.
analyticsListenerInOrder
.verify(mockAnalyticsListener, never())
.onTrackSelectionParametersChanged(any(), any());
analyticsListenerInOrder.verify(mockAnalyticsListener).onAudioEnabled(any(), any());
player.release();
surface.release();
}
@Test
public void customizeParameters_beforeScrubbingModeEnabled() throws Exception {
Timeline timeline = new FakeTimeline();
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
// Prevent any tracks being disabled during scrubbing
ScrubbingModeParameters scrubbingModeParameters =
new ScrubbingModeParameters.Builder().setDisabledTrackTypes(ImmutableSet.of()).build();
player.setScrubbingModeParameters(scrubbingModeParameters);
assertThat(player.getScrubbingModeParameters()).isEqualTo(scrubbingModeParameters);
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
player.setMediaSource(
new FakeMediaSource(
timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT));
player.prepare();
player.play();
advance(player).untilPosition(0, 1000);
AnalyticsListener mockAnalyticsListener = mock(AnalyticsListener.class);
player.addAnalyticsListener(mockAnalyticsListener);
player.setScrubbingModeEnabled(true);
advance(player).untilPendingCommandsAreFullyHandled();
player.setScrubbingModeEnabled(false);
advance(player).untilPendingCommandsAreFullyHandled();
verify(mockAnalyticsListener, never()).onAudioDisabled(any(), any());
player.release();
surface.release();
}
@Test
public void customizeParameters_duringScrubbingMode() throws Exception {
Timeline timeline = new FakeTimeline();
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
player.setMediaSource(
new FakeMediaSource(
timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT));
player.prepare();
player.play();
advance(player).untilPosition(0, 1000);
AnalyticsListener mockAnalyticsListener = mock(AnalyticsListener.class);
player.addAnalyticsListener(mockAnalyticsListener);
player.setScrubbingModeEnabled(true);
advance(player).untilPendingCommandsAreFullyHandled();
// Prevent any tracks being disabled during scrubbing
ScrubbingModeParameters scrubbingModeParameters =
new ScrubbingModeParameters.Builder().setDisabledTrackTypes(ImmutableSet.of()).build();
player.setScrubbingModeParameters(scrubbingModeParameters);
assertThat(player.getScrubbingModeParameters()).isEqualTo(scrubbingModeParameters);
advance(player).untilPendingCommandsAreFullyHandled();
// Check that the audio track gets re-enabled, because the parameters changed to configure this.
verify(mockAnalyticsListener).onAudioEnabled(any(), any());
player.release();
surface.release();
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.exoplayer;
import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.C;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link ScrubbingModeParameters}. */
@RunWith(AndroidJUnit4.class)
public final class ScrubbingModeParametersTest {
@Test
public void defaultValues() {
assertThat(ScrubbingModeParameters.DEFAULT.disabledTrackTypes)
.containsExactly(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_METADATA);
}
}

View File

@ -31,6 +31,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.PlayerMessage;
import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.ScrubbingModeParameters;
import androidx.media3.exoplayer.SeekParameters;
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.analytics.AnalyticsListener;
@ -220,6 +221,16 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
throw new UnsupportedOperationException();
}
@Override
public void setScrubbingModeParameters(ScrubbingModeParameters scrubbingModeParameters) {
throw new UnsupportedOperationException();
}
@Override
public ScrubbingModeParameters getScrubbingModeParameters() {
throw new UnsupportedOperationException();
}
@Override
public void setVideoEffects(List<Effect> videoEffects) {
throw new UnsupportedOperationException();

View File

@ -108,7 +108,7 @@ public class CompositionPlayerSpeedAdjustmentsTest {
compositionPlayer.setComposition(
new Composition.Builder(
new EditedMediaItemSequence.Builder(timestampRecordingEditedMediaItems)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build());
compositionPlayer.prepare();

View File

@ -1357,7 +1357,7 @@ public class TransformerEndToEndTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
ExportTestResult result =
@ -1451,7 +1451,7 @@ public class TransformerEndToEndTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItem, editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
ExportTestResult result =
@ -1891,7 +1891,7 @@ public class TransformerEndToEndTest {
new EditedMediaItemSequence.Builder()
.addGap(100_000)
.addItem(editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build(),
new EditedMediaItemSequence.Builder(editedMediaItem).build())
.build();

View File

@ -106,7 +106,7 @@ public class TransformerGapsTest {
new Composition.Builder(
new EditedMediaItemSequence.Builder(
AUDIO_ONLY_MEDIA_ITEM, AUDIO_VIDEO_MEDIA_ITEM, AUDIO_VIDEO_MEDIA_ITEM)
.setForceVideoTrack(true)
.experimentalSetForceVideoTrack(true)
.build())
.setEffects(
new Effects(
@ -214,7 +214,7 @@ public class TransformerGapsTest {
.addGap(/* durationUs= */ 1_000_000)
.addItem(VIDEO_ONLY_MEDIA_ITEM)
.addItem(VIDEO_ONLY_MEDIA_ITEM)
.setForceVideoTrack(true)
.experimentalSetForceVideoTrack(true)
.build())
.setEffects(
new Effects(
@ -326,7 +326,7 @@ public class TransformerGapsTest {
.addGap(/* durationUs= */ 1_000_000)
.addItem(AUDIO_VIDEO_MEDIA_ITEM)
.addItem(AUDIO_VIDEO_MEDIA_ITEM)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
TransformerAndroidTestRunner transformerAndroidTestRunner =
@ -361,8 +361,8 @@ public class TransformerGapsTest {
.addGap(/* durationUs= */ 1_000_000)
.addItem(AUDIO_VIDEO_MEDIA_ITEM)
.addItem(AUDIO_VIDEO_MEDIA_ITEM)
.setForceAudioTrack(true)
.setForceVideoTrack(true)
.experimentalSetForceAudioTrack(true)
.experimentalSetForceVideoTrack(true)
.build())
.setEffects(
new Effects(

View File

@ -399,7 +399,7 @@ public class VideoTimestampConsistencyTest {
/* testId= */ testName.getMethodName(),
new Composition.Builder(
new EditedMediaItemSequence.Builder(timestampRecordingEditedMediaItems)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build());
@ -427,7 +427,7 @@ public class VideoTimestampConsistencyTest {
compositionPlayer.setComposition(
new Composition.Builder(
new EditedMediaItemSequence.Builder(timestampRecordingEditedMediaItems)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build());
compositionPlayer.prepare();

View File

@ -120,7 +120,8 @@ public final class Composition {
}
/**
* @deprecated Use {@link EditedMediaItemSequence.Builder#setForceAudioTrack(boolean)} instead.
* @deprecated Use {@link
* EditedMediaItemSequence.Builder#experimentalSetForceAudioTrack(boolean)} instead.
*/
@Deprecated
@CanIgnoreReturnValue
@ -234,7 +235,7 @@ public final class Composition {
new ImmutableList.Builder<>();
for (int i = 0; i < sequences.size(); i++) {
updatedSequencesBuilder.add(
sequences.get(i).buildUpon().setForceAudioTrack(forceAudioTrack).build());
sequences.get(i).buildUpon().experimentalSetForceAudioTrack(forceAudioTrack).build());
}
updatedSequences = updatedSequencesBuilder.build();
} else {
@ -359,8 +360,8 @@ public final class Composition {
public final Effects effects;
/**
* @deprecated Use {@link EditedMediaItemSequence.Builder#setForceAudioTrack(boolean)} to set the
* flag and {@link EditedMediaItemSequence#forceAudioTrack} to read the flag.
* @deprecated Use {@link EditedMediaItemSequence.Builder#experimentalSetForceAudioTrack(boolean)}
* to set the flag and {@link EditedMediaItemSequence#forceAudioTrack} to read the flag.
*/
@Deprecated public final boolean forceAudioTrack;

View File

@ -102,8 +102,8 @@ public final class EditedMediaItemSequence {
* <p>A gap is a period of time with no media.
*
* <p>If the gap is added at the start of the sequence, then {@linkplain
* #setForceAudioTrack(boolean) force audio track} or/and {@linkplain
* #setForceVideoTrack(boolean) force video track} flag must be set appropriately.
* #experimentalSetForceAudioTrack(boolean) force audio track} or/and {@linkplain
* #experimentalSetForceVideoTrack(boolean) force video track} flag must be set appropriately.
*
* @param durationUs The duration of the gap, in milliseconds.
* @return This builder, for convenience.
@ -159,10 +159,12 @@ public final class EditedMediaItemSequence {
*
* <p>The default value is {@code false}.
*
* <p>This method is experimental and will be renamed or removed in a future release.
*
* @param forceAudioTrack Whether to force audio track.
*/
@CanIgnoreReturnValue
public Builder setForceAudioTrack(boolean forceAudioTrack) {
public Builder experimentalSetForceAudioTrack(boolean forceAudioTrack) {
this.forceAudioTrack = forceAudioTrack;
return this;
}
@ -196,10 +198,12 @@ public final class EditedMediaItemSequence {
*
* <p>The default value is {@code false}.
*
* <p>This method is experimental and will be renamed or removed in a future release.
*
* @param forceVideoTrack Whether to force video track.
*/
@CanIgnoreReturnValue
public Builder setForceVideoTrack(boolean forceVideoTrack) {
public Builder experimentalSetForceVideoTrack(boolean forceVideoTrack) {
this.forceVideoTrack = forceVideoTrack;
return this;
}

View File

@ -68,9 +68,9 @@ public final class Effects {
* <p>The {@linkplain AudioProcessor audio processor} and {@linkplain Effect video effect} are
* interlinked to help maintain A/V sync. When using Transformer, if the input file doesn't have
* audio, or audio is being removed, you may have to {@linkplain
* EditedMediaItemSequence.Builder#setForceAudioTrack force an audio track} for the interlinked
* effects to function correctly. Alternatively, you can use {@link SpeedChangeEffect} when input
* has no audio.
* EditedMediaItemSequence.Builder#experimentalSetForceAudioTrack force an audio track} for the
* interlinked effects to function correctly. Alternatively, you can use {@link SpeedChangeEffect}
* when input has no audio.
*
* @param speedProvider The {@link SpeedProvider} determining the speed for the media at specific
* timestamps.

View File

@ -364,11 +364,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
trackType == C.TRACK_TYPE_AUDIO
? "The preceding MediaItem does not contain any audio track. If the sequence starts"
+ " with an item without audio track (like images), followed by items with"
+ " audio tracks, then EditedMediaItemSequence.Builder.setForceAudioTrack()"
+ " needs to be set to true."
+ " audio tracks, then"
+ " EditedMediaItemSequence.Builder.experimentalSetForceAudioTrack() needs to"
+ " be set to true."
: "The preceding MediaItem does not contain any video track. If the sequence starts"
+ " with an item without video track (audio only), followed by items with video"
+ " tracks, then EditedMediaItemSequence.Builder.setForceVideoTrack() needs to"
+ " tracks, then"
+ " EditedMediaItemSequence.Builder.experimentalSetForceVideoTrack() needs to"
+ " be set to true.";
sampleConsumer =
checkStateNotNull(sampleConsumersByTrackType.get(trackType), missingTrackMessage);

View File

@ -942,7 +942,7 @@ public final class Transformer {
* C.TrackType track}, so must all items in that sequence.
* <ul>
* <li>For audio, this condition can be removed by setting {@link
* EditedMediaItemSequence.Builder#setForceAudioTrack(boolean)} flag.
* EditedMediaItemSequence.Builder#experimentalSetForceAudioTrack(boolean)} flag.
* </ul>
* <li>If a sequence starts with an HDR {@link EditedMediaItem}, all the following items in the
* sequence must be HDR.

View File

@ -483,7 +483,7 @@ public class CompositionExportTest {
new Composition.Builder(
new EditedMediaItemSequence.Builder()
.addGap(1_000_000)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build(),
new EditedMediaItemSequence.Builder(audioItem1000ms).build())
.build();
@ -515,7 +515,7 @@ public class CompositionExportTest {
new EditedMediaItemSequence.Builder()
.addGap(100_000)
.addItem(audioEditedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build(),
new EditedMediaItemSequence.Builder(otherAudioEditedMediaItem).build())
.build();
@ -595,7 +595,7 @@ public class CompositionExportTest {
new EditedMediaItemSequence.Builder()
.addGap(200_000)
.addItem(audioEditedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -723,7 +723,7 @@ public class CompositionExportTest {
new EditedMediaItemSequence.Builder(audioItem1000ms).build(),
new EditedMediaItemSequence.Builder()
.addGap(1_000_000)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
@ -743,11 +743,11 @@ public class CompositionExportTest {
new Composition.Builder(
new EditedMediaItemSequence.Builder()
.addGap(500_000)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build(),
new EditedMediaItemSequence.Builder()
.addGap(500_000)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();

View File

@ -148,7 +148,10 @@ public final class MediaItemExportTest {
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
EditedMediaItemSequence gapSequence =
new EditedMediaItemSequence.Builder().addGap(500_000).setForceAudioTrack(true).build();
new EditedMediaItemSequence.Builder()
.addGap(500_000)
.experimentalSetForceAudioTrack(true)
.build();
transformer.start(new Composition.Builder(gapSequence).build(), outputDir.newFile().getPath());
ExportResult result = TransformerTestRunner.runLooper(transformer);
@ -403,7 +406,7 @@ public final class MediaItemExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
@ -424,7 +427,7 @@ public final class MediaItemExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
@ -451,7 +454,7 @@ public final class MediaItemExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
@ -479,7 +482,7 @@ public final class MediaItemExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();
@ -502,7 +505,7 @@ public final class MediaItemExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.build();

View File

@ -173,7 +173,9 @@ public final class ParameterizedAudioExportTest {
}
return new Composition.Builder(
new EditedMediaItemSequence.Builder(items.build()).setForceAudioTrack(true).build())
new EditedMediaItemSequence.Builder(items.build())
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
}

View File

@ -136,7 +136,9 @@ public final class ParameterizedItemExportTest {
.build();
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(item).setForceAudioTrack(true).build())
new EditedMediaItemSequence.Builder(item)
.experimentalSetForceAudioTrack(true)
.build())
.build();
transformer.start(composition, outputDir.newFile().getPath());

View File

@ -255,7 +255,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(audioVideoMediaItem, videoOnlyMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -285,7 +285,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(videoOnlyMediaItem, audioVideoMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -321,7 +321,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(audioEditedMediaItem, noAudioEditedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -357,7 +357,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(silenceEditedMediaItem, audioEditedMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -386,7 +386,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(videoOnlyMediaItem, videoOnlyMediaItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -420,7 +420,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(silenceWithEffectsItem, silenceItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -454,7 +454,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(silenceItem, silenceWithEffectsItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -491,7 +491,7 @@ public final class SequenceExportTest {
Composition composition =
new Composition.Builder(
new EditedMediaItemSequence.Builder(firstItem, secondItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build())
.setTransmuxVideo(true)
.build();
@ -554,7 +554,7 @@ public final class SequenceExportTest {
new EditedMediaItemSequence.Builder()
.addGap(500_000)
.addItem(audioVideoItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build();
Composition composition = new Composition.Builder(sequence).build();
@ -574,7 +574,7 @@ public final class SequenceExportTest {
new EditedMediaItemSequence.Builder()
.addGap(300_000)
.addGap(200_000)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build();
Composition composition = new Composition.Builder(sequence).build();
@ -622,7 +622,7 @@ public final class SequenceExportTest {
.addGap(200_000)
.addGap(500_000)
.addItem(audioItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build();
Composition composition = new Composition.Builder(sequence).build();
@ -718,7 +718,7 @@ public final class SequenceExportTest {
.addItem(firstAudioItem)
.addGap(200_000)
.addItem(secondAudioItem)
.setForceAudioTrack(true)
.experimentalSetForceAudioTrack(true)
.build();
Composition composition = new Composition.Builder(sequence).build();