Commit c1181000 by tonihei Committed by Oliver Woodman

Correctly report buffered position for multi-period window.

Currently only the buffered position in the current media period can be queried.

To achieve this, we save the buffered positions of all MediaPeriods to the
PlaybackInfo together with a list of MediaPeriodIds. ExoPlayerImpl can then
determine the correct buffered position for multi-period windows and windows
with midroll ads.

In addition, this change adds two new convenience methods to the Player interface
to query the total buffered duration across all windows and to get the buffered
duration of the content while playing an ad.

Issue:#4023

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=200041791
parent d6878f15
......@@ -21,6 +21,10 @@
* Add method to `BandwidthMeter` to return the `TransferListener` used to gather
bandwidth information.
* Add callback to `VideoListener` to notify of surface size changes.
* Fix bug when reporting buffered position for multi-period windows and add
two additional convenience methods `Player.getTotalBufferedDuration` and
`Player.getContentBufferedDuration`
([#4023](https://github.com/google/ExoPlayer/issues/4023)).
* Allow apps to register custom MIME types
([#4264](https://github.com/google/ExoPlayer/issues/4264)).
......
......@@ -527,6 +527,15 @@ public final class CastPlayer implements Player {
}
@Override
public long getTotalBufferedDuration() {
long bufferedPosition = getBufferedPosition();
long currentPosition = getCurrentPosition();
return bufferedPosition == C.TIME_UNSET || currentPosition == C.TIME_UNSET
? 0
: bufferedPosition - currentPosition;
}
@Override
public boolean isCurrentWindowDynamic() {
return !currentTimeline.isEmpty()
&& currentTimeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
......@@ -563,6 +572,11 @@ public final class CastPlayer implements Player {
return getCurrentPosition();
}
@Override
public long getContentBufferedPosition() {
return getBufferedPosition();
}
// Internal methods.
public void updateInternalState() {
......
......@@ -470,30 +470,38 @@ import java.util.concurrent.CopyOnWriteArraySet;
public long getCurrentPosition() {
if (shouldMaskPosition()) {
return maskingWindowPositionMs;
} else if (playbackInfo.periodId.isAd()) {
return C.usToMs(playbackInfo.positionUs);
} else {
return playbackInfoPositionUsToWindowPositionMs(playbackInfo.positionUs);
return periodPositionUsToWindowPositionMs(playbackInfo.periodId, playbackInfo.positionUs);
}
}
@Override
public long getBufferedPosition() {
// TODO - Implement this properly.
if (shouldMaskPosition()) {
return maskingWindowPositionMs;
} else {
return playbackInfoPositionUsToWindowPositionMs(playbackInfo.bufferedPositionUs);
if (isPlayingAd()) {
return playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId)
? C.usToMs(playbackInfo.bufferedPositionUs)
: getDuration();
}
return getContentBufferedPosition();
}
@Override
public int getBufferedPercentage() {
long position = getBufferedPosition();
long duration = getDuration();
return position == C.TIME_UNSET || duration == C.TIME_UNSET ? 0
return position == C.TIME_UNSET || duration == C.TIME_UNSET
? 0
: (duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100));
}
@Override
public long getTotalBufferedDuration() {
return Math.max(0, C.usToMs(playbackInfo.totalBufferedDurationUs));
}
@Override
public boolean isCurrentWindowDynamic() {
Timeline timeline = playbackInfo.timeline;
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
......@@ -531,6 +539,29 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@Override
public long getContentBufferedPosition() {
if (shouldMaskPosition()) {
return maskingWindowPositionMs;
}
if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber
!= playbackInfo.periodId.windowSequenceNumber) {
return playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
}
long contentBufferedPositionUs = playbackInfo.bufferedPositionUs;
if (playbackInfo.loadingMediaPeriodId.isAd()) {
Timeline.Period loadingPeriod =
playbackInfo.timeline.getPeriod(playbackInfo.loadingMediaPeriodId.periodIndex, period);
contentBufferedPositionUs =
loadingPeriod.getAdGroupTimeUs(playbackInfo.loadingMediaPeriodId.adGroupIndex);
if (contentBufferedPositionUs == C.TIME_END_OF_SOURCE) {
contentBufferedPositionUs = loadingPeriod.durationUs;
}
}
return periodPositionUsToWindowPositionMs(
playbackInfo.loadingMediaPeriodId, contentBufferedPositionUs);
}
@Override
public int getRendererCount() {
return renderers.length;
}
......@@ -649,7 +680,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
playbackState,
/* isLoading= */ false,
resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult);
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult,
playbackInfo.periodId,
playbackInfo.startPositionUs,
/* totalBufferedDurationUs= */ 0,
playbackInfo.startPositionUs);
}
private void updatePlaybackInfo(
......@@ -683,12 +718,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
}
private long playbackInfoPositionUsToWindowPositionMs(long positionUs) {
private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) {
long positionMs = C.usToMs(positionUs);
if (!playbackInfo.periodId.isAd()) {
playbackInfo.timeline.getPeriod(playbackInfo.periodId.periodIndex, period);
positionMs += period.getPositionInWindowMs();
}
playbackInfo.timeline.getPeriod(periodId.periodIndex, period);
positionMs += period.getPositionInWindowMs();
return positionMs;
}
......
......@@ -419,6 +419,7 @@ import java.util.Collections;
if (!queue.updateRepeatMode(repeatMode)) {
seekToCurrentPosition(/* sendDiscontinuity= */ true);
}
updateLoadingMediaPeriodId();
}
private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled)
......@@ -427,6 +428,7 @@ import java.util.Collections;
if (!queue.updateShuffleModeEnabled(shuffleModeEnabled)) {
seekToCurrentPosition(/* sendDiscontinuity= */ true);
}
updateLoadingMediaPeriodId();
}
private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlaybackException {
......@@ -483,11 +485,12 @@ import java.util.Collections;
playbackInfo.positionUs = periodPositionUs;
}
// Update the buffered position.
// Update the buffered position and total buffered duration.
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
playbackInfo.bufferedPositionUs =
enabledRenderers.length == 0
? playingPeriodHolder.info.durationUs
: playingPeriodHolder.getBufferedPositionUs(/* convertEosToDuration= */ true);
loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true);
playbackInfo.totalBufferedDurationUs =
playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs);
}
private void doSomeWork() throws ExoPlaybackException, IOException {
......@@ -691,6 +694,7 @@ import java.util.Collections;
resetRendererPosition(periodPositionUs);
}
updateLoadingMediaPeriodId();
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
return periodPositionUs;
}
......@@ -785,18 +789,26 @@ import java.util.Collections;
pendingMessages.clear();
nextPendingMessageIndex = 0;
}
// Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored.
MediaPeriodId mediaPeriodId =
resetPosition ? new MediaPeriodId(getFirstPeriodIndex()) : playbackInfo.periodId;
long startPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.positionUs;
long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs;
playbackInfo =
new PlaybackInfo(
resetState ? Timeline.EMPTY : playbackInfo.timeline,
resetState ? null : playbackInfo.manifest,
resetPosition ? new MediaPeriodId(getFirstPeriodIndex()) : playbackInfo.periodId,
// Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored.
resetPosition ? C.TIME_UNSET : playbackInfo.positionUs,
resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs,
mediaPeriodId,
startPositionUs,
contentPositionUs,
playbackInfo.playbackState,
/* isLoading= */ false,
resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult);
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult,
mediaPeriodId,
startPositionUs,
/* totalBufferedDurationUs= */ 0,
startPositionUs);
if (releaseMediaSource) {
if (mediaSource != null) {
mediaSource.releaseSource(/* listener= */ this);
......@@ -1051,6 +1063,7 @@ import java.util.Collections;
updateLoadControlTrackSelection(periodHolder.trackGroups, periodHolder.trackSelectorResult);
}
}
updateLoadingMediaPeriodId();
if (playbackInfo.playbackState != Player.STATE_ENDED) {
maybeContinueLoading();
updatePlaybackPositions();
......@@ -1249,6 +1262,7 @@ import java.util.Collections;
if (!queue.updateQueuedPeriods(playingPeriodId, rendererPositionUs)) {
seekToCurrentPosition(/* sendDiscontinuity= */ false);
}
updateLoadingMediaPeriodId();
}
private void handleSourceInfoRefreshEndedPlayback() {
......@@ -1494,6 +1508,7 @@ import java.util.Collections;
info);
mediaPeriod.prepare(this, info.startPositionUs);
setIsLoading(true);
updateLoadingMediaPeriodId();
}
}
}
......@@ -1620,6 +1635,13 @@ import java.util.Collections;
&& renderer.hasReadStreamToEnd();
}
private void updateLoadingMediaPeriodId() {
MediaPeriodHolder loadingMediaPeriodHolder = queue.getLoadingPeriod();
MediaPeriodId loadingMediaPeriodId =
loadingMediaPeriodHolder == null ? playbackInfo.periodId : loadingMediaPeriodHolder.info.id;
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId);
}
@NonNull
private static Format[] getFormats(TrackSelection newSelection) {
// Build an array of formats contained by the selection.
......
......@@ -127,7 +127,8 @@ import com.google.android.exoplayer2.util.Assertions;
if (!prepared) {
return info.startPositionUs;
}
long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
long bufferedPositionUs =
hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE;
return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration
? info.durationUs
: bufferedPositionUs;
......
......@@ -25,18 +25,49 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
*/
/* package */ final class PlaybackInfo {
/** The current {@link Timeline}. */
public final Timeline timeline;
/** The current manifest. */
public final @Nullable Object manifest;
/** The {@link MediaPeriodId} of the currently playing media period in the {@link #timeline}. */
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.
*/
public final long startPositionUs;
/**
* If {@link #periodId} refers to an ad, the position of the suspended content relative to the
* start of the associated period in the {@link #timeline}, in microseconds. {@link C#TIME_UNSET}
* if {@link #periodId} does not refer to an ad.
*/
public final long contentPositionUs;
/** The current playback state. One of the {@link Player}.STATE_ constants. */
public final int playbackState;
/** Whether the player is currently loading. */
public final boolean isLoading;
/** The currently available track groups. */
public final TrackGroupArray trackGroups;
/** The result of the current track selection. */
public final TrackSelectorResult trackSelectorResult;
/** The {@link MediaPeriodId} of the currently loading media period in the {@link #timeline}. */
public final MediaPeriodId loadingMediaPeriodId;
public volatile long positionUs;
/**
* Position up to which media is buffered in {@link #loadingMediaPeriodId) relative to the start
* of the associated period in the {@link #timeline}, in microseconds.
*/
public volatile long bufferedPositionUs;
/**
* Total duration of buffered media from {@link #positionUs} to {@link #bufferedPositionUs}
* including all ads.
*/
public volatile long totalBufferedDurationUs;
/**
* Current playback position in {@link #periodId} relative to the start of the associated period
* in the {@link #timeline}, in microseconds.
*/
public volatile long positionUs;
public PlaybackInfo(
Timeline timeline,
......@@ -52,7 +83,11 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
Player.STATE_IDLE,
/* isLoading= */ false,
trackGroups,
trackSelectorResult);
trackSelectorResult,
new MediaPeriodId(/* periodIndex= */ 0),
startPositionUs,
/* totalBufferedDurationUs= */ 0,
startPositionUs);
}
public PlaybackInfo(
......@@ -64,18 +99,24 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
int playbackState,
boolean isLoading,
TrackGroupArray trackGroups,
TrackSelectorResult trackSelectorResult) {
TrackSelectorResult trackSelectorResult,
MediaPeriodId loadingMediaPeriodId,
long bufferedPositionUs,
long totalBufferedDurationUs,
long positionUs) {
this.timeline = timeline;
this.manifest = manifest;
this.periodId = periodId;
this.startPositionUs = startPositionUs;
this.contentPositionUs = contentPositionUs;
this.positionUs = startPositionUs;
this.bufferedPositionUs = startPositionUs;
this.playbackState = playbackState;
this.isLoading = isLoading;
this.trackGroups = trackGroups;
this.trackSelectorResult = trackSelectorResult;
this.loadingMediaPeriodId = loadingMediaPeriodId;
this.bufferedPositionUs = bufferedPositionUs;
this.totalBufferedDurationUs = totalBufferedDurationUs;
this.positionUs = positionUs;
}
public PlaybackInfo fromNewPosition(
......@@ -89,93 +130,113 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
playbackState,
isLoading,
trackGroups,
trackSelectorResult);
trackSelectorResult,
periodId,
startPositionUs,
/* totalBufferedDurationUs= */ 0,
startPositionUs);
}
public PlaybackInfo copyWithPeriodIndex(int periodIndex) {
PlaybackInfo playbackInfo =
new PlaybackInfo(
timeline,
manifest,
periodId.copyWithPeriodIndex(periodIndex),
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult);
copyMutablePositions(this, playbackInfo);
return playbackInfo;
return new PlaybackInfo(
timeline,
manifest,
periodId.copyWithPeriodIndex(periodIndex),
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) {
PlaybackInfo playbackInfo =
new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult);
copyMutablePositions(this, playbackInfo);
return playbackInfo;
return new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
public PlaybackInfo copyWithPlaybackState(int playbackState) {
PlaybackInfo playbackInfo =
new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult);
copyMutablePositions(this, playbackInfo);
return playbackInfo;
return new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
public PlaybackInfo copyWithIsLoading(boolean isLoading) {
PlaybackInfo playbackInfo =
new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult);
copyMutablePositions(this, playbackInfo);
return playbackInfo;
return new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
public PlaybackInfo copyWithTrackInfo(
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
PlaybackInfo playbackInfo =
new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult);
copyMutablePositions(this, playbackInfo);
return playbackInfo;
return new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
private static void copyMutablePositions(PlaybackInfo from, PlaybackInfo to) {
to.positionUs = from.positionUs;
to.bufferedPositionUs = from.bufferedPositionUs;
public PlaybackInfo copyWithLoadingMediaPeriodId(MediaPeriodId loadingMediaPeriodId) {
return new PlaybackInfo(
timeline,
manifest,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
}
......@@ -678,24 +678,28 @@ public interface Player {
*/
long getDuration();
/**
* Returns the playback position in the current window, in milliseconds.
*/
/** Returns the playback position in the current content window or ad, in milliseconds. */
long getCurrentPosition();
/**
* Returns an estimate of the position in the current window up to which data is buffered, in
* milliseconds.
* Returns an estimate of the position in the current content window or ad up to which data is
* buffered, in milliseconds.
*/
long getBufferedPosition();
/**
* Returns an estimate of the percentage in the current window up to which data is buffered, or 0
* if no estimate is available.
* Returns an estimate of the percentage in the current content window or ad up to which data is
* buffered, or 0 if no estimate is available.
*/
int getBufferedPercentage();
/**
* Returns an estimate of the total buffered duration from the current position, in milliseconds.
* This includes pre-buffered data for subsequent ads and windows.
*/
long getTotalBufferedDuration();
/**
* Returns whether the current window is dynamic, or {@code false} if the {@link Timeline} is
* empty.
*
......@@ -735,4 +739,10 @@ public interface Player {
*/
long getContentPosition();
/**
* If {@link #isPlayingAd()} returns {@code true}, returns an estimate of the content position in
* the current content window up to which data is buffered, in milliseconds. If there is no ad
* playing, the returned position is the same as that returned by {@link #getBufferedPosition()}.
*/
long getContentBufferedPosition();
}
......@@ -906,6 +906,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
}
@Override
public long getTotalBufferedDuration() {
return player.getTotalBufferedDuration();
}
@Override
public boolean isCurrentWindowDynamic() {
return player.isCurrentWindowDynamic();
}
......@@ -935,6 +940,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
return player.getContentPosition();
}
@Override
public long getContentBufferedPosition() {
return player.getContentBufferedPosition();
}
// Internal methods.
/**
......
......@@ -715,7 +715,7 @@ public class PlayerControlView extends FrameLayout {
long bufferedPosition = 0;
long duration = 0;
if (player != null) {
long currentWindowTimeBarOffsetUs = 0;
long currentWindowTimeBarOffsetMs = 0;
long durationUs = 0;
int adGroupCount = 0;
Timeline timeline = player.getCurrentTimeline();
......@@ -726,7 +726,7 @@ public class PlayerControlView extends FrameLayout {
multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex;
for (int i = firstWindowIndex; i <= lastWindowIndex; i++) {
if (i == currentWindowIndex) {
currentWindowTimeBarOffsetUs = durationUs;
currentWindowTimeBarOffsetMs = C.usToMs(durationUs);
}
timeline.getWindow(i, window);
if (window.durationUs == C.TIME_UNSET) {
......@@ -762,15 +762,8 @@ public class PlayerControlView extends FrameLayout {
}
}
duration = C.usToMs(durationUs);
position = C.usToMs(currentWindowTimeBarOffsetUs);
bufferedPosition = position;
if (player.isPlayingAd()) {
position += player.getContentPosition();
bufferedPosition = position;
} else {
position += player.getCurrentPosition();
bufferedPosition += player.getBufferedPosition();
}
position = currentWindowTimeBarOffsetMs + player.getContentPosition();
bufferedPosition = currentWindowTimeBarOffsetMs + player.getContentBufferedPosition();
if (timeBar != null) {
int extraAdGroupCount = extraAdGroupTimesMs.length;
int totalAdGroupCount = adGroupCount + extraAdGroupCount;
......
......@@ -255,6 +255,11 @@ public abstract class StubExoPlayer implements ExoPlayer {
}
@Override
public long getTotalBufferedDuration() {
throw new UnsupportedOperationException();
}
@Override
public boolean isCurrentWindowDynamic() {
throw new UnsupportedOperationException();
}
......@@ -283,4 +288,9 @@ public abstract class StubExoPlayer implements ExoPlayer {
public long getContentPosition() {
throw new UnsupportedOperationException();
}
@Override
public long getContentBufferedPosition() {
throw new UnsupportedOperationException();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment