mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
This commit is contained in:
parent
6c4c4bdea4
commit
83efd8eb66
@ -14,7 +14,9 @@
|
|||||||
([#2229](https://github.com/androidx/media/issues/2229)).
|
([#2229](https://github.com/androidx/media/issues/2229)).
|
||||||
* Add `ExoPlayer.setScrubbingModeEnabled(boolean)` method. This optimizes
|
* Add `ExoPlayer.setScrubbingModeEnabled(boolean)` method. This optimizes
|
||||||
the player for many frequent seeks (for example, from a user dragging a
|
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
|
* `AdPlaybackState.withAdDurationsUs(long[][])` can be used after ad
|
||||||
groups have been removed. The user still needs to pass in an array of
|
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
|
durations for removed ad groups which can be empty or null
|
||||||
|
@ -849,10 +849,8 @@ public class TrackSelectionParameters {
|
|||||||
*
|
*
|
||||||
* @param disabledTrackTypes The track types to disable.
|
* @param disabledTrackTypes The track types to disable.
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
|
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
@Deprecated
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
||||||
this.disabledTrackTypes.clear();
|
this.disabledTrackTypes.clear();
|
||||||
|
@ -238,6 +238,7 @@ public interface ExoPlayer extends Player {
|
|||||||
@C.VideoChangeFrameRateStrategy /* package */ int videoChangeFrameRateStrategy;
|
@C.VideoChangeFrameRateStrategy /* package */ int videoChangeFrameRateStrategy;
|
||||||
/* package */ boolean useLazyPreparation;
|
/* package */ boolean useLazyPreparation;
|
||||||
/* package */ SeekParameters seekParameters;
|
/* package */ SeekParameters seekParameters;
|
||||||
|
/* package */ ScrubbingModeParameters scrubbingModeParameters;
|
||||||
/* package */ long seekBackIncrementMs;
|
/* package */ long seekBackIncrementMs;
|
||||||
/* package */ long seekForwardIncrementMs;
|
/* package */ long seekForwardIncrementMs;
|
||||||
/* package */ long maxSeekToPreviousPositionMs;
|
/* package */ long maxSeekToPreviousPositionMs;
|
||||||
@ -452,6 +453,7 @@ public interface ExoPlayer extends Player {
|
|||||||
seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS;
|
seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS;
|
||||||
seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS;
|
seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS;
|
||||||
maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
|
maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
|
||||||
|
scrubbingModeParameters = ScrubbingModeParameters.DEFAULT;
|
||||||
livePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build();
|
livePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build();
|
||||||
clock = Clock.DEFAULT;
|
clock = Clock.DEFAULT;
|
||||||
releaseTimeoutMs = DEFAULT_RELEASE_TIMEOUT_MS;
|
releaseTimeoutMs = DEFAULT_RELEASE_TIMEOUT_MS;
|
||||||
@ -897,6 +899,22 @@ public interface ExoPlayer extends Player {
|
|||||||
return this;
|
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}.
|
* Sets a timeout for calls to {@link #release} and {@link #setForegroundMode}.
|
||||||
*
|
*
|
||||||
@ -1466,6 +1484,20 @@ public interface ExoPlayer extends Player {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
void setScrubbingModeEnabled(boolean scrubbingModeEnabled);
|
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
|
* Sets a {@link List} of {@linkplain Effect video effects} that will be applied to each video
|
||||||
* frame.
|
* frame.
|
||||||
|
@ -58,6 +58,7 @@ import androidx.media3.common.AudioAttributes;
|
|||||||
import androidx.media3.common.AuxEffectInfo;
|
import androidx.media3.common.AuxEffectInfo;
|
||||||
import androidx.media3.common.BasePlayer;
|
import androidx.media3.common.BasePlayer;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.C.TrackType;
|
||||||
import androidx.media3.common.DeviceInfo;
|
import androidx.media3.common.DeviceInfo;
|
||||||
import androidx.media3.common.Effect;
|
import androidx.media3.common.Effect;
|
||||||
import androidx.media3.common.Format;
|
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.CameraMotionListener;
|
||||||
import androidx.media3.exoplayer.video.spherical.SphericalGLSurfaceView;
|
import androidx.media3.exoplayer.video.spherical.SphericalGLSurfaceView;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -182,6 +184,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
private boolean pendingDiscontinuity;
|
private boolean pendingDiscontinuity;
|
||||||
private boolean foregroundMode;
|
private boolean foregroundMode;
|
||||||
private boolean scrubbingModeEnabled;
|
private boolean scrubbingModeEnabled;
|
||||||
|
@Nullable private ImmutableSet<@TrackType Integer> disabledTrackTypesWithoutScrubbingMode;
|
||||||
|
private ScrubbingModeParameters scrubbingModeParameters;
|
||||||
private SeekParameters seekParameters;
|
private SeekParameters seekParameters;
|
||||||
private ShuffleOrder shuffleOrder;
|
private ShuffleOrder shuffleOrder;
|
||||||
private PreloadConfiguration preloadConfiguration;
|
private PreloadConfiguration preloadConfiguration;
|
||||||
@ -284,6 +288,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
this.seekBackIncrementMs = builder.seekBackIncrementMs;
|
this.seekBackIncrementMs = builder.seekBackIncrementMs;
|
||||||
this.seekForwardIncrementMs = builder.seekForwardIncrementMs;
|
this.seekForwardIncrementMs = builder.seekForwardIncrementMs;
|
||||||
this.maxSeekToPreviousPositionMs = builder.maxSeekToPreviousPositionMs;
|
this.maxSeekToPreviousPositionMs = builder.maxSeekToPreviousPositionMs;
|
||||||
|
this.scrubbingModeParameters = builder.scrubbingModeParameters;
|
||||||
this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems;
|
this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems;
|
||||||
this.applicationLooper = builder.looper;
|
this.applicationLooper = builder.looper;
|
||||||
this.clock = builder.clock;
|
this.clock = builder.clock;
|
||||||
@ -1224,20 +1229,38 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
@Override
|
@Override
|
||||||
public TrackSelectionParameters getTrackSelectionParameters() {
|
public TrackSelectionParameters getTrackSelectionParameters() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
return trackSelector.getParameters();
|
TrackSelectionParameters parameters = trackSelector.getParameters();
|
||||||
|
return scrubbingModeEnabled
|
||||||
|
? parameters
|
||||||
|
.buildUpon()
|
||||||
|
.setDisabledTrackTypes(disabledTrackTypesWithoutScrubbingMode)
|
||||||
|
.build()
|
||||||
|
: parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
|
public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
if (!trackSelector.isSetParametersSupported()
|
if (!trackSelector.isSetParametersSupported()) {
|
||||||
|| parameters.equals(trackSelector.getParameters())) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
trackSelector.setParameters(parameters);
|
TrackSelectionParameters publicParametersBeforeUpdate = getTrackSelectionParameters();
|
||||||
listeners.sendEvent(
|
TrackSelectionParameters internalParameters;
|
||||||
EVENT_TRACK_SELECTION_PARAMETERS_CHANGED,
|
if (scrubbingModeEnabled) {
|
||||||
listener -> listener.onTrackSelectionParametersChanged(parameters));
|
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
|
@Override
|
||||||
@ -1559,10 +1582,64 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scrubbingModeEnabled = scrubbingModeEnabled;
|
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);
|
internalPlayer.setScrubbingModeEnabled(scrubbingModeEnabled);
|
||||||
maybeUpdatePlaybackSuppressionReason();
|
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
|
@Override
|
||||||
public AnalyticsCollector getAnalyticsCollector() {
|
public AnalyticsCollector getAnalyticsCollector() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -2927,6 +3004,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
.build();
|
.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 static final class MediaSourceHolderSnapshot implements MediaSourceInfoHolder {
|
||||||
|
|
||||||
private final Object uid;
|
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);
|
player.setScrubbingModeEnabled(scrubbingModeEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScrubbingModeParameters(ScrubbingModeParameters scrubbingModeParameters) {
|
||||||
|
blockUntilConstructorFinished();
|
||||||
|
player.setScrubbingModeParameters(scrubbingModeParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScrubbingModeParameters getScrubbingModeParameters() {
|
||||||
|
blockUntilConstructorFinished();
|
||||||
|
return player.getScrubbingModeParameters();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AnalyticsCollector getAnalyticsCollector() {
|
public AnalyticsCollector getAnalyticsCollector() {
|
||||||
blockUntilConstructorFinished();
|
blockUntilConstructorFinished();
|
||||||
|
@ -668,12 +668,8 @@ public class DefaultTrackSelector extends MappingTrackSelector
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
|
|
||||||
*/
|
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
@Override
|
@Override
|
||||||
@Deprecated
|
|
||||||
@SuppressWarnings("deprecation") // Intentionally returning deprecated type
|
@SuppressWarnings("deprecation") // Intentionally returning deprecated type
|
||||||
public ParametersBuilder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
public ParametersBuilder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
||||||
delegate.setDisabledTrackTypes(disabledTrackTypes);
|
delegate.setDisabledTrackTypes(disabledTrackTypes);
|
||||||
@ -1527,13 +1523,8 @@ public class DefaultTrackSelector extends MappingTrackSelector
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
|
|
||||||
*/
|
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
@Override
|
@Override
|
||||||
@Deprecated
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
||||||
super.setDisabledTrackTypes(disabledTrackTypes);
|
super.setDisabledTrackTypes(disabledTrackTypes);
|
||||||
return this;
|
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 androidx.media3.test.utils.robolectric.TestPlayerRunHelper.advance;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.media3.common.C;
|
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;
|
||||||
import androidx.media3.common.Player.PositionInfo;
|
import androidx.media3.common.Player.PositionInfo;
|
||||||
import androidx.media3.common.Timeline;
|
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.drm.DrmSessionManager;
|
||||||
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
|
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
|
||||||
import androidx.media3.test.utils.ExoPlayerTestRunner;
|
import androidx.media3.test.utils.ExoPlayerTestRunner;
|
||||||
|
import androidx.media3.test.utils.FakeAudioRenderer;
|
||||||
import androidx.media3.test.utils.FakeMediaPeriod.TrackDataFactory;
|
import androidx.media3.test.utils.FakeMediaPeriod.TrackDataFactory;
|
||||||
import androidx.media3.test.utils.FakeMediaSource;
|
import androidx.media3.test.utils.FakeMediaSource;
|
||||||
import androidx.media3.test.utils.FakeRenderer;
|
import androidx.media3.test.utils.FakeRenderer;
|
||||||
import androidx.media3.test.utils.FakeTimeline;
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
|
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
|
||||||
|
import androidx.media3.test.utils.FakeVideoRenderer;
|
||||||
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
/** Tests for {@linkplain ExoPlayer#setScrubbingModeEnabled(boolean) scrubbing mode}. */
|
/** Tests for {@linkplain ExoPlayer#setScrubbingModeEnabled(boolean) scrubbing mode}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@ -52,19 +65,14 @@ public final class ExoPlayerScrubbingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void scrubbingMode_suppressesPlayback() throws Exception {
|
public void scrubbingMode_suppressesPlayback() throws Exception {
|
||||||
Timeline timeline = new FakeTimeline();
|
|
||||||
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
|
|
||||||
ExoPlayer player =
|
ExoPlayer player =
|
||||||
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
|
||||||
.setRenderers(renderer)
|
|
||||||
.build();
|
|
||||||
Player.Listener mockListener = mock(Player.Listener.class);
|
Player.Listener mockListener = mock(Player.Listener.class);
|
||||||
player.addListener(mockListener);
|
player.addListener(mockListener);
|
||||||
|
player.setMediaSource(
|
||||||
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
|
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
player.play();
|
player.play();
|
||||||
|
|
||||||
advance(player).untilPosition(0, 2000);
|
advance(player).untilPosition(0, 2000);
|
||||||
|
|
||||||
player.setScrubbingModeEnabled(true);
|
player.setScrubbingModeEnabled(true);
|
||||||
@ -89,7 +97,6 @@ public final class ExoPlayerScrubbingTest {
|
|||||||
player.setVideoSurface(surface);
|
player.setVideoSurface(surface);
|
||||||
Player.Listener mockListener = mock(Player.Listener.class);
|
Player.Listener mockListener = mock(Player.Listener.class);
|
||||||
player.addListener(mockListener);
|
player.addListener(mockListener);
|
||||||
|
|
||||||
player.setMediaSource(
|
player.setMediaSource(
|
||||||
new FakeMediaSource(
|
new FakeMediaSource(
|
||||||
timeline,
|
timeline,
|
||||||
@ -102,12 +109,11 @@ public final class ExoPlayerScrubbingTest {
|
|||||||
ExoPlayerTestRunner.VIDEO_FORMAT));
|
ExoPlayerTestRunner.VIDEO_FORMAT));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
player.play();
|
player.play();
|
||||||
|
|
||||||
advance(player).untilPosition(0, 1000);
|
advance(player).untilPosition(0, 1000);
|
||||||
|
|
||||||
VideoFrameMetadataListener mockVideoFrameMetadataListener =
|
VideoFrameMetadataListener mockVideoFrameMetadataListener =
|
||||||
mock(VideoFrameMetadataListener.class);
|
mock(VideoFrameMetadataListener.class);
|
||||||
player.setVideoFrameMetadataListener(mockVideoFrameMetadataListener);
|
player.setVideoFrameMetadataListener(mockVideoFrameMetadataListener);
|
||||||
|
|
||||||
player.setScrubbingModeEnabled(true);
|
player.setScrubbingModeEnabled(true);
|
||||||
advance(player).untilPendingCommandsAreFullyHandled();
|
advance(player).untilPendingCommandsAreFullyHandled();
|
||||||
player.seekTo(2500);
|
player.seekTo(2500);
|
||||||
@ -123,7 +129,6 @@ public final class ExoPlayerScrubbingTest {
|
|||||||
player.setScrubbingModeEnabled(false);
|
player.setScrubbingModeEnabled(false);
|
||||||
advance(player).untilPendingCommandsAreFullyHandled();
|
advance(player).untilPendingCommandsAreFullyHandled();
|
||||||
player.clearVideoFrameMetadataListener(mockVideoFrameMetadataListener);
|
player.clearVideoFrameMetadataListener(mockVideoFrameMetadataListener);
|
||||||
|
|
||||||
advance(player).untilState(Player.STATE_ENDED);
|
advance(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
surface.release();
|
surface.release();
|
||||||
@ -131,7 +136,6 @@ public final class ExoPlayerScrubbingTest {
|
|||||||
ArgumentCaptor<Long> presentationTimeUsCaptor = ArgumentCaptor.forClass(Long.class);
|
ArgumentCaptor<Long> presentationTimeUsCaptor = ArgumentCaptor.forClass(Long.class);
|
||||||
verify(mockVideoFrameMetadataListener, atLeastOnce())
|
verify(mockVideoFrameMetadataListener, atLeastOnce())
|
||||||
.onVideoFrameAboutToBeRendered(presentationTimeUsCaptor.capture(), anyLong(), any(), any());
|
.onVideoFrameAboutToBeRendered(presentationTimeUsCaptor.capture(), anyLong(), any(), any());
|
||||||
|
|
||||||
assertThat(presentationTimeUsCaptor.getAllValues())
|
assertThat(presentationTimeUsCaptor.getAllValues())
|
||||||
.containsExactly(2_500_000L, 3_500_000L, 4_500_000L)
|
.containsExactly(2_500_000L, 3_500_000L, 4_500_000L)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
@ -148,4 +152,249 @@ public final class ExoPlayerScrubbingTest {
|
|||||||
.containsExactly(2500L, 3000L, 3500L, 4000L, 4500L)
|
.containsExactly(2500L, 3000L, 3500L, 4000L, 4500L)
|
||||||
.inOrder();
|
.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.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.PlayerMessage;
|
import androidx.media3.exoplayer.PlayerMessage;
|
||||||
import androidx.media3.exoplayer.Renderer;
|
import androidx.media3.exoplayer.Renderer;
|
||||||
|
import androidx.media3.exoplayer.ScrubbingModeParameters;
|
||||||
import androidx.media3.exoplayer.SeekParameters;
|
import androidx.media3.exoplayer.SeekParameters;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||||
@ -220,6 +221,16 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScrubbingModeParameters(ScrubbingModeParameters scrubbingModeParameters) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScrubbingModeParameters getScrubbingModeParameters() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setVideoEffects(List<Effect> videoEffects) {
|
public void setVideoEffects(List<Effect> videoEffects) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user