From f150856567747be1f6995b36a40ca133e57efff0 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 30 Oct 2017 10:11:00 -0700 Subject: [PATCH] Fix masking step 1 1. Move Timeline/Manifest into PlaybackInfo 2. Don't update externally visible Timeline/Manifest during preparation 3. Ignore MSG_POSITION_DISCONTINUITY during preparation 4. Correctly set masking variables at start of preparation, and use them Once this change goes in, PlaybackInfo will contain timeline, manifest and position, which should always be self-consistent with one another. The next step would then be to move a bunch of logic in ExoPlayerImpl that derives state from timeline and position into PlaybackInfo, and split that into its own top level class that can be easily tested to make sure it never IndexOutOfBounds. I think we could also replace the masking variables and instead just assign a new PlaybackInfo to the playbackInfo variable whenever we're doing something that requires masking. This should be possible because we no longer update playbackInfo whenever we have pending acks. It would require allowing PlaybackInfo to mask the window position internally when the timeline is empty, but I think this is ok, and again is something we could test pretty easily. Issue: #3362 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=173909791 --- .../android/exoplayer2/ExoPlayerTest.java | 2 +- .../android/exoplayer2/ExoPlayerImpl.java | 115 +++++++----- .../exoplayer2/ExoPlayerImplInternal.java | 177 +++++++++--------- 3 files changed, 157 insertions(+), 137 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index eb9dc6f9d8..56d5f05d00 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -56,7 +56,7 @@ public final class ExoPlayerTest extends TestCase { .setTimeline(timeline).setRenderers(renderer) .build().start().blockUntilEnded(TIMEOUT_MS); testRunner.assertPositionDiscontinuityCount(0); - testRunner.assertTimelinesEqual(timeline); + testRunner.assertTimelinesEqual(); assertEquals(0, renderer.formatReadCount); assertEquals(0, renderer.bufferReadCount); assertFalse(renderer.isEnded); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 7f9b25b606..92f5d0c844 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -22,7 +22,6 @@ import android.os.Message; import android.support.annotation.Nullable; import android.util.Log; import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; -import com.google.android.exoplayer2.ExoPlayerImplInternal.SourceInfo; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -105,9 +104,9 @@ import java.util.concurrent.CopyOnWriteArraySet; ExoPlayerImpl.this.handleEvent(msg); } }; - playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0, 0); + playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(timeline, manifest, 0, 0); internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady, - repeatMode, shuffleModeEnabled, eventHandler, playbackInfo, this); + repeatMode, shuffleModeEnabled, eventHandler, this); } @Override @@ -137,6 +136,15 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { + if (!resetPosition) { + maskingWindowIndex = getCurrentWindowIndex(); + maskingPeriodIndex = getCurrentPeriodIndex(); + maskingWindowPositionMs = getCurrentPosition(); + } else { + maskingWindowIndex = 0; + maskingPeriodIndex = 0; + maskingWindowPositionMs = 0; + } if (resetState) { if (!timeline.isEmpty() || manifest != null) { timeline = Timeline.EMPTY; @@ -263,6 +271,7 @@ import java.util.concurrent.CopyOnWriteArraySet; maskingPeriodIndex = periodIndex; } if (positionMs == C.TIME_UNSET) { + // TODO: Work out when to call onPositionDiscontinuity on listeners for this case. maskingWindowPositionMs = 0; internalPlayer.seekTo(timeline, windowIndex, C.TIME_UNSET); } else { @@ -313,7 +322,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public int getCurrentPeriodIndex() { - if (timeline.isEmpty() || pendingSeekAcks > 0) { + if (shouldMaskPosition()) { return maskingPeriodIndex; } else { return playbackInfo.periodId.periodIndex; @@ -322,7 +331,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public int getCurrentWindowIndex() { - if (timeline.isEmpty() || pendingSeekAcks > 0) { + if (shouldMaskPosition()) { return maskingWindowIndex; } else { return timeline.getPeriod(playbackInfo.periodId.periodIndex, period).windowIndex; @@ -358,7 +367,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public long getCurrentPosition() { - if (timeline.isEmpty() || pendingSeekAcks > 0) { + if (shouldMaskPosition()) { return maskingWindowPositionMs; } else { return playbackInfoPositionUsToWindowPositionMs(playbackInfo.positionUs); @@ -368,7 +377,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public long getBufferedPosition() { // TODO - Implement this properly. - if (timeline.isEmpty() || pendingSeekAcks > 0) { + if (shouldMaskPosition()) { return maskingWindowPositionMs; } else { return playbackInfoPositionUsToWindowPositionMs(playbackInfo.bufferedPositionUs); @@ -398,7 +407,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public boolean isPlayingAd() { - return !timeline.isEmpty() && pendingSeekAcks == 0 && playbackInfo.periodId.isAd(); + return !shouldMaskPosition() && playbackInfo.periodId.isAd(); } @Override @@ -469,28 +478,9 @@ import java.util.concurrent.CopyOnWriteArraySet; break; } case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: { - SourceInfo sourceInfo = (SourceInfo) msg.obj; - pendingPrepareAcks -= sourceInfo.prepareAcks; - pendingSeekAcks -= sourceInfo.seekAcks; - if (pendingPrepareAcks == 0) { - timeline = sourceInfo.timeline; - manifest = sourceInfo.manifest; - playbackInfo = sourceInfo.playbackInfo; - if (pendingSeekAcks == 0 && timeline.isEmpty()) { - // Update the masking variables, which are used when the timeline is empty. - maskingPeriodIndex = 0; - maskingWindowIndex = 0; - maskingWindowPositionMs = 0; - } - for (Player.EventListener listener : listeners) { - listener.onTimelineChanged(timeline, manifest); - } - } - if (pendingSeekAcks == 0 && sourceInfo.seekAcks > 0) { - for (Player.EventListener listener : listeners) { - listener.onSeekProcessed(); - } - } + int prepareAcks = msg.arg1; + int seekAcks = msg.arg2; + handlePlaybackInfo((PlaybackInfo) msg.obj, prepareAcks, seekAcks, false); break; } case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: { @@ -507,30 +497,12 @@ import java.util.concurrent.CopyOnWriteArraySet; break; } case ExoPlayerImplInternal.MSG_SEEK_ACK: { - if (--pendingSeekAcks == 0) { - playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; - if (timeline.isEmpty()) { - // Update the masking variables, which are used when the timeline is empty. - maskingPeriodIndex = 0; - maskingWindowIndex = 0; - maskingWindowPositionMs = 0; - } - for (Player.EventListener listener : listeners) { - if (msg.arg1 != 0) { - listener.onPositionDiscontinuity(DISCONTINUITY_REASON_INTERNAL); - } - listener.onSeekProcessed(); - } - } + boolean seekPositionAdjusted = msg.arg1 != 0; + handlePlaybackInfo((PlaybackInfo) msg.obj, 0, 1, seekPositionAdjusted); break; } case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: { - if (pendingSeekAcks == 0) { - playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; - for (Player.EventListener listener : listeners) { - listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION); - } - } + handlePlaybackInfo((PlaybackInfo) msg.obj, 0, 0, true); break; } case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: { @@ -555,6 +527,43 @@ import java.util.concurrent.CopyOnWriteArraySet; } } + private void handlePlaybackInfo(PlaybackInfo playbackInfo, int prepareAcks, int seekAcks, + boolean positionDiscontinuity) { + Assertions.checkNotNull(playbackInfo.timeline); + pendingPrepareAcks -= prepareAcks; + pendingSeekAcks -= seekAcks; + if (pendingPrepareAcks == 0 && pendingSeekAcks == 0) { + this.playbackInfo = playbackInfo; + boolean timelineOrManifestChanged = timeline != playbackInfo.timeline + || manifest != playbackInfo.manifest; + timeline = playbackInfo.timeline; + manifest = playbackInfo.manifest; + if (timeline.isEmpty()) { + // Update the masking variables, which are used when the timeline is empty. + maskingPeriodIndex = 0; + maskingWindowIndex = 0; + maskingWindowPositionMs = 0; + } + if (timelineOrManifestChanged) { + for (Player.EventListener listener : listeners) { + listener.onTimelineChanged(timeline, manifest); + } + } + if (positionDiscontinuity) { + for (Player.EventListener listener : listeners) { + listener.onPositionDiscontinuity( + seekAcks > 0 ? DISCONTINUITY_REASON_INTERNAL : DISCONTINUITY_REASON_PERIOD_TRANSITION + ); + } + } + } + if (pendingSeekAcks == 0 && seekAcks > 0) { + for (Player.EventListener listener : listeners) { + listener.onSeekProcessed(); + } + } + } + private long playbackInfoPositionUsToWindowPositionMs(long positionUs) { long positionMs = C.usToMs(positionUs); if (!playbackInfo.periodId.isAd()) { @@ -564,4 +573,8 @@ import java.util.concurrent.CopyOnWriteArraySet; return positionMs; } + private boolean shouldMaskPosition() { + return timeline.isEmpty() || pendingSeekAcks > 0 || pendingPrepareAcks > 0; + } + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index a9a289d597..fec3b3c65a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -54,6 +54,8 @@ import java.io.IOException; */ public static final class PlaybackInfo { + public final Timeline timeline; + public final Object manifest; public final MediaPeriodId periodId; public final long startPositionUs; public final long contentPositionUs; @@ -61,15 +63,14 @@ import java.io.IOException; public volatile long positionUs; public volatile long bufferedPositionUs; - public PlaybackInfo(int periodIndex, long startPositionUs) { - this(new MediaPeriodId(periodIndex), startPositionUs); + public PlaybackInfo(Timeline timeline, Object manifest, int periodIndex, long startPositionUs) { + this(timeline, manifest, new MediaPeriodId(periodIndex), startPositionUs, C.TIME_UNSET); } - public PlaybackInfo(MediaPeriodId periodId, long startPositionUs) { - this(periodId, startPositionUs, C.TIME_UNSET); - } - - public PlaybackInfo(MediaPeriodId periodId, long startPositionUs, long contentPositionUs) { + public PlaybackInfo(Timeline timeline, Object manifest, MediaPeriodId periodId, + long startPositionUs, long contentPositionUs) { + this.timeline = timeline; + this.manifest = manifest; this.periodId = periodId; this.startPositionUs = startPositionUs; this.contentPositionUs = contentPositionUs; @@ -77,31 +78,33 @@ import java.io.IOException; bufferedPositionUs = startPositionUs; } + public PlaybackInfo fromNewPosition(int periodIndex, long startPositionUs, + long contentPositionUs) { + return fromNewPosition(new MediaPeriodId(periodIndex), startPositionUs, contentPositionUs); + } + + public PlaybackInfo fromNewPosition(MediaPeriodId periodId, long startPositionUs, + long contentPositionUs) { + return new PlaybackInfo(timeline, manifest, periodId, startPositionUs, contentPositionUs); + } + public PlaybackInfo copyWithPeriodIndex(int periodIndex) { - PlaybackInfo playbackInfo = new PlaybackInfo(periodId.copyWithPeriodIndex(periodIndex), - startPositionUs, contentPositionUs); - playbackInfo.positionUs = positionUs; - playbackInfo.bufferedPositionUs = bufferedPositionUs; + PlaybackInfo playbackInfo = new PlaybackInfo(timeline, manifest, + periodId.copyWithPeriodIndex(periodIndex), startPositionUs, contentPositionUs); + copyMutablePositions(this, playbackInfo); return playbackInfo; } - } + public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) { + PlaybackInfo playbackInfo = new PlaybackInfo(timeline, manifest, periodId, startPositionUs, + contentPositionUs); + copyMutablePositions(this, playbackInfo); + return playbackInfo; + } - public static final class SourceInfo { - - public final Timeline timeline; - public final Object manifest; - public final PlaybackInfo playbackInfo; - public final int prepareAcks; - public final int seekAcks; - - public SourceInfo(Timeline timeline, Object manifest, PlaybackInfo playbackInfo, - int prepareAcks, int seekAcks) { - this.timeline = timeline; - this.manifest = manifest; - this.playbackInfo = playbackInfo; - this.prepareAcks = prepareAcks; - this.seekAcks = seekAcks; + private static void copyMutablePositions(PlaybackInfo from, PlaybackInfo to) { + to.positionUs = from.positionUs; + to.bufferedPositionUs = from.bufferedPositionUs; } } @@ -192,12 +195,9 @@ import java.io.IOException; private MediaPeriodHolder readingPeriodHolder; private MediaPeriodHolder playingPeriodHolder; - private Timeline timeline; - public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, boolean playWhenReady, @Player.RepeatMode int repeatMode, - boolean shuffleModeEnabled, Handler eventHandler, PlaybackInfo playbackInfo, - ExoPlayer player) { + boolean shuffleModeEnabled, Handler eventHandler, ExoPlayer player) { this.renderers = renderers; this.trackSelector = trackSelector; this.loadControl = loadControl; @@ -206,9 +206,9 @@ import java.io.IOException; this.shuffleModeEnabled = shuffleModeEnabled; this.eventHandler = eventHandler; this.state = Player.STATE_IDLE; - this.playbackInfo = playbackInfo; this.player = player; + playbackInfo = new PlaybackInfo(null, null, 0, C.TIME_UNSET); rendererCapabilities = new RendererCapabilities[renderers.length]; for (int i = 0; i < renderers.length; i++) { renderers[i].setIndex(i); @@ -446,10 +446,10 @@ import java.io.IOException; resetInternal(true); loadControl.onPrepared(); if (resetPosition) { - playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); + playbackInfo = new PlaybackInfo(null, null, 0, C.TIME_UNSET); } else { // The new start position is the current playback position. - playbackInfo = new PlaybackInfo(playbackInfo.periodId, playbackInfo.positionUs, + playbackInfo = new PlaybackInfo(null, null, playbackInfo.periodId, playbackInfo.positionUs, playbackInfo.contentPositionUs); } this.mediaSource = mediaSource; @@ -496,8 +496,9 @@ import java.io.IOException; return; } while (true) { - int nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.info.id.periodIndex, - period, window, repeatMode, shuffleModeEnabled); + int nextPeriodIndex = playbackInfo.timeline.getNextPeriodIndex( + lastValidPeriodHolder.info.id.periodIndex, period, window, repeatMode, + shuffleModeEnabled); while (lastValidPeriodHolder.next != null && !lastValidPeriodHolder.info.isLastInTimelinePeriod) { lastValidPeriodHolder = lastValidPeriodHolder.next; @@ -534,7 +535,8 @@ import java.io.IOException; // position of the playing period to make sure none of the removed period is played. MediaPeriodId periodId = playingPeriodHolder.info.id; long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs); - playbackInfo = new PlaybackInfo(periodId, newPositionUs, playbackInfo.contentPositionUs); + playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs, + playbackInfo.contentPositionUs); } } @@ -696,6 +698,7 @@ import java.io.IOException; } private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { + Timeline timeline = playbackInfo.timeline; if (timeline == null) { pendingInitialSeekCount++; pendingSeekPosition = seekPosition; @@ -710,10 +713,10 @@ import java.io.IOException; // timeline has changed and a suitable seek position could not be resolved in the new one. // Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to // (firstPeriodIndex,0) isn't ignored. - playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET); + playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET); setState(Player.STATE_ENDED); - eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, new PlaybackInfo(firstPeriodIndex, 0)) - .sendToTarget(); + eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, + playbackInfo.fromNewPosition(firstPeriodIndex, 0, C.TIME_UNSET)).sendToTarget(); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal(false); return; @@ -739,7 +742,7 @@ import java.io.IOException; seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs; periodPositionUs = newPeriodPositionUs; } finally { - playbackInfo = new PlaybackInfo(periodId, periodPositionUs, contentPositionUs); + playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs); eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo) .sendToTarget(); } @@ -809,7 +812,7 @@ import java.io.IOException; private boolean shouldKeepPeriodHolder(MediaPeriodId seekPeriodId, long positionUs, MediaPeriodHolder holder) { if (seekPeriodId.equals(holder.info.id) && holder.prepared) { - timeline.getPeriod(holder.info.id.periodIndex, period); + playbackInfo.timeline.getPeriod(holder.info.id.periodIndex, period); int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs); if (nextAdGroupIndex == C.INDEX_UNSET || period.getAdGroupTimeUs(nextAdGroupIndex) == holder.info.endPositionUs) { @@ -884,7 +887,7 @@ import java.io.IOException; mediaSource = null; } mediaPeriodInfoSequence.setTimeline(null); - timeline = null; + playbackInfo = playbackInfo.copyWithTimeline(null, null); } } @@ -1024,10 +1027,11 @@ import java.io.IOException; return; } - Timeline oldTimeline = timeline; - timeline = sourceRefreshInfo.timeline; - mediaPeriodInfoSequence.setTimeline(timeline); + Timeline oldTimeline = playbackInfo.timeline; + Timeline timeline = sourceRefreshInfo.timeline; Object manifest = sourceRefreshInfo.manifest; + mediaPeriodInfoSequence.setTimeline(timeline); + playbackInfo = playbackInfo.copyWithTimeline(timeline, manifest); if (oldTimeline == null) { int processedPrepareAcks = pendingPrepareCount; @@ -1040,32 +1044,32 @@ import java.io.IOException; if (periodPosition == null) { // The seek position was valid for the timeline that it was performed into, but the // timeline has changed and a suitable seek position could not be resolved in the new one. - handleSourceInfoRefreshEndedPlayback(manifest, processedPrepareAcks, - processedInitialSeekCount); + handleSourceInfoRefreshEndedPlayback(processedPrepareAcks, processedInitialSeekCount); } else { int periodIndex = periodPosition.first; long positionUs = periodPosition.second; MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs); - playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs, positionUs); - notifySourceInfoRefresh(manifest, processedPrepareAcks, processedInitialSeekCount); + playbackInfo = playbackInfo.fromNewPosition(periodId, periodId.isAd() ? 0 : positionUs, + positionUs); + notifySourceInfoRefresh(processedPrepareAcks, processedInitialSeekCount); } } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { if (timeline.isEmpty()) { - handleSourceInfoRefreshEndedPlayback(manifest, processedPrepareAcks, 0); + handleSourceInfoRefreshEndedPlayback(processedPrepareAcks, 0); } else { - Pair defaultPosition = getPeriodPosition( + Pair defaultPosition = getPeriodPosition(timeline, timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET); int periodIndex = defaultPosition.first; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, startPositionUs); - playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : startPositionUs, - startPositionUs); - notifySourceInfoRefresh(manifest, processedPrepareAcks, 0); + playbackInfo = playbackInfo.fromNewPosition(periodId, + periodId.isAd() ? 0 : startPositionUs, startPositionUs); + notifySourceInfoRefresh(processedPrepareAcks, 0); } } else { - notifySourceInfoRefresh(manifest, processedPrepareAcks, 0); + notifySourceInfoRefresh(processedPrepareAcks, 0); } return; } @@ -1074,7 +1078,7 @@ import java.io.IOException; MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder : loadingPeriodHolder; if (periodHolder == null && playingPeriodIndex >= oldTimeline.getPeriodCount()) { - notifySourceInfoRefresh(manifest); + notifySourceInfoRefresh(); return; } Object playingPeriodUid = periodHolder == null @@ -1086,11 +1090,11 @@ import java.io.IOException; int newPeriodIndex = resolveSubsequentPeriod(playingPeriodIndex, oldTimeline, timeline); if (newPeriodIndex == C.INDEX_UNSET) { // We failed to resolve a suitable restart position. - handleSourceInfoRefreshEndedPlayback(manifest); + handleSourceInfoRefreshEndedPlayback(); return; } // We resolved a subsequent period. Seek to the default position in the corresponding window. - Pair defaultPosition = getPeriodPosition( + Pair defaultPosition = getPeriodPosition(timeline, timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET); newPeriodIndex = defaultPosition.first; long newPositionUs = defaultPosition.second; @@ -1113,8 +1117,8 @@ import java.io.IOException; // Actually do the seek. MediaPeriodId periodId = new MediaPeriodId(newPeriodIndex); newPositionUs = seekToPeriodPosition(periodId, newPositionUs); - playbackInfo = new PlaybackInfo(periodId, newPositionUs); - notifySourceInfoRefresh(manifest); + playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs, C.TIME_UNSET); + notifySourceInfoRefresh(); return; } @@ -1130,15 +1134,15 @@ import java.io.IOException; if (!periodId.isAd() || periodId.adIndexInAdGroup != playbackInfo.periodId.adIndexInAdGroup) { long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.contentPositionUs); long contentPositionUs = periodId.isAd() ? playbackInfo.contentPositionUs : C.TIME_UNSET; - playbackInfo = new PlaybackInfo(periodId, newPositionUs, contentPositionUs); - notifySourceInfoRefresh(manifest); + playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs, contentPositionUs); + notifySourceInfoRefresh(); return; } } if (periodHolder == null) { // We don't have any period holders, so we're done. - notifySourceInfoRefresh(manifest); + notifySourceInfoRefresh(); return; } @@ -1163,7 +1167,7 @@ import java.io.IOException; // position of the playing period to make sure none of the removed period is played. long newPositionUs = seekToPeriodPosition(playingPeriodHolder.info.id, playbackInfo.positionUs); - playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, newPositionUs, + playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id, newPositionUs, playbackInfo.contentPositionUs); } else { // Update the loading period to be the last period that's still valid, and release all @@ -1177,7 +1181,7 @@ import java.io.IOException; } } - notifySourceInfoRefresh(manifest); + notifySourceInfoRefresh(); } private MediaPeriodHolder updatePeriodInfo(MediaPeriodHolder periodHolder, int periodIndex) { @@ -1191,36 +1195,36 @@ import java.io.IOException; } } - private void handleSourceInfoRefreshEndedPlayback(Object manifest) { - handleSourceInfoRefreshEndedPlayback(manifest, 0, 0); + private void handleSourceInfoRefreshEndedPlayback() { + handleSourceInfoRefreshEndedPlayback(0, 0); } - private void handleSourceInfoRefreshEndedPlayback(Object manifest, int prepareAcks, - int seekAcks) { + private void handleSourceInfoRefreshEndedPlayback(int prepareAcks, int seekAcks) { + Timeline timeline = playbackInfo.timeline; int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow( timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex; // Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to // (firstPeriodIndex,0) isn't ignored. - playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET); + playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET); setState(Player.STATE_ENDED); // Set the playback position to (firstPeriodIndex,0) for notifying the eventHandler. - notifySourceInfoRefresh(manifest, prepareAcks, seekAcks, new PlaybackInfo(firstPeriodIndex, 0)); + notifySourceInfoRefresh(prepareAcks, seekAcks, + playbackInfo.fromNewPosition(firstPeriodIndex, 0, C.TIME_UNSET)); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal(false); } - private void notifySourceInfoRefresh(Object manifest) { - notifySourceInfoRefresh(manifest, 0, 0); + private void notifySourceInfoRefresh() { + notifySourceInfoRefresh(0, 0); } - private void notifySourceInfoRefresh(Object manifest, int prepareAcks, int seekAcks) { - notifySourceInfoRefresh(manifest, prepareAcks, seekAcks, playbackInfo); + private void notifySourceInfoRefresh(int prepareAcks, int seekAcks) { + notifySourceInfoRefresh(prepareAcks, seekAcks, playbackInfo); } - private void notifySourceInfoRefresh(Object manifest, int prepareAcks, int seekAcks, - PlaybackInfo playbackInfo) { - eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, - new SourceInfo(timeline, manifest, playbackInfo, prepareAcks, seekAcks)).sendToTarget(); + private void notifySourceInfoRefresh(int prepareAcks, int seekAcks, PlaybackInfo playbackInfo) { + eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, prepareAcks, seekAcks, playbackInfo) + .sendToTarget(); } /** @@ -1260,6 +1264,7 @@ import java.io.IOException; * bounds of the timeline. */ private Pair resolveSeekPosition(SeekPosition seekPosition) { + Timeline timeline = playbackInfo.timeline; Timeline seekTimeline = seekPosition.timeline; if (seekTimeline.isEmpty()) { // The application performed a blind seek without a non-empty timeline (most likely based on @@ -1291,7 +1296,8 @@ import java.io.IOException; periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); if (periodIndex != C.INDEX_UNSET) { // We found one. Map the SeekPosition onto the corresponding default position. - return getPeriodPosition(timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET); + return getPeriodPosition(timeline, timeline.getPeriod(periodIndex, period).windowIndex, + C.TIME_UNSET); } // We didn't find one. Give up. return null; @@ -1301,12 +1307,13 @@ import java.io.IOException; * Calls {@link Timeline#getPeriodPosition(Timeline.Window, Timeline.Period, int, long)} using the * current timeline. */ - private Pair getPeriodPosition(int windowIndex, long windowPositionUs) { + private Pair getPeriodPosition(Timeline timeline, int windowIndex, + long windowPositionUs) { return timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); } private void updatePeriods() throws ExoPlaybackException, IOException { - if (timeline == null) { + if (playbackInfo.timeline == null) { // We're waiting to get information about periods. mediaSource.maybeThrowSourceInfoRefreshError(); return; @@ -1332,7 +1339,7 @@ import java.io.IOException; // the end of the playing period, so advance playback to the next period. playingPeriodHolder.release(); setPlayingPeriodHolder(playingPeriodHolder.next); - playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, + playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id, playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs); updatePlaybackPositions(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); @@ -1439,7 +1446,7 @@ import java.io.IOException; ? RENDERER_TIMESTAMP_OFFSET_US : (loadingPeriodHolder.getRendererOffset() + loadingPeriodHolder.info.durationUs); int holderIndex = loadingPeriodHolder == null ? 0 : loadingPeriodHolder.index + 1; - Object uid = timeline.getPeriod(info.id.periodIndex, period, true).uid; + Object uid = playbackInfo.timeline.getPeriod(info.id.periodIndex, period, true).uid; MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, uid, holderIndex, info); if (loadingPeriodHolder != null) {