Commit 36fa9d5a by bachinger Committed by Oliver Woodman

add top-level playlist API

Design doc: https://docs.google.com/document/d/11h0S91KI5TB3NNZUtsCzg0S7r6nyTnF_tDZZAtmY93g/edit

Issue: #6161, #5155
PiperOrigin-RevId: 286020313
parent 43bbc172
Showing with 493 additions and 141 deletions
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
([6773](https://github.com/google/ExoPlayer/issues/6773)). ([6773](https://github.com/google/ExoPlayer/issues/6773)).
* Suppress ProGuard warnings for compile-time `javax.annotation` package * Suppress ProGuard warnings for compile-time `javax.annotation` package
([#6771](https://github.com/google/ExoPlayer/issues/6771)). ([#6771](https://github.com/google/ExoPlayer/issues/6771)).
* Add playlist API ([#6161](https://github.com/google/ExoPlayer/issues/6161)).
### 2.11.0 (2019-12-11) ### ### 2.11.0 (2019-12-11) ###
......
...@@ -51,7 +51,6 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep ...@@ -51,7 +51,6 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep
import com.google.android.exoplayer2.offline.DownloadHelper; import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.DownloadRequest; import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.MergingMediaSource;
...@@ -82,6 +81,8 @@ import java.lang.reflect.Constructor; ...@@ -82,6 +81,8 @@ import java.lang.reflect.Constructor;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
import java.util.ArrayList;
import java.util.List;
/** An activity that plays media using {@link SimpleExoPlayer}. */ /** An activity that plays media using {@link SimpleExoPlayer}. */
public class PlayerActivity extends AppCompatActivity public class PlayerActivity extends AppCompatActivity
...@@ -147,7 +148,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -147,7 +148,7 @@ public class PlayerActivity extends AppCompatActivity
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
private SimpleExoPlayer player; private SimpleExoPlayer player;
private MediaSource mediaSource; private List<MediaSource> mediaSources;
private DefaultTrackSelector trackSelector; private DefaultTrackSelector trackSelector;
private DefaultTrackSelector.Parameters trackSelectorParameters; private DefaultTrackSelector.Parameters trackSelectorParameters;
private DebugTextViewHelper debugViewHelper; private DebugTextViewHelper debugViewHelper;
...@@ -349,12 +350,10 @@ public class PlayerActivity extends AppCompatActivity ...@@ -349,12 +350,10 @@ public class PlayerActivity extends AppCompatActivity
private void initializePlayer() { private void initializePlayer() {
if (player == null) { if (player == null) {
Intent intent = getIntent(); Intent intent = getIntent();
mediaSources = createTopLevelMediaSources(intent);
mediaSource = createTopLevelMediaSource(intent); if (mediaSources.isEmpty()) {
if (mediaSource == null) {
return; return;
} }
TrackSelection.Factory trackSelectionFactory; TrackSelection.Factory trackSelectionFactory;
String abrAlgorithm = intent.getStringExtra(ABR_ALGORITHM_EXTRA); String abrAlgorithm = intent.getStringExtra(ABR_ALGORITHM_EXTRA);
if (abrAlgorithm == null || ABR_ALGORITHM_DEFAULT.equals(abrAlgorithm)) { if (abrAlgorithm == null || ABR_ALGORITHM_DEFAULT.equals(abrAlgorithm)) {
...@@ -395,13 +394,12 @@ public class PlayerActivity extends AppCompatActivity ...@@ -395,13 +394,12 @@ public class PlayerActivity extends AppCompatActivity
if (haveStartPosition) { if (haveStartPosition) {
player.seekTo(startWindow, startPosition); player.seekTo(startWindow, startPosition);
} }
player.setMediaSource(mediaSource); player.setMediaSources(mediaSources, /* resetPosition= */ !haveStartPosition);
player.prepare(); player.prepare();
updateButtonVisibility(); updateButtonVisibility();
} }
@Nullable private List<MediaSource> createTopLevelMediaSources(Intent intent) {
private MediaSource createTopLevelMediaSource(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
boolean actionIsListView = ACTION_VIEW_LIST.equals(action); boolean actionIsListView = ACTION_VIEW_LIST.equals(action);
if (!actionIsListView && !ACTION_VIEW.equals(action)) { if (!actionIsListView && !ACTION_VIEW.equals(action)) {
...@@ -429,10 +427,10 @@ public class PlayerActivity extends AppCompatActivity ...@@ -429,10 +427,10 @@ public class PlayerActivity extends AppCompatActivity
} }
} }
MediaSource[] mediaSources = new MediaSource[samples.length]; List<MediaSource> mediaSources = new ArrayList<>();
for (int i = 0; i < samples.length; i++) { for (UriSample sample : samples) {
mediaSources[i] = createLeafMediaSource(samples[i]); MediaSource mediaSource = createLeafMediaSource(sample);
Sample.SubtitleInfo subtitleInfo = samples[i].subtitleInfo; Sample.SubtitleInfo subtitleInfo = sample.subtitleInfo;
if (subtitleInfo != null) { if (subtitleInfo != null) {
Format subtitleFormat = Format subtitleFormat =
Format.createTextSampleFormat( Format.createTextSampleFormat(
...@@ -443,33 +441,30 @@ public class PlayerActivity extends AppCompatActivity ...@@ -443,33 +441,30 @@ public class PlayerActivity extends AppCompatActivity
MediaSource subtitleMediaSource = MediaSource subtitleMediaSource =
new SingleSampleMediaSource.Factory(dataSourceFactory) new SingleSampleMediaSource.Factory(dataSourceFactory)
.createMediaSource(subtitleInfo.uri, subtitleFormat, C.TIME_UNSET); .createMediaSource(subtitleInfo.uri, subtitleFormat, C.TIME_UNSET);
mediaSources[i] = new MergingMediaSource(mediaSources[i], subtitleMediaSource); mediaSource = new MergingMediaSource(mediaSource, subtitleMediaSource);
} }
mediaSources.add(mediaSource);
} }
MediaSource mediaSource = if (seenAdsTagUri && mediaSources.size() == 1) {
mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
if (seenAdsTagUri) {
Uri adTagUri = samples[0].adTagUri; Uri adTagUri = samples[0].adTagUri;
if (actionIsListView) { if (!adTagUri.equals(loadedAdTagUri)) {
showToast(R.string.unsupported_ads_in_concatenation); releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
MediaSource adsMediaSource = createAdsMediaSource(mediaSources.get(0), adTagUri);
if (adsMediaSource != null) {
mediaSources.set(0, adsMediaSource);
} else { } else {
if (!adTagUri.equals(loadedAdTagUri)) { showToast(R.string.ima_not_loaded);
releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
MediaSource adsMediaSource = createAdsMediaSource(mediaSource, adTagUri);
if (adsMediaSource != null) {
mediaSource = adsMediaSource;
} else {
showToast(R.string.ima_not_loaded);
}
} }
} else if (seenAdsTagUri && mediaSources.size() > 1) {
showToast(R.string.unsupported_ads_in_concatenation);
releaseAdsLoader();
} else { } else {
releaseAdsLoader(); releaseAdsLoader();
} }
return mediaSource; return mediaSources;
} }
private MediaSource createLeafMediaSource(UriSample parameters) { private MediaSource createLeafMediaSource(UriSample parameters) {
...@@ -557,7 +552,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -557,7 +552,7 @@ public class PlayerActivity extends AppCompatActivity
debugViewHelper = null; debugViewHelper = null;
player.release(); player.release();
player = null; player = null;
mediaSource = null; mediaSources = null;
trackSelector = null; trackSelector = null;
} }
if (adsLoader != null) { if (adsLoader != null) {
......
...@@ -110,7 +110,6 @@ public final class CastPlayer extends BasePlayer { ...@@ -110,7 +110,6 @@ public final class CastPlayer extends BasePlayer {
private int pendingSeekCount; private int pendingSeekCount;
private int pendingSeekWindowIndex; private int pendingSeekWindowIndex;
private long pendingSeekPositionMs; private long pendingSeekPositionMs;
private boolean waitingForInitialTimeline;
/** /**
* @param castContext The context from which the cast session is obtained. * @param castContext The context from which the cast session is obtained.
...@@ -173,7 +172,6 @@ public final class CastPlayer extends BasePlayer { ...@@ -173,7 +172,6 @@ public final class CastPlayer extends BasePlayer {
MediaQueueItem[] items, int startIndex, long positionMs, @RepeatMode int repeatMode) { MediaQueueItem[] items, int startIndex, long positionMs, @RepeatMode int repeatMode) {
if (remoteMediaClient != null) { if (remoteMediaClient != null) {
positionMs = positionMs != C.TIME_UNSET ? positionMs : 0; positionMs = positionMs != C.TIME_UNSET ? positionMs : 0;
waitingForInitialTimeline = true;
return remoteMediaClient.queueLoad(items, startIndex, getCastRepeatMode(repeatMode), return remoteMediaClient.queueLoad(items, startIndex, getCastRepeatMode(repeatMode),
positionMs, null); positionMs, null);
} }
...@@ -641,15 +639,13 @@ public final class CastPlayer extends BasePlayer { ...@@ -641,15 +639,13 @@ public final class CastPlayer extends BasePlayer {
private void updateTimelineAndNotifyIfChanged() { private void updateTimelineAndNotifyIfChanged() {
if (updateTimeline()) { if (updateTimeline()) {
@Player.TimelineChangeReason // TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and
int reason = // TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553].
waitingForInitialTimeline
? Player.TIMELINE_CHANGE_REASON_PREPARED
: Player.TIMELINE_CHANGE_REASON_DYNAMIC;
waitingForInitialTimeline = false;
notificationsBatch.add( notificationsBatch.add(
new ListenerNotificationTask( new ListenerNotificationTask(
listener -> listener.onTimelineChanged(currentTimeline, reason))); listener ->
listener.onTimelineChanged(
currentTimeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)));
} }
} }
......
...@@ -288,7 +288,7 @@ public class ImaAdsLoaderTest { ...@@ -288,7 +288,7 @@ public class ImaAdsLoaderTest {
this.adPlaybackState = adPlaybackState; this.adPlaybackState = adPlaybackState;
fakeExoPlayer.updateTimeline( fakeExoPlayer.updateTimeline(
new SinglePeriodAdTimeline(contentTimeline, adPlaybackState), new SinglePeriodAdTimeline(contentTimeline, adPlaybackState),
Player.TIMELINE_CHANGE_REASON_DYNAMIC); Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
} }
@Override @Override
......
...@@ -297,6 +297,7 @@ public final class ExoPlayerFactory { ...@@ -297,6 +297,7 @@ public final class ExoPlayerFactory {
drmSessionManager, drmSessionManager,
bandwidthMeter, bandwidthMeter,
analyticsCollector, analyticsCollector,
/* useLazyPreparation= */ true,
Clock.DEFAULT, Clock.DEFAULT,
looper); looper);
} }
...@@ -345,6 +346,13 @@ public final class ExoPlayerFactory { ...@@ -345,6 +346,13 @@ public final class ExoPlayerFactory {
BandwidthMeter bandwidthMeter, BandwidthMeter bandwidthMeter,
Looper looper) { Looper looper) {
return new ExoPlayerImpl( return new ExoPlayerImpl(
renderers, trackSelector, loadControl, bandwidthMeter, Clock.DEFAULT, looper); renderers,
trackSelector,
loadControl,
bandwidthMeter,
/* analyticsCollector= */ null,
/* useLazyPreparation= */ true,
Clock.DEFAULT,
looper);
} }
} }
...@@ -19,7 +19,6 @@ import androidx.annotation.Nullable; ...@@ -19,7 +19,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.ClippingMediaPeriod; import com.google.android.exoplayer2.source.ClippingMediaPeriod;
import com.google.android.exoplayer2.source.EmptySampleStream; import com.google.android.exoplayer2.source.EmptySampleStream;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
...@@ -56,7 +55,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -56,7 +55,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private final boolean[] mayRetainStreamFlags; private final boolean[] mayRetainStreamFlags;
private final RendererCapabilities[] rendererCapabilities; private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final MediaSource mediaSource; private final Playlist playlist;
@Nullable private MediaPeriodHolder next; @Nullable private MediaPeriodHolder next;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
...@@ -70,7 +69,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -70,7 +69,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* @param rendererPositionOffsetUs The renderer time of the start of the period, in microseconds. * @param rendererPositionOffsetUs The renderer time of the start of the period, in microseconds.
* @param trackSelector The track selector. * @param trackSelector The track selector.
* @param allocator The allocator. * @param allocator The allocator.
* @param mediaSource The media source that produced the media period. * @param playlist The playlist.
* @param info Information used to identify this media period in its timeline period. * @param info Information used to identify this media period in its timeline period.
* @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each * @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each
* renderer. * renderer.
...@@ -80,13 +79,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -80,13 +79,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
long rendererPositionOffsetUs, long rendererPositionOffsetUs,
TrackSelector trackSelector, TrackSelector trackSelector,
Allocator allocator, Allocator allocator,
MediaSource mediaSource, Playlist playlist,
MediaPeriodInfo info, MediaPeriodInfo info,
TrackSelectorResult emptyTrackSelectorResult) { TrackSelectorResult emptyTrackSelectorResult) {
this.rendererCapabilities = rendererCapabilities; this.rendererCapabilities = rendererCapabilities;
this.rendererPositionOffsetUs = rendererPositionOffsetUs; this.rendererPositionOffsetUs = rendererPositionOffsetUs;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
this.mediaSource = mediaSource; this.playlist = playlist;
this.uid = info.id.periodUid; this.uid = info.id.periodUid;
this.info = info; this.info = info;
this.trackGroups = TrackGroupArray.EMPTY; this.trackGroups = TrackGroupArray.EMPTY;
...@@ -94,8 +93,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -94,8 +93,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
sampleStreams = new SampleStream[rendererCapabilities.length]; sampleStreams = new SampleStream[rendererCapabilities.length];
mayRetainStreamFlags = new boolean[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length];
mediaPeriod = mediaPeriod =
createMediaPeriod( createMediaPeriod(info.id, playlist, allocator, info.startPositionUs, info.endPositionUs);
info.id, mediaSource, allocator, info.startPositionUs, info.endPositionUs);
} }
/** /**
...@@ -305,7 +303,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -305,7 +303,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Releases the media period. No other method should be called after the release. */ /** Releases the media period. No other method should be called after the release. */
public void release() { public void release() {
disableTrackSelectionsInResult(); disableTrackSelectionsInResult();
releaseMediaPeriod(info.endPositionUs, mediaSource, mediaPeriod); releaseMediaPeriod(info.endPositionUs, playlist, mediaPeriod);
} }
/** /**
...@@ -402,11 +400,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -402,11 +400,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Returns a media period corresponding to the given {@code id}. */ /** Returns a media period corresponding to the given {@code id}. */
private static MediaPeriod createMediaPeriod( private static MediaPeriod createMediaPeriod(
MediaPeriodId id, MediaPeriodId id,
MediaSource mediaSource, Playlist playlist,
Allocator allocator, Allocator allocator,
long startPositionUs, long startPositionUs,
long endPositionUs) { long endPositionUs) {
MediaPeriod mediaPeriod = mediaSource.createPeriod(id, allocator, startPositionUs); MediaPeriod mediaPeriod = playlist.createPeriod(id, allocator, startPositionUs);
if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) { if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
mediaPeriod = mediaPeriod =
new ClippingMediaPeriod( new ClippingMediaPeriod(
...@@ -417,12 +415,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -417,12 +415,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */ /** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */
private static void releaseMediaPeriod( private static void releaseMediaPeriod(
long endPositionUs, MediaSource mediaSource, MediaPeriod mediaPeriod) { long endPositionUs, Playlist playlist, MediaPeriod mediaPeriod) {
try { try {
if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) { if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); playlist.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
} else { } else {
mediaSource.releasePeriod(mediaPeriod); playlist.releasePeriod(mediaPeriod);
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
// There's nothing we can do. // There's nothing we can do.
......
...@@ -19,7 +19,6 @@ import android.util.Pair; ...@@ -19,7 +19,6 @@ 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.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
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;
...@@ -134,7 +133,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -134,7 +133,7 @@ import com.google.android.exoplayer2.util.Assertions;
* @param rendererCapabilities The renderer capabilities. * @param rendererCapabilities The renderer capabilities.
* @param trackSelector The track selector. * @param trackSelector The track selector.
* @param allocator The allocator. * @param allocator The allocator.
* @param mediaSource The media source that produced the media period. * @param playlist The playlist.
* @param info Information used to identify this media period in its timeline period. * @param info Information used to identify this media period in its timeline period.
* @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each * @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each
* renderer. * renderer.
...@@ -143,7 +142,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -143,7 +142,7 @@ import com.google.android.exoplayer2.util.Assertions;
RendererCapabilities[] rendererCapabilities, RendererCapabilities[] rendererCapabilities,
TrackSelector trackSelector, TrackSelector trackSelector,
Allocator allocator, Allocator allocator,
MediaSource mediaSource, Playlist playlist,
MediaPeriodInfo info, MediaPeriodInfo info,
TrackSelectorResult emptyTrackSelectorResult) { TrackSelectorResult emptyTrackSelectorResult) {
long rendererPositionOffsetUs = long rendererPositionOffsetUs =
...@@ -158,7 +157,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -158,7 +157,7 @@ import com.google.android.exoplayer2.util.Assertions;
rendererPositionOffsetUs, rendererPositionOffsetUs,
trackSelector, trackSelector,
allocator, allocator,
mediaSource, playlist,
info, info,
emptyTrackSelectorResult); emptyTrackSelectorResult);
if (loading != null) { if (loading != null) {
......
...@@ -162,7 +162,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; ...@@ -162,7 +162,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
public MediaPeriodId getDummyFirstMediaPeriodId( public MediaPeriodId getDummyFirstMediaPeriodId(
boolean shuffleModeEnabled, Timeline.Window window, Timeline.Period period) { boolean shuffleModeEnabled, Timeline.Window window, Timeline.Period period) {
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
return DUMMY_MEDIA_PERIOD_ID; return getDummyPeriodForEmptyTimeline();
} }
int firstWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); int firstWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
int firstPeriodIndex = timeline.getWindow(firstWindowIndex, window).firstPeriodIndex; int firstPeriodIndex = timeline.getWindow(firstWindowIndex, window).firstPeriodIndex;
...@@ -178,6 +178,11 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; ...@@ -178,6 +178,11 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex), windowSequenceNumber); return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex), windowSequenceNumber);
} }
/** Returns dummy period id for an empty timeline. */
public MediaPeriodId getDummyPeriodForEmptyTimeline() {
return DUMMY_MEDIA_PERIOD_ID;
}
/** /**
* Copies playback info with new playing position. * Copies playback info with new playing position.
* *
......
...@@ -381,7 +381,8 @@ public interface Player { ...@@ -381,7 +381,8 @@ public interface Player {
* {@link #onPositionDiscontinuity(int)}. * {@link #onPositionDiscontinuity(int)}.
* *
* @param timeline The latest timeline. Never null, but may be empty. * @param timeline The latest timeline. Never null, but may be empty.
* @param manifest The latest manifest. May be null. * @param manifest The latest manifest in case the timeline has a single window only. Always
* null if the timeline has more than a single window.
* @param reason The {@link TimelineChangeReason} responsible for this timeline change. * @param reason The {@link TimelineChangeReason} responsible for this timeline change.
* @deprecated Use {@link #onTimelineChanged(Timeline, int)} instead. The manifest can be * @deprecated Use {@link #onTimelineChanged(Timeline, int)} instead. The manifest can be
* accessed by using {@link #getCurrentManifest()} or {@code timeline.getWindow(windowIndex, * accessed by using {@link #getCurrentManifest()} or {@code timeline.getWindow(windowIndex,
...@@ -619,25 +620,17 @@ public interface Player { ...@@ -619,25 +620,17 @@ public interface Player {
int DISCONTINUITY_REASON_INTERNAL = 4; int DISCONTINUITY_REASON_INTERNAL = 4;
/** /**
* Reasons for timeline changes. One of {@link #TIMELINE_CHANGE_REASON_PREPARED}, {@link * Reasons for timeline changes. One of {@link #TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED} or {@link
* #TIMELINE_CHANGE_REASON_RESET} or {@link #TIMELINE_CHANGE_REASON_DYNAMIC}. * #TIMELINE_CHANGE_REASON_SOURCE_UPDATE}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, TIMELINE_CHANGE_REASON_SOURCE_UPDATE})
TIMELINE_CHANGE_REASON_PREPARED,
TIMELINE_CHANGE_REASON_RESET,
TIMELINE_CHANGE_REASON_DYNAMIC
})
@interface TimelineChangeReason {} @interface TimelineChangeReason {}
/** Timeline and manifest changed as a result of a player initialization with new media. */ /** Timeline changed as a result of a change of the playlist items or the order of the items. */
int TIMELINE_CHANGE_REASON_PREPARED = 0; int TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED = 0;
/** Timeline and manifest changed as a result of a player reset. */ /** Timeline changed as a result of a dynamic update introduced by the played media. */
int TIMELINE_CHANGE_REASON_RESET = 1; int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1;
/**
* Timeline or manifest changed as a result of an dynamic update introduced by the played media.
*/
int TIMELINE_CHANGE_REASON_DYNAMIC = 2;
/** Returns the component of this player for audio output, or null if audio is not supported. */ /** Returns the component of this player for audio output, or null if audio is not supported. */
@Nullable @Nullable
......
...@@ -135,11 +135,8 @@ public class AnalyticsCollector ...@@ -135,11 +135,8 @@ public class AnalyticsCollector
} }
} }
/** /** Resets the analytics collector for a new playlist. */
* Resets the analytics collector for a new media source. Should be called before the player is public final void resetForNewPlaylist() {
* prepared with a new media source.
*/
public final void resetForNewMediaSource() {
// Copying the list is needed because onMediaPeriodReleased will modify the list. // Copying the list is needed because onMediaPeriodReleased will modify the list.
List<MediaPeriodInfo> mediaPeriodInfos = List<MediaPeriodInfo> mediaPeriodInfos =
new ArrayList<>(mediaPeriodQueueTracker.mediaPeriodInfoQueue); new ArrayList<>(mediaPeriodQueueTracker.mediaPeriodInfoQueue);
...@@ -806,9 +803,13 @@ public class AnalyticsCollector ...@@ -806,9 +803,13 @@ public class AnalyticsCollector
/** Updates the queue with a newly created media period. */ /** Updates the queue with a newly created media period. */
public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) { public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
boolean isInTimeline = timeline.getIndexOfPeriod(mediaPeriodId.periodUid) != C.INDEX_UNSET; int periodIndex = timeline.getIndexOfPeriod(mediaPeriodId.periodUid);
boolean isInTimeline = periodIndex != C.INDEX_UNSET;
MediaPeriodInfo mediaPeriodInfo = MediaPeriodInfo mediaPeriodInfo =
new MediaPeriodInfo(mediaPeriodId, isInTimeline ? timeline : Timeline.EMPTY, windowIndex); new MediaPeriodInfo(
mediaPeriodId,
isInTimeline ? timeline : Timeline.EMPTY,
isInTimeline ? timeline.getPeriod(periodIndex, period).windowIndex : windowIndex);
mediaPeriodInfoQueue.add(mediaPeriodInfo); mediaPeriodInfoQueue.add(mediaPeriodInfo);
mediaPeriodIdToInfo.put(mediaPeriodId, mediaPeriodInfo); mediaPeriodIdToInfo.put(mediaPeriodId, mediaPeriodInfo);
lastPlayingMediaPeriod = mediaPeriodInfoQueue.get(0); lastPlayingMediaPeriod = mediaPeriodInfoQueue.get(0);
...@@ -824,7 +825,7 @@ public class AnalyticsCollector ...@@ -824,7 +825,7 @@ public class AnalyticsCollector
public boolean onMediaPeriodReleased(MediaPeriodId mediaPeriodId) { public boolean onMediaPeriodReleased(MediaPeriodId mediaPeriodId) {
MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.remove(mediaPeriodId); MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.remove(mediaPeriodId);
if (mediaPeriodInfo == null) { if (mediaPeriodInfo == null) {
// The media period has already been removed from the queue in resetForNewMediaSource(). // The media period has already been removed from the queue in resetForNewPlaylist().
return false; return false;
} }
mediaPeriodInfoQueue.remove(mediaPeriodInfo); mediaPeriodInfoQueue.remove(mediaPeriodInfo);
......
...@@ -617,12 +617,10 @@ public class EventLogger implements AnalyticsListener { ...@@ -617,12 +617,10 @@ public class EventLogger implements AnalyticsListener {
private static String getTimelineChangeReasonString(@Player.TimelineChangeReason int reason) { private static String getTimelineChangeReasonString(@Player.TimelineChangeReason int reason) {
switch (reason) { switch (reason) {
case Player.TIMELINE_CHANGE_REASON_PREPARED: case Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE:
return "PREPARED"; return "SOURCE_UPDATE";
case Player.TIMELINE_CHANGE_REASON_RESET: case Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED:
return "RESET"; return "PLAYLIST_CHANGED";
case Player.TIMELINE_CHANGE_REASON_DYNAMIC:
return "DYNAMIC";
default: default:
return "?"; return "?";
} }
......
...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.Format; ...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.source.BaseMediaSource; import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.ForwardingTimeline;
import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
...@@ -49,6 +50,22 @@ import java.util.List; ...@@ -49,6 +50,22 @@ import java.util.List;
*/ */
public class FakeMediaSource extends BaseMediaSource { public class FakeMediaSource extends BaseMediaSource {
/** A forwarding timeline to provide an initial timeline for fake multi window sources. */
public static class InitialTimeline extends ForwardingTimeline {
public InitialTimeline(Timeline timeline) {
super(timeline);
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
Window childWindow = timeline.getWindow(windowIndex, window, defaultPositionProjectionUs);
childWindow.isDynamic = true;
childWindow.isSeekable = false;
return childWindow;
}
}
private static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://manifest.uri")); private static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://manifest.uri"));
private static final int MANIFEST_LOAD_BYTES = 100; private static final int MANIFEST_LOAD_BYTES = 100;
...@@ -92,6 +109,19 @@ public class FakeMediaSource extends BaseMediaSource { ...@@ -92,6 +109,19 @@ public class FakeMediaSource extends BaseMediaSource {
return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null; return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null;
} }
@Nullable
@Override
public Timeline getInitialTimeline() {
return timeline == null || timeline == Timeline.EMPTY || timeline.getWindowCount() == 1
? null
: new InitialTimeline(timeline);
}
@Override
public boolean isSingleWindow() {
return timeline == null || timeline == Timeline.EMPTY || timeline.getWindowCount() == 1;
}
@Override @Override
public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
assertThat(preparedSource).isFalse(); assertThat(preparedSource).isFalse();
...@@ -249,5 +279,4 @@ public class FakeMediaSource extends BaseMediaSource { ...@@ -249,5 +279,4 @@ public class FakeMediaSource extends BaseMediaSource {
} }
return new TrackGroupArray(trackGroups); return new TrackGroupArray(trackGroups);
} }
} }
...@@ -25,8 +25,10 @@ import com.google.android.exoplayer2.PlayerMessage; ...@@ -25,8 +25,10 @@ import com.google.android.exoplayer2.PlayerMessage;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import java.util.List;
/** /**
* An abstract {@link ExoPlayer} implementation that throws {@link UnsupportedOperationException} * An abstract {@link ExoPlayer} implementation that throws {@link UnsupportedOperationException}
...@@ -91,21 +93,36 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { ...@@ -91,21 +93,36 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/** @deprecated Use {@link #prepare()} instead. */
@Deprecated
@Override @Override
public void retry() { public void retry() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* @deprecated Use {@link #setMediaSource(MediaSource)} and {@link ExoPlayer#prepare()} instead.
*/
@Deprecated
@Override @Override
public void prepare() { public void prepare() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* @deprecated Use {@link #setMediaSource(MediaSource)} and {@link ExoPlayer#prepare()} instead.
*/
@Deprecated
@Override @Override
public void prepare(MediaSource mediaSource) { public void prepare(MediaSource mediaSource) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* @deprecated Use {@link #setMediaSource(MediaSource, boolean)} and {@link ExoPlayer#prepare()}
* instead.
*/
@Deprecated
@Override @Override
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
...@@ -122,6 +139,72 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { ...@@ -122,6 +139,72 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
} }
@Override @Override
public void setMediaSource(MediaSource mediaSource, boolean resetPosition) {
throw new UnsupportedOperationException();
}
@Override
public void setMediaSources(List<MediaSource> mediaSources) {
throw new UnsupportedOperationException();
}
@Override
public void setMediaSources(List<MediaSource> mediaSources, boolean resetPosition) {
throw new UnsupportedOperationException();
}
@Override
public void setMediaSources(
List<MediaSource> mediaSources, int startWindowIndex, long startPositionMs) {
throw new UnsupportedOperationException();
}
@Override
public void addMediaSource(MediaSource mediaSource) {
throw new UnsupportedOperationException();
}
@Override
public void addMediaSource(int index, MediaSource mediaSource) {
throw new UnsupportedOperationException();
}
@Override
public void addMediaSources(List<MediaSource> mediaSources) {
throw new UnsupportedOperationException();
}
@Override
public void addMediaSources(int index, List<MediaSource> mediaSources) {
throw new UnsupportedOperationException();
}
@Override
public void moveMediaItem(int currentIndex, int newIndex) {
throw new UnsupportedOperationException();
}
@Override
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
throw new UnsupportedOperationException();
}
@Override
public MediaSource removeMediaItem(int index) {
throw new UnsupportedOperationException();
}
@Override
public void removeMediaItems(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
@Override
public void clearMediaItems() {
throw new UnsupportedOperationException();
}
@Override
public void setPlayWhenReady(boolean playWhenReady) { public void setPlayWhenReady(boolean playWhenReady) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
...@@ -142,6 +225,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { ...@@ -142,6 +225,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
} }
@Override @Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) {
throw new UnsupportedOperationException();
}
@Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) { public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment