mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
ExoPlayer V2 Refactor - Step 7 (partial)
This change removes the need for SourceBuilders to load their manifests before building their sources. This is done by pushing initial manifest loads into the ChunkSource classes. This simplifies the SourceBuilders a lot, and also DemoPlayer. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=113259259
This commit is contained in:
parent
be47148940
commit
ce324f1ca9
13 changed files with 217 additions and 797 deletions
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer.demo.player;
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
import com.google.android.exoplayer.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer.LoadControl;
|
import com.google.android.exoplayer.LoadControl;
|
||||||
import com.google.android.exoplayer.MultiSampleSource;
|
import com.google.android.exoplayer.MultiSampleSource;
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||||
|
|
@ -25,27 +26,22 @@ import com.google.android.exoplayer.dash.DashChunkSource;
|
||||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
||||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
||||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
||||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElement;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback;
|
|
||||||
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
||||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||||
import com.google.android.exoplayer.upstream.UriDataSource;
|
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SourceBuilder} for DASH.
|
* A {@link SourceBuilder} for DASH.
|
||||||
*/
|
*/
|
||||||
|
// TODO[REFACTOR]: Bring back DRM 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 String TAG = "DashSourceBuilder";
|
||||||
|
|
@ -61,8 +57,6 @@ public class DashSourceBuilder implements SourceBuilder {
|
||||||
private final String url;
|
private final String url;
|
||||||
private final MediaDrmCallback drmCallback;
|
private final MediaDrmCallback drmCallback;
|
||||||
|
|
||||||
private AsyncRendererBuilder currentAsyncBuilder;
|
|
||||||
|
|
||||||
public DashSourceBuilder(Context context, String userAgent, String url,
|
public DashSourceBuilder(Context context, String userAgent, String url,
|
||||||
MediaDrmCallback drmCallback) {
|
MediaDrmCallback drmCallback) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
@ -72,172 +66,41 @@ public class DashSourceBuilder implements SourceBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void buildRenderers(DemoPlayer player) {
|
public SampleSource buildRenderers(DemoPlayer player) {
|
||||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player);
|
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
||||||
currentAsyncBuilder.init();
|
DefaultUriDataSource manifestDataSource = new DefaultUriDataSource(context, userAgent);
|
||||||
}
|
ManifestFetcher<MediaPresentationDescription> manifestFetcher = new ManifestFetcher<>(url,
|
||||||
|
manifestDataSource, parser);
|
||||||
|
|
||||||
@Override
|
Handler mainHandler = player.getMainHandler();
|
||||||
public void cancel() {
|
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
|
||||||
if (currentAsyncBuilder != null) {
|
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||||
currentAsyncBuilder.cancel();
|
|
||||||
currentAsyncBuilder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AsyncRendererBuilder
|
// Build the video renderer.
|
||||||
implements ManifestFetcher.ManifestCallback<MediaPresentationDescription>, UtcTimingCallback {
|
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
|
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO,
|
||||||
|
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS,
|
||||||
|
0, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||||
|
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||||
|
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||||
|
|
||||||
private final Context context;
|
// Build the audio renderer.
|
||||||
private final String userAgent;
|
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
private final MediaDrmCallback drmCallback;
|
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO,
|
||||||
private final DemoPlayer player;
|
audioDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_AUDIO);
|
||||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||||
private final UriDataSource manifestDataSource;
|
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||||
|
DemoPlayer.TYPE_AUDIO);
|
||||||
|
|
||||||
private boolean canceled;
|
// Build the text renderer.
|
||||||
private MediaPresentationDescription manifest;
|
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
private long elapsedRealtimeOffset;
|
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT,
|
||||||
|
textDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_TEXT);
|
||||||
|
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||||
|
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||||
|
DemoPlayer.TYPE_TEXT);
|
||||||
|
|
||||||
public AsyncRendererBuilder(Context context, String userAgent, String url,
|
return new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource);
|
||||||
MediaDrmCallback drmCallback, DemoPlayer player) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.drmCallback = drmCallback;
|
|
||||||
this.player = player;
|
|
||||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
|
||||||
manifestDataSource = new DefaultUriDataSource(context, userAgent);
|
|
||||||
manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
canceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifest(MediaPresentationDescription manifest) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.manifest = manifest;
|
|
||||||
if (manifest.dynamic && manifest.utcTiming != null) {
|
|
||||||
UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming,
|
|
||||||
manifestFetcher.getManifestLoadCompleteTimestamp(), this);
|
|
||||||
} else {
|
|
||||||
buildRenderers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifestError(IOException e) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.onSourceBuilderError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
|
||||||
buildRenderers();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimestampError(UtcTimingElement utcTiming, IOException e) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e);
|
|
||||||
// Be optimistic and continue in the hope that the device clock is correct.
|
|
||||||
buildRenderers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildRenderers() {
|
|
||||||
// TODO[REFACTOR]: Bring back DRM support.
|
|
||||||
/*
|
|
||||||
Period period = manifest.getPeriod(0);
|
|
||||||
boolean hasContentProtection = false;
|
|
||||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
|
||||||
if (adaptationSet.type != AdaptationSet.TYPE_UNKNOWN) {
|
|
||||||
hasContentProtection |= adaptationSet.hasContentProtection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check drm support if necessary.
|
|
||||||
boolean filterHdContent = false;
|
|
||||||
StreamingDrmSessionManager drmSessionManager = null;
|
|
||||||
if (hasContentProtection) {
|
|
||||||
if (Util.SDK_INT < 18) {
|
|
||||||
player.onSourceBuilderError(
|
|
||||||
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance(
|
|
||||||
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
|
|
||||||
filterHdContent = getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1;
|
|
||||||
} catch (UnsupportedDrmException e) {
|
|
||||||
player.onSourceBuilderError(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Handler mainHandler = player.getMainHandler();
|
|
||||||
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
|
||||||
|
|
||||||
// Build the video renderer.
|
|
||||||
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO,
|
|
||||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS,
|
|
||||||
elapsedRealtimeOffset, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
|
||||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
|
||||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
DemoPlayer.TYPE_VIDEO);
|
|
||||||
|
|
||||||
// Build the audio renderer.
|
|
||||||
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO,
|
|
||||||
audioDataSource, null, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, mainHandler, player,
|
|
||||||
DemoPlayer.TYPE_AUDIO);
|
|
||||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
|
||||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
DemoPlayer.TYPE_AUDIO);
|
|
||||||
|
|
||||||
// Build the text renderer.
|
|
||||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT,
|
|
||||||
textDataSource, null, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, mainHandler, player,
|
|
||||||
DemoPlayer.TYPE_TEXT);
|
|
||||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
|
||||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
DemoPlayer.TYPE_TEXT);
|
|
||||||
|
|
||||||
// Invoke the callback.
|
|
||||||
player.onSource(
|
|
||||||
new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) {
|
|
||||||
String securityLevelProperty = sessionManager.getPropertyString("securityLevel");
|
|
||||||
return securityLevelProperty.equals("L1") ? SECURITY_LEVEL_1 : securityLevelProperty
|
|
||||||
.equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,24 +69,16 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
|
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds renderers for the player.
|
* Builds a source to play.
|
||||||
*/
|
*/
|
||||||
public interface SourceBuilder {
|
public interface SourceBuilder {
|
||||||
/**
|
/**
|
||||||
* Builds renderers for playback.
|
* Builds a source to play.
|
||||||
*
|
*
|
||||||
* @param player The player for which renderers are being built. {@link DemoPlayer#onSource}
|
* @param player The player for which renderers are being built.
|
||||||
* should be invoked once the renderers have been built. If building fails,
|
* @return SampleSource The source to play.
|
||||||
* {@link DemoPlayer#onSourceBuilderError} should be invoked.
|
|
||||||
*/
|
*/
|
||||||
void buildRenderers(DemoPlayer player);
|
SampleSource buildRenderers(DemoPlayer player);
|
||||||
/**
|
|
||||||
* Cancels the current build operation, if there is one. Else does nothing.
|
|
||||||
* <p>
|
|
||||||
* A canceled build operation must not invoke {@link DemoPlayer#onSource} or
|
|
||||||
* {@link DemoPlayer#onSourceBuilderError} on the player, which may have been released.
|
|
||||||
*/
|
|
||||||
void cancel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -164,10 +156,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
public static final int TYPE_TEXT = 2;
|
public static final int TYPE_TEXT = 2;
|
||||||
public static final int TYPE_METADATA = 3;
|
public static final int TYPE_METADATA = 3;
|
||||||
|
|
||||||
private static final int SOURCE_BUILDING_STATE_IDLE = 1;
|
|
||||||
private static final int SOURCE_BUILDING_STATE_BUILDING = 2;
|
|
||||||
private static final int SOURCE_BUILDING_STATE_BUILT = 3;
|
|
||||||
|
|
||||||
private final ExoPlayer player;
|
private final ExoPlayer player;
|
||||||
private final SourceBuilder sourceBuilder;
|
private final SourceBuilder sourceBuilder;
|
||||||
private final BandwidthMeter bandwidthMeter;
|
private final BandwidthMeter bandwidthMeter;
|
||||||
|
|
@ -176,10 +164,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
private final Handler mainHandler;
|
private final Handler mainHandler;
|
||||||
private final CopyOnWriteArrayList<Listener> listeners;
|
private final CopyOnWriteArrayList<Listener> listeners;
|
||||||
|
|
||||||
private int sourceBuildingState;
|
|
||||||
private int lastReportedPlaybackState;
|
|
||||||
private boolean lastReportedPlayWhenReady;
|
|
||||||
|
|
||||||
private Surface surface;
|
private Surface surface;
|
||||||
private Format videoFormat;
|
private Format videoFormat;
|
||||||
private int videoTrackToRestore;
|
private int videoTrackToRestore;
|
||||||
|
|
@ -215,8 +199,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
playerControl = new PlayerControl(player);
|
playerControl = new PlayerControl(player);
|
||||||
|
|
||||||
// Set initial state, with the text renderer initially disabled.
|
// Set initial state, with the text renderer initially disabled.
|
||||||
lastReportedPlaybackState = STATE_IDLE;
|
|
||||||
sourceBuildingState = SOURCE_BUILDING_STATE_IDLE;
|
|
||||||
player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED);
|
player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,39 +282,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepare() {
|
public void prepare() {
|
||||||
if (sourceBuildingState == SOURCE_BUILDING_STATE_BUILT) {
|
player.prepare(sourceBuilder.buildRenderers(this));
|
||||||
player.stop();
|
|
||||||
}
|
|
||||||
sourceBuilder.cancel();
|
|
||||||
sourceBuildingState = SOURCE_BUILDING_STATE_BUILDING;
|
|
||||||
maybeReportPlayerState();
|
|
||||||
sourceBuilder.buildRenderers(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked with the results from a {@link SourceBuilder}.
|
|
||||||
*
|
|
||||||
* @param source The {@link SampleSource} to play.
|
|
||||||
*/
|
|
||||||
/* package */ void onSource(SampleSource source) {
|
|
||||||
player.prepare(source);
|
|
||||||
sourceBuildingState = SOURCE_BUILDING_STATE_BUILT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked if a {@link SourceBuilder} encounters an error.
|
|
||||||
*
|
|
||||||
* @param e Describes the error.
|
|
||||||
*/
|
|
||||||
/* package */ void onSourceBuilderError(Exception e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onRendererInitializationError(e);
|
|
||||||
}
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onError(e);
|
|
||||||
}
|
|
||||||
sourceBuildingState = SOURCE_BUILDING_STATE_IDLE;
|
|
||||||
maybeReportPlayerState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayWhenReady(boolean playWhenReady) {
|
public void setPlayWhenReady(boolean playWhenReady) {
|
||||||
|
|
@ -344,23 +294,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
sourceBuilder.cancel();
|
|
||||||
sourceBuildingState = SOURCE_BUILDING_STATE_IDLE;
|
|
||||||
surface = null;
|
surface = null;
|
||||||
player.release();
|
player.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPlaybackState() {
|
public int getPlaybackState() {
|
||||||
if (sourceBuildingState == SOURCE_BUILDING_STATE_BUILDING) {
|
return player.getPlaybackState();
|
||||||
return STATE_PREPARING;
|
|
||||||
}
|
|
||||||
int playerState = player.getPlaybackState();
|
|
||||||
if (sourceBuildingState == SOURCE_BUILDING_STATE_BUILT && playerState == STATE_IDLE) {
|
|
||||||
// This is an edge case where the renderers are built, but are still being passed to the
|
|
||||||
// player's playback thread.
|
|
||||||
return STATE_PREPARING;
|
|
||||||
}
|
|
||||||
return playerState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -401,12 +340,13 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerStateChanged(boolean playWhenReady, int state) {
|
public void onPlayerStateChanged(boolean playWhenReady, int state) {
|
||||||
maybeReportPlayerState();
|
for (Listener listener : listeners) {
|
||||||
|
listener.onStateChanged(playWhenReady, state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException exception) {
|
public void onPlayerError(ExoPlaybackException exception) {
|
||||||
sourceBuildingState = SOURCE_BUILDING_STATE_IDLE;
|
|
||||||
for (Listener listener : listeners) {
|
for (Listener listener : listeners) {
|
||||||
listener.onError(exception);
|
listener.onError(exception);
|
||||||
}
|
}
|
||||||
|
|
@ -569,18 +509,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeReportPlayerState() {
|
|
||||||
boolean playWhenReady = player.getPlayWhenReady();
|
|
||||||
int playbackState = getPlaybackState();
|
|
||||||
if (lastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) {
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onStateChanged(playWhenReady, playbackState);
|
|
||||||
}
|
|
||||||
lastReportedPlayWhenReady = playWhenReady;
|
|
||||||
lastReportedPlaybackState = playbackState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pushSurface(boolean blockForSurfacePush) {
|
private void pushSurface(boolean blockForSurfacePush) {
|
||||||
if (blockForSurfacePush) {
|
if (blockForSurfacePush) {
|
||||||
player.blockingSendMessage(
|
player.blockingSendMessage(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.demo.player;
|
package com.google.android.exoplayer.demo.player;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
||||||
|
|
@ -45,18 +46,12 @@ public class ExtractorSourceBuilder implements SourceBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void buildRenderers(DemoPlayer player) {
|
public SampleSource buildRenderers(DemoPlayer player) {
|
||||||
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
|
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
|
||||||
DataSource dataSource = new DefaultUriDataSource(context, player.getBandwidthMeter(),
|
DataSource dataSource = new DefaultUriDataSource(context, player.getBandwidthMeter(),
|
||||||
userAgent);
|
userAgent);
|
||||||
ExtractorSampleSource source = new ExtractorSampleSource(uri, dataSource, allocator,
|
return new ExtractorSampleSource(uri, dataSource, allocator,
|
||||||
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
|
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
|
||||||
player.onSource(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer.demo.player;
|
||||||
|
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
import com.google.android.exoplayer.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer.LoadControl;
|
import com.google.android.exoplayer.LoadControl;
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
||||||
import com.google.android.exoplayer.hls.HlsChunkSource;
|
import com.google.android.exoplayer.hls.HlsChunkSource;
|
||||||
import com.google.android.exoplayer.hls.HlsPlaylist;
|
import com.google.android.exoplayer.hls.HlsPlaylist;
|
||||||
|
|
@ -28,16 +29,14 @@ import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SourceBuilder} for HLS.
|
* A {@link SourceBuilder} for HLS.
|
||||||
*/
|
*/
|
||||||
|
// TODO[REFACTOR]: Bring back caption support.
|
||||||
public class HlsSourceBuilder implements SourceBuilder {
|
public class HlsSourceBuilder implements SourceBuilder {
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||||
|
|
@ -47,8 +46,6 @@ public class HlsSourceBuilder implements SourceBuilder {
|
||||||
private final String userAgent;
|
private final String userAgent;
|
||||||
private final String url;
|
private final String url;
|
||||||
|
|
||||||
private AsyncRendererBuilder currentAsyncBuilder;
|
|
||||||
|
|
||||||
public HlsSourceBuilder(Context context, String userAgent, String url) {
|
public HlsSourceBuilder(Context context, String userAgent, String url) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
|
|
@ -56,98 +53,22 @@ public class HlsSourceBuilder implements SourceBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void buildRenderers(DemoPlayer player) {
|
public SampleSource buildRenderers(DemoPlayer player) {
|
||||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, player);
|
HlsPlaylistParser parser = new HlsPlaylistParser();
|
||||||
currentAsyncBuilder.init();
|
DefaultUriDataSource manifestDataSource = new DefaultUriDataSource(context, userAgent);
|
||||||
}
|
ManifestFetcher<HlsPlaylist> manifestFetcher = new ManifestFetcher<>(url,
|
||||||
|
manifestDataSource, parser);
|
||||||
|
|
||||||
@Override
|
Handler mainHandler = player.getMainHandler();
|
||||||
public void cancel() {
|
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
|
||||||
if (currentAsyncBuilder != null) {
|
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||||
currentAsyncBuilder.cancel();
|
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
|
||||||
currentAsyncBuilder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AsyncRendererBuilder implements ManifestCallback<HlsPlaylist> {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final String url;
|
|
||||||
private final DemoPlayer player;
|
|
||||||
private final ManifestFetcher<HlsPlaylist> playlistFetcher;
|
|
||||||
|
|
||||||
private boolean canceled;
|
|
||||||
|
|
||||||
public AsyncRendererBuilder(Context context, String userAgent, String url, DemoPlayer player) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.url = url;
|
|
||||||
this.player = player;
|
|
||||||
HlsPlaylistParser parser = new HlsPlaylistParser();
|
|
||||||
playlistFetcher = new ManifestFetcher<>(url, new DefaultUriDataSource(context, userAgent),
|
|
||||||
parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
canceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifestError(IOException e) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.onSourceBuilderError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifest(HlsPlaylist manifest) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Handler mainHandler = player.getMainHandler();
|
|
||||||
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
|
||||||
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
|
|
||||||
|
|
||||||
// Build the video/audio/metadata renderers.
|
|
||||||
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
HlsChunkSource chunkSource = new HlsChunkSource(HlsChunkSource.TYPE_DEFAULT, dataSource, url,
|
|
||||||
manifest, bandwidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
|
||||||
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
|
|
||||||
MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
|
||||||
|
|
||||||
// TODO[REFACTOR]: Bring back caption support.
|
|
||||||
// Build the text renderer, preferring Webvtt where available.
|
|
||||||
/*
|
|
||||||
boolean preferWebvtt = false;
|
|
||||||
if (manifest instanceof HlsMasterPlaylist) {
|
|
||||||
preferWebvtt = !((HlsMasterPlaylist) manifest).subtitles.isEmpty();
|
|
||||||
}
|
|
||||||
TrackRenderer textRenderer;
|
|
||||||
if (preferWebvtt) {
|
|
||||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
HlsChunkSource textChunkSource = new HlsChunkSource(false, textDataSource,
|
|
||||||
url, manifest, DefaultHlsTrackSelector.newVttInstance(), bandwidthMeter,
|
|
||||||
timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
|
||||||
HlsSampleSource textSampleSource = new HlsSampleSource(textChunkSource, loadControl,
|
|
||||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT);
|
|
||||||
textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper());
|
|
||||||
} else {
|
|
||||||
textRenderer = new Eia608TrackRenderer(sampleSource, player, mainHandler.getLooper());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
player.onSource(sampleSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
|
HlsChunkSource chunkSource = new HlsChunkSource(manifestFetcher, HlsChunkSource.TYPE_DEFAULT,
|
||||||
|
dataSource, bandwidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
||||||
|
return new HlsSampleSource(chunkSource, loadControl,
|
||||||
|
MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer.demo.player;
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
import com.google.android.exoplayer.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer.LoadControl;
|
import com.google.android.exoplayer.LoadControl;
|
||||||
import com.google.android.exoplayer.MultiSampleSource;
|
import com.google.android.exoplayer.MultiSampleSource;
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||||
|
|
@ -37,11 +38,10 @@ import com.google.android.exoplayer.util.Util;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SourceBuilder} for SmoothStreaming.
|
* A {@link SourceBuilder} for SmoothStreaming.
|
||||||
*/
|
*/
|
||||||
|
// TODO[REFACTOR]: Bring back DRM support.
|
||||||
public class SmoothStreamingSourceBuilder implements SourceBuilder {
|
public class SmoothStreamingSourceBuilder implements SourceBuilder {
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||||
|
|
@ -55,8 +55,6 @@ public class SmoothStreamingSourceBuilder implements SourceBuilder {
|
||||||
private final String url;
|
private final String url;
|
||||||
private final MediaDrmCallback drmCallback;
|
private final MediaDrmCallback drmCallback;
|
||||||
|
|
||||||
private AsyncRendererBuilder currentAsyncBuilder;
|
|
||||||
|
|
||||||
public SmoothStreamingSourceBuilder(Context context, String userAgent, String url,
|
public SmoothStreamingSourceBuilder(Context context, String userAgent, String url,
|
||||||
MediaDrmCallback drmCallback) {
|
MediaDrmCallback drmCallback) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
@ -66,118 +64,40 @@ public class SmoothStreamingSourceBuilder implements SourceBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void buildRenderers(DemoPlayer player) {
|
public SampleSource buildRenderers(DemoPlayer player) {
|
||||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player);
|
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
||||||
currentAsyncBuilder.init();
|
ManifestFetcher<SmoothStreamingManifest> manifestFetcher = new ManifestFetcher<>(url,
|
||||||
}
|
new DefaultHttpDataSource(userAgent, null), parser);
|
||||||
|
Handler mainHandler = player.getMainHandler();
|
||||||
|
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
|
||||||
|
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||||
|
|
||||||
@Override
|
// Build the video renderer.
|
||||||
public void cancel() {
|
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
if (currentAsyncBuilder != null) {
|
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||||
currentAsyncBuilder.cancel();
|
SmoothStreamingManifest.StreamElement.TYPE_VIDEO,
|
||||||
currentAsyncBuilder = null;
|
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
|
||||||
}
|
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||||
}
|
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||||
|
DemoPlayer.TYPE_VIDEO);
|
||||||
|
|
||||||
private static final class AsyncRendererBuilder
|
// Build the audio renderer.
|
||||||
implements ManifestFetcher.ManifestCallback<SmoothStreamingManifest> {
|
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
|
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||||
|
SmoothStreamingManifest.StreamElement.TYPE_AUDIO, audioDataSource, null,
|
||||||
|
LIVE_EDGE_LATENCY_MS);
|
||||||
|
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||||
|
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_AUDIO);
|
||||||
|
|
||||||
private final Context context;
|
// Build the text renderer.
|
||||||
private final String userAgent;
|
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
private final MediaDrmCallback drmCallback;
|
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||||
private final DemoPlayer player;
|
SmoothStreamingManifest.StreamElement.TYPE_TEXT, textDataSource, null,
|
||||||
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
LIVE_EDGE_LATENCY_MS);
|
||||||
|
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||||
private boolean canceled;
|
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT);
|
||||||
|
|
||||||
public AsyncRendererBuilder(Context context, String userAgent, String url,
|
|
||||||
MediaDrmCallback drmCallback, DemoPlayer player) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.drmCallback = drmCallback;
|
|
||||||
this.player = player;
|
|
||||||
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
|
||||||
manifestFetcher = new ManifestFetcher<>(url, new DefaultHttpDataSource(userAgent, null),
|
|
||||||
parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
canceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifestError(IOException exception) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.onSourceBuilderError(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifest(SmoothStreamingManifest manifest) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO[REFACTOR]: Bring back DRM support.
|
|
||||||
/*
|
|
||||||
// Check drm support if necessary.
|
|
||||||
DrmSessionManager drmSessionManager = null;
|
|
||||||
if (manifest.protectionElement != null) {
|
|
||||||
if (Util.SDK_INT < 18) {
|
|
||||||
player.onSourceBuilderError(
|
|
||||||
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid,
|
|
||||||
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
|
|
||||||
} catch (UnsupportedDrmException e) {
|
|
||||||
player.onSourceBuilderError(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Handler mainHandler = player.getMainHandler();
|
|
||||||
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
|
||||||
|
|
||||||
// Build the video renderer.
|
|
||||||
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
|
||||||
SmoothStreamingManifest.StreamElement.TYPE_VIDEO,
|
|
||||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
|
|
||||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
|
||||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
DemoPlayer.TYPE_VIDEO);
|
|
||||||
|
|
||||||
// Build the audio renderer.
|
|
||||||
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
|
||||||
SmoothStreamingManifest.StreamElement.TYPE_AUDIO, audioDataSource, null,
|
|
||||||
LIVE_EDGE_LATENCY_MS);
|
|
||||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
|
||||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_AUDIO);
|
|
||||||
|
|
||||||
// Build the text renderer.
|
|
||||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
|
||||||
SmoothStreamingManifest.StreamElement.TYPE_TEXT, textDataSource, null,
|
|
||||||
LIVE_EDGE_LATENCY_MS);
|
|
||||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
|
||||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT);
|
|
||||||
|
|
||||||
// Invoke the callback.
|
|
||||||
player.onSource(
|
|
||||||
new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,37 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.dash;
|
package com.google.android.exoplayer.dash;
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.TimeRange;
|
|
||||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
|
||||||
import com.google.android.exoplayer.chunk.Format;
|
import com.google.android.exoplayer.chunk.Format;
|
||||||
import com.google.android.exoplayer.chunk.InitializationChunk;
|
|
||||||
import com.google.android.exoplayer.chunk.MediaChunk;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.Period;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.RangedUri;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.UrlTemplate;
|
|
||||||
import com.google.android.exoplayer.testutil.TestUtil;
|
import com.google.android.exoplayer.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer.util.FakeClock;
|
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DashChunkSource}.
|
* Tests {@link DashChunkSource}.
|
||||||
*/
|
*/
|
||||||
|
|
@ -80,6 +54,8 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
|
||||||
TestUtil.setUpMockito(this);
|
TestUtil.setUpMockito(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO[REFACTOR]: Restore tests.
|
||||||
|
/*
|
||||||
public void testGetAvailableRangeOnVod() {
|
public void testGetAvailableRangeOnVod() {
|
||||||
DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(), AdaptationSet.TYPE_VIDEO, null,
|
DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(), AdaptationSet.TYPE_VIDEO, null,
|
||||||
null);
|
null);
|
||||||
|
|
@ -490,5 +466,6 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
|
||||||
chunkSource.getChunkOperation(queue, seekPositionMs * 1000, out);
|
chunkSource.getChunkOperation(queue, seekPositionMs * 1000, out);
|
||||||
assertNull(out.chunk);
|
assertNull(out.chunk);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!chunkSource.prepare()) {
|
if (!chunkSource.prepare()) {
|
||||||
maybeThrowError();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
durationUs = C.UNKNOWN_TIME_US;
|
durationUs = C.UNKNOWN_TIME_US;
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@ import java.util.List;
|
||||||
public interface ChunkSource {
|
public interface ChunkSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the source is currently having difficulty preparing or providing chunks, then this method
|
* If the source is currently having difficulty providing chunks, then this method throws the
|
||||||
* throws the underlying error. Otherwise does nothing.
|
* underlying error. Otherwise does nothing.
|
||||||
|
* <p>
|
||||||
|
* This method should only be called after the source has been prepared.
|
||||||
*
|
*
|
||||||
* @throws IOException The underlying error.
|
* @throws IOException The underlying error.
|
||||||
*/
|
*/
|
||||||
|
|
@ -44,8 +46,9 @@ public interface ChunkSource {
|
||||||
* The method can be called repeatedly until the return value indicates success.
|
* The method can be called repeatedly until the return value indicates success.
|
||||||
*
|
*
|
||||||
* @return True if the source was prepared, false otherwise.
|
* @return True if the source was prepared, false otherwise.
|
||||||
|
* @throws IOException If an error occurs preparing the source.
|
||||||
*/
|
*/
|
||||||
boolean prepare();
|
boolean prepare() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the group of tracks provided by the source.
|
* Gets the group of tracks provided by the source.
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ import android.util.SparseArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -118,10 +117,10 @@ public class DashChunkSource implements ChunkSource {
|
||||||
private final long liveEdgeLatencyUs;
|
private final long liveEdgeLatencyUs;
|
||||||
private final long elapsedRealtimeOffsetUs;
|
private final long elapsedRealtimeOffsetUs;
|
||||||
private final long[] availableRangeValues;
|
private final long[] availableRangeValues;
|
||||||
private final boolean live;
|
|
||||||
private final int eventSourceId;
|
private final int eventSourceId;
|
||||||
|
|
||||||
private boolean prepareCalled;
|
private boolean manifestFetcherEnabled;
|
||||||
|
private boolean live;
|
||||||
private MediaPresentationDescription currentManifest;
|
private MediaPresentationDescription currentManifest;
|
||||||
private MediaPresentationDescription processedManifest;
|
private MediaPresentationDescription processedManifest;
|
||||||
private int nextPeriodHolderIndex;
|
private int nextPeriodHolderIndex;
|
||||||
|
|
@ -141,63 +140,7 @@ public class DashChunkSource implements ChunkSource {
|
||||||
private int adaptiveMaxHeight;
|
private int adaptiveMaxHeight;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lightweight constructor to use for fixed duration content.
|
* @param manifestFetcher A fetcher for the manifest.
|
||||||
*
|
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
|
||||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
|
||||||
* @param durationMs The duration of the content.
|
|
||||||
* @param adaptationSetType The type of the adaptation set to which the representations belong.
|
|
||||||
* One of {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and
|
|
||||||
* {@link AdaptationSet#TYPE_TEXT}.
|
|
||||||
* @param representations The representations to be considered by the source.
|
|
||||||
*/
|
|
||||||
public DashChunkSource(DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
|
||||||
long durationMs, int adaptationSetType, Representation... representations) {
|
|
||||||
this(dataSource, adaptiveFormatEvaluator, durationMs, adaptationSetType,
|
|
||||||
Arrays.asList(representations));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lightweight constructor to use for fixed duration content.
|
|
||||||
*
|
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
|
||||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
|
||||||
* @param durationMs The duration of the content.
|
|
||||||
* @param adaptationSetType The type of the adaptation set to which the representations belong.
|
|
||||||
* One of {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and
|
|
||||||
* {@link AdaptationSet#TYPE_TEXT}.
|
|
||||||
* @param representations The representations to be considered by the source.
|
|
||||||
*/
|
|
||||||
public DashChunkSource(DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
|
||||||
long durationMs, int adaptationSetType, List<Representation> representations) {
|
|
||||||
this(buildManifest(durationMs, adaptationSetType, representations), adaptationSetType,
|
|
||||||
dataSource, adaptiveFormatEvaluator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor to use for fixed duration content.
|
|
||||||
*
|
|
||||||
* @param manifest 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.
|
|
||||||
*/
|
|
||||||
public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetType,
|
|
||||||
DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) {
|
|
||||||
this(null, manifest, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(),
|
|
||||||
0, 0, false, null, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor to use for live streaming.
|
|
||||||
* <p>
|
|
||||||
* May also be used for fixed duration content, in which case the call is equivalent to calling
|
|
||||||
* the other constructor, passing {@code manifestFetcher.getManifest()} is the first argument.
|
|
||||||
*
|
|
||||||
* @param manifestFetcher A fetcher for the manifest, which must have already successfully
|
|
||||||
* completed an initial load.
|
|
||||||
* @param adaptationSetType The type of the adaptation set exposed by this source. One of
|
* @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_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and
|
||||||
* {@link AdaptationSet#TYPE_TEXT}.
|
* {@link AdaptationSet#TYPE_TEXT}.
|
||||||
|
|
@ -220,16 +163,15 @@ public class DashChunkSource implements ChunkSource {
|
||||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||||
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, Handler eventHandler,
|
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, Handler eventHandler,
|
||||||
EventListener eventListener, int eventSourceId) {
|
EventListener eventListener, int eventSourceId) {
|
||||||
this(manifestFetcher, manifestFetcher.getManifest(), adaptationSetType,
|
this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(),
|
||||||
dataSource, adaptiveFormatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000,
|
liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, true, eventHandler, eventListener,
|
||||||
elapsedRealtimeOffsetMs * 1000, true, eventHandler, eventListener, eventSourceId);
|
eventSourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor to use for live DVR streaming.
|
* Constructor to use for live DVR streaming.
|
||||||
*
|
*
|
||||||
* @param manifestFetcher A fetcher for the manifest, which must have already successfully
|
* @param manifestFetcher A fetcher for the manifest.
|
||||||
* completed an initial load.
|
|
||||||
* @param adaptationSetType The type of the adaptation set exposed by this source. One of
|
* @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_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and
|
||||||
* {@link AdaptationSet#TYPE_TEXT}.
|
* {@link AdaptationSet#TYPE_TEXT}.
|
||||||
|
|
@ -254,20 +196,17 @@ public class DashChunkSource implements ChunkSource {
|
||||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||||
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, boolean startAtLiveEdge,
|
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, boolean startAtLiveEdge,
|
||||||
Handler eventHandler, EventListener eventListener, int eventSourceId) {
|
Handler eventHandler, EventListener eventListener, int eventSourceId) {
|
||||||
this(manifestFetcher, manifestFetcher.getManifest(), adaptationSetType,
|
this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(),
|
||||||
dataSource, adaptiveFormatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000,
|
liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, startAtLiveEdge, eventHandler,
|
||||||
elapsedRealtimeOffsetMs * 1000, startAtLiveEdge, eventHandler, eventListener,
|
eventListener, eventSourceId);
|
||||||
eventSourceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
/* package */ DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
||||||
MediaPresentationDescription initialManifest, int adaptationSetType,
|
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||||
DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
|
||||||
Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs,
|
Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs,
|
||||||
boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener,
|
boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener,
|
||||||
int eventSourceId) {
|
int eventSourceId) {
|
||||||
this.manifestFetcher = manifestFetcher;
|
this.manifestFetcher = manifestFetcher;
|
||||||
this.currentManifest = initialManifest;
|
|
||||||
this.adaptationSetType = adaptationSetType;
|
this.adaptationSetType = adaptationSetType;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||||
|
|
@ -281,7 +220,6 @@ public class DashChunkSource implements ChunkSource {
|
||||||
this.evaluation = new Evaluation();
|
this.evaluation = new Evaluation();
|
||||||
this.availableRangeValues = new long[2];
|
this.availableRangeValues = new long[2];
|
||||||
periodHolders = new SparseArray<>();
|
periodHolders = new SparseArray<>();
|
||||||
live = initialManifest.dynamic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChunkSource implementation.
|
// ChunkSource implementation.
|
||||||
|
|
@ -290,16 +228,28 @@ public class DashChunkSource implements ChunkSource {
|
||||||
public void maybeThrowError() throws IOException {
|
public void maybeThrowError() throws IOException {
|
||||||
if (fatalError != null) {
|
if (fatalError != null) {
|
||||||
throw fatalError;
|
throw fatalError;
|
||||||
} else if (manifestFetcher != null) {
|
} else if (live) {
|
||||||
manifestFetcher.maybeThrowError();
|
manifestFetcher.maybeThrowError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare() {
|
public boolean prepare() throws IOException {
|
||||||
if (!prepareCalled) {
|
if (!manifestFetcherEnabled) {
|
||||||
prepareCalled = true;
|
// TODO[REFACTOR]: We need to disable this at some point.
|
||||||
selectTracks(currentManifest, 0);
|
manifestFetcher.enable();
|
||||||
|
manifestFetcherEnabled = true;
|
||||||
|
}
|
||||||
|
if (currentManifest == null) {
|
||||||
|
currentManifest = manifestFetcher.getManifest();
|
||||||
|
if (currentManifest == null) {
|
||||||
|
manifestFetcher.maybeThrowError();
|
||||||
|
manifestFetcher.requestRefresh();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
live = currentManifest.dynamic;
|
||||||
|
selectTracks(currentManifest, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -328,17 +278,12 @@ public class DashChunkSource implements ChunkSource {
|
||||||
adaptiveMaxWidth = -1;
|
adaptiveMaxWidth = -1;
|
||||||
adaptiveMaxHeight = -1;
|
adaptiveMaxHeight = -1;
|
||||||
}
|
}
|
||||||
if (manifestFetcher != null) {
|
processManifest(manifestFetcher.getManifest());
|
||||||
manifestFetcher.enable();
|
|
||||||
processManifest(manifestFetcher.getManifest());
|
|
||||||
} else {
|
|
||||||
processManifest(currentManifest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long playbackPositionUs) {
|
public void continueBuffering(long playbackPositionUs) {
|
||||||
if (manifestFetcher == null || !currentManifest.dynamic || fatalError != null) {
|
if (!currentManifest.dynamic || fatalError != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -543,9 +488,6 @@ public class DashChunkSource implements ChunkSource {
|
||||||
if (enabledFormats.length > 1) {
|
if (enabledFormats.length > 1) {
|
||||||
adaptiveFormatEvaluator.disable();
|
adaptiveFormatEvaluator.disable();
|
||||||
}
|
}
|
||||||
if (manifestFetcher != null) {
|
|
||||||
manifestFetcher.disable();
|
|
||||||
}
|
|
||||||
periodHolders.clear();
|
periodHolders.clear();
|
||||||
evaluation.format = null;
|
evaluation.format = null;
|
||||||
availableRange = null;
|
availableRange = null;
|
||||||
|
|
@ -602,14 +544,6 @@ public class DashChunkSource implements ChunkSource {
|
||||||
return availableRange;
|
return availableRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaPresentationDescription buildManifest(long durationMs,
|
|
||||||
int adaptationSetType, List<Representation> representations) {
|
|
||||||
AdaptationSet adaptationSet = new AdaptationSet(0, adaptationSetType, representations);
|
|
||||||
Period period = new Period(null, 0, Collections.singletonList(adaptationSet));
|
|
||||||
return new MediaPresentationDescription(-1, durationMs, -1, false, -1, -1, null, null,
|
|
||||||
Collections.singletonList(period));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MediaFormat getTrackFormat(int adaptationSetType, Format format,
|
private static MediaFormat getTrackFormat(int adaptationSetType, Format format,
|
||||||
String mediaMimeType, long durationUs) {
|
String mediaMimeType, long durationUs) {
|
||||||
switch (adaptationSetType) {
|
switch (adaptationSetType) {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ 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.upstream.HttpDataSource.InvalidResponseCodeException;
|
import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
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.UriUtil;
|
import com.google.android.exoplayer.util.UriUtil;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
@ -123,22 +124,24 @@ public class HlsChunkSource {
|
||||||
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
|
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
|
||||||
private static final float BANDWIDTH_FRACTION = 0.8f;
|
private static final float BANDWIDTH_FRACTION = 0.8f;
|
||||||
|
|
||||||
|
private final ManifestFetcher<HlsPlaylist> manifestFetcher;
|
||||||
private final int type;
|
private final int type;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final HlsPlaylistParser playlistParser;
|
private final HlsPlaylistParser playlistParser;
|
||||||
private final HlsMasterPlaylist masterPlaylist;
|
|
||||||
private final BandwidthMeter bandwidthMeter;
|
private final BandwidthMeter bandwidthMeter;
|
||||||
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
||||||
private final int adaptiveMode;
|
private final int adaptiveMode;
|
||||||
private final String baseUri;
|
|
||||||
private final long minBufferDurationToSwitchUpUs;
|
private final long minBufferDurationToSwitchUpUs;
|
||||||
private final long maxBufferDurationToSwitchDownUs;
|
private final long maxBufferDurationToSwitchDownUs;
|
||||||
|
|
||||||
private boolean prepareCalled;
|
private boolean manifestFetcherEnabled;
|
||||||
private byte[] scratchSpace;
|
private byte[] scratchSpace;
|
||||||
private boolean live;
|
private boolean live;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private IOException fatalError;
|
private IOException fatalError;
|
||||||
|
private HlsMasterPlaylist masterPlaylist;
|
||||||
|
private String baseUri;
|
||||||
|
|
||||||
private Uri encryptionKeyUri;
|
private Uri encryptionKeyUri;
|
||||||
private byte[] encryptionKey;
|
private byte[] encryptionKey;
|
||||||
|
|
@ -158,11 +161,10 @@ public class HlsChunkSource {
|
||||||
private int selectedVariantIndex;
|
private int selectedVariantIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param manifestFetcher A fetcher for the playlist.
|
||||||
* @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and
|
* @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and
|
||||||
* {@link #TYPE_VTT}.
|
* {@link #TYPE_VTT}.
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param playlistUrl The playlist URL.
|
|
||||||
* @param playlist The hls playlist.
|
|
||||||
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
|
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
|
||||||
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
||||||
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
||||||
|
|
@ -171,19 +173,18 @@ public class HlsChunkSource {
|
||||||
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
|
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
|
||||||
* {@link #ADAPTIVE_MODE_SPLICE}.
|
* {@link #ADAPTIVE_MODE_SPLICE}.
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(int type, DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
public HlsChunkSource(ManifestFetcher<HlsPlaylist> manifestFetcher, int type,
|
||||||
BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider,
|
DataSource dataSource, BandwidthMeter bandwidthMeter,
|
||||||
int adaptiveMode) {
|
PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode) {
|
||||||
this(type, dataSource, playlistUrl, playlist, bandwidthMeter, timestampAdjusterProvider,
|
this(manifestFetcher, type, dataSource, bandwidthMeter, timestampAdjusterProvider,
|
||||||
adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS);
|
adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param manifestFetcher A fetcher for the playlist.
|
||||||
* @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and
|
* @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and
|
||||||
* {@link #TYPE_VTT}.
|
* {@link #TYPE_VTT}.
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param playlistUrl The playlist URL.
|
|
||||||
* @param playlist The hls playlist.
|
|
||||||
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
|
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
|
||||||
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
||||||
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
||||||
|
|
@ -196,9 +197,11 @@ public class HlsChunkSource {
|
||||||
* @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered
|
* @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered
|
||||||
* for a switch to a lower quality variant to be considered.
|
* for a switch to a lower quality variant to be considered.
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(int type, DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
public HlsChunkSource(ManifestFetcher<HlsPlaylist> manifestFetcher, int type,
|
||||||
BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider,
|
DataSource dataSource, BandwidthMeter bandwidthMeter,
|
||||||
int adaptiveMode, long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) {
|
PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode,
|
||||||
|
long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) {
|
||||||
|
this.manifestFetcher = manifestFetcher;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.bandwidthMeter = bandwidthMeter;
|
this.bandwidthMeter = bandwidthMeter;
|
||||||
|
|
@ -206,18 +209,7 @@ public class HlsChunkSource {
|
||||||
this.adaptiveMode = adaptiveMode;
|
this.adaptiveMode = adaptiveMode;
|
||||||
minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000;
|
minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000;
|
||||||
maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000;
|
maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000;
|
||||||
baseUri = playlist.baseUri;
|
|
||||||
playlistParser = new HlsPlaylistParser();
|
playlistParser = new HlsPlaylistParser();
|
||||||
if (playlist.type == HlsPlaylist.TYPE_MASTER) {
|
|
||||||
masterPlaylist = (HlsMasterPlaylist) playlist;
|
|
||||||
} else {
|
|
||||||
Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null,
|
|
||||||
null);
|
|
||||||
List<Variant> variants = new ArrayList<>();
|
|
||||||
variants.add(new Variant(playlistUrl, format));
|
|
||||||
masterPlaylist = new HlsMasterPlaylist(playlistUrl, variants,
|
|
||||||
Collections.<Variant>emptyList());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -237,12 +229,34 @@ public class HlsChunkSource {
|
||||||
*
|
*
|
||||||
* @return True if the source was prepared, false otherwise.
|
* @return True if the source was prepared, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean prepare() {
|
public boolean prepare() throws IOException {
|
||||||
if (!prepareCalled) {
|
if (!manifestFetcherEnabled) {
|
||||||
prepareCalled = true;
|
// TODO[REFACTOR]: We need to disable this at some point.
|
||||||
processMasterPlaylist(masterPlaylist);
|
manifestFetcher.enable();
|
||||||
// TODO[REFACTOR]: Come up with a sane default here.
|
manifestFetcherEnabled = true;
|
||||||
selectTracks(new int[] {0});
|
}
|
||||||
|
if (masterPlaylist == null) {
|
||||||
|
HlsPlaylist playlist = manifestFetcher.getManifest();
|
||||||
|
if (playlist == null) {
|
||||||
|
manifestFetcher.maybeThrowError();
|
||||||
|
manifestFetcher.requestRefresh();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
baseUri = playlist.baseUri;
|
||||||
|
if (playlist.type == HlsPlaylist.TYPE_MASTER) {
|
||||||
|
masterPlaylist = (HlsMasterPlaylist) playlist;
|
||||||
|
} else {
|
||||||
|
Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null,
|
||||||
|
null);
|
||||||
|
List<Variant> variants = new ArrayList<>();
|
||||||
|
variants.add(new Variant(baseUri, format));
|
||||||
|
masterPlaylist = new HlsMasterPlaylist(baseUri, variants,
|
||||||
|
Collections.<Variant>emptyList());
|
||||||
|
}
|
||||||
|
processMasterPlaylist(masterPlaylist);
|
||||||
|
// TODO[REFACTOR]: Come up with a sane default here.
|
||||||
|
selectTracks(new int[] {0});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -602,7 +616,7 @@ public class HlsChunkSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type is TYPE_DEFAULT.
|
// Type is TYPE_DEFAULT.
|
||||||
List<Variant> enabledVariantList = playlist.variants;
|
List<Variant> enabledVariantList = new ArrayList<>(playlist.variants);
|
||||||
ArrayList<Variant> definiteVideoVariants = new ArrayList<>();
|
ArrayList<Variant> definiteVideoVariants = new ArrayList<>();
|
||||||
ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<>();
|
ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<>();
|
||||||
for (int i = 0; i < enabledVariantList.size(); i++) {
|
for (int i = 0; i < enabledVariantList.size(); i++) {
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||||
if (prepared) {
|
if (prepared) {
|
||||||
return true;
|
return true;
|
||||||
} else if (!chunkSource.prepare()) {
|
} else if (!chunkSource.prepare()) {
|
||||||
maybeThrowError();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!extractors.isEmpty()) {
|
if (!extractors.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -67,17 +67,16 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final Evaluation evaluation;
|
private final Evaluation evaluation;
|
||||||
private final long liveEdgeLatencyUs;
|
private final long liveEdgeLatencyUs;
|
||||||
private final TrackEncryptionBox[] trackEncryptionBoxes;
|
|
||||||
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
||||||
private final DrmInitData.Mapped drmInitData;
|
|
||||||
private final FormatEvaluator adaptiveFormatEvaluator;
|
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||||
private final boolean live;
|
|
||||||
|
|
||||||
private boolean prepareCalled;
|
private boolean manifestFetcherEnabled;
|
||||||
|
private boolean live;
|
||||||
|
private TrackEncryptionBox[] trackEncryptionBoxes;
|
||||||
|
private DrmInitData.Mapped drmInitData;
|
||||||
private SmoothStreamingManifest currentManifest;
|
private SmoothStreamingManifest currentManifest;
|
||||||
private int currentManifestChunkOffset;
|
private int currentManifestChunkOffset;
|
||||||
private boolean needManifestRefresh;
|
private boolean needManifestRefresh;
|
||||||
private IOException fatalError;
|
|
||||||
|
|
||||||
// Properties of exposed tracks.
|
// Properties of exposed tracks.
|
||||||
private int elementIndex;
|
private int elementIndex;
|
||||||
|
|
@ -93,14 +92,10 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
private final SparseArray<ChunkExtractorWrapper> extractorWrappers;
|
private final SparseArray<ChunkExtractorWrapper> extractorWrappers;
|
||||||
private final SparseArray<MediaFormat> mediaFormats;
|
private final SparseArray<MediaFormat> mediaFormats;
|
||||||
|
|
||||||
|
private IOException fatalError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor to use for live streaming.
|
* @param manifestFetcher A fetcher for the manifest.
|
||||||
* <p>
|
|
||||||
* May also be used for fixed duration content, in which case the call is equivalent to calling
|
|
||||||
* the other constructor, passing {@code manifestFetcher.getManifest()} is the first argument.
|
|
||||||
*
|
|
||||||
* @param manifestFetcher A fetcher for the manifest, which must have already successfully
|
|
||||||
* completed an initial load.
|
|
||||||
* @param streamElementType The type of stream element exposed by this source. One of
|
* @param streamElementType The type of stream element exposed by this source. One of
|
||||||
* {@link StreamElement#TYPE_VIDEO}, {@link StreamElement#TYPE_AUDIO} and
|
* {@link StreamElement#TYPE_VIDEO}, {@link StreamElement#TYPE_AUDIO} and
|
||||||
* {@link StreamElement#TYPE_TEXT}.
|
* {@link StreamElement#TYPE_TEXT}.
|
||||||
|
|
@ -115,30 +110,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
public SmoothStreamingChunkSource(ManifestFetcher<SmoothStreamingManifest> manifestFetcher,
|
public SmoothStreamingChunkSource(ManifestFetcher<SmoothStreamingManifest> manifestFetcher,
|
||||||
int streamElementType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
int streamElementType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||||
long liveEdgeLatencyMs) {
|
long liveEdgeLatencyMs) {
|
||||||
this(manifestFetcher, manifestFetcher.getManifest(), streamElementType, dataSource,
|
|
||||||
adaptiveFormatEvaluator, liveEdgeLatencyMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor to use for fixed duration content.
|
|
||||||
*
|
|
||||||
* @param manifest The manifest parsed from {@code baseUrl + "/Manifest"}.
|
|
||||||
* @param streamElementType The type of stream element exposed by this source. One of
|
|
||||||
* {@link StreamElement#TYPE_VIDEO}, {@link StreamElement#TYPE_AUDIO} and
|
|
||||||
* {@link StreamElement#TYPE_TEXT}.
|
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
|
||||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
|
||||||
*/
|
|
||||||
public SmoothStreamingChunkSource(SmoothStreamingManifest manifest, int streamElementType,
|
|
||||||
DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) {
|
|
||||||
this(null, manifest, streamElementType, dataSource, adaptiveFormatEvaluator, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SmoothStreamingChunkSource(ManifestFetcher<SmoothStreamingManifest> manifestFetcher,
|
|
||||||
SmoothStreamingManifest initialManifest, int streamElementType, DataSource dataSource,
|
|
||||||
FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs) {
|
|
||||||
this.manifestFetcher = manifestFetcher;
|
this.manifestFetcher = manifestFetcher;
|
||||||
this.currentManifest = initialManifest;
|
|
||||||
this.streamElementType = streamElementType;
|
this.streamElementType = streamElementType;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||||
|
|
@ -146,20 +118,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
evaluation = new Evaluation();
|
evaluation = new Evaluation();
|
||||||
extractorWrappers = new SparseArray<>();
|
extractorWrappers = new SparseArray<>();
|
||||||
mediaFormats = new SparseArray<>();
|
mediaFormats = new SparseArray<>();
|
||||||
live = initialManifest.isLive;
|
|
||||||
|
|
||||||
ProtectionElement protectionElement = initialManifest.protectionElement;
|
|
||||||
if (protectionElement != null) {
|
|
||||||
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
|
|
||||||
trackEncryptionBoxes = new TrackEncryptionBox[1];
|
|
||||||
trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId);
|
|
||||||
drmInitData = new DrmInitData.Mapped();
|
|
||||||
drmInitData.put(protectionElement.uuid,
|
|
||||||
new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data));
|
|
||||||
} else {
|
|
||||||
trackEncryptionBoxes = null;
|
|
||||||
drmInitData = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChunkSource implementation.
|
// ChunkSource implementation.
|
||||||
|
|
@ -168,16 +126,40 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
public void maybeThrowError() throws IOException {
|
public void maybeThrowError() throws IOException {
|
||||||
if (fatalError != null) {
|
if (fatalError != null) {
|
||||||
throw fatalError;
|
throw fatalError;
|
||||||
} else {
|
} else if (live) {
|
||||||
manifestFetcher.maybeThrowError();
|
manifestFetcher.maybeThrowError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare() {
|
public boolean prepare() throws IOException {
|
||||||
if (!prepareCalled) {
|
if (!manifestFetcherEnabled) {
|
||||||
selectTracks(currentManifest);
|
// TODO[REFACTOR]: We need to disable this at some point.
|
||||||
prepareCalled = true;
|
manifestFetcher.enable();
|
||||||
|
manifestFetcherEnabled = true;
|
||||||
|
}
|
||||||
|
if (currentManifest == null) {
|
||||||
|
currentManifest = manifestFetcher.getManifest();
|
||||||
|
if (currentManifest == null) {
|
||||||
|
manifestFetcher.maybeThrowError();
|
||||||
|
manifestFetcher.requestRefresh();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
live = currentManifest.isLive;
|
||||||
|
ProtectionElement protectionElement = currentManifest.protectionElement;
|
||||||
|
if (protectionElement != null) {
|
||||||
|
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
|
||||||
|
trackEncryptionBoxes = new TrackEncryptionBox[1];
|
||||||
|
trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId);
|
||||||
|
drmInitData = new DrmInitData.Mapped();
|
||||||
|
drmInitData.put(protectionElement.uuid,
|
||||||
|
new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data));
|
||||||
|
} else {
|
||||||
|
trackEncryptionBoxes = null;
|
||||||
|
drmInitData = null;
|
||||||
|
}
|
||||||
|
selectTracks(currentManifest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -206,14 +188,11 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
adaptiveMaxWidth = -1;
|
adaptiveMaxWidth = -1;
|
||||||
adaptiveMaxHeight = -1;
|
adaptiveMaxHeight = -1;
|
||||||
}
|
}
|
||||||
if (manifestFetcher != null) {
|
|
||||||
manifestFetcher.enable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long playbackPositionUs) {
|
public void continueBuffering(long playbackPositionUs) {
|
||||||
if (manifestFetcher == null || !currentManifest.isLive || fatalError != null) {
|
if (!currentManifest.isLive || fatalError != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,9 +329,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
if (enabledFormats.length > 1) {
|
if (enabledFormats.length > 1) {
|
||||||
adaptiveFormatEvaluator.disable();
|
adaptiveFormatEvaluator.disable();
|
||||||
}
|
}
|
||||||
if (manifestFetcher != null) {
|
|
||||||
manifestFetcher.disable();
|
|
||||||
}
|
|
||||||
evaluation.format = null;
|
evaluation.format = null;
|
||||||
fatalError = null;
|
fatalError = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,28 +21,14 @@ import com.google.android.exoplayer.upstream.UriDataSource;
|
||||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs both single and repeated loads of media manifests.
|
* Loads and refreshes a media manifest.
|
||||||
* <p>
|
|
||||||
* Client code is responsible for ensuring that only one load is taking place at any one time.
|
|
||||||
* Typical usage of this class is as follows:
|
|
||||||
* <ol>
|
|
||||||
* <li>Create an instance.</li>
|
|
||||||
* <li>Obtain an initial manifest by calling {@link #singleLoad(Looper, ManifestCallback)} and
|
|
||||||
* waiting for the callback to be invoked.</li>
|
|
||||||
* <li>For on-demand playbacks, the loader is no longer required. For live playbacks, the loader
|
|
||||||
* may be required to periodically refresh the manifest. In this case it is injected into any
|
|
||||||
* components that require it. These components will call {@link #requestRefresh()} on the
|
|
||||||
* loader whenever a refresh is required.</li>
|
|
||||||
* </ol>
|
|
||||||
*
|
*
|
||||||
* @param <T> The type of manifest.
|
* @param <T> The type of manifest.
|
||||||
*/
|
*/
|
||||||
|
|
@ -69,29 +55,6 @@ public class ManifestFetcher<T> implements Loader.Callback {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for the result of a single load.
|
|
||||||
*
|
|
||||||
* @param <T> The type of manifest.
|
|
||||||
*/
|
|
||||||
public interface ManifestCallback<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the load has successfully completed.
|
|
||||||
*
|
|
||||||
* @param manifest The loaded manifest.
|
|
||||||
*/
|
|
||||||
void onSingleManifest(T manifest);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the load has failed.
|
|
||||||
*
|
|
||||||
* @param e The cause of the failure.
|
|
||||||
*/
|
|
||||||
void onSingleManifestError(IOException e);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for manifests that are able to specify that subsequent loads should use a different
|
* Interface for manifests that are able to specify that subsequent loads should use a different
|
||||||
* URI.
|
* URI.
|
||||||
|
|
@ -162,19 +125,6 @@ public class ManifestFetcher<T> implements Loader.Callback {
|
||||||
this.manifestUri = manifestUri;
|
this.manifestUri = manifestUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a single manifest load.
|
|
||||||
*
|
|
||||||
* @param callbackLooper The looper associated with the thread on which the callback should be
|
|
||||||
* invoked.
|
|
||||||
* @param callback The callback to receive the result.
|
|
||||||
*/
|
|
||||||
public void singleLoad(Looper callbackLooper, final ManifestCallback<T> callback) {
|
|
||||||
SingleFetchHelper fetchHelper = new SingleFetchHelper(
|
|
||||||
new UriLoadable<>(manifestUri, uriDataSource, parser), callbackLooper, callback);
|
|
||||||
fetchHelper.startLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a {@link Pair} containing the most recently loaded manifest together with the timestamp
|
* Gets a {@link Pair} containing the most recently loaded manifest together with the timestamp
|
||||||
* at which the load completed.
|
* at which the load completed.
|
||||||
|
|
@ -348,63 +298,4 @@ public class ManifestFetcher<T> implements Loader.Callback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SingleFetchHelper implements Loader.Callback {
|
|
||||||
|
|
||||||
private final UriLoadable<T> singleUseLoadable;
|
|
||||||
private final Looper callbackLooper;
|
|
||||||
private final ManifestCallback<T> wrappedCallback;
|
|
||||||
private final Loader singleUseLoader;
|
|
||||||
|
|
||||||
private long loadStartTimestamp;
|
|
||||||
|
|
||||||
public SingleFetchHelper(UriLoadable<T> singleUseLoadable, Looper callbackLooper,
|
|
||||||
ManifestCallback<T> wrappedCallback) {
|
|
||||||
this.singleUseLoadable = singleUseLoadable;
|
|
||||||
this.callbackLooper = callbackLooper;
|
|
||||||
this.wrappedCallback = wrappedCallback;
|
|
||||||
singleUseLoader = new Loader("manifestLoader:single");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startLoading() {
|
|
||||||
loadStartTimestamp = SystemClock.elapsedRealtime();
|
|
||||||
singleUseLoader.startLoading(callbackLooper, singleUseLoadable, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadCompleted(Loadable loadable) {
|
|
||||||
try {
|
|
||||||
T result = singleUseLoadable.getResult();
|
|
||||||
onSingleFetchCompleted(result, loadStartTimestamp);
|
|
||||||
wrappedCallback.onSingleManifest(result);
|
|
||||||
} finally {
|
|
||||||
releaseLoader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadCanceled(Loadable loadable) {
|
|
||||||
// This shouldn't ever happen, but handle it anyway.
|
|
||||||
try {
|
|
||||||
IOException exception = new ManifestIOException(new CancellationException());
|
|
||||||
wrappedCallback.onSingleManifestError(exception);
|
|
||||||
} finally {
|
|
||||||
releaseLoader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadError(Loadable loadable, IOException exception) {
|
|
||||||
try {
|
|
||||||
wrappedCallback.onSingleManifestError(exception);
|
|
||||||
} finally {
|
|
||||||
releaseLoader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseLoader() {
|
|
||||||
singleUseLoader.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue