mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Abstract EPII renderer logic into holder class
Move EPII calls to renderers to a separate managing class. PiperOrigin-RevId: 688932712
This commit is contained in:
parent
e677c8dccd
commit
e5133e78f5
@ -19,9 +19,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Util.castNonNull;
|
||||
import static androidx.media3.common.util.Util.msToUs;
|
||||
import static androidx.media3.exoplayer.Renderer.STATE_DISABLED;
|
||||
import static androidx.media3.exoplayer.Renderer.STATE_ENABLED;
|
||||
import static androidx.media3.exoplayer.Renderer.STATE_STARTED;
|
||||
import static androidx.media3.exoplayer.audio.AudioSink.OFFLOAD_MODE_DISABLED;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
@ -59,26 +56,21 @@ import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.drm.DrmSession;
|
||||
import androidx.media3.exoplayer.metadata.MetadataRenderer;
|
||||
import androidx.media3.exoplayer.source.BehindLiveWindowException;
|
||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.text.TextRenderer;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Implements the internal behavior of {@link ExoPlayerImpl}. */
|
||||
@ -181,8 +173,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
*/
|
||||
private static final long PLAYBACK_BUFFER_EMPTY_THRESHOLD_US = 500_000;
|
||||
|
||||
private final Renderer[] renderers;
|
||||
private final Set<Renderer> renderersToReset;
|
||||
private final RendererHolder[] renderers;
|
||||
private final RendererCapabilities[] rendererCapabilities;
|
||||
private final boolean[] rendererReportedReady;
|
||||
private final TrackSelector trackSelector;
|
||||
@ -258,7 +249,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@Nullable PlaybackLooperProvider playbackLooperProvider,
|
||||
PreloadConfiguration preloadConfiguration) {
|
||||
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
||||
this.renderers = renderers;
|
||||
this.trackSelector = trackSelector;
|
||||
this.emptyTrackSelectorResult = emptyTrackSelectorResult;
|
||||
this.loadControl = loadControl;
|
||||
@ -289,16 +279,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@Nullable
|
||||
RendererCapabilities.Listener rendererCapabilitiesListener =
|
||||
trackSelector.getRendererCapabilitiesListener();
|
||||
|
||||
this.renderers = new RendererHolder[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
renderers[i].init(/* index= */ i, playerId, clock);
|
||||
rendererCapabilities[i] = renderers[i].getCapabilities();
|
||||
if (rendererCapabilitiesListener != null) {
|
||||
rendererCapabilities[i].setListener(rendererCapabilitiesListener);
|
||||
}
|
||||
this.renderers[i] = new RendererHolder(renderers[i], /* index= */ i);
|
||||
}
|
||||
mediaClock = new DefaultMediaClock(this, clock);
|
||||
pendingMessages = new ArrayList<>();
|
||||
renderersToReset = Sets.newIdentityHashSet();
|
||||
window = new Timeline.Window();
|
||||
period = new Timeline.Period();
|
||||
trackSelector.init(/* listener= */ this, bandwidthMeter);
|
||||
@ -981,18 +973,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult();
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (trackSelectorResult.isRendererEnabled(i) && renderers[i].getState() == STATE_ENABLED) {
|
||||
renderers[i].start();
|
||||
if (!trackSelectorResult.isRendererEnabled(i)) {
|
||||
continue;
|
||||
}
|
||||
renderers[i].start();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopRenderers() throws ExoPlaybackException {
|
||||
mediaClock.stop();
|
||||
for (Renderer renderer : renderers) {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
ensureStopped(renderer);
|
||||
}
|
||||
for (RendererHolder rendererHolder : renderers) {
|
||||
rendererHolder.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1127,8 +1118,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
playingPeriodHolder.mediaPeriod.discardBuffer(
|
||||
playbackInfo.positionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
if (!isRendererEnabled(renderer)) {
|
||||
RendererHolder renderer = renderers[i];
|
||||
if (renderer.getEnabledRendererCount() == 0) {
|
||||
maybeTriggerOnRendererReadyChanged(/* rendererIndex= */ i, /* allowsPlayback= */ false);
|
||||
continue;
|
||||
}
|
||||
@ -1136,16 +1127,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
// again. The minimum of these values should then be used as the delay before the next
|
||||
// invocation of this method.
|
||||
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
|
||||
// Determine whether the renderer allows playback to continue. Playback can
|
||||
// continue if the renderer is ready or ended. Also continue playback if the renderer is
|
||||
// reading ahead into the next stream or is waiting for the next stream. This is to avoid
|
||||
// getting stuck if tracks in the current period have uneven durations and are still being
|
||||
// read by another renderer. See: https://github.com/google/ExoPlayer/issues/1874.
|
||||
renderersEnded = renderersEnded && renderer.isEnded();
|
||||
// Determine whether the renderer allows playback to continue. Playback can continue if the
|
||||
// renderer is ready or ended. Also continue playback if the renderer is reading ahead into
|
||||
// the next stream or is waiting for the next stream. This is to avoid getting stuck if
|
||||
// tracks in the current period have uneven durations and are still being read by another
|
||||
// renderer. See: https://github.com/google/ExoPlayer/issues/1874.
|
||||
boolean isReadingAhead = playingPeriodHolder.sampleStreams[i] != renderer.getStream();
|
||||
boolean isWaitingForNextStream = !isReadingAhead && renderer.hasReadStreamToEnd();
|
||||
boolean allowsPlayback =
|
||||
isReadingAhead || isWaitingForNextStream || renderer.isReady() || renderer.isEnded();
|
||||
boolean allowsPlayback = renderer.allowsPlayback(playingPeriodHolder);
|
||||
maybeTriggerOnRendererReadyChanged(/* rendererIndex= */ i, allowsPlayback);
|
||||
renderersAllowPlayback = renderersAllowPlayback && allowsPlayback;
|
||||
if (!allowsPlayback) {
|
||||
@ -1198,8 +1186,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
boolean playbackMaybeStuck = false;
|
||||
if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (isRendererEnabled(renderers[i])
|
||||
&& renderers[i].getStream() == playingPeriodHolder.sampleStreams[i]) {
|
||||
if (renderers[i].isReadingFromPeriod(playingPeriodHolder)) {
|
||||
maybeThrowRendererStreamError(/* rendererIndex= */ i);
|
||||
}
|
||||
}
|
||||
@ -1285,15 +1272,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
? READY_MAXIMUM_INTERVAL_MS
|
||||
: BUFFERING_MAXIMUM_INTERVAL_MS;
|
||||
if (dynamicSchedulingEnabled && shouldPlayWhenReady()) {
|
||||
for (Renderer renderer : renderers) {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
wakeUpTimeIntervalMs =
|
||||
min(
|
||||
wakeUpTimeIntervalMs,
|
||||
Util.usToMs(
|
||||
renderer.getDurationToProgressUs(
|
||||
rendererPositionUs, rendererPositionElapsedRealtimeUs)));
|
||||
}
|
||||
for (RendererHolder rendererHolder : renderers) {
|
||||
wakeUpTimeIntervalMs =
|
||||
min(
|
||||
wakeUpTimeIntervalMs,
|
||||
Util.usToMs(
|
||||
rendererHolder.getMinDurationToProgressUs(
|
||||
rendererPositionUs, rendererPositionElapsedRealtimeUs)));
|
||||
}
|
||||
}
|
||||
handler.sendEmptyMessageAtTime(
|
||||
@ -1448,9 +1433,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|| oldPlayingPeriodHolder != newPlayingPeriodHolder
|
||||
|| (newPlayingPeriodHolder != null
|
||||
&& newPlayingPeriodHolder.toRendererTime(periodPositionUs) < 0)) {
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
disableRenderer(/* rendererIndex= */ i);
|
||||
}
|
||||
disableRenderers();
|
||||
if (newPlayingPeriodHolder != null) {
|
||||
// Update the queue and reenable renderers if the requested media period already exists.
|
||||
while (queue.getPlayingPeriod() != newPlayingPeriodHolder) {
|
||||
@ -1494,10 +1477,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
? MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + periodPositionUs
|
||||
: playingMediaPeriod.toRendererTime(periodPositionUs);
|
||||
mediaClock.resetPosition(rendererPositionUs);
|
||||
for (Renderer renderer : renderers) {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
renderer.resetPosition(rendererPositionUs);
|
||||
}
|
||||
for (RendererHolder rendererHolder : renderers) {
|
||||
rendererHolder.resetPosition(rendererPositionUs);
|
||||
}
|
||||
notifyTrackSelectionDiscontinuity();
|
||||
}
|
||||
@ -1517,9 +1498,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
if (this.foregroundMode != foregroundMode) {
|
||||
this.foregroundMode = foregroundMode;
|
||||
if (!foregroundMode) {
|
||||
for (Renderer renderer : renderers) {
|
||||
if (!isRendererEnabled(renderer) && renderersToReset.remove(renderer)) {
|
||||
renderer.reset();
|
||||
for (RendererHolder rendererHolder : renderers) {
|
||||
if (rendererHolder.getEnabledRendererCount() == 0) {
|
||||
rendererHolder.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1572,23 +1553,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
updateRebufferingState(/* isRebuffering= */ false, /* resetLastRebufferRealtimeMs= */ true);
|
||||
mediaClock.stop();
|
||||
rendererPositionUs = MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
try {
|
||||
disableRenderer(/* rendererIndex= */ i);
|
||||
} catch (ExoPlaybackException | RuntimeException e) {
|
||||
// There's nothing we can do.
|
||||
Log.e(TAG, "Disable failed.", e);
|
||||
}
|
||||
try {
|
||||
disableRenderers();
|
||||
} catch (RuntimeException e) {
|
||||
// There's nothing we can do.
|
||||
Log.e(TAG, "Disable failed.", e);
|
||||
}
|
||||
if (resetRenderers) {
|
||||
for (Renderer renderer : renderers) {
|
||||
if (renderersToReset.remove(renderer)) {
|
||||
try {
|
||||
renderer.reset();
|
||||
} catch (RuntimeException e) {
|
||||
// There's nothing we can do.
|
||||
Log.e(TAG, "Reset failed.", e);
|
||||
}
|
||||
for (RendererHolder rendererHolder : renderers) {
|
||||
try {
|
||||
rendererHolder.reset();
|
||||
} catch (RuntimeException e) {
|
||||
// There's nothing we can do.
|
||||
Log.e(TAG, "Reset failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1842,22 +1819,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
nextPendingMessageIndexHint = nextPendingMessageIndex;
|
||||
}
|
||||
|
||||
private void ensureStopped(Renderer renderer) {
|
||||
if (renderer.getState() == STATE_STARTED) {
|
||||
renderer.stop();
|
||||
private void disableRenderers() {
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
disableRenderer(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void disableRenderer(int rendererIndex) throws ExoPlaybackException {
|
||||
Renderer renderer = renderers[rendererIndex];
|
||||
if (!isRendererEnabled(renderer)) {
|
||||
return;
|
||||
}
|
||||
private void disableRenderer(int rendererIndex) {
|
||||
int holderEnabledRendererCount = renderers[rendererIndex].getEnabledRendererCount();
|
||||
renderers[rendererIndex].disable(mediaClock);
|
||||
maybeTriggerOnRendererReadyChanged(rendererIndex, /* allowsPlayback= */ false);
|
||||
mediaClock.onRendererDisabled(renderer);
|
||||
ensureStopped(renderer);
|
||||
renderer.disable();
|
||||
enabledRendererCount--;
|
||||
enabledRendererCount -= holderEnabledRendererCount;
|
||||
}
|
||||
|
||||
private void reselectTracksInternalAndSeek() throws ExoPlaybackException {
|
||||
@ -1925,16 +1897,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
rendererWasEnabledFlags[i] = isRendererEnabled(renderer);
|
||||
SampleStream sampleStream = playingPeriodHolder.sampleStreams[i];
|
||||
rendererWasEnabledFlags[i] = renderers[i].getEnabledRendererCount() > 0;
|
||||
if (rendererWasEnabledFlags[i]) {
|
||||
if (sampleStream != renderer.getStream()) {
|
||||
// We need to disable the renderer.
|
||||
disableRenderer(/* rendererIndex= */ i);
|
||||
if (!renderers[i].isReadingFromPeriod(playingPeriodHolder)) {
|
||||
disableRenderer(i);
|
||||
} else if (streamResetFlags[i]) {
|
||||
// The renderer will continue to consume from its current stream, but needs to be reset.
|
||||
renderer.resetPosition(rendererPositionUs);
|
||||
renderers[i].resetPosition(rendererPositionUs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2000,7 +1968,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
|
||||
: C.TIME_UNSET;
|
||||
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
|
||||
boolean isBufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
|
||||
boolean isBufferedToEnd = (loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal);
|
||||
// Ad loader implementations may only load ad media once playback has nearly reached the ad, but
|
||||
// it is possible for playback to be stuck buffering waiting for this. Therefore, we start
|
||||
// playback regardless of buffered duration if we are waiting for an ad media period to prepare.
|
||||
@ -2062,8 +2030,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
/* releaseMediaSourceList= */ false,
|
||||
/* resetError= */ true);
|
||||
}
|
||||
for (Renderer renderer : renderers) {
|
||||
renderer.setTimeline(timeline);
|
||||
for (RendererHolder rendererHolder : renderers) {
|
||||
rendererHolder.setTimeline(timeline);
|
||||
}
|
||||
if (!periodPositionChanged) {
|
||||
// We can keep the current playing period. Update the rest of the queued periods.
|
||||
@ -2181,12 +2149,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return maxReadPositionUs;
|
||||
}
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (!isRendererEnabled(renderers[i])
|
||||
|| renderers[i].getStream() != readingHolder.sampleStreams[i]) {
|
||||
if (!renderers[i].isReadingFromPeriod(readingHolder)) {
|
||||
// Ignore disabled renderers and renderers with sample streams from previous periods.
|
||||
continue;
|
||||
}
|
||||
long readingPositionUs = renderers[i].getReadingPositionUs();
|
||||
long readingPositionUs = renderers[i].getReadingPositionUs(readingHolder);
|
||||
if (readingPositionUs == C.TIME_END_OF_SOURCE) {
|
||||
return C.TIME_END_OF_SOURCE;
|
||||
} else {
|
||||
@ -2250,19 +2217,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
// intentionally to pause at the end of the period.
|
||||
if (readingPeriodHolder.info.isFinal || pendingPauseAtEndOfPeriod) {
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
|
||||
RendererHolder renderer = renderers[i];
|
||||
if (!renderer.isReadingFromPeriod(readingPeriodHolder)) {
|
||||
continue;
|
||||
}
|
||||
// Defer setting the stream as final until the renderer has actually consumed the whole
|
||||
// stream in case of playlist changes that cause the stream to be no longer final.
|
||||
if (sampleStream != null
|
||||
&& renderer.getStream() == sampleStream
|
||||
&& renderer.hasReadStreamToEnd()) {
|
||||
if (renderer.hasReadStreamToEnd()) {
|
||||
long streamEndPositionUs =
|
||||
readingPeriodHolder.info.durationUs != C.TIME_UNSET
|
||||
&& readingPeriodHolder.info.durationUs != C.TIME_END_OF_SOURCE
|
||||
? readingPeriodHolder.getRendererOffset() + readingPeriodHolder.info.durationUs
|
||||
: C.TIME_UNSET;
|
||||
setCurrentStreamFinal(renderer, streamEndPositionUs);
|
||||
renderer.setCurrentStreamFinal(streamEndPositionUs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2320,8 +2287,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
// it's a no-sample renderer for which rendererOffsetUs should be updated only when
|
||||
// starting to play the next period. Mark the SampleStream as final to play out any
|
||||
// remaining data.
|
||||
setCurrentStreamFinal(
|
||||
renderers[i],
|
||||
renderers[i].setCurrentStreamFinal(
|
||||
/* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime());
|
||||
}
|
||||
}
|
||||
@ -2384,12 +2350,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
||||
boolean needsToWaitForRendererToEnd = false;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
if (!isRendererEnabled(renderer)) {
|
||||
RendererHolder renderer = renderers[i];
|
||||
if (renderer.getEnabledRendererCount() == 0) {
|
||||
continue;
|
||||
}
|
||||
boolean rendererIsReadingOldStream =
|
||||
renderer.getStream() != readingPeriodHolder.sampleStreams[i];
|
||||
boolean rendererIsReadingOldStream = !renderer.isReadingFromPeriod(readingPeriodHolder);
|
||||
boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(i);
|
||||
if (rendererShouldBeEnabled && !rendererIsReadingOldStream) {
|
||||
// All done.
|
||||
@ -2411,7 +2376,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
} else if (renderer.isEnded()) {
|
||||
// The renderer has finished playback, so we can disable it now.
|
||||
disableRenderer(/* rendererIndex= */ i);
|
||||
disableRenderer(i);
|
||||
} else {
|
||||
// We need to wait until rendering finished before disabling the renderer.
|
||||
needsToWaitForRendererToEnd = true;
|
||||
@ -2479,9 +2444,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private void allowRenderersToRenderStartOfStreams() {
|
||||
TrackSelectorResult playingTracks = queue.getPlayingPeriod().getTrackSelectorResult();
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (playingTracks.isRendererEnabled(i)) {
|
||||
renderers[i].enableMayRenderStartOfStream();
|
||||
if (!playingTracks.isRendererEnabled(i)) {
|
||||
continue;
|
||||
}
|
||||
renderers[i].enableMayRenderStartOfStream();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2514,46 +2480,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
|
||||
if (renderer.getStream() != sampleStream
|
||||
|| (sampleStream != null
|
||||
&& !renderer.hasReadStreamToEnd()
|
||||
&& !hasReachedServerSideInsertedAdsTransition(renderer, readingPeriodHolder))) {
|
||||
// The current reading period is still being read by at least one renderer.
|
||||
if (!renderers[i].hasFinishedReadingFromPeriod(readingPeriodHolder)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasReachedServerSideInsertedAdsTransition(
|
||||
Renderer renderer, MediaPeriodHolder reading) {
|
||||
MediaPeriodHolder nextPeriod = reading.getNext();
|
||||
// We can advance the reading period early once we read beyond the transition point in a
|
||||
// server-side inserted ads stream because we know the samples are read from the same underlying
|
||||
// stream. This shortcut is helpful in case the transition point moved and renderers already
|
||||
// read beyond the new transition point. But wait until the next period is actually prepared to
|
||||
// allow a seamless transition.
|
||||
return reading.info.isFollowedByTransitionToSameStream
|
||||
&& nextPeriod.prepared
|
||||
&& (renderer instanceof TextRenderer // [internal: b/181312195]
|
||||
|| renderer instanceof MetadataRenderer
|
||||
|| renderer.getReadingPositionUs() >= nextPeriod.getStartPositionRendererTime());
|
||||
}
|
||||
|
||||
private void setAllRendererStreamsFinal(long streamEndPositionUs) {
|
||||
for (Renderer renderer : renderers) {
|
||||
if (renderer.getStream() != null) {
|
||||
setCurrentStreamFinal(renderer, streamEndPositionUs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setCurrentStreamFinal(Renderer renderer, long streamEndPositionUs) {
|
||||
renderer.setCurrentStreamFinal();
|
||||
if (renderer instanceof TextRenderer) {
|
||||
((TextRenderer) renderer).setFinalStreamEndPositionUs(streamEndPositionUs);
|
||||
for (RendererHolder renderer : renderers) {
|
||||
renderer.setCurrentStreamFinal(streamEndPositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2635,11 +2571,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
playbackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters);
|
||||
}
|
||||
updateTrackSelectionPlaybackSpeed(playbackParameters.speed);
|
||||
for (Renderer renderer : renderers) {
|
||||
if (renderer != null) {
|
||||
renderer.setPlaybackSpeed(
|
||||
currentPlaybackSpeed, /* targetPlaybackSpeed= */ playbackParameters.speed);
|
||||
}
|
||||
for (RendererHolder rendererHolder : renderers) {
|
||||
rendererHolder.setPlaybackSpeed(
|
||||
currentPlaybackSpeed, /* targetPlaybackSpeed= */ playbackParameters.speed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2799,7 +2733,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
// Reset all disabled renderers before enabling any new ones. This makes sure resources released
|
||||
// by the disabled renderers will be available to renderers that are being enabled.
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (!trackSelectorResult.isRendererEnabled(i) && renderersToReset.remove(renderers[i])) {
|
||||
if (!trackSelectorResult.isRendererEnabled(i)) {
|
||||
renderers[i].reset();
|
||||
}
|
||||
}
|
||||
@ -2814,11 +2748,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
private void enableRenderer(int rendererIndex, boolean wasRendererEnabled, long startPositionUs)
|
||||
throws ExoPlaybackException {
|
||||
Renderer renderer = renderers[rendererIndex];
|
||||
if (isRendererEnabled(renderer)) {
|
||||
MediaPeriodHolder periodHolder = queue.getReadingPeriod();
|
||||
RendererHolder renderer = renderers[rendererIndex];
|
||||
if (renderer.getEnabledRendererCount() > 0) {
|
||||
return;
|
||||
}
|
||||
MediaPeriodHolder periodHolder = queue.getReadingPeriod();
|
||||
boolean arePlayingAndReadingTheSamePeriod = periodHolder == queue.getPlayingPeriod();
|
||||
TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
|
||||
RendererConfiguration rendererConfiguration =
|
||||
@ -2831,7 +2765,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
boolean joining = !wasRendererEnabled && playing;
|
||||
// Enable the renderer.
|
||||
enabledRendererCount++;
|
||||
renderersToReset.add(renderer);
|
||||
renderer.enable(
|
||||
rendererConfiguration,
|
||||
formats,
|
||||
@ -2841,7 +2774,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
/* mayRenderStartOfStream= */ arePlayingAndReadingTheSamePeriod,
|
||||
startPositionUs,
|
||||
periodHolder.getRendererOffset(),
|
||||
periodHolder.info.id);
|
||||
periodHolder.info.id,
|
||||
mediaClock);
|
||||
renderer.handleMessage(
|
||||
Renderer.MSG_SET_WAKEUP_LISTENER,
|
||||
new Renderer.WakeupListener() {
|
||||
@ -2857,8 +2791,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mediaClock.onRendererEnabled(renderer);
|
||||
// Start the renderer if playing and the Playing and Reading periods are the same.
|
||||
if (playing && arePlayingAndReadingTheSamePeriod) {
|
||||
renderer.start();
|
||||
@ -2948,7 +2880,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
private void maybeThrowRendererStreamError(int rendererIndex)
|
||||
throws IOException, ExoPlaybackException {
|
||||
Renderer renderer = renderers[rendererIndex];
|
||||
RendererHolder renderer = renderers[rendererIndex];
|
||||
try {
|
||||
renderer.maybeThrowStreamError();
|
||||
} catch (IOException | RuntimeException e) {
|
||||
@ -3442,10 +3374,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return formats;
|
||||
}
|
||||
|
||||
private static boolean isRendererEnabled(Renderer renderer) {
|
||||
return renderer.getState() != STATE_DISABLED;
|
||||
}
|
||||
|
||||
private static final class SeekPosition {
|
||||
|
||||
public final Timeline timeline;
|
||||
|
@ -0,0 +1,446 @@
|
||||
/*
|
||||
* Copyright 2024 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 androidx.media3.exoplayer.Renderer.STATE_DISABLED;
|
||||
import static androidx.media3.exoplayer.Renderer.STATE_ENABLED;
|
||||
import static androidx.media3.exoplayer.Renderer.STATE_STARTED;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.exoplayer.metadata.MetadataRenderer;
|
||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.text.TextRenderer;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Holds a {@link Renderer renderer}. */
|
||||
/* package */ class RendererHolder {
|
||||
private final Renderer renderer;
|
||||
// Index of renderer in renderer list held by the {@link Player}.
|
||||
private final int index;
|
||||
private boolean requiresReset;
|
||||
|
||||
public RendererHolder(Renderer renderer, int index) {
|
||||
this.renderer = renderer;
|
||||
this.index = index;
|
||||
requiresReset = false;
|
||||
}
|
||||
|
||||
public int getEnabledRendererCount() {
|
||||
return isRendererEnabled(renderer) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the track type that the renderer handles.
|
||||
*
|
||||
* @see Renderer#getTrackType()
|
||||
*/
|
||||
public @C.TrackType int getTrackType() {
|
||||
return renderer.getTrackType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns reading position from the {@link Renderer} enabled on the {@link MediaPeriodHolder
|
||||
* media period}.
|
||||
*
|
||||
* <p>Call requires that {@link Renderer} is enabled on the provided {@link MediaPeriodHolder
|
||||
* media period}.
|
||||
*
|
||||
* @param period The {@link MediaPeriodHolder media period}
|
||||
* @return The {@link Renderer#getReadingPositionUs()} from the {@link Renderer} enabled on the
|
||||
* {@link MediaPeriodHolder media period}.
|
||||
*/
|
||||
public long getReadingPositionUs(@Nullable MediaPeriodHolder period) {
|
||||
Assertions.checkState(isReadingFromPeriod(period));
|
||||
return renderer.getReadingPositionUs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link Renderer#hasReadStreamToEnd()}.
|
||||
*
|
||||
* @see Renderer#hasReadStreamToEnd()
|
||||
*/
|
||||
public boolean hasReadStreamToEnd() {
|
||||
return renderer.hasReadStreamToEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to the renderer that the current {@link SampleStream} will be the final one supplied
|
||||
* before it is next disabled or reset.
|
||||
*
|
||||
* @see Renderer#setCurrentStreamFinal()
|
||||
* @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to
|
||||
* render until the end of the current stream.
|
||||
*/
|
||||
public void setCurrentStreamFinal(long streamEndPositionUs) {
|
||||
setCurrentStreamFinal(renderer, streamEndPositionUs);
|
||||
}
|
||||
|
||||
private void setCurrentStreamFinal(Renderer renderer, long streamEndPositionUs) {
|
||||
renderer.setCurrentStreamFinal();
|
||||
if (renderer instanceof TextRenderer) {
|
||||
((TextRenderer) renderer).setFinalStreamEndPositionUs(streamEndPositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current {@link SampleStream} will be the final one supplied before the
|
||||
* renderer is next disabled or reset.
|
||||
*
|
||||
* @see Renderer#isCurrentStreamFinal()
|
||||
*/
|
||||
public boolean isCurrentStreamFinal() {
|
||||
return renderer.isCurrentStreamFinal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link Renderer#replaceStream}.
|
||||
*
|
||||
* @see Renderer#replaceStream
|
||||
*/
|
||||
public void replaceStream(
|
||||
Format[] formats,
|
||||
SampleStream stream,
|
||||
long startPositionUs,
|
||||
long offsetUs,
|
||||
MediaSource.MediaPeriodId mediaPeriodId)
|
||||
throws ExoPlaybackException {
|
||||
renderer.replaceStream(formats, stream, startPositionUs, offsetUs, mediaPeriodId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns minimum amount of playback clock time that must pass in order for the {@link #render}
|
||||
* call to make progress.
|
||||
*
|
||||
* <p>Returns {@code Long.MAX_VALUE} if {@link Renderer renderers} are not enabled.
|
||||
*
|
||||
* @see Renderer#getDurationToProgressUs
|
||||
* @param rendererPositionUs The current render position in microseconds, measured at the start of
|
||||
* the current iteration of the rendering loop.
|
||||
* @param rendererPositionElapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in
|
||||
* microseconds, measured at the start of the current iteration of the rendering loop.
|
||||
* @return Minimum amount of playback clock time that must pass before renderer is able to make
|
||||
* progress.
|
||||
*/
|
||||
public long getMinDurationToProgressUs(
|
||||
long rendererPositionUs, long rendererPositionElapsedRealtimeUs) {
|
||||
return isRendererEnabled(renderer)
|
||||
? renderer.getDurationToProgressUs(rendererPositionUs, rendererPositionElapsedRealtimeUs)
|
||||
: Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Renderer#enableMayRenderStartOfStream} on enabled {@link Renderer renderers}.
|
||||
*
|
||||
* @see Renderer#enableMayRenderStartOfStream
|
||||
*/
|
||||
public void enableMayRenderStartOfStream() {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
renderer.enableMayRenderStartOfStream();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Renderer#setPlaybackSpeed} on the {@link Renderer renderers}.
|
||||
*
|
||||
* @see Renderer#setPlaybackSpeed
|
||||
*/
|
||||
public void setPlaybackSpeed(float currentPlaybackSpeed, float targetPlaybackSpeed)
|
||||
throws ExoPlaybackException {
|
||||
renderer.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Renderer#setTimeline} on the {@link Renderer renderers}.
|
||||
*
|
||||
* @see Renderer#setTimeline
|
||||
*/
|
||||
public void setTimeline(Timeline timeline) {
|
||||
renderer.setTimeline(timeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all renderers have {@link Renderer#isEnded() ended}.
|
||||
*
|
||||
* @see Renderer#isEnded()
|
||||
* @return if all renderers have {@link Renderer#isEnded() ended}.
|
||||
*/
|
||||
public boolean isEnded() {
|
||||
return renderer.isEnded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link Renderer} is enabled on a {@link MediaPeriodHolder media period}.
|
||||
*
|
||||
* @param period The {@link MediaPeriodHolder media period} to check.
|
||||
* @return Whether {@link Renderer} is enabled on a {@link MediaPeriodHolder media period}.
|
||||
*/
|
||||
public boolean isReadingFromPeriod(@Nullable MediaPeriodHolder period) {
|
||||
return getRendererReadingFromPeriod(period) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Renderer} that is enabled on the provided media {@link MediaPeriodHolder
|
||||
* period}.
|
||||
*
|
||||
* <p>Returns null if the renderer is not enabled on the requested period.
|
||||
*
|
||||
* @param period The {@link MediaPeriodHolder period} with which to retrieve the linked {@link
|
||||
* Renderer}
|
||||
* @return {@link Renderer} enabled on the {@link MediaPeriodHolder period} or {@code null} if the
|
||||
* renderer is not enabled on the provided period.
|
||||
*/
|
||||
@Nullable
|
||||
private Renderer getRendererReadingFromPeriod(@Nullable MediaPeriodHolder period) {
|
||||
if (period == null || period.sampleStreams[index] == null) {
|
||||
return null;
|
||||
}
|
||||
if (renderer.getStream() == period.sampleStreams[index]) {
|
||||
return renderer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link Renderer renderers} are still reading a {@link MediaPeriodHolder
|
||||
* media period}.
|
||||
*
|
||||
* @param periodHolder The {@link MediaPeriodHolder media period} to check.
|
||||
* @return true if {@link Renderer renderers} are reading the current reading period.
|
||||
*/
|
||||
public boolean hasFinishedReadingFromPeriod(MediaPeriodHolder periodHolder) {
|
||||
return hasFinishedReadingFromPeriodInternal(periodHolder);
|
||||
}
|
||||
|
||||
private boolean hasFinishedReadingFromPeriodInternal(MediaPeriodHolder readingPeriodHolder) {
|
||||
SampleStream sampleStream = readingPeriodHolder.sampleStreams[index];
|
||||
if (renderer.getStream() != sampleStream
|
||||
|| (sampleStream != null
|
||||
&& !renderer.hasReadStreamToEnd()
|
||||
&& !hasReachedServerSideInsertedAdsTransition(renderer, readingPeriodHolder))) {
|
||||
// The current reading period is still being read by at least one renderer.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasReachedServerSideInsertedAdsTransition(
|
||||
Renderer renderer, MediaPeriodHolder reading) {
|
||||
MediaPeriodHolder nextPeriod = reading.getNext();
|
||||
// We can advance the reading period early once we read beyond the transition point in a
|
||||
// server-side inserted ads stream because we know the samples are read from the same underlying
|
||||
// stream. This shortcut is helpful in case the transition point moved and renderers already
|
||||
// read beyond the new transition point. But wait until the next period is actually prepared to
|
||||
// allow a seamless transition.
|
||||
return reading.info.isFollowedByTransitionToSameStream
|
||||
&& nextPeriod != null
|
||||
&& nextPeriod.prepared
|
||||
&& (renderer instanceof TextRenderer // [internal: b/181312195]
|
||||
|| renderer instanceof MetadataRenderer
|
||||
|| renderer.getReadingPositionUs() >= nextPeriod.getStartPositionRendererTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Renderer#render} on all enabled {@link Renderer renderers}.
|
||||
*
|
||||
* @param rendererPositionUs The current media time in microseconds, measured at the start of the
|
||||
* current iteration of the rendering loop.
|
||||
* @param rendererPositionElapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in
|
||||
* microseconds, measured at the start of the current iteration of the rendering loop.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
public void render(long rendererPositionUs, long rendererPositionElapsedRealtimeUs)
|
||||
throws ExoPlaybackException {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the renderers allow playback to continue.
|
||||
*
|
||||
* <p>Determine whether the renderer allows playback to continue. Playback can continue if the
|
||||
* renderer is ready or ended. Also continue playback if the renderer is reading ahead into the
|
||||
* next stream or is waiting for the next stream. This is to avoid getting stuck if tracks in the
|
||||
* current period have uneven durations and are still being read by another renderer. See:
|
||||
* https://github.com/google/ExoPlayer/issues/1874.
|
||||
*
|
||||
* @param playingPeriodHolder The currently playing media {@link MediaPeriodHolder period}.
|
||||
* @return whether renderer allows playback.
|
||||
*/
|
||||
public boolean allowsPlayback(MediaPeriodHolder playingPeriodHolder) throws IOException {
|
||||
return allowsPlayback(renderer, playingPeriodHolder);
|
||||
}
|
||||
|
||||
private boolean allowsPlayback(Renderer renderer, MediaPeriodHolder playingPeriodHolder) {
|
||||
boolean isReadingAhead = playingPeriodHolder.sampleStreams[index] != renderer.getStream();
|
||||
boolean isWaitingForNextStream = !isReadingAhead && renderer.hasReadStreamToEnd();
|
||||
return isReadingAhead || isWaitingForNextStream || renderer.isReady() || renderer.isEnded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {Renderer#maybeThrowStreamError}.
|
||||
*
|
||||
* @see Renderer#maybeThrowStreamError()
|
||||
*/
|
||||
public void maybeThrowStreamError() throws IOException {
|
||||
renderer.maybeThrowStreamError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Renderer#start()} on all enabled {@link Renderer renderers}.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
public void start() throws ExoPlaybackException {
|
||||
if (renderer.getState() == STATE_ENABLED) {
|
||||
renderer.start();
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls {@link Renderer#stop()} on all enabled {@link Renderer renderers}. */
|
||||
public void stop() {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
ensureStopped(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureStopped(Renderer renderer) {
|
||||
if (renderer.getState() == STATE_STARTED) {
|
||||
renderer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the renderer to consume from the specified {@link SampleStream}.
|
||||
*
|
||||
* @see Renderer#enable
|
||||
* @param configuration The renderer configuration.
|
||||
* @param formats The enabled formats.
|
||||
* @param stream The {@link SampleStream} from which the renderer should consume.
|
||||
* @param positionUs The player's current position.
|
||||
* @param joining Whether this renderer is being enabled to join an ongoing playback.
|
||||
* @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
|
||||
* stream even if the state is not {@link Renderer#STATE_STARTED} yet.
|
||||
* @param startPositionUs The start position of the stream in renderer time (microseconds).
|
||||
* @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before
|
||||
* they are rendered.
|
||||
* @param mediaPeriodId The {@link MediaSource.MediaPeriodId} of the {@link MediaPeriod} producing
|
||||
* the {@code stream}.
|
||||
* @param mediaClock The {@link DefaultMediaClock} with which to call {@link
|
||||
* DefaultMediaClock#onRendererEnabled(Renderer)}.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
public void enable(
|
||||
RendererConfiguration configuration,
|
||||
Format[] formats,
|
||||
SampleStream stream,
|
||||
long positionUs,
|
||||
boolean joining,
|
||||
boolean mayRenderStartOfStream,
|
||||
long startPositionUs,
|
||||
long offsetUs,
|
||||
MediaSource.MediaPeriodId mediaPeriodId,
|
||||
DefaultMediaClock mediaClock)
|
||||
throws ExoPlaybackException {
|
||||
requiresReset = true;
|
||||
renderer.enable(
|
||||
configuration,
|
||||
formats,
|
||||
stream,
|
||||
positionUs,
|
||||
joining,
|
||||
mayRenderStartOfStream,
|
||||
startPositionUs,
|
||||
offsetUs,
|
||||
mediaPeriodId);
|
||||
mediaClock.onRendererEnabled(renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link Renderer#handleMessage} on the {@link Renderer}.
|
||||
*
|
||||
* @see Renderer#handleMessage(int, Object)
|
||||
*/
|
||||
public void handleMessage(@Renderer.MessageType int messageType, @Nullable Object message)
|
||||
throws ExoPlaybackException {
|
||||
renderer.handleMessage(messageType, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and disables all {@link Renderer renderers}.
|
||||
*
|
||||
* @param mediaClock To call {@link DefaultMediaClock#onRendererDisabled} if disabling a {@link
|
||||
* Renderer}.
|
||||
*/
|
||||
public void disable(DefaultMediaClock mediaClock) {
|
||||
disableRenderer(renderer, mediaClock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a {@link Renderer} if its enabled.
|
||||
*
|
||||
* <p>The {@link DefaultMediaClock#onRendererDisabled} callback will be invoked if the renderer is
|
||||
* disabled.
|
||||
*
|
||||
* @param renderer The {@link Renderer} to disable.
|
||||
* @param mediaClock The {@link DefaultMediaClock} to invoke {@link
|
||||
* DefaultMediaClock#onRendererDisabled onRendererDisabled} with the provided {@code
|
||||
* renderer}.
|
||||
*/
|
||||
private void disableRenderer(Renderer renderer, DefaultMediaClock mediaClock) {
|
||||
if (!isRendererEnabled(renderer)) {
|
||||
return;
|
||||
}
|
||||
mediaClock.onRendererDisabled(renderer);
|
||||
ensureStopped(renderer);
|
||||
renderer.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Renderer#resetPosition} on the {@link Renderer} if its enabled.
|
||||
*
|
||||
* @see Renderer#resetPosition
|
||||
*/
|
||||
public void resetPosition(long positionUs) throws ExoPlaybackException {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
renderer.resetPosition(positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls {@link Renderer#reset()} on all renderers that must be reset. */
|
||||
public void reset() {
|
||||
if (requiresReset) {
|
||||
renderer.reset();
|
||||
requiresReset = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls {@link Renderer#release()} on all {@link Renderer renderers}. */
|
||||
public void release() {
|
||||
renderer.release();
|
||||
requiresReset = false;
|
||||
}
|
||||
|
||||
private static boolean isRendererEnabled(Renderer renderer) {
|
||||
return renderer.getState() != STATE_DISABLED;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user