mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Expose the seekable window.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=129747377
This commit is contained in:
parent
39482f244b
commit
88bf1d0739
25 changed files with 470 additions and 155 deletions
|
|
@ -91,11 +91,17 @@ import java.util.Locale;
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
boolean isFinal = timeline.isFinal();
|
||||
int periodCount = timeline.getPeriodCount();
|
||||
Log.d(TAG, "timelineChanged [" + isFinal + ", "
|
||||
+ (periodCount == Timeline.UNKNOWN_PERIOD_COUNT ? "?" : periodCount) + "]");
|
||||
int seekWindowCount = timeline.getSeekWindowCount();
|
||||
Log.d(TAG, "sourceInfo[isFinal=" + isFinal + ", periodCount="
|
||||
+ (periodCount == Timeline.UNKNOWN_PERIOD_COUNT ? "?" : periodCount) + ", seekWindows: "
|
||||
+ seekWindowCount);
|
||||
for (int seekWindowIndex = 0; seekWindowIndex < seekWindowCount; seekWindowIndex++) {
|
||||
Log.d(TAG, " " + timeline.getSeekWindow(seekWindowIndex));
|
||||
}
|
||||
Log.d(TAG, "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,11 +140,12 @@ public interface ExoPlayer {
|
|||
void onPositionDiscontinuity(int periodIndex, long positionMs);
|
||||
|
||||
/**
|
||||
* Called when the timeline changes.
|
||||
* Called when manifest and/or timeline has been refreshed.
|
||||
*
|
||||
* @param timeline The new timeline.
|
||||
* @param timeline The source's timeline.
|
||||
* @param manifest The loaded manifest.
|
||||
*/
|
||||
void onTimelineChanged(Timeline timeline);
|
||||
void onSourceInfoRefreshed(Timeline timeline, Object manifest);
|
||||
|
||||
/**
|
||||
* Called when an error occurs. The playback state will transition to
|
||||
|
|
@ -375,6 +376,12 @@ public interface ExoPlayer {
|
|||
*/
|
||||
Timeline getCurrentTimeline();
|
||||
|
||||
/**
|
||||
* Returns the current manifest. The type depends on the {@link MediaSource} passed to
|
||||
* {@link #setMediaSource(MediaSource)} or {@link #setMediaSource(MediaSource, boolean)}.
|
||||
*/
|
||||
Object getCurrentManifest();
|
||||
|
||||
/**
|
||||
* Returns an estimate of the absolute position in milliseconds up to which data is buffered,
|
||||
* or {@link ExoPlayer#UNKNOWN_TIME} if no estimate is available.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import android.os.Handler;
|
|||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.Timeline;
|
||||
|
|
@ -44,6 +45,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
private int pendingSeekAcks;
|
||||
private boolean isLoading;
|
||||
private Timeline timeline;
|
||||
private Object manifest;
|
||||
|
||||
// Playback information when there is no pending seek/set source operation.
|
||||
private PlaybackInfo playbackInfo;
|
||||
|
|
@ -206,6 +208,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
return timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCurrentManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPosition() {
|
||||
if (pendingSeekAcks == 0) {
|
||||
|
|
@ -272,10 +279,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
}
|
||||
break;
|
||||
}
|
||||
case ExoPlayerImplInternal.MSG_TIMELINE_CHANGED: {
|
||||
timeline = (Timeline) msg.obj;
|
||||
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
|
||||
@SuppressWarnings("unchecked")
|
||||
Pair<Timeline, Object> timelineAndManifest = (Pair<Timeline, Object>) msg.obj;
|
||||
timeline = timelineAndManifest.first;
|
||||
manifest = timelineAndManifest.second;
|
||||
for (EventListener listener : listeners) {
|
||||
listener.onTimelineChanged(timeline);
|
||||
listener.onSourceInfoRefreshed(timeline, manifest);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ import java.util.ArrayList;
|
|||
* Implements the internal behavior of {@link ExoPlayerImpl}.
|
||||
*/
|
||||
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback,
|
||||
TrackSelector.InvalidationListener, MediaSource.InvalidationListener {
|
||||
TrackSelector.InvalidationListener, MediaSource.Listener {
|
||||
|
||||
/**
|
||||
* Playback position information which is read on the application's thread by
|
||||
|
|
@ -73,7 +73,7 @@ import java.util.ArrayList;
|
|||
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
|
||||
public static final int MSG_SEEK_ACK = 4;
|
||||
public static final int MSG_POSITION_DISCONTINUITY = 5;
|
||||
public static final int MSG_TIMELINE_CHANGED = 6;
|
||||
public static final int MSG_SOURCE_INFO_REFRESHED = 6;
|
||||
public static final int MSG_ERROR = 7;
|
||||
|
||||
// Internal messages
|
||||
|
|
@ -309,12 +309,14 @@ import java.util.ArrayList;
|
|||
}
|
||||
}
|
||||
|
||||
// MediaSource.InvalidationListener implementation.
|
||||
// MediaSource.Listener implementation.
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
try {
|
||||
handleSourceInvalidated(timeline);
|
||||
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, Pair.create(timeline, manifest))
|
||||
.sendToTarget();
|
||||
handleTimelineRefreshed(timeline);
|
||||
} catch (ExoPlaybackException | IOException e) {
|
||||
Log.e(TAG, "Error handling timeline change.", e);
|
||||
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
|
||||
|
|
@ -802,10 +804,9 @@ import java.util.ArrayList;
|
|||
}
|
||||
}
|
||||
|
||||
public void handleSourceInvalidated(Timeline timeline) throws ExoPlaybackException, IOException {
|
||||
public void handleTimelineRefreshed(Timeline timeline) throws ExoPlaybackException, IOException {
|
||||
Timeline oldTimeline = this.timeline;
|
||||
this.timeline = timeline;
|
||||
eventHandler.obtainMessage(MSG_TIMELINE_CHANGED, timeline).sendToTarget();
|
||||
|
||||
// Update the loaded periods to take into account the new timeline.
|
||||
if (playingPeriod != null) {
|
||||
|
|
|
|||
|
|
@ -404,6 +404,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||
return player.getCurrentTimeline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCurrentManifest() {
|
||||
return player.getCurrentManifest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPosition() {
|
||||
return player.getBufferedPosition();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source;
|
|||
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
|
|
@ -26,6 +27,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
|||
|
||||
private final MediaSource[] mediaSources;
|
||||
private final Timeline[] timelines;
|
||||
private final Object[] manifests;
|
||||
|
||||
private ConcatenatedTimeline timeline;
|
||||
|
||||
|
|
@ -35,25 +37,29 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
|||
public ConcatenatingMediaSource(MediaSource... mediaSources) {
|
||||
this.mediaSources = mediaSources;
|
||||
timelines = new Timeline[mediaSources.length];
|
||||
manifests = new Object[mediaSources.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(final InvalidationListener listener) {
|
||||
public void prepareSource(final Listener listener) {
|
||||
for (int i = 0; i < mediaSources.length; i++) {
|
||||
final int index = i;
|
||||
mediaSources[i].prepareSource(new InvalidationListener() {
|
||||
mediaSources[i].prepareSource(new Listener() {
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
timelines[index] = timeline;
|
||||
public void onSourceInfoRefreshed(Timeline sourceTimeline, Object manifest) {
|
||||
timelines[index] = sourceTimeline;
|
||||
manifests[index] = manifest;
|
||||
for (int i = 0; i < timelines.length; i++) {
|
||||
if (timelines[i] == null) {
|
||||
// Don't invoke the listener until all sources have timelines.
|
||||
return;
|
||||
}
|
||||
}
|
||||
ConcatenatingMediaSource.this.timeline = new ConcatenatedTimeline(timelines.clone());
|
||||
listener.onTimelineChanged(ConcatenatingMediaSource.this.timeline);
|
||||
timeline = new ConcatenatedTimeline(timelines.clone());
|
||||
listener.onSourceInfoRefreshed(timeline, manifests.clone());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -100,45 +106,44 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
|||
private static final class ConcatenatedTimeline implements Timeline {
|
||||
|
||||
private final Timeline[] timelines;
|
||||
private final Object[] manifests;
|
||||
private final int count;
|
||||
private final boolean isFinal;
|
||||
private int[] sourceOffsets;
|
||||
private final int[] sourceOffsets;
|
||||
private final SeekWindow[] seekWindows;
|
||||
|
||||
public ConcatenatedTimeline(Timeline[] timelines) {
|
||||
this.timelines = timelines;
|
||||
|
||||
int[] sourceOffsets = new int[timelines.length];
|
||||
int sourceIndexOffset = 0;
|
||||
for (int i = 0; i < timelines.length; i++) {
|
||||
int periodCount = timelines[i].getPeriodCount();
|
||||
if (periodCount == Timeline.UNKNOWN_PERIOD_COUNT) {
|
||||
sourceOffsets = Arrays.copyOf(sourceOffsets, i);
|
||||
break;
|
||||
}
|
||||
sourceIndexOffset += periodCount;
|
||||
sourceOffsets[i] = sourceIndexOffset;
|
||||
}
|
||||
this.sourceOffsets = sourceOffsets;
|
||||
count = sourceOffsets.length == timelines.length ? sourceOffsets[sourceOffsets.length - 1]
|
||||
: UNKNOWN_PERIOD_COUNT;
|
||||
boolean isFinal = true;
|
||||
manifests = new Object[timelines.length];
|
||||
int[] sourceOffsets = new int[timelines.length];
|
||||
int sourceOffset = 0;
|
||||
ArrayList<SeekWindow> concatenatedSeekWindows = new ArrayList<>();
|
||||
for (int i = 0; i < timelines.length; i++) {
|
||||
Timeline timeline = timelines[i];
|
||||
if (timeline != null) {
|
||||
manifests[i] = timeline.getManifest();
|
||||
if (!timeline.isFinal()) {
|
||||
isFinal = false;
|
||||
}
|
||||
isFinal &= timeline.isFinal();
|
||||
// Offset the seek windows so they are relative to the source.
|
||||
int seekWindowCount = timeline.getSeekWindowCount();
|
||||
for (int j = 0; j < seekWindowCount; j++) {
|
||||
SeekWindow sourceSeekWindow = timeline.getSeekWindow(j);
|
||||
concatenatedSeekWindows.add(sourceSeekWindow.copyOffsetByPeriodCount(sourceOffset));
|
||||
}
|
||||
int periodCount = timeline.getPeriodCount();
|
||||
if (periodCount == Timeline.UNKNOWN_PERIOD_COUNT) {
|
||||
sourceOffsets = Arrays.copyOf(sourceOffsets, i);
|
||||
isFinal = false;
|
||||
break;
|
||||
}
|
||||
sourceOffset += periodCount;
|
||||
sourceOffsets[i] = sourceOffset;
|
||||
}
|
||||
this.timelines = timelines;
|
||||
this.isFinal = isFinal;
|
||||
this.sourceOffsets = sourceOffsets;
|
||||
seekWindows =
|
||||
concatenatedSeekWindows.toArray(new SeekWindow[concatenatedSeekWindows.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeriodCount() {
|
||||
return count;
|
||||
return sourceOffsets.length == timelines.length ? sourceOffsets[sourceOffsets.length - 1]
|
||||
: UNKNOWN_PERIOD_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -172,8 +177,13 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object getManifest() {
|
||||
return manifests;
|
||||
public int getSeekWindowCount() {
|
||||
return seekWindows.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekWindow getSeekWindow(int index) {
|
||||
return seekWindows[index];
|
||||
}
|
||||
|
||||
private int getSourceIndexForPeriod(int periodIndex) {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
|
|||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
|
||||
private MediaSource.Listener sourceListener;
|
||||
private DataSource dataSource;
|
||||
private Loader loader;
|
||||
private ExtractorHolder extractorHolder;
|
||||
|
|
@ -178,8 +179,9 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
|
|||
// MediaSource implementation.
|
||||
|
||||
@Override
|
||||
public void prepareSource(InvalidationListener listener) {
|
||||
listener.onTimelineChanged(new SinglePeriodTimeline(this));
|
||||
public void prepareSource(MediaSource.Listener listener) {
|
||||
sourceListener = listener;
|
||||
listener.onSourceInfoRefreshed(SinglePeriodTimeline.createNonFinalTimeline(this), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -200,7 +202,7 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
|
|||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
// do nothing
|
||||
sourceListener = null;
|
||||
}
|
||||
|
||||
// MediaPeriod implementation.
|
||||
|
|
@ -500,6 +502,9 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
|
|||
tracks = new TrackGroupArray(trackArray);
|
||||
prepared = true;
|
||||
callback.onPeriodPrepared(this);
|
||||
sourceListener.onSourceInfoRefreshed(seekMap.isSeekable()
|
||||
? SinglePeriodTimeline.createSeekableFinalTimeline(this, durationUs)
|
||||
: SinglePeriodTimeline.createUnseekableFinalTimeline(this, durationUs), null);
|
||||
}
|
||||
|
||||
private void copyLengthFromLoader(ExtractingLoadable loadable) {
|
||||
|
|
|
|||
|
|
@ -23,18 +23,17 @@ import java.io.IOException;
|
|||
public interface MediaSource {
|
||||
|
||||
/**
|
||||
* Listener for invalidation events.
|
||||
* Listener for source events.
|
||||
*/
|
||||
interface InvalidationListener {
|
||||
interface Listener {
|
||||
|
||||
/**
|
||||
* Called when the timeline is invalidated.
|
||||
* <p>
|
||||
* May only be called on the player's thread.
|
||||
* Called when manifest and/or timeline has been refreshed.
|
||||
*
|
||||
* @param timeline The new timeline.
|
||||
* @param timeline The source's timeline.
|
||||
* @param manifest The loaded manifest.
|
||||
*/
|
||||
void onTimelineChanged(Timeline timeline);
|
||||
void onSourceInfoRefreshed(Timeline timeline, Object manifest);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -68,14 +67,15 @@ public interface MediaSource {
|
|||
this.periodIndex = periodIndex;
|
||||
this.positionUs = positionUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts preparation of the source.
|
||||
*
|
||||
* @param listener The listener for source invalidation events.
|
||||
* @param listener The listener for source events.
|
||||
*/
|
||||
void prepareSource(InvalidationListener listener);
|
||||
void prepareSource(Listener listener);
|
||||
|
||||
/**
|
||||
* Returns the period index to play in this source's new timeline.
|
||||
|
|
|
|||
|
|
@ -38,22 +38,26 @@ public final class MergingMediaSource implements MediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(final InvalidationListener listener) {
|
||||
mediaSources[0].prepareSource(new InvalidationListener() {
|
||||
public void prepareSource(final Listener listener) {
|
||||
mediaSources[0].prepareSource(new Listener() {
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
checkConsistentTimeline(timeline);
|
||||
|
||||
// All source timelines must match.
|
||||
listener.onTimelineChanged(timeline);
|
||||
listener.onSourceInfoRefreshed(timeline, manifest);
|
||||
}
|
||||
|
||||
});
|
||||
for (int i = 1; i < mediaSources.length; i++) {
|
||||
mediaSources[i].prepareSource(new InvalidationListener() {
|
||||
mediaSources[i].prepareSource(new Listener() {
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
checkConsistentTimeline(timeline);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A window of times the player can seek to.
|
||||
*/
|
||||
public final class SeekWindow {
|
||||
|
||||
public static final SeekWindow UNSEEKABLE = new SeekWindow(0);
|
||||
|
||||
/**
|
||||
* The period index at the start of the window.
|
||||
*/
|
||||
public final int startPeriodIndex;
|
||||
/**
|
||||
* The time at the start of the window relative to the start of the period at
|
||||
* {@link #startPeriodIndex}, in microseconds.
|
||||
*/
|
||||
public final long startTimeUs;
|
||||
/**
|
||||
* The period index at the end of the window.
|
||||
*/
|
||||
public final int endPeriodIndex;
|
||||
/**
|
||||
* The time at the end of the window relative to the start of the period at
|
||||
* {@link #endPeriodIndex}, in microseconds.
|
||||
*/
|
||||
public final long endTimeUs;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SeekWindow} containing times from zero up to {@code durationUs} in the
|
||||
* first period.
|
||||
*
|
||||
* @param durationUs The duration of the window, in microseconds.
|
||||
*/
|
||||
public SeekWindow(long durationUs) {
|
||||
this(0, 0, 0, durationUs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SeekWindow} representing the specified time range.
|
||||
*
|
||||
* @param startPeriodIndex The index of the period containing the start of the window.
|
||||
* @param startTimeUs The start time of the window in microseconds, relative to the start of the
|
||||
* specified start period.
|
||||
* @param endPeriodIndex The index of the period containing the end of the window.
|
||||
* @param endTimeUs The end time of the window in microseconds, relative to the start of the
|
||||
* specified end period.
|
||||
*/
|
||||
public SeekWindow(int startPeriodIndex, long startTimeUs, int endPeriodIndex, long endTimeUs) {
|
||||
this.startPeriodIndex = startPeriodIndex;
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.endPeriodIndex = endPeriodIndex;
|
||||
this.endTimeUs = endTimeUs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new seek window that is offset by the specified number of periods.
|
||||
*
|
||||
* @param periodCount The number of periods to add to {@link #startPeriodIndex} and
|
||||
* {@link #endPeriodIndex} when constructing the copy.
|
||||
* @return A new seek window that is offset by the specified number of periods.
|
||||
*/
|
||||
public SeekWindow copyOffsetByPeriodCount(int periodCount) {
|
||||
return new SeekWindow(startPeriodIndex + periodCount, startTimeUs, endPeriodIndex + periodCount,
|
||||
endTimeUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + startPeriodIndex;
|
||||
result = 31 * result + (int) startTimeUs;
|
||||
result = 31 * result + endPeriodIndex;
|
||||
result = 31 * result + (int) endTimeUs;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SeekWindow other = (SeekWindow) obj;
|
||||
return other.startPeriodIndex == startPeriodIndex
|
||||
&& other.startTimeUs == startTimeUs
|
||||
&& other.endPeriodIndex == endPeriodIndex
|
||||
&& other.endTimeUs == endTimeUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SeekWindow[" + startPeriodIndex + ", " + startTimeUs + ", " + endPeriodIndex + ", "
|
||||
+ endTimeUs + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.source;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
/**
|
||||
|
|
@ -23,41 +23,50 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
*/
|
||||
public final class SinglePeriodTimeline implements Timeline {
|
||||
|
||||
/**
|
||||
* Returns a new timeline with one period of unknown duration and no seek window.
|
||||
*
|
||||
* @param id The identifier for the period.
|
||||
* @return A new timeline with one period of unknown duration.
|
||||
*/
|
||||
public static Timeline createNonFinalTimeline(Object id) {
|
||||
return new SinglePeriodTimeline(id, false, C.UNSET_TIME_US);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a final timeline with one period of known duration and an empty seek window.
|
||||
*
|
||||
* @param id The identifier for the period.
|
||||
* @param durationUs The duration of the period, in microseconds.
|
||||
* @return A new, unseekable, final timeline with one period.
|
||||
*/
|
||||
public static Timeline createUnseekableFinalTimeline(Object id, long durationUs) {
|
||||
return new SinglePeriodTimeline(id, true, durationUs, SeekWindow.UNSEEKABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a final timeline with one period of known duration and a seek window extending from
|
||||
* zero to its duration.
|
||||
*
|
||||
* @param id The identifier for the period.
|
||||
* @param durationUs The duration of the period, in microseconds.
|
||||
* @return A new, seekable, final timeline with one period.
|
||||
*/
|
||||
public static Timeline createSeekableFinalTimeline(Object id, long durationUs) {
|
||||
return new SinglePeriodTimeline(id, true, durationUs, new SeekWindow(durationUs));
|
||||
}
|
||||
|
||||
private final Object id;
|
||||
private final Object manifest;
|
||||
private final boolean isFinal;
|
||||
private final long duration;
|
||||
private final SeekWindow[] seekWindows;
|
||||
|
||||
/**
|
||||
* Creates a new timeline with one period of unknown duration.
|
||||
*
|
||||
* @param id The identifier for the period.
|
||||
*/
|
||||
public SinglePeriodTimeline(Object id) {
|
||||
this(id, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timeline with one period of unknown duration providing an optional manifest.
|
||||
*
|
||||
* @param id The identifier for the period.
|
||||
* @param manifest The source-specific manifest that defined the period, or {@code null}.
|
||||
*/
|
||||
public SinglePeriodTimeline(Object id, Object manifest) {
|
||||
this(id, manifest, ExoPlayer.UNKNOWN_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timeline with one period of the specified duration providing an optional
|
||||
* manifest.
|
||||
*
|
||||
* @param id The identifier for the period.
|
||||
* @param manifest The source-specific manifest that defined the period, or {@code null}.
|
||||
* @param duration The duration of the period in milliseconds.
|
||||
*/
|
||||
public SinglePeriodTimeline(Object id, Object manifest, long duration) {
|
||||
private SinglePeriodTimeline(Object id, boolean isFinal, long duration,
|
||||
SeekWindow... seekWindows) {
|
||||
this.id = Assertions.checkNotNull(id);
|
||||
this.manifest = manifest;
|
||||
this.isFinal = isFinal;
|
||||
this.duration = duration;
|
||||
this.seekWindows = seekWindows;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -67,7 +76,7 @@ public final class SinglePeriodTimeline implements Timeline {
|
|||
|
||||
@Override
|
||||
public boolean isFinal() {
|
||||
return true;
|
||||
return isFinal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -89,8 +98,13 @@ public final class SinglePeriodTimeline implements Timeline {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object getManifest() {
|
||||
return manifest;
|
||||
public int getSeekWindowCount() {
|
||||
return seekWindows.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekWindow getSeekWindow(int index) {
|
||||
return seekWindows[index];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,8 +113,9 @@ public final class SingleSampleMediaSource implements MediaPeriod, MediaSource,
|
|||
// MediaSource implementation.
|
||||
|
||||
@Override
|
||||
public void prepareSource(InvalidationListener listener) {
|
||||
listener.onTimelineChanged(new SinglePeriodTimeline(this));
|
||||
public void prepareSource(MediaSource.Listener listener) {
|
||||
Timeline timeline = SinglePeriodTimeline.createSeekableFinalTimeline(this, durationUs);
|
||||
listener.onSourceInfoRefreshed(timeline, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -135,7 +136,7 @@ public final class SingleSampleMediaSource implements MediaPeriod, MediaSource,
|
|||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
// do nothing
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// MediaPeriod implementation.
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public interface Timeline {
|
|||
int getPeriodCount();
|
||||
|
||||
/**
|
||||
* Returns whether the timeline is final, which means it will not be invalidated again.
|
||||
* Returns whether the timeline is final, which means it will not change.
|
||||
*/
|
||||
boolean isFinal();
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ public interface Timeline {
|
|||
* Returns a unique identifier for the period at {@code index}, or {@code null} if the period at
|
||||
* {@code index} is not known. The identifier is stable across {@link Timeline} instances.
|
||||
* <p>
|
||||
* When a source is invalidated the player uses period identifiers to determine what periods are
|
||||
* When the timeline changes the player uses period identifiers to determine what periods are
|
||||
* unchanged. Implementations that associate an object with each period can return the object for
|
||||
* the provided index to guarantee uniqueness. Other implementations must be careful to return
|
||||
* identifiers that can't clash with (for example) identifiers used by other timelines that may be
|
||||
|
|
@ -78,8 +78,15 @@ public interface Timeline {
|
|||
int getIndexOfPeriod(Object id);
|
||||
|
||||
/**
|
||||
* Returns the immutable manifest corresponding to this timeline.
|
||||
* Returns the number of seek windows that can be accessed via {@link #getSeekWindow(int)}.
|
||||
*/
|
||||
Object getManifest();
|
||||
int getSeekWindowCount();
|
||||
|
||||
/**
|
||||
* Returns the {@link SeekWindow} at {@code index}, which represents positions that can be seeked
|
||||
* to in the timeline. The seek windows may change when
|
||||
* {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} is called.
|
||||
*/
|
||||
SeekWindow getSeekWindow(int index);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
|||
mediaChunks = new LinkedList<>();
|
||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||
sampleQueue = new DefaultTrackOutput(allocator);
|
||||
pendingResetPositionUs = C.UNSET_TIME_US;
|
||||
lastSeekPositionUs = positionUs;
|
||||
pendingResetPositionUs = positionUs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,8 +124,7 @@ import java.util.List;
|
|||
newSampleStreamArray(newEnabledSourceCount);
|
||||
int newEnabledSourceIndex = 0;
|
||||
|
||||
// Iterate over currently enabled streams, either releasing them or adding them to the new
|
||||
// list.
|
||||
// Iterate over currently enabled streams, either releasing them or adding them to the new list.
|
||||
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
||||
if (oldStreams.contains(sampleStream)) {
|
||||
sampleStream.release();
|
||||
|
|
@ -137,8 +136,7 @@ import java.util.List;
|
|||
// Instantiate and return new streams.
|
||||
SampleStream[] streamsToReturn = new SampleStream[newSelections.size()];
|
||||
for (int i = 0; i < newSelections.size(); i++) {
|
||||
newSampleStreams[newEnabledSourceIndex] =
|
||||
buildSampleStream(newSelections.get(i), positionUs);
|
||||
newSampleStreams[newEnabledSourceIndex] = buildSampleStream(newSelections.get(i), positionUs);
|
||||
streamsToReturn[i] = newSampleStreams[newEnabledSourceIndex];
|
||||
newEnabledSourceIndex++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
|||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.SeekWindow;
|
||||
import com.google.android.exoplayer2.source.Timeline;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.Loader;
|
||||
|
|
@ -52,6 +54,17 @@ public final class DashMediaSource implements MediaSource {
|
|||
* The default minimum number of times to retry loading data prior to failing.
|
||||
*/
|
||||
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
|
||||
/**
|
||||
* The interval in milliseconds between invocations of
|
||||
* {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} when the source's
|
||||
* {@link SeekWindow} is changing dynamically (for example, for incomplete live streams).
|
||||
*/
|
||||
private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000;
|
||||
/**
|
||||
* The offset in microseconds subtracted from the live edge position when calculating the default
|
||||
* position returned by {@link #getDefaultStartPosition(int)}.
|
||||
*/
|
||||
private static final long LIVE_EDGE_OFFSET_US = 30000000;
|
||||
|
||||
private static final String TAG = "DashMediaSource";
|
||||
|
||||
|
|
@ -62,8 +75,9 @@ public final class DashMediaSource implements MediaSource {
|
|||
private final DashManifestParser manifestParser;
|
||||
private final ManifestCallback manifestCallback;
|
||||
private final Object manifestUriLock;
|
||||
private final Runnable refreshSourceInfoRunnable;
|
||||
|
||||
private MediaSource.InvalidationListener invalidationListener;
|
||||
private MediaSource.Listener sourceListener;
|
||||
private DataSource dataSource;
|
||||
private Loader loader;
|
||||
|
||||
|
|
@ -71,9 +85,10 @@ public final class DashMediaSource implements MediaSource {
|
|||
private long manifestLoadStartTimestamp;
|
||||
private long manifestLoadEndTimestamp;
|
||||
private DashManifest manifest;
|
||||
private Handler manifestRefreshHandler;
|
||||
private Handler handler;
|
||||
private ArrayList<DashMediaPeriod> periods;
|
||||
private long elapsedRealtimeOffset;
|
||||
private SeekWindow seekWindow;
|
||||
private long elapsedRealtimeOffsetMs;
|
||||
|
||||
public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
|
||||
DashChunkSource.Factory chunkSourceFactory, Handler eventHandler,
|
||||
|
|
@ -93,6 +108,12 @@ public final class DashMediaSource implements MediaSource {
|
|||
manifestParser = new DashManifestParser();
|
||||
manifestCallback = new ManifestCallback();
|
||||
manifestUriLock = new Object();
|
||||
refreshSourceInfoRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
refreshSourceInfo();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,11 +130,11 @@ public final class DashMediaSource implements MediaSource {
|
|||
// MediaSource implementation.
|
||||
|
||||
@Override
|
||||
public void prepareSource(InvalidationListener listener) {
|
||||
invalidationListener = listener;
|
||||
public void prepareSource(MediaSource.Listener listener) {
|
||||
sourceListener = listener;
|
||||
dataSource = manifestDataSourceFactory.createDataSource();
|
||||
loader = new Loader("Loader:DashMediaSource");
|
||||
manifestRefreshHandler = new Handler();
|
||||
handler = new Handler();
|
||||
startLoadingManifest();
|
||||
}
|
||||
|
||||
|
|
@ -137,10 +158,21 @@ public final class DashMediaSource implements MediaSource {
|
|||
|
||||
@Override
|
||||
public Position getDefaultStartPosition(int index) {
|
||||
if (seekWindow == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index == 0 && manifest.dynamic) {
|
||||
// The stream is live, so jump to the live edge.
|
||||
// TODO[playlists]: Actually jump to the live edge, rather than the start of the last period.
|
||||
return new Position(periods.size() - 1, 0);
|
||||
// The stream is live, so return a position a position offset from the live edge.
|
||||
int periodIndex = seekWindow.endPeriodIndex;
|
||||
long positionUs = seekWindow.endTimeUs - LIVE_EDGE_OFFSET_US;
|
||||
while (positionUs < 0 && periodIndex > seekWindow.startPeriodIndex) {
|
||||
periodIndex--;
|
||||
positionUs += manifest.getPeriodDuration(periodIndex) * 1000;
|
||||
}
|
||||
positionUs = Math.max(positionUs,
|
||||
periodIndex == seekWindow.startPeriodIndex ? seekWindow.startTimeUs : 0);
|
||||
return new Position(periodIndex, positionUs);
|
||||
}
|
||||
return new Position(index, 0);
|
||||
}
|
||||
|
|
@ -164,11 +196,11 @@ public final class DashMediaSource implements MediaSource {
|
|||
manifestLoadStartTimestamp = 0;
|
||||
manifestLoadEndTimestamp = 0;
|
||||
manifest = null;
|
||||
if (manifestRefreshHandler != null) {
|
||||
manifestRefreshHandler.removeCallbacksAndMessages(null);
|
||||
manifestRefreshHandler = null;
|
||||
if (handler != null) {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
handler = null;
|
||||
}
|
||||
elapsedRealtimeOffset = 0;
|
||||
elapsedRealtimeOffsetMs = 0;
|
||||
}
|
||||
|
||||
// Loadable callbacks.
|
||||
|
|
@ -312,7 +344,7 @@ public final class DashMediaSource implements MediaSource {
|
|||
}
|
||||
|
||||
private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) {
|
||||
this.elapsedRealtimeOffset = elapsedRealtimeOffsetMs;
|
||||
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
|
||||
finishManifestProcessing();
|
||||
}
|
||||
|
||||
|
|
@ -329,13 +361,49 @@ public final class DashMediaSource implements MediaSource {
|
|||
int periodCount = manifest.getPeriodCount();
|
||||
for (int i = periods.size(); i < periodCount; i++) {
|
||||
periods.add(new DashMediaPeriod(manifest, i, chunkSourceFactory, minLoadableRetryCount,
|
||||
eventDispatcher, elapsedRealtimeOffset, loader));
|
||||
eventDispatcher, elapsedRealtimeOffsetMs, loader));
|
||||
}
|
||||
invalidationListener.onTimelineChanged(new DashTimeline(manifest,
|
||||
periods.toArray(new DashMediaPeriod[periods.size()])));
|
||||
|
||||
handler.removeCallbacks(refreshSourceInfoRunnable);
|
||||
refreshSourceInfo();
|
||||
scheduleManifestRefresh();
|
||||
}
|
||||
|
||||
private void refreshSourceInfo() {
|
||||
// Update the seek window.
|
||||
int periodCount = manifest.getPeriodCount();
|
||||
int lastPeriodIndex = periodCount - 1;
|
||||
PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0),
|
||||
manifest.getPeriodDuration(0) * 1000);
|
||||
PeriodSeekInfo lastPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(
|
||||
manifest.getPeriod(lastPeriodIndex), manifest.getPeriodDuration(lastPeriodIndex) * 1000);
|
||||
long currentStartTimeUs;
|
||||
long currentEndTimeUs;
|
||||
if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) {
|
||||
// The seek window is changing so post a Runnable to update it.
|
||||
handler.postDelayed(refreshSourceInfoRunnable, NOTIFY_MANIFEST_INTERVAL_MS);
|
||||
|
||||
long minStartPositionUs = firstPeriodSeekInfo.availableStartTimeUs;
|
||||
long maxEndPositionUs = lastPeriodSeekInfo.availableEndTimeUs;
|
||||
long timeShiftBufferDepthUs = manifest.timeShiftBufferDepth == -1 ? -1
|
||||
: manifest.timeShiftBufferDepth * 1000;
|
||||
currentEndTimeUs = Math.min(maxEndPositionUs,
|
||||
getNowUnixTimeUs() - manifest.availabilityStartTime * 1000);
|
||||
currentStartTimeUs = timeShiftBufferDepthUs == -1 ? minStartPositionUs
|
||||
: Math.max(minStartPositionUs, currentEndTimeUs - timeShiftBufferDepthUs);
|
||||
} else {
|
||||
handler.removeCallbacks(refreshSourceInfoRunnable);
|
||||
currentStartTimeUs = firstPeriodSeekInfo.availableStartTimeUs;
|
||||
currentEndTimeUs = lastPeriodSeekInfo.availableEndTimeUs;
|
||||
}
|
||||
seekWindow = new SeekWindow(0, currentStartTimeUs, lastPeriodIndex, currentEndTimeUs);
|
||||
|
||||
DashMediaPeriod[] mediaPeriods =
|
||||
periods.toArray(new DashMediaPeriod[manifest.getPeriodCount()]);
|
||||
sourceListener.onSourceInfoRefreshed(new DashTimeline(manifest, mediaPeriods, seekWindow),
|
||||
manifest);
|
||||
}
|
||||
|
||||
private void scheduleManifestRefresh() {
|
||||
if (!manifest.dynamic) {
|
||||
return;
|
||||
|
|
@ -350,7 +418,7 @@ public final class DashMediaSource implements MediaSource {
|
|||
}
|
||||
long nextLoadTimestamp = manifestLoadStartTimestamp + minUpdatePeriod;
|
||||
long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime());
|
||||
manifestRefreshHandler.postDelayed(new Runnable() {
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startLoadingManifest();
|
||||
|
|
@ -364,14 +432,65 @@ public final class DashMediaSource implements MediaSource {
|
|||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||
}
|
||||
|
||||
private long getNowUnixTimeUs() {
|
||||
if (elapsedRealtimeOffsetMs != 0) {
|
||||
return SystemClock.elapsedRealtime() * 1000 + elapsedRealtimeOffsetMs;
|
||||
} else {
|
||||
return System.currentTimeMillis() * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PeriodSeekInfo {
|
||||
|
||||
public static PeriodSeekInfo createPeriodSeekInfo(Period period, long durationUs) {
|
||||
int adaptationSetCount = period.adaptationSets.size();
|
||||
long availableStartTimeUs = 0;
|
||||
long availableEndTimeUs = Long.MAX_VALUE;
|
||||
boolean isIndexExplicit = false;
|
||||
for (int i = 0; i < adaptationSetCount; i++) {
|
||||
DashSegmentIndex index = period.adaptationSets.get(i).representations.get(0).getIndex();
|
||||
if (index == null) {
|
||||
return new PeriodSeekInfo(true, 0, durationUs);
|
||||
}
|
||||
int firstSegmentNum = index.getFirstSegmentNum();
|
||||
int lastSegmentNum = index.getLastSegmentNum(durationUs);
|
||||
isIndexExplicit |= index.isExplicit();
|
||||
long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstSegmentNum);
|
||||
availableStartTimeUs = Math.max(availableStartTimeUs, adaptationSetAvailableStartTimeUs);
|
||||
if (lastSegmentNum != DashSegmentIndex.INDEX_UNBOUNDED) {
|
||||
long adaptationSetAvailableEndTimeUs = index.getTimeUs(lastSegmentNum)
|
||||
+ index.getDurationUs(lastSegmentNum, durationUs);
|
||||
availableEndTimeUs = Math.min(availableEndTimeUs, adaptationSetAvailableEndTimeUs);
|
||||
} else {
|
||||
// The available end time is unmodified, because this index is unbounded.
|
||||
}
|
||||
}
|
||||
return new PeriodSeekInfo(isIndexExplicit, availableStartTimeUs, availableEndTimeUs);
|
||||
}
|
||||
|
||||
public final boolean isIndexExplicit;
|
||||
public final long availableStartTimeUs;
|
||||
public final long availableEndTimeUs;
|
||||
|
||||
private PeriodSeekInfo(boolean isIndexExplicit, long availableStartTimeUs,
|
||||
long availableEndTimeUs) {
|
||||
this.isIndexExplicit = isIndexExplicit;
|
||||
this.availableStartTimeUs = availableStartTimeUs;
|
||||
this.availableEndTimeUs = availableEndTimeUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class DashTimeline implements Timeline {
|
||||
|
||||
private final DashManifest manifest;
|
||||
private final DashMediaPeriod[] periods;
|
||||
private final SeekWindow[] seekWindows;
|
||||
|
||||
public DashTimeline(DashManifest manifest, DashMediaPeriod[] periods) {
|
||||
public DashTimeline(DashManifest manifest, DashMediaPeriod[] periods, SeekWindow seekWindow) {
|
||||
this.manifest = manifest;
|
||||
this.periods = periods;
|
||||
seekWindows = new SeekWindow[] {seekWindow};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -405,8 +524,13 @@ public final class DashMediaSource implements MediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object getManifest() {
|
||||
return manifest;
|
||||
public int getSeekWindowCount() {
|
||||
return seekWindows.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekWindow getSeekWindow(int index) {
|
||||
return seekWindows[index];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -516,7 +516,6 @@ public class DashManifestParser extends DefaultHandler
|
|||
|
||||
protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, String baseUrl,
|
||||
SegmentTemplate parent) throws XmlPullParserException, IOException {
|
||||
|
||||
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
||||
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
|
||||
parent != null ? parent.presentationTimeOffset : 0);
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
||||
private final HlsPlaylistParser manifestParser;
|
||||
|
||||
private MediaSource.Listener sourceListener;
|
||||
private DataSource manifestDataSource;
|
||||
private Loader manifestFetcher;
|
||||
|
||||
|
|
@ -75,6 +76,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
private long preparePositionUs;
|
||||
private int pendingPrepareCount;
|
||||
|
||||
private HlsPlaylist playlist;
|
||||
private boolean seenFirstTrackSelection;
|
||||
private long durationUs;
|
||||
private long pendingDiscontinuityPositionUs;
|
||||
|
|
@ -108,9 +110,10 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
// MediaSource implementation.
|
||||
|
||||
@Override
|
||||
public void prepareSource(InvalidationListener listener) {
|
||||
public void prepareSource(MediaSource.Listener listener) {
|
||||
sourceListener = listener;
|
||||
// TODO: Defer until the playlist has been loaded.
|
||||
listener.onTimelineChanged(new SinglePeriodTimeline(this));
|
||||
listener.onSourceInfoRefreshed(SinglePeriodTimeline.createNonFinalTimeline(this), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -132,7 +135,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
// do nothing
|
||||
sourceListener = null;
|
||||
}
|
||||
|
||||
// MediaPeriod implementation.
|
||||
|
|
@ -255,6 +258,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
allocator = null;
|
||||
preparePositionUs = 0;
|
||||
pendingPrepareCount = 0;
|
||||
playlist = null;
|
||||
seenFirstTrackSelection = false;
|
||||
durationUs = 0;
|
||||
isLive = false;
|
||||
|
|
@ -277,8 +281,8 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
long loadDurationMs) {
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
HlsPlaylist playlist = loadable.getResult();
|
||||
List<HlsSampleStreamWrapper> sampleStreamWrapperList = buildSampleStreamWrappers(playlist);
|
||||
playlist = loadable.getResult();
|
||||
List<HlsSampleStreamWrapper> sampleStreamWrapperList = buildSampleStreamWrappers();
|
||||
sampleStreamWrappers = new HlsSampleStreamWrapper[sampleStreamWrapperList.size()];
|
||||
sampleStreamWrapperList.toArray(sampleStreamWrappers);
|
||||
selectedTrackCounts = new int[sampleStreamWrappers.length];
|
||||
|
|
@ -330,6 +334,12 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
}
|
||||
trackGroups = new TrackGroupArray(trackGroupArray);
|
||||
callback.onPeriodPrepared(this);
|
||||
|
||||
// TODO[playlists]: Calculate the seek window.
|
||||
Timeline timeline =
|
||||
isLive ? SinglePeriodTimeline.createUnseekableFinalTimeline(this, durationUs)
|
||||
: SinglePeriodTimeline.createSeekableFinalTimeline(this, durationUs);
|
||||
sourceListener.onSourceInfoRefreshed(timeline, playlist);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -343,7 +353,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||
|
||||
// Internal methods.
|
||||
|
||||
private List<HlsSampleStreamWrapper> buildSampleStreamWrappers(HlsPlaylist playlist) {
|
||||
private List<HlsSampleStreamWrapper> buildSampleStreamWrappers() {
|
||||
ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
|
||||
String baseUri = playlist.baseUri;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public final class SsMediaSource implements MediaSource,
|
|||
private final EventDispatcher eventDispatcher;
|
||||
private final SsManifestParser manifestParser;
|
||||
|
||||
private MediaSource.InvalidationListener invalidationListener;
|
||||
private MediaSource.Listener sourceListener;
|
||||
private DataSource manifestDataSource;
|
||||
private Loader manifestLoader;
|
||||
|
||||
|
|
@ -87,8 +87,8 @@ public final class SsMediaSource implements MediaSource,
|
|||
// MediaSource implementation.
|
||||
|
||||
@Override
|
||||
public void prepareSource(InvalidationListener listener) {
|
||||
this.invalidationListener = listener;
|
||||
public void prepareSource(MediaSource.Listener listener) {
|
||||
sourceListener = listener;
|
||||
manifestDataSource = dataSourceFactory.createDataSource();
|
||||
manifestLoader = new Loader("Loader:Manifest");
|
||||
manifestRefreshHandler = new Handler();
|
||||
|
|
@ -114,6 +114,7 @@ public final class SsMediaSource implements MediaSource,
|
|||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
sourceListener = null;
|
||||
period = null;
|
||||
manifest = null;
|
||||
manifestDataSource = null;
|
||||
|
|
@ -140,13 +141,13 @@ public final class SsMediaSource implements MediaSource,
|
|||
if (period == null) {
|
||||
period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
|
||||
eventDispatcher, manifestLoader);
|
||||
Timeline timeline = manifest.durationUs == C.UNSET_TIME_US
|
||||
? new SinglePeriodTimeline(this, manifest)
|
||||
: new SinglePeriodTimeline(this, manifest, manifest.durationUs / 1000);
|
||||
invalidationListener.onTimelineChanged(timeline);
|
||||
} else {
|
||||
period.updateManifest(manifest);
|
||||
}
|
||||
Timeline timeline = manifest.isLive || manifest.durationUs == C.UNSET_TIME_US
|
||||
? SinglePeriodTimeline.createUnseekableFinalTimeline(this, C.UNSET_TIME_US)
|
||||
: SinglePeriodTimeline.createSeekableFinalTimeline(this, manifest.durationUs);
|
||||
sourceListener.onSourceInfoRefreshed(timeline, manifest);
|
||||
scheduleManifestRefresh();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
|||
}
|
||||
|
||||
@Override
|
||||
public final void onTimelineChanged(Timeline timeline) {
|
||||
public final void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue