From 9ca6544311c30a12b7f77dbea034a3fd2c922fbf Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 02:06:32 -0700 Subject: [PATCH 01/15] Simplify some buffered position related code. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215704344 --- .../exoplayer2/ExoPlayerImplInternal.java | 27 ++++++++++++------- .../android/exoplayer2/MediaPeriodHolder.java | 13 +++------ 2 files changed, 21 insertions(+), 19 deletions(-) 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 6810c86558..8b22999679 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 @@ -496,10 +496,8 @@ import java.util.Collections; // Update the buffered position and total buffered duration. MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod(); - playbackInfo.bufferedPositionUs = - loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true); - playbackInfo.totalBufferedDurationUs = - playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs); + playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs(); + playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs(); } private void doSomeWork() throws ExoPlaybackException, IOException { @@ -1097,12 +1095,10 @@ import java.util.Collections; } // Renderers are ready and we're loading. Ask the LoadControl whether to transition. MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); - long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal); - return bufferedPositionUs == C.TIME_END_OF_SOURCE + boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal; + return bufferedToEnd || loadControl.shouldStartPlayback( - bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs), - mediaClock.getPlaybackParameters().speed, - rebuffering); + getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering); } private boolean isTimelineReady() { @@ -1574,7 +1570,7 @@ import java.util.Collections; return; } long bufferedDurationUs = - nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs); + getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs); boolean continueLoading = loadControl.shouldContinueLoading( bufferedDurationUs, mediaClock.getPlaybackParameters().speed); @@ -1680,6 +1676,17 @@ import java.util.Collections; } } + private long getTotalBufferedDurationUs() { + return getTotalBufferedDurationUs(playbackInfo.bufferedPositionUs); + } + + private long getTotalBufferedDurationUs(long bufferedPositionInLoadingPeriodUs) { + MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); + return loadingPeriodHolder == null + ? 0 + : bufferedPositionInLoadingPeriodUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs); + } + private void updateLoadControlTrackSelection( TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 3ba96d2b80..4fc21475d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -133,23 +133,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } /** - * Returns the buffered position in microseconds. If the period is buffered to the end then - * {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which - * case the period duration is returned. + * Returns the buffered position in microseconds. If the period is buffered to the end, then the + * period duration is returned. * - * @param convertEosToDuration Whether to return the period duration rather than - * {@link C#TIME_END_OF_SOURCE} if the period is fully buffered. * @return The buffered position in microseconds. */ - public long getBufferedPositionUs(boolean convertEosToDuration) { + public long getBufferedPositionUs() { if (!prepared) { return info.startPositionUs; } long bufferedPositionUs = hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE; - return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration - ? info.durationUs - : bufferedPositionUs; + return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs; } /** From 711f9f8b6ada665a5d2f9cd29724f3b718304d9d Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 02:07:17 -0700 Subject: [PATCH 02/15] Add test action to wait for an isLoading change. This allows to wait until loading started or finished. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215704424 --- .../android/exoplayer2/testutil/Action.java | 50 +++++++++++++++++++ .../exoplayer2/testutil/ActionSchedule.java | 11 ++++ 2 files changed, 61 insertions(+) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 14d62e85c8..c988c0c172 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -686,6 +686,56 @@ public abstract class Action { } } + /** + * Waits for a specified loading state, returning either immediately or after a call to {@link + * Player.EventListener#onLoadingChanged(boolean)}. + */ + public static final class WaitForIsLoading extends Action { + + private final boolean targetIsLoading; + + /** + * @param tag A tag to use for logging. + * @param targetIsLoading The loading state to wait for. + */ + public WaitForIsLoading(String tag, boolean targetIsLoading) { + super(tag, "WaitForIsLoading"); + this.targetIsLoading = targetIsLoading; + } + + @Override + protected void doActionAndScheduleNextImpl( + final SimpleExoPlayer player, + final DefaultTrackSelector trackSelector, + final Surface surface, + final HandlerWrapper handler, + final ActionNode nextAction) { + if (nextAction == null) { + return; + } + if (targetIsLoading == player.isLoading()) { + nextAction.schedule(player, trackSelector, surface, handler); + } else { + player.addListener( + new Player.EventListener() { + @Override + public void onLoadingChanged(boolean isLoading) { + if (targetIsLoading == isLoading) { + player.removeListener(this); + nextAction.schedule(player, trackSelector, surface, handler); + } + } + }); + } + } + + @Override + protected void doActionImpl( + SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) { + // Not triggered. + } + } + /** * Waits for {@link Player.EventListener#onSeekProcessed()}. */ diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 54d97fb905..71f5fdeae1 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -41,6 +41,7 @@ import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled; import com.google.android.exoplayer2.testutil.Action.SetVideoSurface; import com.google.android.exoplayer2.testutil.Action.Stop; import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException; +import com.google.android.exoplayer2.testutil.Action.WaitForIsLoading; import com.google.android.exoplayer2.testutil.Action.WaitForPlaybackState; import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity; import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed; @@ -414,6 +415,16 @@ public final class ActionSchedule { return apply(new WaitForPlaybackState(tag, targetPlaybackState)); } + /** + * Schedules a delay until {@code player.isLoading()} changes to the specified value. + * + * @param targetIsLoading The target value of {@code player.isLoading()}. + * @return The builder, for convenience. + */ + public Builder waitForIsLoading(boolean targetIsLoading) { + return apply(new WaitForIsLoading(tag, targetIsLoading)); + } + /** * Schedules a {@link Runnable} to be executed. * From d4b87cdc2338e79f6d020d076da82491566fdfff Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 03:32:54 -0700 Subject: [PATCH 03/15] Fix updates of loading period and buffered positions in PlaybackInfo. This makes the following changes to improve consistency among the PlaybackInfo values: 1. Update buffered position and total buffered duration after loading period is set as both values are affected by a loading period update. 2. Add copyWithPosition to allow updating the position without resetting the loading period. 3. Forward the total buffered duration to playing position updates as it may have changed with the new playing position. Issue:#4899 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215712328 --- RELEASENOTES.md | 3 + .../android/exoplayer2/ExoPlayerImpl.java | 2 +- .../exoplayer2/ExoPlayerImplInternal.java | 55 +++++++--- .../android/exoplayer2/PlaybackInfo.java | 102 +++++++++++++++++- .../android/exoplayer2/ExoPlayerTest.java | 53 +++++++++ 5 files changed, 198 insertions(+), 17 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0510055942..e2f33d9150 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,6 +19,9 @@ ([#4788](https://github.com/google/ExoPlayer/issues/4788)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). +* Fix issue where buffered position is not updated correctly when transitioning + between periods + ([#4899](https://github.com/google/ExoPlayer/issues/4899)). ### 2.9.0 ### 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 5fb6202d2f..e1f942147d 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 @@ -619,7 +619,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (playbackInfo.startPositionUs == C.TIME_UNSET) { // Replace internal unset start position with externally visible start position of zero. playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs); } if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare) 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 8b22999679..40716c14c0 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 @@ -448,7 +448,11 @@ import java.util.Collections; seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true); if (newPositionUs != playbackInfo.positionUs) { playbackInfo = - playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs); + playbackInfo.copyWithNewPosition( + periodId, + newPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); if (sendDiscontinuity) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } @@ -483,8 +487,12 @@ import java.util.Collections; // A MediaPeriod may report a discontinuity at the current playback position to ensure the // renderers are flushed. Only report the discontinuity externally if the position changed. if (periodPositionUs != playbackInfo.positionUs) { - playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, - playbackInfo.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playbackInfo.periodId, + periodPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } } else { @@ -645,7 +653,9 @@ import java.util.Collections; periodPositionUs = newPeriodPositionUs; } } finally { - playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, periodPositionUs, contentPositionUs, getTotalBufferedDurationUs()); if (seekPositionAdjusted) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); } @@ -1020,8 +1030,12 @@ import java.util.Collections; newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags); if (playbackInfo.playbackState != Player.STATE_ENDED && periodPositionUs != playbackInfo.positionUs) { - playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, - playbackInfo.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playbackInfo.periodId, + periodPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); resetRendererPosition(periodPositionUs); } @@ -1163,7 +1177,7 @@ import java.util.Collections; resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); } catch (IllegalSeekPositionException e) { playbackInfo = - playbackInfo.fromNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); + playbackInfo.resetToNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); throw e; } pendingInitialSeekPosition = null; @@ -1176,7 +1190,7 @@ import java.util.Collections; long positionUs = periodPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs); } } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { @@ -1190,7 +1204,7 @@ import java.util.Collections; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, periodId.isAd() ? 0 : startPositionUs, /* contentPositionUs= */ startPositionUs); @@ -1209,7 +1223,7 @@ import java.util.Collections; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, /* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs, /* contentPositionUs= */ startPositionUs); @@ -1248,7 +1262,9 @@ import java.util.Collections; } // Actually do the seek. long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs()); return; } @@ -1260,7 +1276,9 @@ import java.util.Collections; // The previously playing ad should no longer be played, so skip it. long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs()); return; } } @@ -1416,8 +1434,12 @@ import java.util.Collections; MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder; playingPeriodHolder = queue.advancePlayingPeriod(); updatePlayingPeriodRenderers(oldPlayingPeriodHolder); - playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id, - playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playingPeriodHolder.info.id, + playingPeriodHolder.info.startPositionUs, + playingPeriodHolder.info.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason); updatePlaybackPositions(); advancedPlayingPeriod = true; @@ -1667,6 +1689,11 @@ import java.util.Collections; if (loadingMediaPeriodChanged) { playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId); } + playbackInfo.bufferedPositionUs = + loadingMediaPeriodHolder == null + ? playbackInfo.positionUs + : loadingMediaPeriodHolder.getBufferedPositionUs(); + playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs(); if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged) && loadingMediaPeriodHolder != null && loadingMediaPeriodHolder.prepared) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 02058c0484..8c73fde3be 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.CheckResult; import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -40,7 +41,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public final MediaPeriodId periodId; /** * The start position at which playback started in {@link #periodId} relative to the start of the - * associated period in the {@link #timeline}, in microseconds. + * associated period in the {@link #timeline}, in microseconds. Note that this value changes for + * each position discontinuity. */ public final long startPositionUs; /** @@ -103,6 +105,23 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; startPositionUs); } + /** + * Create playback info. + * + * @param timeline See {@link #timeline}. + * @param manifest See {@link #manifest}. + * @param periodId See {@link #periodId}. + * @param startPositionUs See {@link #startPositionUs}. + * @param contentPositionUs See {@link #contentPositionUs}. + * @param playbackState See {@link #playbackState}. + * @param isLoading See {@link #isLoading}. + * @param trackGroups See {@link #trackGroups}. + * @param trackSelectorResult See {@link #trackSelectorResult}. + * @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}. + * @param bufferedPositionUs See {@link #bufferedPositionUs}. + * @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}. + * @param positionUs See {@link #positionUs}. + */ public PlaybackInfo( Timeline timeline, @Nullable Object manifest, @@ -132,7 +151,17 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.positionUs = positionUs; } - public PlaybackInfo fromNewPosition( + /** + * Copies playback info and resets playing and loading position. + * + * @param periodId New playing and loading {@link MediaPeriodId}. + * @param startPositionUs New start position. See {@link #startPositionUs}. + * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored + * if {@code periodId.isAd()} is true. + * @return Copied playback info with reset position. + */ + @CheckResult + public PlaybackInfo resetToNewPosition( MediaPeriodId periodId, long startPositionUs, long contentPositionUs) { return new PlaybackInfo( timeline, @@ -150,6 +179,46 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; startPositionUs); } + /** + * Copied playback info with new playing position. + * + * @param periodId New playing media period. See {@link #periodId}. + * @param positionUs New position. See {@link #positionUs}. + * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored + * if {@code periodId.isAd()} is true. + * @param totalBufferedDurationUs New buffered duration. See {@link #totalBufferedDurationUs}. + * @return Copied playback info with new playing position. + */ + @CheckResult + public PlaybackInfo copyWithNewPosition( + MediaPeriodId periodId, + long positionUs, + long contentPositionUs, + long totalBufferedDurationUs) { + return new PlaybackInfo( + timeline, + manifest, + periodId, + positionUs, + periodId.isAd() ? contentPositionUs : C.TIME_UNSET, + playbackState, + isLoading, + trackGroups, + trackSelectorResult, + loadingMediaPeriodId, + bufferedPositionUs, + totalBufferedDurationUs, + positionUs); + } + + /** + * Copies playback info with new timeline and manifest. + * + * @param timeline New timeline. See {@link #timeline}. + * @param manifest New manifest. See {@link #manifest}. + * @return Copied playback info with new timeline and manifest. + */ + @CheckResult public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) { return new PlaybackInfo( timeline, @@ -167,6 +236,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new playback state. + * + * @param playbackState New playback state. See {@link #playbackState}. + * @return Copied playback info with new playback state. + */ + @CheckResult public PlaybackInfo copyWithPlaybackState(int playbackState) { return new PlaybackInfo( timeline, @@ -184,6 +260,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new loading state. + * + * @param isLoading New loading state. See {@link #isLoading}. + * @return Copied playback info with new loading state. + */ + @CheckResult public PlaybackInfo copyWithIsLoading(boolean isLoading) { return new PlaybackInfo( timeline, @@ -201,6 +284,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new track information. + * + * @param trackGroups New track groups. See {@link #trackGroups}. + * @param trackSelectorResult New track selector result. See {@link #trackSelectorResult}. + * @return Copied playback info with new track information. + */ + @CheckResult public PlaybackInfo copyWithTrackInfo( TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { return new PlaybackInfo( @@ -219,6 +310,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new loading media period. + * + * @param loadingMediaPeriodId New loading media period id. See {@link #loadingMediaPeriodId}. + * @return Copied playback info with new loading media period. + */ + @CheckResult public PlaybackInfo copyWithLoadingMediaPeriodId(MediaPeriodId loadingMediaPeriodId) { return new PlaybackInfo( timeline, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 407e9a3827..d131ed0f51 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -2534,6 +2534,59 @@ public final class ExoPlayerTest { assertThat(positionWhenReady.get()).isEqualTo(periodDurationMs + 10); } + @Test + public void periodTransitionReportsCorrectBufferedPosition() throws Exception { + int periodCount = 3; + long periodDurationUs = 5 * C.MICROS_PER_SECOND; + long windowDurationUs = periodCount * periodDurationUs; + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + periodCount, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ false, + windowDurationUs)); + AtomicReference playerReference = new AtomicReference<>(); + AtomicLong bufferedPositionAtFirstDiscontinuityMs = new AtomicLong(C.TIME_UNSET); + EventListener eventListener = + new EventListener() { + @Override + public void onPositionDiscontinuity(@DiscontinuityReason int reason) { + if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + if (bufferedPositionAtFirstDiscontinuityMs.get() == C.TIME_UNSET) { + bufferedPositionAtFirstDiscontinuityMs.set( + playerReference.get().getBufferedPosition()); + } + } + } + }; + ActionSchedule actionSchedule = + new ActionSchedule.Builder("periodTransitionReportsCorrectBufferedPosition") + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + playerReference.set(player); + player.addListener(eventListener); + } + }) + .pause() + // Wait until all periods are fully buffered. + .waitForIsLoading(/* targetIsLoading= */ true) + .waitForIsLoading(/* targetIsLoading= */ false) + .play() + .build(); + new Builder() + .setTimeline(timeline) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilEnded(TIMEOUT_MS); + + assertThat(bufferedPositionAtFirstDiscontinuityMs.get()).isEqualTo(C.usToMs(windowDurationUs)); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { From cc7684d09f0a66de0e501882da668cdd35504752 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 4 Oct 2018 06:56:29 -0700 Subject: [PATCH 04/15] Introduce Renderer.reset - It's a no-op for now - Renderers that want to support retaining resources will move some functionality from their disable() implementations into reset() - ExoPlayerImplInternal will be updated to not always call reset() immediately after disable(), which is what will enable resources to actually be retained. Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215729783 --- .../android/exoplayer2/BaseRenderer.java | 15 +++++++++++++++ .../exoplayer2/ExoPlayerImplInternal.java | 1 + .../android/exoplayer2/NoSampleRenderer.java | 15 +++++++++++++++ .../google/android/exoplayer2/Renderer.java | 18 +++++++++++++++--- .../video/spherical/CameraMotionRenderer.java | 6 +++--- .../audio/SimpleDecoderAudioRendererTest.java | 1 + 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 51e724bee1..73602d85aa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -153,6 +153,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { onDisabled(); } + @Override + public final void reset() { + Assertions.checkState(state == STATE_DISABLED); + onReset(); + } + // RendererCapabilities implementation. @Override @@ -247,6 +253,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { // Do nothing. } + /** + * Called when the renderer is reset. + * + *

