mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Finalize V2 ExoPlayer API
There's still some internal to clean up to do, and in particular it remains a TODO to be able to handle seek calls before the timeline is set (for this CL, such calls are dropped). This change does however finalize the API. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=131171318
This commit is contained in:
parent
298464ebe7
commit
5f1a2c71f0
27 changed files with 848 additions and 1010 deletions
|
|
@ -22,8 +22,8 @@ import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||||
|
|
@ -57,16 +57,22 @@ import java.util.Locale;
|
||||||
MappingTrackSelector.EventListener, MetadataRenderer.Output<List<Id3Frame>> {
|
MappingTrackSelector.EventListener, MetadataRenderer.Output<List<Id3Frame>> {
|
||||||
|
|
||||||
private static final String TAG = "EventLogger";
|
private static final String TAG = "EventLogger";
|
||||||
|
private static final int MAX_TIMELINE_ITEM_LINES = 3;
|
||||||
private static final NumberFormat TIME_FORMAT;
|
private static final NumberFormat TIME_FORMAT;
|
||||||
static {
|
static {
|
||||||
TIME_FORMAT = NumberFormat.getInstance(Locale.US);
|
TIME_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||||
TIME_FORMAT.setMinimumFractionDigits(2);
|
TIME_FORMAT.setMinimumFractionDigits(2);
|
||||||
TIME_FORMAT.setMaximumFractionDigits(2);
|
TIME_FORMAT.setMaximumFractionDigits(2);
|
||||||
|
TIME_FORMAT.setGroupingUsed(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Timeline.Window window;
|
||||||
|
private final Timeline.Period period;
|
||||||
private final long startTimeMs;
|
private final long startTimeMs;
|
||||||
|
|
||||||
public EventLogger() {
|
public EventLogger() {
|
||||||
|
window = new Timeline.Window();
|
||||||
|
period = new Timeline.Period();
|
||||||
startTimeMs = SystemClock.elapsedRealtime();
|
startTimeMs = SystemClock.elapsedRealtime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,13 +95,24 @@ import java.util.Locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
int periodCount = timeline.getPeriodCount();
|
int periodCount = timeline.getPeriodCount();
|
||||||
int windowCount = timeline.getWindowCount();
|
int windowCount = timeline.getWindowCount();
|
||||||
Log.d(TAG, "sourceInfo[startTime=" + timeline.getAbsoluteStartTime() + ", periodCount="
|
Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount);
|
||||||
+ periodCount + ", windows: " + windowCount);
|
for (int i = 0; i < Math.min(periodCount, MAX_TIMELINE_ITEM_LINES); i++) {
|
||||||
for (int windowIndex = 0; windowIndex < windowCount; windowIndex++) {
|
timeline.getPeriod(i, period);
|
||||||
Log.d(TAG, " " + timeline.getWindow(windowIndex));
|
Log.d(TAG, " " + "period [" + getTimeString(period.getDurationMs()) + "]");
|
||||||
|
}
|
||||||
|
if (periodCount > MAX_TIMELINE_ITEM_LINES) {
|
||||||
|
Log.d(TAG, " ...");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < Math.min(windowCount, MAX_TIMELINE_ITEM_LINES); i++) {
|
||||||
|
timeline.getWindow(i, window);
|
||||||
|
Log.d(TAG, " " + "window [" + getTimeString(window.getDurationMs()) + ", "
|
||||||
|
+ window.isSeekable + ", " + window.isDynamic + "]");
|
||||||
|
}
|
||||||
|
if (windowCount > MAX_TIMELINE_ITEM_LINES) {
|
||||||
|
Log.d(TAG, " ...");
|
||||||
}
|
}
|
||||||
Log.d(TAG, "]");
|
Log.d(TAG, "]");
|
||||||
}
|
}
|
||||||
|
|
@ -333,7 +350,7 @@ import java.util.Locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getTimeString(long timeMs) {
|
private static String getTimeString(long timeMs) {
|
||||||
return TIME_FORMAT.format((timeMs) / 1000f);
|
return timeMs == C.TIME_UNSET ? "?" : TIME_FORMAT.format((timeMs) / 1000f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getStateString(int state) {
|
private static String getStateString(int state) {
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,8 @@ import com.google.android.exoplayer2.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.MediaWindow;
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||||
|
|
@ -127,7 +126,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
|
||||||
private boolean playerNeedsSource;
|
private boolean playerNeedsSource;
|
||||||
|
|
||||||
private boolean shouldRestorePosition;
|
private boolean shouldRestorePosition;
|
||||||
private int playerPeriod;
|
private int playerWindow;
|
||||||
private long playerPosition;
|
private long playerPosition;
|
||||||
|
|
||||||
// Activity lifecycle
|
// Activity lifecycle
|
||||||
|
|
@ -283,9 +282,9 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
|
||||||
player.setVideoSurfaceHolder(surfaceView.getHolder());
|
player.setVideoSurfaceHolder(surfaceView.getHolder());
|
||||||
if (shouldRestorePosition) {
|
if (shouldRestorePosition) {
|
||||||
if (playerPosition == C.TIME_UNSET) {
|
if (playerPosition == C.TIME_UNSET) {
|
||||||
player.seekToDefaultPositionForPeriod(playerPeriod);
|
player.seekToDefaultPosition(playerWindow);
|
||||||
} else {
|
} else {
|
||||||
player.seekInPeriod(playerPeriod, playerPosition);
|
player.seekTo(playerWindow, playerPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.setPlayWhenReady(true);
|
player.setPlayWhenReady(true);
|
||||||
|
|
@ -376,13 +375,13 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
|
||||||
debugViewHelper.stop();
|
debugViewHelper.stop();
|
||||||
debugViewHelper = null;
|
debugViewHelper = null;
|
||||||
shouldRestorePosition = false;
|
shouldRestorePosition = false;
|
||||||
MediaTimeline playerTimeline = player.getCurrentTimeline();
|
Timeline timeline = player.getCurrentTimeline();
|
||||||
if (playerTimeline != null) {
|
if (timeline != null) {
|
||||||
MediaWindow window = playerTimeline.getWindow(player.getCurrentWindowIndex());
|
playerWindow = player.getCurrentWindowIndex();
|
||||||
|
Timeline.Window window = timeline.getWindow(playerWindow, new Timeline.Window());
|
||||||
if (!window.isDynamic) {
|
if (!window.isDynamic) {
|
||||||
shouldRestorePosition = true;
|
shouldRestorePosition = true;
|
||||||
playerPeriod = player.getCurrentPeriodIndex();
|
playerPosition = window.isSeekable ? player.getCurrentPosition() : C.TIME_UNSET;
|
||||||
playerPosition = window.isSeekable ? player.getCurrentPositionInPeriod() : C.TIME_UNSET;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -417,7 +416,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
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.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
|
|
@ -96,7 +96,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
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.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
|
|
@ -96,7 +96,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
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.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
|
|
@ -115,7 +115,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,10 +117,10 @@ public interface ExoPlayer {
|
||||||
*/
|
*/
|
||||||
void onPlayerStateChanged(boolean playWhenReady, int playbackState);
|
void onPlayerStateChanged(boolean playWhenReady, int playbackState);
|
||||||
|
|
||||||
// TODO[playlists]: Should source-initiated resets also cause this to be called?
|
// TODO: Should be windowIndex and position in the window.
|
||||||
/**
|
/**
|
||||||
* Called when the player's position changes due to a discontinuity (seeking or playback
|
* Called when the player's position changes due to a discontinuity (i.e. due to seeking,
|
||||||
* transitioning to the next period).
|
* playback transitioning to the next window, or a source induced discontinuity).
|
||||||
*
|
*
|
||||||
* @param periodIndex The index of the period being played.
|
* @param periodIndex The index of the period being played.
|
||||||
* @param positionMs The playback position in that period, in milliseconds.
|
* @param positionMs The playback position in that period, in milliseconds.
|
||||||
|
|
@ -133,7 +133,7 @@ public interface ExoPlayer {
|
||||||
* @param timeline The source's timeline.
|
* @param timeline The source's timeline.
|
||||||
* @param manifest The loaded manifest.
|
* @param manifest The loaded manifest.
|
||||||
*/
|
*/
|
||||||
void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest);
|
void onSourceInfoRefreshed(Timeline timeline, Object manifest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an error occurs. The playback state will transition to {@link #STATE_IDLE}
|
* Called when an error occurs. The playback state will transition to {@link #STATE_IDLE}
|
||||||
|
|
@ -249,7 +249,7 @@ public interface ExoPlayer {
|
||||||
* @param mediaSource The {@link MediaSource} to play.
|
* @param mediaSource The {@link MediaSource} to play.
|
||||||
* @param resetPosition Whether the playback position should be reset to the source's default
|
* @param resetPosition Whether the playback position should be reset to the source's default
|
||||||
* position. If false, playback will start from the position defined by
|
* position. If false, playback will start from the position defined by
|
||||||
* {@link #getCurrentPeriodIndex()} and {@link #getCurrentPositionInPeriod()}.
|
* {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}.
|
||||||
*/
|
*/
|
||||||
void setMediaSource(MediaSource mediaSource, boolean resetPosition);
|
void setMediaSource(MediaSource mediaSource, boolean resetPosition);
|
||||||
|
|
||||||
|
|
@ -277,35 +277,6 @@ public interface ExoPlayer {
|
||||||
*/
|
*/
|
||||||
boolean isLoading();
|
boolean isLoading();
|
||||||
|
|
||||||
/**
|
|
||||||
* Seeks to a position specified in milliseconds in the current period.
|
|
||||||
*
|
|
||||||
* @param positionMs The seek position.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
void seekInCurrentPeriod(long positionMs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seeks to the default position associated with the specified period. The position can depend on
|
|
||||||
* the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will
|
|
||||||
* typically be the live edge of the window to which the period belongs. For other streams it will
|
|
||||||
* typically be the start of the period.
|
|
||||||
*
|
|
||||||
* @param periodIndex The index of the period whose associated default position should be seeked
|
|
||||||
* to.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
void seekToDefaultPositionForPeriod(int periodIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seeks to a position specified in milliseconds in the specified period.
|
|
||||||
*
|
|
||||||
* @param periodIndex The index of the period.
|
|
||||||
* @param positionMs The seek position relative to the start of the period.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
void seekInPeriod(int periodIndex, long positionMs);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seeks to the default position associated with the current window. The position can depend on
|
* Seeks to the default position associated with the current window. The position can depend on
|
||||||
* the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will
|
* the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will
|
||||||
|
|
@ -333,7 +304,7 @@ public interface ExoPlayer {
|
||||||
void seekTo(long positionMs);
|
void seekTo(long positionMs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seeks to a position specified in milliseconds in the specified seek window.
|
* Seeks to a position specified in milliseconds in the specified window.
|
||||||
*
|
*
|
||||||
* @param windowIndex The index of the window.
|
* @param windowIndex The index of the window.
|
||||||
* @param positionMs The seek position relative to the start of the window.
|
* @param positionMs The seek position relative to the start of the window.
|
||||||
|
|
@ -376,9 +347,9 @@ public interface ExoPlayer {
|
||||||
void blockingSendMessages(ExoPlayerMessage... messages);
|
void blockingSendMessages(ExoPlayerMessage... messages);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current {@link MediaTimeline}, or {@code null} if there is no timeline.
|
* Returns the current {@link Timeline}, or {@code null} if there is no timeline.
|
||||||
*/
|
*/
|
||||||
MediaTimeline getCurrentTimeline();
|
Timeline getCurrentTimeline();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current manifest. The type depends on the {@link MediaSource} passed to
|
* Returns the current manifest. The type depends on the {@link MediaSource} passed to
|
||||||
|
|
@ -386,46 +357,9 @@ public interface ExoPlayer {
|
||||||
*/
|
*/
|
||||||
Object getCurrentManifest();
|
Object getCurrentManifest();
|
||||||
|
|
||||||
// Period based.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the index of the current period.
|
* Returns the index of the window associated with the current period, or {@link C#INDEX_UNSET} if
|
||||||
*/
|
* the timeline is not set.
|
||||||
@Deprecated
|
|
||||||
int getCurrentPeriodIndex();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the duration of the current period in milliseconds, or {@link C#TIME_UNSET} if the
|
|
||||||
* duration is not known.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
long getCurrentPeriodDuration();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the playback position in the current period, in milliseconds.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
long getCurrentPositionInPeriod();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an estimate of the position in the current period up to which data is buffered, or
|
|
||||||
* {@link C#TIME_UNSET} if no estimate is available.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
long getBufferedPositionInPeriod();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an estimate of the percentage in the current period up to which data is buffered, or 0
|
|
||||||
* if no estimate is available.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
int getBufferedPercentageInPeriod();
|
|
||||||
|
|
||||||
// MediaWindow based.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the seek window associated with the current period, or
|
|
||||||
* {@link C#INDEX_UNSET} if the timeline is not set.
|
|
||||||
*/
|
*/
|
||||||
int getCurrentWindowIndex();
|
int getCurrentWindowIndex();
|
||||||
|
|
||||||
|
|
@ -436,8 +370,8 @@ public interface ExoPlayer {
|
||||||
long getDuration();
|
long getDuration();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the playback position in the current seek window, in milliseconds, or
|
* Returns the playback position in the current window, in milliseconds, or {@link C#TIME_UNSET}
|
||||||
* {@link C#TIME_UNSET} if the timeline is not set.
|
* if the timeline is not set.
|
||||||
*/
|
*/
|
||||||
long getCurrentPosition();
|
long getCurrentPosition();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final ExoPlayerImplInternal internalPlayer;
|
private final ExoPlayerImplInternal internalPlayer;
|
||||||
private final CopyOnWriteArraySet<EventListener> listeners;
|
private final CopyOnWriteArraySet<EventListener> listeners;
|
||||||
|
private final Timeline.Window window;
|
||||||
|
private final Timeline.Period period;
|
||||||
|
|
||||||
private boolean playWhenReady;
|
private boolean playWhenReady;
|
||||||
private int playbackState;
|
private int playbackState;
|
||||||
private int pendingSeekAcks;
|
private int pendingSeekAcks;
|
||||||
private boolean isLoading;
|
private boolean isLoading;
|
||||||
private MediaTimeline timeline;
|
private Timeline timeline;
|
||||||
private Object manifest;
|
private Object manifest;
|
||||||
|
|
||||||
// Playback information when there is no pending seek/set source operation.
|
// Playback information when there is no pending seek/set source operation.
|
||||||
|
|
@ -68,6 +70,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
this.playWhenReady = false;
|
this.playWhenReady = false;
|
||||||
this.playbackState = STATE_IDLE;
|
this.playbackState = STATE_IDLE;
|
||||||
this.listeners = new CopyOnWriteArraySet<>();
|
this.listeners = new CopyOnWriteArraySet<>();
|
||||||
|
window = new Timeline.Window();
|
||||||
|
period = new Timeline.Period();
|
||||||
eventHandler = new Handler() {
|
eventHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
|
|
@ -126,30 +130,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
return isLoading;
|
return isLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seekInCurrentPeriod(long positionMs) {
|
|
||||||
seekInPeriod(getCurrentPeriodIndex(), positionMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seekToDefaultPositionForPeriod(int periodIndex) {
|
|
||||||
seekInPeriod(periodIndex, C.TIME_UNSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seekInPeriod(int periodIndex, long positionMs) {
|
|
||||||
boolean seekToDefaultPosition = positionMs == C.TIME_UNSET;
|
|
||||||
maskingPeriodIndex = periodIndex;
|
|
||||||
maskingPositionMs = seekToDefaultPosition ? 0 : positionMs;
|
|
||||||
pendingSeekAcks++;
|
|
||||||
internalPlayer.seekTo(periodIndex, seekToDefaultPosition ? C.TIME_UNSET : positionMs * 1000);
|
|
||||||
if (!seekToDefaultPosition) {
|
|
||||||
for (EventListener listener : listeners) {
|
|
||||||
listener.onPositionDiscontinuity(periodIndex, positionMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekToDefaultPosition() {
|
public void seekToDefaultPosition() {
|
||||||
seekToDefaultPosition(getCurrentWindowIndex());
|
seekToDefaultPosition(getCurrentWindowIndex());
|
||||||
|
|
@ -158,38 +138,38 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
@Override
|
@Override
|
||||||
public void seekToDefaultPosition(int windowIndex) {
|
public void seekToDefaultPosition(int windowIndex) {
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
throw new IllegalArgumentException("Windows are not yet known");
|
// TODO: Handle seeks before the timeline is set.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
|
Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
|
||||||
seekToDefaultPositionForPeriod(timeline.getWindowFirstPeriodIndex(windowIndex));
|
int periodIndex = timeline.getWindow(windowIndex, window).firstPeriodIndex;
|
||||||
|
seekToDefaultPositionForPeriod(periodIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(long positionMs) {
|
public void seekTo(long positionMs) {
|
||||||
MediaTimeline timeline = getCurrentTimeline();
|
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
throw new IllegalArgumentException("Windows are not yet known");
|
// TODO: Handle seeks before the timeline is set.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
int windowIndex = timeline.getPeriodWindowIndex(getCurrentPeriodIndex());
|
seekTo(getCurrentWindowIndex(), positionMs);
|
||||||
seekTo(windowIndex, positionMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(int windowIndex, long positionMs) {
|
public void seekTo(int windowIndex, long positionMs) {
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
throw new IllegalArgumentException("Windows are not yet known");
|
// TODO: Handle seeks before the timeline is set.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
|
Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
|
||||||
int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex);
|
timeline.getWindow(windowIndex, window);
|
||||||
int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex);
|
int periodIndex = window.firstPeriodIndex;
|
||||||
int periodIndex = firstPeriodIndex;
|
long periodPositionMs = window.getPositionInFirstPeriodMs() + positionMs;
|
||||||
long periodPositionMs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000
|
long periodDurationMs = timeline.getPeriod(periodIndex, period).getDurationMs();
|
||||||
+ positionMs;
|
|
||||||
long periodDurationMs = timeline.getPeriodDurationMs(periodIndex);
|
|
||||||
while (periodDurationMs != C.TIME_UNSET && periodPositionMs >= periodDurationMs
|
while (periodDurationMs != C.TIME_UNSET && periodPositionMs >= periodDurationMs
|
||||||
&& periodIndex < lastPeriodIndex) {
|
&& periodIndex < window.lastPeriodIndex) {
|
||||||
periodPositionMs -= periodDurationMs;
|
periodPositionMs -= periodDurationMs;
|
||||||
periodDurationMs = timeline.getPeriodDurationMs(++periodIndex);
|
periodDurationMs = timeline.getPeriod(++periodIndex, period).getDurationMs();
|
||||||
}
|
}
|
||||||
seekInPeriod(periodIndex, periodPositionMs);
|
seekInPeriod(periodIndex, periodPositionMs);
|
||||||
}
|
}
|
||||||
|
|
@ -215,55 +195,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
internalPlayer.blockingSendMessages(messages);
|
internalPlayer.blockingSendMessages(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public int getCurrentPeriodIndex() {
|
|
||||||
return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public long getCurrentPeriodDuration() {
|
|
||||||
if (timeline == null) {
|
|
||||||
return C.TIME_UNSET;
|
|
||||||
}
|
|
||||||
return timeline.getPeriodDurationMs(getCurrentPeriodIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public long getCurrentPositionInPeriod() {
|
|
||||||
return pendingSeekAcks > 0 ? maskingPositionMs : C.usToMs(playbackInfo.positionUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public long getBufferedPositionInPeriod() {
|
|
||||||
if (pendingSeekAcks == 0) {
|
|
||||||
return C.usToMs(playbackInfo.bufferedPositionUs);
|
|
||||||
} else {
|
|
||||||
return maskingPositionMs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public int getBufferedPercentageInPeriod() {
|
|
||||||
if (timeline == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
long bufferedPosition = getBufferedPositionInPeriod();
|
|
||||||
long duration = getCurrentPeriodDuration();
|
|
||||||
return (bufferedPosition == C.TIME_UNSET || duration == C.TIME_UNSET) ? 0
|
|
||||||
: (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentWindowIndex() {
|
public int getCurrentWindowIndex() {
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
return timeline.getPeriodWindowIndex(getCurrentPeriodIndex());
|
return timeline.getPeriod(getCurrentPeriodIndex(), period).windowIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -271,7 +208,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
return C.usToMs(timeline.getWindow(getCurrentWindowIndex()).durationUs);
|
return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -279,14 +216,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
int periodIndex = getCurrentPeriodIndex();
|
timeline.getPeriod(getCurrentPeriodIndex(), period);
|
||||||
int windowIndex = timeline.getPeriodWindowIndex(periodIndex);
|
return period.getPositionInWindowMs() + getCurrentPositionInPeriod();
|
||||||
long positionMs = getCurrentPositionInPeriod();
|
|
||||||
for (int i = timeline.getWindowFirstPeriodIndex(windowIndex); i < periodIndex; i++) {
|
|
||||||
positionMs += timeline.getPeriodDurationMs(i);
|
|
||||||
}
|
|
||||||
positionMs -= timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000;
|
|
||||||
return positionMs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -296,13 +227,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
int periodIndex = getCurrentPeriodIndex();
|
int periodIndex = getCurrentPeriodIndex();
|
||||||
int windowIndex = timeline.getPeriodWindowIndex(periodIndex);
|
timeline.getPeriod(periodIndex, period);
|
||||||
MediaWindow window = timeline.getWindow(windowIndex);
|
int windowIndex = period.windowIndex;
|
||||||
int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex);
|
timeline.getWindow(windowIndex, window);
|
||||||
int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex);
|
if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex
|
||||||
if (firstPeriodIndex == periodIndex && lastPeriodIndex == periodIndex
|
&& window.getPositionInFirstPeriodUs() == 0
|
||||||
&& timeline.getWindowOffsetInFirstPeriodUs(windowIndex) == 0
|
&& window.getDurationUs() == period.getDurationUs()) {
|
||||||
&& window.durationUs == timeline.getPeriodDurationUs(periodIndex)) {
|
|
||||||
return getBufferedPositionInPeriod();
|
return getBufferedPositionInPeriod();
|
||||||
}
|
}
|
||||||
return getCurrentPosition();
|
return getCurrentPosition();
|
||||||
|
|
@ -320,7 +250,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaTimeline getCurrentTimeline() {
|
public Timeline getCurrentTimeline() {
|
||||||
return timeline;
|
return timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,6 +259,44 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
|
private void seekToDefaultPositionForPeriod(int periodIndex) {
|
||||||
|
seekInPeriod(periodIndex, C.TIME_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
|
private void seekInPeriod(int periodIndex, long positionMs) {
|
||||||
|
boolean seekToDefaultPosition = positionMs == C.TIME_UNSET;
|
||||||
|
maskingPeriodIndex = periodIndex;
|
||||||
|
maskingPositionMs = seekToDefaultPosition ? 0 : positionMs;
|
||||||
|
pendingSeekAcks++;
|
||||||
|
internalPlayer.seekTo(periodIndex, seekToDefaultPosition ? C.TIME_UNSET : positionMs * 1000);
|
||||||
|
if (!seekToDefaultPosition) {
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onPositionDiscontinuity(periodIndex, positionMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
|
private int getCurrentPeriodIndex() {
|
||||||
|
return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
|
private long getCurrentPositionInPeriod() {
|
||||||
|
return pendingSeekAcks > 0 ? maskingPositionMs : C.usToMs(playbackInfo.positionUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
|
private long getBufferedPositionInPeriod() {
|
||||||
|
if (pendingSeekAcks == 0) {
|
||||||
|
return C.usToMs(playbackInfo.bufferedPositionUs);
|
||||||
|
} else {
|
||||||
|
return maskingPositionMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Not private so it can be called from an inner class without going through a thunk method.
|
// Not private so it can be called from an inner class without going through a thunk method.
|
||||||
/* package */ void handleEvent(Message msg) {
|
/* package */ void handleEvent(Message msg) {
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
|
|
@ -361,16 +329,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: {
|
case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: {
|
||||||
if (pendingSeekAcks == 0) {
|
if (pendingSeekAcks == 0) {
|
||||||
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
||||||
|
long positionMs = C.usToMs(playbackInfo.startPositionUs);
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
listener.onPositionDiscontinuity(playbackInfo.periodIndex,
|
listener.onPositionDiscontinuity(playbackInfo.periodIndex, positionMs);
|
||||||
C.usToMs(playbackInfo.startPositionUs));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
|
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Pair<MediaTimeline, Object> timelineAndManifest = (Pair<MediaTimeline, Object>) msg.obj;
|
Pair<Timeline, Object> timelineAndManifest = (Pair<Timeline, Object>) msg.obj;
|
||||||
timeline = timelineAndManifest.first;
|
timeline = timelineAndManifest.first;
|
||||||
manifest = timelineAndManifest.second;
|
manifest = timelineAndManifest.second;
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,8 @@ import java.io.IOException;
|
||||||
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.Window window;
|
||||||
|
private final Timeline.Period period;
|
||||||
|
|
||||||
private PlaybackInfo playbackInfo;
|
private PlaybackInfo playbackInfo;
|
||||||
private Renderer rendererMediaClockSource;
|
private Renderer rendererMediaClockSource;
|
||||||
|
|
@ -126,11 +128,11 @@ import java.io.IOException;
|
||||||
private boolean isTimelineReady;
|
private boolean isTimelineReady;
|
||||||
private boolean isTimelineEnded;
|
private boolean isTimelineEnded;
|
||||||
private int bufferAheadPeriodCount;
|
private int bufferAheadPeriodCount;
|
||||||
private Period playingPeriod;
|
private MediaPeriodHolder playingPeriodHolder;
|
||||||
private Period readingPeriod;
|
private MediaPeriodHolder readingPeriodHolder;
|
||||||
private Period loadingPeriod;
|
private MediaPeriodHolder loadingPeriodHolder;
|
||||||
|
|
||||||
private MediaTimeline timeline;
|
private Timeline timeline;
|
||||||
|
|
||||||
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
|
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
|
||||||
LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
|
LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
|
||||||
|
|
@ -150,6 +152,8 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
standaloneMediaClock = new StandaloneMediaClock();
|
standaloneMediaClock = new StandaloneMediaClock();
|
||||||
enabledRenderers = new Renderer[0];
|
enabledRenderers = new Renderer[0];
|
||||||
|
window = new Timeline.Window();
|
||||||
|
period = new Timeline.Period();
|
||||||
trackSelector.init(this);
|
trackSelector.init(this);
|
||||||
|
|
||||||
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
|
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
|
||||||
|
|
@ -220,7 +224,7 @@ import java.io.IOException;
|
||||||
// MediaSource.Listener implementation.
|
// MediaSource.Listener implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
handler.obtainMessage(MSG_REFRESH_SOURCE_INFO, Pair.create(timeline, manifest)).sendToTarget();
|
handler.obtainMessage(MSG_REFRESH_SOURCE_INFO, Pair.create(timeline, manifest)).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,7 +282,7 @@ import java.io.IOException;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MSG_REFRESH_SOURCE_INFO: {
|
case MSG_REFRESH_SOURCE_INFO: {
|
||||||
handleSourceInfoRefreshed((Pair<MediaTimeline, Object>) msg.obj);
|
handleSourceInfoRefreshed((Pair<Timeline, Object>) msg.obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: {
|
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: {
|
||||||
|
|
@ -375,13 +379,12 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePlaybackPositions() throws ExoPlaybackException {
|
private void updatePlaybackPositions() throws ExoPlaybackException {
|
||||||
if (playingPeriod == null) {
|
if (playingPeriodHolder == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MediaPeriod mediaPeriod = playingPeriod.mediaPeriod;
|
|
||||||
|
|
||||||
// Update the playback position.
|
// Update the playback position.
|
||||||
long periodPositionUs = mediaPeriod.readDiscontinuity();
|
long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity();
|
||||||
if (periodPositionUs != C.TIME_UNSET) {
|
if (periodPositionUs != C.TIME_UNSET) {
|
||||||
resetRendererPosition(periodPositionUs);
|
resetRendererPosition(periodPositionUs);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -391,23 +394,24 @@ import java.io.IOException;
|
||||||
} else {
|
} else {
|
||||||
rendererPositionUs = standaloneMediaClock.getPositionUs();
|
rendererPositionUs = standaloneMediaClock.getPositionUs();
|
||||||
}
|
}
|
||||||
periodPositionUs = rendererPositionUs - playingPeriod.rendererPositionOffsetUs;
|
periodPositionUs = rendererPositionUs - playingPeriodHolder.rendererPositionOffsetUs;
|
||||||
}
|
}
|
||||||
playbackInfo.positionUs = periodPositionUs;
|
playbackInfo.positionUs = periodPositionUs;
|
||||||
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
|
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
|
||||||
|
|
||||||
// Update the buffered position.
|
// Update the buffered position.
|
||||||
long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE
|
long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE
|
||||||
: mediaPeriod.getBufferedPositionUs();
|
: playingPeriodHolder.mediaPeriod.getBufferedPositionUs();
|
||||||
playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE
|
playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE
|
||||||
? timeline.getPeriodDurationUs(playingPeriod.index) : bufferedPositionUs;
|
? timeline.getPeriod(playingPeriodHolder.index, period).getDurationUs()
|
||||||
|
: bufferedPositionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSomeWork() throws ExoPlaybackException, IOException {
|
private void doSomeWork() throws ExoPlaybackException, IOException {
|
||||||
long operationStartTimeMs = SystemClock.elapsedRealtime();
|
long operationStartTimeMs = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
updatePeriods();
|
updatePeriods();
|
||||||
if (playingPeriod == null) {
|
if (playingPeriodHolder == null) {
|
||||||
// We're still waiting for the first period to be prepared.
|
// We're still waiting for the first period to be prepared.
|
||||||
maybeThrowPeriodPrepareError();
|
maybeThrowPeriodPrepareError();
|
||||||
scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
|
scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
|
||||||
|
|
@ -438,10 +442,11 @@ import java.io.IOException;
|
||||||
maybeThrowPeriodPrepareError();
|
maybeThrowPeriodPrepareError();
|
||||||
}
|
}
|
||||||
|
|
||||||
long playingPeriodDuration = timeline.getPeriodDurationUs(playingPeriod.index);
|
long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period)
|
||||||
|
.getDurationUs();
|
||||||
if (allRenderersEnded
|
if (allRenderersEnded
|
||||||
&& (playingPeriodDuration == C.TIME_UNSET
|
&& (playingPeriodDurationUs == C.TIME_UNSET
|
||||||
|| playingPeriodDuration <= playbackInfo.positionUs)
|
|| playingPeriodDurationUs <= playbackInfo.positionUs)
|
||||||
&& isTimelineEnded) {
|
&& isTimelineEnded) {
|
||||||
setState(ExoPlayer.STATE_ENDED);
|
setState(ExoPlayer.STATE_ENDED);
|
||||||
stopRenderers();
|
stopRenderers();
|
||||||
|
|
@ -526,27 +531,27 @@ import java.io.IOException;
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
|
|
||||||
if (periodPositionUs == C.TIME_UNSET
|
if (periodPositionUs == C.TIME_UNSET
|
||||||
|| (readingPeriod != playingPeriod && (periodIndex == playingPeriod.index
|
|| (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index
|
||||||
|| (readingPeriod != null && periodIndex == readingPeriod.index)))) {
|
|| (readingPeriodHolder != null && periodIndex == readingPeriodHolder.index)))) {
|
||||||
// Clear the timeline because either the seek position is not known, or a renderer is reading
|
// Clear the timeline because either the seek position is not known, or a renderer is reading
|
||||||
// ahead to the next period and the seek is to either the playing or reading period.
|
// ahead to the next period and the seek is to either the playing or reading period.
|
||||||
periodIndex = C.INDEX_UNSET;
|
periodIndex = C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the timeline, but keep the requested period if it is already prepared.
|
// Clear the timeline, but keep the requested period if it is already prepared.
|
||||||
Period period = playingPeriod;
|
MediaPeriodHolder periodHolder = playingPeriodHolder;
|
||||||
Period newPlayingPeriod = null;
|
MediaPeriodHolder newPlayingPeriodHolder = null;
|
||||||
while (period != null) {
|
while (periodHolder != null) {
|
||||||
if (period.index == periodIndex && period.prepared) {
|
if (periodHolder.index == periodIndex && periodHolder.prepared) {
|
||||||
newPlayingPeriod = period;
|
newPlayingPeriodHolder = periodHolder;
|
||||||
} else {
|
} else {
|
||||||
period.release();
|
periodHolder.release();
|
||||||
}
|
}
|
||||||
period = period.nextPeriod;
|
periodHolder = periodHolder.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable all the renderers if the period is changing.
|
// Disable all the renderers if the period is changing.
|
||||||
if (newPlayingPeriod != playingPeriod) {
|
if (newPlayingPeriodHolder != playingPeriodHolder) {
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
renderer.disable();
|
renderer.disable();
|
||||||
}
|
}
|
||||||
|
|
@ -557,21 +562,21 @@ import java.io.IOException;
|
||||||
|
|
||||||
// Update loaded periods.
|
// Update loaded periods.
|
||||||
bufferAheadPeriodCount = 0;
|
bufferAheadPeriodCount = 0;
|
||||||
if (newPlayingPeriod != null) {
|
if (newPlayingPeriodHolder != null) {
|
||||||
newPlayingPeriod.nextPeriod = null;
|
newPlayingPeriodHolder.next = null;
|
||||||
setPlayingPeriod(newPlayingPeriod);
|
setPlayingPeriodHolder(newPlayingPeriodHolder);
|
||||||
updateTimelineState();
|
updateTimelineState();
|
||||||
readingPeriod = playingPeriod;
|
readingPeriodHolder = playingPeriodHolder;
|
||||||
loadingPeriod = playingPeriod;
|
loadingPeriodHolder = playingPeriodHolder;
|
||||||
if (playingPeriod.hasEnabledTracks) {
|
if (playingPeriodHolder.hasEnabledTracks) {
|
||||||
periodPositionUs = playingPeriod.mediaPeriod.seekToUs(periodPositionUs);
|
periodPositionUs = playingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs);
|
||||||
}
|
}
|
||||||
resetRendererPosition(periodPositionUs);
|
resetRendererPosition(periodPositionUs);
|
||||||
maybeContinueLoading();
|
maybeContinueLoading();
|
||||||
} else {
|
} else {
|
||||||
playingPeriod = null;
|
playingPeriodHolder = null;
|
||||||
readingPeriod = null;
|
readingPeriodHolder = null;
|
||||||
loadingPeriod = null;
|
loadingPeriodHolder = null;
|
||||||
if (periodPositionUs != C.TIME_UNSET) {
|
if (periodPositionUs != C.TIME_UNSET) {
|
||||||
resetRendererPosition(periodPositionUs);
|
resetRendererPosition(periodPositionUs);
|
||||||
}
|
}
|
||||||
|
|
@ -582,7 +587,8 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
|
private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
|
||||||
long periodOffsetUs = playingPeriod == null ? 0 : playingPeriod.rendererPositionOffsetUs;
|
long periodOffsetUs = playingPeriodHolder == null ? 0
|
||||||
|
: playingPeriodHolder.rendererPositionOffsetUs;
|
||||||
rendererPositionUs = periodOffsetUs + periodPositionUs;
|
rendererPositionUs = periodOffsetUs + periodPositionUs;
|
||||||
standaloneMediaClock.setPositionUs(rendererPositionUs);
|
standaloneMediaClock.setPositionUs(rendererPositionUs);
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
|
|
@ -624,12 +630,13 @@ import java.io.IOException;
|
||||||
mediaSource.releaseSource();
|
mediaSource.releaseSource();
|
||||||
mediaSource = null;
|
mediaSource = null;
|
||||||
}
|
}
|
||||||
releasePeriodsFrom(playingPeriod != null ? playingPeriod : loadingPeriod);
|
releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder
|
||||||
|
: loadingPeriodHolder);
|
||||||
isTimelineReady = false;
|
isTimelineReady = false;
|
||||||
isTimelineEnded = false;
|
isTimelineEnded = false;
|
||||||
playingPeriod = null;
|
playingPeriodHolder = null;
|
||||||
readingPeriod = null;
|
readingPeriodHolder = null;
|
||||||
loadingPeriod = null;
|
loadingPeriodHolder = null;
|
||||||
timeline = null;
|
timeline = null;
|
||||||
bufferAheadPeriodCount = 0;
|
bufferAheadPeriodCount = 0;
|
||||||
loadControl.onTracksDisabled();
|
loadControl.onTracksDisabled();
|
||||||
|
|
@ -660,43 +667,43 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
private void reselectTracksInternal() throws ExoPlaybackException {
|
||||||
if (playingPeriod == null) {
|
if (playingPeriodHolder == 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;
|
||||||
}
|
}
|
||||||
// Reselect tracks on each period in turn, until the selection changes.
|
// Reselect tracks on each period in turn, until the selection changes.
|
||||||
Period period = playingPeriod;
|
MediaPeriodHolder periodHolder = playingPeriodHolder;
|
||||||
boolean selectionsChangedForReadPeriod = true;
|
boolean selectionsChangedForReadPeriod = true;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (period == null || !period.prepared) {
|
if (periodHolder == null || !periodHolder.prepared) {
|
||||||
// The reselection did not change any prepared periods.
|
// The reselection did not change any prepared periods.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (period.selectTracks()) {
|
if (periodHolder.selectTracks()) {
|
||||||
// Selected tracks have changed for this period.
|
// Selected tracks have changed for this period.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (period == readingPeriod) {
|
if (periodHolder == readingPeriodHolder) {
|
||||||
// The track reselection didn't affect any period that has been read.
|
// The track reselection didn't affect any period that has been read.
|
||||||
selectionsChangedForReadPeriod = false;
|
selectionsChangedForReadPeriod = false;
|
||||||
}
|
}
|
||||||
period = period.nextPeriod;
|
periodHolder = periodHolder.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
releasePeriodsFrom(playingPeriod.nextPeriod);
|
releasePeriodHoldersFrom(playingPeriodHolder.next);
|
||||||
playingPeriod.nextPeriod = null;
|
playingPeriodHolder.next = null;
|
||||||
readingPeriod = playingPeriod;
|
readingPeriodHolder = playingPeriodHolder;
|
||||||
loadingPeriod = playingPeriod;
|
loadingPeriodHolder = playingPeriodHolder;
|
||||||
bufferAheadPeriodCount = 0;
|
bufferAheadPeriodCount = 0;
|
||||||
|
|
||||||
// Update streams for the new selection, recreating all streams if reading ahead.
|
// Update streams for the new selection, recreating all streams if reading ahead.
|
||||||
boolean recreateStreams = readingPeriod != playingPeriod;
|
boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
|
||||||
boolean[] streamResetFlags = new boolean[renderers.length];
|
boolean[] streamResetFlags = new boolean[renderers.length];
|
||||||
long periodPositionUs = playingPeriod.updatePeriodTrackSelection(playbackInfo.positionUs,
|
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
|
||||||
loadControl, recreateStreams, streamResetFlags);
|
playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags);
|
||||||
if (periodPositionUs != playbackInfo.positionUs) {
|
if (periodPositionUs != playbackInfo.positionUs) {
|
||||||
playbackInfo.positionUs = periodPositionUs;
|
playbackInfo.positionUs = periodPositionUs;
|
||||||
resetRendererPosition(periodPositionUs);
|
resetRendererPosition(periodPositionUs);
|
||||||
|
|
@ -707,7 +714,7 @@ import java.io.IOException;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
Renderer renderer = renderers[i];
|
||||||
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
|
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
|
||||||
SampleStream sampleStream = playingPeriod.sampleStreams[i];
|
SampleStream sampleStream = playingPeriodHolder.sampleStreams[i];
|
||||||
if (sampleStream != null) {
|
if (sampleStream != null) {
|
||||||
enabledRendererCount++;
|
enabledRendererCount++;
|
||||||
}
|
}
|
||||||
|
|
@ -732,21 +739,21 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackSelector.onSelectionActivated(playingPeriod.trackSelectionData);
|
trackSelector.onSelectionActivated(playingPeriodHolder.trackSelectionData);
|
||||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||||
} else {
|
} else {
|
||||||
// Release and re-prepare/buffer periods after the one whose selection changed.
|
// Release and re-prepare/buffer periods after the one whose selection changed.
|
||||||
loadingPeriod = period;
|
loadingPeriodHolder = periodHolder;
|
||||||
period = loadingPeriod.nextPeriod;
|
periodHolder = loadingPeriodHolder.next;
|
||||||
while (period != null) {
|
while (periodHolder != null) {
|
||||||
period.release();
|
periodHolder.release();
|
||||||
period = period.nextPeriod;
|
periodHolder = periodHolder.next;
|
||||||
bufferAheadPeriodCount--;
|
bufferAheadPeriodCount--;
|
||||||
}
|
}
|
||||||
loadingPeriod.nextPeriod = null;
|
loadingPeriodHolder.next = null;
|
||||||
long loadingPeriodPositionUs = Math.max(0,
|
long loadingPeriodPositionUs = Math.max(0,
|
||||||
rendererPositionUs - loadingPeriod.rendererPositionOffsetUs);
|
rendererPositionUs - loadingPeriodHolder.rendererPositionOffsetUs);
|
||||||
loadingPeriod.updatePeriodTrackSelection(loadingPeriodPositionUs, loadControl, false);
|
loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, loadControl, false);
|
||||||
}
|
}
|
||||||
maybeContinueLoading();
|
maybeContinueLoading();
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
|
|
@ -754,67 +761,72 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean haveSufficientBuffer(boolean rebuffering) {
|
private boolean haveSufficientBuffer(boolean rebuffering) {
|
||||||
if (loadingPeriod == null) {
|
if (loadingPeriodHolder == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
long loadingPeriodPositionUs = rendererPositionUs - loadingPeriod.rendererPositionOffsetUs;
|
long loadingPeriodPositionUs = rendererPositionUs
|
||||||
|
- loadingPeriodHolder.rendererPositionOffsetUs;
|
||||||
long loadingPeriodBufferedPositionUs =
|
long loadingPeriodBufferedPositionUs =
|
||||||
!loadingPeriod.prepared ? 0 : loadingPeriod.mediaPeriod.getBufferedPositionUs();
|
!loadingPeriodHolder.prepared ? 0 : loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
|
||||||
if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) {
|
if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) {
|
||||||
if (loadingPeriod.isLast) {
|
if (loadingPeriodHolder.isLast) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
loadingPeriodBufferedPositionUs = timeline.getPeriodDurationUs(loadingPeriod.index);
|
loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.index, period)
|
||||||
|
.getDurationUs();
|
||||||
}
|
}
|
||||||
return loadControl.shouldStartPlayback(
|
return loadControl.shouldStartPlayback(
|
||||||
loadingPeriodBufferedPositionUs - loadingPeriodPositionUs, rebuffering);
|
loadingPeriodBufferedPositionUs - loadingPeriodPositionUs, rebuffering);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeThrowPeriodPrepareError() throws IOException {
|
private void maybeThrowPeriodPrepareError() throws IOException {
|
||||||
if (loadingPeriod != null && !loadingPeriod.prepared
|
if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared
|
||||||
&& (readingPeriod == null || readingPeriod.nextPeriod == loadingPeriod)) {
|
&& (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) {
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
if (!renderer.hasReadStreamToEnd()) {
|
if (!renderer.hasReadStreamToEnd()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadingPeriod.mediaPeriod.maybeThrowPrepareError();
|
loadingPeriodHolder.mediaPeriod.maybeThrowPrepareError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSourceInfoRefreshed(Pair<MediaTimeline, Object> timelineAndManifest)
|
private void handleSourceInfoRefreshed(Pair<Timeline, Object> timelineAndManifest)
|
||||||
throws ExoPlaybackException, IOException {
|
throws ExoPlaybackException, IOException {
|
||||||
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, timelineAndManifest).sendToTarget();
|
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, timelineAndManifest).sendToTarget();
|
||||||
MediaTimeline oldTimeline = this.timeline;
|
Timeline oldTimeline = this.timeline;
|
||||||
this.timeline = timelineAndManifest.first;
|
this.timeline = timelineAndManifest.first;
|
||||||
|
|
||||||
// Update the loaded periods to take into account the new timeline.
|
// Update the loaded periods to take into account the new timeline.
|
||||||
if (playingPeriod != null) {
|
if (playingPeriodHolder != null) {
|
||||||
int index = timeline.getIndexOfPeriod(playingPeriod.id);
|
int index = timeline.getIndexOfPeriod(playingPeriodHolder.uid);
|
||||||
if (index == C.INDEX_UNSET) {
|
if (index == C.INDEX_UNSET) {
|
||||||
attemptRestart(timeline, oldTimeline, playingPeriod.index);
|
attemptRestart(timeline, oldTimeline, playingPeriodHolder.index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The playing period is also in the new timeline. Update the index for each loaded period
|
// The playing period is also in the new timeline. Update the index for each loaded period
|
||||||
// until a period is found that does not match the old timeline.
|
// until a period is found that does not match the old timeline.
|
||||||
playingPeriod.setIndex(timeline, index);
|
timeline.getPeriod(index, period, true);
|
||||||
|
playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window),
|
||||||
|
index);
|
||||||
|
|
||||||
Period previousPeriod = playingPeriod;
|
MediaPeriodHolder previousPeriod = playingPeriodHolder;
|
||||||
boolean seenReadingPeriod = false;
|
boolean seenReadingPeriod = false;
|
||||||
bufferAheadPeriodCount = 0;
|
bufferAheadPeriodCount = 0;
|
||||||
while (previousPeriod.nextPeriod != null) {
|
while (previousPeriod.next != null) {
|
||||||
Period period = previousPeriod.nextPeriod;
|
MediaPeriodHolder periodHolder = previousPeriod.next;
|
||||||
index++;
|
index++;
|
||||||
if (!period.id.equals(timeline.getPeriodId(index))) {
|
timeline.getPeriod(index, period, true);
|
||||||
|
if (!periodHolder.uid.equals(period.uid)) {
|
||||||
if (!seenReadingPeriod) {
|
if (!seenReadingPeriod) {
|
||||||
// Renderers may have read a period that has been removed, so release all loaded periods
|
// Renderers may have read a period that has been removed, so release all loaded periods
|
||||||
// and seek to the current position of the playing period index.
|
// and seek to the current position of the playing period index.
|
||||||
index = playingPeriod.index;
|
index = playingPeriodHolder.index;
|
||||||
releasePeriodsFrom(playingPeriod);
|
releasePeriodHoldersFrom(playingPeriodHolder);
|
||||||
playingPeriod = null;
|
playingPeriodHolder = null;
|
||||||
readingPeriod = null;
|
readingPeriodHolder = null;
|
||||||
loadingPeriod = null;
|
loadingPeriodHolder = null;
|
||||||
long newPositionUs = seekToPeriodPosition(index, playbackInfo.positionUs);
|
long newPositionUs = seekToPeriodPosition(index, playbackInfo.positionUs);
|
||||||
if (newPositionUs != playbackInfo.positionUs) {
|
if (newPositionUs != playbackInfo.positionUs) {
|
||||||
playbackInfo = new PlaybackInfo(index, newPositionUs);
|
playbackInfo = new PlaybackInfo(index, newPositionUs);
|
||||||
|
|
@ -824,36 +836,39 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the loading period to be the latest period that is still valid.
|
// Update the loading period to be the latest period that is still valid.
|
||||||
loadingPeriod = previousPeriod;
|
loadingPeriodHolder = previousPeriod;
|
||||||
loadingPeriod.nextPeriod = null;
|
loadingPeriodHolder.next = null;
|
||||||
|
|
||||||
// Release the rest of the timeline.
|
// Release the rest of the timeline.
|
||||||
releasePeriodsFrom(period);
|
releasePeriodHoldersFrom(periodHolder);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferAheadPeriodCount++;
|
bufferAheadPeriodCount++;
|
||||||
period.setIndex(timeline, index);
|
int windowIndex = timeline.getPeriod(index, period).windowIndex;
|
||||||
if (period == readingPeriod) {
|
periodHolder.setIndex(timeline, timeline.getWindow(windowIndex, window), index);
|
||||||
|
if (periodHolder == readingPeriodHolder) {
|
||||||
seenReadingPeriod = true;
|
seenReadingPeriod = true;
|
||||||
}
|
}
|
||||||
previousPeriod = period;
|
previousPeriod = periodHolder;
|
||||||
}
|
}
|
||||||
} else if (loadingPeriod != null) {
|
} else if (loadingPeriodHolder != null) {
|
||||||
Object id = loadingPeriod.id;
|
Object uid = loadingPeriodHolder.uid;
|
||||||
int index = timeline.getIndexOfPeriod(id);
|
int index = timeline.getIndexOfPeriod(uid);
|
||||||
if (index == C.INDEX_UNSET) {
|
if (index == C.INDEX_UNSET) {
|
||||||
attemptRestart(timeline, oldTimeline, loadingPeriod.index);
|
attemptRestart(timeline, oldTimeline, loadingPeriodHolder.index);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
loadingPeriod.setIndex(timeline, index);
|
int windowIndex = timeline.getPeriod(index, this.period).windowIndex;
|
||||||
|
loadingPeriodHolder.setIndex(timeline, timeline.getWindow(windowIndex, window),
|
||||||
|
index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
|
// TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
|
||||||
if (oldTimeline != null) {
|
if (oldTimeline != null) {
|
||||||
int newPlayingIndex = playingPeriod != null ? playingPeriod.index
|
int newPlayingIndex = playingPeriodHolder != null ? playingPeriodHolder.index
|
||||||
: loadingPeriod != null ? loadingPeriod.index : C.INDEX_UNSET;
|
: loadingPeriodHolder != null ? loadingPeriodHolder.index : C.INDEX_UNSET;
|
||||||
if (newPlayingIndex != C.INDEX_UNSET
|
if (newPlayingIndex != C.INDEX_UNSET
|
||||||
&& newPlayingIndex != playbackInfo.periodIndex) {
|
&& newPlayingIndex != playbackInfo.periodIndex) {
|
||||||
playbackInfo = new PlaybackInfo(newPlayingIndex, playbackInfo.positionUs);
|
playbackInfo = new PlaybackInfo(newPlayingIndex, playbackInfo.positionUs);
|
||||||
|
|
@ -863,12 +878,13 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attemptRestart(MediaTimeline newTimeline, MediaTimeline oldTimeline,
|
private void attemptRestart(Timeline newTimeline, Timeline oldTimeline,
|
||||||
int oldPeriodIndex) throws ExoPlaybackException {
|
int oldPeriodIndex) throws ExoPlaybackException {
|
||||||
int newPeriodIndex = C.INDEX_UNSET;
|
int newPeriodIndex = C.INDEX_UNSET;
|
||||||
while (newPeriodIndex == C.INDEX_UNSET
|
while (newPeriodIndex == C.INDEX_UNSET
|
||||||
&& oldPeriodIndex < oldTimeline.getPeriodCount() - 1) {
|
&& oldPeriodIndex < oldTimeline.getPeriodCount() - 1) {
|
||||||
newPeriodIndex = newTimeline.getIndexOfPeriod(oldTimeline.getPeriodId(++oldPeriodIndex));
|
newPeriodIndex =
|
||||||
|
newTimeline.getIndexOfPeriod(oldTimeline.getPeriod(++oldPeriodIndex, period, true).uid);
|
||||||
}
|
}
|
||||||
if (newPeriodIndex == C.INDEX_UNSET) {
|
if (newPeriodIndex == C.INDEX_UNSET) {
|
||||||
// We failed to find a replacement period. Stop the player.
|
// We failed to find a replacement period. Stop the player.
|
||||||
|
|
@ -877,11 +893,11 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release all loaded periods.
|
// Release all loaded periods.
|
||||||
releasePeriodsFrom(playingPeriod);
|
releasePeriodHoldersFrom(playingPeriodHolder);
|
||||||
bufferAheadPeriodCount = 0;
|
bufferAheadPeriodCount = 0;
|
||||||
playingPeriod = null;
|
playingPeriodHolder = null;
|
||||||
readingPeriod = null;
|
readingPeriodHolder = null;
|
||||||
loadingPeriod = null;
|
loadingPeriodHolder = null;
|
||||||
|
|
||||||
// Find the default initial position in the window and seek to it.
|
// Find the default initial position in the window and seek to it.
|
||||||
Pair<Integer, Long> defaultPosition = getDefaultPosition(newPeriodIndex);
|
Pair<Integer, Long> defaultPosition = getDefaultPosition(newPeriodIndex);
|
||||||
|
|
@ -893,14 +909,16 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<Integer, Long> getDefaultPosition(int periodIndex) {
|
private Pair<Integer, Long> getDefaultPosition(int periodIndex) {
|
||||||
int windowIndex = timeline.getPeriodWindowIndex(periodIndex);
|
timeline.getPeriod(periodIndex, period);
|
||||||
periodIndex = timeline.getWindowFirstPeriodIndex(windowIndex);
|
timeline.getWindow(period.windowIndex, window);
|
||||||
int maxPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex);
|
periodIndex = window.firstPeriodIndex;
|
||||||
long periodPositionUs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex)
|
long periodPositionUs = window.getPositionInFirstPeriodUs()
|
||||||
+ timeline.getWindow(windowIndex).defaultStartPositionUs;
|
+ window.getDefaultStartPositionUs();
|
||||||
while (periodIndex < maxPeriodIndex
|
timeline.getPeriod(periodIndex, period);
|
||||||
&& periodPositionUs > timeline.getPeriodDurationUs(periodIndex)) {
|
while (periodIndex < window.lastPeriodIndex
|
||||||
periodPositionUs -= timeline.getPeriodDurationUs(periodIndex++);
|
&& periodPositionUs > period.getDurationMs()) {
|
||||||
|
periodPositionUs -= period.getDurationUs();
|
||||||
|
timeline.getPeriod(periodIndex++, period);
|
||||||
}
|
}
|
||||||
return Pair.create(periodIndex, periodPositionUs);
|
return Pair.create(periodIndex, periodPositionUs);
|
||||||
}
|
}
|
||||||
|
|
@ -912,18 +930,21 @@ import java.io.IOException;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast
|
if (loadingPeriodHolder == null
|
||||||
|
|| (loadingPeriodHolder.isFullyBuffered() && !loadingPeriodHolder.isLast
|
||||||
&& bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) {
|
&& bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) {
|
||||||
// We don't have a loading period or it's fully loaded, so try and create the next one.
|
// We don't have a loading period or it's fully loaded, so try and create the next one.
|
||||||
int newLoadingPeriodIndex = loadingPeriod == null ? playbackInfo.periodIndex
|
int newLoadingPeriodIndex = loadingPeriodHolder == null ? playbackInfo.periodIndex
|
||||||
: loadingPeriod.index + 1;
|
: loadingPeriodHolder.index + 1;
|
||||||
if (newLoadingPeriodIndex >= timeline.getPeriodCount()) {
|
if (newLoadingPeriodIndex >= timeline.getPeriodCount()) {
|
||||||
// The period is not available yet.
|
// The period is not available yet.
|
||||||
mediaSource.maybeThrowSourceInfoRefreshError();
|
mediaSource.maybeThrowSourceInfoRefreshError();
|
||||||
} else {
|
} else {
|
||||||
long periodStartPositionUs = loadingPeriod == null ? playbackInfo.positionUs
|
int windowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
|
||||||
: (newLoadingPeriodIndex == timeline.getWindowFirstPeriodIndex(newLoadingPeriodIndex)
|
boolean isFirstPeriodInWindow = newLoadingPeriodIndex
|
||||||
? C.TIME_UNSET : 0);
|
== timeline.getWindow(windowIndex, window).firstPeriodIndex;
|
||||||
|
long periodStartPositionUs = loadingPeriodHolder == null ? playbackInfo.positionUs
|
||||||
|
: (isFirstPeriodInWindow ? C.TIME_UNSET : 0);
|
||||||
if (periodStartPositionUs == C.TIME_UNSET) {
|
if (periodStartPositionUs == C.TIME_UNSET) {
|
||||||
// This is the first period of a new window or we don't have a start position, so seek to
|
// This is the first period of a new window or we don't have a start position, so seek to
|
||||||
// the default position for the window.
|
// the default position for the window.
|
||||||
|
|
@ -931,47 +952,50 @@ import java.io.IOException;
|
||||||
newLoadingPeriodIndex = defaultPosition.first;
|
newLoadingPeriodIndex = defaultPosition.first;
|
||||||
periodStartPositionUs = defaultPosition.second;
|
periodStartPositionUs = defaultPosition.second;
|
||||||
}
|
}
|
||||||
MediaPeriod mediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this,
|
Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid;
|
||||||
|
MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this,
|
||||||
loadControl.getAllocator(), periodStartPositionUs);
|
loadControl.getAllocator(), periodStartPositionUs);
|
||||||
Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaSource,
|
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
|
||||||
mediaPeriod, timeline.getPeriodId(newLoadingPeriodIndex), periodStartPositionUs);
|
trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs);
|
||||||
newPeriod.setIndex(timeline, newLoadingPeriodIndex);
|
timeline.getWindow(windowIndex, window);
|
||||||
if (loadingPeriod != null) {
|
newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex);
|
||||||
loadingPeriod.setNextPeriod(newPeriod);
|
if (loadingPeriodHolder != null) {
|
||||||
newPeriod.rendererPositionOffsetUs = loadingPeriod.rendererPositionOffsetUs
|
loadingPeriodHolder.setNext(newPeriodHolder);
|
||||||
+ timeline.getPeriodDurationUs(loadingPeriod.index);
|
newPeriodHolder.rendererPositionOffsetUs = loadingPeriodHolder.rendererPositionOffsetUs
|
||||||
|
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs();
|
||||||
}
|
}
|
||||||
bufferAheadPeriodCount++;
|
bufferAheadPeriodCount++;
|
||||||
loadingPeriod = newPeriod;
|
loadingPeriodHolder = newPeriodHolder;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadingPeriod == null || loadingPeriod.isFullyBuffered()) {
|
if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else if (loadingPeriod != null && loadingPeriod.needsContinueLoading) {
|
} else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) {
|
||||||
maybeContinueLoading();
|
maybeContinueLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playingPeriod == null) {
|
if (playingPeriodHolder == null) {
|
||||||
// We're waiting for the first period to be prepared.
|
// We're waiting for the first period to be prepared.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the playing and reading periods.
|
// Update the playing and reading periods.
|
||||||
while (playingPeriod != readingPeriod && playingPeriod.nextPeriod != null
|
while (playingPeriodHolder != readingPeriodHolder && playingPeriodHolder.next != null
|
||||||
&& rendererPositionUs >= playingPeriod.nextPeriod.rendererPositionOffsetUs) {
|
&& rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) {
|
||||||
// All enabled renderers' streams have been read to the end, and the playback position reached
|
// All enabled renderers' streams have been read to the end, and the playback position reached
|
||||||
// the end of the playing period, so advance playback to the next period.
|
// the end of the playing period, so advance playback to the next period.
|
||||||
playingPeriod.release();
|
playingPeriodHolder.release();
|
||||||
setPlayingPeriod(playingPeriod.nextPeriod);
|
setPlayingPeriodHolder(playingPeriodHolder.next);
|
||||||
bufferAheadPeriodCount--;
|
bufferAheadPeriodCount--;
|
||||||
playbackInfo = new PlaybackInfo(playingPeriod.index, playingPeriod.startPositionUs);
|
playbackInfo = new PlaybackInfo(playingPeriodHolder.index,
|
||||||
|
playingPeriodHolder.startPositionUs);
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||||
}
|
}
|
||||||
updateTimelineState();
|
updateTimelineState();
|
||||||
if (readingPeriod == null) {
|
if (readingPeriodHolder == null) {
|
||||||
// The renderers have their final SampleStreams.
|
// The renderers have their final SampleStreams.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -980,10 +1004,10 @@ import java.io.IOException;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (readingPeriod.nextPeriod != null && readingPeriod.nextPeriod.prepared) {
|
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
|
||||||
TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections;
|
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections;
|
||||||
readingPeriod = readingPeriod.nextPeriod;
|
readingPeriodHolder = readingPeriodHolder.next;
|
||||||
TrackSelectionArray newTrackSelections = readingPeriod.trackSelections;
|
TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
Renderer renderer = renderers[i];
|
||||||
TrackSelection oldSelection = oldTrackSelections.get(i);
|
TrackSelection oldSelection = oldTrackSelections.get(i);
|
||||||
|
|
@ -996,8 +1020,8 @@ import java.io.IOException;
|
||||||
for (int j = 0; j < formats.length; j++) {
|
for (int j = 0; j < formats.length; j++) {
|
||||||
formats[j] = newSelection.getFormat(j);
|
formats[j] = newSelection.getFormat(j);
|
||||||
}
|
}
|
||||||
renderer.replaceStream(formats, readingPeriod.sampleStreams[i],
|
renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i],
|
||||||
readingPeriod.rendererPositionOffsetUs);
|
readingPeriodHolder.rendererPositionOffsetUs);
|
||||||
} else {
|
} else {
|
||||||
// The renderer will be disabled when transitioning to playing the next period. Mark the
|
// The renderer will be disabled when transitioning to playing the next period. Mark the
|
||||||
// SampleStream as final to play out any remaining data.
|
// SampleStream as final to play out any remaining data.
|
||||||
|
|
@ -1005,8 +1029,8 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (readingPeriod.isLast) {
|
} else if (readingPeriodHolder.isLast) {
|
||||||
readingPeriod = null;
|
readingPeriodHolder = null;
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
renderer.setCurrentStreamIsFinal();
|
renderer.setCurrentStreamIsFinal();
|
||||||
}
|
}
|
||||||
|
|
@ -1014,18 +1038,19 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException {
|
private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException {
|
||||||
if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) {
|
if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
|
||||||
// Stale event.
|
// Stale event.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadingPeriod.handlePrepared(loadingPeriod.startPositionUs, loadControl);
|
loadingPeriodHolder.handlePrepared(loadingPeriodHolder.startPositionUs, loadControl);
|
||||||
if (playingPeriod == null) {
|
if (playingPeriodHolder == null) {
|
||||||
// This is the first prepared period, so start playing it.
|
// This is the first prepared period, so start playing it.
|
||||||
readingPeriod = loadingPeriod;
|
readingPeriodHolder = loadingPeriodHolder;
|
||||||
setPlayingPeriod(readingPeriod);
|
setPlayingPeriodHolder(readingPeriodHolder);
|
||||||
if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||||
// Update the playback info when seeking to a default position.
|
// Update the playback info when seeking to a default position.
|
||||||
playbackInfo = new PlaybackInfo(playingPeriod.index, playingPeriod.startPositionUs);
|
playbackInfo = new PlaybackInfo(playingPeriodHolder.index,
|
||||||
|
playingPeriodHolder.startPositionUs);
|
||||||
resetRendererPosition(playbackInfo.startPositionUs);
|
resetRendererPosition(playbackInfo.startPositionUs);
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||||
|
|
@ -1036,45 +1061,45 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleContinueLoadingRequested(MediaPeriod period) {
|
private void handleContinueLoadingRequested(MediaPeriod period) {
|
||||||
if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) {
|
if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
maybeContinueLoading();
|
maybeContinueLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeContinueLoading() {
|
private void maybeContinueLoading() {
|
||||||
long nextLoadPositionUs = loadingPeriod.mediaPeriod.getNextLoadPositionUs();
|
long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
|
||||||
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
|
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
|
||||||
long loadingPeriodPositionUs = rendererPositionUs - loadingPeriod.rendererPositionOffsetUs
|
long loadingPeriodPositionUs = rendererPositionUs
|
||||||
+ loadingPeriod.startPositionUs;
|
- loadingPeriodHolder.rendererPositionOffsetUs + loadingPeriodHolder.startPositionUs;
|
||||||
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
|
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
|
||||||
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
|
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
|
||||||
setIsLoading(continueLoading);
|
setIsLoading(continueLoading);
|
||||||
if (continueLoading) {
|
if (continueLoading) {
|
||||||
loadingPeriod.needsContinueLoading = false;
|
loadingPeriodHolder.needsContinueLoading = false;
|
||||||
loadingPeriod.mediaPeriod.continueLoading(loadingPeriodPositionUs);
|
loadingPeriodHolder.mediaPeriod.continueLoading(loadingPeriodPositionUs);
|
||||||
} else {
|
} else {
|
||||||
loadingPeriod.needsContinueLoading = true;
|
loadingPeriodHolder.needsContinueLoading = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releasePeriodsFrom(Period period) {
|
private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) {
|
||||||
while (period != null) {
|
while (periodHolder != null) {
|
||||||
period.release();
|
periodHolder.release();
|
||||||
period = period.nextPeriod;
|
periodHolder = periodHolder.next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPlayingPeriod(Period period) throws ExoPlaybackException {
|
private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException {
|
||||||
int enabledRendererCount = 0;
|
int enabledRendererCount = 0;
|
||||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
Renderer renderer = renderers[i];
|
||||||
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
|
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
|
||||||
TrackSelection newSelection = period.trackSelections.get(i);
|
TrackSelection newSelection = periodHolder.trackSelections.get(i);
|
||||||
if (newSelection != null) {
|
if (newSelection != null) {
|
||||||
// The renderer should be enabled when playing the new period.
|
// The renderer should be enabled when playing the new period.
|
||||||
enabledRendererCount++;
|
enabledRendererCount++;
|
||||||
|
|
@ -1091,17 +1116,18 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackSelector.onSelectionActivated(period.trackSelectionData);
|
trackSelector.onSelectionActivated(periodHolder.trackSelectionData);
|
||||||
playingPeriod = period;
|
playingPeriodHolder = periodHolder;
|
||||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTimelineState() {
|
private void updateTimelineState() {
|
||||||
long playingPeriodDurationUs = timeline.getPeriodDurationUs(playingPeriod.index);
|
long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period)
|
||||||
|
.getDurationUs();
|
||||||
isTimelineReady = playingPeriodDurationUs == C.TIME_UNSET
|
isTimelineReady = playingPeriodDurationUs == C.TIME_UNSET
|
||||||
|| playbackInfo.positionUs < playingPeriodDurationUs
|
|| playbackInfo.positionUs < playingPeriodDurationUs
|
||||||
|| (playingPeriod.nextPeriod != null && playingPeriod.nextPeriod.prepared);
|
|| (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared);
|
||||||
isTimelineEnded = playingPeriod.isLast;
|
isTimelineEnded = playingPeriodHolder.isLast;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount)
|
private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount)
|
||||||
|
|
@ -1110,7 +1136,7 @@ import java.io.IOException;
|
||||||
enabledRendererCount = 0;
|
enabledRendererCount = 0;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
Renderer renderer = renderers[i];
|
||||||
TrackSelection newSelection = playingPeriod.trackSelections.get(i);
|
TrackSelection newSelection = playingPeriodHolder.trackSelections.get(i);
|
||||||
if (newSelection != null) {
|
if (newSelection != null) {
|
||||||
enabledRenderers[enabledRendererCount++] = renderer;
|
enabledRenderers[enabledRendererCount++] = renderer;
|
||||||
if (renderer.getState() == Renderer.STATE_DISABLED) {
|
if (renderer.getState() == Renderer.STATE_DISABLED) {
|
||||||
|
|
@ -1124,8 +1150,8 @@ import java.io.IOException;
|
||||||
formats[j] = newSelection.getFormat(j);
|
formats[j] = newSelection.getFormat(j);
|
||||||
}
|
}
|
||||||
// Enable the renderer.
|
// Enable the renderer.
|
||||||
renderer.enable(formats, playingPeriod.sampleStreams[i], rendererPositionUs, joining,
|
renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs,
|
||||||
playingPeriod.rendererPositionOffsetUs);
|
joining, playingPeriodHolder.rendererPositionOffsetUs);
|
||||||
MediaClock mediaClock = renderer.getMediaClock();
|
MediaClock mediaClock = renderer.getMediaClock();
|
||||||
if (mediaClock != null) {
|
if (mediaClock != null) {
|
||||||
if (rendererMediaClock != null) {
|
if (rendererMediaClock != null) {
|
||||||
|
|
@ -1145,12 +1171,12 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a {@link MediaPeriod} with information required to play it as part of a timeline.
|
* Holds a {@link MediaPeriod} with information required to play it as part of a timeline.
|
||||||
*/
|
*/
|
||||||
private static final class Period {
|
private static final class MediaPeriodHolder {
|
||||||
|
|
||||||
public final MediaPeriod mediaPeriod;
|
public final MediaPeriod mediaPeriod;
|
||||||
public final Object id;
|
public final Object uid;
|
||||||
|
|
||||||
public final SampleStream[] sampleStreams;
|
public final SampleStream[] sampleStreams;
|
||||||
public final boolean[] mayRetainStreamFlags;
|
public final boolean[] mayRetainStreamFlags;
|
||||||
|
|
@ -1161,7 +1187,7 @@ import java.io.IOException;
|
||||||
public boolean prepared;
|
public boolean prepared;
|
||||||
public boolean hasEnabledTracks;
|
public boolean hasEnabledTracks;
|
||||||
public long rendererPositionOffsetUs;
|
public long rendererPositionOffsetUs;
|
||||||
public Period nextPeriod;
|
public MediaPeriodHolder next;
|
||||||
public boolean needsContinueLoading;
|
public boolean needsContinueLoading;
|
||||||
|
|
||||||
private final Renderer[] renderers;
|
private final Renderer[] renderers;
|
||||||
|
|
@ -1173,27 +1199,27 @@ import java.io.IOException;
|
||||||
private TrackSelectionArray trackSelections;
|
private TrackSelectionArray trackSelections;
|
||||||
private TrackSelectionArray periodTrackSelections;
|
private TrackSelectionArray periodTrackSelections;
|
||||||
|
|
||||||
public Period(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
||||||
TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object id,
|
TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid,
|
||||||
long positionUs) {
|
long positionUs) {
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
this.rendererCapabilities = rendererCapabilities;
|
this.rendererCapabilities = rendererCapabilities;
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
this.mediaSource = mediaSource;
|
this.mediaSource = mediaSource;
|
||||||
this.mediaPeriod = mediaPeriod;
|
this.mediaPeriod = mediaPeriod;
|
||||||
this.id = Assertions.checkNotNull(id);
|
this.uid = Assertions.checkNotNull(uid);
|
||||||
sampleStreams = new SampleStream[renderers.length];
|
sampleStreams = new SampleStream[renderers.length];
|
||||||
mayRetainStreamFlags = new boolean[renderers.length];
|
mayRetainStreamFlags = new boolean[renderers.length];
|
||||||
startPositionUs = positionUs;
|
startPositionUs = positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNextPeriod(Period nextPeriod) {
|
public void setNext(MediaPeriodHolder next) {
|
||||||
this.nextPeriod = nextPeriod;
|
this.next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIndex(MediaTimeline timeline, int index) {
|
public void setIndex(Timeline timeline, Timeline.Window window, int periodIndex) {
|
||||||
this.index = index;
|
this.index = periodIndex;
|
||||||
isLast = index == timeline.getPeriodCount() - 1 && !timeline.getPeriodWindow(index).isDynamic;
|
isLast = index == timeline.getPeriodCount() - 1 && !window.isDynamic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFullyBuffered() {
|
public boolean isFullyBuffered() {
|
||||||
|
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The player's timeline consisting of one or more periods. Instances are immutable.
|
|
||||||
*/
|
|
||||||
public interface MediaTimeline {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of periods in the timeline.
|
|
||||||
*/
|
|
||||||
int getPeriodCount();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute start time of the timeline in milliseconds.
|
|
||||||
*/
|
|
||||||
long getAbsoluteStartTime();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the duration of the period at {@code periodIndex} in the timeline, in milliseconds, or
|
|
||||||
* {@link C#TIME_UNSET} if not known.
|
|
||||||
*
|
|
||||||
* @param periodIndex The index of the period.
|
|
||||||
* @return The duration of the period in milliseconds, or {@link C#TIME_UNSET}.
|
|
||||||
*/
|
|
||||||
long getPeriodDurationMs(int periodIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the duration of the period at {@code periodIndex} in the timeline, in microseconds, or
|
|
||||||
* {@link C#TIME_UNSET} if not known.
|
|
||||||
*
|
|
||||||
* @param periodIndex The index of the period.
|
|
||||||
* @return The duration of the period in microseconds, or {@link C#TIME_UNSET}.
|
|
||||||
*/
|
|
||||||
long getPeriodDurationUs(int periodIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a unique identifier for the period at {@code periodIndex}, or {@code null} if the
|
|
||||||
* period at {@code periodIndex} is not known. The identifier is stable across timeline changes.
|
|
||||||
*
|
|
||||||
* @param periodIndex A period index.
|
|
||||||
* @return An identifier for the period, or {@code null} if the period is not known.
|
|
||||||
*/
|
|
||||||
Object getPeriodId(int periodIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link MediaWindow} to which the period with the specified index belongs.
|
|
||||||
*
|
|
||||||
* @param periodIndex The period index.
|
|
||||||
* @return The corresponding window.
|
|
||||||
*/
|
|
||||||
MediaWindow getPeriodWindow(int periodIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the window to which the period with the specified index belongs.
|
|
||||||
*
|
|
||||||
* @param periodIndex The period index.
|
|
||||||
* @return The index of the corresponding window.
|
|
||||||
*/
|
|
||||||
int getPeriodWindowIndex(int periodIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the period identified by {@code id}, or {@link C#INDEX_UNSET} if the
|
|
||||||
* period is not in the timeline.
|
|
||||||
*
|
|
||||||
* @param id An identifier for a period.
|
|
||||||
* @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found.
|
|
||||||
*/
|
|
||||||
int getIndexOfPeriod(Object id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of windows that can be accessed via {@link #getWindow(int)}.
|
|
||||||
*/
|
|
||||||
int getWindowCount();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link MediaWindow} at the specified index.
|
|
||||||
*
|
|
||||||
* @param windowIndex The window index.
|
|
||||||
*/
|
|
||||||
MediaWindow getWindow(int windowIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the first period belonging to the window at the specified index.
|
|
||||||
*
|
|
||||||
* @param windowIndex The window index.
|
|
||||||
* @return The index of the first period in the window.
|
|
||||||
*/
|
|
||||||
int getWindowFirstPeriodIndex(int windowIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the last period belonging to the window at the specified index.
|
|
||||||
*
|
|
||||||
* @param windowIndex The window index.
|
|
||||||
* @return The index of the last period in the window.
|
|
||||||
*/
|
|
||||||
int getWindowLastPeriodIndex(int windowIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the start position of the specified window in the first period belonging to it, in
|
|
||||||
* microseconds.
|
|
||||||
*
|
|
||||||
* @param windowIndex The window index.
|
|
||||||
* @return The start position of the window in the first period belonging to it.
|
|
||||||
*/
|
|
||||||
long getWindowOffsetInFirstPeriodUs(int windowIndex);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A window of available media. Instances are immutable.
|
|
||||||
*/
|
|
||||||
public final class MediaWindow {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new window consisting of a single period with the specified duration. The default
|
|
||||||
* start position is zero.
|
|
||||||
*
|
|
||||||
* @param durationUs The duration of the window, in microseconds.
|
|
||||||
* @param isSeekable Whether seeking is supported within the window.
|
|
||||||
* @param isDynamic Whether this seek window may change when the timeline is updated.
|
|
||||||
*/
|
|
||||||
public static MediaWindow createWindowFromZero(long durationUs, boolean isSeekable,
|
|
||||||
boolean isDynamic) {
|
|
||||||
return new MediaWindow(durationUs, isSeekable, isDynamic, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default position relative to the start of the window at which to start playback, in
|
|
||||||
* microseconds.
|
|
||||||
*/
|
|
||||||
public final long defaultStartPositionUs;
|
|
||||||
/**
|
|
||||||
* The duration of the window in microseconds, or {@link C#TIME_UNSET} if unknown.
|
|
||||||
*/
|
|
||||||
public final long durationUs;
|
|
||||||
/**
|
|
||||||
* Whether it's possible to seek within the window.
|
|
||||||
*/
|
|
||||||
public final boolean isSeekable;
|
|
||||||
/**
|
|
||||||
* Whether this seek window may change when the timeline is updated.
|
|
||||||
*/
|
|
||||||
public final boolean isDynamic;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param durationUs The duration of the window in microseconds, or {@link C#TIME_UNSET} if
|
|
||||||
* unknown.
|
|
||||||
* @param isSeekable Whether seeking is supported within the window.
|
|
||||||
* @param isDynamic Whether this seek window may change when the timeline is updated.
|
|
||||||
* @param defaultStartPositionUs The default position relative to the start of the window at which
|
|
||||||
* to start playback, in microseconds.
|
|
||||||
*/
|
|
||||||
public MediaWindow(long durationUs, boolean isSeekable, boolean isDynamic,
|
|
||||||
long defaultStartPositionUs) {
|
|
||||||
this.durationUs = durationUs;
|
|
||||||
this.isSeekable = isSeekable;
|
|
||||||
this.isDynamic = isDynamic;
|
|
||||||
this.defaultStartPositionUs = defaultStartPositionUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = 17;
|
|
||||||
result = 31 * result + (isSeekable ? 1 : 2);
|
|
||||||
result = 31 * result + (isDynamic ? 1 : 2);
|
|
||||||
result = 31 * result + (int) defaultStartPositionUs;
|
|
||||||
result = 31 * result + (int) durationUs;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
MediaWindow other = (MediaWindow) obj;
|
|
||||||
return other.durationUs == durationUs
|
|
||||||
&& other.isSeekable == isSeekable
|
|
||||||
&& other.isDynamic == isDynamic
|
|
||||||
&& other.defaultStartPositionUs == defaultStartPositionUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "MediaWindow[" + durationUs + ", " + defaultStartPositionUs + ", " + isSeekable + ", "
|
|
||||||
+ isDynamic + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -391,24 +391,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||||
return player.isLoading();
|
return player.isLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public void seekInCurrentPeriod(long positionMs) {
|
|
||||||
player.seekInCurrentPeriod(positionMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public void seekToDefaultPositionForPeriod(int periodIndex) {
|
|
||||||
player.seekToDefaultPositionForPeriod(periodIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public void seekInPeriod(int periodIndex, long positionMs) {
|
|
||||||
player.seekInPeriod(periodIndex, positionMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekToDefaultPosition() {
|
public void seekToDefaultPosition() {
|
||||||
player.seekToDefaultPosition();
|
player.seekToDefaultPosition();
|
||||||
|
|
@ -449,36 +431,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||||
player.blockingSendMessages(messages);
|
player.blockingSendMessages(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public int getCurrentPeriodIndex() {
|
|
||||||
return player.getCurrentPeriodIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public long getCurrentPeriodDuration() {
|
|
||||||
return player.getCurrentPeriodDuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public long getCurrentPositionInPeriod() {
|
|
||||||
return player.getCurrentPositionInPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public long getBufferedPositionInPeriod() {
|
|
||||||
return player.getBufferedPositionInPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public int getBufferedPercentageInPeriod() {
|
|
||||||
return player.getBufferedPercentageInPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentWindowIndex() {
|
public int getCurrentWindowIndex() {
|
||||||
return player.getCurrentWindowIndex();
|
return player.getCurrentWindowIndex();
|
||||||
|
|
@ -505,7 +457,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaTimeline getCurrentTimeline() {
|
public Timeline getCurrentTimeline() {
|
||||||
return player.getCurrentTimeline();
|
return player.getCurrentTimeline();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A media timeline. Instances are immutable.
|
||||||
|
*/
|
||||||
|
public abstract class Timeline {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of windows in the timeline.
|
||||||
|
*/
|
||||||
|
public abstract int getWindowCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates a {@link Window} with data for the window at the specified index. Does not populate
|
||||||
|
* {@link Window#id}.
|
||||||
|
*
|
||||||
|
* @param windowIndex The index of the window.
|
||||||
|
* @param window The {@link Window} to populate. Must not be null.
|
||||||
|
* @return The populated {@link Window}, for convenience.
|
||||||
|
*/
|
||||||
|
public final Window getWindow(int windowIndex, Window window) {
|
||||||
|
return getWindow(windowIndex, window, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates a {@link Window} with data for the window at the specified index.
|
||||||
|
*
|
||||||
|
* @param windowIndex The index of the window.
|
||||||
|
* @param window The {@link Window} to populate. Must not be null.
|
||||||
|
* @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to
|
||||||
|
* null. The caller should pass false for efficiency reasons unless the field is required.
|
||||||
|
* @return The populated {@link Window}, for convenience.
|
||||||
|
*/
|
||||||
|
public abstract Window getWindow(int windowIndex, Window window, boolean setIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of periods in the timeline.
|
||||||
|
*/
|
||||||
|
public abstract int getPeriodCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates a {@link Period} with data for the period at the specified index. Does not populate
|
||||||
|
* {@link Period#id} and {@link Period#uid}.
|
||||||
|
*
|
||||||
|
* @param periodIndex The index of the period.
|
||||||
|
* @param period The {@link Period} to populate. Must not be null.
|
||||||
|
* @return The populated {@link Period}, for convenience.
|
||||||
|
*/
|
||||||
|
public final Period getPeriod(int periodIndex, Period period) {
|
||||||
|
return getPeriod(periodIndex, period, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates a {@link Period} with data for the period at the specified index.
|
||||||
|
*
|
||||||
|
* @param periodIndex The index of the period.
|
||||||
|
* @param period The {@link Period} to populate. Must not be null.
|
||||||
|
* @param setIds Whether {@link Period#id} and {@link Period#uid} should be populated. If false,
|
||||||
|
* the fields will be set to null. The caller should pass false for efficiency reasons unless
|
||||||
|
* the fields are required.
|
||||||
|
* @return The populated {@link Period}, for convenience.
|
||||||
|
*/
|
||||||
|
public abstract Period getPeriod(int periodIndex, Period period, boolean setIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the period identified by its unique {@code id}, or {@link C#INDEX_UNSET}
|
||||||
|
* if the period is not in the timeline.
|
||||||
|
*
|
||||||
|
* @param uid A unique identifier for a period.
|
||||||
|
* @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found.
|
||||||
|
*/
|
||||||
|
public abstract int getIndexOfPeriod(Object uid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds information about a window of available media.
|
||||||
|
*/
|
||||||
|
public static final class Window {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier for the window. Not necessarily unique.
|
||||||
|
*/
|
||||||
|
public Object id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start time of the presentation to which this window belongs in milliseconds since the
|
||||||
|
* epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes only.
|
||||||
|
*/
|
||||||
|
public long presentationStartTimeMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The windows start time in milliseconds since the epoch, or {@link C#TIME_UNSET} if unknown or
|
||||||
|
* not applicable. For informational purposes only.
|
||||||
|
*/
|
||||||
|
public long windowStartTimeMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether it's possible to seek within this window.
|
||||||
|
*/
|
||||||
|
public boolean isSeekable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this window may change when the timeline is updated.
|
||||||
|
*/
|
||||||
|
public boolean isDynamic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the first period that belongs to this window.
|
||||||
|
*/
|
||||||
|
public int firstPeriodIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the last period that belongs to this window.
|
||||||
|
*/
|
||||||
|
public int lastPeriodIndex;
|
||||||
|
|
||||||
|
private long defaultStartPositionUs;
|
||||||
|
private long durationUs;
|
||||||
|
private long positionInFirstPeriodUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data held by this window.
|
||||||
|
*/
|
||||||
|
public Window set(Object id, long presentationStartTimeMs, long windowStartTimeMs,
|
||||||
|
boolean isSeekable, boolean isDynamic, long defaultStartPositionUs, long durationUs,
|
||||||
|
int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs) {
|
||||||
|
this.id = id;
|
||||||
|
this.presentationStartTimeMs = presentationStartTimeMs;
|
||||||
|
this.windowStartTimeMs = windowStartTimeMs;
|
||||||
|
this.isSeekable = isSeekable;
|
||||||
|
this.isDynamic = isDynamic;
|
||||||
|
this.defaultStartPositionUs = defaultStartPositionUs;
|
||||||
|
this.durationUs = durationUs;
|
||||||
|
this.firstPeriodIndex = firstPeriodIndex;
|
||||||
|
this.lastPeriodIndex = lastPeriodIndex;
|
||||||
|
this.positionInFirstPeriodUs = positionInFirstPeriodUs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default position relative to the start of the window at which to begin playback,
|
||||||
|
* in milliseconds.
|
||||||
|
*/
|
||||||
|
public long getDefaultStartPositionMs() {
|
||||||
|
return C.usToMs(defaultStartPositionUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default position relative to the start of the window at which to begin playback,
|
||||||
|
* in microseconds.
|
||||||
|
*/
|
||||||
|
public long getDefaultStartPositionUs() {
|
||||||
|
return defaultStartPositionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration of the window in milliseconds, or {@link C#TIME_UNSET} if unknown.
|
||||||
|
*/
|
||||||
|
public long getDurationMs() {
|
||||||
|
return C.usToMs(durationUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown.
|
||||||
|
*/
|
||||||
|
public long getDurationUs() {
|
||||||
|
return durationUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position of the start of this window relative to the start of the first period
|
||||||
|
* belonging to it, in milliseconds.
|
||||||
|
*/
|
||||||
|
public long getPositionInFirstPeriodMs() {
|
||||||
|
return C.usToMs(positionInFirstPeriodUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position of the start of this window relative to the start of the first period
|
||||||
|
* belonging to it, in microseconds.
|
||||||
|
*/
|
||||||
|
public long getPositionInFirstPeriodUs() {
|
||||||
|
return positionInFirstPeriodUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds information about a media period.
|
||||||
|
*/
|
||||||
|
public static final class Period {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier for the period. Not necessarily unique.
|
||||||
|
*/
|
||||||
|
public Object id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unique identifier for the period.
|
||||||
|
*/
|
||||||
|
public Object uid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the window to which this period belongs.
|
||||||
|
*/
|
||||||
|
public int windowIndex;
|
||||||
|
|
||||||
|
private long durationUs;
|
||||||
|
private long positionInWindowUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data held by this period.
|
||||||
|
*/
|
||||||
|
public Period set(Object id, Object uid, int windowIndex, long durationUs,
|
||||||
|
long positionInWindowUs) {
|
||||||
|
this.id = id;
|
||||||
|
this.uid = uid;
|
||||||
|
this.windowIndex = windowIndex;
|
||||||
|
this.durationUs = durationUs;
|
||||||
|
this.positionInWindowUs = positionInWindowUs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration of the period in milliseconds, or {@link C#TIME_UNSET} if unknown.
|
||||||
|
*/
|
||||||
|
public long getDurationMs() {
|
||||||
|
return C.usToMs(durationUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown.
|
||||||
|
*/
|
||||||
|
public long getDurationUs() {
|
||||||
|
return durationUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position of the start of this period relative to the start of the window to which
|
||||||
|
* it belongs, in milliseconds. May be negative if the start of the period is not within the
|
||||||
|
* window.
|
||||||
|
*/
|
||||||
|
public long getPositionInWindowMs() {
|
||||||
|
return C.usToMs(positionInWindowUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position of the start of this period relative to the start of the window to which
|
||||||
|
* it belongs, in microseconds. May be negative if the start of the period is not within the
|
||||||
|
* window.
|
||||||
|
*/
|
||||||
|
public long getPositionInWindowUs() {
|
||||||
|
return positionInWindowUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,8 +17,7 @@ package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.MediaWindow;
|
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
@ -32,18 +31,18 @@ import java.util.Map;
|
||||||
public final class ConcatenatingMediaSource implements MediaSource {
|
public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
|
|
||||||
private final MediaSource[] mediaSources;
|
private final MediaSource[] mediaSources;
|
||||||
private final MediaTimeline[] timelines;
|
private final Timeline[] timelines;
|
||||||
private final Object[] manifests;
|
private final Object[] manifests;
|
||||||
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
|
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
|
||||||
|
|
||||||
private ConcatenatedMediaTimeline timeline;
|
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 MediaTimeline[mediaSources.length];
|
timelines = new Timeline[mediaSources.length];
|
||||||
manifests = new Object[mediaSources.length];
|
manifests = new Object[mediaSources.length];
|
||||||
sourceIndexByMediaPeriod = new HashMap<>();
|
sourceIndexByMediaPeriod = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
@ -55,16 +54,16 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
mediaSources[i].prepareSource(new Listener() {
|
mediaSources[i].prepareSource(new Listener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline sourceTimeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline sourceTimeline, Object manifest) {
|
||||||
timelines[index] = sourceTimeline;
|
timelines[index] = sourceTimeline;
|
||||||
manifests[index] = manifest;
|
manifests[index] = manifest;
|
||||||
for (MediaTimeline timeline : timelines) {
|
for (Timeline timeline : timelines) {
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
// Don't invoke the listener until all sources have timelines.
|
// Don't invoke the listener until all sources have timelines.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timeline = new ConcatenatedMediaTimeline(timelines.clone());
|
timeline = new ConcatenatedTimeline(timelines.clone());
|
||||||
listener.onSourceInfoRefreshed(timeline, manifests.clone());
|
listener.onSourceInfoRefreshed(timeline, manifests.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,21 +104,21 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MediaTimeline} that is the concatenation of one or more {@link MediaTimeline}s.
|
* A {@link Timeline} that is the concatenation of one or more {@link Timeline}s.
|
||||||
*/
|
*/
|
||||||
private static final class ConcatenatedMediaTimeline implements MediaTimeline {
|
private static final class ConcatenatedTimeline extends Timeline {
|
||||||
|
|
||||||
private final MediaTimeline[] timelines;
|
private final Timeline[] timelines;
|
||||||
private final int[] sourcePeriodOffsets;
|
private final int[] sourcePeriodOffsets;
|
||||||
private final int[] sourceWindowOffsets;
|
private final int[] sourceWindowOffsets;
|
||||||
|
|
||||||
public ConcatenatedMediaTimeline(MediaTimeline[] timelines) {
|
public ConcatenatedTimeline(Timeline[] timelines) {
|
||||||
int[] sourcePeriodOffsets = new int[timelines.length];
|
int[] sourcePeriodOffsets = new int[timelines.length];
|
||||||
int[] sourceWindowOffsets = new int[timelines.length];
|
int[] sourceWindowOffsets = new int[timelines.length];
|
||||||
int periodCount = 0;
|
int periodCount = 0;
|
||||||
int windowCount = 0;
|
int windowCount = 0;
|
||||||
for (int i = 0; i < timelines.length; i++) {
|
for (int i = 0; i < timelines.length; i++) {
|
||||||
MediaTimeline timeline = timelines[i];
|
Timeline timeline = timelines[i];
|
||||||
periodCount += timeline.getPeriodCount();
|
periodCount += timeline.getPeriodCount();
|
||||||
sourcePeriodOffsets[i] = periodCount;
|
sourcePeriodOffsets[i] = periodCount;
|
||||||
windowCount += timeline.getWindowCount();
|
windowCount += timeline.getWindowCount();
|
||||||
|
|
@ -131,8 +130,19 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getAbsoluteStartTime() {
|
public int getWindowCount() {
|
||||||
return timelines[0].getAbsoluteStartTime();
|
return sourceWindowOffsets[sourceWindowOffsets.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Window getWindow(int windowIndex, Window window, boolean setIds) {
|
||||||
|
int sourceIndex = getSourceIndexForWindow(windowIndex);
|
||||||
|
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
|
||||||
|
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
|
||||||
|
timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds);
|
||||||
|
window.firstPeriodIndex += firstPeriodIndexInSource;
|
||||||
|
window.lastPeriodIndex += firstPeriodIndexInSource;
|
||||||
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -141,50 +151,28 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPeriodDurationMs(int periodIndex) {
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
int sourceIndex = getSourceIndexForPeriod(periodIndex);
|
int sourceIndex = getSourceIndexForPeriod(periodIndex);
|
||||||
|
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
|
||||||
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
|
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
|
||||||
return timelines[sourceIndex].getPeriodDurationMs(periodIndex - firstPeriodIndexInSource);
|
timelines[sourceIndex].getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds);
|
||||||
|
period.windowIndex += firstWindowIndexInSource;
|
||||||
|
if (setIds) {
|
||||||
|
period.uid = Pair.create(sourceIndex, period.uid);
|
||||||
|
}
|
||||||
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPeriodDurationUs(int periodIndex) {
|
public int getIndexOfPeriod(Object uid) {
|
||||||
int sourceIndex = getSourceIndexForPeriod(periodIndex);
|
if (!(uid instanceof Pair)) {
|
||||||
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
|
|
||||||
return timelines[sourceIndex].getPeriodDurationUs(periodIndex - firstPeriodIndexInSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getPeriodId(int periodIndex) {
|
|
||||||
int sourceIndex = getSourceIndexForPeriod(periodIndex);
|
|
||||||
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(periodIndex);
|
|
||||||
Object periodId = timelines[sourceIndex].getPeriodId(periodIndex - firstPeriodIndexInSource);
|
|
||||||
return Pair.create(sourceIndex, periodId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaWindow getPeriodWindow(int periodIndex) {
|
|
||||||
return getWindow(getPeriodWindowIndex(periodIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPeriodWindowIndex(int periodIndex) {
|
|
||||||
int sourceIndex = getSourceIndexForPeriod(periodIndex);
|
|
||||||
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(periodIndex);
|
|
||||||
return (sourceIndex > 0 ? sourceWindowOffsets[sourceIndex - 1] : 0)
|
|
||||||
+ timelines[sourceIndex].getPeriodWindowIndex(periodIndex - firstPeriodIndexInSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndexOfPeriod(Object id) {
|
|
||||||
if (!(id instanceof Pair)) {
|
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
Pair sourceIndexAndPeriodId = (Pair) id;
|
Pair<?, ?> sourceIndexAndPeriodId = (Pair<?, ?>) uid;
|
||||||
if (!(sourceIndexAndPeriodId.first instanceof Integer)) {
|
if (!(sourceIndexAndPeriodId.first instanceof Integer)) {
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
int sourceIndex = (int) sourceIndexAndPeriodId.first;
|
int sourceIndex = (Integer) sourceIndexAndPeriodId.first;
|
||||||
Object periodId = sourceIndexAndPeriodId.second;
|
Object periodId = sourceIndexAndPeriodId.second;
|
||||||
if (sourceIndex < 0 || sourceIndex >= timelines.length) {
|
if (sourceIndex < 0 || sourceIndex >= timelines.length) {
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
|
|
@ -194,44 +182,6 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
||||||
: getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource;
|
: getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowCount() {
|
|
||||||
return sourceWindowOffsets[sourceWindowOffsets.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaWindow getWindow(int windowIndex) {
|
|
||||||
int sourceIndex = getSourceIndexForWindow(windowIndex);
|
|
||||||
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
|
|
||||||
return timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowFirstPeriodIndex(int windowIndex) {
|
|
||||||
int sourceIndex = getSourceIndexForWindow(windowIndex);
|
|
||||||
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
|
|
||||||
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
|
|
||||||
return firstPeriodIndexInSource + timelines[sourceIndex].getWindowFirstPeriodIndex(
|
|
||||||
windowIndex - firstWindowIndexInSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowLastPeriodIndex(int windowIndex) {
|
|
||||||
int sourceIndex = getSourceIndexForWindow(windowIndex);
|
|
||||||
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
|
|
||||||
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
|
|
||||||
return firstPeriodIndexInSource + timelines[sourceIndex].getWindowLastPeriodIndex(
|
|
||||||
windowIndex - firstWindowIndexInSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getWindowOffsetInFirstPeriodUs(int windowIndex) {
|
|
||||||
int sourceIndex = getSourceIndexForWindow(windowIndex);
|
|
||||||
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
|
|
||||||
return timelines[sourceIndex].getWindowOffsetInFirstPeriodUs(
|
|
||||||
windowIndex - firstWindowIndexInSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSourceIndexForPeriod(int periodIndex) {
|
private int getSourceIndexForPeriod(int periodIndex) {
|
||||||
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
|
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,7 @@ import java.util.Arrays;
|
||||||
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
|
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
|
||||||
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
|
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
|
||||||
sourceListener.onSourceInfoRefreshed(
|
sourceListener.onSourceInfoRefreshed(
|
||||||
new SinglePeriodMediaTimeline(durationUs, seekMap.isSeekable()), null);
|
new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,7 +382,7 @@ import java.util.Arrays;
|
||||||
tracks = new TrackGroupArray(trackArray);
|
tracks = new TrackGroupArray(trackArray);
|
||||||
prepared = true;
|
prepared = true;
|
||||||
sourceListener.onSourceInfoRefreshed(
|
sourceListener.onSourceInfoRefreshed(
|
||||||
new SinglePeriodMediaTimeline(durationUs, seekMap.isSeekable()), null);
|
new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null);
|
||||||
callback.onPrepared(this);
|
callback.onPrepared(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ package com.google.android.exoplayer2.source;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||||
|
|
@ -92,9 +92,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
|
||||||
private final int minLoadableRetryCount;
|
private final int minLoadableRetryCount;
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
|
private final Timeline.Period period;
|
||||||
|
|
||||||
private MediaSource.Listener sourceListener;
|
private MediaSource.Listener sourceListener;
|
||||||
private MediaTimeline timeline;
|
private Timeline timeline;
|
||||||
|
private boolean timelineHasDuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param uri The {@link Uri} of the media stream.
|
* @param uri The {@link Uri} of the media stream.
|
||||||
|
|
@ -130,12 +132,13 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
|
||||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
|
period = new Timeline.Period();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource(MediaSource.Listener listener) {
|
public void prepareSource(MediaSource.Listener listener) {
|
||||||
sourceListener = listener;
|
sourceListener = listener;
|
||||||
timeline = new SinglePeriodMediaTimeline(C.TIME_UNSET, false);
|
timeline = new SinglePeriodTimeline(C.TIME_UNSET, false);
|
||||||
listener.onSourceInfoRefreshed(timeline, null);
|
listener.onSourceInfoRefreshed(timeline, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,13 +169,15 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
|
||||||
// MediaSource.Listener implementation.
|
// MediaSource.Listener implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) {
|
||||||
if (this.timeline.getPeriodDurationUs(0) != C.TIME_UNSET
|
long newTimelineDurationUs = newTimeline.getPeriod(0, period).getDurationUs();
|
||||||
&& timeline.getPeriodDurationUs(0) == C.TIME_UNSET) {
|
boolean newTimelineHasDuration = newTimelineDurationUs != C.TIME_UNSET;
|
||||||
|
if (timelineHasDuration && !newTimelineHasDuration) {
|
||||||
// Suppress source info changes that would make the duration unknown when it is already known.
|
// Suppress source info changes that would make the duration unknown when it is already known.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.timeline = timeline;
|
timeline = newTimeline;
|
||||||
|
timelineHasDuration = newTimelineHasDuration;
|
||||||
sourceListener.onSourceInfoRefreshed(timeline, null);
|
sourceListener.onSourceInfoRefreshed(timeline, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -36,7 +36,7 @@ public interface MediaSource {
|
||||||
* @param timeline The source's timeline.
|
* @param timeline The source's timeline.
|
||||||
* @param manifest The loaded manifest.
|
* @param manifest The loaded manifest.
|
||||||
*/
|
*/
|
||||||
void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest);
|
void onSourceInfoRefreshed(Timeline timeline, Object manifest);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -31,6 +31,7 @@ public final class MergingMediaSource implements MediaSource {
|
||||||
private static final int PERIOD_COUNT_UNSET = -1;
|
private static final int PERIOD_COUNT_UNSET = -1;
|
||||||
|
|
||||||
private final MediaSource[] mediaSources;
|
private final MediaSource[] mediaSources;
|
||||||
|
private final Timeline.Window window;
|
||||||
|
|
||||||
private int periodCount;
|
private int periodCount;
|
||||||
|
|
||||||
|
|
@ -39,30 +40,26 @@ public final class MergingMediaSource implements MediaSource {
|
||||||
*/
|
*/
|
||||||
public MergingMediaSource(MediaSource... mediaSources) {
|
public MergingMediaSource(MediaSource... mediaSources) {
|
||||||
this.mediaSources = mediaSources;
|
this.mediaSources = mediaSources;
|
||||||
|
window = new Timeline.Window();
|
||||||
periodCount = PERIOD_COUNT_UNSET;
|
periodCount = PERIOD_COUNT_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource(final Listener listener) {
|
public void prepareSource(final Listener listener) {
|
||||||
mediaSources[0].prepareSource(new Listener() {
|
mediaSources[0].prepareSource(new Listener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
checkConsistentTimeline(timeline);
|
checkConsistentTimeline(timeline);
|
||||||
|
|
||||||
// All source timelines must match.
|
// All source timelines must match.
|
||||||
listener.onSourceInfoRefreshed(timeline, manifest);
|
listener.onSourceInfoRefreshed(timeline, manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
for (int i = 1; i < mediaSources.length; i++) {
|
for (int i = 1; i < mediaSources.length; i++) {
|
||||||
mediaSources[i].prepareSource(new Listener() {
|
mediaSources[i].prepareSource(new Listener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
checkConsistentTimeline(timeline);
|
checkConsistentTimeline(timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,10 +99,10 @@ public final class MergingMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkConsistentTimeline(MediaTimeline timeline) {
|
private void checkConsistentTimeline(Timeline timeline) {
|
||||||
int windowCount = timeline.getWindowCount();
|
int windowCount = timeline.getWindowCount();
|
||||||
for (int i = 0; i < windowCount; i++) {
|
for (int i = 0; i < windowCount; i++) {
|
||||||
Assertions.checkArgument(!timeline.getWindow(i).isDynamic);
|
Assertions.checkArgument(!timeline.getWindow(i, window, false).isDynamic);
|
||||||
}
|
}
|
||||||
int periodCount = timeline.getPeriodCount();
|
int periodCount = timeline.getPeriodCount();
|
||||||
if (this.periodCount == PERIOD_COUNT_UNSET) {
|
if (this.periodCount == PERIOD_COUNT_UNSET) {
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.C;
|
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.MediaWindow;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link MediaTimeline} consisting of a single period and static window.
|
|
||||||
*/
|
|
||||||
public final class SinglePeriodMediaTimeline implements MediaTimeline {
|
|
||||||
|
|
||||||
private static final Object ID = new Object();
|
|
||||||
|
|
||||||
private final long offsetInFirstPeriodUs;
|
|
||||||
private final MediaWindow window;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a timeline with one period of known duration and a window extending from zero to its
|
|
||||||
* duration.
|
|
||||||
*
|
|
||||||
* @param durationUs The duration of the period, in microseconds.
|
|
||||||
* @param isSeekable Whether seeking is supported within the period.
|
|
||||||
*/
|
|
||||||
public SinglePeriodMediaTimeline(long durationUs, boolean isSeekable) {
|
|
||||||
this(0, MediaWindow.createWindowFromZero(durationUs, isSeekable, false /* isDynamic */));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a timeline with one period of known duration and a window extending from zero to its
|
|
||||||
* duration.
|
|
||||||
*
|
|
||||||
* @param offsetInFirstPeriodUs The offset of the start of the window in the period.
|
|
||||||
* @param window The available window within the period.
|
|
||||||
*/
|
|
||||||
public SinglePeriodMediaTimeline(long offsetInFirstPeriodUs, MediaWindow window) {
|
|
||||||
this.offsetInFirstPeriodUs = offsetInFirstPeriodUs;
|
|
||||||
this.window = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getAbsoluteStartTime() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPeriodCount() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getPeriodDurationMs(int periodIndex) {
|
|
||||||
return C.usToMs(getPeriodDurationUs(periodIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getPeriodDurationUs(int periodIndex) {
|
|
||||||
Assertions.checkIndex(periodIndex, 0, 1);
|
|
||||||
return window.durationUs == C.TIME_UNSET ? C.TIME_UNSET
|
|
||||||
: (offsetInFirstPeriodUs + window.durationUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getPeriodId(int periodIndex) {
|
|
||||||
Assertions.checkIndex(periodIndex, 0, 1);
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaWindow getPeriodWindow(int periodIndex) {
|
|
||||||
Assertions.checkIndex(periodIndex, 0, 1);
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPeriodWindowIndex(int periodIndex) {
|
|
||||||
Assertions.checkIndex(periodIndex, 0, 1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndexOfPeriod(Object id) {
|
|
||||||
return ID.equals(id) ? 0 : C.INDEX_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowCount() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaWindow getWindow(int windowIndex) {
|
|
||||||
Assertions.checkIndex(windowIndex, 0, 1);
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowFirstPeriodIndex(int windowIndex) {
|
|
||||||
Assertions.checkIndex(windowIndex, 0, 1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWindowLastPeriodIndex(int windowIndex) {
|
|
||||||
Assertions.checkIndex(windowIndex, 0, 1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getWindowOffsetInFirstPeriodUs(int windowIndex) {
|
|
||||||
Assertions.checkIndex(windowIndex, 0, 1);
|
|
||||||
return offsetInFirstPeriodUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* 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.C;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Timeline} consisting of a single period and static window.
|
||||||
|
*/
|
||||||
|
public final class SinglePeriodTimeline extends Timeline {
|
||||||
|
|
||||||
|
private static final Object ID = new Object();
|
||||||
|
|
||||||
|
private final long periodDurationUs;
|
||||||
|
private final long windowDurationUs;
|
||||||
|
private final long windowPositionInPeriodUs;
|
||||||
|
private final long windowDefaultStartPositionUs;
|
||||||
|
private final boolean isSeekable;
|
||||||
|
private final boolean isDynamic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a timeline of one period of known duration, and a static window starting at zero and
|
||||||
|
* extending to that duration.
|
||||||
|
*
|
||||||
|
* @param durationUs The duration of the period, in microseconds.
|
||||||
|
* @param isSeekable Whether seeking is supported within the period.
|
||||||
|
*/
|
||||||
|
public SinglePeriodTimeline(long durationUs, boolean isSeekable) {
|
||||||
|
this(durationUs, durationUs, 0, 0, isSeekable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a timeline with one period of known duration, and a window of known duration starting
|
||||||
|
* at a specified position in the period.
|
||||||
|
*
|
||||||
|
* @param periodDurationUs The duration of the period in microseconds.
|
||||||
|
* @param windowDurationUs The duration of the window in microseconds.
|
||||||
|
* @param windowPositionInPeriodUs The position of the start of the window in the period, in
|
||||||
|
* microseconds.
|
||||||
|
* @param windowDefaultStartPositionUs The default position relative to the start of the window at
|
||||||
|
* which to begin playback, in microseconds.
|
||||||
|
* @param isSeekable Whether seeking is supported within the window.
|
||||||
|
* @param isDynamic Whether the window may change when the timeline is updated.
|
||||||
|
*/
|
||||||
|
public SinglePeriodTimeline(long periodDurationUs, long windowDurationUs,
|
||||||
|
long windowPositionInPeriodUs, long windowDefaultStartPositionUs, boolean isSeekable,
|
||||||
|
boolean isDynamic) {
|
||||||
|
this.periodDurationUs = periodDurationUs;
|
||||||
|
this.windowDurationUs = windowDurationUs;
|
||||||
|
this.windowPositionInPeriodUs = windowPositionInPeriodUs;
|
||||||
|
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
|
||||||
|
this.isSeekable = isSeekable;
|
||||||
|
this.isDynamic = isDynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Window getWindow(int windowIndex, Window window, boolean setIds) {
|
||||||
|
Assertions.checkIndex(windowIndex, 0, 1);
|
||||||
|
Object id = setIds ? ID : null;
|
||||||
|
return window.set(id, C.TIME_UNSET, C.TIME_UNSET, isSeekable, isDynamic,
|
||||||
|
windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeriodCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
|
Assertions.checkIndex(periodIndex, 0, 1);
|
||||||
|
Object id = setIds ? ID : null;
|
||||||
|
return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexOfPeriod(Object uid) {
|
||||||
|
return ID.equals(uid) ? 0 : C.INDEX_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ package com.google.android.exoplayer2.source;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
|
@ -57,7 +57,7 @@ public final class SingleSampleMediaSource implements MediaSource {
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
private final int eventSourceId;
|
private final int eventSourceId;
|
||||||
private final MediaTimeline timeline;
|
private final Timeline timeline;
|
||||||
|
|
||||||
public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format,
|
public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format,
|
||||||
long durationUs) {
|
long durationUs) {
|
||||||
|
|
@ -79,7 +79,7 @@ public final class SingleSampleMediaSource implements MediaSource {
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
this.eventSourceId = eventSourceId;
|
this.eventSourceId = eventSourceId;
|
||||||
timeline = new SinglePeriodMediaTimeline(durationUs, true);
|
timeline = new SinglePeriodTimeline(durationUs, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,8 @@ import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.MediaWindow;
|
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
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;
|
||||||
|
|
@ -71,8 +70,8 @@ public final class DashMediaSource implements MediaSource {
|
||||||
public static final long DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS = 30000;
|
public static final long DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS = 30000;
|
||||||
/**
|
/**
|
||||||
* The interval in milliseconds between invocations of
|
* The interval in milliseconds between invocations of
|
||||||
* {@link MediaSource.Listener#onSourceInfoRefreshed(MediaTimeline, Object)} when the source's
|
* {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} when the source's
|
||||||
* {@link MediaWindow} is changing dynamically (for example, for incomplete live streams).
|
* {@link Timeline} is changing dynamically (for example, for incomplete live streams).
|
||||||
*/
|
*/
|
||||||
private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000;
|
private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000;
|
||||||
|
|
||||||
|
|
@ -99,7 +98,6 @@ public final class DashMediaSource implements MediaSource {
|
||||||
private long manifestLoadEndTimestamp;
|
private long manifestLoadEndTimestamp;
|
||||||
private DashManifest manifest;
|
private DashManifest manifest;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
private MediaWindow window;
|
|
||||||
private long elapsedRealtimeOffsetMs;
|
private long elapsedRealtimeOffsetMs;
|
||||||
|
|
||||||
private int firstPeriodId;
|
private int firstPeriodId;
|
||||||
|
|
@ -360,12 +358,12 @@ public final class DashMediaSource implements MediaSource {
|
||||||
if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) {
|
if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) {
|
||||||
// The manifest describes an incomplete live stream. Update the start/end times to reflect the
|
// The manifest describes an incomplete live stream. Update the start/end times to reflect the
|
||||||
// live stream duration and the manifest's time shift buffer depth.
|
// live stream duration and the manifest's time shift buffer depth.
|
||||||
long liveStreamDurationUs = getNowUnixTimeUs() - manifest.availabilityStartTime * 1000;
|
long liveStreamDurationUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTime);
|
||||||
long liveStreamEndPositionInLastPeriodUs =
|
long liveStreamEndPositionInLastPeriodUs = liveStreamDurationUs
|
||||||
liveStreamDurationUs - manifest.getPeriod(lastPeriodIndex).startMs * 1000;
|
- C.msToUs(manifest.getPeriod(lastPeriodIndex).startMs);
|
||||||
currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs);
|
currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs);
|
||||||
if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
|
if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
|
||||||
long timeShiftBufferDepthUs = manifest.timeShiftBufferDepth * 1000;
|
long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepth);
|
||||||
long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs;
|
long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs;
|
||||||
int periodIndex = lastPeriodIndex;
|
int periodIndex = lastPeriodIndex;
|
||||||
while (offsetInPeriodUs < 0 && periodIndex > 0) {
|
while (offsetInPeriodUs < 0 && periodIndex > 0) {
|
||||||
|
|
@ -386,7 +384,7 @@ public final class DashMediaSource implements MediaSource {
|
||||||
for (int i = 0; i < manifest.getPeriodCount() - 1; i++) {
|
for (int i = 0; i < manifest.getPeriodCount() - 1; i++) {
|
||||||
windowDurationUs += manifest.getPeriodDurationUs(i);
|
windowDurationUs += manifest.getPeriodDurationUs(i);
|
||||||
}
|
}
|
||||||
long defaultInitialTimeUs = 0;
|
long windowDefaultStartPositionUs = 0;
|
||||||
if (manifest.dynamic) {
|
if (manifest.dynamic) {
|
||||||
long liveEdgeOffsetForManifestMs = liveEdgeOffsetMs;
|
long liveEdgeOffsetForManifestMs = liveEdgeOffsetMs;
|
||||||
if (liveEdgeOffsetForManifestMs == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) {
|
if (liveEdgeOffsetForManifestMs == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) {
|
||||||
|
|
@ -395,7 +393,7 @@ public final class DashMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
// Snap the default position to the start of the segment containing it.
|
// Snap the default position to the start of the segment containing it.
|
||||||
long initialTimeOffsetInWindowUs =
|
long initialTimeOffsetInWindowUs =
|
||||||
Math.max(0, windowDurationUs - (liveEdgeOffsetForManifestMs * 1000));
|
Math.max(0, windowDurationUs - C.msToUs(liveEdgeOffsetForManifestMs));
|
||||||
int periodIndex = 0;
|
int periodIndex = 0;
|
||||||
long initialTimeOffsetInPeriodUs = currentStartTimeUs + initialTimeOffsetInWindowUs;
|
long initialTimeOffsetInPeriodUs = currentStartTimeUs + initialTimeOffsetInWindowUs;
|
||||||
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
|
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
|
||||||
|
|
@ -413,16 +411,18 @@ public final class DashMediaSource implements MediaSource {
|
||||||
DashSegmentIndex index =
|
DashSegmentIndex index =
|
||||||
period.adaptationSets.get(videoAdaptationSetIndex).representations.get(0).getIndex();
|
period.adaptationSets.get(videoAdaptationSetIndex).representations.get(0).getIndex();
|
||||||
int segmentNum = index.getSegmentNum(initialTimeOffsetInPeriodUs, periodDurationUs);
|
int segmentNum = index.getSegmentNum(initialTimeOffsetInPeriodUs, periodDurationUs);
|
||||||
defaultInitialTimeUs =
|
windowDefaultStartPositionUs =
|
||||||
initialTimeOffsetInWindowUs - initialTimeOffsetInPeriodUs + index.getTimeUs(segmentNum);
|
initialTimeOffsetInWindowUs - initialTimeOffsetInPeriodUs + index.getTimeUs(segmentNum);
|
||||||
} else {
|
} else {
|
||||||
defaultInitialTimeUs = initialTimeOffsetInWindowUs;
|
windowDefaultStartPositionUs = initialTimeOffsetInWindowUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window = new MediaWindow(windowDurationUs, true /* isSeekable */, manifest.dynamic,
|
long windowStartTimeMs = manifest.availabilityStartTime
|
||||||
defaultInitialTimeUs);
|
+ manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs);
|
||||||
sourceListener.onSourceInfoRefreshed(new DashMediaTimeline(firstPeriodId, currentStartTimeUs,
|
DashTimeline timeline = new DashTimeline(manifest.availabilityStartTime, windowStartTimeMs,
|
||||||
manifest, window), manifest);
|
firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs,
|
||||||
|
manifest);
|
||||||
|
sourceListener.onSourceInfoRefreshed(timeline, manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleManifestRefresh() {
|
private void scheduleManifestRefresh() {
|
||||||
|
|
@ -450,9 +450,9 @@ public final class DashMediaSource implements MediaSource {
|
||||||
|
|
||||||
private long getNowUnixTimeUs() {
|
private long getNowUnixTimeUs() {
|
||||||
if (elapsedRealtimeOffsetMs != 0) {
|
if (elapsedRealtimeOffsetMs != 0) {
|
||||||
return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000;
|
return C.msToUs(SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs);
|
||||||
} else {
|
} else {
|
||||||
return System.currentTimeMillis() * 1000;
|
return C.msToUs(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,7 +462,8 @@ public final class DashMediaSource implements MediaSource {
|
||||||
|
|
||||||
private static final class PeriodSeekInfo {
|
private static final class PeriodSeekInfo {
|
||||||
|
|
||||||
public static PeriodSeekInfo createPeriodSeekInfo(Period period, long durationUs) {
|
public static PeriodSeekInfo createPeriodSeekInfo(
|
||||||
|
com.google.android.exoplayer2.source.dash.manifest.Period period, long durationUs) {
|
||||||
int adaptationSetCount = period.adaptationSets.size();
|
int adaptationSetCount = period.adaptationSets.size();
|
||||||
long availableStartTimeUs = 0;
|
long availableStartTimeUs = 0;
|
||||||
long availableEndTimeUs = Long.MAX_VALUE;
|
long availableEndTimeUs = Long.MAX_VALUE;
|
||||||
|
|
@ -501,24 +502,27 @@ public final class DashMediaSource implements MediaSource {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class DashMediaTimeline implements MediaTimeline {
|
private static final class DashTimeline extends Timeline {
|
||||||
|
|
||||||
|
private final long presentationStartTimeMs;
|
||||||
|
private final long windowStartTimeMs;
|
||||||
|
|
||||||
private final int firstPeriodId;
|
private final int firstPeriodId;
|
||||||
private final long offsetInFirstPeriodUs;
|
private final long offsetInFirstPeriodUs;
|
||||||
|
private final long windowDurationUs;
|
||||||
|
private final long windowDefaultStartPositionUs;
|
||||||
private final DashManifest manifest;
|
private final DashManifest manifest;
|
||||||
private final MediaWindow window;
|
|
||||||
|
|
||||||
public DashMediaTimeline(int firstPeriodId, long offsetInFirstPeriodUs, DashManifest manifest,
|
public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs,
|
||||||
MediaWindow window) {
|
int firstPeriodId, long offsetInFirstPeriodUs, long windowDurationUs,
|
||||||
|
long windowDefaultStartPositionUs, DashManifest manifest) {
|
||||||
|
this.presentationStartTimeMs = presentationStartTimeMs;
|
||||||
|
this.windowStartTimeMs = windowStartTimeMs;
|
||||||
this.firstPeriodId = firstPeriodId;
|
this.firstPeriodId = firstPeriodId;
|
||||||
this.offsetInFirstPeriodUs = offsetInFirstPeriodUs;
|
this.offsetInFirstPeriodUs = offsetInFirstPeriodUs;
|
||||||
|
this.windowDurationUs = windowDurationUs;
|
||||||
|
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.window = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getAbsoluteStartTime() {
|
|
||||||
return manifest.availabilityStartTime + manifest.getPeriod(0).startMs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -527,42 +531,14 @@ public final class DashMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPeriodDurationMs(int periodIndex) {
|
public Period getPeriod(int periodIndex, Period period, boolean setIdentifiers) {
|
||||||
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount());
|
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount());
|
||||||
return manifest.getPeriodDurationMs(periodIndex);
|
Object id = setIdentifiers ? manifest.getPeriod(periodIndex).id : null;
|
||||||
}
|
Object uid = setIdentifiers ? firstPeriodId
|
||||||
|
+ Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()) : null;
|
||||||
@Override
|
return period.set(id, uid, 0, manifest.getPeriodDurationUs(periodIndex),
|
||||||
public long getPeriodDurationUs(int periodIndex) {
|
C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs)
|
||||||
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount());
|
- offsetInFirstPeriodUs);
|
||||||
return manifest.getPeriodDurationUs(periodIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getPeriodId(int periodIndex) {
|
|
||||||
return firstPeriodId + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaWindow getPeriodWindow(int periodIndex) {
|
|
||||||
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount());
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPeriodWindowIndex(int periodIndex) {
|
|
||||||
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndexOfPeriod(Object id) {
|
|
||||||
if (!(id instanceof Integer)) {
|
|
||||||
return C.INDEX_UNSET;
|
|
||||||
}
|
|
||||||
int periodId = (int) id;
|
|
||||||
return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount()
|
|
||||||
? C.INDEX_UNSET : (periodId - firstPeriodId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -571,24 +547,21 @@ public final class DashMediaSource implements MediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaWindow getWindow(int windowIndex) {
|
public Window getWindow(int windowIndex, Window window, boolean setIdentifier) {
|
||||||
Assertions.checkIndex(windowIndex, 0, 1);
|
Assertions.checkIndex(windowIndex, 0, 1);
|
||||||
return window;
|
return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */,
|
||||||
|
manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0,
|
||||||
|
manifest.getPeriodCount() - 1, offsetInFirstPeriodUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWindowFirstPeriodIndex(int windowIndex) {
|
public int getIndexOfPeriod(Object uid) {
|
||||||
return 0;
|
if (!(uid instanceof Integer)) {
|
||||||
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
int periodId = (int) uid;
|
||||||
@Override
|
return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount()
|
||||||
public int getWindowLastPeriodIndex(int windowIndex) {
|
? C.INDEX_UNSET : (periodId - firstPeriodId);
|
||||||
return manifest.getPeriodCount() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getWindowOffsetInFirstPeriodUs(int windowIndex) {
|
|
||||||
return offsetInFirstPeriodUs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.CompositeSequenceableLoader;
|
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.SinglePeriodMediaTimeline;
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
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.hls.playlist.HlsMasterPlaylist;
|
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
|
||||||
|
|
@ -282,7 +282,7 @@ import java.util.List;
|
||||||
callback.onPrepared(this);
|
callback.onPrepared(this);
|
||||||
|
|
||||||
// TODO[playlists]: Calculate the window.
|
// TODO[playlists]: Calculate the window.
|
||||||
MediaTimeline timeline = new SinglePeriodMediaTimeline(durationUs, !isLive);
|
Timeline timeline = new SinglePeriodTimeline(durationUs, !isLive);
|
||||||
sourceListener.onSourceInfoRefreshed(timeline, playlist);
|
sourceListener.onSourceInfoRefreshed(timeline, playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.Eve
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.SinglePeriodMediaTimeline;
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -64,7 +64,7 @@ public final class HlsMediaSource implements MediaSource {
|
||||||
public void prepareSource(MediaSource.Listener listener) {
|
public void prepareSource(MediaSource.Listener listener) {
|
||||||
sourceListener = listener;
|
sourceListener = listener;
|
||||||
// TODO: Defer until the playlist has been loaded.
|
// TODO: Defer until the playlist has been loaded.
|
||||||
listener.onSourceInfoRefreshed(new SinglePeriodMediaTimeline(C.TIME_UNSET, false), null);
|
listener.onSourceInfoRefreshed(new SinglePeriodTimeline(C.TIME_UNSET, false), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,14 @@ import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.MediaWindow;
|
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
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.MediaPeriod.Callback;
|
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.SinglePeriodMediaTimeline;
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
|
||||||
|
|
@ -157,7 +156,7 @@ public final class SsMediaSource implements MediaSource,
|
||||||
for (int i = 0; i < mediaPeriods.size(); i++) {
|
for (int i = 0; i < mediaPeriods.size(); i++) {
|
||||||
mediaPeriods.get(i).updateManifest(manifest);
|
mediaPeriods.get(i).updateManifest(manifest);
|
||||||
}
|
}
|
||||||
MediaTimeline timeline;
|
Timeline timeline;
|
||||||
if (manifest.isLive) {
|
if (manifest.isLive) {
|
||||||
long startTimeUs = Long.MAX_VALUE;
|
long startTimeUs = Long.MAX_VALUE;
|
||||||
long endTimeUs = Long.MIN_VALUE;
|
long endTimeUs = Long.MIN_VALUE;
|
||||||
|
|
@ -170,21 +169,20 @@ public final class SsMediaSource implements MediaSource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (startTimeUs == Long.MAX_VALUE) {
|
if (startTimeUs == Long.MAX_VALUE) {
|
||||||
timeline = new SinglePeriodMediaTimeline(C.TIME_UNSET, false);
|
timeline = new SinglePeriodTimeline(C.TIME_UNSET, false);
|
||||||
} else {
|
} else {
|
||||||
if (manifest.dvrWindowLengthUs != C.TIME_UNSET
|
if (manifest.dvrWindowLengthUs != C.TIME_UNSET
|
||||||
&& manifest.dvrWindowLengthUs > 0) {
|
&& manifest.dvrWindowLengthUs > 0) {
|
||||||
startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs);
|
startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs);
|
||||||
}
|
}
|
||||||
long durationUs = endTimeUs - startTimeUs;
|
long durationUs = endTimeUs - startTimeUs;
|
||||||
long defaultInitialStartPositionUs = Math.max(0, durationUs - (liveEdgeOffsetMs * 1000));
|
long defaultStartPositionUs = Math.max(0, durationUs - (liveEdgeOffsetMs * 1000));
|
||||||
MediaWindow window = new MediaWindow(durationUs, true /* isSeekable */,
|
timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs,
|
||||||
true /* isDynamic */, defaultInitialStartPositionUs);
|
defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */);
|
||||||
timeline = new SinglePeriodMediaTimeline(startTimeUs, window);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
boolean isSeekable = manifest.durationUs != C.TIME_UNSET;
|
boolean isSeekable = manifest.durationUs != C.TIME_UNSET;
|
||||||
timeline = new SinglePeriodMediaTimeline(manifest.durationUs, isSeekable);
|
timeline = new SinglePeriodTimeline(manifest.durationUs, isSeekable);
|
||||||
}
|
}
|
||||||
sourceListener.onSourceInfoRefreshed(timeline, manifest);
|
sourceListener.onSourceInfoRefreshed(timeline, manifest);
|
||||||
scheduleManifestRefresh();
|
scheduleManifestRefresh();
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import android.widget.TextView;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -89,7 +89,7 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,14 @@ package com.google.android.exoplayer2.ui;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link OnClickListener} that can be passed to
|
* An {@link OnClickListener} that can be passed to
|
||||||
* {@link android.widget.MediaController#setPrevNextListeners(OnClickListener, OnClickListener)} to
|
* {@link android.widget.MediaController#setPrevNextListeners(OnClickListener, OnClickListener)} to
|
||||||
* make the controller's previous and next buttons seek to the previous and next windows in the
|
* make the controller's previous and next buttons seek to the previous and next windows in the
|
||||||
* {@link MediaTimeline}.
|
* {@link Timeline}.
|
||||||
*/
|
*/
|
||||||
public class MediaControllerPrevNextClickListener implements OnClickListener {
|
public class MediaControllerPrevNextClickListener implements OnClickListener {
|
||||||
|
|
||||||
|
|
@ -50,15 +49,15 @@ public class MediaControllerPrevNextClickListener implements OnClickListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
int currentWindowIndex = player.getCurrentWindowIndex();
|
Timeline timeline = player.getCurrentTimeline();
|
||||||
if (currentWindowIndex == C.INDEX_UNSET) {
|
if (timeline == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MediaTimeline timeline = player.getCurrentTimeline();
|
int currentWindowIndex = player.getCurrentWindowIndex();
|
||||||
if (isNext) {
|
if (isNext) {
|
||||||
if (currentWindowIndex < timeline.getWindowCount() - 1) {
|
if (currentWindowIndex < timeline.getWindowCount() - 1) {
|
||||||
player.seekToDefaultPosition(currentWindowIndex + 1);
|
player.seekToDefaultPosition(currentWindowIndex + 1);
|
||||||
} else if (timeline.getWindow(currentWindowIndex).isDynamic) {
|
} else if (timeline.getWindow(currentWindowIndex, new Timeline.Window(), false).isDynamic) {
|
||||||
// Seek to the live edge.
|
// Seek to the live edge.
|
||||||
player.seekToDefaultPosition();
|
player.seekToDefaultPosition();
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +66,7 @@ public class MediaControllerPrevNextClickListener implements OnClickListener {
|
||||||
&& player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS) {
|
&& player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS) {
|
||||||
player.seekToDefaultPosition(currentWindowIndex - 1);
|
player.seekToDefaultPosition(currentWindowIndex - 1);
|
||||||
} else {
|
} else {
|
||||||
player.seekTo(currentWindowIndex, 0);
|
player.seekTo(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.MediaTimeline;
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||||
import com.google.android.exoplayer2.audio.AudioTrack;
|
import com.google.android.exoplayer2.audio.AudioTrack;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
|
@ -215,7 +215,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) {
|
public final void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue