mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Expose period information from MediaSources.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=128357449
This commit is contained in:
parent
2a70a58f0a
commit
d657360341
22 changed files with 779 additions and 170 deletions
|
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
|
|
@ -87,6 +88,14 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
boolean isFinal = timeline.isFinal();
|
||||||
|
int periodCount = timeline.getPeriodCount();
|
||||||
|
Log.d(TAG, "timelineChanged [" + isFinal + ", "
|
||||||
|
+ (periodCount == Timeline.UNKNOWN_PERIOD_COUNT ? "?" : periodCount) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException e) {
|
public void onPlayerError(ExoPlaybackException e) {
|
||||||
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
|
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import com.google.android.exoplayer2.metadata.id3.TxxxFrame;
|
||||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
|
import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
|
||||||
import com.google.android.exoplayer2.source.chunk.FormatEvaluator.AdaptiveEvaluator;
|
import com.google.android.exoplayer2.source.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||||
|
|
@ -457,6 +458,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException e) {
|
public void onPlayerError(ExoPlaybackException e) {
|
||||||
String errorString = null;
|
String errorString = null;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
|
||||||
|
|
@ -100,6 +101,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
playbackException = error;
|
playbackException = error;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
|
||||||
|
|
@ -100,6 +101,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
playbackException = error;
|
playbackException = error;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
|
||||||
|
|
@ -119,6 +120,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
playbackException = error;
|
playbackException = error;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
|
||||||
|
|
||||||
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;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An extensible media player exposing traditional high-level media player functionality, such as
|
* An extensible media player exposing traditional high-level media player functionality, such as
|
||||||
|
|
@ -138,6 +139,13 @@ public interface ExoPlayer {
|
||||||
*/
|
*/
|
||||||
void onPositionDiscontinuity(int periodIndex, long positionMs);
|
void onPositionDiscontinuity(int periodIndex, long positionMs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the timeline changes.
|
||||||
|
*
|
||||||
|
* @param timeline The new timeline.
|
||||||
|
*/
|
||||||
|
void onTimelineChanged(Timeline timeline);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when an error occurs. The playback state will transition to
|
* Invoked when an error occurs. The playback state will transition to
|
||||||
* {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance
|
* {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance
|
||||||
|
|
@ -329,10 +337,10 @@ public interface ExoPlayer {
|
||||||
void blockingSendMessages(ExoPlayerMessage... messages);
|
void blockingSendMessages(ExoPlayerMessage... messages);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the duration of the track in milliseconds.
|
* Gets the duration of the current period in milliseconds.
|
||||||
*
|
*
|
||||||
* @return The duration of the track in milliseconds, or {@link ExoPlayer#UNKNOWN_TIME} if the
|
* @return The duration of the current period in milliseconds, or {@link ExoPlayer#UNKNOWN_TIME}
|
||||||
* duration is not known.
|
* if the duration is not known.
|
||||||
*/
|
*/
|
||||||
long getDuration();
|
long getDuration();
|
||||||
|
|
||||||
|
|
@ -350,6 +358,13 @@ public interface ExoPlayer {
|
||||||
*/
|
*/
|
||||||
int getCurrentPeriodIndex();
|
int getCurrentPeriodIndex();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current {@link Timeline}, or {@code null} if there is no timeline.
|
||||||
|
*
|
||||||
|
* @return The current {@link Timeline}, or {@code null} if there is no timeline.
|
||||||
|
*/
|
||||||
|
Timeline getCurrentTimeline();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an estimate of the absolute position in milliseconds up to which data is buffered.
|
* Gets an estimate of the absolute position in milliseconds up to which data is buffered.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
|
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
|
|
@ -44,6 +45,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
private int pendingPlayWhenReadyAcks;
|
private int pendingPlayWhenReadyAcks;
|
||||||
private int pendingSeekAcks;
|
private int pendingSeekAcks;
|
||||||
private boolean isLoading;
|
private boolean isLoading;
|
||||||
|
private Timeline timeline;
|
||||||
|
|
||||||
// Playback information when there is no pending seek/set source operation.
|
// Playback information when there is no pending seek/set source operation.
|
||||||
private PlaybackInfo playbackInfo;
|
private PlaybackInfo playbackInfo;
|
||||||
|
|
@ -75,9 +77,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
ExoPlayerImpl.this.handleEvent(msg);
|
ExoPlayerImpl.this.handleEvent(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady,
|
|
||||||
eventHandler);
|
|
||||||
playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0);
|
playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0);
|
||||||
|
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady,
|
||||||
|
eventHandler, playbackInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -97,6 +99,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaSource(MediaSource mediaSource) {
|
public void setMediaSource(MediaSource mediaSource) {
|
||||||
|
timeline = null;
|
||||||
internalPlayer.setMediaSource(mediaSource);
|
internalPlayer.setMediaSource(mediaSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,6 +191,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex;
|
return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline getCurrentTimeline() {
|
||||||
|
return timeline;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getBufferedPosition() {
|
public long getBufferedPosition() {
|
||||||
if (pendingSeekAcks == 0) {
|
if (pendingSeekAcks == 0) {
|
||||||
|
|
@ -245,6 +253,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ExoPlayerImplInternal.MSG_TIMELINE_CHANGED: {
|
||||||
|
timeline = (Timeline) msg.obj;
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onTimelineChanged(timeline);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ExoPlayerImplInternal.MSG_ERROR: {
|
case ExoPlayerImplInternal.MSG_ERROR: {
|
||||||
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
|
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@ import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
|
||||||
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;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MediaClock;
|
import com.google.android.exoplayer2.util.MediaClock;
|
||||||
import com.google.android.exoplayer2.util.PriorityHandlerThread;
|
import com.google.android.exoplayer2.util.PriorityHandlerThread;
|
||||||
import com.google.android.exoplayer2.util.StandaloneMediaClock;
|
import com.google.android.exoplayer2.util.StandaloneMediaClock;
|
||||||
|
|
@ -44,7 +45,7 @@ import java.util.ArrayList;
|
||||||
* Implements the internal behavior of {@link ExoPlayerImpl}.
|
* Implements the internal behavior of {@link ExoPlayerImpl}.
|
||||||
*/
|
*/
|
||||||
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback,
|
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback,
|
||||||
InvalidationListener {
|
TrackSelector.InvalidationListener, MediaSource.InvalidationListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playback position information which is read on the application's thread by
|
* Playback position information which is read on the application's thread by
|
||||||
|
|
@ -73,7 +74,8 @@ import java.util.ArrayList;
|
||||||
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
|
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
|
||||||
public static final int MSG_SEEK_ACK = 4;
|
public static final int MSG_SEEK_ACK = 4;
|
||||||
public static final int MSG_PERIOD_CHANGED = 5;
|
public static final int MSG_PERIOD_CHANGED = 5;
|
||||||
public static final int MSG_ERROR = 6;
|
public static final int MSG_TIMELINE_CHANGED = 6;
|
||||||
|
public static final int MSG_ERROR = 7;
|
||||||
|
|
||||||
// Internal messages
|
// Internal messages
|
||||||
private static final int MSG_SET_MEDIA_SOURCE = 0;
|
private static final int MSG_SET_MEDIA_SOURCE = 0;
|
||||||
|
|
@ -85,18 +87,19 @@ import java.util.ArrayList;
|
||||||
private static final int MSG_PERIOD_PREPARED = 6;
|
private static final int MSG_PERIOD_PREPARED = 6;
|
||||||
private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 7;
|
private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 7;
|
||||||
private static final int MSG_TRACK_SELECTION_INVALIDATED = 8;
|
private static final int MSG_TRACK_SELECTION_INVALIDATED = 8;
|
||||||
private static final int MSG_CUSTOM = 9;
|
private static final int MSG_SOURCE_INVALIDATED = 9;
|
||||||
|
private static final int MSG_CUSTOM = 10;
|
||||||
|
|
||||||
private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
|
private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
|
||||||
private static final int RENDERING_INTERVAL_MS = 10;
|
private static final int RENDERING_INTERVAL_MS = 10;
|
||||||
private static final int IDLE_INTERVAL_MS = 1000;
|
private static final int IDLE_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Limits the maximum number of sources to buffer ahead of the current source in the timeline. The
|
* Limits the maximum number of periods to buffer ahead of the current playing period. The
|
||||||
* source buffering policy normally prevents buffering too far ahead, but the policy could allow
|
* buffering policy normally prevents buffering too far ahead, but the policy could allow too many
|
||||||
* too many very small sources to be buffered if the buffered source count were not limited.
|
* small periods to be buffered if the period count were not limited.
|
||||||
*/
|
*/
|
||||||
private static final int MAXIMUM_BUFFER_AHEAD_SOURCES = 100;
|
private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100;
|
||||||
|
|
||||||
private final TrackSelector trackSelector;
|
private final TrackSelector trackSelector;
|
||||||
private final LoadControl loadControl;
|
private final LoadControl loadControl;
|
||||||
|
|
@ -104,7 +107,7 @@ import java.util.ArrayList;
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final HandlerThread internalPlaybackThread;
|
private final HandlerThread internalPlaybackThread;
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final Timeline timeline;
|
private final InternalTimeline internalTimeline;
|
||||||
|
|
||||||
private PlaybackInfo playbackInfo;
|
private PlaybackInfo playbackInfo;
|
||||||
private Renderer rendererMediaClockSource;
|
private Renderer rendererMediaClockSource;
|
||||||
|
|
@ -123,12 +126,14 @@ import java.util.ArrayList;
|
||||||
private long internalPositionUs;
|
private long internalPositionUs;
|
||||||
|
|
||||||
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
|
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
|
||||||
LoadControl loadControl, boolean playWhenReady, Handler eventHandler) {
|
LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
|
||||||
|
PlaybackInfo playbackInfo) {
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
this.playWhenReady = playWhenReady;
|
this.playWhenReady = playWhenReady;
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.state = ExoPlayer.STATE_IDLE;
|
this.state = ExoPlayer.STATE_IDLE;
|
||||||
|
this.playbackInfo = playbackInfo;
|
||||||
|
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
renderers[i].setIndex(i);
|
renderers[i].setIndex(i);
|
||||||
|
|
@ -136,8 +141,7 @@ import java.util.ArrayList;
|
||||||
|
|
||||||
standaloneMediaClock = new StandaloneMediaClock();
|
standaloneMediaClock = new StandaloneMediaClock();
|
||||||
enabledRenderers = new Renderer[0];
|
enabledRenderers = new Renderer[0];
|
||||||
timeline = new Timeline(renderers);
|
internalTimeline = new InternalTimeline(renderers);
|
||||||
playbackInfo = new PlaybackInfo(0);
|
|
||||||
|
|
||||||
trackSelector.init(this);
|
trackSelector.init(this);
|
||||||
|
|
||||||
|
|
@ -205,7 +209,7 @@ import java.util.ArrayList;
|
||||||
internalPlaybackThread.quit();
|
internalPlaybackThread.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvalidationListener implementation.
|
// TrackSelector.InvalidationListener implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackSelectionsInvalidated() {
|
public void onTrackSelectionsInvalidated() {
|
||||||
|
|
@ -255,17 +259,21 @@ import java.util.ArrayList;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MSG_PERIOD_PREPARED: {
|
case MSG_PERIOD_PREPARED: {
|
||||||
timeline.handlePeriodPrepared((MediaPeriod) msg.obj);
|
internalTimeline.handlePeriodPrepared((MediaPeriod) msg.obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: {
|
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: {
|
||||||
timeline.handleContinueLoadingRequested((MediaPeriod) msg.obj);
|
internalTimeline.handleContinueLoadingRequested((MediaPeriod) msg.obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MSG_TRACK_SELECTION_INVALIDATED: {
|
case MSG_TRACK_SELECTION_INVALIDATED: {
|
||||||
reselectTracksInternal();
|
reselectTracksInternal();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case MSG_SOURCE_INVALIDATED: {
|
||||||
|
internalTimeline.invalidate((Timeline) msg.obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case MSG_CUSTOM: {
|
case MSG_CUSTOM: {
|
||||||
sendMessagesInternal((ExoPlayerMessage[]) msg.obj);
|
sendMessagesInternal((ExoPlayerMessage[]) msg.obj);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -292,6 +300,19 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MediaSource.InvalidationListener implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
try {
|
||||||
|
internalTimeline.invalidate(timeline);
|
||||||
|
} catch (ExoPlaybackException | IOException e) {
|
||||||
|
Log.e(TAG, "Error handling timeline change.", e);
|
||||||
|
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
|
||||||
|
stopInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
||||||
private void setState(int state) {
|
private void setState(int state) {
|
||||||
|
|
@ -311,7 +332,7 @@ import java.util.ArrayList;
|
||||||
private void setMediaSourceInternal(MediaSource mediaSource) {
|
private void setMediaSourceInternal(MediaSource mediaSource) {
|
||||||
resetInternal();
|
resetInternal();
|
||||||
this.mediaSource = mediaSource;
|
this.mediaSource = mediaSource;
|
||||||
mediaSource.prepareSource();
|
mediaSource.prepareSource(this);
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +373,7 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePlaybackPositions() throws ExoPlaybackException {
|
private void updatePlaybackPositions() throws ExoPlaybackException {
|
||||||
MediaPeriod mediaPeriod = timeline.getPeriod();
|
MediaPeriod mediaPeriod = internalTimeline.getPeriod();
|
||||||
if (mediaPeriod == null) {
|
if (mediaPeriod == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -373,7 +394,7 @@ import java.util.ArrayList;
|
||||||
} else {
|
} else {
|
||||||
internalPositionUs = standaloneMediaClock.getPositionUs();
|
internalPositionUs = standaloneMediaClock.getPositionUs();
|
||||||
}
|
}
|
||||||
positionUs = internalPositionUs - timeline.playingPeriod.offsetUs;
|
positionUs = internalPositionUs - internalTimeline.playingPeriod.offsetUs;
|
||||||
}
|
}
|
||||||
playbackInfo.positionUs = positionUs;
|
playbackInfo.positionUs = positionUs;
|
||||||
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
|
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
|
||||||
|
|
@ -391,10 +412,10 @@ import java.util.ArrayList;
|
||||||
private void doSomeWork() throws ExoPlaybackException, IOException {
|
private void doSomeWork() throws ExoPlaybackException, IOException {
|
||||||
long operationStartTimeMs = SystemClock.elapsedRealtime();
|
long operationStartTimeMs = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
timeline.updatePeriods();
|
internalTimeline.updatePeriods();
|
||||||
if (timeline.getPeriod() == null) {
|
if (internalTimeline.getPeriod() == null) {
|
||||||
// We're still waiting for the first source to be prepared.
|
// We're still waiting for the first source to be prepared.
|
||||||
timeline.maybeThrowPeriodPrepareError();
|
internalTimeline.maybeThrowPeriodPrepareError();
|
||||||
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
|
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -420,23 +441,23 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allRenderersReadyOrEnded) {
|
if (!allRenderersReadyOrEnded) {
|
||||||
timeline.maybeThrowPeriodPrepareError();
|
internalTimeline.maybeThrowPeriodPrepareError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allRenderersEnded && (playbackInfo.durationUs == C.UNSET_TIME_US
|
if (allRenderersEnded && (playbackInfo.durationUs == C.UNSET_TIME_US
|
||||||
|| playbackInfo.durationUs <= playbackInfo.positionUs) && timeline.isEnded) {
|
|| playbackInfo.durationUs <= playbackInfo.positionUs) && internalTimeline.isEnded) {
|
||||||
setState(ExoPlayer.STATE_ENDED);
|
setState(ExoPlayer.STATE_ENDED);
|
||||||
stopRenderers();
|
stopRenderers();
|
||||||
} else if (state == ExoPlayer.STATE_BUFFERING) {
|
} else if (state == ExoPlayer.STATE_BUFFERING) {
|
||||||
if ((enabledRenderers.length > 0 ? allRenderersReadyOrEnded : timeline.isReady)
|
if ((enabledRenderers.length > 0 ? allRenderersReadyOrEnded : internalTimeline.isReady)
|
||||||
&& timeline.haveSufficientBuffer(rebuffering)) {
|
&& internalTimeline.haveSufficientBuffer(rebuffering)) {
|
||||||
setState(ExoPlayer.STATE_READY);
|
setState(ExoPlayer.STATE_READY);
|
||||||
if (playWhenReady) {
|
if (playWhenReady) {
|
||||||
startRenderers();
|
startRenderers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (state == ExoPlayer.STATE_READY) {
|
} else if (state == ExoPlayer.STATE_READY) {
|
||||||
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !timeline.isReady) {
|
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !internalTimeline.isReady) {
|
||||||
rebuffering = playWhenReady;
|
rebuffering = playWhenReady;
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
stopRenderers();
|
stopRenderers();
|
||||||
|
|
@ -464,38 +485,42 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekToInternal(int periodIndex, long seekPositionUs) throws ExoPlaybackException {
|
private void seekToInternal(int periodIndex, long positionUs) throws ExoPlaybackException {
|
||||||
try {
|
try {
|
||||||
if (periodIndex == playbackInfo.periodIndex
|
if (periodIndex == playbackInfo.periodIndex
|
||||||
&& (seekPositionUs / 1000) == (playbackInfo.positionUs / 1000)) {
|
&& (positionUs / 1000) == (playbackInfo.positionUs / 1000)) {
|
||||||
// Seek position equals the current position to the nearest millisecond. Do nothing.
|
// Seek position equals the current position to the nearest millisecond. Do nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
seekToPeriodPosition(periodIndex, positionUs);
|
||||||
stopRenderers();
|
|
||||||
rebuffering = false;
|
|
||||||
|
|
||||||
seekPositionUs = timeline.seekTo(periodIndex, seekPositionUs);
|
|
||||||
if (periodIndex != playbackInfo.periodIndex) {
|
|
||||||
playbackInfo = new PlaybackInfo(periodIndex);
|
|
||||||
playbackInfo.positionUs = seekPositionUs;
|
|
||||||
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
|
|
||||||
} else {
|
|
||||||
playbackInfo.positionUs = seekPositionUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePlaybackPositions();
|
|
||||||
if (mediaSource != null) {
|
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
eventHandler.sendEmptyMessage(MSG_SEEK_ACK);
|
eventHandler.sendEmptyMessage(MSG_SEEK_ACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void seekToPeriodPosition(int periodIndex, long positionUs) throws ExoPlaybackException {
|
||||||
|
stopRenderers();
|
||||||
|
rebuffering = false;
|
||||||
|
|
||||||
|
positionUs = internalTimeline.seekTo(periodIndex, positionUs);
|
||||||
|
if (periodIndex != playbackInfo.periodIndex) {
|
||||||
|
playbackInfo = new PlaybackInfo(periodIndex);
|
||||||
|
playbackInfo.positionUs = positionUs;
|
||||||
|
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
|
||||||
|
} else {
|
||||||
|
playbackInfo.positionUs = positionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePlaybackPositions();
|
||||||
|
if (mediaSource != null) {
|
||||||
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
|
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void resetInternalPosition(long periodPositionUs) throws ExoPlaybackException {
|
private void resetInternalPosition(long periodPositionUs) throws ExoPlaybackException {
|
||||||
long sourceOffsetUs = timeline.playingPeriod == null ? 0 : timeline.playingPeriod.offsetUs;
|
long sourceOffsetUs =
|
||||||
|
internalTimeline.playingPeriod == null ? 0 : internalTimeline.playingPeriod.offsetUs;
|
||||||
internalPositionUs = sourceOffsetUs + periodPositionUs;
|
internalPositionUs = sourceOffsetUs + periodPositionUs;
|
||||||
standaloneMediaClock.setPositionUs(internalPositionUs);
|
standaloneMediaClock.setPositionUs(internalPositionUs);
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
|
|
@ -537,7 +562,7 @@ import java.util.ArrayList;
|
||||||
mediaSource.releaseSource();
|
mediaSource.releaseSource();
|
||||||
mediaSource = null;
|
mediaSource = null;
|
||||||
}
|
}
|
||||||
timeline.reset();
|
internalTimeline.reset();
|
||||||
loadControl.reset();
|
loadControl.reset();
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -566,19 +591,20 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
private void reselectTracksInternal() throws ExoPlaybackException {
|
||||||
if (timeline.getPeriod() == null) {
|
if (internalTimeline.getPeriod() == null) {
|
||||||
// We don't have tracks yet, so we don't care.
|
// We don't have tracks yet, so we don't care.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timeline.reselectTracks();
|
internalTimeline.reselectTracks();
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO[playlists]: Merge this into the outer class.
|
||||||
/**
|
/**
|
||||||
* Keeps track of the {@link Period}s of media being played in the timeline.
|
* Keeps track of the {@link Period}s of media being played in the timeline.
|
||||||
*/
|
*/
|
||||||
private final class Timeline {
|
private final class InternalTimeline {
|
||||||
|
|
||||||
private final Renderer[] renderers;
|
private final Renderer[] renderers;
|
||||||
private final RendererCapabilities[] rendererCapabilities;
|
private final RendererCapabilities[] rendererCapabilities;
|
||||||
|
|
@ -592,7 +618,9 @@ import java.util.ArrayList;
|
||||||
|
|
||||||
private long playingPeriodEndPositionUs;
|
private long playingPeriodEndPositionUs;
|
||||||
|
|
||||||
public Timeline(Renderer[] renderers) {
|
private Timeline timeline;
|
||||||
|
|
||||||
|
public InternalTimeline(Renderer[] renderers) {
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
rendererCapabilities = new RendererCapabilities[renderers.length];
|
rendererCapabilities = new RendererCapabilities[renderers.length];
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
|
|
@ -613,9 +641,7 @@ import java.util.ArrayList;
|
||||||
long bufferedPositionUs = !loadingPeriod.prepared ? 0
|
long bufferedPositionUs = !loadingPeriod.prepared ? 0
|
||||||
: loadingPeriod.mediaPeriod.getBufferedPositionUs();
|
: loadingPeriod.mediaPeriod.getBufferedPositionUs();
|
||||||
if (bufferedPositionUs == C.END_OF_SOURCE_US) {
|
if (bufferedPositionUs == C.END_OF_SOURCE_US) {
|
||||||
int periodCount = mediaSource.getPeriodCount();
|
if (loadingPeriod.isLast) {
|
||||||
if (periodCount != MediaSource.UNKNOWN_PERIOD_COUNT
|
|
||||||
&& loadingPeriod.index == periodCount - 1) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bufferedPositionUs = loadingPeriod.mediaPeriod.getDurationUs();
|
bufferedPositionUs = loadingPeriod.mediaPeriod.getDurationUs();
|
||||||
|
|
@ -635,32 +661,122 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void invalidate(Timeline timeline) throws ExoPlaybackException, IOException {
|
||||||
|
Timeline oldTimeline = this.timeline;
|
||||||
|
this.timeline = timeline;
|
||||||
|
eventHandler.obtainMessage(MSG_TIMELINE_CHANGED, timeline).sendToTarget();
|
||||||
|
|
||||||
|
// Update the loaded periods to take into account the new timeline.
|
||||||
|
if (playingPeriod != null) {
|
||||||
|
int index = timeline.getIndexOfPeriod(playingPeriod.id);
|
||||||
|
if (index == Timeline.NO_PERIOD_INDEX) {
|
||||||
|
int newPlayingPeriodIndex =
|
||||||
|
mediaSource.getNewPlayingPeriodIndex(playingPeriod.index, oldTimeline);
|
||||||
|
if (newPlayingPeriodIndex == Timeline.NO_PERIOD_INDEX) {
|
||||||
|
// There is no period to play, so stop the player.
|
||||||
|
stopInternal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release all loaded periods and seek to the new playing period index.
|
||||||
|
releasePeriodsFrom(playingPeriod);
|
||||||
|
playingPeriod = null;
|
||||||
|
seekToPeriodPosition(newPlayingPeriodIndex, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The playing period is also in the new timeline. Update index and isLast on each loaded
|
||||||
|
// period until a period is found that has changed.
|
||||||
|
int periodCount = timeline.getPeriodCount();
|
||||||
|
playingPeriod.index = index;
|
||||||
|
playingPeriod.isLast = timeline.isFinal() && index == periodCount - 1;
|
||||||
|
|
||||||
|
Period previousPeriod = playingPeriod;
|
||||||
|
boolean seenReadingPeriod = false;
|
||||||
|
while (previousPeriod != null) {
|
||||||
|
Period period = previousPeriod.nextPeriod;
|
||||||
|
index++;
|
||||||
|
if (!period.id.equals(timeline.getPeriodId(index))) {
|
||||||
|
if (!seenReadingPeriod) {
|
||||||
|
// Renderers may have read a period that has been removed, so release all loaded
|
||||||
|
// periods and seek to the playing period index.
|
||||||
|
index = playingPeriod.index;
|
||||||
|
releasePeriodsFrom(playingPeriod);
|
||||||
|
playingPeriod = null;
|
||||||
|
seekToPeriodPosition(index, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the loading period to be the latest period that is still valid.
|
||||||
|
loadingPeriod = previousPeriod;
|
||||||
|
loadingPeriod.nextPeriod = null;
|
||||||
|
|
||||||
|
// Release the rest of the timeline.
|
||||||
|
releasePeriodsFrom(period);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
period.index = index;
|
||||||
|
period.isLast = timeline.isFinal() && index == periodCount - 1;
|
||||||
|
if (period == readingPeriod) {
|
||||||
|
seenReadingPeriod = true;
|
||||||
|
}
|
||||||
|
previousPeriod = period;
|
||||||
|
}
|
||||||
|
} else if (loadingPeriod != null) {
|
||||||
|
Object id = loadingPeriod.id;
|
||||||
|
int index = timeline.getIndexOfPeriod(id);
|
||||||
|
if (index == Timeline.NO_PERIOD_INDEX) {
|
||||||
|
loadingPeriod.release();
|
||||||
|
loadingPeriod = null;
|
||||||
|
} else {
|
||||||
|
int periodCount = timeline.getPeriodCount();
|
||||||
|
loadingPeriod.index = index;
|
||||||
|
loadingPeriod.isLast = timeline.isFinal() && index == periodCount - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
|
||||||
|
if (oldTimeline != null) {
|
||||||
|
int newPlayingIndex = playingPeriod != null ? playingPeriod.index
|
||||||
|
: loadingPeriod != null ? loadingPeriod.index
|
||||||
|
: mediaSource.getNewPlayingPeriodIndex(playbackInfo.periodIndex, oldTimeline);
|
||||||
|
if (newPlayingIndex != Timeline.NO_PERIOD_INDEX
|
||||||
|
&& newPlayingIndex != playbackInfo.periodIndex) {
|
||||||
|
playbackInfo = new PlaybackInfo(newPlayingIndex);
|
||||||
|
updatePlaybackPositions();
|
||||||
|
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updatePeriods() throws ExoPlaybackException, IOException {
|
public void updatePeriods() throws ExoPlaybackException, IOException {
|
||||||
// TODO[playlists]: Let MediaSource invalidate periods that are already loaded.
|
if (timeline == null) {
|
||||||
|
// We're waiting to get information about periods.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the loading period.
|
// Update the loading period.
|
||||||
int periodCount = mediaSource.getPeriodCount();
|
if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast
|
||||||
if (loadingPeriod == null
|
&& (playingPeriod == null || loadingPeriod.index - playingPeriod.index
|
||||||
|| (loadingPeriod.isFullyBuffered() && loadingPeriod.index
|
< MAXIMUM_BUFFER_AHEAD_PERIODS))) {
|
||||||
- (playingPeriod != null ? playingPeriod.index : 0) < MAXIMUM_BUFFER_AHEAD_SOURCES)) {
|
// Try to obtain the next period to start loading.
|
||||||
// Try and obtain the next period to start loading.
|
|
||||||
int periodIndex = loadingPeriod == null ? playbackInfo.periodIndex
|
int periodIndex = loadingPeriod == null ? playbackInfo.periodIndex
|
||||||
: loadingPeriod.index + 1;
|
: loadingPeriod.index + 1;
|
||||||
if (periodCount == MediaSource.UNKNOWN_PERIOD_COUNT || periodIndex < periodCount) {
|
// Attempt to create the next period.
|
||||||
// Attempt to create the next period.
|
MediaPeriod mediaPeriod = mediaSource.createPeriod(periodIndex);
|
||||||
MediaPeriod mediaPeriod = mediaSource.createPeriod(periodIndex);
|
if (mediaPeriod != null) {
|
||||||
if (mediaPeriod != null) {
|
Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaPeriod,
|
||||||
Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector,
|
timeline.getPeriodId(periodIndex), periodIndex);
|
||||||
mediaPeriod, periodIndex);
|
newPeriod.isLast = timeline.isFinal() && periodIndex == timeline.getPeriodCount() - 1;
|
||||||
if (loadingPeriod != null) {
|
if (loadingPeriod != null) {
|
||||||
loadingPeriod.setNextPeriod(newPeriod);
|
loadingPeriod.setNextPeriod(newPeriod);
|
||||||
}
|
|
||||||
loadingPeriod = newPeriod;
|
|
||||||
long startPositionUs = playingPeriod == null ? playbackInfo.positionUs : 0;
|
|
||||||
setIsLoading(true);
|
|
||||||
loadingPeriod.mediaPeriod.preparePeriod(ExoPlayerImplInternal.this,
|
|
||||||
loadControl.getAllocator(), startPositionUs);
|
|
||||||
}
|
}
|
||||||
|
loadingPeriod = newPeriod;
|
||||||
|
long startPositionUs = playingPeriod == null ? playbackInfo.positionUs : 0;
|
||||||
|
setIsLoading(true);
|
||||||
|
loadingPeriod.mediaPeriod.preparePeriod(ExoPlayerImplInternal.this,
|
||||||
|
loadControl.getAllocator(), startPositionUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -725,10 +841,8 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (periodCount != MediaSource.UNKNOWN_PERIOD_COUNT
|
} else if (readingPeriod.isLast) {
|
||||||
&& readingPeriod.index == periodCount - 1) {
|
|
||||||
readingPeriod = null;
|
readingPeriod = null;
|
||||||
// This is the last period, so signal the renderers to read the end of the stream.
|
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
renderer.setCurrentStreamIsFinal();
|
renderer.setCurrentStreamIsFinal();
|
||||||
}
|
}
|
||||||
|
|
@ -825,6 +939,7 @@ import java.util.ArrayList;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (period.selectTracks()) {
|
if (period.selectTracks()) {
|
||||||
|
// Selected tracks have changed for this period.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (period == readingPeriod) {
|
if (period == readingPeriod) {
|
||||||
|
|
@ -837,11 +952,7 @@ import java.util.ArrayList;
|
||||||
if (selectionsChangedForReadPeriod) {
|
if (selectionsChangedForReadPeriod) {
|
||||||
// Release everything after the playing period because a renderer may have read data from a
|
// Release everything after the playing period because a renderer may have read data from a
|
||||||
// track whose selection has now changed.
|
// track whose selection has now changed.
|
||||||
period = playingPeriod.nextPeriod;
|
releasePeriodsFrom(playingPeriod.nextPeriod);
|
||||||
while (period != null) {
|
|
||||||
period.release();
|
|
||||||
period = period.nextPeriod;
|
|
||||||
}
|
|
||||||
playingPeriod.nextPeriod = null;
|
playingPeriod.nextPeriod = null;
|
||||||
readingPeriod = playingPeriod;
|
readingPeriod = playingPeriod;
|
||||||
loadingPeriod = playingPeriod;
|
loadingPeriod = playingPeriod;
|
||||||
|
|
@ -898,18 +1009,21 @@ import java.util.ArrayList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
Period period = playingPeriod != null ? playingPeriod : loadingPeriod;
|
releasePeriodsFrom(playingPeriod != null ? playingPeriod : loadingPeriod);
|
||||||
while (period != null) {
|
|
||||||
period.release();
|
|
||||||
period = period.nextPeriod;
|
|
||||||
}
|
|
||||||
playingPeriodEndPositionUs = C.UNSET_TIME_US;
|
playingPeriodEndPositionUs = C.UNSET_TIME_US;
|
||||||
isReady = false;
|
isReady = false;
|
||||||
isEnded = false;
|
isEnded = false;
|
||||||
playingPeriod = null;
|
playingPeriod = null;
|
||||||
readingPeriod = null;
|
readingPeriod = null;
|
||||||
loadingPeriod = null;
|
loadingPeriod = null;
|
||||||
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
|
timeline = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releasePeriodsFrom(Period period) {
|
||||||
|
while (period != null) {
|
||||||
|
period.release();
|
||||||
|
period = period.nextPeriod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPlayingPeriod(Period period) throws ExoPlaybackException {
|
private void setPlayingPeriod(Period period) throws ExoPlaybackException {
|
||||||
|
|
@ -945,9 +1059,7 @@ import java.util.ArrayList;
|
||||||
isReady = playingPeriodEndPositionUs == C.UNSET_TIME_US
|
isReady = playingPeriodEndPositionUs == C.UNSET_TIME_US
|
||||||
|| internalPositionUs < playingPeriodEndPositionUs
|
|| internalPositionUs < playingPeriodEndPositionUs
|
||||||
|| (playingPeriod.nextPeriod != null && playingPeriod.nextPeriod.prepared);
|
|| (playingPeriod.nextPeriod != null && playingPeriod.nextPeriod.prepared);
|
||||||
int periodCount = mediaSource.getPeriodCount();
|
isEnded = playingPeriod.isLast;
|
||||||
isEnded = periodCount != MediaSource.UNKNOWN_PERIOD_COUNT
|
|
||||||
&& playingPeriod.index == periodCount - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount)
|
private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount)
|
||||||
|
|
@ -998,9 +1110,11 @@ import java.util.ArrayList;
|
||||||
private static final class Period {
|
private static final class Period {
|
||||||
|
|
||||||
public final MediaPeriod mediaPeriod;
|
public final MediaPeriod mediaPeriod;
|
||||||
public final int index;
|
public final Object id;
|
||||||
public final SampleStream[] sampleStreams;
|
public final SampleStream[] sampleStreams;
|
||||||
|
|
||||||
|
public int index;
|
||||||
|
public boolean isLast;
|
||||||
public boolean prepared;
|
public boolean prepared;
|
||||||
public boolean hasEnabledTracks;
|
public boolean hasEnabledTracks;
|
||||||
public long offsetUs;
|
public long offsetUs;
|
||||||
|
|
@ -1016,13 +1130,14 @@ import java.util.ArrayList;
|
||||||
private TrackSelectionArray periodTrackSelections;
|
private TrackSelectionArray periodTrackSelections;
|
||||||
|
|
||||||
public Period(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
public Period(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
||||||
TrackSelector trackSelector, MediaPeriod mediaPeriod, int index) {
|
TrackSelector trackSelector, MediaPeriod mediaPeriod, Object id, int index) {
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
this.rendererCapabilities = rendererCapabilities;
|
this.rendererCapabilities = rendererCapabilities;
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
this.mediaPeriod = mediaPeriod;
|
this.mediaPeriod = mediaPeriod;
|
||||||
this.index = index;
|
this.id = Assertions.checkNotNull(id);
|
||||||
sampleStreams = new SampleStream[renderers.length];
|
sampleStreams = new SampleStream[renderers.length];
|
||||||
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNextPeriod(Period nextPeriod) {
|
public void setNextPeriod(Period nextPeriod) {
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,13 @@ import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
||||||
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
||||||
import com.google.android.exoplayer2.metadata.id3.Id3Frame;
|
import com.google.android.exoplayer2.metadata.id3.Id3Frame;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.TextRenderer;
|
import com.google.android.exoplayer2.text.TextRenderer;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
||||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
|
@ -389,6 +391,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||||
return player.getCurrentPeriodIndex();
|
return player.getCurrentPeriodIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline getCurrentTimeline() {
|
||||||
|
return player.getCurrentTimeline();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getBufferedPosition() {
|
public long getBufferedPosition() {
|
||||||
return player.getBufferedPosition();
|
return player.getBufferedPosition();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenates multiple {@link MediaSource}s.
|
* Concatenates multiple {@link MediaSource}s.
|
||||||
|
|
@ -23,45 +26,50 @@ import java.io.IOException;
|
||||||
public final class ConcatenatingMediaSource implements MediaSource {
|
public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
|
|
||||||
private final MediaSource[] mediaSources;
|
private final MediaSource[] mediaSources;
|
||||||
|
private final Timeline[] timelines;
|
||||||
|
|
||||||
|
private ConcatenatedTimeline timeline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mediaSources The {@link MediaSource}s to concatenate.
|
* @param mediaSources The {@link MediaSource}s to concatenate.
|
||||||
*/
|
*/
|
||||||
public ConcatenatingMediaSource(MediaSource... mediaSources) {
|
public ConcatenatingMediaSource(MediaSource... mediaSources) {
|
||||||
this.mediaSources = mediaSources;
|
this.mediaSources = mediaSources;
|
||||||
|
timelines = new Timeline[mediaSources.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource() {
|
public void prepareSource(final InvalidationListener listener) {
|
||||||
for (MediaSource mediaSource : mediaSources) {
|
for (int i = 0; i < mediaSources.length; i++) {
|
||||||
mediaSource.prepareSource();
|
final int index = i;
|
||||||
|
mediaSources[i].prepareSource(new InvalidationListener() {
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
timelines[index] = timeline;
|
||||||
|
ConcatenatingMediaSource.this.timeline = new ConcatenatedTimeline(timelines.clone());
|
||||||
|
listener.onTimelineChanged(ConcatenatingMediaSource.this.timeline);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPeriodCount() {
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline)
|
||||||
int sourceCount = 0;
|
throws IOException {
|
||||||
for (MediaSource mediaSource : mediaSources) {
|
ConcatenatedTimeline oldConcatenatedTimeline = (ConcatenatedTimeline) oldTimeline;
|
||||||
int count = mediaSource.getPeriodCount();
|
int sourceIndex = oldConcatenatedTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex);
|
||||||
if (count == MediaSource.UNKNOWN_PERIOD_COUNT) {
|
int sourceFirstPeriodIndex = oldConcatenatedTimeline.getFirstPeriodIndexInSource(sourceIndex);
|
||||||
return UNKNOWN_PERIOD_COUNT;
|
return sourceFirstPeriodIndex == Timeline.NO_PERIOD_INDEX ? Timeline.NO_PERIOD_INDEX
|
||||||
}
|
: sourceFirstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex(
|
||||||
sourceCount += count;
|
oldPlayingPeriodIndex - sourceFirstPeriodIndex,
|
||||||
}
|
oldConcatenatedTimeline.timelines[sourceIndex]);
|
||||||
return sourceCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) throws IOException {
|
public MediaPeriod createPeriod(int index) throws IOException {
|
||||||
int sourceCount = 0;
|
int sourceIndex = timeline.getSourceIndexForPeriod(index);
|
||||||
for (MediaSource mediaSource : mediaSources) {
|
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
|
||||||
int count = mediaSource.getPeriodCount();
|
return mediaSources[sourceIndex].createPeriod(periodIndexInSource);
|
||||||
if (count == MediaSource.UNKNOWN_PERIOD_COUNT || index < sourceCount + count) {
|
|
||||||
return mediaSource.createPeriod(index - sourceCount);
|
|
||||||
}
|
|
||||||
sourceCount += count;
|
|
||||||
}
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -71,4 +79,99 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Timeline} that is the concatenation of one or more {@link Timeline}s.
|
||||||
|
*/
|
||||||
|
private static final class ConcatenatedTimeline implements Timeline {
|
||||||
|
|
||||||
|
private final Timeline[] timelines;
|
||||||
|
private final Object[] manifests;
|
||||||
|
private final int count;
|
||||||
|
private final boolean isFinal;
|
||||||
|
private int[] sourceOffsets;
|
||||||
|
|
||||||
|
public ConcatenatedTimeline(Timeline[] timelines) {
|
||||||
|
this.timelines = timelines;
|
||||||
|
|
||||||
|
int[] sourceOffsets = new int[timelines.length];
|
||||||
|
int sourceIndexOffset = 0;
|
||||||
|
for (int i = 0; i < timelines.length; i++) {
|
||||||
|
Timeline manifest = timelines[i];
|
||||||
|
int periodCount;
|
||||||
|
if (manifest == null
|
||||||
|
|| (periodCount = manifest.getPeriodCount()) == Timeline.UNKNOWN_PERIOD_COUNT) {
|
||||||
|
sourceOffsets = Arrays.copyOf(sourceOffsets, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sourceIndexOffset += periodCount;
|
||||||
|
sourceOffsets[i] = sourceIndexOffset;
|
||||||
|
}
|
||||||
|
this.sourceOffsets = sourceOffsets;
|
||||||
|
count = sourceOffsets.length == timelines.length ? sourceOffsets[sourceOffsets.length - 1]
|
||||||
|
: UNKNOWN_PERIOD_COUNT;
|
||||||
|
boolean isFinal = true;
|
||||||
|
manifests = new Object[timelines.length];
|
||||||
|
for (int i = 0; i < timelines.length; i++) {
|
||||||
|
Timeline timeline = timelines[i];
|
||||||
|
if (timeline != null) {
|
||||||
|
manifests[i] = timeline.getManifest();
|
||||||
|
if (!timeline.isFinal()) {
|
||||||
|
isFinal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isFinal = isFinal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeriodCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinal() {
|
||||||
|
return isFinal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getPeriodDuration(int index) {
|
||||||
|
int sourceIndex = getSourceIndexForPeriod(index);
|
||||||
|
return timelines[sourceIndex].getPeriodDuration(sourceIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPeriodId(int index) {
|
||||||
|
int sourceIndex = getSourceIndexForPeriod(index);
|
||||||
|
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(index);
|
||||||
|
return timelines[sourceIndex].getPeriodId(index - firstPeriodIndexInSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexOfPeriod(Object id) {
|
||||||
|
for (int sourceIndex = 0; sourceIndex < timelines.length; sourceIndex++) {
|
||||||
|
int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(id);
|
||||||
|
if (periodIndexInSource != NO_PERIOD_INDEX) {
|
||||||
|
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
|
||||||
|
return firstPeriodIndexInSource + periodIndexInSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NO_PERIOD_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getManifest() {
|
||||||
|
return manifests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSourceIndexForPeriod(int periodIndex) {
|
||||||
|
return Util.binarySearchFloor(sourceOffsets, periodIndex, true, false) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getFirstPeriodIndexInSource(int sourceIndex) {
|
||||||
|
return sourceIndex == 0 ? 0 : sourceIndex > sourceOffsets.length
|
||||||
|
? Timeline.NO_PERIOD_INDEX : sourceOffsets[sourceIndex - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,13 +180,13 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource() {
|
public void prepareSource(InvalidationListener listener) {
|
||||||
// do nothing
|
listener.onTimelineChanged(new SinglePeriodTimeline(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPeriodCount() {
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) {
|
||||||
return 1;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -23,28 +23,44 @@ import java.io.IOException;
|
||||||
public interface MediaSource {
|
public interface MediaSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returned by {@link #getPeriodCount()} if the number of periods is not known.
|
* Listener for invalidation events.
|
||||||
*/
|
*/
|
||||||
int UNKNOWN_PERIOD_COUNT = -1;
|
interface InvalidationListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the timeline is invalidated.
|
||||||
|
* <p>
|
||||||
|
* May only be called on the player's thread.
|
||||||
|
*
|
||||||
|
* @param timeline The new timeline.
|
||||||
|
*/
|
||||||
|
void onTimelineChanged(Timeline timeline);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts preparation of the source.
|
* Starts preparation of the source.
|
||||||
|
*
|
||||||
|
* @param listener The listener for source invalidation events.
|
||||||
*/
|
*/
|
||||||
void prepareSource();
|
void prepareSource(InvalidationListener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of periods in the source, or {@link #UNKNOWN_PERIOD_COUNT} if the number
|
* Returns the period index to play in this source's new timeline.
|
||||||
* of periods is not yet known.
|
*
|
||||||
|
* @param oldPlayingPeriodIndex The period index that was being played in the old timeline.
|
||||||
|
* @param oldTimeline The old timeline.
|
||||||
|
* @return The period index to play in this source's new timeline.
|
||||||
|
* @throws IOException Thrown if the required period can't be loaded.
|
||||||
*/
|
*/
|
||||||
int getPeriodCount();
|
int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link MediaPeriod} corresponding to the period at the specified index, or
|
* Returns a {@link MediaPeriod} corresponding to the period at the specified index, or
|
||||||
* {@code null} if the period at the specified index is not yet available.
|
* {@code null} if the period at the specified index is not yet available.
|
||||||
*
|
*
|
||||||
* @param index The index of the period. Must be less than {@link #getPeriodCount()} unless the
|
* @param index The index of the period.
|
||||||
* period count is {@link #UNKNOWN_PERIOD_COUNT}.
|
* @return A {@link MediaPeriod}, or {@code null} if the source at the specified index is not
|
||||||
* @return A {@link MediaPeriod}, or {@code null} if the source at the specified index is not yet
|
|
||||||
* available.
|
* available.
|
||||||
* @throws IOException If there is an error that's preventing the source from becoming prepared or
|
* @throws IOException If there is an error that's preventing the source from becoming prepared or
|
||||||
* creating periods.
|
* creating periods.
|
||||||
|
|
|
||||||
|
|
@ -22,38 +22,47 @@ import java.io.IOException;
|
||||||
/**
|
/**
|
||||||
* Merges multiple {@link MediaPeriod} instances.
|
* Merges multiple {@link MediaPeriod} instances.
|
||||||
* <p>
|
* <p>
|
||||||
* The {@link MediaSource}s being merged must have known and equal period counts, and may not return
|
* The {@link MediaSource}s being merged must have final timelines and equal period counts.
|
||||||
* {@code null} from {@link #createPeriod(int)}.
|
|
||||||
*/
|
*/
|
||||||
public final class MergingMediaSource implements MediaSource {
|
public final class MergingMediaSource implements MediaSource {
|
||||||
|
|
||||||
private final MediaSource[] mediaSources;
|
private final MediaSource[] mediaSources;
|
||||||
private final int periodCount;
|
|
||||||
|
private int periodCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mediaSources The {@link MediaSource}s to merge.
|
* @param mediaSources The {@link MediaSource}s to merge.
|
||||||
*/
|
*/
|
||||||
public MergingMediaSource(MediaSource... mediaSources) {
|
public MergingMediaSource(MediaSource... mediaSources) {
|
||||||
this.mediaSources = mediaSources;
|
this.mediaSources = mediaSources;
|
||||||
periodCount = mediaSources[0].getPeriodCount();
|
periodCount = -1;
|
||||||
Assertions.checkState(periodCount != UNKNOWN_PERIOD_COUNT,
|
}
|
||||||
"Child sources must have known period counts");
|
|
||||||
for (MediaSource mediaSource : mediaSources) {
|
@Override
|
||||||
Assertions.checkState(mediaSource.getPeriodCount() == periodCount,
|
public void prepareSource(final InvalidationListener listener) {
|
||||||
"Child sources must have equal period counts");
|
mediaSources[0].prepareSource(new InvalidationListener() {
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
checkConsistentTimeline(timeline);
|
||||||
|
|
||||||
|
// All source timelines must match.
|
||||||
|
listener.onTimelineChanged(timeline);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (int i = 1; i < mediaSources.length; i++) {
|
||||||
|
mediaSources[i].prepareSource(new InvalidationListener() {
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
checkConsistentTimeline(timeline);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource() {
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline)
|
||||||
for (MediaSource mediaSource : mediaSources) {
|
throws IOException {
|
||||||
mediaSource.prepareSource();
|
return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPeriodCount() {
|
|
||||||
return periodCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -73,4 +82,14 @@ public final class MergingMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkConsistentTimeline(Timeline timeline) {
|
||||||
|
Assertions.checkArgument(timeline.isFinal());
|
||||||
|
int periodCount = timeline.getPeriodCount();
|
||||||
|
if (this.periodCount == -1) {
|
||||||
|
this.periodCount = periodCount;
|
||||||
|
} else {
|
||||||
|
Assertions.checkState(this.periodCount == periodCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Timeline} consisting of a single period.
|
||||||
|
*/
|
||||||
|
public final class SinglePeriodTimeline implements Timeline {
|
||||||
|
|
||||||
|
private final Object id;
|
||||||
|
private final Object manifest;
|
||||||
|
private final long duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new timeline with one period of unknown duration.
|
||||||
|
*
|
||||||
|
* @param id The identifier for the period.
|
||||||
|
*/
|
||||||
|
public SinglePeriodTimeline(Object id) {
|
||||||
|
this(id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new timeline with one period of unknown duration providing an optional manifest.
|
||||||
|
*
|
||||||
|
* @param id The identifier for the period.
|
||||||
|
* @param manifest The source-specific manifest that defined the period, or {@code null}.
|
||||||
|
*/
|
||||||
|
public SinglePeriodTimeline(Object id, Object manifest) {
|
||||||
|
this(id, manifest, ExoPlayer.UNKNOWN_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new timeline with one period of the specified duration providing an optional
|
||||||
|
* manifest.
|
||||||
|
*
|
||||||
|
* @param id The identifier for the period.
|
||||||
|
* @param manifest The source-specific manifest that defined the period, or {@code null}.
|
||||||
|
* @param duration The duration of the period in milliseconds.
|
||||||
|
*/
|
||||||
|
public SinglePeriodTimeline(Object id, Object manifest, long duration) {
|
||||||
|
this.id = Assertions.checkNotNull(id);
|
||||||
|
this.manifest = manifest;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeriodCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getPeriodDuration(int index) {
|
||||||
|
if (index != 0) {
|
||||||
|
throw new IndexOutOfBoundsException("Index " + index + " out of bounds");
|
||||||
|
}
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPeriodId(int index) {
|
||||||
|
return index == 0 ? id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexOfPeriod(Object id) {
|
||||||
|
return id.equals(this.id) ? 0 : Timeline.NO_PERIOD_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getManifest() {
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -115,13 +115,13 @@ public final class SingleSampleMediaSource implements MediaPeriod, MediaSource,
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource() {
|
public void prepareSource(InvalidationListener listener) {
|
||||||
// do nothing
|
listener.onTimelineChanged(new SinglePeriodTimeline(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPeriodCount() {
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) {
|
||||||
return 1;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The player's timeline consisting of one or more periods. Instances are immutable.
|
||||||
|
*/
|
||||||
|
public interface Timeline {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returned by {@link #getPeriodCount()} when the number of periods is not known.
|
||||||
|
*/
|
||||||
|
int UNKNOWN_PERIOD_COUNT = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returned by {@link #getIndexOfPeriod(Object)} if no period index corresponds to the specified
|
||||||
|
* identifier.
|
||||||
|
*/
|
||||||
|
int NO_PERIOD_INDEX = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of periods in the timeline, or {@link #UNKNOWN_PERIOD_COUNT} if not known.
|
||||||
|
* If {@link #isFinal()} returns {@code true}, the number of periods must be known.
|
||||||
|
*/
|
||||||
|
int getPeriodCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the timeline is final, which means it will not be invalidated again.
|
||||||
|
*/
|
||||||
|
boolean isFinal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration of the period at {@code index} in the timeline, in milliseconds, or
|
||||||
|
* {@link ExoPlayer#UNKNOWN_TIME} if not known.
|
||||||
|
*
|
||||||
|
* @param index The index of the period.
|
||||||
|
* @return The duration of the period in milliseconds, or {@link ExoPlayer#UNKNOWN_TIME}.
|
||||||
|
*/
|
||||||
|
long getPeriodDuration(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a unique identifier for the period at {@code index}, or {@code null} if the period at
|
||||||
|
* {@code index} is not known. The identifier is stable across {@link Timeline} instances.
|
||||||
|
* <p>
|
||||||
|
* When a source is invalidated the player uses period identifiers to determine what periods are
|
||||||
|
* unchanged. Implementations that associate an object with each period can return the object for
|
||||||
|
* the provided index to guarantee uniqueness. Other implementations must be careful to return
|
||||||
|
* identifiers that can't clash with (for example) identifiers used by other timelines that may be
|
||||||
|
* concatenated with this one.
|
||||||
|
*
|
||||||
|
* @param index A period index.
|
||||||
|
* @return An identifier for the period, or {@code null} if the period is not known.
|
||||||
|
*/
|
||||||
|
Object getPeriodId(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the period identified by {@code id}, or {@link #NO_PERIOD_INDEX} if the
|
||||||
|
* period is not in the timeline.
|
||||||
|
*
|
||||||
|
* @param id An identifier for a period.
|
||||||
|
* @return The index of the period, or {@link #NO_PERIOD_INDEX} if the period was not found.
|
||||||
|
*/
|
||||||
|
int getIndexOfPeriod(Object id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the immutable manifest corresponding to this timeline.
|
||||||
|
*/
|
||||||
|
Object getManifest();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
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;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
|
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
|
||||||
|
|
@ -62,6 +63,7 @@ public final class DashMediaSource implements MediaSource {
|
||||||
private final DashManifestParser manifestParser;
|
private final DashManifestParser manifestParser;
|
||||||
private final ManifestCallback manifestCallback;
|
private final ManifestCallback manifestCallback;
|
||||||
|
|
||||||
|
private MediaSource.InvalidationListener invalidationListener;
|
||||||
private DataSource dataSource;
|
private DataSource dataSource;
|
||||||
private Loader loader;
|
private Loader loader;
|
||||||
|
|
||||||
|
|
@ -95,7 +97,8 @@ public final class DashMediaSource implements MediaSource {
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource() {
|
public void prepareSource(InvalidationListener listener) {
|
||||||
|
invalidationListener = listener;
|
||||||
dataSource = manifestDataSourceFactory.createDataSource();
|
dataSource = manifestDataSourceFactory.createDataSource();
|
||||||
loader = new Loader("Loader:DashMediaSource");
|
loader = new Loader("Loader:DashMediaSource");
|
||||||
manifestRefreshHandler = new Handler();
|
manifestRefreshHandler = new Handler();
|
||||||
|
|
@ -103,11 +106,22 @@ public final class DashMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPeriodCount() {
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) {
|
||||||
if (manifest == null) {
|
int periodIndex = oldPlayingPeriodIndex;
|
||||||
return UNKNOWN_PERIOD_COUNT;
|
int oldPeriodCount = oldTimeline.getPeriodCount();
|
||||||
|
while (oldPeriodCount == Timeline.UNKNOWN_PERIOD_COUNT || periodIndex < oldPeriodCount) {
|
||||||
|
Object id = oldTimeline.getPeriodId(periodIndex);
|
||||||
|
if (id == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < periods.length; i++) {
|
||||||
|
if (periods[i] == id) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
periodIndex++;
|
||||||
}
|
}
|
||||||
return manifest.getPeriodCount();
|
return Timeline.NO_PERIOD_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -161,6 +175,8 @@ public final class DashMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
scheduleManifestRefresh();
|
scheduleManifestRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalidationListener.onTimelineChanged(new DashTimeline(manifest, periods));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int onManifestLoadError(ParsingLoadable<DashManifest> loadable,
|
/* package */ int onManifestLoadError(ParsingLoadable<DashManifest> loadable,
|
||||||
|
|
@ -278,6 +294,53 @@ public final class DashMediaSource implements MediaSource {
|
||||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class DashTimeline implements Timeline {
|
||||||
|
|
||||||
|
private final DashManifest manifest;
|
||||||
|
private final DashMediaPeriod[] periods;
|
||||||
|
|
||||||
|
public DashTimeline(DashManifest manifest, DashMediaPeriod[] periods) {
|
||||||
|
this.manifest = manifest;
|
||||||
|
this.periods = periods;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeriodCount() {
|
||||||
|
return manifest.getPeriodCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinal() {
|
||||||
|
return !manifest.dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getPeriodDuration(int index) {
|
||||||
|
return manifest.getPeriodDuration(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPeriodId(int index) {
|
||||||
|
return index >= periods.length ? null : periods[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexOfPeriod(Object id) {
|
||||||
|
for (int i = 0; i < periods.length; i++) {
|
||||||
|
if (id == periods[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Timeline.NO_PERIOD_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getManifest() {
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private final class ManifestCallback implements
|
private final class ManifestCallback implements
|
||||||
Loader.Callback<ParsingLoadable<DashManifest>> {
|
Loader.Callback<ParsingLoadable<DashManifest>> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import com.google.android.exoplayer2.source.CompositeSequenceableLoader;
|
||||||
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;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
|
import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
|
||||||
|
|
@ -110,13 +112,14 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource() {
|
public void prepareSource(InvalidationListener listener) {
|
||||||
// do nothing
|
// TODO: Defer until the playlist has been loaded.
|
||||||
|
listener.onTimelineChanged(new SinglePeriodTimeline(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPeriodCount() {
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) {
|
||||||
return 1;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
import com.google.android.exoplayer2.source.SequenceableLoader;
|
import com.google.android.exoplayer2.source.SequenceableLoader;
|
||||||
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
|
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
|
||||||
|
|
@ -72,6 +74,7 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
private final EventDispatcher eventDispatcher;
|
private final EventDispatcher eventDispatcher;
|
||||||
private final SsManifestParser manifestParser;
|
private final SsManifestParser manifestParser;
|
||||||
|
|
||||||
|
private MediaSource.InvalidationListener invalidationListener;
|
||||||
private DataSource manifestDataSource;
|
private DataSource manifestDataSource;
|
||||||
private Loader manifestLoader;
|
private Loader manifestLoader;
|
||||||
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
|
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
|
||||||
|
|
@ -111,13 +114,13 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource() {
|
public void prepareSource(InvalidationListener listener) {
|
||||||
// do nothing
|
this.invalidationListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPeriodCount() {
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) {
|
||||||
return 1;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -273,6 +276,9 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
|
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
|
||||||
if (!prepared) {
|
if (!prepared) {
|
||||||
durationUs = manifest.durationUs;
|
durationUs = manifest.durationUs;
|
||||||
|
Timeline timeline = durationUs == C.UNSET_TIME_US ? new SinglePeriodTimeline(this, manifest)
|
||||||
|
: new SinglePeriodTimeline(this, manifest, durationUs / 1000);
|
||||||
|
invalidationListener.onTimelineChanged(timeline);
|
||||||
buildTrackGroups(manifest);
|
buildTrackGroups(manifest);
|
||||||
ProtectionElement protectionElement = manifest.protectionElement;
|
ProtectionElement protectionElement = manifest.protectionElement;
|
||||||
if (protectionElement != null) {
|
if (protectionElement != null) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
|
@ -163,6 +164,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
|
||||||
updateTextView();
|
updateTextView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,26 @@ public final class Util {
|
||||||
return Math.max(min, Math.min(value, max));
|
return Math.max(min, Math.min(value, max));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the largest value in an array that is less than (or optionally equal to)
|
||||||
|
* a specified key.
|
||||||
|
* <p>
|
||||||
|
* The search is performed using a binary search algorithm, and so the array must be sorted.
|
||||||
|
*
|
||||||
|
* @param a The array to search.
|
||||||
|
* @param key The key being searched for.
|
||||||
|
* @param inclusive If the key is present in the array, whether to return the corresponding index.
|
||||||
|
* If false then the returned index corresponds to the largest value in the array that is
|
||||||
|
* strictly less than the key.
|
||||||
|
* @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than
|
||||||
|
* the smallest value in the array. If false then -1 will be returned.
|
||||||
|
*/
|
||||||
|
public static int binarySearchFloor(int[] a, int key, boolean inclusive, boolean stayInBounds) {
|
||||||
|
int index = Arrays.binarySearch(a, key);
|
||||||
|
index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1));
|
||||||
|
return stayInBounds ? Math.max(0, index) : index;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the index of the largest value in an array that is less than (or optionally equal to)
|
* Returns the index of the largest value in an array that is less than (or optionally equal to)
|
||||||
* a specified key.
|
* a specified key.
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.playbacktests.util.HostActivity.HostedTest;
|
import com.google.android.exoplayer2.playbacktests.util.HostActivity.HostedTest;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.Timeline;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
@ -34,6 +35,7 @@ import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -212,6 +214,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onTimelineChanged(Timeline timeline) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
// SimpleExoPlayer.DebugListener
|
// SimpleExoPlayer.DebugListener
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue