mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Compare commits
3 Commits
9a700d21bf
...
83efd8eb66
Author | SHA1 | Date | |
---|---|---|---|
![]() |
83efd8eb66 | ||
![]() |
6c4c4bdea4 | ||
![]() |
72bb474604 |
@ -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:
|
||||
|
@ -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 =
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -108,7 +108,7 @@ public class CompositionPlayerSpeedAdjustmentsTest {
|
||||
compositionPlayer.setComposition(
|
||||
new Composition.Builder(
|
||||
new EditedMediaItemSequence.Builder(timestampRecordingEditedMediaItems)
|
||||
.setForceAudioTrack(true)
|
||||
.experimentalSetForceAudioTrack(true)
|
||||
.build())
|
||||
.build());
|
||||
compositionPlayer.prepare();
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user