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 39d45bf958..3068fbf471 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 @@ -950,15 +950,14 @@ import java.util.concurrent.atomic.AtomicBoolean; setState(Player.STATE_BUFFERING); } - // Clear the timeline, but keep the requested period if it is already prepared. - MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod(); - MediaPeriodHolder newPlayingPeriodHolder = oldPlayingPeriodHolder; + // Find the requested period if it's already prepared. + @Nullable MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod(); + @Nullable MediaPeriodHolder newPlayingPeriodHolder = oldPlayingPeriodHolder; while (newPlayingPeriodHolder != null) { if (periodId.equals(newPlayingPeriodHolder.info.id) && newPlayingPeriodHolder.prepared) { - queue.removeAfter(newPlayingPeriodHolder); break; } - newPlayingPeriodHolder = queue.advancePlayingPeriod(); + newPlayingPeriodHolder = newPlayingPeriodHolder.getNext(); } // Disable all renderers if the period being played is changing, if the seek results in negative @@ -971,15 +970,19 @@ import java.util.concurrent.atomic.AtomicBoolean; disableRenderer(renderer); } enabledRenderers = new Renderer[0]; - oldPlayingPeriodHolder = null; if (newPlayingPeriodHolder != null) { + // Update the queue and reenable renderers if the requested media period already exists. + while (queue.getPlayingPeriod() != newPlayingPeriodHolder) { + queue.advancePlayingPeriod(); + } newPlayingPeriodHolder.setRendererOffset(/* rendererPositionOffsetUs= */ 0); + enablePlayingPeriodRenderers(); } } - // Update the holders. + // Do the actual seeking. if (newPlayingPeriodHolder != null) { - updatePlayingPeriodRenderers(oldPlayingPeriodHolder); + queue.removeAfter(newPlayingPeriodHolder); if (newPlayingPeriodHolder.hasEnabledTracks) { periodPositionUs = newPlayingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs); newPlayingPeriodHolder.mediaPeriod.discardBuffer( @@ -1694,7 +1697,7 @@ import java.util.concurrent.atomic.AtomicBoolean; // The window index of the seek position was outside the bounds of the timeline. return null; } - if (timeline == seekTimeline) { + if (timeline.equals(seekTimeline)) { // Our internal timeline is the seek timeline, so the mapped position is correct. return periodPosition; } @@ -1869,8 +1872,10 @@ import java.util.concurrent.atomic.AtomicBoolean; // anymore and need to re-enable the renderers. Set all current streams final to do that. setAllRendererStreamsFinal(); } + boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; + disablePlayingPeriodRenderersForTransition(rendererWasEnabledFlags); MediaPeriodHolder newPlayingPeriodHolder = queue.advancePlayingPeriod(); - updatePlayingPeriodRenderers(oldPlayingPeriodHolder); + enablePlayingPeriodRenderers(rendererWasEnabledFlags); playbackInfo = copyWithNewPosition( newPlayingPeriodHolder.info.id, @@ -1943,7 +1948,7 @@ import java.util.concurrent.atomic.AtomicBoolean; if (loadingPeriodHolder == queue.getPlayingPeriod()) { // This is the first prepared period, so update the position and the renderers. resetRendererPosition(loadingPeriodHolder.info.startPositionUs); - updatePlayingPeriodRenderers(/* oldPlayingPeriodHolder= */ null); + enablePlayingPeriodRenderers(); } maybeContinueLoading(); } @@ -2025,22 +2030,15 @@ import java.util.concurrent.atomic.AtomicBoolean; mediaPeriodId, positionUs, contentPositionUs, getTotalBufferedDurationUs()); } - @SuppressWarnings("ParameterNotNullable") - private void updatePlayingPeriodRenderers(@Nullable MediaPeriodHolder oldPlayingPeriodHolder) + private void disablePlayingPeriodRenderersForTransition(boolean[] outRendererWasEnabledFlags) throws ExoPlaybackException { - MediaPeriodHolder newPlayingPeriodHolder = queue.getPlayingPeriod(); - if (newPlayingPeriodHolder == null || oldPlayingPeriodHolder == newPlayingPeriodHolder) { - return; - } - int enabledRendererCount = 0; - boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; + MediaPeriodHolder oldPlayingPeriodHolder = Assertions.checkNotNull(queue.getPlayingPeriod()); + MediaPeriodHolder newPlayingPeriodHolder = + Assertions.checkNotNull(oldPlayingPeriodHolder.getNext()); for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; - rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; - if (newPlayingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { - enabledRendererCount++; - } - if (rendererWasEnabledFlags[i] + outRendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; + if (outRendererWasEnabledFlags[i] && (!newPlayingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i) || (renderer.isCurrentStreamFinal() && renderer.getStream() == oldPlayingPeriodHolder.sampleStreams[i]))) { @@ -2050,10 +2048,24 @@ import java.util.concurrent.atomic.AtomicBoolean; disableRenderer(renderer); } } + } + + private void enablePlayingPeriodRenderers() throws ExoPlaybackException { + enablePlayingPeriodRenderers(/* rendererWasEnabledFlags= */ new boolean[renderers.length]); + } + + private void enablePlayingPeriodRenderers(boolean[] rendererWasEnabledFlags) + throws ExoPlaybackException { + MediaPeriodHolder playingPeriodHolder = Assertions.checkNotNull(queue.getPlayingPeriod()); playbackInfo = playbackInfo.copyWithTrackInfo( - newPlayingPeriodHolder.getTrackGroups(), - newPlayingPeriodHolder.getTrackSelectorResult()); + playingPeriodHolder.getTrackGroups(), playingPeriodHolder.getTrackSelectorResult()); + int enabledRendererCount = 0; + for (int i = 0; i < renderers.length; i++) { + if (playingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { + enabledRendererCount++; + } + } enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 2cb46e099b..29b8967587 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -127,7 +127,7 @@ public class AnalyticsCollector */ public final void notifySeekStarted() { if (!mediaPeriodQueueTracker.isSeeking()) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); mediaPeriodQueueTracker.onSeekStarted(); for (AnalyticsListener listener : listeners) { listener.onSeekStarted(eventTime); @@ -149,7 +149,7 @@ public class AnalyticsCollector @Override public final void onMetadata(Metadata metadata) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onMetadata(eventTime, metadata); } @@ -195,9 +195,7 @@ public class AnalyticsCollector @Override public final void onAudioDisabled(DecoderCounters counters) { - // The renderers are disabled after we changed the playing media period on the playback thread - // but before this change is reported to the app thread. - EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime(); + EventTime eventTime = generatePlayingMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_AUDIO, counters); } @@ -260,7 +258,7 @@ public class AnalyticsCollector @Override public final void onDroppedFrames(int count, long elapsedMs) { - EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime(); + EventTime eventTime = generatePlayingMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onDroppedVideoFrames(eventTime, count, elapsedMs); } @@ -268,9 +266,7 @@ public class AnalyticsCollector @Override public final void onVideoDisabled(DecoderCounters counters) { - // The renderers are disabled after we changed the playing media period on the playback thread - // but before this change is reported to the app thread. - EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime(); + EventTime eventTime = generatePlayingMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_VIDEO, counters); } @@ -416,7 +412,7 @@ public class AnalyticsCollector @Override public final void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) { mediaPeriodQueueTracker.onTimelineChanged(timeline); - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onTimelineChanged(eventTime, reason); } @@ -425,7 +421,7 @@ public class AnalyticsCollector @Override public final void onTracksChanged( TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onTracksChanged(eventTime, trackGroups, trackSelections); } @@ -433,7 +429,7 @@ public class AnalyticsCollector @Override public final void onLoadingChanged(boolean isLoading) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onLoadingChanged(eventTime, isLoading); } @@ -441,7 +437,7 @@ public class AnalyticsCollector @Override public final void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPlayerStateChanged(eventTime, playWhenReady, playbackState); } @@ -450,7 +446,7 @@ public class AnalyticsCollector @Override public void onPlaybackSuppressionReasonChanged( @PlaybackSuppressionReason int playbackSuppressionReason) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPlaybackSuppressionReasonChanged(eventTime, playbackSuppressionReason); } @@ -458,7 +454,7 @@ public class AnalyticsCollector @Override public void onIsPlayingChanged(boolean isPlaying) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onIsPlayingChanged(eventTime, isPlaying); } @@ -466,7 +462,7 @@ public class AnalyticsCollector @Override public final void onRepeatModeChanged(@Player.RepeatMode int repeatMode) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onRepeatModeChanged(eventTime, repeatMode); } @@ -474,7 +470,7 @@ public class AnalyticsCollector @Override public final void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onShuffleModeChanged(eventTime, shuffleModeEnabled); } @@ -482,7 +478,7 @@ public class AnalyticsCollector @Override public final void onPlayerError(ExoPlaybackException error) { - EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime(); + EventTime eventTime = generatePlayingMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPlayerError(eventTime, error); } @@ -490,8 +486,7 @@ public class AnalyticsCollector @Override public final void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { - mediaPeriodQueueTracker.onPositionDiscontinuity(reason); - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPositionDiscontinuity(eventTime, reason); } @@ -499,7 +494,7 @@ public class AnalyticsCollector @Override public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPlaybackParametersChanged(eventTime, playbackParameters); } @@ -509,7 +504,7 @@ public class AnalyticsCollector public final void onSeekProcessed() { if (mediaPeriodQueueTracker.isSeeking()) { mediaPeriodQueueTracker.onSeekProcessed(); - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onSeekProcessed(eventTime); } @@ -570,7 +565,7 @@ public class AnalyticsCollector @Override public final void onDrmSessionReleased() { - EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime(); + EventTime eventTime = generatePlayingMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onDrmSessionReleased(eventTime); } @@ -594,7 +589,8 @@ public class AnalyticsCollector long realtimeMs = clock.elapsedRealtime(); long eventPositionMs; boolean isInCurrentWindow = - timeline == player.getCurrentTimeline() && windowIndex == player.getCurrentWindowIndex(); + timeline.equals(player.getCurrentTimeline()) + && windowIndex == player.getCurrentWindowIndex(); if (mediaPeriodId != null && mediaPeriodId.isAd()) { boolean isCurrentAd = isInCurrentWindow @@ -636,8 +632,8 @@ public class AnalyticsCollector mediaPeriodInfo.timeline, mediaPeriodInfo.windowIndex, mediaPeriodInfo.mediaPeriodId); } - private EventTime generateLastReportedPlayingMediaPeriodEventTime() { - return generateEventTime(mediaPeriodQueueTracker.getLastReportedPlayingMediaPeriod()); + private EventTime generateCurrentPlayerMediaPeriodEventTime() { + return generateEventTime(mediaPeriodQueueTracker.getCurrentPlayerMediaPeriod()); } private EventTime generatePlayingMediaPeriodEventTime() { @@ -677,8 +673,7 @@ public class AnalyticsCollector private final HashMap mediaPeriodIdToInfo; private final Period period; - @Nullable private MediaPeriodInfo lastPlayingMediaPeriod; - @Nullable private MediaPeriodInfo lastReportedPlayingMediaPeriod; + @Nullable private MediaPeriodInfo playingMediaPeriod; @Nullable private MediaPeriodInfo readingMediaPeriod; private Timeline timeline; private boolean isSeeking; @@ -691,34 +686,34 @@ public class AnalyticsCollector } /** - * Returns the {@link MediaPeriodInfo} of the media period in the front of the queue. This is - * the playing media period unless the player hasn't started playing yet (in which case it is - * the loading media period or null). While the player is seeking or preparing, this method will - * always return null to reflect the uncertainty about the current playing period. May also be - * null, if the timeline is empty or no media period is active yet. + * Returns the {@link MediaPeriodInfo} of the media period corresponding the current position of + * the player. + * + *

