Commit e89c40cf by Oliver Woodman

Handle renderer building cancellation.

If the manifest server response arrived after the player was released, the
renderer builder would set up renderers for a released player, causing an
exception to the thrown in the DASH case.

Also fix Issue #657
parent 9e0ca9e7
...@@ -41,7 +41,6 @@ import com.google.android.exoplayer.dash.mpd.UtcTimingElement; ...@@ -41,7 +41,6 @@ import com.google.android.exoplayer.dash.mpd.UtcTimingElement;
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver; import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver;
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback; import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.drm.UnsupportedDrmException; import com.google.android.exoplayer.drm.UnsupportedDrmException;
...@@ -54,7 +53,6 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; ...@@ -54,7 +53,6 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
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.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.content.Context; import android.content.Context;
...@@ -69,8 +67,7 @@ import java.util.List; ...@@ -69,8 +67,7 @@ import java.util.List;
/** /**
* A {@link RendererBuilder} for DASH. * A {@link RendererBuilder} for DASH.
*/ */
public class DashRendererBuilder implements RendererBuilder, public class DashRendererBuilder implements RendererBuilder {
ManifestCallback<MediaPresentationDescription>, UtcTimingCallback {
private static final String TAG = "DashRendererBuilder"; private static final String TAG = "DashRendererBuilder";
...@@ -102,13 +99,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -102,13 +99,7 @@ public class DashRendererBuilder implements RendererBuilder,
private final MediaDrmCallback drmCallback; private final MediaDrmCallback drmCallback;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private DemoPlayer player; private AsyncRendererBuilder currentAsyncBuilder;
private RendererBuilderCallback callback;
private ManifestFetcher<MediaPresentationDescription> manifestFetcher;
private UriDataSource manifestDataSource;
private MediaPresentationDescription manifest;
private long elapsedRealtimeOffset;
public DashRendererBuilder(Context context, String userAgent, String url, public DashRendererBuilder(Context context, String userAgent, String url,
MediaDrmCallback drmCallback, AudioCapabilities audioCapabilities) { MediaDrmCallback drmCallback, AudioCapabilities audioCapabilities) {
...@@ -120,236 +111,294 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -120,236 +111,294 @@ public class DashRendererBuilder implements RendererBuilder,
} }
@Override @Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { public void buildRenderers(DemoPlayer player) {
this.player = player; currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback,
this.callback = callback; audioCapabilities, player);
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); currentAsyncBuilder.init();
manifestDataSource = new DefaultUriDataSource(context, userAgent);
manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser);
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
@Override @Override
public void onSingleManifest(MediaPresentationDescription manifest) { public void cancel() {
this.manifest = manifest; if (currentAsyncBuilder != null) {
if (manifest.dynamic && manifest.utcTiming != null) { currentAsyncBuilder.cancel();
UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming, currentAsyncBuilder = null;
manifestFetcher.getManifestLoadCompleteTimestamp(), this);
} else {
buildRenderers();
} }
} }
@Override private static final class AsyncRendererBuilder
public void onSingleManifestError(IOException e) { implements ManifestFetcher.ManifestCallback<MediaPresentationDescription>, UtcTimingCallback {
callback.onRenderersError(e);
} private final Context context;
private final String userAgent;
@Override private final MediaDrmCallback drmCallback;
public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) { private final AudioCapabilities audioCapabilities;
this.elapsedRealtimeOffset = elapsedRealtimeOffset; private final DemoPlayer player;
buildRenderers(); private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
} private final UriDataSource manifestDataSource;
@Override private boolean canceled;
public void onTimestampError(UtcTimingElement utcTiming, IOException e) { private MediaPresentationDescription manifest;
Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e); private long elapsedRealtimeOffset;
// Be optimistic and continue in the hope that the device clock is correct.
buildRenderers(); public AsyncRendererBuilder(Context context, String userAgent, String url,
} MediaDrmCallback drmCallback, AudioCapabilities audioCapabilities, DemoPlayer player) {
this.context = context;
private void buildRenderers() { this.userAgent = userAgent;
Period period = manifest.periods.get(0); this.drmCallback = drmCallback;
Handler mainHandler = player.getMainHandler(); this.audioCapabilities = audioCapabilities;
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); this.player = player;
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player); MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
manifestDataSource = new DefaultUriDataSource(context, userAgent);
boolean hasContentProtection = false; manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser);
int videoAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_VIDEO);
int audioAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_AUDIO);
AdaptationSet videoAdaptationSet = null;
AdaptationSet audioAdaptationSet = null;
if (videoAdaptationSetIndex != -1) {
videoAdaptationSet = period.adaptationSets.get(videoAdaptationSetIndex);
hasContentProtection |= videoAdaptationSet.hasContentProtection();
} }
if (audioAdaptationSetIndex != -1) {
audioAdaptationSet = period.adaptationSets.get(audioAdaptationSetIndex); public void init() {
hasContentProtection |= audioAdaptationSet.hasContentProtection(); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
// Fail if we have neither video or audio. public void cancel() {
if (videoAdaptationSet == null && audioAdaptationSet == null) { canceled = true;
callback.onRenderersError(new IllegalStateException("No video or audio adaptation sets"));
return;
} }
// Check drm support if necessary. @Override
boolean filterHdContent = false; public void onSingleManifest(MediaPresentationDescription manifest) {
StreamingDrmSessionManager drmSessionManager = null; if (canceled) {
if (hasContentProtection) {
if (Util.SDK_INT < 18) {
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
return; return;
} }
try {
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance( this.manifest = manifest;
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player); if (manifest.dynamic && manifest.utcTiming != null) {
filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection() UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming,
&& getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1; manifestFetcher.getManifestLoadCompleteTimestamp(), this);
} catch (UnsupportedDrmException e) { } else {
callback.onRenderersError(e); buildRenderers();
}
}
@Override
public void onSingleManifestError(IOException e) {
if (canceled) {
return; return;
} }
player.onRenderersError(e);
} }
// Determine which video representations we should use for playback. @Override
int[] videoRepresentationIndices = null; public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) {
if (videoAdaptationSet != null) { if (canceled) {
try {
videoRepresentationIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, videoAdaptationSet.representations, null, filterHdContent);
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return; return;
} }
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
buildRenderers();
} }
// Build the video renderer. @Override
final MediaCodecVideoTrackRenderer videoRenderer; public void onTimestampError(UtcTimingElement utcTiming, IOException e) {
if (videoRepresentationIndices == null || videoRepresentationIndices.length == 0) { if (canceled) {
videoRenderer = null; return;
} else { }
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e);
videoAdaptationSetIndex, videoRepresentationIndices, videoDataSource, // Be optimistic and continue in the hope that the device clock is correct.
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, buildRenderers();
mainHandler, player);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
} }
// Build the audio chunk sources. private void buildRenderers() {
List<ChunkSource> audioChunkSourceList = new ArrayList<>(); Period period = manifest.periods.get(0);
List<String> audioTrackNameList = new ArrayList<>(); Handler mainHandler = player.getMainHandler();
if (audioAdaptationSet != null) { LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();
List<Representation> audioRepresentations = audioAdaptationSet.representations; boolean hasContentProtection = false;
List<String> codecs = new ArrayList<>(); int videoAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_VIDEO);
for (int i = 0; i < audioRepresentations.size(); i++) { int audioAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_AUDIO);
Format format = audioRepresentations.get(i).format; AdaptationSet videoAdaptationSet = null;
audioTrackNameList.add(format.id + " (" + format.numChannels + "ch, " + AdaptationSet audioAdaptationSet = null;
format.audioSamplingRate + "Hz)"); if (videoAdaptationSetIndex != -1) {
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex, videoAdaptationSet = period.adaptationSets.get(videoAdaptationSetIndex);
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS, hasContentProtection |= videoAdaptationSet.hasContentProtection();
elapsedRealtimeOffset, mainHandler, player)); }
codecs.add(format.codecs); if (audioAdaptationSetIndex != -1) {
audioAdaptationSet = period.adaptationSets.get(audioAdaptationSetIndex);
hasContentProtection |= audioAdaptationSet.hasContentProtection();
} }
if (audioCapabilities != null) { // Fail if we have neither video or audio.
// If there are any passthrough audio encodings available, select the highest priority if (videoAdaptationSet == null && audioAdaptationSet == null) {
// supported format (e.g. E-AC-3) and remove other tracks. player.onRenderersError(new IllegalStateException("No video or audio adaptation sets"));
for (int i = 0; i < PASSTHROUGH_CODECS_PRIORITY.length; i++) { return;
String codec = PASSTHROUGH_CODECS_PRIORITY[i]; }
int encoding = PASSTHROUGH_ENCODINGS_PRIORITY[i];
if (codecs.indexOf(codec) == -1 || !audioCapabilities.supportsEncoding(encoding)) { // Check drm support if necessary.
continue; boolean filterHdContent = false;
} StreamingDrmSessionManager drmSessionManager = null;
if (hasContentProtection) {
if (Util.SDK_INT < 18) {
player.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
return;
}
try {
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance(
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection()
&& getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1;
} catch (UnsupportedDrmException e) {
player.onRenderersError(e);
return;
}
}
// Determine which video representations we should use for playback.
int[] videoRepresentationIndices = null;
if (videoAdaptationSet != null) {
try {
videoRepresentationIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, videoAdaptationSet.representations, null, filterHdContent);
} catch (DecoderQueryException e) {
player.onRenderersError(e);
return;
}
}
// Build the video renderer.
final MediaCodecVideoTrackRenderer videoRenderer;
if (videoRepresentationIndices == null || videoRepresentationIndices.length == 0) {
videoRenderer = null;
} else {
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher,
videoAdaptationSetIndex, videoRepresentationIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset,
mainHandler, player);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
}
// Build the audio chunk sources.
List<ChunkSource> audioChunkSourceList = new ArrayList<>();
List<String> audioTrackNameList = new ArrayList<>();
if (audioAdaptationSet != null) {
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();
List<Representation> audioRepresentations = audioAdaptationSet.representations;
List<String> codecs = new ArrayList<>();
for (int i = 0; i < audioRepresentations.size(); i++) {
Format format = audioRepresentations.get(i).format;
audioTrackNameList.add(format.id + " (" + format.numChannels + "ch, " +
format.audioSamplingRate + "Hz)");
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS,
elapsedRealtimeOffset, mainHandler, player));
codecs.add(format.codecs);
}
if (audioCapabilities != null) {
// If there are any passthrough audio encodings available, select the highest priority
// supported format (e.g. E-AC-3) and remove other tracks.
for (int i = 0; i < PASSTHROUGH_CODECS_PRIORITY.length; i++) {
String codec = PASSTHROUGH_CODECS_PRIORITY[i];
int encoding = PASSTHROUGH_ENCODINGS_PRIORITY[i];
if (codecs.indexOf(codec) == -1 || !audioCapabilities.supportsEncoding(encoding)) {
continue;
}
for (int j = audioRepresentations.size() - 1; j >= 0; j--) { for (int j = audioRepresentations.size() - 1; j >= 0; j--) {
if (!audioRepresentations.get(j).format.codecs.equals(codec)) { if (!audioRepresentations.get(j).format.codecs.equals(codec)) {
audioTrackNameList.remove(j); audioTrackNameList.remove(j);
audioChunkSourceList.remove(j); audioChunkSourceList.remove(j);
}
} }
break;
} }
break;
} }
} }
}
// Build the audio renderer. // Build the audio renderer.
final String[] audioTrackNames; final String[] audioTrackNames;
final MultiTrackChunkSource audioChunkSource; final MultiTrackChunkSource audioChunkSource;
final TrackRenderer audioRenderer; final TrackRenderer audioRenderer;
if (audioChunkSourceList.isEmpty()) { if (audioChunkSourceList.isEmpty()) {
audioTrackNames = null; audioTrackNames = null;
audioChunkSource = null; audioChunkSource = null;
audioRenderer = null; audioRenderer = null;
} else { } else {
audioTrackNames = new String[audioTrackNameList.size()]; audioTrackNames = new String[audioTrackNameList.size()];
audioTrackNameList.toArray(audioTrackNames); audioTrackNameList.toArray(audioTrackNames);
audioChunkSource = new MultiTrackChunkSource(audioChunkSourceList); audioChunkSource = new MultiTrackChunkSource(audioChunkSourceList);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_AUDIO); DemoPlayer.TYPE_AUDIO);
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true, audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true,
mainHandler, player); mainHandler, player);
} }
// Build the text chunk sources. // Build the text chunk sources.
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
FormatEvaluator textEvaluator = new FormatEvaluator.FixedEvaluator(); FormatEvaluator textEvaluator = new FormatEvaluator.FixedEvaluator();
List<ChunkSource> textChunkSourceList = new ArrayList<>(); List<ChunkSource> textChunkSourceList = new ArrayList<>();
List<String> textTrackNameList = new ArrayList<>(); List<String> textTrackNameList = new ArrayList<>();
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 == AdaptationSet.TYPE_TEXT) { if (adaptationSet.type == AdaptationSet.TYPE_TEXT) {
List<Representation> representations = adaptationSet.representations; List<Representation> representations = adaptationSet.representations;
for (int j = 0; j < representations.size(); j++) { for (int j = 0; j < representations.size(); j++) {
Representation representation = representations.get(j); Representation representation = representations.get(j);
textTrackNameList.add(representation.format.id); textTrackNameList.add(representation.format.id);
textChunkSourceList.add(new DashChunkSource(manifestFetcher, i, new int[] {j}, textChunkSourceList.add(new DashChunkSource(manifestFetcher, i, new int[] {j},
textDataSource, textEvaluator, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, textDataSource, textEvaluator, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset,
mainHandler, player)); mainHandler, player));
}
} }
} }
}
// Build the text renderers // Build the text renderers
final String[] textTrackNames; final String[] textTrackNames;
final MultiTrackChunkSource textChunkSource; final MultiTrackChunkSource textChunkSource;
final TrackRenderer textRenderer; final TrackRenderer textRenderer;
if (textChunkSourceList.isEmpty()) { if (textChunkSourceList.isEmpty()) {
textTrackNames = null; textTrackNames = null;
textChunkSource = null; textChunkSource = null;
textRenderer = null; textRenderer = null;
} else { } else {
textTrackNames = new String[textTrackNameList.size()]; textTrackNames = new String[textTrackNameList.size()];
textTrackNameList.toArray(textTrackNames); textTrackNameList.toArray(textTrackNames);
textChunkSource = new MultiTrackChunkSource(textChunkSourceList); textChunkSource = new MultiTrackChunkSource(textChunkSourceList);
SampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, SampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_TEXT); DemoPlayer.TYPE_TEXT);
textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper(), textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper(),
new TtmlParser(), new WebvttParser()); new TtmlParser(), new WebvttParser());
}
// Invoke the callback.
String[][] trackNames = new String[DemoPlayer.RENDERER_COUNT][];
trackNames[DemoPlayer.TYPE_AUDIO] = audioTrackNames;
trackNames[DemoPlayer.TYPE_TEXT] = textTrackNames;
MultiTrackChunkSource[] multiTrackChunkSources =
new MultiTrackChunkSource[DemoPlayer.RENDERER_COUNT];
multiTrackChunkSources[DemoPlayer.TYPE_AUDIO] = audioChunkSource;
multiTrackChunkSources[DemoPlayer.TYPE_TEXT] = textChunkSource;
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
player.onRenderers(trackNames, multiTrackChunkSources, renderers, bandwidthMeter);
} }
// Invoke the callback. private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) {
String[][] trackNames = new String[DemoPlayer.RENDERER_COUNT][]; String securityLevelProperty = sessionManager.getPropertyString("securityLevel");
trackNames[DemoPlayer.TYPE_AUDIO] = audioTrackNames; return securityLevelProperty.equals("L1") ? SECURITY_LEVEL_1 : securityLevelProperty
trackNames[DemoPlayer.TYPE_TEXT] = textTrackNames; .equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN;
}
MultiTrackChunkSource[] multiTrackChunkSources =
new MultiTrackChunkSource[DemoPlayer.RENDERER_COUNT];
multiTrackChunkSources[DemoPlayer.TYPE_AUDIO] = audioChunkSource;
multiTrackChunkSources[DemoPlayer.TYPE_TEXT] = textChunkSource;
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
callback.onRenderers(trackNames, multiTrackChunkSources, renderers, bandwidthMeter);
}
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;
} }
} }
...@@ -67,39 +67,20 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -67,39 +67,20 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
*/ */
public interface RendererBuilder { public interface RendererBuilder {
/** /**
* Constructs the necessary components for playback. * Builds renderers for playback.
* *
* @param player The parent player. * @param player The player for which renderers are being built. {@link DemoPlayer#onRenderers}
* @param callback The callback to invoke with the constructed components. * should be invoked once the renderers have been built. If building fails,
* {@link DemoPlayer#onRenderersError} should be invoked.
*/ */
void buildRenderers(DemoPlayer player, RendererBuilderCallback callback); void buildRenderers(DemoPlayer player);
}
/**
* A callback invoked by a {@link RendererBuilder}.
*/
public interface RendererBuilderCallback {
/** /**
* Invoked with the results from a {@link RendererBuilder}. * Cancels the current build operation, if there is one. Else does nothing.
* * <p>
* @param trackNames The names of the available tracks, indexed by {@link DemoPlayer} TYPE_* * A canceled build operation must not invoke {@link DemoPlayer#onRenderers} or
* constants. May be null if the track names are unknown. An individual element may be null * {@link DemoPlayer#onRenderersError} on the player, which may have been released.
* if the track names are unknown for the corresponding type.
* @param multiTrackSources Sources capable of switching between multiple available tracks,
* indexed by {@link DemoPlayer} TYPE_* constants. May be null if there are no types with
* multiple tracks. An individual element may be null if it does not have multiple tracks.
* @param renderers Renderers indexed by {@link DemoPlayer} TYPE_* constants. An individual
* element may be null if there do not exist tracks of the corresponding type.
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. May be null.
*/ */
void onRenderers(String[][] trackNames, MultiTrackChunkSource[] multiTrackSources, void cancel();
TrackRenderer[] renderers, BandwidthMeter bandwidthMeter);
/**
* Invoked if a {@link RendererBuilder} encounters an error.
*
* @param e Describes the error.
*/
void onRenderersError(Exception e);
} }
/** /**
...@@ -191,7 +172,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -191,7 +172,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
private boolean lastReportedPlayWhenReady; private boolean lastReportedPlayWhenReady;
private Surface surface; private Surface surface;
private InternalRendererBuilderCallback builderCallback;
private TrackRenderer videoRenderer; private TrackRenderer videoRenderer;
private CodecCounters codecCounters; private CodecCounters codecCounters;
private Format videoFormat; private Format videoFormat;
...@@ -305,22 +285,31 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -305,22 +285,31 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT) { if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT) {
player.stop(); player.stop();
} }
if (builderCallback != null) { rendererBuilder.cancel();
builderCallback.cancel();
}
videoFormat = null; videoFormat = null;
videoRenderer = null; videoRenderer = null;
multiTrackSources = null; multiTrackSources = null;
rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING; rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
maybeReportPlayerState(); maybeReportPlayerState();
builderCallback = new InternalRendererBuilderCallback(); rendererBuilder.buildRenderers(this);
rendererBuilder.buildRenderers(this, builderCallback);
} }
/**
* Invoked with the results from a {@link RendererBuilder}.
*
* @param trackNames The names of the available tracks, indexed by {@link DemoPlayer} TYPE_*
* constants. May be null if the track names are unknown. An individual element may be null
* if the track names are unknown for the corresponding type.
* @param multiTrackSources Sources capable of switching between multiple available tracks,
* indexed by {@link DemoPlayer} TYPE_* constants. May be null if there are no types with
* multiple tracks. An individual element may be null if it does not have multiple tracks.
* @param renderers Renderers indexed by {@link DemoPlayer} TYPE_* constants. An individual
* element may be null if there do not exist tracks of the corresponding type.
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. May be null.
*/
/* package */ void onRenderers(String[][] trackNames, /* package */ void onRenderers(String[][] trackNames,
MultiTrackChunkSource[] multiTrackSources, TrackRenderer[] renderers, MultiTrackChunkSource[] multiTrackSources, TrackRenderer[] renderers,
BandwidthMeter bandwidthMeter) { BandwidthMeter bandwidthMeter) {
builderCallback = null;
// Normalize the results. // Normalize the results.
if (trackNames == null) { if (trackNames == null) {
trackNames = new String[RENDERER_COUNT][]; trackNames = new String[RENDERER_COUNT][];
...@@ -357,8 +346,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -357,8 +346,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
rendererBuildingState = RENDERER_BUILDING_STATE_BUILT; rendererBuildingState = RENDERER_BUILDING_STATE_BUILT;
} }
/**
* Invoked if a {@link RendererBuilder} encounters an error.
*
* @param e Describes the error.
*/
/* package */ void onRenderersError(Exception e) { /* package */ void onRenderersError(Exception e) {
builderCallback = null;
if (internalErrorListener != null) { if (internalErrorListener != null) {
internalErrorListener.onRendererInitializationError(e); internalErrorListener.onRendererInitializationError(e);
} }
...@@ -378,10 +371,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -378,10 +371,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
public void release() { public void release() {
if (builderCallback != null) { rendererBuilder.cancel();
builderCallback.cancel();
builderCallback = null;
}
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
surface = null; surface = null;
player.release(); player.release();
...@@ -390,14 +380,13 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -390,14 +380,13 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public int getPlaybackState() { public int getPlaybackState() {
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) { if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
return ExoPlayer.STATE_PREPARING; return STATE_PREPARING;
} }
int playerState = player.getPlaybackState(); int playerState = player.getPlaybackState();
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT && playerState == STATE_IDLE) {
&& rendererBuildingState == RENDERER_BUILDING_STATE_IDLE) {
// This is an edge case where the renderers are built, but are still being passed to the // This is an edge case where the renderers are built, but are still being passed to the
// player's playback thread. // player's playback thread.
return ExoPlayer.STATE_PREPARING; return STATE_PREPARING;
} }
return playerState; return playerState;
} }
...@@ -645,29 +634,4 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -645,29 +634,4 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
} }
private class InternalRendererBuilderCallback implements RendererBuilderCallback {
private boolean canceled;
public void cancel() {
canceled = true;
}
@Override
public void onRenderers(String[][] trackNames, MultiTrackChunkSource[] multiTrackSources,
TrackRenderer[] renderers, BandwidthMeter bandwidthMeter) {
if (!canceled) {
DemoPlayer.this.onRenderers(trackNames, multiTrackSources, renderers, bandwidthMeter);
}
}
@Override
public void onRenderersError(Exception e) {
if (!canceled) {
DemoPlayer.this.onRenderersError(e);
}
}
}
} }
...@@ -19,7 +19,6 @@ import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; ...@@ -19,7 +19,6 @@ import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
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;
import com.google.android.exoplayer.text.TextTrackRenderer; import com.google.android.exoplayer.text.TextTrackRenderer;
...@@ -52,7 +51,7 @@ public class ExtractorRendererBuilder implements RendererBuilder { ...@@ -52,7 +51,7 @@ public class ExtractorRendererBuilder implements RendererBuilder {
} }
@Override @Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { public void buildRenderers(DemoPlayer player) {
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE); Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
// Build the video and audio renderers. // Build the video and audio renderers.
...@@ -74,7 +73,12 @@ public class ExtractorRendererBuilder implements RendererBuilder { ...@@ -74,7 +73,12 @@ public class ExtractorRendererBuilder implements RendererBuilder {
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TEXT] = textRenderer; renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
callback.onRenderers(null, null, renderers, bandwidthMeter); player.onRenderers(null, null, renderers, bandwidthMeter);
}
@Override
public void cancel() {
// Do nothing.
} }
} }
...@@ -24,7 +24,6 @@ import com.google.android.exoplayer.TrackRenderer; ...@@ -24,7 +24,6 @@ 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.chunk.VideoFormatSelectorUtil; import com.google.android.exoplayer.chunk.VideoFormatSelectorUtil;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource; import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist; import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylist; import com.google.android.exoplayer.hls.HlsPlaylist;
...@@ -50,7 +49,7 @@ import java.util.Map; ...@@ -50,7 +49,7 @@ import java.util.Map;
/** /**
* A {@link RendererBuilder} for HLS. * A {@link RendererBuilder} for HLS.
*/ */
public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsPlaylist> { public class HlsRendererBuilder implements RendererBuilder {
private static final int BUFFER_SEGMENT_SIZE = 256 * 1024; private static final int BUFFER_SEGMENT_SIZE = 256 * 1024;
private static final int BUFFER_SEGMENTS = 64; private static final int BUFFER_SEGMENTS = 64;
...@@ -60,8 +59,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -60,8 +59,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
private final String url; private final String url;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private DemoPlayer player; private AsyncRendererBuilder currentAsyncBuilder;
private RendererBuilderCallback callback;
public HlsRendererBuilder(Context context, String userAgent, String url, public HlsRendererBuilder(Context context, String userAgent, String url,
AudioCapabilities audioCapabilities) { AudioCapabilities audioCapabilities) {
...@@ -72,59 +70,103 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -72,59 +70,103 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
} }
@Override @Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { public void buildRenderers(DemoPlayer player) {
this.player = player; currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, audioCapabilities,
this.callback = callback; player);
HlsPlaylistParser parser = new HlsPlaylistParser(); currentAsyncBuilder.init();
ManifestFetcher<HlsPlaylist> playlistFetcher = new ManifestFetcher<>(url,
new DefaultUriDataSource(context, userAgent), parser);
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
@Override @Override
public void onSingleManifestError(IOException e) { public void cancel() {
callback.onRenderersError(e); if (currentAsyncBuilder != null) {
currentAsyncBuilder.cancel();
currentAsyncBuilder = null;
}
} }
@Override private static final class AsyncRendererBuilder implements ManifestCallback<HlsPlaylist> {
public void onSingleManifest(HlsPlaylist manifest) {
Handler mainHandler = player.getMainHandler(); private final Context context;
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); private final String userAgent;
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); private final String url;
private final AudioCapabilities audioCapabilities;
int[] variantIndices = null; private final DemoPlayer player;
if (manifest instanceof HlsMasterPlaylist) { private final ManifestFetcher<HlsPlaylist> playlistFetcher;
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) manifest;
try { private boolean canceled;
variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, masterPlaylist.variants, null, false); public AsyncRendererBuilder(Context context, String userAgent, String url,
} catch (DecoderQueryException e) { AudioCapabilities audioCapabilities, DemoPlayer player) {
callback.onRenderersError(e); this.context = context;
this.userAgent = userAgent;
this.url = url;
this.audioCapabilities = audioCapabilities;
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; return;
} }
player.onRenderersError(e);
}
@Override
public void onSingleManifest(HlsPlaylist manifest) {
if (canceled) {
return;
}
Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
int[] variantIndices = null;
if (manifest instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) manifest;
try {
variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, masterPlaylist.variants, null, false);
} catch (DecoderQueryException e) {
player.onRenderersError(e);
return;
}
}
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter,
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE, audioCapabilities);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
sampleSource, new Id3Parser(), player, mainHandler.getLooper());
Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player,
mainHandler.getLooper());
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_METADATA] = id3Renderer;
renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer;
player.onRenderers(null, null, renderers, bandwidthMeter);
} }
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter,
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE, audioCapabilities);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
MetadataTrackRenderer<Map<String, Object>> id3Renderer =
new MetadataTrackRenderer<>(sampleSource, new Id3Parser(), player, mainHandler.getLooper());
Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player,
mainHandler.getLooper());
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_METADATA] = id3Renderer;
renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer;
callback.onRenderers(null, null, renderers, bandwidthMeter);
} }
} }
...@@ -28,7 +28,6 @@ import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; ...@@ -28,7 +28,6 @@ import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource; import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
import com.google.android.exoplayer.chunk.VideoFormatSelectorUtil; import com.google.android.exoplayer.chunk.VideoFormatSelectorUtil;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
...@@ -57,8 +56,7 @@ import java.util.Arrays; ...@@ -57,8 +56,7 @@ import java.util.Arrays;
/** /**
* A {@link RendererBuilder} for SmoothStreaming. * A {@link RendererBuilder} for SmoothStreaming.
*/ */
public class SmoothStreamingRendererBuilder implements RendererBuilder, public class SmoothStreamingRendererBuilder implements RendererBuilder {
ManifestFetcher.ManifestCallback<SmoothStreamingManifest> {
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;
...@@ -71,179 +69,218 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -71,179 +69,218 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
private final String url; private final String url;
private final MediaDrmCallback drmCallback; private final MediaDrmCallback drmCallback;
private DemoPlayer player; private AsyncRendererBuilder currentAsyncBuilder;
private RendererBuilderCallback callback;
private ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
public SmoothStreamingRendererBuilder(Context context, String userAgent, String url, public SmoothStreamingRendererBuilder(Context context, String userAgent, String url,
MediaDrmCallback drmCallback) { MediaDrmCallback drmCallback) {
this.context = context; this.context = context;
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = Util.toLowerInvariant(url).endsWith("/manifest") ? url : url + "/Manifest";
this.drmCallback = drmCallback; this.drmCallback = drmCallback;
} }
@Override @Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { public void buildRenderers(DemoPlayer player) {
this.player = player; currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player);
this.callback = callback; currentAsyncBuilder.init();
String manifestUrl = url;
if (!manifestUrl.endsWith("/Manifest")) {
manifestUrl += "/Manifest";
}
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
manifestFetcher = new ManifestFetcher<>(manifestUrl,
new DefaultHttpDataSource(userAgent, null), parser);
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
@Override @Override
public void onSingleManifestError(IOException exception) { public void cancel() {
callback.onRenderersError(exception); if (currentAsyncBuilder != null) {
currentAsyncBuilder.cancel();
currentAsyncBuilder = null;
}
} }
@Override private static final class AsyncRendererBuilder
public void onSingleManifest(SmoothStreamingManifest manifest) { implements ManifestFetcher.ManifestCallback<SmoothStreamingManifest> {
Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); private final Context context;
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player); private final String userAgent;
private final MediaDrmCallback drmCallback;
// Check drm support if necessary. private final DemoPlayer player;
DrmSessionManager drmSessionManager = null; private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
if (manifest.protectionElement != null) {
if (Util.SDK_INT < 18) { private boolean canceled;
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)); public AsyncRendererBuilder(Context context, String userAgent, String url,
return; MediaDrmCallback drmCallback, DemoPlayer player) {
} this.context = context;
try { this.userAgent = userAgent;
drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid, this.drmCallback = drmCallback;
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player); this.player = player;
} catch (UnsupportedDrmException e) { SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
callback.onRenderersError(e); manifestFetcher = new ManifestFetcher<>(url, new DefaultHttpDataSource(userAgent, null),
return; parser);
}
} }
// Obtain stream elements for playback. public void init() {
int audioStreamElementCount = 0; manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
int textStreamElementCount = 0; }
int videoStreamElementIndex = -1;
for (int i = 0; i < manifest.streamElements.length; i++) { public void cancel() {
if (manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) { canceled = true;
audioStreamElementCount++;
} else if (manifest.streamElements[i].type == StreamElement.TYPE_TEXT) {
textStreamElementCount++;
} else if (videoStreamElementIndex == -1
&& manifest.streamElements[i].type == StreamElement.TYPE_VIDEO) {
videoStreamElementIndex = i;
}
} }
// Determine which video tracks we should use for playback. @Override
int[] videoTrackIndices = null; public void onSingleManifestError(IOException exception) {
if (videoStreamElementIndex != -1) { if (canceled) {
try {
videoTrackIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(context,
Arrays.asList(manifest.streamElements[videoStreamElementIndex].tracks), null, false);
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return; return;
} }
}
// Build the video renderer. player.onRenderersError(exception);
final MediaCodecVideoTrackRenderer videoRenderer;
if (videoTrackIndices == null || videoTrackIndices.length == 0) {
videoRenderer = null;
} else {
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
videoStreamElementIndex, videoTrackIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
} }
// Build the audio renderer. @Override
final String[] audioTrackNames; public void onSingleManifest(SmoothStreamingManifest manifest) {
final MultiTrackChunkSource audioChunkSource; if (canceled) {
final MediaCodecAudioTrackRenderer audioRenderer; return;
if (audioStreamElementCount == 0) { }
audioTrackNames = null;
audioChunkSource = null; Handler mainHandler = player.getMainHandler();
audioRenderer = null; LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
} else { DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
audioTrackNames = new String[audioStreamElementCount];
ChunkSource[] audioChunkSources = new ChunkSource[audioStreamElementCount]; // Check drm support if necessary.
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DrmSessionManager drmSessionManager = null;
FormatEvaluator audioFormatEvaluator = new FormatEvaluator.FixedEvaluator(); if (manifest.protectionElement != null) {
audioStreamElementCount = 0; if (Util.SDK_INT < 18) {
player.onRenderersError(
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.onRenderersError(e);
return;
}
}
// Obtain stream elements for playback.
int audioStreamElementCount = 0;
int textStreamElementCount = 0;
int videoStreamElementIndex = -1;
for (int i = 0; i < manifest.streamElements.length; i++) { for (int i = 0; i < manifest.streamElements.length; i++) {
if (manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) { if (manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) {
audioTrackNames[audioStreamElementCount] = manifest.streamElements[i].name;
audioChunkSources[audioStreamElementCount] = new SmoothStreamingChunkSource(
manifestFetcher, i, new int[] {0}, audioDataSource, audioFormatEvaluator,
LIVE_EDGE_LATENCY_MS);
audioStreamElementCount++; audioStreamElementCount++;
} else if (manifest.streamElements[i].type == StreamElement.TYPE_TEXT) {
textStreamElementCount++;
} else if (videoStreamElementIndex == -1
&& manifest.streamElements[i].type == StreamElement.TYPE_VIDEO) {
videoStreamElementIndex = i;
} }
} }
audioChunkSource = new MultiTrackChunkSource(audioChunkSources);
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_AUDIO);
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true,
mainHandler, player);
}
// Build the text renderer. // Determine which video tracks we should use for playback.
final String[] textTrackNames; int[] videoTrackIndices = null;
final MultiTrackChunkSource textChunkSource; if (videoStreamElementIndex != -1) {
final TrackRenderer textRenderer; try {
if (textStreamElementCount == 0) { videoTrackIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(context,
textTrackNames = null; Arrays.asList(manifest.streamElements[videoStreamElementIndex].tracks), null, false);
textChunkSource = null; } catch (DecoderQueryException e) {
textRenderer = null; player.onRenderersError(e);
} else { return;
textTrackNames = new String[textStreamElementCount]; }
ChunkSource[] textChunkSources = new ChunkSource[textStreamElementCount]; }
DataSource ttmlDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
FormatEvaluator ttmlFormatEvaluator = new FormatEvaluator.FixedEvaluator(); // Build the video renderer.
textStreamElementCount = 0; final MediaCodecVideoTrackRenderer videoRenderer;
for (int i = 0; i < manifest.streamElements.length; i++) { if (videoTrackIndices == null || videoTrackIndices.length == 0) {
if (manifest.streamElements[i].type == StreamElement.TYPE_TEXT) { videoRenderer = null;
textTrackNames[textStreamElementCount] = manifest.streamElements[i].language; } else {
textChunkSources[textStreamElementCount] = new SmoothStreamingChunkSource(manifestFetcher, DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
i, new int[] {0}, ttmlDataSource, ttmlFormatEvaluator, LIVE_EDGE_LATENCY_MS); ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
textStreamElementCount++; videoStreamElementIndex, videoTrackIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
}
// Build the audio renderer.
final String[] audioTrackNames;
final MultiTrackChunkSource audioChunkSource;
final MediaCodecAudioTrackRenderer audioRenderer;
if (audioStreamElementCount == 0) {
audioTrackNames = null;
audioChunkSource = null;
audioRenderer = null;
} else {
audioTrackNames = new String[audioStreamElementCount];
ChunkSource[] audioChunkSources = new ChunkSource[audioStreamElementCount];
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
FormatEvaluator audioFormatEvaluator = new FormatEvaluator.FixedEvaluator();
audioStreamElementCount = 0;
for (int i = 0; i < manifest.streamElements.length; i++) {
if (manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) {
audioTrackNames[audioStreamElementCount] = manifest.streamElements[i].name;
audioChunkSources[audioStreamElementCount] = new SmoothStreamingChunkSource(
manifestFetcher, i, new int[] {0}, audioDataSource, audioFormatEvaluator,
LIVE_EDGE_LATENCY_MS);
audioStreamElementCount++;
}
} }
audioChunkSource = new MultiTrackChunkSource(audioChunkSources);
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_AUDIO);
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true,
mainHandler, player);
} }
textChunkSource = new MultiTrackChunkSource(textChunkSources);
ChunkSampleSource ttmlSampleSource = new ChunkSampleSource(textChunkSource, loadControl, // Build the text renderer.
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, final String[] textTrackNames;
DemoPlayer.TYPE_TEXT); final MultiTrackChunkSource textChunkSource;
textRenderer = new TextTrackRenderer(ttmlSampleSource, player, mainHandler.getLooper(), final TrackRenderer textRenderer;
new TtmlParser()); if (textStreamElementCount == 0) {
textTrackNames = null;
textChunkSource = null;
textRenderer = null;
} else {
textTrackNames = new String[textStreamElementCount];
ChunkSource[] textChunkSources = new ChunkSource[textStreamElementCount];
DataSource ttmlDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
FormatEvaluator ttmlFormatEvaluator = new FormatEvaluator.FixedEvaluator();
textStreamElementCount = 0;
for (int i = 0; i < manifest.streamElements.length; i++) {
if (manifest.streamElements[i].type == StreamElement.TYPE_TEXT) {
textTrackNames[textStreamElementCount] = manifest.streamElements[i].language;
textChunkSources[textStreamElementCount] = new SmoothStreamingChunkSource(
manifestFetcher, i, new int[] {0}, ttmlDataSource, ttmlFormatEvaluator,
LIVE_EDGE_LATENCY_MS);
textStreamElementCount++;
}
}
textChunkSource = new MultiTrackChunkSource(textChunkSources);
ChunkSampleSource ttmlSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_TEXT);
textRenderer = new TextTrackRenderer(ttmlSampleSource, player, mainHandler.getLooper(),
new TtmlParser());
}
// Invoke the callback.
String[][] trackNames = new String[DemoPlayer.RENDERER_COUNT][];
trackNames[DemoPlayer.TYPE_AUDIO] = audioTrackNames;
trackNames[DemoPlayer.TYPE_TEXT] = textTrackNames;
MultiTrackChunkSource[] multiTrackChunkSources =
new MultiTrackChunkSource[DemoPlayer.RENDERER_COUNT];
multiTrackChunkSources[DemoPlayer.TYPE_AUDIO] = audioChunkSource;
multiTrackChunkSources[DemoPlayer.TYPE_TEXT] = textChunkSource;
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
player.onRenderers(trackNames, multiTrackChunkSources, renderers, bandwidthMeter);
} }
// Invoke the callback.
String[][] trackNames = new String[DemoPlayer.RENDERER_COUNT][];
trackNames[DemoPlayer.TYPE_AUDIO] = audioTrackNames;
trackNames[DemoPlayer.TYPE_TEXT] = textTrackNames;
MultiTrackChunkSource[] multiTrackChunkSources =
new MultiTrackChunkSource[DemoPlayer.RENDERER_COUNT];
multiTrackChunkSources[DemoPlayer.TYPE_AUDIO] = audioChunkSource;
multiTrackChunkSources[DemoPlayer.TYPE_TEXT] = textChunkSource;
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
callback.onRenderers(trackNames, multiTrackChunkSources, renderers, bandwidthMeter);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment