Commit 27c239d6 by tonihei Committed by Ian Baker

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
parent b30e2b96
...@@ -173,7 +173,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -173,7 +173,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
this.pauseAtEndOfWindow = pauseAtEndOfWindow; this.pauseAtEndOfWindow = pauseAtEndOfWindow;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.clock = clock; this.clock = clock;
this.queue = new MediaPeriodQueue(); this.queue = new MediaPeriodQueue(analyticsCollector, eventHandler);
backBufferDurationUs = loadControl.getBackBufferDurationUs(); backBufferDurationUs = loadControl.getBackBufferDurationUs();
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe(); retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe();
......
...@@ -15,15 +15,18 @@ ...@@ -15,15 +15,18 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.os.Handler;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Player.RepeatMode; 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.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions; 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 * 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; ...@@ -41,6 +44,8 @@ import com.google.android.exoplayer2.util.Assertions;
private final Timeline.Period period; private final Timeline.Period period;
private final Timeline.Window window; private final Timeline.Window window;
@Nullable private final AnalyticsCollector analyticsCollector;
private final Handler analyticsCollectorHandler;
private long nextWindowSequenceNumber; private long nextWindowSequenceNumber;
private @RepeatMode int repeatMode; private @RepeatMode int repeatMode;
...@@ -52,8 +57,18 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -52,8 +57,18 @@ import com.google.android.exoplayer2.util.Assertions;
@Nullable private Object oldFrontPeriodUid; @Nullable private Object oldFrontPeriodUid;
private long oldFrontPeriodWindowSequenceNumber; 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(); period = new Timeline.Period();
window = new Timeline.Window(); window = new Timeline.Window();
} }
...@@ -168,6 +183,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -168,6 +183,7 @@ import com.google.android.exoplayer2.util.Assertions;
oldFrontPeriodUid = null; oldFrontPeriodUid = null;
loading = newPeriodHolder; loading = newPeriodHolder;
length++; length++;
notifyQueueUpdate();
return newPeriodHolder; return newPeriodHolder;
} }
...@@ -203,6 +219,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -203,6 +219,7 @@ import com.google.android.exoplayer2.util.Assertions;
public MediaPeriodHolder advanceReadingPeriod() { public MediaPeriodHolder advanceReadingPeriod() {
Assertions.checkState(reading != null && reading.getNext() != null); Assertions.checkState(reading != null && reading.getNext() != null);
reading = reading.getNext(); reading = reading.getNext();
notifyQueueUpdate();
return reading; return reading;
} }
...@@ -228,6 +245,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -228,6 +245,7 @@ import com.google.android.exoplayer2.util.Assertions;
oldFrontPeriodWindowSequenceNumber = playing.info.id.windowSequenceNumber; oldFrontPeriodWindowSequenceNumber = playing.info.id.windowSequenceNumber;
} }
playing = playing.getNext(); playing = playing.getNext();
notifyQueueUpdate();
return playing; return playing;
} }
...@@ -241,6 +259,9 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -241,6 +259,9 @@ import com.google.android.exoplayer2.util.Assertions;
*/ */
public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) { public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) {
Assertions.checkState(mediaPeriodHolder != null); Assertions.checkState(mediaPeriodHolder != null);
if (mediaPeriodHolder.equals(loading)) {
return false;
}
boolean removedReading = false; boolean removedReading = false;
loading = mediaPeriodHolder; loading = mediaPeriodHolder;
while (mediaPeriodHolder.getNext() != null) { while (mediaPeriodHolder.getNext() != null) {
...@@ -253,22 +274,27 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -253,22 +274,27 @@ import com.google.android.exoplayer2.util.Assertions;
length--; length--;
} }
loading.setNext(null); loading.setNext(null);
notifyQueueUpdate();
return removedReading; return removedReading;
} }
/** Clears the queue. */ /** Clears the queue. */
public void clear() { public void clear() {
MediaPeriodHolder front = playing; if (length == 0) {
if (front != null) { return;
}
MediaPeriodHolder front = Assertions.checkStateNotNull(playing);
oldFrontPeriodUid = front.uid; oldFrontPeriodUid = front.uid;
oldFrontPeriodWindowSequenceNumber = front.info.id.windowSequenceNumber; oldFrontPeriodWindowSequenceNumber = front.info.id.windowSequenceNumber;
removeAfter(front); while (front != null) {
front.release(); front.release();
front = front.getNext();
} }
playing = null; playing = null;
loading = null; loading = null;
reading = null; reading = null;
length = 0; length = 0;
notifyQueueUpdate();
} }
/** /**
...@@ -392,6 +418,20 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -392,6 +418,20 @@ import com.google.android.exoplayer2.util.Assertions;
// Internal methods. // 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 * 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 * played, returning an identifier for an ad group if one needs to be played before the specified
......
...@@ -74,7 +74,9 @@ public final class MediaPeriodQueueTest { ...@@ -74,7 +74,9 @@ public final class MediaPeriodQueueTest {
@Before @Before
public void setUp() { public void setUp() {
mediaPeriodQueue = new MediaPeriodQueue(); mediaPeriodQueue =
new MediaPeriodQueue(
/* analyticsCollector= */ null, Util.createHandlerForCurrentOrMainLooper());
mediaSourceList = mediaSourceList =
new MediaSourceList( new MediaSourceList(
mock(MediaSourceList.MediaSourceListInfoRefreshListener.class), mock(MediaSourceList.MediaSourceListInfoRefreshListener.class),
......
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