Commit 6c0d6760 by tonihei Committed by Oliver Woodman

Add callbacks for media period life cycle.

This adds callbacks for creating, releasing, and starting to read from media
periods.

Such events allow listeners to keep a list of active media periods. This is
useful to determine when no further events for a certain media period are
expected. It also allows listeners to associate renderer events unambigiously
with a reading media period.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=190462717
parent 2a85e792
......@@ -24,6 +24,8 @@
* Allow adding and removing `MediaSourceEventListener`s to MediaSources after
they have been created. Listening to events is now supported for all
media sources including composite sources.
* Added callbacks to `MediaSourceEventListener` to get notified when media
periods are created, released and being read from.
* Audio:
* Factor out `AudioTrack` position tracking from `DefaultAudioSink`.
* Fix an issue where the playback position would pause just after playback
......
......@@ -186,6 +186,20 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
}
@Override
public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
eventDispatcher.mediaPeriodCreated();
}
}
@Override
public void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId) {
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
eventDispatcher.mediaPeriodReleased();
}
}
@Override
public void onLoadStarted(
int windowIndex,
@Nullable MediaPeriodId mediaPeriodId,
......@@ -233,6 +247,13 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
}
@Override
public void onReadingStarted(int windowIndex, MediaPeriodId mediaPeriodId) {
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
eventDispatcher.readingStarted();
}
}
@Override
public void onUpstreamDiscarded(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
......
......@@ -26,6 +26,16 @@ import java.io.IOException;
public abstract class DefaultMediaSourceEventListener implements MediaSourceEventListener {
@Override
public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
// Do nothing.
}
@Override
public void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId) {
// Do nothing.
}
@Override
public void onLoadStarted(
int windowIndex,
@Nullable MediaPeriodId mediaPeriodId,
......@@ -64,6 +74,11 @@ public abstract class DefaultMediaSourceEventListener implements MediaSourceEven
}
@Override
public void onReadingStarted(int windowIndex, MediaPeriodId mediaPeriodId) {
// Do nothing.
}
@Override
public void onUpstreamDiscarded(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
// Do nothing.
......
......@@ -100,6 +100,7 @@ import java.util.Arrays;
private boolean seenFirstTrackSelection;
private boolean notifyDiscontinuity;
private boolean notifiedReadingStarted;
private int enabledTrackCount;
private TrackGroupArray tracks;
private long durationUs;
......@@ -176,6 +177,7 @@ import java.util.Arrays;
minLoadableRetryCount == ExtractorMediaSource.MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA
? ExtractorMediaSource.DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND
: minLoadableRetryCount;
eventDispatcher.mediaPeriodCreated();
}
public void release() {
......@@ -189,6 +191,7 @@ import java.util.Arrays;
loader.release(this);
handler.removeCallbacksAndMessages(null);
released = true;
eventDispatcher.mediaPeriodReleased();
}
@Override
......@@ -319,6 +322,10 @@ import java.util.Arrays;
@Override
public long readDiscontinuity() {
if (!notifiedReadingStarted) {
eventDispatcher.readingStarted();
notifiedReadingStarted = true;
}
if (notifyDiscontinuity
&& (loadingFinished || getExtractedSamplesCount() > extractedSamplesCountAtStartOfLoad)) {
notifyDiscontinuity = false;
......
......@@ -131,7 +131,8 @@ public interface MediaPeriod extends SequenceableLoader {
* <p>After this method has returned a value other than {@link C#TIME_UNSET}, all {@link
* SampleStream}s provided by the period are guaranteed to start from a key frame.
*
* <p>This method should only be called after the period has been prepared.
* <p>This method should only be called after the period has been prepared. It must be called
* before attempting to read from any {@link SampleStream}s provided by the period.
*
* @return If a discontinuity was read then the playback position in microseconds after the
* discontinuity. Else {@link C#TIME_UNSET}.
......
......@@ -134,6 +134,22 @@ public interface MediaSourceEventListener {
}
/**
* Called when a media period is created by the media source.
*
* @param windowIndex The window index in the timeline this media period belongs to.
* @param mediaPeriodId The {@link MediaPeriodId} of the created media period.
*/
void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId);
/**
* Called when a media period is released by the media source.
*
* @param windowIndex The window index in the timeline this media period belongs to.
* @param mediaPeriodId The {@link MediaPeriodId} of the released media period.
*/
void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId);
/**
* Called when a load begins.
*
* @param windowIndex The window index in the timeline of the media source this load belongs to.
......@@ -209,6 +225,14 @@ public interface MediaSourceEventListener {
boolean wasCanceled);
/**
* Called when a media period is first being read from.
*
* @param windowIndex The window index in the timeline this media period belongs to.
* @param mediaPeriodId The {@link MediaPeriodId} of the media period being read from.
*/
void onReadingStarted(int windowIndex, MediaPeriodId mediaPeriodId);
/**
* Called when data is removed from the back of a media buffer, typically so that it can be
* re-buffered in a different format.
*
......@@ -301,6 +325,38 @@ public interface MediaSourceEventListener {
}
}
/** Dispatches {@link #onMediaPeriodCreated(int, MediaPeriodId)}. */
public void mediaPeriodCreated() {
Assertions.checkState(mediaPeriodId != null);
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
final MediaSourceEventListener listener = listenerAndHandler.listener;
postOrRun(
listenerAndHandler.handler,
new Runnable() {
@Override
public void run() {
listener.onMediaPeriodCreated(windowIndex, mediaPeriodId);
}
});
}
}
/** Dispatches {@link #onMediaPeriodReleased(int, MediaPeriodId)}. */
public void mediaPeriodReleased() {
Assertions.checkState(mediaPeriodId != null);
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
final MediaSourceEventListener listener = listenerAndHandler.listener;
postOrRun(
listenerAndHandler.handler,
new Runnable() {
@Override
public void run() {
listener.onMediaPeriodReleased(windowIndex, mediaPeriodId);
}
});
}
}
/** Dispatches {@link #onLoadStarted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadStarted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs) {
loadStarted(
......@@ -560,6 +616,22 @@ public interface MediaSourceEventListener {
}
}
/** Dispatches {@link #onReadingStarted(int, MediaPeriodId)}. */
public void readingStarted() {
Assertions.checkState(mediaPeriodId != null);
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
final MediaSourceEventListener listener = listenerAndHandler.listener;
postOrRun(
listenerAndHandler.handler,
new Runnable() {
@Override
public void run() {
listener.onReadingStarted(windowIndex, mediaPeriodId);
}
});
}
}
/** Dispatches {@link #onUpstreamDiscarded(int, MediaPeriodId, MediaLoadData)}. */
public void upstreamDiscarded(int trackType, long mediaStartTimeUs, long mediaEndTimeUs) {
upstreamDiscarded(
......
......@@ -56,6 +56,7 @@ import java.util.Arrays;
/* package */ final Format format;
/* package */ final boolean treatLoadErrorsAsEndOfStream;
/* package */ boolean notifiedReadingStarted;
/* package */ boolean loadingFinished;
/* package */ boolean loadingSucceeded;
/* package */ byte[] sampleData;
......@@ -80,10 +81,12 @@ import java.util.Arrays;
tracks = new TrackGroupArray(new TrackGroup(format));
sampleStreams = new ArrayList<>();
loader = new Loader("Loader:SingleSampleMediaPeriod");
eventDispatcher.mediaPeriodCreated();
}
public void release() {
loader.release();
eventDispatcher.mediaPeriodReleased();
}
@Override
......@@ -154,6 +157,10 @@ import java.util.Arrays;
@Override
public long readDiscontinuity() {
if (!notifiedReadingStarted) {
eventDispatcher.readingStarted();
notifiedReadingStarted = true;
}
return C.TIME_UNSET;
}
......
......@@ -363,6 +363,16 @@ public class EventLogger
// MediaSourceEventListener
@Override
public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
// Do nothing.
}
@Override
public void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId) {
// Do nothing.
}
@Override
public void onLoadStarted(
int windowIndex,
@Nullable MediaPeriodId mediaPeriodId,
......@@ -401,6 +411,11 @@ public class EventLogger
}
@Override
public void onReadingStarted(int windowIndex, MediaPeriodId mediaPeriodId) {
// Do nothing.
}
@Override
public void onUpstreamDiscarded(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
// Do nothing.
......
......@@ -79,6 +79,7 @@ import java.util.List;
private DashManifest manifest;
private int periodIndex;
private List<EventStream> eventStreams;
private boolean notifiedReadingStarted;
public DashMediaPeriod(
int id,
......@@ -114,6 +115,7 @@ import java.util.List;
eventStreams);
trackGroups = result.first;
trackGroupInfos = result.second;
eventDispatcher.mediaPeriodCreated();
}
/**
......@@ -148,6 +150,7 @@ import java.util.List;
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
sampleStream.release(this);
}
eventDispatcher.mediaPeriodReleased();
}
// ChunkSampleStream.ReleaseCallback implementation.
......@@ -325,6 +328,10 @@ import java.util.List;
@Override
public long readDiscontinuity() {
if (!notifiedReadingStarted) {
eventDispatcher.readingStarted();
notifiedReadingStarted = true;
}
return C.TIME_UNSET;
}
......
......@@ -65,6 +65,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper[] sampleStreamWrappers;
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private SequenceableLoader compositeSequenceableLoader;
private boolean notifiedReadingStarted;
public HlsMediaPeriod(
HlsExtractorFactory extractorFactory,
......@@ -88,6 +89,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
continueLoadingHandler = new Handler();
sampleStreamWrappers = new HlsSampleStreamWrapper[0];
enabledSampleStreamWrappers = new HlsSampleStreamWrapper[0];
eventDispatcher.mediaPeriodCreated();
}
public void release() {
......@@ -96,6 +98,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
sampleStreamWrapper.release();
}
eventDispatcher.mediaPeriodReleased();
}
@Override
......@@ -221,6 +224,10 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
@Override
public long readDiscontinuity() {
if (!notifiedReadingStarted) {
eventDispatcher.readingStarted();
notifiedReadingStarted = true;
}
return C.TIME_UNSET;
}
......
......@@ -56,6 +56,7 @@ import java.util.ArrayList;
private SsManifest manifest;
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
private SequenceableLoader compositeSequenceableLoader;
private boolean notifiedReadingStarted;
public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
......@@ -82,6 +83,7 @@ import java.util.ArrayList;
sampleStreams = newSampleStreamArray(0);
compositeSequenceableLoader =
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams);
eventDispatcher.mediaPeriodCreated();
}
public void updateManifest(SsManifest manifest) {
......@@ -96,6 +98,7 @@ import java.util.ArrayList;
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.release();
}
eventDispatcher.mediaPeriodReleased();
}
@Override
......@@ -167,6 +170,10 @@ import java.util.ArrayList;
@Override
public long readDiscontinuity() {
if (!notifiedReadingStarted) {
eventDispatcher.readingStarted();
notifiedReadingStarted = true;
}
return C.TIME_UNSET;
}
......
......@@ -58,10 +58,10 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
@Override
public void release() {
super.release();
for (ChunkSampleStream<FakeChunkSource> sampleStream : sampleStreams) {
sampleStream.release();
}
super.release();
}
@Override
......
......@@ -48,6 +48,7 @@ public class FakeMediaPeriod implements MediaPeriod {
@Nullable private Callback prepareCallback;
private boolean deferOnPrepared;
private boolean notifiedReadingStarted;
private boolean prepared;
private long seekOffsetUs;
private long discontinuityPositionUs;
......@@ -73,6 +74,7 @@ public class FakeMediaPeriod implements MediaPeriod {
this.eventDispatcher = eventDispatcher;
this.deferOnPrepared = deferOnPrepared;
discontinuityPositionUs = C.TIME_UNSET;
eventDispatcher.mediaPeriodCreated();
}
/**
......@@ -112,6 +114,7 @@ public class FakeMediaPeriod implements MediaPeriod {
public void release() {
prepared = false;
eventDispatcher.mediaPeriodReleased();
}
@Override
......@@ -182,6 +185,10 @@ public class FakeMediaPeriod implements MediaPeriod {
@Override
public long readDiscontinuity() {
assertThat(prepared).isTrue();
if (!notifiedReadingStarted) {
eventDispatcher.readingStarted();
notifiedReadingStarted = true;
}
long positionDiscontinuityUs = this.discontinuityPositionUs;
this.discontinuityPositionUs = C.TIME_UNSET;
return positionDiscontinuityUs;
......
......@@ -47,6 +47,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/** A runner for {@link MediaSource} tests. */
public class MediaSourceTestRunner {
......@@ -62,6 +63,9 @@ public class MediaSourceTestRunner {
private final LinkedBlockingDeque<Timeline> timelines;
private final CopyOnWriteArrayList<Pair<Integer, MediaPeriodId>> completedLoads;
private final AtomicReference<MediaPeriodId> lastCreatedMediaPeriod;
private final AtomicReference<MediaPeriodId> lastReleasedMediaPeriod;
private Timeline timeline;
/**
......@@ -79,6 +83,8 @@ public class MediaSourceTestRunner {
mediaSourceListener = new MediaSourceListener();
timelines = new LinkedBlockingDeque<>();
completedLoads = new CopyOnWriteArrayList<>();
lastCreatedMediaPeriod = new AtomicReference<>();
lastReleasedMediaPeriod = new AtomicReference<>();
mediaSource.addEventListener(playbackHandler, mediaSourceListener);
}
......@@ -282,16 +288,20 @@ public class MediaSourceTestRunner {
private void assertPrepareAndReleasePeriod(MediaPeriodId mediaPeriodId)
throws InterruptedException {
MediaPeriod mediaPeriod = createPeriod(mediaPeriodId);
assertThat(lastCreatedMediaPeriod.getAndSet(/* newValue= */ null)).isEqualTo(mediaPeriodId);
CountDownLatch preparedCondition = preparePeriod(mediaPeriod, 0);
assertThat(preparedCondition.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
// MediaSource is supposed to support multiple calls to createPeriod with the same id without an
// intervening call to releasePeriod.
MediaPeriod secondMediaPeriod = createPeriod(mediaPeriodId);
assertThat(lastCreatedMediaPeriod.getAndSet(/* newValue= */ null)).isEqualTo(mediaPeriodId);
CountDownLatch secondPreparedCondition = preparePeriod(secondMediaPeriod, 0);
assertThat(secondPreparedCondition.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
// Release the periods.
releasePeriod(mediaPeriod);
assertThat(lastReleasedMediaPeriod.getAndSet(/* newValue= */ null)).isEqualTo(mediaPeriodId);
releasePeriod(secondMediaPeriod);
assertThat(lastReleasedMediaPeriod.getAndSet(/* newValue= */ null)).isEqualTo(mediaPeriodId);
}
/**
......@@ -355,6 +365,18 @@ public class MediaSourceTestRunner {
// MediaSourceEventListener methods.
@Override
public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
lastCreatedMediaPeriod.set(mediaPeriodId);
}
@Override
public void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
lastReleasedMediaPeriod.set(mediaPeriodId);
}
@Override
public void onLoadStarted(
int windowIndex,
@Nullable MediaPeriodId mediaPeriodId,
......@@ -394,6 +416,11 @@ public class MediaSourceTestRunner {
}
@Override
public void onReadingStarted(int windowIndex, MediaPeriodId mediaPeriodId) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
}
@Override
public void onUpstreamDiscarded(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
......
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