May be null if no matching media period has been created yet or the player is currently + * masking its state. */ @Nullable - public MediaPeriodInfo getPlayingMediaPeriod() { + public MediaPeriodInfo getCurrentPlayerMediaPeriod() { return mediaPeriodInfoQueue.isEmpty() || timeline.isEmpty() || isSeeking ? null : mediaPeriodInfoQueue.get(0); } /** - * Returns the {@link MediaPeriodInfo} of the currently playing media period. This is the - * publicly reported period which should always match {@link Player#getCurrentPeriodIndex()} - * unless the player is currently seeking or being prepared in which case the previous period is - * reported until the seek or preparation is processed. May be null, if no media period is - * active yet. + * Returns the {@link MediaPeriodInfo} of the media period at the front of the queue. If the + * queue is empty, this is the last media period which was at the front of the queue. + * + *

May be null, if no media period has been created yet. */ @Nullable - public MediaPeriodInfo getLastReportedPlayingMediaPeriod() { - return lastReportedPlayingMediaPeriod; + public MediaPeriodInfo getPlayingMediaPeriod() { + return playingMediaPeriod; } /** * Returns the {@link MediaPeriodInfo} of the media period currently being read by the player. - * May be null, if the player is not reading a media period. + * + *

May be null, if the player is not reading a media period. */ @Nullable public MediaPeriodInfo getReadingMediaPeriod() { @@ -727,8 +722,9 @@ public class AnalyticsCollector /** * Returns the {@link MediaPeriodInfo} of the media period at the end of the queue which is - * currently loading or will be the next one loading. May be null, if no media period is active - * yet. + * currently loading or will be the next one loading. + * + *

May be null, if no media period is active yet. */ @Nullable public MediaPeriodInfo getLoadingMediaPeriod() { @@ -770,11 +766,6 @@ public class AnalyticsCollector return match; } - /** Updates the queue with a reported position discontinuity . */ - public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { - lastReportedPlayingMediaPeriod = lastPlayingMediaPeriod; - } - /** Updates the queue with a reported timeline change. */ public void onTimelineChanged(Timeline timeline) { for (int i = 0; i < mediaPeriodInfoQueue.size(); i++) { @@ -786,8 +777,10 @@ public class AnalyticsCollector if (readingMediaPeriod != null) { readingMediaPeriod = updateMediaPeriodInfoToNewTimeline(readingMediaPeriod, timeline); } + if (!mediaPeriodInfoQueue.isEmpty()) { + playingMediaPeriod = mediaPeriodInfoQueue.get(0); + } this.timeline = timeline; - lastReportedPlayingMediaPeriod = lastPlayingMediaPeriod; } /** Updates the queue with a reported start of seek. */ @@ -798,7 +791,6 @@ public class AnalyticsCollector /** Updates the queue with a reported processed seek. */ public void onSeekProcessed() { isSeeking = false; - lastReportedPlayingMediaPeriod = lastPlayingMediaPeriod; } /** Updates the queue with a newly created media period. */ @@ -812,10 +804,7 @@ public class AnalyticsCollector isInTimeline ? timeline.getPeriod(periodIndex, period).windowIndex : windowIndex); mediaPeriodInfoQueue.add(mediaPeriodInfo); mediaPeriodIdToInfo.put(mediaPeriodId, mediaPeriodInfo); - lastPlayingMediaPeriod = mediaPeriodInfoQueue.get(0); - if (mediaPeriodInfoQueue.size() == 1 && !timeline.isEmpty()) { - lastReportedPlayingMediaPeriod = lastPlayingMediaPeriod; - } + playingMediaPeriod = mediaPeriodInfoQueue.get(0); } /** @@ -833,7 +822,7 @@ public class AnalyticsCollector readingMediaPeriod = mediaPeriodInfoQueue.isEmpty() ? null : mediaPeriodInfoQueue.get(0); } if (!mediaPeriodInfoQueue.isEmpty()) { - lastPlayingMediaPeriod = mediaPeriodInfoQueue.get(0); + playingMediaPeriod = mediaPeriodInfoQueue.get(0); } return true; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index b34a7fb899..d99ac62395 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -434,7 +434,7 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1Seq2); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)) - .containsExactly(period0, period0, period1Seq2); + .containsExactly(period0, period1Seq2, period1Seq2); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0, period1Seq2); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(period0, period1Seq2);