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.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
import static androidx.media3.common.util.Util.msToUs;
|
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 androidx.media3.exoplayer.audio.AudioSink.OFFLOAD_MODE_DISABLED;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
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.AnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||||
import androidx.media3.exoplayer.drm.DrmSession;
|
import androidx.media3.exoplayer.drm.DrmSession;
|
||||||
import androidx.media3.exoplayer.metadata.MetadataRenderer;
|
|
||||||
import androidx.media3.exoplayer.source.BehindLiveWindowException;
|
import androidx.media3.exoplayer.source.BehindLiveWindowException;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||||
import androidx.media3.exoplayer.source.SampleStream;
|
|
||||||
import androidx.media3.exoplayer.source.ShuffleOrder;
|
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||||
import androidx.media3.exoplayer.text.TextRenderer;
|
|
||||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||||
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
||||||
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
|
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
|
||||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/** Implements the internal behavior of {@link ExoPlayerImpl}. */
|
/** 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 static final long PLAYBACK_BUFFER_EMPTY_THRESHOLD_US = 500_000;
|
||||||
|
|
||||||
private final Renderer[] renderers;
|
private final RendererHolder[] renderers;
|
||||||
private final Set<Renderer> renderersToReset;
|
|
||||||
private final RendererCapabilities[] rendererCapabilities;
|
private final RendererCapabilities[] rendererCapabilities;
|
||||||
private final boolean[] rendererReportedReady;
|
private final boolean[] rendererReportedReady;
|
||||||
private final TrackSelector trackSelector;
|
private final TrackSelector trackSelector;
|
||||||
@ -258,7 +249,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
@Nullable PlaybackLooperProvider playbackLooperProvider,
|
@Nullable PlaybackLooperProvider playbackLooperProvider,
|
||||||
PreloadConfiguration preloadConfiguration) {
|
PreloadConfiguration preloadConfiguration) {
|
||||||
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
||||||
this.renderers = renderers;
|
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
this.emptyTrackSelectorResult = emptyTrackSelectorResult;
|
this.emptyTrackSelectorResult = emptyTrackSelectorResult;
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
@ -289,16 +279,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
@Nullable
|
@Nullable
|
||||||
RendererCapabilities.Listener rendererCapabilitiesListener =
|
RendererCapabilities.Listener rendererCapabilitiesListener =
|
||||||
trackSelector.getRendererCapabilitiesListener();
|
trackSelector.getRendererCapabilitiesListener();
|
||||||
|
|
||||||
|
this.renderers = new RendererHolder[renderers.length];
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
renderers[i].init(/* index= */ i, playerId, clock);
|
renderers[i].init(/* index= */ i, playerId, clock);
|
||||||
rendererCapabilities[i] = renderers[i].getCapabilities();
|
rendererCapabilities[i] = renderers[i].getCapabilities();
|
||||||
if (rendererCapabilitiesListener != null) {
|
if (rendererCapabilitiesListener != null) {
|
||||||
rendererCapabilities[i].setListener(rendererCapabilitiesListener);
|
rendererCapabilities[i].setListener(rendererCapabilitiesListener);
|
||||||
}
|
}
|
||||||
|
this.renderers[i] = new RendererHolder(renderers[i], /* index= */ i);
|
||||||
}
|
}
|
||||||
mediaClock = new DefaultMediaClock(this, clock);
|
mediaClock = new DefaultMediaClock(this, clock);
|
||||||
pendingMessages = new ArrayList<>();
|
pendingMessages = new ArrayList<>();
|
||||||
renderersToReset = Sets.newIdentityHashSet();
|
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
trackSelector.init(/* listener= */ this, bandwidthMeter);
|
trackSelector.init(/* listener= */ this, bandwidthMeter);
|
||||||
@ -981,18 +973,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult();
|
TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult();
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
if (trackSelectorResult.isRendererEnabled(i) && renderers[i].getState() == STATE_ENABLED) {
|
if (!trackSelectorResult.isRendererEnabled(i)) {
|
||||||
renderers[i].start();
|
continue;
|
||||||
}
|
}
|
||||||
|
renderers[i].start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopRenderers() throws ExoPlaybackException {
|
private void stopRenderers() throws ExoPlaybackException {
|
||||||
mediaClock.stop();
|
mediaClock.stop();
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder rendererHolder : renderers) {
|
||||||
if (isRendererEnabled(renderer)) {
|
rendererHolder.stop();
|
||||||
ensureStopped(renderer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1127,8 +1118,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
playingPeriodHolder.mediaPeriod.discardBuffer(
|
playingPeriodHolder.mediaPeriod.discardBuffer(
|
||||||
playbackInfo.positionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
|
playbackInfo.positionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
RendererHolder renderer = renderers[i];
|
||||||
if (!isRendererEnabled(renderer)) {
|
if (renderer.getEnabledRendererCount() == 0) {
|
||||||
maybeTriggerOnRendererReadyChanged(/* rendererIndex= */ i, /* allowsPlayback= */ false);
|
maybeTriggerOnRendererReadyChanged(/* rendererIndex= */ i, /* allowsPlayback= */ false);
|
||||||
continue;
|
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
|
// again. The minimum of these values should then be used as the delay before the next
|
||||||
// invocation of this method.
|
// invocation of this method.
|
||||||
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
|
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();
|
renderersEnded = renderersEnded && renderer.isEnded();
|
||||||
// Determine whether the renderer allows playback to continue. Playback can continue if the
|
boolean allowsPlayback = renderer.allowsPlayback(playingPeriodHolder);
|
||||||
// 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();
|
|
||||||
maybeTriggerOnRendererReadyChanged(/* rendererIndex= */ i, allowsPlayback);
|
maybeTriggerOnRendererReadyChanged(/* rendererIndex= */ i, allowsPlayback);
|
||||||
renderersAllowPlayback = renderersAllowPlayback && allowsPlayback;
|
renderersAllowPlayback = renderersAllowPlayback && allowsPlayback;
|
||||||
if (!allowsPlayback) {
|
if (!allowsPlayback) {
|
||||||
@ -1198,8 +1186,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
boolean playbackMaybeStuck = false;
|
boolean playbackMaybeStuck = false;
|
||||||
if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
if (isRendererEnabled(renderers[i])
|
if (renderers[i].isReadingFromPeriod(playingPeriodHolder)) {
|
||||||
&& renderers[i].getStream() == playingPeriodHolder.sampleStreams[i]) {
|
|
||||||
maybeThrowRendererStreamError(/* rendererIndex= */ i);
|
maybeThrowRendererStreamError(/* rendererIndex= */ i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1285,15 +1272,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
? READY_MAXIMUM_INTERVAL_MS
|
? READY_MAXIMUM_INTERVAL_MS
|
||||||
: BUFFERING_MAXIMUM_INTERVAL_MS;
|
: BUFFERING_MAXIMUM_INTERVAL_MS;
|
||||||
if (dynamicSchedulingEnabled && shouldPlayWhenReady()) {
|
if (dynamicSchedulingEnabled && shouldPlayWhenReady()) {
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder rendererHolder : renderers) {
|
||||||
if (isRendererEnabled(renderer)) {
|
wakeUpTimeIntervalMs =
|
||||||
wakeUpTimeIntervalMs =
|
min(
|
||||||
min(
|
wakeUpTimeIntervalMs,
|
||||||
wakeUpTimeIntervalMs,
|
Util.usToMs(
|
||||||
Util.usToMs(
|
rendererHolder.getMinDurationToProgressUs(
|
||||||
renderer.getDurationToProgressUs(
|
rendererPositionUs, rendererPositionElapsedRealtimeUs)));
|
||||||
rendererPositionUs, rendererPositionElapsedRealtimeUs)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler.sendEmptyMessageAtTime(
|
handler.sendEmptyMessageAtTime(
|
||||||
@ -1448,9 +1433,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|| oldPlayingPeriodHolder != newPlayingPeriodHolder
|
|| oldPlayingPeriodHolder != newPlayingPeriodHolder
|
||||||
|| (newPlayingPeriodHolder != null
|
|| (newPlayingPeriodHolder != null
|
||||||
&& newPlayingPeriodHolder.toRendererTime(periodPositionUs) < 0)) {
|
&& newPlayingPeriodHolder.toRendererTime(periodPositionUs) < 0)) {
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
disableRenderers();
|
||||||
disableRenderer(/* rendererIndex= */ i);
|
|
||||||
}
|
|
||||||
if (newPlayingPeriodHolder != null) {
|
if (newPlayingPeriodHolder != null) {
|
||||||
// Update the queue and reenable renderers if the requested media period already exists.
|
// Update the queue and reenable renderers if the requested media period already exists.
|
||||||
while (queue.getPlayingPeriod() != newPlayingPeriodHolder) {
|
while (queue.getPlayingPeriod() != newPlayingPeriodHolder) {
|
||||||
@ -1494,10 +1477,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
? MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + periodPositionUs
|
? MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + periodPositionUs
|
||||||
: playingMediaPeriod.toRendererTime(periodPositionUs);
|
: playingMediaPeriod.toRendererTime(periodPositionUs);
|
||||||
mediaClock.resetPosition(rendererPositionUs);
|
mediaClock.resetPosition(rendererPositionUs);
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder rendererHolder : renderers) {
|
||||||
if (isRendererEnabled(renderer)) {
|
rendererHolder.resetPosition(rendererPositionUs);
|
||||||
renderer.resetPosition(rendererPositionUs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
notifyTrackSelectionDiscontinuity();
|
notifyTrackSelectionDiscontinuity();
|
||||||
}
|
}
|
||||||
@ -1517,9 +1498,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
if (this.foregroundMode != foregroundMode) {
|
if (this.foregroundMode != foregroundMode) {
|
||||||
this.foregroundMode = foregroundMode;
|
this.foregroundMode = foregroundMode;
|
||||||
if (!foregroundMode) {
|
if (!foregroundMode) {
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder rendererHolder : renderers) {
|
||||||
if (!isRendererEnabled(renderer) && renderersToReset.remove(renderer)) {
|
if (rendererHolder.getEnabledRendererCount() == 0) {
|
||||||
renderer.reset();
|
rendererHolder.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1572,23 +1553,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
updateRebufferingState(/* isRebuffering= */ false, /* resetLastRebufferRealtimeMs= */ true);
|
updateRebufferingState(/* isRebuffering= */ false, /* resetLastRebufferRealtimeMs= */ true);
|
||||||
mediaClock.stop();
|
mediaClock.stop();
|
||||||
rendererPositionUs = MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US;
|
rendererPositionUs = MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
try {
|
||||||
try {
|
disableRenderers();
|
||||||
disableRenderer(/* rendererIndex= */ i);
|
} catch (RuntimeException e) {
|
||||||
} catch (ExoPlaybackException | RuntimeException e) {
|
// There's nothing we can do.
|
||||||
// There's nothing we can do.
|
Log.e(TAG, "Disable failed.", e);
|
||||||
Log.e(TAG, "Disable failed.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (resetRenderers) {
|
if (resetRenderers) {
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder rendererHolder : renderers) {
|
||||||
if (renderersToReset.remove(renderer)) {
|
try {
|
||||||
try {
|
rendererHolder.reset();
|
||||||
renderer.reset();
|
} catch (RuntimeException e) {
|
||||||
} catch (RuntimeException e) {
|
// There's nothing we can do.
|
||||||
// There's nothing we can do.
|
Log.e(TAG, "Reset failed.", e);
|
||||||
Log.e(TAG, "Reset failed.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1842,22 +1819,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
nextPendingMessageIndexHint = nextPendingMessageIndex;
|
nextPendingMessageIndexHint = nextPendingMessageIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureStopped(Renderer renderer) {
|
private void disableRenderers() {
|
||||||
if (renderer.getState() == STATE_STARTED) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
renderer.stop();
|
disableRenderer(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disableRenderer(int rendererIndex) throws ExoPlaybackException {
|
private void disableRenderer(int rendererIndex) {
|
||||||
Renderer renderer = renderers[rendererIndex];
|
int holderEnabledRendererCount = renderers[rendererIndex].getEnabledRendererCount();
|
||||||
if (!isRendererEnabled(renderer)) {
|
renderers[rendererIndex].disable(mediaClock);
|
||||||
return;
|
|
||||||
}
|
|
||||||
maybeTriggerOnRendererReadyChanged(rendererIndex, /* allowsPlayback= */ false);
|
maybeTriggerOnRendererReadyChanged(rendererIndex, /* allowsPlayback= */ false);
|
||||||
mediaClock.onRendererDisabled(renderer);
|
enabledRendererCount -= holderEnabledRendererCount;
|
||||||
ensureStopped(renderer);
|
|
||||||
renderer.disable();
|
|
||||||
enabledRendererCount--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reselectTracksInternalAndSeek() throws ExoPlaybackException {
|
private void reselectTracksInternalAndSeek() throws ExoPlaybackException {
|
||||||
@ -1925,16 +1897,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
rendererWasEnabledFlags[i] = renderers[i].getEnabledRendererCount() > 0;
|
||||||
rendererWasEnabledFlags[i] = isRendererEnabled(renderer);
|
|
||||||
SampleStream sampleStream = playingPeriodHolder.sampleStreams[i];
|
|
||||||
if (rendererWasEnabledFlags[i]) {
|
if (rendererWasEnabledFlags[i]) {
|
||||||
if (sampleStream != renderer.getStream()) {
|
if (!renderers[i].isReadingFromPeriod(playingPeriodHolder)) {
|
||||||
// We need to disable the renderer.
|
disableRenderer(i);
|
||||||
disableRenderer(/* rendererIndex= */ i);
|
|
||||||
} else if (streamResetFlags[i]) {
|
} else if (streamResetFlags[i]) {
|
||||||
// The renderer will continue to consume from its current stream, but needs to be reset.
|
renderers[i].resetPosition(rendererPositionUs);
|
||||||
renderer.resetPosition(rendererPositionUs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2000,7 +1968,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
|
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
|
||||||
: C.TIME_UNSET;
|
: C.TIME_UNSET;
|
||||||
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
|
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
|
// 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
|
// 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.
|
// 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,
|
/* releaseMediaSourceList= */ false,
|
||||||
/* resetError= */ true);
|
/* resetError= */ true);
|
||||||
}
|
}
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder rendererHolder : renderers) {
|
||||||
renderer.setTimeline(timeline);
|
rendererHolder.setTimeline(timeline);
|
||||||
}
|
}
|
||||||
if (!periodPositionChanged) {
|
if (!periodPositionChanged) {
|
||||||
// We can keep the current playing period. Update the rest of the queued periods.
|
// 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;
|
return maxReadPositionUs;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
if (!isRendererEnabled(renderers[i])
|
if (!renderers[i].isReadingFromPeriod(readingHolder)) {
|
||||||
|| renderers[i].getStream() != readingHolder.sampleStreams[i]) {
|
|
||||||
// Ignore disabled renderers and renderers with sample streams from previous periods.
|
// Ignore disabled renderers and renderers with sample streams from previous periods.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
long readingPositionUs = renderers[i].getReadingPositionUs();
|
long readingPositionUs = renderers[i].getReadingPositionUs(readingHolder);
|
||||||
if (readingPositionUs == C.TIME_END_OF_SOURCE) {
|
if (readingPositionUs == C.TIME_END_OF_SOURCE) {
|
||||||
return C.TIME_END_OF_SOURCE;
|
return C.TIME_END_OF_SOURCE;
|
||||||
} else {
|
} else {
|
||||||
@ -2250,19 +2217,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
// intentionally to pause at the end of the period.
|
// intentionally to pause at the end of the period.
|
||||||
if (readingPeriodHolder.info.isFinal || pendingPauseAtEndOfPeriod) {
|
if (readingPeriodHolder.info.isFinal || pendingPauseAtEndOfPeriod) {
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
RendererHolder renderer = renderers[i];
|
||||||
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
|
if (!renderer.isReadingFromPeriod(readingPeriodHolder)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Defer setting the stream as final until the renderer has actually consumed the whole
|
// 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.
|
// stream in case of playlist changes that cause the stream to be no longer final.
|
||||||
if (sampleStream != null
|
if (renderer.hasReadStreamToEnd()) {
|
||||||
&& renderer.getStream() == sampleStream
|
|
||||||
&& renderer.hasReadStreamToEnd()) {
|
|
||||||
long streamEndPositionUs =
|
long streamEndPositionUs =
|
||||||
readingPeriodHolder.info.durationUs != C.TIME_UNSET
|
readingPeriodHolder.info.durationUs != C.TIME_UNSET
|
||||||
&& readingPeriodHolder.info.durationUs != C.TIME_END_OF_SOURCE
|
&& readingPeriodHolder.info.durationUs != C.TIME_END_OF_SOURCE
|
||||||
? readingPeriodHolder.getRendererOffset() + readingPeriodHolder.info.durationUs
|
? readingPeriodHolder.getRendererOffset() + readingPeriodHolder.info.durationUs
|
||||||
: C.TIME_UNSET;
|
: 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
|
// 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
|
// starting to play the next period. Mark the SampleStream as final to play out any
|
||||||
// remaining data.
|
// remaining data.
|
||||||
setCurrentStreamFinal(
|
renderers[i].setCurrentStreamFinal(
|
||||||
renderers[i],
|
|
||||||
/* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime());
|
/* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2384,12 +2350,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
||||||
boolean needsToWaitForRendererToEnd = false;
|
boolean needsToWaitForRendererToEnd = false;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
RendererHolder renderer = renderers[i];
|
||||||
if (!isRendererEnabled(renderer)) {
|
if (renderer.getEnabledRendererCount() == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
boolean rendererIsReadingOldStream =
|
boolean rendererIsReadingOldStream = !renderer.isReadingFromPeriod(readingPeriodHolder);
|
||||||
renderer.getStream() != readingPeriodHolder.sampleStreams[i];
|
|
||||||
boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(i);
|
boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(i);
|
||||||
if (rendererShouldBeEnabled && !rendererIsReadingOldStream) {
|
if (rendererShouldBeEnabled && !rendererIsReadingOldStream) {
|
||||||
// All done.
|
// All done.
|
||||||
@ -2411,7 +2376,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
} else if (renderer.isEnded()) {
|
} else if (renderer.isEnded()) {
|
||||||
// The renderer has finished playback, so we can disable it now.
|
// The renderer has finished playback, so we can disable it now.
|
||||||
disableRenderer(/* rendererIndex= */ i);
|
disableRenderer(i);
|
||||||
} else {
|
} else {
|
||||||
// We need to wait until rendering finished before disabling the renderer.
|
// We need to wait until rendering finished before disabling the renderer.
|
||||||
needsToWaitForRendererToEnd = true;
|
needsToWaitForRendererToEnd = true;
|
||||||
@ -2479,9 +2444,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private void allowRenderersToRenderStartOfStreams() {
|
private void allowRenderersToRenderStartOfStreams() {
|
||||||
TrackSelectorResult playingTracks = queue.getPlayingPeriod().getTrackSelectorResult();
|
TrackSelectorResult playingTracks = queue.getPlayingPeriod().getTrackSelectorResult();
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
if (playingTracks.isRendererEnabled(i)) {
|
if (!playingTracks.isRendererEnabled(i)) {
|
||||||
renderers[i].enableMayRenderStartOfStream();
|
continue;
|
||||||
}
|
}
|
||||||
|
renderers[i].enableMayRenderStartOfStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2514,46 +2480,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
if (!renderers[i].hasFinishedReadingFromPeriod(readingPeriodHolder)) {
|
||||||
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.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
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) {
|
private void setAllRendererStreamsFinal(long streamEndPositionUs) {
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder renderer : renderers) {
|
||||||
if (renderer.getStream() != null) {
|
renderer.setCurrentStreamFinal(streamEndPositionUs);
|
||||||
setCurrentStreamFinal(renderer, streamEndPositionUs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCurrentStreamFinal(Renderer renderer, long streamEndPositionUs) {
|
|
||||||
renderer.setCurrentStreamFinal();
|
|
||||||
if (renderer instanceof TextRenderer) {
|
|
||||||
((TextRenderer) renderer).setFinalStreamEndPositionUs(streamEndPositionUs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2635,11 +2571,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
playbackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters);
|
playbackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
updateTrackSelectionPlaybackSpeed(playbackParameters.speed);
|
updateTrackSelectionPlaybackSpeed(playbackParameters.speed);
|
||||||
for (Renderer renderer : renderers) {
|
for (RendererHolder rendererHolder : renderers) {
|
||||||
if (renderer != null) {
|
rendererHolder.setPlaybackSpeed(
|
||||||
renderer.setPlaybackSpeed(
|
currentPlaybackSpeed, /* targetPlaybackSpeed= */ playbackParameters.speed);
|
||||||
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
|
// 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.
|
// by the disabled renderers will be available to renderers that are being enabled.
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
if (!trackSelectorResult.isRendererEnabled(i) && renderersToReset.remove(renderers[i])) {
|
if (!trackSelectorResult.isRendererEnabled(i)) {
|
||||||
renderers[i].reset();
|
renderers[i].reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2814,11 +2748,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
private void enableRenderer(int rendererIndex, boolean wasRendererEnabled, long startPositionUs)
|
private void enableRenderer(int rendererIndex, boolean wasRendererEnabled, long startPositionUs)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
Renderer renderer = renderers[rendererIndex];
|
MediaPeriodHolder periodHolder = queue.getReadingPeriod();
|
||||||
if (isRendererEnabled(renderer)) {
|
RendererHolder renderer = renderers[rendererIndex];
|
||||||
|
if (renderer.getEnabledRendererCount() > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MediaPeriodHolder periodHolder = queue.getReadingPeriod();
|
|
||||||
boolean arePlayingAndReadingTheSamePeriod = periodHolder == queue.getPlayingPeriod();
|
boolean arePlayingAndReadingTheSamePeriod = periodHolder == queue.getPlayingPeriod();
|
||||||
TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
|
TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
|
||||||
RendererConfiguration rendererConfiguration =
|
RendererConfiguration rendererConfiguration =
|
||||||
@ -2831,7 +2765,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
boolean joining = !wasRendererEnabled && playing;
|
boolean joining = !wasRendererEnabled && playing;
|
||||||
// Enable the renderer.
|
// Enable the renderer.
|
||||||
enabledRendererCount++;
|
enabledRendererCount++;
|
||||||
renderersToReset.add(renderer);
|
|
||||||
renderer.enable(
|
renderer.enable(
|
||||||
rendererConfiguration,
|
rendererConfiguration,
|
||||||
formats,
|
formats,
|
||||||
@ -2841,7 +2774,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
/* mayRenderStartOfStream= */ arePlayingAndReadingTheSamePeriod,
|
/* mayRenderStartOfStream= */ arePlayingAndReadingTheSamePeriod,
|
||||||
startPositionUs,
|
startPositionUs,
|
||||||
periodHolder.getRendererOffset(),
|
periodHolder.getRendererOffset(),
|
||||||
periodHolder.info.id);
|
periodHolder.info.id,
|
||||||
|
mediaClock);
|
||||||
renderer.handleMessage(
|
renderer.handleMessage(
|
||||||
Renderer.MSG_SET_WAKEUP_LISTENER,
|
Renderer.MSG_SET_WAKEUP_LISTENER,
|
||||||
new Renderer.WakeupListener() {
|
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.
|
// Start the renderer if playing and the Playing and Reading periods are the same.
|
||||||
if (playing && arePlayingAndReadingTheSamePeriod) {
|
if (playing && arePlayingAndReadingTheSamePeriod) {
|
||||||
renderer.start();
|
renderer.start();
|
||||||
@ -2948,7 +2880,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
private void maybeThrowRendererStreamError(int rendererIndex)
|
private void maybeThrowRendererStreamError(int rendererIndex)
|
||||||
throws IOException, ExoPlaybackException {
|
throws IOException, ExoPlaybackException {
|
||||||
Renderer renderer = renderers[rendererIndex];
|
RendererHolder renderer = renderers[rendererIndex];
|
||||||
try {
|
try {
|
||||||
renderer.maybeThrowStreamError();
|
renderer.maybeThrowStreamError();
|
||||||
} catch (IOException | RuntimeException e) {
|
} catch (IOException | RuntimeException e) {
|
||||||
@ -3442,10 +3374,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
return formats;
|
return formats;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isRendererEnabled(Renderer renderer) {
|
|
||||||
return renderer.getState() != STATE_DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class SeekPosition {
|
private static final class SeekPosition {
|
||||||
|
|
||||||
public final Timeline timeline;
|
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