The default implementation is a no-op. + */ + protected void onReset() { + // Do nothing. + } + // Methods to be called by subclasses. /** Returns the formats of the currently enabled stream. */ 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 40716c14c0..891ea48108 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 @@ -989,6 +989,7 @@ import java.util.Collections; mediaClock.onRendererDisabled(renderer); ensureStopped(renderer); renderer.disable(); + renderer.reset(); } private void reselectTracksInternal() throws ExoPlaybackException { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java index 45d6537b84..6645850d3b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java @@ -158,6 +158,12 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities onDisabled(); } + @Override + public final void reset() { + Assertions.checkState(state == STATE_DISABLED); + onReset(); + } + @Override public boolean isReady() { return true; @@ -260,6 +266,15 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities // Do nothing. } + /** + * Called when the renderer is reset. + * + *

The default implementation is a no-op. + */ + protected void onReset() { + // Do nothing. + } + // Methods to be called by subclasses. /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index c6456e5f7f..1d4d587aeb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -44,12 +44,16 @@ public interface Renderer extends PlayerMessage.Target { @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) @interface State {} /** - * The renderer is disabled. + * The renderer is disabled. A renderer in this state may hold resources that it requires for + * rendering (e.g. media decoders), for use if it's subsequently enabled. {@link #reset()} can be + * called to force the renderer to release these resources. */ int STATE_DISABLED = 0; /** - * The renderer is enabled but not started. A renderer in this state is not actively rendering - * media, but will typically hold resources that it requires for rendering (e.g. media decoders). + * The renderer is enabled but not started. A renderer in this state may render media at the + * current position (e.g. an initial video frame), but the position will not advance. A renderer + * in this state will typically hold resources that it requires for rendering (e.g. media + * decoders). */ int STATE_ENABLED = 1; /** @@ -279,4 +283,12 @@ public interface Renderer extends PlayerMessage.Target { */ void disable(); + /** + * Forces the renderer to give up any resources (e.g. media decoders) that it may be holding. If + * the renderer is not holding any resources, the call is a no-op. + * + *

This method may be called when the renderer is in the following states: {@link + * #STATE_DISABLED}. + */ + void reset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java index 4a8354d17f..663c9fe284 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java @@ -72,12 +72,12 @@ public class CameraMotionRenderer extends BaseRenderer { @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { - reset(); + resetListener(); } @Override protected void onDisabled() { - reset(); + resetListener(); } @Override @@ -124,7 +124,7 @@ public class CameraMotionRenderer extends BaseRenderer { return result; } - private void reset() { + private void resetListener() { lastTimestampUs = 0; if (listener != null) { listener.onCameraMotionReset(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java index 8dc60a15a4..48e71c619c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java @@ -103,6 +103,7 @@ public class SimpleDecoderAudioRendererTest { } verify(mockAudioSink, times(1)).playToEndOfStream(); audioRenderer.disable(); + audioRenderer.reset(); verify(mockAudioSink, times(1)).release(); } From 76a1569e79dbb3edb64ed225c075727252336927 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 07:24:07 -0700 Subject: [PATCH 05/15] Update release notes to better describe Java 8 change. Apps need to set the target compatibility to VERSION_1_8 to enable the automatic desugaring if they haven't done so already. Issue:#4907 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215733070 --- RELEASENOTES.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e2f33d9150..6f04fcd8f1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -25,9 +25,8 @@ ### 2.9.0 ### -* Turn on Java 8 compiler support for the ExoPlayer library. Apps that depend - on ExoPlayer via its source code rather than an AAR may need to add - `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their +* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to + add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their gradle settings to ensure bytecode compatibility. * Set `compileSdkVersion` and `targetSdkVersion` to 28. * Support for automatic audio focus handling via From c5f9ad9f8b9594c9abb12b25b9bc088eb3dee76c Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 4 Oct 2018 10:40:29 -0700 Subject: [PATCH 06/15] Minor fixes for period clipping - Always clip to period duration for the last chunk. We previously did this only when the last chunk explicitly exceeded the period end time. We now also do it when the chunk claims to end at the period boundary, but still contains samples that exceed it. - If pendingResetPositionUs == chunk.startTimeUs == 0 but the chunk still contains samples with negative timestamps, we now clip them by setting the decode only flag. Previously we only clipped such samples if the first chunk explicitly preceeded the start of the period. Issue: #4899 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215763467 --- .../android/exoplayer2/source/chunk/ChunkSampleStream.java | 4 ++-- .../exoplayer2/source/dash/DefaultDashChunkSource.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index a993c79b4a..9fac69b281 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -308,7 +308,7 @@ public class ChunkSampleStream implements SampleStream, S // chunk even if the sample timestamps are slightly offset from the chunk start times. seekInsideBuffer = primarySampleQueue.setReadPosition(seekToMediaChunk.getFirstSampleIndex(0)); - decodeOnlyUntilPositionUs = Long.MIN_VALUE; + decodeOnlyUntilPositionUs = 0; } else { seekInsideBuffer = primarySampleQueue.advanceTo( @@ -583,7 +583,7 @@ public class ChunkSampleStream implements SampleStream, S if (pendingReset) { boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs; // Only enable setting of the decode only flag if we're not resetting to a chunk boundary. - decodeOnlyUntilPositionUs = resetToMediaChunk ? Long.MIN_VALUE : pendingResetPositionUs; + decodeOnlyUntilPositionUs = resetToMediaChunk ? 0 : pendingResetPositionUs; pendingResetPositionUs = C.TIME_UNSET; } mediaChunk.init(mediaChunkOutput); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 37c9e313ae..1ea25ecc36 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -544,7 +544,7 @@ public class DefaultDashChunkSource implements DashChunkSource { long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1); long periodDurationUs = representationHolder.periodDurationUs; long clippedEndTimeUs = - periodDurationUs != C.TIME_UNSET && periodDurationUs < endTimeUs + periodDurationUs != C.TIME_UNSET && periodDurationUs <= endTimeUs ? periodDurationUs : C.TIME_UNSET; DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), From 8dedbd5748b87f4bda71e83bb57cb60f1808cd9d Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 5 Oct 2018 01:01:51 -0700 Subject: [PATCH 07/15] Fix release notes Markdown syntax ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215868785 --- RELEASENOTES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6f04fcd8f1..6433788846 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -348,18 +348,18 @@ begins, and poll the audio timestamp less frequently once it starts advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)). * Add an option to skip silent audio in `PlaybackParameters` - ((#2635)[https://github.com/google/ExoPlayer/issues/2635]). + ([#2635](https://github.com/google/ExoPlayer/issues/2635)). * Fix an issue where playback of TrueHD streams would get stuck after seeking due to not finding a syncframe - ((#3845)[https://github.com/google/ExoPlayer/issues/3845]). + ([#3845](https://github.com/google/ExoPlayer/issues/3845)). * Fix an issue with eac3-joc playback where a codec would fail to configure - ((#4165)[https://github.com/google/ExoPlayer/issues/4165]). + ([#4165](https://github.com/google/ExoPlayer/issues/4165)). * Handle non-empty end-of-stream buffers, to fix gapless playback of streams with encoder padding when the decoder returns a non-empty final buffer. * Allow trimming more than one sample when applying an elst audio edit via gapless playback info. * Allow overriding skipping/scaling with custom `AudioProcessor`s - ((#3142)[https://github.com/google/ExoPlayer/issues/3142]). + ([#3142](https://github.com/google/ExoPlayer/issues/3142)). * Caching: * Add release method to the `Cache` interface, and prevent multiple instances of `SimpleCache` using the same folder at the same time. From 0a7745bc03c93709e178c578a4649eb534e8a9e0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 5 Oct 2018 07:36:40 -0700 Subject: [PATCH 08/15] Fix some random Android Studio warnings. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215904019 --- .../android/exoplayer2/demo/SampleChooserActivity.java | 3 +-- .../google/android/exoplayer2/DefaultRenderersFactory.java | 2 +- .../google/android/exoplayer2/ExoPlayerImplInternal.java | 2 +- .../google/android/exoplayer2/audio/DefaultAudioSink.java | 3 +-- .../java/com/google/android/exoplayer2/drm/DrmInitData.java | 4 ++-- .../android/exoplayer2/extractor/BinarySearchSeeker.java | 3 ++- .../android/exoplayer2/extractor/mp4/Mp4Extractor.java | 2 +- .../android/exoplayer2/source/ConcatenatingMediaSource.java | 2 +- .../exoplayer2/upstream/cache/CachedRegionTracker.java | 4 ++-- .../java/com/google/android/exoplayer2/ui/PlayerView.java | 6 ++++++ 10 files changed, 18 insertions(+), 13 deletions(-) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 6395ea4c24..20e27d8d48 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -350,8 +350,7 @@ public class SampleChooserActivity extends Activity ? null : new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession); if (playlistSamples != null) { - UriSample[] playlistSamplesArray = playlistSamples.toArray( - new UriSample[playlistSamples.size()]); + UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]); return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray); } else { return new UriSample( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index cc16c43b05..6ccda2b8e9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -187,7 +187,7 @@ public class DefaultRenderersFactory implements RenderersFactory { extensionRendererMode, renderersList); buildCameraMotionRenderers(context, extensionRendererMode, renderersList); buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); - return renderersList.toArray(new Renderer[renderersList.size()]); + return renderersList.toArray(new Renderer[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 891ea48108..0c6c3ca202 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 @@ -1002,7 +1002,7 @@ import java.util.Collections; MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); boolean selectionsChangedForReadPeriod = true; - TrackSelectorResult newTrackSelectorResult = null; + TrackSelectorResult newTrackSelectorResult; while (true) { if (periodHolder == null || !periodHolder.prepared) { // The reselection did not change any prepared periods. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 4ce34ad41a..6ad56a78d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -351,8 +351,7 @@ public final class DefaultAudioSink implements AudioSink { channelMappingAudioProcessor, trimmingAudioProcessor); Collections.addAll(toIntPcmAudioProcessors, audioProcessorChain.getAudioProcessors()); - toIntPcmAvailableAudioProcessors = - toIntPcmAudioProcessors.toArray(new AudioProcessor[toIntPcmAudioProcessors.size()]); + toIntPcmAvailableAudioProcessors = toIntPcmAudioProcessors.toArray(new AudioProcessor[0]); toFloatPcmAvailableAudioProcessors = new AudioProcessor[] {new FloatResamplingAudioProcessor()}; volume = 1.0f; startMediaTimeState = START_NOT_SET; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java index ef96c7ae75..ff01cbc2b5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java @@ -97,7 +97,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ public DrmInitData(List schemeDatas) { - this(null, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); + this(null, false, schemeDatas.toArray(new SchemeData[0])); } /** @@ -105,7 +105,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ public DrmInitData(@Nullable String schemeType, List schemeDatas) { - this(schemeType, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); + this(schemeType, false, schemeDatas.toArray(new SchemeData[0])); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java index 3b0b834427..022b5b4c75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java @@ -81,8 +81,9 @@ public abstract class BinarySearchSeeker { */ public static final class OutputFrameHolder { + public final ByteBuffer byteBuffer; + public long timeUs; - public ByteBuffer byteBuffer; /** Constructs an instance, wrapping the given byte buffer. */ public OutputFrameHolder(ByteBuffer outputByteBuffer) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 17c82c2c5b..ec24bed964 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -423,7 +423,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { } this.firstVideoTrackIndex = firstVideoTrackIndex; this.durationUs = durationUs; - this.tracks = tracks.toArray(new Mp4Track[tracks.size()]); + this.tracks = tracks.toArray(new Mp4Track[0]); accumulatedSampleSizes = calculateAccumulatedSampleSizes(this.tracks); extractorOutput.endTracks(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 7418e84449..fb99eef6e7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -801,6 +801,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource activeMediaPeriods; public DeferredTimeline timeline; public int childIndex; @@ -809,7 +810,6 @@ public class ConcatenatingMediaSource extends CompositeMediaSource activeMediaPeriods; public MediaSourceHolder(MediaSource mediaSource) { this.mediaSource = mediaSource; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java index 6d090d073e..e8315385e0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.upstream.cache; import android.support.annotation.NonNull; import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.Iterator; import java.util.NavigableSet; @@ -195,8 +196,7 @@ public final class CachedRegionTracker implements Cache.Listener { @Override public int compareTo(@NonNull Region another) { - return startOffset < another.startOffset ? -1 - : startOffset == another.startOffset ? 0 : 1; + return Util.compareLong(startOffset, another.startOffset); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index e429f5bfa0..7e693111ab 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -1032,6 +1032,12 @@ public class PlayerView extends FrameLayout { if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) { return false; } + return performClick(); + } + + @Override + public boolean performClick() { + super.performClick(); return toggleControllerVisibility(); } From 048c8bd68f437ab384a573c4b5b1ba481393ecda Mon Sep 17 00:00:00 2001 From: vsethia Date: Wed, 10 Oct 2018 19:39:52 -0700 Subject: [PATCH 09/15] Snapshots are going to become the default soon and these tests are failing when reloading frmo snapshots. b/117347850. This is a no-op change as the current default == _fullboot. When we globally flip and make snapshots the default, these tests would fail and we are moving them over so that when we flip your tests still pass. https://groups.google.com[]forum/#!topic/mobile-ninjas/Y69PTAT5GyY ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216634430 --- library/ui/src/main/res/values-mr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui/src/main/res/values-mr/strings.xml b/library/ui/src/main/res/values-mr/strings.xml index 753e6ba8d8..45bac7c61f 100644 --- a/library/ui/src/main/res/values-mr/strings.xml +++ b/library/ui/src/main/res/values-mr/strings.xml @@ -11,7 +11,7 @@ एक रीपीट करा सर्व रीपीट करा शफल करा - पूर्ण स्क्रीन मोड + फुल स्क्रीन मोड डाउनलोड करा डाउनलोड डाउनलोड होत आहे From 81a9293072faabdd95f241f84b29e32217982ac9 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 11 Oct 2018 04:27:22 -0700 Subject: [PATCH 10/15] Project start position for preroll ad to content transitions ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216675981 --- RELEASENOTES.md | 3 ++ .../android/exoplayer2/MediaPeriodQueue.java | 41 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6433788846..90ba31c623 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -22,6 +22,9 @@ * Fix issue where buffered position is not updated correctly when transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). +* IMA extension: + * For preroll to live stream transitions, project forward the loading position + to avoid being behind the live window. ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 6370299334..40bc658953 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -533,6 +533,11 @@ import com.google.android.exoplayer2.util.Assertions; // until the timeline is updated. Store whether the next timeline period is ready when the // timeline is updated, to avoid repeatedly checking the same timeline. MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info; + // The expected delay until playback transitions to the new period is equal the duration of + // media that's currently buffered (assuming no interruptions). This is used to project forward + // the start position for transitions to new windows. + long bufferedDurationUs = + mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; if (mediaPeriodInfo.isLastInTimelinePeriod) { int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid); int nextPeriodIndex = @@ -550,19 +555,15 @@ import com.google.android.exoplayer2.util.Assertions; long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber; if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) { // We're starting to buffer a new window. When playback transitions to this window we'll - // want it to be from its default start position. The expected delay until playback - // transitions is equal the duration of media that's currently buffered (assuming no - // interruptions). Hence we project the default start position forward by the duration of - // the buffer, and start buffering from this point. - long defaultPositionProjectionUs = - mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; + // want it to be from its default start position, so project the default start position + // forward by the duration of the buffer, and start buffering from this point. Pair defaultPosition = timeline.getPeriodPosition( window, period, nextWindowIndex, - C.TIME_UNSET, - Math.max(0, defaultPositionProjectionUs)); + /* windowPositionUs= */ C.TIME_UNSET, + /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); if (defaultPosition == null) { return null; } @@ -603,11 +604,27 @@ import com.google.android.exoplayer2.util.Assertions; mediaPeriodInfo.contentPositionUs, currentPeriodId.windowSequenceNumber); } else { - // Play content from the ad group position. + // Play content from the ad group position. As a special case, if we're transitioning from a + // preroll ad group to content and there are no other ad groups, project the start position + // forward as if this were a transition to a new window. No attempt is made to handle + // midrolls in live streams, as it's unclear what content position should play after an ad + // (server-side dynamic ad insertion is more appropriate for this use case). + long startPositionUs = mediaPeriodInfo.contentPositionUs; + if (period.getAdGroupCount() == 1 && period.getAdGroupTimeUs(0) == 0) { + Pair defaultPosition = + timeline.getPeriodPosition( + window, + period, + period.windowIndex, + /* windowPositionUs= */ C.TIME_UNSET, + /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); + if (defaultPosition == null) { + return null; + } + startPositionUs = defaultPosition.second; + } return getMediaPeriodInfoForContent( - currentPeriodId.periodUid, - mediaPeriodInfo.contentPositionUs, - currentPeriodId.windowSequenceNumber); + currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber); } } else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) { // Play the next ad group if it's available. From 42c3ff393413966bcd3743421511a1c05b016ef1 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 11 Oct 2018 09:00:00 -0700 Subject: [PATCH 11/15] Handle rotation signaled in MKV track name from HTC devices ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216704331 --- .../extractor/mkv/MatroskaExtractor.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 63fee48771..86b750e821 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -157,6 +157,7 @@ public final class MatroskaExtractor implements Extractor { private static final int ID_FLAG_DEFAULT = 0x88; private static final int ID_FLAG_FORCED = 0x55AA; private static final int ID_DEFAULT_DURATION = 0x23E383; + private static final int ID_NAME = 0x536E; private static final int ID_CODEC_ID = 0x86; private static final int ID_CODEC_PRIVATE = 0x63A2; private static final int ID_CODEC_DELAY = 0x56AA; @@ -815,6 +816,9 @@ public final class MatroskaExtractor implements Extractor { throw new ParserException("DocType " + value + " not supported"); } break; + case ID_NAME: + currentTrack.name = value; + break; case ID_CODEC_ID: currentTrack.codecId = value; break; @@ -1463,6 +1467,7 @@ public final class MatroskaExtractor implements Extractor { case ID_MAX_FALL: return TYPE_UNSIGNED_INT; case ID_DOC_TYPE: + case ID_NAME: case ID_CODEC_ID: case ID_LANGUAGE: return TYPE_STRING; @@ -1609,6 +1614,7 @@ public final class MatroskaExtractor implements Extractor { private static final int DEFAULT_MAX_FALL = 200; // nits. // Common elements. + public String name; public String codecId; public int number; public int type; @@ -1833,10 +1839,34 @@ public final class MatroskaExtractor implements Extractor { byte[] hdrStaticInfo = getHdrStaticInfo(); colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo); } - format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, - Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, - Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, - drmInitData); + int rotationDegrees = Format.NO_VALUE; + // Some HTC devices signal rotation in track names. + if ("htc_video_rotA-000".equals(name)) { + rotationDegrees = 0; + } else if ("htc_video_rotA-090".equals(name)) { + rotationDegrees = 90; + } else if ("htc_video_rotA-180".equals(name)) { + rotationDegrees = 180; + } else if ("htc_video_rotA-270".equals(name)) { + rotationDegrees = 270; + } + format = + Format.createVideoSampleFormat( + Integer.toString(trackId), + mimeType, + /* codecs= */ null, + /* bitrate= */ Format.NO_VALUE, + maxInputSize, + width, + height, + /* frameRate= */ Format.NO_VALUE, + initializationData, + rotationDegrees, + pixelWidthHeightRatio, + projectionData, + stereoMode, + colorInfo, + drmInitData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags, From 842f622d29eb1fa3929a0b2e91f5fc425c78313b Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Oct 2018 02:57:28 -0700 Subject: [PATCH 12/15] Allow codec flushing without re-initialization For decoder reuse, we want disable() to flush the decoder. However, if the flush needs to release the decoder for some reason, it seems non-ideal to immediately re-initialize it. Re-initialization can also throw ExoPlaybackException, which we don't want for disabling. This change allows a variant of flush that wont re-initialize the decoder if it has to be released, which will be used from disable(). Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216834862 --- .../mediacodec/MediaCodecRenderer.java | 41 +++++++++++++++---- .../video/MediaCodecVideoRenderer.java | 11 +++-- .../testutil/DebugRenderersFactory.java | 9 ++-- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 87d56fdf3d..8554d84420 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -546,9 +546,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { inputStreamEnded = false; outputStreamEnded = false; - if (codec != null) { - flushCodec(); - } + flushOrReinitCodec(); formatQueue.clear(); } @@ -687,10 +685,36 @@ public abstract class MediaCodecRenderer extends BaseRenderer { decoderCounters.ensureUpdated(); } - protected void flushCodec() throws ExoPlaybackException { + /** + * Flushes the codec. If flushing is not possible, the codec will be released and re-instantiated. + * This method is a no-op if the codec is {@code null}. + * + *

The implementation of this method calls {@link #flushOrReleaseCodec()}, and {@link + * #maybeInitCodec()} if the codec needs to be re-instantiated. + * + * @throws ExoPlaybackException If an error occurs re-instantiating the codec. + */ + protected final void flushOrReinitCodec() throws ExoPlaybackException { + if (flushOrReleaseCodec()) { + maybeInitCodec(); + } + } + + /** + * Flushes the codec. If flushing is not possible, the codec will be released. This method is a + * no-op if the codec is {@code null}. + * + * @return Whether the codec was released. + */ + protected boolean flushOrReleaseCodec() { + if (codec == null) { + // Nothing to do. + return false; + } codecHotswapDeadlineMs = C.TIME_UNSET; resetInputBuffer(); resetOutputBuffer(); + codecReceivedBuffers = false; waitingForFirstSyncFrame = true; waitingForKeys = false; shouldSkipOutputBuffer = false; @@ -699,28 +723,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer { shouldSkipAdaptationWorkaroundOutputBuffer = false; if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { releaseCodec(); - maybeInitCodec(); + return true; } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { // We're already waiting to re-initialize the codec. Since we're now flushing, there's no need // to wait any longer. if (codecReinitializationIsRelease) { releaseCodec(); - maybeInitCodec(); + return true; } else { codec.flush(); - codecReceivedBuffers = false; codecReinitializationState = REINITIALIZATION_STATE_NONE; } } else { // We can flush and re-use the existing decoder. codec.flush(); - codecReceivedBuffers = false; } if (codecReconfigured && format != null) { // Any reconfiguration data that we send shortly before the flush may be discarded. We // avoid this issue by sending reconfiguration data following every flush. codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; } + return false; } private boolean initCodecWithFallback(MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder) @@ -1496,7 +1519,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { releaseCodec(); maybeInitCodec(); } else { - flushCodec(); + flushOrReinitCodec(); } } else { outputStreamEnded = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index d217b47785..2b2e628017 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -520,9 +520,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @CallSuper @Override - protected void flushCodec() throws ExoPlaybackException { - super.flushCodec(); - buffersInCodecCount = 0; + protected boolean flushOrReleaseCodec() { + try { + return super.flushOrReleaseCodec(); + } finally { + buffersInCodecCount = 0; + } } @Override @@ -859,7 +862,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { // We dropped some buffers to catch up, so update the decoder counters and flush the codec, // which releases all pending buffers buffers including the current output buffer. updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount); - flushCodec(); + flushOrReinitCodec(); return true; } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java index 627b5b72f3..39194d48fe 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java @@ -105,9 +105,12 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { } @Override - protected void flushCodec() throws ExoPlaybackException { - super.flushCodec(); - clearTimestamps(); + protected boolean flushOrReleaseCodec() { + try { + return super.flushOrReleaseCodec(); + } finally { + clearTimestamps(); + } } @Override From ba6a1189981cddb4950bb0bcff64ead93555664d Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Oct 2018 06:21:39 -0700 Subject: [PATCH 13/15] Retain decoder instance after the renderer is disabled Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216852679 --- .../exoplayer2/mediacodec/MediaCodecRenderer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 8554d84420..27b0d89a3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -558,6 +558,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override protected void onDisabled() { + if (drmSession != null || pendingDrmSession != null) { + // TODO: Do something better with this case. + onReset(); + } else { + flushOrReleaseCodec(); + } + } + + @Override + protected void onReset() { format = null; availableCodecInfos = null; try { From d511370338415733a1b9a40a23c3e9837dd5ed7d Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 01:03:23 -0700 Subject: [PATCH 14/15] Fix DashManifestParser to properly skip unknown tags Robustness fix to make sure we ignore tags with known names, but which are nested inside of unknown tags. For example we don't want to parse the third period in: ... ... ... ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217101630 --- .../dash/manifest/DashManifestParser.java | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 153856af8c..31ff7d283e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -155,6 +155,8 @@ public class DashManifestParser extends DefaultHandler : (period.startMs + periodDurationMs); periods.add(period); } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "MPD")); @@ -221,6 +223,8 @@ public class DashManifestParser extends DefaultHandler segmentBase = parseSegmentList(xpp, null); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { segmentBase = parseSegmentTemplate(xpp, null); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "Period")); @@ -409,22 +413,26 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) { String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); - } else if (data == null) { - if (XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh") - && xpp.next() == XmlPullParser.TEXT) { - // The cenc:pssh element is defined in 23001-7:2015. - data = Base64.decode(xpp.getText(), Base64.DEFAULT); - uuid = PsshAtomUtil.parseUuid(data); - if (uuid == null) { - Log.w(TAG, "Skipping malformed cenc:pssh data"); - data = null; - } - } else if (C.PLAYREADY_UUID.equals(uuid) && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") - && xpp.next() == XmlPullParser.TEXT) { - // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. - data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, - Base64.decode(xpp.getText(), Base64.DEFAULT)); + } else if (data == null + && XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh") + && xpp.next() == XmlPullParser.TEXT) { + // The cenc:pssh element is defined in 23001-7:2015. + data = Base64.decode(xpp.getText(), Base64.DEFAULT); + uuid = PsshAtomUtil.parseUuid(data); + if (uuid == null) { + Log.w(TAG, "Skipping malformed cenc:pssh data"); + data = null; } + } else if (data == null + && C.PLAYREADY_UUID.equals(uuid) + && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") + && xpp.next() == XmlPullParser.TEXT) { + // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. + data = + PsshAtomUtil.buildPsshAtom( + C.PLAYREADY_UUID, Base64.decode(xpp.getText(), Base64.DEFAULT)); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); SchemeData schemeData = @@ -462,7 +470,7 @@ public class DashManifestParser extends DefaultHandler */ protected void parseAdaptationSetChild(XmlPullParser xpp) throws XmlPullParserException, IOException { - // pass + maybeSkipTag(xpp); } // Representation parsing. @@ -526,6 +534,8 @@ public class DashManifestParser extends DefaultHandler inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); @@ -664,6 +674,8 @@ public class DashManifestParser extends DefaultHandler xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { initialization = parseInitialization(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); @@ -701,6 +713,8 @@ public class DashManifestParser extends DefaultHandler segments = new ArrayList<>(); } segments.add(parseSegmentUrl(xpp)); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); @@ -747,6 +761,8 @@ public class DashManifestParser extends DefaultHandler initialization = parseInitialization(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { timeline = parseSegmentTimeline(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate")); @@ -794,6 +810,8 @@ public class DashManifestParser extends DefaultHandler EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream); eventMessages.add(event); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "EventStream")); @@ -932,6 +950,8 @@ public class DashManifestParser extends DefaultHandler segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration)); elapsedTime += duration; } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline")); return segmentTimeline; @@ -995,6 +1015,29 @@ public class DashManifestParser extends DefaultHandler // Utility methods. + /** + * If the provided {@link XmlPullParser} is currently positioned at the start of a tag, skips + * forward to the end of that tag. + * + * @param xpp The {@link XmlPullParser}. + * @throws XmlPullParserException If an error occurs parsing the stream. + * @throws IOException If an error occurs reading the stream. + */ + public static void maybeSkipTag(XmlPullParser xpp) throws IOException, XmlPullParserException { + if (!XmlPullParserUtil.isStartTag(xpp)) { + return; + } + int depth = 1; + while (depth != 0) { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp)) { + depth++; + } else if (XmlPullParserUtil.isEndTag(xpp)) { + depth--; + } + } + } + /** * Removes unnecessary {@link SchemeData}s with null {@link SchemeData#data}. */ From 6fb8f66ba6e86850d52777bf591941d9ef479c34 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 02:49:05 -0700 Subject: [PATCH 15/15] Remove assertion causing failure on some Samsung devices The assertion was so weak it probably wouldn't detect genuine misuse of the DefaultAllocator API, so it seems fine just to remove it. We don't really know what happens when the player is allowed to continue on the affected devices, but hopefully it either "just works" or fails in a more graceful way. Issue: #4532 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217113060 --- .../exoplayer2/upstream/DefaultAllocator.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java index 06ca83dd93..71e2d8d19f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java @@ -117,19 +117,6 @@ public final class DefaultAllocator implements Allocator { Math.max(availableAllocations.length * 2, availableCount + allocations.length)); } for (Allocation allocation : allocations) { - // Weak sanity check that the allocation probably originated from this pool. - if (allocation.data != initialAllocationBlock - && allocation.data.length != individualAllocationSize) { - throw new IllegalArgumentException( - "Unexpected allocation: " - + System.identityHashCode(allocation.data) - + ", " - + System.identityHashCode(initialAllocationBlock) - + ", " - + allocation.data.length - + ", " - + individualAllocationSize); - } availableAllocations[availableCount++] = allocation; } allocatedCount -= allocations.length;