Commit d6573603 by andrewlewis Committed by Oliver Woodman

Expose period information from MediaSources.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=128357449
parent 2a70a58f
Showing with 752 additions and 143 deletions
...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; ...@@ -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;
...@@ -88,6 +89,14 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb ...@@ -88,6 +89,14 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
} }
@Override @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
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; ...@@ -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;
...@@ -458,6 +459,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -458,6 +459,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
} }
@Override @Override
public void onTimelineChanged(Timeline timeline) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException e) { public void onPlayerError(ExoPlaybackException e) {
String errorString = null; String errorString = null;
if (e.type == ExoPlaybackException.TYPE_RENDERER) { if (e.type == ExoPlaybackException.TYPE_RENDERER) {
......
...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory; ...@@ -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;
...@@ -101,6 +102,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { ...@@ -101,6 +102,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onTimelineChanged(Timeline timeline) {
// Do nothing.
}
@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; ...@@ -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;
...@@ -101,6 +102,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { ...@@ -101,6 +102,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onTimelineChanged(Timeline timeline) {
// Do nothing.
}
@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; ...@@ -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;
...@@ -120,6 +121,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { ...@@ -120,6 +121,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onTimelineChanged(Timeline timeline) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) { public void onPlayerError(ExoPlaybackException error) {
playbackException = error; playbackException = error;
} }
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2; ...@@ -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
...@@ -139,6 +140,13 @@ public interface ExoPlayer { ...@@ -139,6 +140,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
* can still be used, and {@link ExoPlayer#release()} must still be called on the player should * can still be used, and {@link ExoPlayer#release()} must still be called on the player should
...@@ -329,10 +337,10 @@ public interface ExoPlayer { ...@@ -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();
...@@ -351,6 +359,13 @@ public interface ExoPlayer { ...@@ -351,6 +359,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.
* *
* @return An estimate of the absolute position in milliseconds up to which data is buffered, * @return An estimate of the absolute position in milliseconds up to which data is buffered,
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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);
} }
...@@ -189,6 +192,11 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -189,6 +192,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public Timeline getCurrentTimeline() {
return timeline;
}
@Override
public long getBufferedPosition() { public long getBufferedPosition() {
if (pendingSeekAcks == 0) { if (pendingSeekAcks == 0) {
long bufferedPositionUs = playbackInfo.bufferedPositionUs; long bufferedPositionUs = playbackInfo.bufferedPositionUs;
...@@ -245,6 +253,13 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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,24 +485,30 @@ import java.util.ArrayList; ...@@ -464,24 +485,30 @@ 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);
} finally {
eventHandler.sendEmptyMessage(MSG_SEEK_ACK);
}
}
private void seekToPeriodPosition(int periodIndex, long positionUs) throws ExoPlaybackException {
stopRenderers(); stopRenderers();
rebuffering = false; rebuffering = false;
seekPositionUs = timeline.seekTo(periodIndex, seekPositionUs); positionUs = internalTimeline.seekTo(periodIndex, positionUs);
if (periodIndex != playbackInfo.periodIndex) { if (periodIndex != playbackInfo.periodIndex) {
playbackInfo = new PlaybackInfo(periodIndex); playbackInfo = new PlaybackInfo(periodIndex);
playbackInfo.positionUs = seekPositionUs; playbackInfo.positionUs = positionUs;
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget(); eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
} else { } else {
playbackInfo.positionUs = seekPositionUs; playbackInfo.positionUs = positionUs;
} }
updatePlaybackPositions(); updatePlaybackPositions();
...@@ -489,13 +516,11 @@ import java.util.ArrayList; ...@@ -489,13 +516,11 @@ import java.util.ArrayList;
setState(ExoPlayer.STATE_BUFFERING); setState(ExoPlayer.STATE_BUFFERING);
handler.sendEmptyMessage(MSG_DO_SOME_WORK); handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} }
} finally {
eventHandler.sendEmptyMessage(MSG_SEEK_ACK);
}
} }
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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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,23 +661,114 @@ import java.util.ArrayList; ...@@ -635,23 +661,114 @@ 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, Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaPeriod,
mediaPeriod, periodIndex); timeline.getPeriodId(periodIndex), periodIndex);
newPeriod.isLast = timeline.isFinal() && periodIndex == timeline.getPeriodCount() - 1;
if (loadingPeriod != null) { if (loadingPeriod != null) {
loadingPeriod.setNextPeriod(newPeriod); loadingPeriod.setNextPeriod(newPeriod);
} }
...@@ -662,7 +779,6 @@ import java.util.ArrayList; ...@@ -662,7 +779,6 @@ import java.util.ArrayList;
loadControl.getAllocator(), startPositionUs); loadControl.getAllocator(), startPositionUs);
} }
} }
}
if (loadingPeriod == null || loadingPeriod.isFullyBuffered()) { if (loadingPeriod == null || loadingPeriod.isFullyBuffered()) {
setIsLoading(false); setIsLoading(false);
...@@ -725,10 +841,8 @@ import java.util.ArrayList; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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;
...@@ -390,6 +392,11 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -390,6 +392,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public Timeline getCurrentTimeline() {
return player.getCurrentTimeline();
}
@Override
public long getBufferedPosition() { public long getBufferedPosition() {
return player.getBufferedPosition(); return player.getBufferedPosition();
} }
......
...@@ -15,7 +15,10 @@ ...@@ -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,52 +26,152 @@ import java.io.IOException; ...@@ -23,52 +26,152 @@ 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
public void prepareSource(final InvalidationListener listener) {
for (int i = 0; i < mediaSources.length; i++) {
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
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline)
throws IOException {
ConcatenatedTimeline oldConcatenatedTimeline = (ConcatenatedTimeline) oldTimeline;
int sourceIndex = oldConcatenatedTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex);
int sourceFirstPeriodIndex = oldConcatenatedTimeline.getFirstPeriodIndexInSource(sourceIndex);
return sourceFirstPeriodIndex == Timeline.NO_PERIOD_INDEX ? Timeline.NO_PERIOD_INDEX
: sourceFirstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex(
oldPlayingPeriodIndex - sourceFirstPeriodIndex,
oldConcatenatedTimeline.timelines[sourceIndex]);
}
@Override
public MediaPeriod createPeriod(int index) throws IOException {
int sourceIndex = timeline.getSourceIndexForPeriod(index);
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
return mediaSources[sourceIndex].createPeriod(periodIndexInSource);
} }
@Override @Override
public void prepareSource() { public void releaseSource() {
for (MediaSource mediaSource : mediaSources) { for (MediaSource mediaSource : mediaSources) {
mediaSource.prepareSource(); mediaSource.releaseSource();
} }
} }
/**
* 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 @Override
public int getPeriodCount() { public int getPeriodCount() {
int sourceCount = 0; return count;
for (MediaSource mediaSource : mediaSources) {
int count = mediaSource.getPeriodCount();
if (count == MediaSource.UNKNOWN_PERIOD_COUNT) {
return UNKNOWN_PERIOD_COUNT;
} }
sourceCount += count;
@Override
public boolean isFinal() {
return isFinal;
} }
return sourceCount;
@Override
public long getPeriodDuration(int index) {
int sourceIndex = getSourceIndexForPeriod(index);
return timelines[sourceIndex].getPeriodDuration(sourceIndex);
} }
@Override @Override
public MediaPeriod createPeriod(int index) throws IOException { public Object getPeriodId(int index) {
int sourceCount = 0; int sourceIndex = getSourceIndexForPeriod(index);
for (MediaSource mediaSource : mediaSources) { int firstPeriodIndexInSource = getFirstPeriodIndexInSource(index);
int count = mediaSource.getPeriodCount(); return timelines[sourceIndex].getPeriodId(index - firstPeriodIndexInSource);
if (count == MediaSource.UNKNOWN_PERIOD_COUNT || index < sourceCount + count) { }
return mediaSource.createPeriod(index - sourceCount);
@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;
} }
sourceCount += count;
} }
throw new IndexOutOfBoundsException(); return NO_PERIOD_INDEX;
} }
@Override @Override
public void releaseSource() { public Object getManifest() {
for (MediaSource mediaSource : mediaSources) { return manifests;
mediaSource.releaseSource();
} }
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, ...@@ -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; ...@@ -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; ...@@ -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) {
Assertions.checkState(mediaSource.getPeriodCount() == periodCount,
"Child sources must have equal period counts");
}
} }
@Override @Override
public void prepareSource() { public void prepareSource(final InvalidationListener listener) {
for (MediaSource mediaSource : mediaSources) { mediaSources[0].prepareSource(new InvalidationListener() {
mediaSource.prepareSource(); @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 int getPeriodCount() { public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline)
return periodCount; throws IOException {
return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline);
} }
@Override @Override
...@@ -73,4 +82,14 @@ public final class MergingMediaSource implements MediaSource { ...@@ -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);
}
}
} }
/*
* 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, ...@@ -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
......
/*
* 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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
} }
return manifest.getPeriodCount(); for (int i = 0; i < periods.length; i++) {
if (periods[i] == id) {
return i;
}
}
periodIndex++;
}
return Timeline.NO_PERIOD_INDEX;
} }
@Override @Override
...@@ -161,6 +175,8 @@ public final class DashMediaSource implements MediaSource { ...@@ -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 { ...@@ -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; ...@@ -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, ...@@ -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; ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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; ...@@ -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;
...@@ -164,6 +165,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe ...@@ -164,6 +165,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
} }
@Override @Override
public void onTimelineChanged(Timeline timeline) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) { public void onPlayerError(ExoPlaybackException error) {
// Do nothing. // Do nothing.
} }
......
...@@ -269,6 +269,26 @@ public final class Util { ...@@ -269,6 +269,26 @@ public final class Util {
* @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than * @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. * 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)
* 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(long[] a, long key, boolean inclusive, boolean stayInBounds) { public static int binarySearchFloor(long[] a, long key, boolean inclusive, boolean stayInBounds) {
int index = Arrays.binarySearch(a, key); int index = Arrays.binarySearch(a, key);
index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1));
......
...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; ...@@ -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; ...@@ -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 ...@@ -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
......
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