Commit 66d12271 by andrewlewis Committed by Oliver Woodman

Add support for in-period ads

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=160294524
parent d5c2cf79
......@@ -24,6 +24,7 @@ import android.util.Log;
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
import com.google.android.exoplayer2.ExoPlayerImplInternal.SourceInfo;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
......@@ -299,8 +300,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline.isEmpty()) {
return C.TIME_UNSET;
}
if (isPlayingAd()) {
MediaPeriodId periodId = playbackInfo.periodId;
timeline.getPeriod(periodId.periodIndex, period);
long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup);
return C.usToMs(adDurationUs);
} else {
return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
}
}
@Override
public long getCurrentPosition() {
......@@ -463,4 +471,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
}
// TODO: Add to the public ExoPlayer interface.
private boolean isPlayingAd() {
return pendingSeekAcks == 0 && playbackInfo.periodId.adGroupIndex != C.INDEX_UNSET;
}
}
......@@ -24,6 +24,8 @@ import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo;
import com.google.android.exoplayer2.source.ClippingMediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
......@@ -150,6 +152,7 @@ import java.io.IOException;
private final ExoPlayer player;
private final Timeline.Window window;
private final Timeline.Period period;
private final MediaPeriodInfoSequence mediaPeriodInfoSequence;
private PlaybackInfo playbackInfo;
private PlaybackParameters playbackParameters;
......@@ -199,6 +202,7 @@ import java.io.IOException;
enabledRenderers = new Renderer[0];
window = new Timeline.Window();
period = new Timeline.Period();
mediaPeriodInfoSequence = new MediaPeriodInfoSequence();
trackSelector.init(this);
playbackParameters = PlaybackParameters.DEFAULT;
......@@ -435,6 +439,7 @@ import java.io.IOException;
private void setRepeatModeInternal(@ExoPlayer.RepeatMode int repeatMode)
throws ExoPlaybackException {
this.repeatMode = repeatMode;
mediaPeriodInfoSequence.setRepeatMode(repeatMode);
// Find the last existing period holder that matches the new period order.
MediaPeriodHolder lastValidPeriodHolder = playingPeriodHolder != null
......@@ -442,13 +447,18 @@ import java.io.IOException;
if (lastValidPeriodHolder == null) {
return;
}
int nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.periodIndex, period,
window, repeatMode);
while (lastValidPeriodHolder.next != null && nextPeriodIndex != C.INDEX_UNSET
&& lastValidPeriodHolder.next.periodIndex == nextPeriodIndex) {
while (true) {
int nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.info.id.periodIndex,
period, window, repeatMode);
while (lastValidPeriodHolder.next != null
&& !lastValidPeriodHolder.info.isLastInTimelinePeriod) {
lastValidPeriodHolder = lastValidPeriodHolder.next;
}
if (nextPeriodIndex == C.INDEX_UNSET || lastValidPeriodHolder.next == null
|| lastValidPeriodHolder.next.info.id.periodIndex != nextPeriodIndex) {
break;
}
lastValidPeriodHolder = lastValidPeriodHolder.next;
nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.periodIndex, period,
window, repeatMode);
}
// Release any period holders that don't match the new period order.
......@@ -459,7 +469,10 @@ import java.io.IOException;
releasePeriodHoldersFrom(lastValidPeriodHolder.next);
lastValidPeriodHolder.next = null;
}
lastValidPeriodHolder.isFinal = isFinalPeriod(lastValidPeriodHolder.periodIndex);
// Update the period info for the last holder, as it may now be the last period in the timeline.
lastValidPeriodHolder.info =
mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info);
// Handle cases where loadingPeriodHolder or readingPeriodHolder have been removed.
boolean seenLoadingPeriodHolder = loadingPeriodHolderIndex <= lastValidPeriodHolder.index;
......@@ -471,9 +484,9 @@ import java.io.IOException;
if (!seenReadingPeriodHolder && playingPeriodHolder != null) {
// Renderers may have read from a period that's been removed. Seek back to the current
// position of the playing period to make sure none of the removed period is played.
int playingPeriodIndex = playingPeriodHolder.periodIndex;
long newPositionUs = seekToPeriodPosition(playingPeriodIndex, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(playingPeriodIndex, newPositionUs);
MediaPeriodId periodId = playingPeriodHolder.info.id;
long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(periodId, newPositionUs);
}
// Restart buffering if playback has ended and repetition is enabled.
......@@ -522,8 +535,7 @@ import java.io.IOException;
long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE
: playingPeriodHolder.mediaPeriod.getBufferedPositionUs();
playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE
? timeline.getPeriod(playingPeriodHolder.periodIndex, period).getDurationUs()
: bufferedPositionUs;
? playingPeriodHolder.info.durationUs : bufferedPositionUs;
}
private void doSomeWork() throws ExoPlaybackException, IOException {
......@@ -575,12 +587,11 @@ import java.io.IOException;
}
}
long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.periodIndex, period)
.getDurationUs();
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
if (allRenderersEnded
&& (playingPeriodDurationUs == C.TIME_UNSET
|| playingPeriodDurationUs <= playbackInfo.positionUs)
&& playingPeriodHolder.isFinal) {
&& playingPeriodHolder.info.isFinal) {
setState(ExoPlayer.STATE_ENDED);
stopRenderers();
} else if (state == ExoPlayer.STATE_BUFFERING) {
......@@ -656,24 +667,30 @@ import java.io.IOException;
boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
int periodIndex = periodPosition.first;
long periodPositionUs = periodPosition.second;
MediaPeriodId periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, periodPositionUs);
if (periodId.isAd()) {
seekPositionAdjusted = true;
// TODO: Resume content at periodPositionUs after the ad plays.
periodPositionUs = 0;
}
try {
if (periodIndex == playbackInfo.periodId.periodIndex
if (periodId.equals(playbackInfo.periodId)
&& ((periodPositionUs / 1000) == (playbackInfo.positionUs / 1000))) {
// Seek position equals the current position. Do nothing.
return;
}
long newPeriodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
long newPeriodPositionUs = seekToPeriodPosition(periodId, periodPositionUs);
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs;
} finally {
playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
playbackInfo = new PlaybackInfo(periodId, periodPositionUs);
eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
.sendToTarget();
}
}
private long seekToPeriodPosition(int periodIndex, long periodPositionUs)
private long seekToPeriodPosition(MediaPeriodId periodId, long periodPositionUs)
throws ExoPlaybackException {
stopRenderers();
rebuffering = false;
......@@ -689,7 +706,7 @@ import java.io.IOException;
// Clear the timeline, but keep the requested period if it is already prepared.
MediaPeriodHolder periodHolder = playingPeriodHolder;
while (periodHolder != null) {
if (periodHolder.periodIndex == periodIndex && periodHolder.prepared) {
if (shouldKeepPeriodHolder(periodId, periodPositionUs, periodHolder)) {
newPlayingPeriodHolder = periodHolder;
} else {
periodHolder.release();
......@@ -733,6 +750,19 @@ import java.io.IOException;
return periodPositionUs;
}
private boolean shouldKeepPeriodHolder(MediaPeriodId seekPeriodId, long positionUs,
MediaPeriodHolder holder) {
if (seekPeriodId.equals(holder.info.id) && holder.prepared) {
timeline.getPeriod(holder.info.id.periodIndex, period);
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs);
if (nextAdGroupIndex == C.INDEX_UNSET
|| period.getAdGroupTimeUs(nextAdGroupIndex) == holder.info.endPositionUs) {
return true;
}
}
return false;
}
private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
rendererPositionUs = playingPeriodHolder == null
? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US
......@@ -795,6 +825,7 @@ import java.io.IOException;
mediaSource.releaseSource();
mediaSource = null;
}
mediaPeriodInfoSequence.setTimeline(null);
timeline = null;
}
}
......@@ -905,7 +936,7 @@ import java.io.IOException;
}
loadingPeriodHolder.next = null;
if (loadingPeriodHolder.prepared) {
long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.startPositionUs,
long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.info.startPositionUs,
loadingPeriodHolder.toPeriodTime(rendererPositionUs));
loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false);
}
......@@ -918,19 +949,19 @@ import java.io.IOException;
private boolean isTimelineReady(long playingPeriodDurationUs) {
return playingPeriodDurationUs == C.TIME_UNSET
|| playbackInfo.positionUs < playingPeriodDurationUs
|| (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared);
|| (playingPeriodHolder.next != null
&& (playingPeriodHolder.next.prepared || playingPeriodHolder.next.info.id.isAd()));
}
private boolean haveSufficientBuffer(boolean rebuffering) {
long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared
? loadingPeriodHolder.startPositionUs
? loadingPeriodHolder.info.startPositionUs
: loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) {
if (loadingPeriodHolder.isFinal) {
if (loadingPeriodHolder.info.isFinal) {
return true;
}
loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.periodIndex, period)
.getDurationUs();
loadingPeriodBufferedPositionUs = loadingPeriodHolder.info.durationUs;
}
return loadControl.shouldStartPlayback(
loadingPeriodBufferedPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs),
......@@ -953,6 +984,7 @@ import java.io.IOException;
throws ExoPlaybackException {
Timeline oldTimeline = timeline;
timeline = timelineAndManifest.first;
mediaPeriodInfoSequence.setTimeline(timeline);
Object manifest = timelineAndManifest.second;
int processedInitialSeekCount = 0;
......@@ -968,14 +1000,20 @@ import java.io.IOException;
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
return;
}
playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second);
int periodIndex = periodPosition.first;
long positionUs = periodPosition.second;
MediaPeriodId periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs);
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
if (timeline.isEmpty()) {
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
return;
}
Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET);
playbackInfo = new PlaybackInfo(defaultPosition.first, defaultPosition.second);
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(
defaultPosition.first, defaultPosition.second);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : defaultPosition.second);
}
}
......@@ -991,7 +1029,8 @@ import java.io.IOException;
if (periodIndex == C.INDEX_UNSET) {
// We didn't find the current period in the new timeline. Attempt to resolve a subsequent
// period whose window we can restart from.
int newPeriodIndex = resolveSubsequentPeriod(periodHolder.periodIndex, oldTimeline, timeline);
int newPeriodIndex = resolveSubsequentPeriod(periodHolder.info.id.periodIndex, oldTimeline,
timeline);
if (newPeriodIndex == C.INDEX_UNSET) {
// We failed to resolve a suitable restart position.
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
......@@ -1006,23 +1045,29 @@ import java.io.IOException;
// Clear the index of each holder that doesn't contain the default position. If a holder
// contains the default position then update its index so it can be re-used when seeking.
Object newPeriodUid = period.uid;
periodHolder.periodIndex = C.INDEX_UNSET;
periodHolder.info = periodHolder.info.copyWithPeriodIndex(C.INDEX_UNSET);
while (periodHolder.next != null) {
periodHolder = periodHolder.next;
periodHolder.periodIndex = periodHolder.uid.equals(newPeriodUid)
? newPeriodIndex : C.INDEX_UNSET;
if (periodHolder.uid.equals(newPeriodUid)) {
periodHolder.info = mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(periodHolder.info,
newPeriodIndex);
} else {
periodHolder.info = periodHolder.info.copyWithPeriodIndex(C.INDEX_UNSET);
}
}
// Actually do the seek.
newPositionUs = seekToPeriodPosition(newPeriodIndex, newPositionUs);
playbackInfo = new PlaybackInfo(newPeriodIndex, newPositionUs);
MediaPeriodId periodId = new MediaPeriodId(newPeriodIndex);
newPositionUs = seekToPeriodPosition(periodId, newPositionUs);
playbackInfo = new PlaybackInfo(periodId, newPositionUs);
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
return;
}
// The current period is in the new timeline. Update the holder and playbackInfo.
periodHolder.setPeriodIndex(periodIndex, isFinalPeriod(periodIndex));
periodHolder = updatePeriodInfo(periodHolder, periodIndex);
if (periodIndex != playbackInfo.periodId.periodIndex) {
playbackInfo = playbackInfo.copyWithPeriodId(new MediaPeriodId(periodIndex));
playbackInfo =
playbackInfo.copyWithPeriodId(playbackInfo.periodId.copyWithPeriodIndex(periodIndex));
}
// If there are subsequent holders, update the index for each of them. If we find a holder
......@@ -1034,7 +1079,7 @@ import java.io.IOException;
if (periodIndex != C.INDEX_UNSET
&& periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) {
// The holder is consistent with the new timeline. Update its index and continue.
periodHolder.setPeriodIndex(periodIndex, isFinalPeriod(periodIndex));
periodHolder = updatePeriodInfo(periodHolder, periodIndex);
} else {
// The holder is inconsistent with the new timeline.
boolean seenReadingPeriodHolder =
......@@ -1042,9 +1087,9 @@ import java.io.IOException;
if (!seenReadingPeriodHolder) {
// Renderers may have read from a period that's been removed. Seek back to the current
// position of the playing period to make sure none of the removed period is played.
periodIndex = playingPeriodHolder.periodIndex;
long newPositionUs = seekToPeriodPosition(periodIndex, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(periodIndex, newPositionUs);
long newPositionUs =
seekToPeriodPosition(playingPeriodHolder.info.id, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, newPositionUs);
} else {
// Update the loading period to be the last period that's still valid, and release all
// subsequent periods.
......@@ -1060,6 +1105,17 @@ import java.io.IOException;
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
}
private MediaPeriodHolder updatePeriodInfo(MediaPeriodHolder periodHolder, int periodIndex) {
while (true) {
periodHolder.info =
mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex);
if (periodHolder.info.isLastInTimelinePeriod || periodHolder.next == null) {
return periodHolder;
}
periodHolder = periodHolder.next;
}
}
private void handleSourceInfoRefreshEndedPlayback(Object manifest,
int processedInitialSeekCount) {
// Set the playback position to (0,0) for notifying the eventHandler.
......@@ -1103,12 +1159,6 @@ import java.io.IOException;
return newPeriodIndex;
}
private boolean isFinalPeriod(int periodIndex) {
int windowIndex = timeline.getPeriod(periodIndex, period).windowIndex;
return !timeline.getWindow(windowIndex, window).isDynamic
&& timeline.isLastPeriod(periodIndex, period, window, repeatMode);
}
/**
* Converts a {@link SeekPosition} into the corresponding (periodIndex, periodPositionUs) for the
* internal timeline.
......@@ -1191,13 +1241,13 @@ import java.io.IOException;
// the end of the playing period, so advance playback to the next period.
playingPeriodHolder.release();
setPlayingPeriodHolder(playingPeriodHolder.next);
playbackInfo = new PlaybackInfo(playingPeriodHolder.periodIndex,
playingPeriodHolder.startPositionUs);
playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id,
playingPeriodHolder.info.startPositionUs);
updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
}
if (readingPeriodHolder.isFinal) {
if (readingPeriodHolder.info.isFinal) {
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
......@@ -1261,15 +1311,12 @@ import java.io.IOException;
}
private void maybeUpdateLoadingPeriod() throws IOException {
int newLoadingPeriodIndex;
MediaPeriodInfo info;
if (loadingPeriodHolder == null) {
newLoadingPeriodIndex = playbackInfo.periodId.periodIndex;
info = mediaPeriodInfoSequence.getFirstMediaPeriodInfo(playbackInfo);
} else {
int loadingPeriodIndex = loadingPeriodHolder.periodIndex;
if (loadingPeriodHolder.isFinal || !loadingPeriodHolder.isFullyBuffered()
|| timeline.getPeriod(loadingPeriodIndex, period).getDurationUs() == C.TIME_UNSET) {
// Either the existing loading period is the last period, or we are not ready to advance to
// loading the next period because it hasn't been fully buffered or its duration is unknown.
if (loadingPeriodHolder.info.isFinal || !loadingPeriodHolder.isFullyBuffered()
|| loadingPeriodHolder.info.durationUs == C.TIME_UNSET) {
return;
}
if (playingPeriodHolder != null) {
......@@ -1279,60 +1326,26 @@ import java.io.IOException;
return;
}
}
newLoadingPeriodIndex = timeline.getNextPeriodIndex(loadingPeriodIndex, period, window,
repeatMode);
if (newLoadingPeriodIndex == C.INDEX_UNSET) {
// The next period is not available yet.
mediaSource.maybeThrowSourceInfoRefreshError();
return;
info = mediaPeriodInfoSequence.getNextMediaPeriodInfo(loadingPeriodHolder.info,
loadingPeriodHolder.getRendererOffset(), rendererPositionUs);
}
}
long newLoadingPeriodStartPositionUs;
if (loadingPeriodHolder == null) {
newLoadingPeriodStartPositionUs = playbackInfo.positionUs;
} else {
int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
if (newLoadingPeriodIndex
!= timeline.getWindow(newLoadingWindowIndex, window).firstPeriodIndex) {
// We're starting to buffer a new period in the current window. Always start from the
// beginning of the period.
newLoadingPeriodStartPositionUs = 0;
} else {
// 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 = loadingPeriodHolder.getRendererOffset()
+ timeline.getPeriod(loadingPeriodHolder.periodIndex, period).getDurationUs()
- rendererPositionUs;
Pair<Integer, Long> defaultPosition = timeline.getPeriodPosition(window, period,
newLoadingWindowIndex, C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
if (defaultPosition == null) {
if (info == null) {
mediaSource.maybeThrowSourceInfoRefreshError();
return;
}
newLoadingPeriodIndex = defaultPosition.first;
newLoadingPeriodStartPositionUs = defaultPosition.second;
}
}
long rendererPositionOffsetUs = loadingPeriodHolder == null
? newLoadingPeriodStartPositionUs + RENDERER_TIMESTAMP_OFFSET_US
: (loadingPeriodHolder.getRendererOffset()
+ timeline.getPeriod(loadingPeriodHolder.periodIndex, period).getDurationUs());
? (info.startPositionUs + RENDERER_TIMESTAMP_OFFSET_US)
: (loadingPeriodHolder.getRendererOffset() + loadingPeriodHolder.info.durationUs);
int holderIndex = loadingPeriodHolder == null ? 0 : loadingPeriodHolder.index + 1;
boolean isLastPeriod = isFinalPeriod(newLoadingPeriodIndex);
timeline.getPeriod(newLoadingPeriodIndex, period, true);
Object uid = timeline.getPeriod(info.id.periodIndex, period, true).uid;
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, period.uid,
holderIndex, newLoadingPeriodIndex, isLastPeriod, newLoadingPeriodStartPositionUs);
rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, uid, holderIndex, info);
if (loadingPeriodHolder != null) {
loadingPeriodHolder.next = newPeriodHolder;
}
loadingPeriodHolder = newPeriodHolder;
loadingPeriodHolder.mediaPeriod.prepare(this, newLoadingPeriodStartPositionUs);
loadingPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs);
setIsLoading(true);
}
......@@ -1345,7 +1358,7 @@ import java.io.IOException;
if (playingPeriodHolder == null) {
// This is the first prepared period, so start playing it.
readingPeriodHolder = loadingPeriodHolder;
resetRendererPosition(readingPeriodHolder.startPositionUs);
resetRendererPosition(readingPeriodHolder.info.startPositionUs);
setPlayingPeriodHolder(readingPeriodHolder);
}
maybeContinueLoading();
......@@ -1473,9 +1486,7 @@ import java.io.IOException;
public final boolean[] mayRetainStreamFlags;
public final long rendererPositionOffsetUs;
public int periodIndex;
public long startPositionUs;
public boolean isFinal;
public MediaPeriodInfo info;
public boolean prepared;
public boolean hasEnabledTracks;
public MediaPeriodHolder next;
......@@ -1491,8 +1502,7 @@ import java.io.IOException;
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl,
MediaSource mediaSource, Object periodUid, int index, int periodIndex,
boolean isFinalPeriod, long startPositionUs) {
MediaSource mediaSource, Object periodUid, int index, MediaPeriodInfo info) {
this.renderers = renderers;
this.rendererCapabilities = rendererCapabilities;
this.rendererPositionOffsetUs = rendererPositionOffsetUs;
......@@ -1501,13 +1511,16 @@ import java.io.IOException;
this.mediaSource = mediaSource;
this.uid = Assertions.checkNotNull(periodUid);
this.index = index;
this.periodIndex = periodIndex;
this.isFinal = isFinalPeriod;
this.startPositionUs = startPositionUs;
this.info = info;
sampleStreams = new SampleStream[renderers.length];
mayRetainStreamFlags = new boolean[renderers.length];
mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(periodIndex),
loadControl.getAllocator());
MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, loadControl.getAllocator());
if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true);
clippingMediaPeriod.setClipping(0, info.endPositionUs);
mediaPeriod = clippingMediaPeriod;
}
this.mediaPeriod = mediaPeriod;
}
public long toRendererTime(long periodTimeUs) {
......@@ -1519,12 +1532,7 @@ import java.io.IOException;
}
public long getRendererOffset() {
return rendererPositionOffsetUs - startPositionUs;
}
public void setPeriodIndex(int periodIndex, boolean isFinal) {
this.periodIndex = periodIndex;
this.isFinal = isFinal;
return rendererPositionOffsetUs - info.startPositionUs;
}
public boolean isFullyBuffered() {
......@@ -1535,7 +1543,8 @@ import java.io.IOException;
public void handlePrepared() throws ExoPlaybackException {
prepared = true;
selectTracks();
startPositionUs = updatePeriodTrackSelection(startPositionUs, false);
long newStartPositionUs = updatePeriodTrackSelection(info.startPositionUs, false);
info = info.copyWithStartPositionUs(newStartPositionUs);
}
public boolean selectTracks() throws ExoPlaybackException {
......@@ -1584,7 +1593,11 @@ import java.io.IOException;
public void release() {
try {
if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
} else {
mediaSource.releasePeriod(mediaPeriod);
}
} catch (RuntimeException e) {
// There's nothing we can do.
Log.e(TAG, "Period release failed.", e);
......
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
import android.util.Pair;
import com.google.android.exoplayer2.ExoPlayer.RepeatMode;
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
/**
* Provides a sequence of {@link MediaPeriodInfo}s to the player, determining the order and
* start/end positions for {@link MediaPeriod}s to load and play.
*/
/* package */ final class MediaPeriodInfoSequence {
// TODO: Consider merging this class with the MediaPeriodHolder queue in ExoPlayerImplInternal.
/**
* Stores the information required to load and play a {@link MediaPeriod}.
*/
public static final class MediaPeriodInfo {
/**
* The media period's identifier.
*/
public final MediaPeriodId id;
/**
* The start position of the media to play within the media period, in microseconds.
*/
public final long startPositionUs;
/**
* The end position of the media to play within the media period, in microseconds, or
* {@link C#TIME_END_OF_SOURCE} if the end position is the end of the media period.
*/
public final long endPositionUs;
/**
* The duration of the media to play within the media period, in microseconds, or
* {@link C#TIME_UNSET} if not known.
*/
public final long durationUs;
/**
* Whether this is the last media period in its timeline period (e.g., a postroll ad, or a media
* period corresponding to a timeline period without ads).
*/
public final boolean isLastInTimelinePeriod;
/**
* Whether this is the last media period in the entire timeline. If true,
* {@link #isLastInTimelinePeriod} will also be true.
*/
public final boolean isFinal;
private MediaPeriodInfo(MediaPeriodId id, long startPositionUs, long endPositionUs,
long durationUs, boolean isLastInTimelinePeriod, boolean isFinal) {
this.id = id;
this.startPositionUs = startPositionUs;
this.endPositionUs = endPositionUs;
this.durationUs = durationUs;
this.isLastInTimelinePeriod = isLastInTimelinePeriod;
this.isFinal = isFinal;
}
/**
* Returns a copy of this instance with the period identifier's period index set to the
* specified value.
*/
public MediaPeriodInfo copyWithPeriodIndex(int periodIndex) {
return new MediaPeriodInfo(id.copyWithPeriodIndex(periodIndex), startPositionUs,
endPositionUs, durationUs, isLastInTimelinePeriod, isFinal);
}
/**
* Returns a copy of this instance with the start position set to the specified value.
*/
public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) {
return new MediaPeriodInfo(id, startPositionUs, endPositionUs, durationUs,
isLastInTimelinePeriod, isFinal);
}
}
private final Timeline.Period period;
private final Timeline.Window window;
private Timeline timeline;
@RepeatMode
private int repeatMode;
/**
* Creates a new media period info sequence.
*/
public MediaPeriodInfoSequence() {
period = new Timeline.Period();
window = new Timeline.Window();
}
/**
* Sets the {@link Timeline}. Call {@link #getUpdatedMediaPeriodInfo} to update period information
* taking into account the new timeline.
*/
public void setTimeline(Timeline timeline) {
this.timeline = timeline;
}
/**
* Sets the {@link RepeatMode}. Call {@link #getUpdatedMediaPeriodInfo} to update period
* information taking into account the new repeat mode.
*/
public void setRepeatMode(@RepeatMode int repeatMode) {
this.repeatMode = repeatMode;
}
/**
* Returns the first {@link MediaPeriodInfo} to play, based on the specified playback position.
*/
public MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) {
return getMediaPeriodInfo(playbackInfo.periodId, playbackInfo.startPositionUs);
}
/**
* Returns the {@link MediaPeriodInfo} following {@code currentMediaPeriodInfo}.
*
* @param currentMediaPeriodInfo The current media period info.
* @param rendererOffsetUs The current renderer offset in microseconds.
* @param rendererPositionUs The current renderer position in microseconds.
* @return The following media period info, or {@code null} if it is not yet possible to get the
* next media period info.
*/
public MediaPeriodInfo getNextMediaPeriodInfo(MediaPeriodInfo currentMediaPeriodInfo,
long rendererOffsetUs, long rendererPositionUs) {
// TODO: This method is called repeatedly from ExoPlayerImplInternal.maybeUpdateLoadingPeriod
// but if the timeline is not ready to provide the next period it can't return a non-null value
// 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.
if (currentMediaPeriodInfo.isLastInTimelinePeriod) {
int nextPeriodIndex = timeline.getNextPeriodIndex(currentMediaPeriodInfo.id.periodIndex,
period, window, repeatMode);
if (nextPeriodIndex == C.INDEX_UNSET) {
// We can't create a next period yet.
return null;
}
long startPositionUs;
int nextWindowIndex = timeline.getPeriod(nextPeriodIndex, period).windowIndex;
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 =
rendererOffsetUs + currentMediaPeriodInfo.durationUs - rendererPositionUs;
Pair<Integer, Long> defaultPosition = timeline.getPeriodPosition(window, period,
nextWindowIndex, C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
if (defaultPosition == null) {
return null;
}
nextPeriodIndex = defaultPosition.first;
startPositionUs = defaultPosition.second;
} else {
startPositionUs = 0;
}
return getMediaPeriodInfo(resolvePeriodPositionForAds(nextPeriodIndex, startPositionUs),
startPositionUs);
}
MediaPeriodId currentPeriodId = currentMediaPeriodInfo.id;
if (currentPeriodId.isAd()) {
int currentAdGroupIndex = currentPeriodId.adGroupIndex;
timeline.getPeriod(currentPeriodId.periodIndex, period);
int adCountInCurrentAdGroup = period.getAdGroupCount() == C.LENGTH_UNSET ? C.LENGTH_UNSET
: period.getAdCountInAdGroup(currentAdGroupIndex);
if (adCountInCurrentAdGroup == C.LENGTH_UNSET) {
return null;
}
int nextAdIndexInAdGroup = currentPeriodId.adIndexInAdGroup + 1;
if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) {
// Play the next ad in the ad group if it's available.
return !period.isAdAvailable(currentAdGroupIndex, nextAdIndexInAdGroup) ? null
: getMediaPeriodInfoForAd(currentPeriodId.periodIndex, currentAdGroupIndex,
nextAdIndexInAdGroup);
} else {
// Play content from the ad group position.
return getMediaPeriodInfo(new MediaPeriodId(currentPeriodId.periodIndex),
period.getAdGroupTimeUs(currentAdGroupIndex));
}
} else if (currentMediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) {
// Play the next ad group if it's available.
int nextAdGroupIndex =
period.getAdGroupIndexForPositionUs(currentMediaPeriodInfo.endPositionUs);
return !period.isAdAvailable(nextAdGroupIndex, 0) ? null
: getMediaPeriodInfoForAd(currentPeriodId.periodIndex, nextAdGroupIndex, 0);
} else {
// Check if the postroll ad should be played.
int adGroupCount = period.getAdGroupCount();
if (adGroupCount == C.LENGTH_UNSET || adGroupCount == 0
|| period.getAdGroupTimeUs(adGroupCount - 1) != C.TIME_END_OF_SOURCE
|| period.hasPlayedAdGroup(adGroupCount - 1)
|| !period.isAdAvailable(adGroupCount - 1, 0)) {
return null;
}
return getMediaPeriodInfoForAd(currentPeriodId.periodIndex, adGroupCount - 1, 0);
}
}
/**
* Resolves the specified timeline period and position to a {@link MediaPeriodId} that should be
* played, returning an identifier for an ad group if one needs to be played before the specified
* position, or an identifier for a content media period if not.
*/
public MediaPeriodId resolvePeriodPositionForAds(int periodIndex, long positionUs) {
timeline.getPeriod(periodIndex, period);
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
return adGroupIndex == C.INDEX_UNSET ? new MediaPeriodId(periodIndex)
: new MediaPeriodId(periodIndex, adGroupIndex, 0);
}
/**
* Returns the {@code mediaPeriodInfo} updated to take into account the current timeline.
*/
public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo mediaPeriodInfo) {
return getUpdatedMediaPeriodInfo(mediaPeriodInfo, mediaPeriodInfo.id);
}
/**
* Returns the {@code mediaPeriodInfo} updated to take into account the current timeline,
* resetting the identifier of the media period to the specified {@code newPeriodIndex}.
*/
public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo mediaPeriodInfo,
int newPeriodIndex) {
return getUpdatedMediaPeriodInfo(mediaPeriodInfo,
mediaPeriodInfo.id.copyWithPeriodIndex(newPeriodIndex));
}
// Internal methods.
private MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info, MediaPeriodId newId) {
long startPositionUs = info.startPositionUs;
long endPositionUs = info.endPositionUs;
boolean isLastInPeriod = isLastInPeriod(newId, endPositionUs);
boolean isLastInTimeline = isLastInTimeline(newId, isLastInPeriod);
timeline.getPeriod(newId.periodIndex, period);
long durationUs = newId.isAd()
? period.getAdDurationUs(newId.adGroupIndex, newId.adIndexInAdGroup)
: (endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endPositionUs);
return new MediaPeriodInfo(newId, startPositionUs, endPositionUs, durationUs, isLastInPeriod,
isLastInTimeline);
}
private MediaPeriodInfo getMediaPeriodInfo(MediaPeriodId id, long startPositionUs) {
timeline.getPeriod(id.periodIndex, period);
if (id.isAd()) {
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
return null;
}
return getMediaPeriodInfoForAd(id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup);
} else {
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs);
long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE
: period.getAdGroupTimeUs(nextAdGroupIndex);
return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs, endUs);
}
}
private MediaPeriodInfo getMediaPeriodInfoForAd(int periodIndex, int adGroupIndex,
int adIndexInAdGroup) {
MediaPeriodId id = new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup);
boolean isLastInPeriod = isLastInPeriod(id, C.TIME_END_OF_SOURCE);
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
long durationUs = timeline.getPeriod(id.periodIndex, period)
.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup);
return new MediaPeriodInfo(id, 0, C.TIME_END_OF_SOURCE, durationUs, isLastInPeriod,
isLastInTimeline);
}
private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs,
long endUs) {
MediaPeriodId id = new MediaPeriodId(periodIndex);
boolean isLastInPeriod = isLastInPeriod(id, endUs);
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
timeline.getPeriod(id.periodIndex, period);
long durationUs = endUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endUs;
return new MediaPeriodInfo(id, startPositionUs, endUs, durationUs, isLastInPeriod,
isLastInTimeline);
}
private boolean isLastInPeriod(MediaPeriodId id, long endPositionUs) {
int adGroupCount = timeline.getPeriod(id.periodIndex, period).getAdGroupCount();
if (adGroupCount == 0) {
return true;
}
if (adGroupCount == C.LENGTH_UNSET) {
return false;
}
int lastAdGroupIndex = adGroupCount - 1;
boolean periodHasPostrollAd = period.getAdGroupTimeUs(lastAdGroupIndex) == C.TIME_END_OF_SOURCE;
if (!id.isAd()) {
return !periodHasPostrollAd && endPositionUs == C.TIME_END_OF_SOURCE;
} else if (periodHasPostrollAd && id.adGroupIndex == lastAdGroupIndex) {
int adCountInLastAdGroup = period.getAdCountInAdGroup(lastAdGroupIndex);
return adCountInLastAdGroup != C.LENGTH_UNSET
&& id.adIndexInAdGroup == adCountInLastAdGroup - 1;
}
return false;
}
private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
int windowIndex = timeline.getPeriod(id.periodIndex, period).windowIndex;
return !timeline.getWindow(windowIndex, window).isDynamic
&& timeline.isLastPeriod(id.periodIndex, period, window, repeatMode)
&& isLastMediaPeriodInPeriod;
}
}
......@@ -235,7 +235,7 @@ public abstract class Timeline {
/**
* Holds information about a period in a {@link Timeline}. A period defines a single logical piece
* of media, for example a a media file. See {@link Timeline} for more details. The figure below
* of media, for example a media file. See {@link Timeline} for more details. The figure below
* shows some of the information defined by a period, as well as how this information relates to a
* corresponding {@link Window} in the timeline.
* <p align="center">
......@@ -264,24 +264,77 @@ public abstract class Timeline {
*/
public long durationUs;
// TODO: Remove this flag now that in-period ads are supported.
/**
* Whether this period contains an ad.
*/
public boolean isAd;
private long positionInWindowUs;
private long[] adGroupTimesUs;
private boolean[] hasPlayedAdGroup;
private int[] adCounts;
private boolean[][] isAdAvailable;
private long[][] adDurationsUs;
/**
* Sets the data held by this period.
*
* @param id An identifier for the period. Not necessarily unique.
* @param uid A unique identifier for the period.
* @param windowIndex The index of the window to which this period belongs.
* @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if
* unknown.
* @param positionInWindowUs The position of the start of this period relative to the start of
* the window to which it belongs, in milliseconds. May be negative if the start of the
* period is not within the window.
* @param isAd Whether this period is an ad.
* @return This period, for convenience.
*/
public Period set(Object id, Object uid, int windowIndex, long durationUs,
long positionInWindowUs, boolean isAd) {
return set(id, uid, windowIndex, durationUs, positionInWindowUs, isAd, null, null, null, null,
null);
}
/**
* Sets the data held by this period.
*
* @param id An identifier for the period. Not necessarily unique.
* @param uid A unique identifier for the period.
* @param windowIndex The index of the window to which this period belongs.
* @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if
* unknown.
* @param positionInWindowUs The position of the start of this period relative to the start of
* the window to which it belongs, in milliseconds. May be negative if the start of the
* period is not within the window.
* @param isAd Whether this period is an ad.
* @param adGroupTimesUs The times of ad groups relative to the start of the period, in
* microseconds. A final element with the value {@link C#TIME_END_OF_SOURCE} indicates that
* the period has a postroll ad.
* @param hasPlayedAdGroup Whether each ad group has been played.
* @param adCounts The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET}
* if the number of ads is not yet known.
* @param isAdAvailable Whether each ad in each ad group is available.
* @param adDurationsUs The duration of each ad in each ad group, in microseconds. An element
* may be {@link C#TIME_UNSET} if the duration is not yet known.
* @return This period, for convenience.
*/
public Period set(Object id, Object uid, int windowIndex, long durationUs,
long positionInWindowUs, boolean isAd, long[] adGroupTimesUs, boolean[] hasPlayedAdGroup,
int[] adCounts, boolean[][] isAdAvailable, long[][] adDurationsUs) {
this.id = id;
this.uid = uid;
this.windowIndex = windowIndex;
this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs;
this.isAd = isAd;
this.adGroupTimesUs = adGroupTimesUs;
this.hasPlayedAdGroup = hasPlayedAdGroup;
this.adCounts = adCounts;
this.isAdAvailable = isAdAvailable;
this.adDurationsUs = adDurationsUs;
return this;
}
......@@ -317,6 +370,128 @@ public abstract class Timeline {
return positionInWindowUs;
}
/**
* Returns the number of ad groups in the period.
*/
public int getAdGroupCount() {
return adGroupTimesUs == null ? 0 : adGroupTimesUs.length;
}
/**
* Returns the time of the ad group at index {@code adGroupIndex} in the period, in
* microseconds.
*
* @param adGroupIndex The ad group index.
* @return The time of the ad group at the index, in microseconds.
*/
public long getAdGroupTimeUs(int adGroupIndex) {
if (adGroupTimesUs == null) {
throw new IndexOutOfBoundsException();
}
return adGroupTimesUs[adGroupIndex];
}
/**
* Returns whether the ad group at index {@code adGroupIndex} has been played.
*
* @param adGroupIndex The ad group index.
* @return Whether the ad group at index {@code adGroupIndex} has been played.
*/
public boolean hasPlayedAdGroup(int adGroupIndex) {
if (hasPlayedAdGroup == null) {
throw new IndexOutOfBoundsException();
}
return hasPlayedAdGroup[adGroupIndex];
}
/**
* Returns the index of the ad group at or before {@code positionUs}, if that ad group is
* unplayed. Returns {@link C#INDEX_UNSET} if the ad group before {@code positionUs} has been
* played, or if there is no such ad group.
*
* @param positionUs The position at or before which to find an ad group, in microseconds.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexForPositionUs(long positionUs) {
if (adGroupTimesUs == null) {
return C.INDEX_UNSET;
}
// Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE.
// In practice we expect there to be few ad groups so the search shouldn't be expensive.
int index = adGroupTimesUs.length - 1;
while (index >= 0 && (adGroupTimesUs[index] == C.TIME_END_OF_SOURCE
|| adGroupTimesUs[index] > positionUs)) {
index--;
}
return index >= 0 && !hasPlayedAdGroup(index) ? index : C.INDEX_UNSET;
}
/**
* Returns the index of the next unplayed ad group after {@code positionUs}. Returns
* {@link C#INDEX_UNSET} if there is no such ad group.
*
* @param positionUs The position after which to find an ad group, in microseconds.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexAfterPositionUs(long positionUs) {
if (adGroupTimesUs == null) {
return C.INDEX_UNSET;
}
// Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE.
// In practice we expect there to be few ad groups so the search shouldn't be expensive.
int index = 0;
while (index < adGroupTimesUs.length && adGroupTimesUs[index] != C.TIME_END_OF_SOURCE
&& (positionUs >= adGroupTimesUs[index] || hasPlayedAdGroup(index))) {
index++;
}
return index < adGroupTimesUs.length ? index : C.INDEX_UNSET;
}
/**
* Returns the number of ads in the ad group at index {@code adGroupIndex}, or
* {@link C#LENGTH_UNSET} if not yet known.
*
* @param adGroupIndex The ad group index.
* @return The number of ads in the ad group, or {@link C#LENGTH_UNSET} if not yet known.
*/
public int getAdCountInAdGroup(int adGroupIndex) {
if (adCounts == null) {
throw new IndexOutOfBoundsException();
}
return adCounts[adGroupIndex];
}
/**
* Returns whether the URL for the specified ad is known.
*
* @param adGroupIndex The ad group index.
* @param adIndexInAdGroup The ad index in the ad group.
* @return Whether the URL for the specified ad is known.
*/
public boolean isAdAvailable(int adGroupIndex, int adIndexInAdGroup) {
return isAdAvailable != null && adGroupIndex < isAdAvailable.length
&& adIndexInAdGroup < isAdAvailable[adGroupIndex].length
&& isAdAvailable[adGroupIndex][adIndexInAdGroup];
}
/**
* Returns the duration of the ad at index {@code adIndexInAdGroup} in the ad group at
* {@code adGroupIndex}, in microseconds, or {@link C#TIME_UNSET} if not yet known.
*
* @param adGroupIndex The ad group index.
* @param adIndexInAdGroup The ad index in the ad group.
* @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known.
*/
public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) {
if (adDurationsUs == null) {
throw new IndexOutOfBoundsException();
}
if (adIndexInAdGroup >= adDurationsUs[adGroupIndex].length) {
return C.TIME_UNSET;
}
return adDurationsUs[adGroupIndex][adIndexInAdGroup];
}
}
/**
......
......@@ -48,21 +48,27 @@ public interface MediaSource {
final class MediaPeriodId {
/**
* Value for unset media period identifiers.
*/
public static final MediaPeriodId UNSET =
new MediaPeriodId(C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET);
/**
* The timeline period index.
*/
public final int periodIndex;
/**
* If the media period is in an ad break, the index of the ad break in the period.
* If the media period is in an ad group, the index of the ad group in the period.
* {@link C#INDEX_UNSET} otherwise.
*/
public final int adBreakIndex;
public final int adGroupIndex;
/**
* If the media period is in an ad break, the index of the ad in its ad break in the period.
* If the media period is in an ad group, the index of the ad in its ad group in the period.
* {@link C#INDEX_UNSET} otherwise.
*/
public final int adIndexInAdBreak;
public final int adIndexInAdGroup;
/**
* Creates a media period identifier for the specified period in the timeline.
......@@ -70,10 +76,59 @@ public interface MediaSource {
* @param periodIndex The timeline period index.
*/
public MediaPeriodId(int periodIndex) {
// TODO: Allow creation of MediaPeriodIds for ad breaks.
this(periodIndex, C.INDEX_UNSET, C.INDEX_UNSET);
}
/**
* Creates a media period identifier that identifies an ad within an ad group at the specified
* timeline period.
*
* @param periodIndex The index of the timeline period that contains the ad group.
* @param adGroupIndex The index of the ad group.
* @param adIndexInAdGroup The index of the ad in the ad group.
*/
public MediaPeriodId(int periodIndex, int adGroupIndex, int adIndexInAdGroup) {
this.periodIndex = periodIndex;
adBreakIndex = C.INDEX_UNSET;
adIndexInAdBreak = C.INDEX_UNSET;
this.adGroupIndex = adGroupIndex;
this.adIndexInAdGroup = adIndexInAdGroup;
}
/**
* Returns a copy of this period identifier but with {@code newPeriodIndex} as its period index.
*/
public MediaPeriodId copyWithPeriodIndex(int newPeriodIndex) {
return periodIndex == newPeriodIndex ? this
: new MediaPeriodId(newPeriodIndex, adGroupIndex, adIndexInAdGroup);
}
/**
* Returns whether this period identifier identifies an ad in an ad group in a period.
*/
public boolean isAd() {
return adGroupIndex != C.INDEX_UNSET;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MediaPeriodId periodId = (MediaPeriodId) obj;
return periodIndex == periodId.periodIndex && adGroupIndex == periodId.adGroupIndex
&& adIndexInAdGroup == periodId.adIndexInAdGroup;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + periodIndex;
result = 31 * result + adGroupIndex;
result = 31 * result + adIndexInAdGroup;
return result;
}
}
......
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