Remove V1 DASH multi-period + seeking-in-window from V2.
Both of these features are being promoted to first class citizens in V2 (multi-period support will be handled via playlists, seeking-in-window will be handled by exposing the window/timeline from the player and via the normal seek API). For now, it's much easier to continue the refactoring process with the features removed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=119518675
This commit is contained in:
parent
1c0a120aeb
commit
7170ff380c
@ -19,7 +19,6 @@ import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
|
|||||||
import com.google.android.exoplayer.ExoPlayer;
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
import com.google.android.exoplayer.Format;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||||
import com.google.android.exoplayer.TimeRange;
|
|
||||||
import com.google.android.exoplayer.TrackGroup;
|
import com.google.android.exoplayer.TrackGroup;
|
||||||
import com.google.android.exoplayer.TrackGroupArray;
|
import com.google.android.exoplayer.TrackGroupArray;
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
@ -50,7 +49,6 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long sessionStartTimeMs;
|
private long sessionStartTimeMs;
|
||||||
private long[] availableRangeValuesUs;
|
|
||||||
|
|
||||||
public void startSession() {
|
public void startSession() {
|
||||||
sessionStartTimeMs = SystemClock.elapsedRealtime();
|
sessionStartTimeMs = SystemClock.elapsedRealtime();
|
||||||
@ -215,13 +213,6 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||||||
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
|
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
|
||||||
availableRangeValuesUs = availableRange.getCurrentBoundsUs(availableRangeValuesUs);
|
|
||||||
Log.d(TAG, "availableRange [" + availableRange.isStatic() + ", " + availableRangeValuesUs[0]
|
|
||||||
+ ", " + availableRangeValuesUs[1] + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printInternalError(String type, Exception e) {
|
private void printInternalError(String type, Exception e) {
|
||||||
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
|
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,6 @@ import android.os.Handler;
|
|||||||
// TODO[REFACTOR]: Bring back UTC timing element support.
|
// TODO[REFACTOR]: Bring back UTC timing element support.
|
||||||
public class DashSourceBuilder implements SourceBuilder {
|
public class DashSourceBuilder implements SourceBuilder {
|
||||||
|
|
||||||
private static final String TAG = "DashSourceBuilder";
|
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
||||||
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
||||||
@ -78,15 +76,14 @@ public class DashSourceBuilder implements SourceBuilder {
|
|||||||
// Build the video renderer.
|
// Build the video renderer.
|
||||||
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO,
|
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO,
|
||||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS,
|
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
|
||||||
0, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
|
||||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||||
|
|
||||||
// Build the audio renderer.
|
// Build the audio renderer.
|
||||||
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO,
|
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO,
|
||||||
audioDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_AUDIO);
|
audioDataSource, null, LIVE_EDGE_LATENCY_MS);
|
||||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||||
DemoPlayer.TYPE_AUDIO);
|
DemoPlayer.TYPE_AUDIO);
|
||||||
@ -94,7 +91,7 @@ public class DashSourceBuilder implements SourceBuilder {
|
|||||||
// Build the text renderer.
|
// Build the text renderer.
|
||||||
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT,
|
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT,
|
||||||
textDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_TEXT);
|
textDataSource, null, LIVE_EDGE_LATENCY_MS);
|
||||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||||
DemoPlayer.TYPE_TEXT);
|
DemoPlayer.TYPE_TEXT);
|
||||||
|
@ -27,12 +27,10 @@ import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializatio
|
|||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.SingleSampleSource;
|
import com.google.android.exoplayer.SingleSampleSource;
|
||||||
import com.google.android.exoplayer.TimeRange;
|
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||||
import com.google.android.exoplayer.audio.AudioTrack;
|
import com.google.android.exoplayer.audio.AudioTrack;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
||||||
import com.google.android.exoplayer.dash.DashChunkSource;
|
|
||||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
||||||
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
|
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
|
||||||
@ -67,8 +65,8 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
|||||||
ChunkSampleSourceEventListener, ExtractorSampleSource.EventListener,
|
ChunkSampleSourceEventListener, ExtractorSampleSource.EventListener,
|
||||||
SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
|
SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
|
||||||
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
|
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
|
||||||
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer,
|
StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer<List<Id3Frame>>,
|
||||||
MetadataRenderer<List<Id3Frame>>, DebugTextViewHelper.Provider {
|
DebugTextViewHelper.Provider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a source to play.
|
* Builds a source to play.
|
||||||
@ -127,7 +125,6 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
|||||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
|
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
|
||||||
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
||||||
long initializationDurationMs);
|
long initializationDurationMs);
|
||||||
void onAvailableRangeChanged(int sourceId, TimeRange availableRange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -441,13 +438,6 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
|
||||||
if (infoListener != null) {
|
|
||||||
infoListener.onAvailableRangeChanged(sourceId, availableRange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayWhenReadyCommitted() {
|
public void onPlayWhenReadyCommitted() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 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.exoplayer;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.TimeRange.StaticTimeRange;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for {@link TimeRange}.
|
|
||||||
*/
|
|
||||||
public class TimeRangeTest extends TestCase {
|
|
||||||
|
|
||||||
public void testStaticEquals() {
|
|
||||||
TimeRange timeRange1 = new StaticTimeRange(0, 30000000);
|
|
||||||
assertTrue(timeRange1.equals(timeRange1));
|
|
||||||
|
|
||||||
TimeRange timeRange2 = new StaticTimeRange(0, 30000000);
|
|
||||||
assertTrue(timeRange1.equals(timeRange2));
|
|
||||||
|
|
||||||
TimeRange timeRange3 = new StaticTimeRange(0, 60000000);
|
|
||||||
assertFalse(timeRange1.equals(timeRange3));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 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.exoplayer;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Clock;
|
|
||||||
|
|
||||||
import android.os.SystemClock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A container to store a start and end time in microseconds.
|
|
||||||
*/
|
|
||||||
public interface TimeRange {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the range is static, meaning repeated calls to {@link #getCurrentBoundsMs(long[])}
|
|
||||||
* or {@link #getCurrentBoundsUs(long[])} will return identical results.
|
|
||||||
*
|
|
||||||
* @return Whether the range is static.
|
|
||||||
*/
|
|
||||||
public boolean isStatic();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the start and end times (in milliseconds) of the TimeRange in the provided array,
|
|
||||||
* or creates a new one.
|
|
||||||
*
|
|
||||||
* @param out An array to store the start and end times; can be null.
|
|
||||||
* @return An array containing the start time (index 0) and end time (index 1) in milliseconds.
|
|
||||||
*/
|
|
||||||
public long[] getCurrentBoundsMs(long[] out);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the start and end times (in microseconds) of the TimeRange in the provided array,
|
|
||||||
* or creates a new one.
|
|
||||||
*
|
|
||||||
* @param out An array to store the start and end times; can be null.
|
|
||||||
* @return An array containing the start time (index 0) and end time (index 1) in microseconds.
|
|
||||||
*/
|
|
||||||
public long[] getCurrentBoundsUs(long[] out);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A static {@link TimeRange}.
|
|
||||||
*/
|
|
||||||
public static final class StaticTimeRange implements TimeRange {
|
|
||||||
|
|
||||||
private final long startTimeUs;
|
|
||||||
private final long endTimeUs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param startTimeUs The beginning of the range.
|
|
||||||
* @param endTimeUs The end of the range.
|
|
||||||
*/
|
|
||||||
public StaticTimeRange(long startTimeUs, long endTimeUs) {
|
|
||||||
this.startTimeUs = startTimeUs;
|
|
||||||
this.endTimeUs = endTimeUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStatic() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long[] getCurrentBoundsMs(long[] out) {
|
|
||||||
out = getCurrentBoundsUs(out);
|
|
||||||
out[0] /= 1000;
|
|
||||||
out[1] /= 1000;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long[] getCurrentBoundsUs(long[] out) {
|
|
||||||
if (out == null || out.length < 2) {
|
|
||||||
out = new long[2];
|
|
||||||
}
|
|
||||||
out[0] = startTimeUs;
|
|
||||||
out[1] = endTimeUs;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = 17;
|
|
||||||
result = 31 * result + (int) startTimeUs;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
StaticTimeRange other = (StaticTimeRange) obj;
|
|
||||||
return other.startTimeUs == startTimeUs
|
|
||||||
&& other.endTimeUs == endTimeUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dynamic {@link TimeRange}.
|
|
||||||
*/
|
|
||||||
public static final class DynamicTimeRange implements TimeRange {
|
|
||||||
|
|
||||||
private final long minStartTimeUs;
|
|
||||||
private final long maxEndTimeUs;
|
|
||||||
private final long elapsedRealtimeAtStartUs;
|
|
||||||
private final long bufferDepthUs;
|
|
||||||
private final Clock systemClock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param minStartTimeUs A lower bound on the beginning of the range.
|
|
||||||
* @param maxEndTimeUs An upper bound on the end of the range.
|
|
||||||
* @param elapsedRealtimeAtStartUs The value of {@link SystemClock#elapsedRealtime()},
|
|
||||||
* multiplied by 1000, corresponding to a media time of zero.
|
|
||||||
* @param bufferDepthUs The buffer depth of the media, or -1.
|
|
||||||
* @param systemClock A system clock.
|
|
||||||
*/
|
|
||||||
public DynamicTimeRange(long minStartTimeUs, long maxEndTimeUs, long elapsedRealtimeAtStartUs,
|
|
||||||
long bufferDepthUs, Clock systemClock) {
|
|
||||||
this.minStartTimeUs = minStartTimeUs;
|
|
||||||
this.maxEndTimeUs = maxEndTimeUs;
|
|
||||||
this.elapsedRealtimeAtStartUs = elapsedRealtimeAtStartUs;
|
|
||||||
this.bufferDepthUs = bufferDepthUs;
|
|
||||||
this.systemClock = systemClock;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStatic() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long[] getCurrentBoundsMs(long[] out) {
|
|
||||||
out = getCurrentBoundsUs(out);
|
|
||||||
out[0] /= 1000;
|
|
||||||
out[1] /= 1000;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long[] getCurrentBoundsUs(long[] out) {
|
|
||||||
if (out == null || out.length < 2) {
|
|
||||||
out = new long[2];
|
|
||||||
}
|
|
||||||
// Don't allow the end time to be greater than the total elapsed time.
|
|
||||||
long currentEndTimeUs = Math.min(maxEndTimeUs,
|
|
||||||
(systemClock.elapsedRealtime() * 1000) - elapsedRealtimeAtStartUs);
|
|
||||||
long currentStartTimeUs = minStartTimeUs;
|
|
||||||
if (bufferDepthUs != -1) {
|
|
||||||
// Don't allow the start time to be less than the current end time minus the buffer depth.
|
|
||||||
currentStartTimeUs = Math.max(currentStartTimeUs,
|
|
||||||
currentEndTimeUs - bufferDepthUs);
|
|
||||||
}
|
|
||||||
out[0] = currentStartTimeUs;
|
|
||||||
out[1] = currentEndTimeUs;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = 17;
|
|
||||||
result = 31 * result + (int) minStartTimeUs;
|
|
||||||
result = 31 * result + (int) maxEndTimeUs;
|
|
||||||
result = 31 * result + (int) elapsedRealtimeAtStartUs;
|
|
||||||
result = 31 * result + (int) bufferDepthUs;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DynamicTimeRange other = (DynamicTimeRange) obj;
|
|
||||||
return other.minStartTimeUs == minStartTimeUs
|
|
||||||
&& other.maxEndTimeUs == maxEndTimeUs
|
|
||||||
&& other.elapsedRealtimeAtStartUs == elapsedRealtimeAtStartUs
|
|
||||||
&& other.bufferDepthUs == bufferDepthUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -50,11 +50,10 @@ public abstract class BaseMediaChunk extends MediaChunk {
|
|||||||
* be called at any time to obtain the sample format and drm initialization data. False if
|
* be called at any time to obtain the sample format and drm initialization data. False if
|
||||||
* these methods are only guaranteed to return correct data after the first sample data has
|
* these methods are only guaranteed to return correct data after the first sample data has
|
||||||
* been output from the chunk.
|
* been output from the chunk.
|
||||||
* @param parentId Identifier for a parent from which this chunk originates.
|
|
||||||
*/
|
*/
|
||||||
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||||
long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal, int parentId) {
|
long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal) {
|
||||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId);
|
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
|
||||||
this.isSampleFormatFinal = isSampleFormatFinal;
|
this.isSampleFormatFinal = isSampleFormatFinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,10 +76,6 @@ public abstract class Chunk implements Loadable {
|
|||||||
* Implementations may define custom {@link #trigger} codes greater than or equal to this value.
|
* Implementations may define custom {@link #trigger} codes greater than or equal to this value.
|
||||||
*/
|
*/
|
||||||
public static final int TRIGGER_CUSTOM_BASE = 10000;
|
public static final int TRIGGER_CUSTOM_BASE = 10000;
|
||||||
/**
|
|
||||||
* Value of {@link #parentId} if no parent id need be specified.
|
|
||||||
*/
|
|
||||||
public static final int NO_PARENT_ID = -1;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the chunk. For reporting only.
|
* The type of the chunk. For reporting only.
|
||||||
@ -98,10 +94,6 @@ public abstract class Chunk implements Loadable {
|
|||||||
* The {@link DataSpec} that defines the data to be loaded.
|
* The {@link DataSpec} that defines the data to be loaded.
|
||||||
*/
|
*/
|
||||||
public final DataSpec dataSpec;
|
public final DataSpec dataSpec;
|
||||||
/**
|
|
||||||
* Optional identifier for a parent from which this chunk originates.
|
|
||||||
*/
|
|
||||||
public final int parentId;
|
|
||||||
|
|
||||||
protected final DataSource dataSource;
|
protected final DataSource dataSource;
|
||||||
|
|
||||||
@ -114,16 +106,13 @@ public abstract class Chunk implements Loadable {
|
|||||||
* @param type See {@link #type}.
|
* @param type See {@link #type}.
|
||||||
* @param trigger See {@link #trigger}.
|
* @param trigger See {@link #trigger}.
|
||||||
* @param format See {@link #format}.
|
* @param format See {@link #format}.
|
||||||
* @param parentId See {@link #parentId}.
|
|
||||||
*/
|
*/
|
||||||
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
|
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format) {
|
||||||
int parentId) {
|
|
||||||
this.dataSource = Assertions.checkNotNull(dataSource);
|
this.dataSource = Assertions.checkNotNull(dataSource);
|
||||||
this.dataSpec = Assertions.checkNotNull(dataSpec);
|
this.dataSpec = Assertions.checkNotNull(dataSpec);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.trigger = trigger;
|
this.trigger = trigger;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.parentId = parentId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,14 +60,13 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
|
|||||||
* @param isSampleFormatFinal True if {@code sampleFormat} and {@code drmInitData} are known to be
|
* @param isSampleFormatFinal True if {@code sampleFormat} and {@code drmInitData} are known to be
|
||||||
* correct and final. False if the data may define its own sample format or initialization
|
* correct and final. False if the data may define its own sample format or initialization
|
||||||
* data.
|
* data.
|
||||||
* @param parentId Identifier for a parent from which this chunk originates.
|
|
||||||
*/
|
*/
|
||||||
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||||
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
|
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
|
||||||
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData,
|
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData,
|
||||||
boolean isSampleFormatFinal, int parentId) {
|
boolean isSampleFormatFinal) {
|
||||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
||||||
isSampleFormatFinal, parentId);
|
isSampleFormatFinal);
|
||||||
this.extractorWrapper = extractorWrapper;
|
this.extractorWrapper = extractorWrapper;
|
||||||
this.sampleOffsetUs = sampleOffsetUs;
|
this.sampleOffsetUs = sampleOffsetUs;
|
||||||
this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs);
|
this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs);
|
||||||
|
@ -44,12 +44,11 @@ public abstract class DataChunk extends Chunk {
|
|||||||
* @param type See {@link #type}.
|
* @param type See {@link #type}.
|
||||||
* @param trigger See {@link #trigger}.
|
* @param trigger See {@link #trigger}.
|
||||||
* @param format See {@link #format}.
|
* @param format See {@link #format}.
|
||||||
* @param parentId Identifier for a parent from which this chunk originates.
|
|
||||||
* @param data An optional recycled array that can be used as a holder for the data.
|
* @param data An optional recycled array that can be used as a holder for the data.
|
||||||
*/
|
*/
|
||||||
public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
|
public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
|
||||||
int parentId, byte[] data) {
|
byte[] data) {
|
||||||
super(dataSource, dataSpec, type, trigger, format, parentId);
|
super(dataSource, dataSpec, type, trigger, format);
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,11 +46,6 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
|
|||||||
private volatile int bytesLoaded;
|
private volatile int bytesLoaded;
|
||||||
private volatile boolean loadCanceled;
|
private volatile boolean loadCanceled;
|
||||||
|
|
||||||
public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
|
||||||
ChunkExtractorWrapper extractorWrapper) {
|
|
||||||
this(dataSource, dataSpec, trigger, format, extractorWrapper, Chunk.NO_PARENT_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for a chunk of media samples.
|
* Constructor for a chunk of media samples.
|
||||||
*
|
*
|
||||||
@ -59,11 +54,10 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
|
|||||||
* @param trigger The reason for this chunk being selected.
|
* @param trigger The reason for this chunk being selected.
|
||||||
* @param format The format of the stream to which this chunk belongs.
|
* @param format The format of the stream to which this chunk belongs.
|
||||||
* @param extractorWrapper A wrapped extractor to use for parsing the initialization data.
|
* @param extractorWrapper A wrapped extractor to use for parsing the initialization data.
|
||||||
* @param parentId Identifier for a parent from which this chunk originates.
|
|
||||||
*/
|
*/
|
||||||
public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||||
ChunkExtractorWrapper extractorWrapper, int parentId) {
|
ChunkExtractorWrapper extractorWrapper) {
|
||||||
super(dataSource, dataSpec, Chunk.TYPE_MEDIA_INITIALIZATION, trigger, format, parentId);
|
super(dataSource, dataSpec, Chunk.TYPE_MEDIA_INITIALIZATION, trigger, format);
|
||||||
this.extractorWrapper = extractorWrapper;
|
this.extractorWrapper = extractorWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,16 +38,6 @@ public abstract class MediaChunk extends Chunk {
|
|||||||
*/
|
*/
|
||||||
public final int chunkIndex;
|
public final int chunkIndex;
|
||||||
|
|
||||||
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
|
||||||
long startTimeUs, long endTimeUs, int chunkIndex) {
|
|
||||||
this(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
|
||||||
Chunk.NO_PARENT_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNextChunkIndex() {
|
|
||||||
return chunkIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param dataSource A {@link DataSource} for loading the data.
|
* @param dataSource A {@link DataSource} for loading the data.
|
||||||
* @param dataSpec Defines the data to be loaded.
|
* @param dataSpec Defines the data to be loaded.
|
||||||
@ -56,15 +46,18 @@ public abstract class MediaChunk extends Chunk {
|
|||||||
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
|
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
|
||||||
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
|
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
|
||||||
* @param chunkIndex The index of the chunk.
|
* @param chunkIndex The index of the chunk.
|
||||||
* @param parentId Identifier for a parent from which this chunk originates.
|
|
||||||
*/
|
*/
|
||||||
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||||
long startTimeUs, long endTimeUs, int chunkIndex, int parentId) {
|
long startTimeUs, long endTimeUs, int chunkIndex) {
|
||||||
super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format, parentId);
|
super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format);
|
||||||
Assertions.checkNotNull(format);
|
Assertions.checkNotNull(format);
|
||||||
this.startTimeUs = startTimeUs;
|
this.startTimeUs = startTimeUs;
|
||||||
this.endTimeUs = endTimeUs;
|
this.endTimeUs = endTimeUs;
|
||||||
this.chunkIndex = chunkIndex;
|
this.chunkIndex = chunkIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final int getNextChunkIndex() {
|
||||||
|
return chunkIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -48,13 +48,11 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
|||||||
* @param sampleFormat The format of the sample.
|
* @param sampleFormat The format of the sample.
|
||||||
* @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm
|
* @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm
|
||||||
* protected.
|
* protected.
|
||||||
* @param parentId Identifier for a parent from which this chunk originates.
|
|
||||||
*/
|
*/
|
||||||
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
|
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
|
||||||
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat,
|
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat,
|
||||||
DrmInitData sampleDrmInitData, int parentId) {
|
DrmInitData sampleDrmInitData) {
|
||||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true,
|
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true);
|
||||||
parentId);
|
|
||||||
this.sampleFormat = sampleFormat;
|
this.sampleFormat = sampleFormat;
|
||||||
this.sampleDrmInitData = sampleDrmInitData;
|
this.sampleDrmInitData = sampleDrmInitData;
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,6 @@ import com.google.android.exoplayer.BehindLiveWindowException;
|
|||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.Format;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.Format.DecreasingBandwidthComparator;
|
import com.google.android.exoplayer.Format.DecreasingBandwidthComparator;
|
||||||
import com.google.android.exoplayer.TimeRange;
|
|
||||||
import com.google.android.exoplayer.TimeRange.DynamicTimeRange;
|
|
||||||
import com.google.android.exoplayer.TimeRange.StaticTimeRange;
|
|
||||||
import com.google.android.exoplayer.TrackGroup;
|
import com.google.android.exoplayer.TrackGroup;
|
||||||
import com.google.android.exoplayer.chunk.Chunk;
|
import com.google.android.exoplayer.chunk.Chunk;
|
||||||
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
|
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
|
||||||
@ -45,13 +42,8 @@ import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor;
|
|||||||
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
|
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer.util.Clock;
|
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.SystemClock;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -73,60 +65,29 @@ import java.util.List;
|
|||||||
// TODO[REFACTOR]: Handle multiple adaptation sets of the same type (at a higher level).
|
// TODO[REFACTOR]: Handle multiple adaptation sets of the same type (at a higher level).
|
||||||
public class DashChunkSource implements ChunkSource {
|
public class DashChunkSource implements ChunkSource {
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface definition for a callback to be notified of {@link DashChunkSource} events.
|
|
||||||
*/
|
|
||||||
public interface EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the available seek range of the stream has changed.
|
|
||||||
*
|
|
||||||
* @param sourceId The id of the reporting {@link DashChunkSource}.
|
|
||||||
* @param availableRange The range which specifies available content that can be seeked to.
|
|
||||||
*/
|
|
||||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when an AdaptationSet is missing from the MPD.
|
|
||||||
*/
|
|
||||||
public static class NoAdaptationSetException extends IOException {
|
|
||||||
|
|
||||||
public NoAdaptationSetException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Handler eventHandler;
|
|
||||||
private final EventListener eventListener;
|
|
||||||
|
|
||||||
private final int adaptationSetType;
|
private final int adaptationSetType;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final FormatEvaluator adaptiveFormatEvaluator;
|
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||||
private final Evaluation evaluation;
|
private final Evaluation evaluation;
|
||||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||||
private final SparseArray<PeriodHolder> periodHolders;
|
|
||||||
private final Clock systemClock;
|
|
||||||
private final long liveEdgeLatencyUs;
|
private final long liveEdgeLatencyUs;
|
||||||
private final long elapsedRealtimeOffsetUs;
|
|
||||||
private final long[] availableRangeValues;
|
|
||||||
private final int eventSourceId;
|
|
||||||
|
|
||||||
|
// Properties of the initial manifest.
|
||||||
private boolean live;
|
private boolean live;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
|
|
||||||
private MediaPresentationDescription currentManifest;
|
private MediaPresentationDescription currentManifest;
|
||||||
private MediaPresentationDescription processedManifest;
|
private DrmInitData drmInitData;
|
||||||
private int nextPeriodHolderIndex;
|
private boolean indexIsUnbounded;
|
||||||
private TimeRange availableRange;
|
private boolean indexIsExplicit;
|
||||||
private boolean startAtLiveEdge;
|
|
||||||
private boolean lastChunkWasInitialization;
|
private boolean lastChunkWasInitialization;
|
||||||
private IOException fatalError;
|
private IOException fatalError;
|
||||||
|
|
||||||
// Properties of exposed tracks.
|
// Properties of exposed tracks.
|
||||||
private int adaptationSetIndex;
|
private int adaptationSetIndex;
|
||||||
private TrackGroup trackGroup;
|
private TrackGroup trackGroup;
|
||||||
|
private RepresentationHolder[] representationHolders;
|
||||||
|
|
||||||
// Properties of enabled tracks.
|
// Properties of enabled tracks.
|
||||||
private Format[] enabledFormats;
|
private Format[] enabledFormats;
|
||||||
@ -144,75 +105,16 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
* manifest). Choosing a small value will minimize latency introduced by the player, however
|
* manifest). Choosing a small value will minimize latency introduced by the player, however
|
||||||
* note that the value sets an upper bound on the length of media that the player can buffer.
|
* note that the value sets an upper bound on the length of media that the player can buffer.
|
||||||
* Hence a small value may increase the probability of rebuffering and playback failures.
|
* Hence a small value may increase the probability of rebuffering and playback failures.
|
||||||
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
|
||||||
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
|
|
||||||
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
|
|
||||||
* @param eventHandler A handler to use when delivering events to {@code EventListener}. May be
|
|
||||||
* null if delivery of events is not required.
|
|
||||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
|
||||||
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
|
|
||||||
*/
|
*/
|
||||||
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
||||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||||
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, Handler eventHandler,
|
long liveEdgeLatencyMs) {
|
||||||
EventListener eventListener, int eventSourceId) {
|
|
||||||
this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(),
|
|
||||||
liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, true, eventHandler, eventListener,
|
|
||||||
eventSourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor to use for live DVR streaming.
|
|
||||||
*
|
|
||||||
* @param manifestFetcher A fetcher for the manifest.
|
|
||||||
* @param adaptationSetType The type of the adaptation set exposed by this source. One of
|
|
||||||
* {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and
|
|
||||||
* {@link AdaptationSet#TYPE_TEXT}.
|
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
|
||||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
|
||||||
* @param liveEdgeLatencyMs For live streams, the number of milliseconds that the playback should
|
|
||||||
* lag behind the "live edge" (i.e. the end of the most recently defined media in the
|
|
||||||
* manifest). Choosing a small value will minimize latency introduced by the player, however
|
|
||||||
* note that the value sets an upper bound on the length of media that the player can buffer.
|
|
||||||
* Hence a small value may increase the probability of rebuffering and playback failures.
|
|
||||||
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
|
||||||
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
|
|
||||||
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
|
|
||||||
* @param startAtLiveEdge True if the stream should start at the live edge; false if it should
|
|
||||||
* at the beginning of the live window.
|
|
||||||
* @param eventHandler A handler to use when delivering events to {@code EventListener}. May be
|
|
||||||
* null if delivery of events is not required.
|
|
||||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
|
||||||
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
|
|
||||||
*/
|
|
||||||
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
|
||||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
|
||||||
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, boolean startAtLiveEdge,
|
|
||||||
Handler eventHandler, EventListener eventListener, int eventSourceId) {
|
|
||||||
this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(),
|
|
||||||
liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, startAtLiveEdge, eventHandler,
|
|
||||||
eventListener, eventSourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
|
||||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
|
||||||
Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs,
|
|
||||||
boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener,
|
|
||||||
int eventSourceId) {
|
|
||||||
this.manifestFetcher = manifestFetcher;
|
this.manifestFetcher = manifestFetcher;
|
||||||
this.adaptationSetType = adaptationSetType;
|
this.adaptationSetType = adaptationSetType;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||||
this.systemClock = systemClock;
|
this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000;
|
||||||
this.liveEdgeLatencyUs = liveEdgeLatencyUs;
|
|
||||||
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetUs;
|
|
||||||
this.startAtLiveEdge = startAtLiveEdge;
|
|
||||||
this.eventHandler = eventHandler;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.eventSourceId = eventSourceId;
|
|
||||||
this.evaluation = new Evaluation();
|
this.evaluation = new Evaluation();
|
||||||
this.availableRangeValues = new long[2];
|
|
||||||
periodHolders = new SparseArray<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChunkSource implementation.
|
// ChunkSource implementation.
|
||||||
@ -235,8 +137,6 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
manifestFetcher.requestRefresh();
|
manifestFetcher.requestRefresh();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
live = currentManifest.dynamic;
|
|
||||||
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
|
|
||||||
initForManifest(currentManifest);
|
initForManifest(currentManifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,11 +174,8 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MediaPresentationDescription newManifest = manifestFetcher.getManifest();
|
MediaPresentationDescription newManifest = manifestFetcher.getManifest();
|
||||||
if (newManifest != null && newManifest != processedManifest) {
|
if (newManifest != null && newManifest != currentManifest) {
|
||||||
processManifest(newManifest);
|
processManifest(newManifest);
|
||||||
// Manifests may be rejected, so the new manifest may not become the next currentManifest.
|
|
||||||
// Track a manifest has been processed to avoid processing twice when it was discarded.
|
|
||||||
processedManifest = newManifest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
|
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
|
||||||
@ -327,112 +224,76 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean startingNewPeriod;
|
|
||||||
PeriodHolder periodHolder;
|
|
||||||
|
|
||||||
availableRange.getCurrentBoundsUs(availableRangeValues);
|
|
||||||
if (previous == null) {
|
|
||||||
if (live) {
|
|
||||||
if (startAtLiveEdge) {
|
|
||||||
// We want live streams to start at the live edge instead of the beginning of the
|
|
||||||
// manifest
|
|
||||||
playbackPositionUs = Math.max(availableRangeValues[0],
|
|
||||||
availableRangeValues[1] - liveEdgeLatencyUs);
|
|
||||||
} else {
|
|
||||||
// we subtract 1 from the upper bound because it's exclusive for that bound
|
|
||||||
playbackPositionUs = Math.min(playbackPositionUs, availableRangeValues[1] - 1);
|
|
||||||
playbackPositionUs = Math.max(playbackPositionUs, availableRangeValues[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
periodHolder = findPeriodHolder(playbackPositionUs);
|
|
||||||
startingNewPeriod = true;
|
|
||||||
} else {
|
|
||||||
if (startAtLiveEdge) {
|
|
||||||
// now that we know the player is consuming media chunks (since the queue isn't empty),
|
|
||||||
// set startAtLiveEdge to false so that the user can perform seek operations
|
|
||||||
startAtLiveEdge = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long nextSegmentStartTimeUs = previous.endTimeUs;
|
|
||||||
if (live && nextSegmentStartTimeUs < availableRangeValues[0]) {
|
|
||||||
// This is before the first chunk in the current manifest.
|
|
||||||
fatalError = new BehindLiveWindowException();
|
|
||||||
return;
|
|
||||||
} else if (currentManifest.dynamic && nextSegmentStartTimeUs >= availableRangeValues[1]) {
|
|
||||||
// This chunk is beyond the last chunk in the current manifest. If the index is bounded
|
|
||||||
// we'll need to wait until it's refreshed. If it's unbounded we just need to wait for a
|
|
||||||
// while before attempting to load the chunk.
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// A period's duration is the maximum of its various representation's durations, so it's
|
|
||||||
// possible that due to the minor differences between them our available range values might
|
|
||||||
// not sync exactly with the actual available content, so double check whether or not we've
|
|
||||||
// really run out of content to play.
|
|
||||||
PeriodHolder lastPeriodHolder = periodHolders.valueAt(periodHolders.size() - 1);
|
|
||||||
if (previous.parentId == lastPeriodHolder.localIndex) {
|
|
||||||
RepresentationHolder representationHolder =
|
|
||||||
lastPeriodHolder.representationHolders[getTrackIndex(previous.format)];
|
|
||||||
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
|
||||||
if (!currentManifest.dynamic) {
|
|
||||||
// The current manifest isn't dynamic, so we've reached the end of the stream.
|
|
||||||
out.endOfStream = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startingNewPeriod = false;
|
|
||||||
periodHolder = periodHolders.get(previous.parentId);
|
|
||||||
if (periodHolder == null) {
|
|
||||||
// The previous chunk was from a period that's no longer on the manifest, therefore the
|
|
||||||
// next chunk must be the first one in the first period that's still on the manifest
|
|
||||||
// (note that we can't actually update the segmentNum yet because the new period might
|
|
||||||
// have a different sequence and its segmentIndex might not have been loaded yet).
|
|
||||||
periodHolder = periodHolders.valueAt(0);
|
|
||||||
startingNewPeriod = true;
|
|
||||||
} else if (!periodHolder.isIndexUnbounded()) {
|
|
||||||
RepresentationHolder representationHolder =
|
|
||||||
periodHolder.representationHolders[getTrackIndex(previous.format)];
|
|
||||||
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
|
||||||
// We reached the end of a period. Start the next one.
|
|
||||||
periodHolder = periodHolders.get(previous.parentId + 1);
|
|
||||||
startingNewPeriod = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RepresentationHolder representationHolder =
|
RepresentationHolder representationHolder =
|
||||||
periodHolder.representationHolders[getTrackIndex(selectedFormat)];
|
representationHolders[getTrackIndex(selectedFormat)];
|
||||||
Representation selectedRepresentation = representationHolder.representation;
|
Representation selectedRepresentation = representationHolder.representation;
|
||||||
|
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
|
||||||
|
|
||||||
RangedUri pendingInitializationUri = null;
|
RangedUri pendingInitializationUri = null;
|
||||||
RangedUri pendingIndexUri = null;
|
RangedUri pendingIndexUri = null;
|
||||||
|
|
||||||
Format sampleFormat = representationHolder.sampleFormat;
|
Format sampleFormat = representationHolder.sampleFormat;
|
||||||
if (sampleFormat == null) {
|
if (sampleFormat == null) {
|
||||||
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
||||||
}
|
}
|
||||||
if (representationHolder.segmentIndex == null) {
|
if (segmentIndex == null) {
|
||||||
pendingIndexUri = selectedRepresentation.getIndexUri();
|
pendingIndexUri = selectedRepresentation.getIndexUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingInitializationUri != null || pendingIndexUri != null) {
|
if (pendingInitializationUri != null || pendingIndexUri != null) {
|
||||||
// We have initialization and/or index requests to make.
|
// We have initialization and/or index requests to make.
|
||||||
Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri,
|
Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri,
|
||||||
selectedRepresentation, representationHolder.extractorWrapper, dataSource,
|
selectedRepresentation, representationHolder.extractorWrapper, dataSource,
|
||||||
periodHolder.localIndex, evaluation.trigger);
|
evaluation.trigger);
|
||||||
lastChunkWasInitialization = true;
|
lastChunkWasInitialization = true;
|
||||||
out.chunk = initializationChunk;
|
out.chunk = initializationChunk;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int segmentNum = previous == null ? representationHolder.getSegmentNum(playbackPositionUs)
|
// TODO[REFACTOR]: Bring back UTC timing element support.
|
||||||
: startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum()
|
long nowUs = System.currentTimeMillis() * 1000;
|
||||||
: previous.getNextChunkIndex();
|
|
||||||
Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource,
|
int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
|
||||||
selectedFormat, sampleFormat, segmentNum, evaluation.trigger);
|
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
|
||||||
|
boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
|
||||||
|
if (indexUnbounded) {
|
||||||
|
// The index is itself unbounded. We need to use the current time to calculate the range of
|
||||||
|
// available segments.
|
||||||
|
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
|
||||||
|
if (currentManifest.timeShiftBufferDepth != -1) {
|
||||||
|
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000;
|
||||||
|
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
|
||||||
|
representationHolder.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
|
||||||
|
}
|
||||||
|
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
|
||||||
|
// index of the last completed segment.
|
||||||
|
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimestampUs) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int segmentNum;
|
||||||
|
if (previous == null) {
|
||||||
|
if (live) {
|
||||||
|
playbackPositionUs = getLiveSeekPosition(nowUs);
|
||||||
|
}
|
||||||
|
segmentNum = representationHolder.getSegmentNum(playbackPositionUs);
|
||||||
|
} else {
|
||||||
|
segmentNum = previous.getNextChunkIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (live && segmentNum < firstAvailableSegmentNum) {
|
||||||
|
// This is before the first chunk in the current manifest.
|
||||||
|
fatalError = new BehindLiveWindowException();
|
||||||
|
return;
|
||||||
|
} else if (currentManifest.dynamic) {
|
||||||
|
if (segmentNum > lastAvailableSegmentNum) {
|
||||||
|
// This is beyond the last chunk in the current manifest.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (segmentNum > lastAvailableSegmentNum) {
|
||||||
|
out.endOfStream = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource, selectedFormat,
|
||||||
|
sampleFormat, segmentNum, evaluation.trigger);
|
||||||
lastChunkWasInitialization = false;
|
lastChunkWasInitialization = false;
|
||||||
out.chunk = nextMediaChunk;
|
out.chunk = nextMediaChunk;
|
||||||
}
|
}
|
||||||
@ -441,14 +302,8 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
public void onChunkLoadCompleted(Chunk chunk) {
|
public void onChunkLoadCompleted(Chunk chunk) {
|
||||||
if (chunk instanceof InitializationChunk) {
|
if (chunk instanceof InitializationChunk) {
|
||||||
InitializationChunk initializationChunk = (InitializationChunk) chunk;
|
InitializationChunk initializationChunk = (InitializationChunk) chunk;
|
||||||
PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId);
|
|
||||||
if (periodHolder == null) {
|
|
||||||
// period for this initialization chunk may no longer be on the manifest
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RepresentationHolder representationHolder =
|
RepresentationHolder representationHolder =
|
||||||
periodHolder.representationHolders[getTrackIndex(initializationChunk.format)];
|
representationHolders[getTrackIndex(initializationChunk.format)];
|
||||||
if (initializationChunk.hasSampleFormat()) {
|
if (initializationChunk.hasSampleFormat()) {
|
||||||
representationHolder.sampleFormat = initializationChunk.getSampleFormat();
|
representationHolder.sampleFormat = initializationChunk.getSampleFormat();
|
||||||
}
|
}
|
||||||
@ -462,8 +317,8 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
// The null check avoids overwriting drmInitData obtained from the manifest with drmInitData
|
// The null check avoids overwriting drmInitData obtained from the manifest with drmInitData
|
||||||
// obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
// obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
||||||
if (periodHolder.drmInitData == null && initializationChunk.hasDrmInitData()) {
|
if (drmInitData == null && initializationChunk.hasDrmInitData()) {
|
||||||
periodHolder.drmInitData = initializationChunk.getDrmInitData();
|
drmInitData = initializationChunk.getDrmInitData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -479,9 +334,7 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
if (enabledFormats.length > 1) {
|
if (enabledFormats.length > 1) {
|
||||||
adaptiveFormatEvaluator.disable();
|
adaptiveFormatEvaluator.disable();
|
||||||
}
|
}
|
||||||
periodHolders.clear();
|
|
||||||
evaluation.clear();
|
evaluation.clear();
|
||||||
availableRange = null;
|
|
||||||
fatalError = null;
|
fatalError = null;
|
||||||
enabledFormats = null;
|
enabledFormats = null;
|
||||||
}
|
}
|
||||||
@ -490,6 +343,9 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
private void initForManifest(MediaPresentationDescription manifest) {
|
private void initForManifest(MediaPresentationDescription manifest) {
|
||||||
Period period = manifest.getPeriod(0);
|
Period period = manifest.getPeriod(0);
|
||||||
|
live = currentManifest.dynamic;
|
||||||
|
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
|
||||||
|
|
||||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
||||||
if (adaptationSet.type == adaptationSetType) {
|
if (adaptationSet.type == adaptationSetType) {
|
||||||
@ -497,11 +353,18 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
List<Representation> representations = adaptationSet.representations;
|
List<Representation> representations = adaptationSet.representations;
|
||||||
if (!representations.isEmpty()) {
|
if (!representations.isEmpty()) {
|
||||||
// We've found a non-empty adaptation set of the exposed type.
|
// We've found a non-empty adaptation set of the exposed type.
|
||||||
|
long periodDurationUs = getPeriodDurationUs(manifest, 0);
|
||||||
|
representationHolders = new RepresentationHolder[representations.size()];
|
||||||
Format[] trackFormats = new Format[representations.size()];
|
Format[] trackFormats = new Format[representations.size()];
|
||||||
for (int j = 0; j < trackFormats.length; j++) {
|
for (int j = 0; j < trackFormats.length; j++) {
|
||||||
trackFormats[j] = representations.get(j).format;
|
Representation representation = representations.get(j);
|
||||||
|
representationHolders[j] = new RepresentationHolder(periodDurationUs, representation);
|
||||||
|
trackFormats[j] = representation.format;
|
||||||
}
|
}
|
||||||
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats);
|
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats);
|
||||||
|
drmInitData = getDrmInitData(adaptationSet);
|
||||||
|
updateRepresentationIndependentProperties(periodDurationUs,
|
||||||
|
representationHolders[0].representation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,14 +372,40 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
trackGroup = null;
|
trackGroup = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
private void processManifest(MediaPresentationDescription newManifest) {
|
||||||
/* package */ TimeRange getAvailableRange() {
|
try {
|
||||||
return availableRange;
|
currentManifest = newManifest;
|
||||||
|
long periodDurationUs = getPeriodDurationUs(currentManifest, 0);
|
||||||
|
List<Representation> representations = currentManifest.getPeriod(0).adaptationSets
|
||||||
|
.get(adaptationSetIndex).representations;
|
||||||
|
for (int i = 0; i < representationHolders.length; i++) {
|
||||||
|
Representation representation = representations.get(i);
|
||||||
|
representationHolders[i].updateRepresentation(periodDurationUs, representation);
|
||||||
|
}
|
||||||
|
updateRepresentationIndependentProperties(periodDurationUs,
|
||||||
|
representationHolders[0].representation);
|
||||||
|
} catch (BehindLiveWindowException e) {
|
||||||
|
fatalError = e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRepresentationIndependentProperties(long periodDurationUs,
|
||||||
|
Representation arbitaryRepresentation) {
|
||||||
|
DashSegmentIndex segmentIndex = arbitaryRepresentation.getIndex();
|
||||||
|
if (segmentIndex != null) {
|
||||||
|
int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs);
|
||||||
|
indexIsUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
|
||||||
|
indexIsExplicit = segmentIndex.isExplicit();
|
||||||
|
} else {
|
||||||
|
indexIsUnbounded = false;
|
||||||
|
indexIsExplicit = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
|
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
|
||||||
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
|
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
|
||||||
int manifestIndex, int trigger) {
|
int trigger) {
|
||||||
RangedUri requestUri;
|
RangedUri requestUri;
|
||||||
if (initializationUri != null) {
|
if (initializationUri != null) {
|
||||||
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
||||||
@ -531,12 +420,11 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
|
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
|
||||||
representation.getCacheKey());
|
representation.getCacheKey());
|
||||||
return new InitializationChunk(dataSource, dataSpec, trigger, representation.format,
|
return new InitializationChunk(dataSource, dataSpec, trigger, representation.format,
|
||||||
extractor, manifestIndex);
|
extractor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Chunk newMediaChunk(PeriodHolder periodHolder,
|
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource,
|
||||||
RepresentationHolder representationHolder, DataSource dataSource, Format trackFormat,
|
Format trackFormat, Format sampleFormat, int segmentNum, int trigger) {
|
||||||
Format sampleFormat, int segmentNum, int trigger) {
|
|
||||||
Representation representation = representationHolder.representation;
|
Representation representation = representationHolder.representation;
|
||||||
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
|
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
|
||||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
|
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
|
||||||
@ -544,112 +432,43 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
||||||
representation.getCacheKey());
|
representation.getCacheKey());
|
||||||
|
|
||||||
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
|
|
||||||
if (representationHolder.extractorWrapper == null) {
|
if (representationHolder.extractorWrapper == null) {
|
||||||
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, trackFormat,
|
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, trackFormat,
|
||||||
startTimeUs, endTimeUs, segmentNum, trackFormat, null, periodHolder.localIndex);
|
startTimeUs, endTimeUs, segmentNum, trackFormat, null);
|
||||||
} else {
|
} else {
|
||||||
boolean isSampleFormatFinal = sampleFormat != null;
|
boolean isSampleFormatFinal = sampleFormat != null;
|
||||||
|
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, trackFormat, startTimeUs,
|
return new ContainerMediaChunk(dataSource, dataSpec, trigger, trackFormat, startTimeUs,
|
||||||
endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper,
|
endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper,
|
||||||
sampleFormat, periodHolder.drmInitData, isSampleFormatFinal, periodHolder.localIndex);
|
sampleFormat, drmInitData, isSampleFormatFinal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getNowUnixTimeUs() {
|
/**
|
||||||
if (elapsedRealtimeOffsetUs != 0) {
|
* For live playbacks, determines the seek position that snaps playback to be
|
||||||
return (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs;
|
* {@link #liveEdgeLatencyUs} behind the live edge of the current manifest
|
||||||
|
*
|
||||||
|
* @return The seek position in microseconds.
|
||||||
|
*/
|
||||||
|
private long getLiveSeekPosition(long nowUs) {
|
||||||
|
long elapsedTimeUs = nowUs - currentManifest.availabilityStartTime * 1000;
|
||||||
|
long liveEdgeTimestampUs;
|
||||||
|
if (indexIsUnbounded) {
|
||||||
|
liveEdgeTimestampUs = elapsedTimeUs;
|
||||||
} else {
|
} else {
|
||||||
return System.currentTimeMillis() * 1000;
|
liveEdgeTimestampUs = Long.MIN_VALUE;
|
||||||
}
|
for (RepresentationHolder representationHolder : representationHolders) {
|
||||||
}
|
int lastSegmentNum = representationHolder.getLastSegmentNum();
|
||||||
|
long indexLiveEdgeTimestampUs = representationHolder.getSegmentEndTimeUs(lastSegmentNum);
|
||||||
private PeriodHolder findPeriodHolder(long positionUs) {
|
liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs);
|
||||||
// if positionUs is before the first period, return the first period
|
}
|
||||||
if (positionUs < periodHolders.valueAt(0).getAvailableStartTimeUs()) {
|
if (!indexIsExplicit) {
|
||||||
return periodHolders.valueAt(0);
|
// Some segments defined by the index may not be available yet. Bound the calculated live
|
||||||
}
|
// edge based on the elapsed time since the manifest became available.
|
||||||
|
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs, elapsedTimeUs);
|
||||||
for (int i = 0; i < periodHolders.size() - 1; i++) {
|
|
||||||
PeriodHolder periodHolder = periodHolders.valueAt(i);
|
|
||||||
if (positionUs < periodHolder.getAvailableEndTimeUs()) {
|
|
||||||
return periodHolder;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return liveEdgeTimestampUs - liveEdgeLatencyUs;
|
||||||
// positionUs is within or after the last period
|
|
||||||
return periodHolders.valueAt(periodHolders.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processManifest(MediaPresentationDescription manifest) {
|
|
||||||
// Remove old periods.
|
|
||||||
Period firstPeriod = manifest.getPeriod(0);
|
|
||||||
while (periodHolders.size() > 0
|
|
||||||
&& periodHolders.valueAt(0).startTimeUs < firstPeriod.startMs * 1000) {
|
|
||||||
PeriodHolder periodHolder = periodHolders.valueAt(0);
|
|
||||||
// TODO: Use periodHolders.removeAt(0) if the minimum API level is ever increased to 11.
|
|
||||||
periodHolders.remove(periodHolder.localIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// After discarding old periods, we should never have more periods than listed in the new
|
|
||||||
// manifest. That would mean that a previously announced period is no longer advertised. If
|
|
||||||
// this condition occurs, assume that we are hitting a manifest server that is out of sync and
|
|
||||||
// behind, discard this manifest, and try again later.
|
|
||||||
if (periodHolders.size() > manifest.getPeriodCount()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update existing periods. Only the first and last periods can change.
|
|
||||||
try {
|
|
||||||
int periodHolderCount = periodHolders.size();
|
|
||||||
if (periodHolderCount > 0) {
|
|
||||||
periodHolders.valueAt(0).updatePeriod(manifest, 0, adaptationSetIndex);
|
|
||||||
if (periodHolderCount > 1) {
|
|
||||||
int lastIndex = periodHolderCount - 1;
|
|
||||||
periodHolders.valueAt(lastIndex).updatePeriod(manifest, lastIndex, adaptationSetIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (BehindLiveWindowException e) {
|
|
||||||
fatalError = e;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new periods.
|
|
||||||
for (int i = periodHolders.size(); i < manifest.getPeriodCount(); i++) {
|
|
||||||
PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i,
|
|
||||||
adaptationSetIndex);
|
|
||||||
periodHolders.put(nextPeriodHolderIndex, holder);
|
|
||||||
nextPeriodHolderIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the available range.
|
|
||||||
TimeRange newAvailableRange = getAvailableRange(getNowUnixTimeUs());
|
|
||||||
if (availableRange == null || !availableRange.equals(newAvailableRange)) {
|
|
||||||
availableRange = newAvailableRange;
|
|
||||||
notifyAvailableRangeChanged(availableRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentManifest = manifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TimeRange getAvailableRange(long nowUnixTimeUs) {
|
|
||||||
PeriodHolder firstPeriod = periodHolders.valueAt(0);
|
|
||||||
PeriodHolder lastPeriod = periodHolders.valueAt(periodHolders.size() - 1);
|
|
||||||
|
|
||||||
if (!currentManifest.dynamic || lastPeriod.isIndexExplicit()) {
|
|
||||||
return new StaticTimeRange(firstPeriod.getAvailableStartTimeUs(),
|
|
||||||
lastPeriod.getAvailableEndTimeUs());
|
|
||||||
}
|
|
||||||
|
|
||||||
long minStartPositionUs = firstPeriod.getAvailableStartTimeUs();
|
|
||||||
long maxEndPositionUs = lastPeriod.isIndexUnbounded() ? Long.MAX_VALUE
|
|
||||||
: lastPeriod.getAvailableEndTimeUs();
|
|
||||||
long elapsedRealtimeAtZeroUs = (systemClock.elapsedRealtime() * 1000)
|
|
||||||
- (nowUnixTimeUs - (currentManifest.availabilityStartTime * 1000));
|
|
||||||
long timeShiftBufferDepthUs = currentManifest.timeShiftBufferDepth == -1 ? -1
|
|
||||||
: currentManifest.timeShiftBufferDepth * 1000;
|
|
||||||
return new DynamicTimeRange(minStartPositionUs, maxEndPositionUs, elapsedRealtimeAtZeroUs,
|
|
||||||
timeShiftBufferDepthUs, systemClock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getTrackIndex(Format format) {
|
private int getTrackIndex(Format format) {
|
||||||
@ -662,14 +481,26 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
throw new IllegalStateException("Invalid format: " + format);
|
throw new IllegalStateException("Invalid format: " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyAvailableRangeChanged(final TimeRange seekRange) {
|
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
|
||||||
if (eventHandler != null && eventListener != null) {
|
DrmInitData.Mapped drmInitData = null;
|
||||||
eventHandler.post(new Runnable() {
|
for (int i = 0; i < adaptationSet.contentProtections.size(); i++) {
|
||||||
@Override
|
ContentProtection contentProtection = adaptationSet.contentProtections.get(i);
|
||||||
public void run() {
|
if (contentProtection.uuid != null && contentProtection.data != null) {
|
||||||
eventListener.onAvailableRangeChanged(eventSourceId, seekRange);
|
if (drmInitData == null) {
|
||||||
|
drmInitData = new DrmInitData.Mapped();
|
||||||
}
|
}
|
||||||
});
|
drmInitData.put(contentProtection.uuid, contentProtection.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drmInitData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) {
|
||||||
|
long durationMs = manifest.getPeriodDuration(index);
|
||||||
|
if (durationMs == -1) {
|
||||||
|
return C.UNKNOWN_TIME_US;
|
||||||
|
} else {
|
||||||
|
return durationMs * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,14 +514,10 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
public DashSegmentIndex segmentIndex;
|
public DashSegmentIndex segmentIndex;
|
||||||
public Format sampleFormat;
|
public Format sampleFormat;
|
||||||
|
|
||||||
private final long periodStartTimeUs;
|
|
||||||
|
|
||||||
private long periodDurationUs;
|
private long periodDurationUs;
|
||||||
private int segmentNumShift;
|
private int segmentNumShift;
|
||||||
|
|
||||||
public RepresentationHolder(long periodStartTimeUs, long periodDurationUs,
|
public RepresentationHolder(long periodDurationUs, Representation representation) {
|
||||||
Representation representation) {
|
|
||||||
this.periodStartTimeUs = periodStartTimeUs;
|
|
||||||
this.periodDurationUs = periodDurationUs;
|
this.periodDurationUs = periodDurationUs;
|
||||||
this.representation = representation;
|
this.representation = representation;
|
||||||
String containerMimeType = representation.format.containerMimeType;
|
String containerMimeType = representation.format.containerMimeType;
|
||||||
@ -738,13 +565,20 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSegmentNum(long positionUs) {
|
public int getFirstSegmentNum() {
|
||||||
return segmentIndex.getSegmentNum(positionUs - periodStartTimeUs, periodDurationUs)
|
return segmentIndex.getFirstSegmentNum() + segmentNumShift;
|
||||||
+ segmentNumShift;
|
}
|
||||||
|
|
||||||
|
public int getLastSegmentNum() {
|
||||||
|
int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs);
|
||||||
|
if (lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED) {
|
||||||
|
return DashSegmentIndex.INDEX_UNBOUNDED;
|
||||||
|
}
|
||||||
|
return lastSegmentNum + segmentNumShift;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSegmentStartTimeUs(int segmentNum) {
|
public long getSegmentStartTimeUs(int segmentNum) {
|
||||||
return segmentIndex.getTimeUs(segmentNum - segmentNumShift) + periodStartTimeUs;
|
return segmentIndex.getTimeUs(segmentNum - segmentNumShift);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSegmentEndTimeUs(int segmentNum) {
|
public long getSegmentEndTimeUs(int segmentNum) {
|
||||||
@ -752,18 +586,8 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
+ segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs);
|
+ segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLastSegmentNum() {
|
public int getSegmentNum(long positionUs) {
|
||||||
return segmentIndex.getLastSegmentNum(periodDurationUs);
|
return segmentIndex.getSegmentNum(positionUs, periodDurationUs) + segmentNumShift;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBeyondLastSegment(int segmentNum) {
|
|
||||||
int lastSegmentNum = getLastSegmentNum();
|
|
||||||
return lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED ? false
|
|
||||||
: segmentNum > (lastSegmentNum + segmentNumShift);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFirstAvailableSegmentNum() {
|
|
||||||
return segmentIndex.getFirstSegmentNum() + segmentNumShift;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RangedUri getSegmentUrl(int segmentNum) {
|
public RangedUri getSegmentUrl(int segmentNum) {
|
||||||
@ -781,129 +605,4 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static final class PeriodHolder {
|
|
||||||
|
|
||||||
public final int localIndex;
|
|
||||||
public final long startTimeUs;
|
|
||||||
public final RepresentationHolder[] representationHolders;
|
|
||||||
|
|
||||||
private DrmInitData drmInitData;
|
|
||||||
|
|
||||||
private boolean indexIsUnbounded;
|
|
||||||
private boolean indexIsExplicit;
|
|
||||||
private long availableStartTimeUs;
|
|
||||||
private long availableEndTimeUs;
|
|
||||||
|
|
||||||
public PeriodHolder(int localIndex, MediaPresentationDescription manifest, int manifestIndex,
|
|
||||||
int adaptationSetIndex) {
|
|
||||||
this.localIndex = localIndex;
|
|
||||||
|
|
||||||
Period period = manifest.getPeriod(manifestIndex);
|
|
||||||
long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex);
|
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
|
|
||||||
List<Representation> representations = adaptationSet.representations;
|
|
||||||
|
|
||||||
startTimeUs = period.startMs * 1000;
|
|
||||||
drmInitData = getDrmInitData(adaptationSet);
|
|
||||||
|
|
||||||
representationHolders = new RepresentationHolder[representations.size()];
|
|
||||||
for (int i = 0; i < representationHolders.length; i++) {
|
|
||||||
Representation representation = representations.get(i);
|
|
||||||
representationHolders[i] = new RepresentationHolder(startTimeUs,
|
|
||||||
periodDurationUs, representation);
|
|
||||||
}
|
|
||||||
updateRepresentationIndependentProperties(periodDurationUs,
|
|
||||||
representationHolders[0].representation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updatePeriod(MediaPresentationDescription manifest, int manifestIndex,
|
|
||||||
int adaptationSetIndex) throws BehindLiveWindowException {
|
|
||||||
Period period = manifest.getPeriod(manifestIndex);
|
|
||||||
long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex);
|
|
||||||
List<Representation> representations = period.adaptationSets
|
|
||||||
.get(adaptationSetIndex).representations;
|
|
||||||
|
|
||||||
for (int i = 0; i < representationHolders.length; i++) {
|
|
||||||
Representation representation = representations.get(i);
|
|
||||||
representationHolders[i].updateRepresentation(periodDurationUs, representation);
|
|
||||||
}
|
|
||||||
updateRepresentationIndependentProperties(periodDurationUs,
|
|
||||||
representationHolders[0].representation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAvailableStartTimeUs() {
|
|
||||||
return availableStartTimeUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAvailableEndTimeUs() {
|
|
||||||
if (isIndexUnbounded()) {
|
|
||||||
throw new IllegalStateException("Period has unbounded index");
|
|
||||||
}
|
|
||||||
return availableEndTimeUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIndexUnbounded() {
|
|
||||||
return indexIsUnbounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIndexExplicit() {
|
|
||||||
return indexIsExplicit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrmInitData getDrmInitData() {
|
|
||||||
return drmInitData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private methods.
|
|
||||||
|
|
||||||
private void updateRepresentationIndependentProperties(long periodDurationUs,
|
|
||||||
Representation arbitaryRepresentation) {
|
|
||||||
DashSegmentIndex segmentIndex = arbitaryRepresentation.getIndex();
|
|
||||||
if (segmentIndex != null) {
|
|
||||||
int firstSegmentNum = segmentIndex.getFirstSegmentNum();
|
|
||||||
int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs);
|
|
||||||
indexIsUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
|
|
||||||
indexIsExplicit = segmentIndex.isExplicit();
|
|
||||||
availableStartTimeUs = startTimeUs + segmentIndex.getTimeUs(firstSegmentNum);
|
|
||||||
if (!indexIsUnbounded) {
|
|
||||||
availableEndTimeUs = startTimeUs + segmentIndex.getTimeUs(lastSegmentNum)
|
|
||||||
+ segmentIndex.getDurationUs(lastSegmentNum, periodDurationUs);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
indexIsUnbounded = false;
|
|
||||||
indexIsExplicit = true;
|
|
||||||
availableStartTimeUs = startTimeUs;
|
|
||||||
availableEndTimeUs = startTimeUs + periodDurationUs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
|
|
||||||
if (adaptationSet.contentProtections.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
DrmInitData.Mapped drmInitData = null;
|
|
||||||
for (int i = 0; i < adaptationSet.contentProtections.size(); i++) {
|
|
||||||
ContentProtection contentProtection = adaptationSet.contentProtections.get(i);
|
|
||||||
if (contentProtection.uuid != null && contentProtection.data != null) {
|
|
||||||
if (drmInitData == null) {
|
|
||||||
drmInitData = new DrmInitData.Mapped();
|
|
||||||
}
|
|
||||||
drmInitData.put(contentProtection.uuid, contentProtection.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return drmInitData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) {
|
|
||||||
long durationMs = manifest.getPeriodDuration(index);
|
|
||||||
if (durationMs == -1) {
|
|
||||||
return C.UNKNOWN_TIME_US;
|
|
||||||
} else {
|
|
||||||
return durationMs * 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -727,7 +727,7 @@ public class HlsChunkSource {
|
|||||||
byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex,
|
byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex,
|
||||||
Uri playlistUri) {
|
Uri playlistUri) {
|
||||||
super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, format,
|
super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, format,
|
||||||
Chunk.NO_PARENT_ID, scratchSpace);
|
scratchSpace);
|
||||||
this.variantIndex = variantIndex;
|
this.variantIndex = variantIndex;
|
||||||
this.playlistParser = playlistParser;
|
this.playlistParser = playlistParser;
|
||||||
this.playlistUri = playlistUri;
|
this.playlistUri = playlistUri;
|
||||||
@ -754,7 +754,7 @@ public class HlsChunkSource {
|
|||||||
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format format,
|
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format format,
|
||||||
byte[] scratchSpace, String iv) {
|
byte[] scratchSpace, String iv) {
|
||||||
super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, format,
|
super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, format,
|
||||||
Chunk.NO_PARENT_ID, scratchSpace);
|
scratchSpace);
|
||||||
this.iv = iv;
|
this.iv = iv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
chunkIndex = streamElement.getChunkIndex(playbackPositionUs);
|
chunkIndex = streamElement.getChunkIndex(playbackPositionUs);
|
||||||
} else {
|
} else {
|
||||||
chunkIndex = previous.chunkIndex + 1 - currentManifestChunkOffset;
|
chunkIndex = previous.getNextChunkIndex() - currentManifestChunkOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (live && chunkIndex < 0) {
|
if (live && chunkIndex < 0) {
|
||||||
@ -404,7 +404,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
long sampleOffsetUs = chunkStartTimeUs;
|
long sampleOffsetUs = chunkStartTimeUs;
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs,
|
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs,
|
||||||
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, sampleFormat, drmInitData,
|
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, sampleFormat, drmInitData,
|
||||||
true, Chunk.NO_PARENT_ID);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getProtectionElementKeyId(byte[] initData) {
|
private static byte[] getProtectionElementKeyId(byte[] initData) {
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
package com.google.android.exoplayer.upstream;
|
package com.google.android.exoplayer.upstream;
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.Clock;
|
|
||||||
import com.google.android.exoplayer.util.SlidingPercentile;
|
import com.google.android.exoplayer.util.SlidingPercentile;
|
||||||
import com.google.android.exoplayer.util.SystemClock;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Counts transferred bytes while transfers are open and creates a bandwidth sample and updated
|
* Counts transferred bytes while transfers are open and creates a bandwidth sample and updated
|
||||||
@ -35,7 +34,6 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
|||||||
|
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
private final Clock clock;
|
|
||||||
private final SlidingPercentile slidingPercentile;
|
private final SlidingPercentile slidingPercentile;
|
||||||
|
|
||||||
private int streamCount;
|
private int streamCount;
|
||||||
@ -51,22 +49,12 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) {
|
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) {
|
||||||
this(eventHandler, eventListener, new SystemClock());
|
this(eventHandler, eventListener, DEFAULT_MAX_WEIGHT);
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, Clock clock) {
|
|
||||||
this(eventHandler, eventListener, clock, DEFAULT_MAX_WEIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) {
|
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) {
|
||||||
this(eventHandler, eventListener, new SystemClock(), maxWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, Clock clock,
|
|
||||||
int maxWeight) {
|
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
this.clock = clock;
|
|
||||||
this.slidingPercentile = new SlidingPercentile(maxWeight);
|
this.slidingPercentile = new SlidingPercentile(maxWeight);
|
||||||
bitrateEstimate = NO_ESTIMATE;
|
bitrateEstimate = NO_ESTIMATE;
|
||||||
}
|
}
|
||||||
@ -79,7 +67,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void onTransferStart() {
|
public synchronized void onTransferStart() {
|
||||||
if (streamCount == 0) {
|
if (streamCount == 0) {
|
||||||
sampleStartTimeMs = clock.elapsedRealtime();
|
sampleStartTimeMs = SystemClock.elapsedRealtime();
|
||||||
}
|
}
|
||||||
streamCount++;
|
streamCount++;
|
||||||
}
|
}
|
||||||
@ -92,7 +80,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void onTransferEnd() {
|
public synchronized void onTransferEnd() {
|
||||||
Assertions.checkState(streamCount > 0);
|
Assertions.checkState(streamCount > 0);
|
||||||
long nowMs = clock.elapsedRealtime();
|
long nowMs = SystemClock.elapsedRealtime();
|
||||||
int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs);
|
int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs);
|
||||||
totalElapsedTimeMs += sampleElapsedTimeMs;
|
totalElapsedTimeMs += sampleElapsedTimeMs;
|
||||||
totalBytesTransferred += sampleBytesTransferred;
|
totalBytesTransferred += sampleBytesTransferred;
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 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.exoplayer.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The standard implementation of {@link Clock}.
|
|
||||||
*/
|
|
||||||
public final class SystemClock implements Clock {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long elapsedRealtime() {
|
|
||||||
return android.os.SystemClock.elapsedRealtime();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user