mirror of
https://github.com/samsonjs/media.git
synced 2026-04-08 11:45:51 +00:00
Directly track playback queue in AnalyticsCollector.
We currently try to keep track of the playback queue (MediaPeriodQueue)
by listening to onMediaPeriodCreated/onMediaPeriodReleased events.
This approach has some problems:
1. It's easily broken by custom MediaSources that don't report these
events correctly.
2. We need to make some assumptions about what the order of these
events actually means. For example it is currently important that
the playing period gets released last in MediaPeriodQueue.clear()
3. We don't see batched events (like MediaPeriodQueue.clear()), so that
it is impossible to keep the "last reading period" for example. This
information is needed to correctly associate renderer errors to
periods after the queue has been cleared.
All of these problems can be solved by directly tracking the queue.
This also makes the onMediaPeriodCreated/Released/ReadingStarted events
obsolete and they can be removed in a future change.
PiperOrigin-RevId: 319739993
This commit is contained in:
parent
b30e2b961f
commit
27c239d6b3
4 changed files with 220 additions and 238 deletions
|
|
@ -173,7 +173,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
this.pauseAtEndOfWindow = pauseAtEndOfWindow;
|
||||
this.eventHandler = eventHandler;
|
||||
this.clock = clock;
|
||||
this.queue = new MediaPeriodQueue();
|
||||
this.queue = new MediaPeriodQueue(analyticsCollector, eventHandler);
|
||||
|
||||
backBufferDurationUs = loadControl.getBackBufferDurationUs();
|
||||
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe();
|
||||
|
|
|
|||
|
|
@ -15,15 +15,18 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.Player.RepeatMode;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Holds a queue of media periods, from the currently playing media period at the front to the
|
||||
|
|
@ -41,6 +44,8 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
|
||||
private final Timeline.Period period;
|
||||
private final Timeline.Window window;
|
||||
@Nullable private final AnalyticsCollector analyticsCollector;
|
||||
private final Handler analyticsCollectorHandler;
|
||||
|
||||
private long nextWindowSequenceNumber;
|
||||
private @RepeatMode int repeatMode;
|
||||
|
|
@ -52,8 +57,18 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
@Nullable private Object oldFrontPeriodUid;
|
||||
private long oldFrontPeriodWindowSequenceNumber;
|
||||
|
||||
/** Creates a new media period queue. */
|
||||
public MediaPeriodQueue() {
|
||||
/**
|
||||
* Creates a new media period queue.
|
||||
*
|
||||
* @param analyticsCollector An optional {@link AnalyticsCollector} to be informed of queue
|
||||
* changes.
|
||||
* @param analyticsCollectorHandler The {@link Handler} to call {@link AnalyticsCollector} methods
|
||||
* on.
|
||||
*/
|
||||
public MediaPeriodQueue(
|
||||
@Nullable AnalyticsCollector analyticsCollector, Handler analyticsCollectorHandler) {
|
||||
this.analyticsCollector = analyticsCollector;
|
||||
this.analyticsCollectorHandler = analyticsCollectorHandler;
|
||||
period = new Timeline.Period();
|
||||
window = new Timeline.Window();
|
||||
}
|
||||
|
|
@ -168,6 +183,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
oldFrontPeriodUid = null;
|
||||
loading = newPeriodHolder;
|
||||
length++;
|
||||
notifyQueueUpdate();
|
||||
return newPeriodHolder;
|
||||
}
|
||||
|
||||
|
|
@ -203,6 +219,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
public MediaPeriodHolder advanceReadingPeriod() {
|
||||
Assertions.checkState(reading != null && reading.getNext() != null);
|
||||
reading = reading.getNext();
|
||||
notifyQueueUpdate();
|
||||
return reading;
|
||||
}
|
||||
|
||||
|
|
@ -228,6 +245,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
oldFrontPeriodWindowSequenceNumber = playing.info.id.windowSequenceNumber;
|
||||
}
|
||||
playing = playing.getNext();
|
||||
notifyQueueUpdate();
|
||||
return playing;
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +259,9 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
*/
|
||||
public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) {
|
||||
Assertions.checkState(mediaPeriodHolder != null);
|
||||
if (mediaPeriodHolder.equals(loading)) {
|
||||
return false;
|
||||
}
|
||||
boolean removedReading = false;
|
||||
loading = mediaPeriodHolder;
|
||||
while (mediaPeriodHolder.getNext() != null) {
|
||||
|
|
@ -253,22 +274,27 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
length--;
|
||||
}
|
||||
loading.setNext(null);
|
||||
notifyQueueUpdate();
|
||||
return removedReading;
|
||||
}
|
||||
|
||||
/** Clears the queue. */
|
||||
public void clear() {
|
||||
MediaPeriodHolder front = playing;
|
||||
if (front != null) {
|
||||
oldFrontPeriodUid = front.uid;
|
||||
oldFrontPeriodWindowSequenceNumber = front.info.id.windowSequenceNumber;
|
||||
removeAfter(front);
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
MediaPeriodHolder front = Assertions.checkStateNotNull(playing);
|
||||
oldFrontPeriodUid = front.uid;
|
||||
oldFrontPeriodWindowSequenceNumber = front.info.id.windowSequenceNumber;
|
||||
while (front != null) {
|
||||
front.release();
|
||||
front = front.getNext();
|
||||
}
|
||||
playing = null;
|
||||
loading = null;
|
||||
reading = null;
|
||||
length = 0;
|
||||
notifyQueueUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -392,6 +418,20 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
|
||||
// Internal methods.
|
||||
|
||||
private void notifyQueueUpdate() {
|
||||
if (analyticsCollector != null) {
|
||||
ImmutableList.Builder<MediaPeriodId> builder = ImmutableList.builder();
|
||||
@Nullable MediaPeriodHolder period = playing;
|
||||
while (period != null) {
|
||||
builder.add(period.info.id);
|
||||
period = period.getNext();
|
||||
}
|
||||
@Nullable MediaPeriodId readingPeriodId = reading == null ? null : reading.info.id;
|
||||
analyticsCollectorHandler.post(
|
||||
() -> analyticsCollector.updateMediaPeriodQueueInfo(builder.build(), readingPeriodId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
|
|
@ -45,12 +47,12 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
|
@ -72,6 +74,7 @@ public class AnalyticsCollector
|
|||
|
||||
private final CopyOnWriteArraySet<AnalyticsListener> listeners;
|
||||
private final Clock clock;
|
||||
private final Period period;
|
||||
private final Window window;
|
||||
private final MediaPeriodQueueTracker mediaPeriodQueueTracker;
|
||||
|
||||
|
|
@ -84,10 +87,11 @@ public class AnalyticsCollector
|
|||
* @param clock A {@link Clock} used to generate timestamps.
|
||||
*/
|
||||
public AnalyticsCollector(Clock clock) {
|
||||
this.clock = Assertions.checkNotNull(clock);
|
||||
this.clock = checkNotNull(clock);
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
mediaPeriodQueueTracker = new MediaPeriodQueueTracker();
|
||||
period = new Period();
|
||||
window = new Window();
|
||||
mediaPeriodQueueTracker = new MediaPeriodQueueTracker(period);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -116,8 +120,22 @@ public class AnalyticsCollector
|
|||
*/
|
||||
public void setPlayer(Player player) {
|
||||
Assertions.checkState(
|
||||
this.player == null || mediaPeriodQueueTracker.mediaPeriodInfoQueue.isEmpty());
|
||||
this.player = Assertions.checkNotNull(player);
|
||||
this.player == null || mediaPeriodQueueTracker.mediaPeriodQueue.isEmpty());
|
||||
this.player = checkNotNull(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the playback queue information used for event association.
|
||||
*
|
||||
* <p>Should only be called by the player controlling the queue and not from app code.
|
||||
*
|
||||
* @param queue The playback queue of media periods identified by their {@link MediaPeriodId}.
|
||||
* @param readingPeriod The media period in the queue that is currently being read by renderers,
|
||||
* or null if the queue is empty.
|
||||
*/
|
||||
public void updateMediaPeriodQueueInfo(
|
||||
List<MediaPeriodId> queue, @Nullable MediaPeriodId readingPeriod) {
|
||||
mediaPeriodQueueTracker.onQueueUpdated(queue, readingPeriod, checkNotNull(player));
|
||||
}
|
||||
|
||||
// External events.
|
||||
|
|
@ -138,12 +156,7 @@ public class AnalyticsCollector
|
|||
|
||||
/** Resets the analytics collector for a new playlist. */
|
||||
public final void resetForNewPlaylist() {
|
||||
// Copying the list is needed because onMediaPeriodReleased will modify the list.
|
||||
List<MediaPeriodInfo> mediaPeriodInfos =
|
||||
new ArrayList<>(mediaPeriodQueueTracker.mediaPeriodInfoQueue);
|
||||
for (MediaPeriodInfo mediaPeriodInfo : mediaPeriodInfos) {
|
||||
onMediaPeriodReleased(mediaPeriodInfo.windowIndex, mediaPeriodInfo.mediaPeriodId);
|
||||
}
|
||||
// TODO: remove method.
|
||||
}
|
||||
|
||||
// MetadataOutput implementation.
|
||||
|
|
@ -325,9 +338,17 @@ public class AnalyticsCollector
|
|||
|
||||
@Override
|
||||
public final void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
|
||||
mediaPeriodQueueTracker.onMediaPeriodCreated(
|
||||
windowIndex, mediaPeriodId, Assertions.checkNotNull(player));
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
// TODO: Remove this method, as it's no longer needed for queue tracking.
|
||||
// We won't find this media period in the tracked queue yet because onQueueUpdated is called
|
||||
// after this method. Try to use the current timeline directly if possible.
|
||||
Timeline timeline = checkNotNull(player).getCurrentTimeline();
|
||||
EventTime eventTime =
|
||||
timeline.getIndexOfPeriod(mediaPeriodId.periodUid) != C.INDEX_UNSET
|
||||
? generateEventTime(
|
||||
timeline,
|
||||
timeline.getPeriodByUid(mediaPeriodId.periodUid, period).windowIndex,
|
||||
mediaPeriodId)
|
||||
: generateEventTime(Timeline.EMPTY, windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onMediaPeriodCreated(eventTime);
|
||||
}
|
||||
|
|
@ -335,12 +356,10 @@ public class AnalyticsCollector
|
|||
|
||||
@Override
|
||||
public final void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId) {
|
||||
// TODO: Remove this method, as it's no longer needed for queue tracking.
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
if (mediaPeriodQueueTracker.onMediaPeriodReleased(
|
||||
mediaPeriodId, Assertions.checkNotNull(player))) {
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onMediaPeriodReleased(eventTime);
|
||||
}
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onMediaPeriodReleased(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +415,7 @@ public class AnalyticsCollector
|
|||
|
||||
@Override
|
||||
public final void onReadingStarted(int windowIndex, MediaPeriodId mediaPeriodId) {
|
||||
mediaPeriodQueueTracker.onReadingStarted(mediaPeriodId);
|
||||
// TODO: Remove this method, as it's no longer needed for queue tracking.
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onReadingStarted(eventTime);
|
||||
|
|
@ -429,7 +448,7 @@ public class AnalyticsCollector
|
|||
|
||||
@Override
|
||||
public final void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
mediaPeriodQueueTracker.onTimelineChanged(timeline, Assertions.checkNotNull(player));
|
||||
mediaPeriodQueueTracker.onTimelineChanged(checkNotNull(player));
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onTimelineChanged(eventTime, reason);
|
||||
|
|
@ -525,7 +544,7 @@ public class AnalyticsCollector
|
|||
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
|
||||
isSeeking = false;
|
||||
}
|
||||
mediaPeriodQueueTracker.onPositionDiscontinuity(Assertions.checkNotNull(player));
|
||||
mediaPeriodQueueTracker.onPositionDiscontinuity(checkNotNull(player));
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity(eventTime, reason);
|
||||
|
|
@ -626,10 +645,6 @@ public class AnalyticsCollector
|
|||
|
||||
// Internal methods.
|
||||
|
||||
/** Returns read-only set of registered listeners. */
|
||||
protected Set<AnalyticsListener> getListeners() {
|
||||
return Collections.unmodifiableSet(listeners);
|
||||
}
|
||||
|
||||
/** Returns a new {@link EventTime} for the specified timeline, window and media period id. */
|
||||
@RequiresNonNull("player")
|
||||
|
|
@ -659,7 +674,8 @@ public class AnalyticsCollector
|
|||
eventPositionMs =
|
||||
timeline.isEmpty() ? 0 : timeline.getWindow(windowIndex, window).getDefaultPositionMs();
|
||||
}
|
||||
@Nullable MediaPeriodInfo currentInfo = mediaPeriodQueueTracker.getCurrentPlayerMediaPeriod();
|
||||
@Nullable
|
||||
MediaPeriodId currentMediaPeriodId = mediaPeriodQueueTracker.getCurrentPlayerMediaPeriod();
|
||||
return new EventTime(
|
||||
realtimeMs,
|
||||
timeline,
|
||||
|
|
@ -668,22 +684,27 @@ public class AnalyticsCollector
|
|||
eventPositionMs,
|
||||
player.getCurrentTimeline(),
|
||||
player.getCurrentWindowIndex(),
|
||||
currentInfo == null ? null : currentInfo.mediaPeriodId,
|
||||
currentMediaPeriodId,
|
||||
player.getCurrentPosition(),
|
||||
player.getTotalBufferedDuration());
|
||||
}
|
||||
|
||||
private EventTime generateEventTime(@Nullable MediaPeriodInfo mediaPeriodInfo) {
|
||||
Assertions.checkNotNull(player);
|
||||
if (mediaPeriodInfo == null) {
|
||||
private EventTime generateEventTime(@Nullable MediaPeriodId mediaPeriodId) {
|
||||
checkNotNull(player);
|
||||
@Nullable
|
||||
Timeline knownTimeline =
|
||||
mediaPeriodId == null
|
||||
? null
|
||||
: mediaPeriodQueueTracker.getMediaPeriodIdTimeline(mediaPeriodId);
|
||||
if (mediaPeriodId == null || knownTimeline == null) {
|
||||
int windowIndex = player.getCurrentWindowIndex();
|
||||
Timeline timeline = player.getCurrentTimeline();
|
||||
boolean windowIsInTimeline = windowIndex < timeline.getWindowCount();
|
||||
return generateEventTime(
|
||||
windowIsInTimeline ? timeline : Timeline.EMPTY, windowIndex, /* mediaPeriodId= */ null);
|
||||
}
|
||||
return generateEventTime(
|
||||
mediaPeriodInfo.timeline, mediaPeriodInfo.windowIndex, mediaPeriodInfo.mediaPeriodId);
|
||||
int windowIndex = knownTimeline.getPeriodByUid(mediaPeriodId.periodUid, period).windowIndex;
|
||||
return generateEventTime(knownTimeline, windowIndex, mediaPeriodId);
|
||||
}
|
||||
|
||||
private EventTime generateCurrentPlayerMediaPeriodEventTime() {
|
||||
|
|
@ -704,11 +725,12 @@ public class AnalyticsCollector
|
|||
|
||||
private EventTime generateMediaPeriodEventTime(
|
||||
int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
Assertions.checkNotNull(player);
|
||||
checkNotNull(player);
|
||||
if (mediaPeriodId != null) {
|
||||
MediaPeriodInfo mediaPeriodInfo = mediaPeriodQueueTracker.getMediaPeriodInfo(mediaPeriodId);
|
||||
return mediaPeriodInfo != null
|
||||
? generateEventTime(mediaPeriodInfo)
|
||||
boolean isInKnownTimeline =
|
||||
mediaPeriodQueueTracker.getMediaPeriodIdTimeline(mediaPeriodId) != null;
|
||||
return isInKnownTimeline
|
||||
? generateEventTime(mediaPeriodId)
|
||||
: generateEventTime(Timeline.EMPTY, windowIndex, mediaPeriodId);
|
||||
}
|
||||
Timeline timeline = player.getCurrentTimeline();
|
||||
|
|
@ -720,161 +742,149 @@ public class AnalyticsCollector
|
|||
/** Keeps track of the active media periods and currently playing and reading media period. */
|
||||
private static final class MediaPeriodQueueTracker {
|
||||
|
||||
// TODO: Investigate reporting MediaPeriodId in renderer events and adding a listener of queue
|
||||
// changes, which would hopefully remove the need to track the queue here.
|
||||
// TODO: Investigate reporting MediaPeriodId in renderer events.
|
||||
|
||||
private final ArrayList<MediaPeriodInfo> mediaPeriodInfoQueue;
|
||||
private final HashMap<MediaPeriodId, MediaPeriodInfo> mediaPeriodIdToInfo;
|
||||
private final Period period;
|
||||
|
||||
@Nullable private MediaPeriodInfo currentPlayerMediaPeriod;
|
||||
private @MonotonicNonNull MediaPeriodInfo playingMediaPeriod;
|
||||
private @MonotonicNonNull MediaPeriodInfo readingMediaPeriod;
|
||||
private Timeline timeline;
|
||||
private ImmutableList<MediaPeriodId> mediaPeriodQueue;
|
||||
private ImmutableMap<MediaPeriodId, Timeline> mediaPeriodTimelines;
|
||||
@Nullable private MediaPeriodId currentPlayerMediaPeriod;
|
||||
private @MonotonicNonNull MediaPeriodId playingMediaPeriod;
|
||||
private @MonotonicNonNull MediaPeriodId readingMediaPeriod;
|
||||
|
||||
public MediaPeriodQueueTracker() {
|
||||
mediaPeriodInfoQueue = new ArrayList<>();
|
||||
mediaPeriodIdToInfo = new HashMap<>();
|
||||
period = new Period();
|
||||
timeline = Timeline.EMPTY;
|
||||
public MediaPeriodQueueTracker(Period period) {
|
||||
this.period = period;
|
||||
mediaPeriodQueue = ImmutableList.of();
|
||||
mediaPeriodTimelines = ImmutableMap.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodInfo} of the media period corresponding the current position of
|
||||
* Returns the {@link MediaPeriodId} of the media period corresponding the current position of
|
||||
* the player.
|
||||
*
|
||||
* <p>May be null if no matching media period has been created yet.
|
||||
*/
|
||||
@Nullable
|
||||
public MediaPeriodInfo getCurrentPlayerMediaPeriod() {
|
||||
public MediaPeriodId getCurrentPlayerMediaPeriod() {
|
||||
return currentPlayerMediaPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodInfo} of the media period at the front of the queue. If the
|
||||
* queue is empty, this is the last media period which was at the front of the queue.
|
||||
* Returns the {@link MediaPeriodId} of the media period at the front of the queue. If the queue
|
||||
* is empty, this is the last media period which was at the front of the queue.
|
||||
*
|
||||
* <p>May be null, if no media period has been created yet.
|
||||
*/
|
||||
@Nullable
|
||||
public MediaPeriodInfo getPlayingMediaPeriod() {
|
||||
public MediaPeriodId getPlayingMediaPeriod() {
|
||||
return playingMediaPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodInfo} of the media period currently being read by the player.
|
||||
* Returns the {@link MediaPeriodId} of the media period currently being read by the player. If
|
||||
* the queue is empty, this is the last media period which was read by the player.
|
||||
*
|
||||
* <p>May be null, if the player has not started reading any media period.
|
||||
* <p>May be null, if no media period has been created yet.
|
||||
*/
|
||||
@Nullable
|
||||
public MediaPeriodInfo getReadingMediaPeriod() {
|
||||
public MediaPeriodId getReadingMediaPeriod() {
|
||||
return readingMediaPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodInfo} of the media period at the end of the queue which is
|
||||
* Returns the {@link MediaPeriodId} of the media period at the end of the queue which is
|
||||
* currently loading or will be the next one loading.
|
||||
*
|
||||
* <p>May be null, if no media period is active yet.
|
||||
*/
|
||||
@Nullable
|
||||
public MediaPeriodInfo getLoadingMediaPeriod() {
|
||||
return mediaPeriodInfoQueue.isEmpty()
|
||||
? null
|
||||
: mediaPeriodInfoQueue.get(mediaPeriodInfoQueue.size() - 1);
|
||||
}
|
||||
|
||||
/** Returns the {@link MediaPeriodInfo} for the given {@link MediaPeriodId}. */
|
||||
@Nullable
|
||||
public MediaPeriodInfo getMediaPeriodInfo(MediaPeriodId mediaPeriodId) {
|
||||
return mediaPeriodIdToInfo.get(mediaPeriodId);
|
||||
}
|
||||
|
||||
/** Updates the queue with a reported position discontinuity. */
|
||||
public void onPositionDiscontinuity(Player player) {
|
||||
currentPlayerMediaPeriod = findMatchingMediaPeriodInQueue(player);
|
||||
}
|
||||
|
||||
/** Updates the queue with a reported timeline change. */
|
||||
public void onTimelineChanged(Timeline timeline, Player player) {
|
||||
for (int i = 0; i < mediaPeriodInfoQueue.size(); i++) {
|
||||
MediaPeriodInfo newMediaPeriodInfo =
|
||||
updateMediaPeriodInfoToNewTimeline(mediaPeriodInfoQueue.get(i), timeline);
|
||||
mediaPeriodInfoQueue.set(i, newMediaPeriodInfo);
|
||||
mediaPeriodIdToInfo.put(newMediaPeriodInfo.mediaPeriodId, newMediaPeriodInfo);
|
||||
}
|
||||
if (!mediaPeriodInfoQueue.isEmpty()) {
|
||||
playingMediaPeriod = mediaPeriodInfoQueue.get(0);
|
||||
} else if (playingMediaPeriod != null) {
|
||||
playingMediaPeriod = updateMediaPeriodInfoToNewTimeline(playingMediaPeriod, timeline);
|
||||
}
|
||||
if (readingMediaPeriod != null) {
|
||||
readingMediaPeriod = updateMediaPeriodInfoToNewTimeline(readingMediaPeriod, timeline);
|
||||
} else if (playingMediaPeriod != null) {
|
||||
readingMediaPeriod = playingMediaPeriod;
|
||||
}
|
||||
this.timeline = timeline;
|
||||
currentPlayerMediaPeriod = findMatchingMediaPeriodInQueue(player);
|
||||
}
|
||||
|
||||
/** Updates the queue with a newly created media period. */
|
||||
public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId, Player player) {
|
||||
int periodIndex = timeline.getIndexOfPeriod(mediaPeriodId.periodUid);
|
||||
boolean isInTimeline = periodIndex != C.INDEX_UNSET;
|
||||
MediaPeriodInfo mediaPeriodInfo =
|
||||
new MediaPeriodInfo(
|
||||
mediaPeriodId,
|
||||
isInTimeline ? timeline : Timeline.EMPTY,
|
||||
isInTimeline ? timeline.getPeriod(periodIndex, period).windowIndex : windowIndex);
|
||||
mediaPeriodInfoQueue.add(mediaPeriodInfo);
|
||||
mediaPeriodIdToInfo.put(mediaPeriodId, mediaPeriodInfo);
|
||||
playingMediaPeriod = mediaPeriodInfoQueue.get(0);
|
||||
if (currentPlayerMediaPeriod == null && isMatchingPlayingMediaPeriod(player)) {
|
||||
currentPlayerMediaPeriod = playingMediaPeriod;
|
||||
}
|
||||
if (mediaPeriodInfoQueue.size() == 1) {
|
||||
readingMediaPeriod = playingMediaPeriod;
|
||||
}
|
||||
public MediaPeriodId getLoadingMediaPeriod() {
|
||||
return mediaPeriodQueue.isEmpty() ? null : Iterables.getLast(mediaPeriodQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the queue with a released media period. Returns whether the media period was still in
|
||||
* the queue.
|
||||
* Returns the most recent {@link Timeline} for the given {@link MediaPeriodId}, or null if no
|
||||
* timeline is available.
|
||||
*/
|
||||
public boolean onMediaPeriodReleased(MediaPeriodId mediaPeriodId, Player player) {
|
||||
@Nullable MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.remove(mediaPeriodId);
|
||||
if (mediaPeriodInfo == null) {
|
||||
// The media period has already been removed from the queue in resetForNewPlaylist().
|
||||
return false;
|
||||
}
|
||||
mediaPeriodInfoQueue.remove(mediaPeriodInfo);
|
||||
if (readingMediaPeriod != null && mediaPeriodId.equals(readingMediaPeriod.mediaPeriodId)) {
|
||||
readingMediaPeriod =
|
||||
mediaPeriodInfoQueue.isEmpty()
|
||||
? Assertions.checkNotNull(playingMediaPeriod)
|
||||
: mediaPeriodInfoQueue.get(0);
|
||||
}
|
||||
if (!mediaPeriodInfoQueue.isEmpty()) {
|
||||
playingMediaPeriod = mediaPeriodInfoQueue.get(0);
|
||||
}
|
||||
if (currentPlayerMediaPeriod == null && isMatchingPlayingMediaPeriod(player)) {
|
||||
currentPlayerMediaPeriod = playingMediaPeriod;
|
||||
}
|
||||
return true;
|
||||
@Nullable
|
||||
public Timeline getMediaPeriodIdTimeline(MediaPeriodId mediaPeriodId) {
|
||||
return mediaPeriodTimelines.get(mediaPeriodId);
|
||||
}
|
||||
|
||||
/** Update the queue with a change in the reading media period. */
|
||||
public void onReadingStarted(MediaPeriodId mediaPeriodId) {
|
||||
@Nullable MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.get(mediaPeriodId);
|
||||
if (mediaPeriodInfo == null) {
|
||||
// The media period has already been removed from the queue in resetForNewPlaylist().
|
||||
/** Updates the queue tracker with a reported position discontinuity. */
|
||||
public void onPositionDiscontinuity(Player player) {
|
||||
currentPlayerMediaPeriod =
|
||||
findCurrentPlayerMediaPeriodInQueue(player, mediaPeriodQueue, playingMediaPeriod, period);
|
||||
}
|
||||
|
||||
/** Updates the queue tracker with a reported timeline change. */
|
||||
public void onTimelineChanged(Player player) {
|
||||
currentPlayerMediaPeriod =
|
||||
findCurrentPlayerMediaPeriodInQueue(player, mediaPeriodQueue, playingMediaPeriod, period);
|
||||
updateMediaPeriodTimelines(/* preferredTimeline= */ player.getCurrentTimeline());
|
||||
}
|
||||
|
||||
/** Updates the queue tracker to a new queue of media periods. */
|
||||
public void onQueueUpdated(
|
||||
List<MediaPeriodId> queue, @Nullable MediaPeriodId readingPeriod, Player player) {
|
||||
mediaPeriodQueue = ImmutableList.copyOf(queue);
|
||||
if (!queue.isEmpty()) {
|
||||
playingMediaPeriod = queue.get(0);
|
||||
readingMediaPeriod = checkNotNull(readingPeriod);
|
||||
}
|
||||
if (currentPlayerMediaPeriod == null) {
|
||||
currentPlayerMediaPeriod =
|
||||
findCurrentPlayerMediaPeriodInQueue(
|
||||
player, mediaPeriodQueue, playingMediaPeriod, period);
|
||||
}
|
||||
updateMediaPeriodTimelines(/* preferredTimeline= */ player.getCurrentTimeline());
|
||||
}
|
||||
|
||||
private void updateMediaPeriodTimelines(Timeline preferredTimeline) {
|
||||
ImmutableMap.Builder<MediaPeriodId, Timeline> builder = ImmutableMap.builder();
|
||||
if (mediaPeriodQueue.isEmpty()) {
|
||||
addTimelineForMediaPeriodId(builder, playingMediaPeriod, preferredTimeline);
|
||||
if (!Objects.equal(readingMediaPeriod, playingMediaPeriod)) {
|
||||
addTimelineForMediaPeriodId(builder, readingMediaPeriod, preferredTimeline);
|
||||
}
|
||||
if (!Objects.equal(currentPlayerMediaPeriod, playingMediaPeriod)
|
||||
&& !Objects.equal(currentPlayerMediaPeriod, readingMediaPeriod)) {
|
||||
addTimelineForMediaPeriodId(builder, currentPlayerMediaPeriod, preferredTimeline);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < mediaPeriodQueue.size(); i++) {
|
||||
addTimelineForMediaPeriodId(builder, mediaPeriodQueue.get(i), preferredTimeline);
|
||||
}
|
||||
if (!mediaPeriodQueue.contains(currentPlayerMediaPeriod)) {
|
||||
addTimelineForMediaPeriodId(builder, currentPlayerMediaPeriod, preferredTimeline);
|
||||
}
|
||||
}
|
||||
mediaPeriodTimelines = builder.build();
|
||||
}
|
||||
|
||||
private void addTimelineForMediaPeriodId(
|
||||
ImmutableMap.Builder<MediaPeriodId, Timeline> mediaPeriodTimelinesBuilder,
|
||||
@Nullable MediaPeriodId mediaPeriodId,
|
||||
Timeline preferredTimeline) {
|
||||
if (mediaPeriodId == null) {
|
||||
return;
|
||||
}
|
||||
readingMediaPeriod = mediaPeriodInfo;
|
||||
if (preferredTimeline.getIndexOfPeriod(mediaPeriodId.periodUid) != C.INDEX_UNSET) {
|
||||
mediaPeriodTimelinesBuilder.put(mediaPeriodId, preferredTimeline);
|
||||
} else {
|
||||
@Nullable Timeline existingTimeline = mediaPeriodTimelines.get(mediaPeriodId);
|
||||
if (existingTimeline != null) {
|
||||
mediaPeriodTimelinesBuilder.put(mediaPeriodId, existingTimeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MediaPeriodInfo findMatchingMediaPeriodInQueue(Player player) {
|
||||
private static MediaPeriodId findCurrentPlayerMediaPeriodInQueue(
|
||||
Player player,
|
||||
ImmutableList<MediaPeriodId> mediaPeriodQueue,
|
||||
@Nullable MediaPeriodId playingMediaPeriod,
|
||||
Period period) {
|
||||
Timeline playerTimeline = player.getCurrentTimeline();
|
||||
int playerPeriodIndex = player.getCurrentPeriodIndex();
|
||||
@Nullable
|
||||
|
|
@ -887,25 +897,21 @@ public class AnalyticsCollector
|
|||
.getPeriod(playerPeriodIndex, period)
|
||||
.getAdGroupIndexAfterPositionUs(
|
||||
C.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs());
|
||||
for (int i = 0; i < mediaPeriodInfoQueue.size(); i++) {
|
||||
MediaPeriodInfo mediaPeriodInfo = mediaPeriodInfoQueue.get(i);
|
||||
for (int i = 0; i < mediaPeriodQueue.size(); i++) {
|
||||
MediaPeriodId mediaPeriodId = mediaPeriodQueue.get(i);
|
||||
if (isMatchingMediaPeriod(
|
||||
mediaPeriodInfo,
|
||||
playerTimeline,
|
||||
player.getCurrentWindowIndex(),
|
||||
mediaPeriodId,
|
||||
playerPeriodUid,
|
||||
player.isPlayingAd(),
|
||||
player.getCurrentAdGroupIndex(),
|
||||
player.getCurrentAdIndexInAdGroup(),
|
||||
playerNextAdGroupIndex)) {
|
||||
return mediaPeriodInfo;
|
||||
return mediaPeriodId;
|
||||
}
|
||||
}
|
||||
if (mediaPeriodInfoQueue.isEmpty() && playingMediaPeriod != null) {
|
||||
if (mediaPeriodQueue.isEmpty() && playingMediaPeriod != null) {
|
||||
if (isMatchingMediaPeriod(
|
||||
playingMediaPeriod,
|
||||
playerTimeline,
|
||||
player.getCurrentWindowIndex(),
|
||||
playerPeriodUid,
|
||||
player.isPlayingAd(),
|
||||
player.getCurrentAdGroupIndex(),
|
||||
|
|
@ -917,89 +923,23 @@ public class AnalyticsCollector
|
|||
return null;
|
||||
}
|
||||
|
||||
private boolean isMatchingPlayingMediaPeriod(Player player) {
|
||||
if (playingMediaPeriod == null) {
|
||||
return false;
|
||||
}
|
||||
Timeline playerTimeline = player.getCurrentTimeline();
|
||||
int playerPeriodIndex = player.getCurrentPeriodIndex();
|
||||
@Nullable
|
||||
Object playerPeriodUid =
|
||||
playerTimeline.isEmpty() ? null : playerTimeline.getUidOfPeriod(playerPeriodIndex);
|
||||
int playerNextAdGroupIndex =
|
||||
player.isPlayingAd() || playerTimeline.isEmpty()
|
||||
? C.INDEX_UNSET
|
||||
: playerTimeline
|
||||
.getPeriod(playerPeriodIndex, period)
|
||||
.getAdGroupIndexAfterPositionUs(
|
||||
C.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs());
|
||||
return isMatchingMediaPeriod(
|
||||
playingMediaPeriod,
|
||||
playerTimeline,
|
||||
player.getCurrentWindowIndex(),
|
||||
playerPeriodUid,
|
||||
player.isPlayingAd(),
|
||||
player.getCurrentAdGroupIndex(),
|
||||
player.getCurrentAdIndexInAdGroup(),
|
||||
playerNextAdGroupIndex);
|
||||
}
|
||||
|
||||
private static boolean isMatchingMediaPeriod(
|
||||
MediaPeriodInfo mediaPeriodInfo,
|
||||
Timeline playerTimeline,
|
||||
int playerWindowIndex,
|
||||
MediaPeriodId mediaPeriodId,
|
||||
@Nullable Object playerPeriodUid,
|
||||
boolean isPlayingAd,
|
||||
int playerAdGroupIndex,
|
||||
int playerAdIndexInAdGroup,
|
||||
int playerNextAdGroupIndex) {
|
||||
if (mediaPeriodInfo.timeline.isEmpty()
|
||||
|| !mediaPeriodInfo.timeline.equals(playerTimeline)
|
||||
|| mediaPeriodInfo.windowIndex != playerWindowIndex
|
||||
|| !mediaPeriodInfo.mediaPeriodId.periodUid.equals(playerPeriodUid)) {
|
||||
if (!mediaPeriodId.periodUid.equals(playerPeriodUid)) {
|
||||
return false;
|
||||
}
|
||||
// Timeline period matches. Still need to check ad information.
|
||||
return (isPlayingAd
|
||||
&& mediaPeriodInfo.mediaPeriodId.adGroupIndex == playerAdGroupIndex
|
||||
&& mediaPeriodInfo.mediaPeriodId.adIndexInAdGroup == playerAdIndexInAdGroup)
|
||||
&& mediaPeriodId.adGroupIndex == playerAdGroupIndex
|
||||
&& mediaPeriodId.adIndexInAdGroup == playerAdIndexInAdGroup)
|
||||
|| (!isPlayingAd
|
||||
&& mediaPeriodInfo.mediaPeriodId.adGroupIndex == C.INDEX_UNSET
|
||||
&& mediaPeriodInfo.mediaPeriodId.nextAdGroupIndex == playerNextAdGroupIndex);
|
||||
}
|
||||
|
||||
private MediaPeriodInfo updateMediaPeriodInfoToNewTimeline(
|
||||
MediaPeriodInfo info, Timeline newTimeline) {
|
||||
int newPeriodIndex = newTimeline.getIndexOfPeriod(info.mediaPeriodId.periodUid);
|
||||
if (newPeriodIndex == C.INDEX_UNSET) {
|
||||
// Media period is not yet or no longer available in the new timeline. Keep it as it is.
|
||||
return info;
|
||||
}
|
||||
int newWindowIndex = newTimeline.getPeriod(newPeriodIndex, period).windowIndex;
|
||||
return new MediaPeriodInfo(info.mediaPeriodId, newTimeline, newWindowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/** Information about a media period and its associated timeline. */
|
||||
private static final class MediaPeriodInfo {
|
||||
|
||||
/** The {@link MediaPeriodId} of the media period. */
|
||||
public final MediaPeriodId mediaPeriodId;
|
||||
/**
|
||||
* The {@link Timeline} in which the media period can be found. Or {@link Timeline#EMPTY} if the
|
||||
* media period is not part of a known timeline yet.
|
||||
*/
|
||||
public final Timeline timeline;
|
||||
/**
|
||||
* The window index of the media period in the timeline. If the timeline is empty, this is the
|
||||
* prospective window index.
|
||||
*/
|
||||
public final int windowIndex;
|
||||
|
||||
public MediaPeriodInfo(MediaPeriodId mediaPeriodId, Timeline timeline, int windowIndex) {
|
||||
this.mediaPeriodId = mediaPeriodId;
|
||||
this.timeline = timeline;
|
||||
this.windowIndex = windowIndex;
|
||||
&& mediaPeriodId.adGroupIndex == C.INDEX_UNSET
|
||||
&& mediaPeriodId.nextAdGroupIndex == playerNextAdGroupIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,9 @@ public final class MediaPeriodQueueTest {
|
|||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mediaPeriodQueue = new MediaPeriodQueue();
|
||||
mediaPeriodQueue =
|
||||
new MediaPeriodQueue(
|
||||
/* analyticsCollector= */ null, Util.createHandlerForCurrentOrMainLooper());
|
||||
mediaSourceList =
|
||||
new MediaSourceList(
|
||||
mock(MediaSourceList.MediaSourceListInfoRefreshListener.class),
|
||||
|
|
|
|||
Loading…
Reference in a new issue