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,17 +111,61 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -120,17 +111,61 @@ public class DashRendererBuilder implements RendererBuilder,
} }
@Override @Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { public void buildRenderers(DemoPlayer player) {
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback,
audioCapabilities, player);
currentAsyncBuilder.init();
}
@Override
public void cancel() {
if (currentAsyncBuilder != null) {
currentAsyncBuilder.cancel();
currentAsyncBuilder = null;
}
}
private static final class AsyncRendererBuilder
implements ManifestFetcher.ManifestCallback<MediaPresentationDescription>, UtcTimingCallback {
private final Context context;
private final String userAgent;
private final MediaDrmCallback drmCallback;
private final AudioCapabilities audioCapabilities;
private final DemoPlayer player;
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
private final UriDataSource manifestDataSource;
private boolean canceled;
private MediaPresentationDescription manifest;
private long elapsedRealtimeOffset;
public AsyncRendererBuilder(Context context, String userAgent, String url,
MediaDrmCallback drmCallback, AudioCapabilities audioCapabilities, DemoPlayer player) {
this.context = context;
this.userAgent = userAgent;
this.drmCallback = drmCallback;
this.audioCapabilities = audioCapabilities;
this.player = player; this.player = player;
this.callback = callback;
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
manifestDataSource = new DefaultUriDataSource(context, userAgent); manifestDataSource = new DefaultUriDataSource(context, userAgent);
manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser); manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser);
}
public void init() {
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
public void cancel() {
canceled = true;
}
@Override @Override
public void onSingleManifest(MediaPresentationDescription manifest) { public void onSingleManifest(MediaPresentationDescription manifest) {
if (canceled) {
return;
}
this.manifest = manifest; this.manifest = manifest;
if (manifest.dynamic && manifest.utcTiming != null) { if (manifest.dynamic && manifest.utcTiming != null) {
UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming, UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming,
...@@ -142,17 +177,29 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -142,17 +177,29 @@ public class DashRendererBuilder implements RendererBuilder,
@Override @Override
public void onSingleManifestError(IOException e) { public void onSingleManifestError(IOException e) {
callback.onRenderersError(e); if (canceled) {
return;
}
player.onRenderersError(e);
} }
@Override @Override
public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) { public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) {
if (canceled) {
return;
}
this.elapsedRealtimeOffset = elapsedRealtimeOffset; this.elapsedRealtimeOffset = elapsedRealtimeOffset;
buildRenderers(); buildRenderers();
} }
@Override @Override
public void onTimestampError(UtcTimingElement utcTiming, IOException e) { public void onTimestampError(UtcTimingElement utcTiming, IOException e) {
if (canceled) {
return;
}
Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e); Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e);
// Be optimistic and continue in the hope that the device clock is correct. // Be optimistic and continue in the hope that the device clock is correct.
buildRenderers(); buildRenderers();
...@@ -180,7 +227,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -180,7 +227,7 @@ public class DashRendererBuilder implements RendererBuilder,
// Fail if we have neither video or audio. // Fail if we have neither video or audio.
if (videoAdaptationSet == null && audioAdaptationSet == null) { if (videoAdaptationSet == null && audioAdaptationSet == null) {
callback.onRenderersError(new IllegalStateException("No video or audio adaptation sets")); player.onRenderersError(new IllegalStateException("No video or audio adaptation sets"));
return; return;
} }
...@@ -189,7 +236,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -189,7 +236,7 @@ public class DashRendererBuilder implements RendererBuilder,
StreamingDrmSessionManager drmSessionManager = null; StreamingDrmSessionManager drmSessionManager = null;
if (hasContentProtection) { if (hasContentProtection) {
if (Util.SDK_INT < 18) { if (Util.SDK_INT < 18) {
callback.onRenderersError( player.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)); new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
return; return;
} }
...@@ -199,7 +246,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -199,7 +246,7 @@ public class DashRendererBuilder implements RendererBuilder,
filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection() filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection()
&& getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1; && getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1;
} catch (UnsupportedDrmException e) { } catch (UnsupportedDrmException e) {
callback.onRenderersError(e); player.onRenderersError(e);
return; return;
} }
} }
...@@ -211,7 +258,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -211,7 +258,7 @@ public class DashRendererBuilder implements RendererBuilder,
videoRepresentationIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay( videoRepresentationIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, videoAdaptationSet.representations, null, filterHdContent); context, videoAdaptationSet.representations, null, filterHdContent);
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
callback.onRenderersError(e); player.onRenderersError(e);
return; return;
} }
} }
...@@ -343,7 +390,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -343,7 +390,7 @@ public class DashRendererBuilder 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(trackNames, multiTrackChunkSources, renderers, bandwidthMeter); player.onRenderers(trackNames, multiTrackChunkSources, renderers, bandwidthMeter);
} }
private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) { private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) {
...@@ -352,4 +399,6 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -352,4 +399,6 @@ public class DashRendererBuilder implements RendererBuilder,
.equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN; .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}.
*
* @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.
*/
void onRenderers(String[][] trackNames, MultiTrackChunkSource[] multiTrackSources,
TrackRenderer[] renderers, BandwidthMeter bandwidthMeter);
/** /**
* Invoked if a {@link RendererBuilder} encounters an error. * Cancels the current build operation, if there is one. Else does nothing.
* * <p>
* @param e Describes the error. * A canceled build operation must not invoke {@link DemoPlayer#onRenderers} or
* {@link DemoPlayer#onRenderersError} on the player, which may have been released.
*/ */
void onRenderersError(Exception e); void cancel();
} }
/** /**
...@@ -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,22 +70,66 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -72,22 +70,66 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
} }
@Override @Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { public void buildRenderers(DemoPlayer player) {
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, audioCapabilities,
player);
currentAsyncBuilder.init();
}
@Override
public void cancel() {
if (currentAsyncBuilder != null) {
currentAsyncBuilder.cancel();
currentAsyncBuilder = null;
}
}
private static final class AsyncRendererBuilder implements ManifestCallback<HlsPlaylist> {
private final Context context;
private final String userAgent;
private final String url;
private final AudioCapabilities audioCapabilities;
private final DemoPlayer player;
private final ManifestFetcher<HlsPlaylist> playlistFetcher;
private boolean canceled;
public AsyncRendererBuilder(Context context, String userAgent, String url,
AudioCapabilities audioCapabilities, DemoPlayer player) {
this.context = context;
this.userAgent = userAgent;
this.url = url;
this.audioCapabilities = audioCapabilities;
this.player = player; this.player = player;
this.callback = callback;
HlsPlaylistParser parser = new HlsPlaylistParser(); HlsPlaylistParser parser = new HlsPlaylistParser();
ManifestFetcher<HlsPlaylist> playlistFetcher = new ManifestFetcher<>(url, playlistFetcher = new ManifestFetcher<>(url, new DefaultUriDataSource(context, userAgent),
new DefaultUriDataSource(context, userAgent), parser); parser);
}
public void init() {
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
public void cancel() {
canceled = true;
}
@Override @Override
public void onSingleManifestError(IOException e) { public void onSingleManifestError(IOException e) {
callback.onRenderersError(e); if (canceled) {
return;
}
player.onRenderersError(e);
} }
@Override @Override
public void onSingleManifest(HlsPlaylist manifest) { public void onSingleManifest(HlsPlaylist manifest) {
if (canceled) {
return;
}
Handler mainHandler = player.getMainHandler(); Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
...@@ -99,7 +141,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -99,7 +141,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay( variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, masterPlaylist.variants, null, false); context, masterPlaylist.variants, null, false);
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
callback.onRenderersError(e); player.onRenderersError(e);
return; return;
} }
} }
...@@ -112,10 +154,8 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -112,10 +154,8 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource); MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
MetadataTrackRenderer<Map<String, Object>> id3Renderer = sampleSource, new Id3Parser(), player, mainHandler.getLooper());
new MetadataTrackRenderer<>(sampleSource, new Id3Parser(), player, mainHandler.getLooper());
Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player, Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player,
mainHandler.getLooper()); mainHandler.getLooper());
...@@ -124,7 +164,9 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -124,7 +164,9 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_METADATA] = id3Renderer; renderers[DemoPlayer.TYPE_METADATA] = id3Renderer;
renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer; renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer;
callback.onRenderers(null, null, renderers, bandwidthMeter); player.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,39 +69,75 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -71,39 +69,75 @@ 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"; @Override
public void cancel() {
if (currentAsyncBuilder != null) {
currentAsyncBuilder.cancel();
currentAsyncBuilder = null;
} }
}
private static final class AsyncRendererBuilder
implements ManifestFetcher.ManifestCallback<SmoothStreamingManifest> {
private final Context context;
private final String userAgent;
private final MediaDrmCallback drmCallback;
private final DemoPlayer player;
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
private boolean canceled;
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(); SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
manifestFetcher = new ManifestFetcher<>(manifestUrl, manifestFetcher = new ManifestFetcher<>(url, new DefaultHttpDataSource(userAgent, null),
new DefaultHttpDataSource(userAgent, null), parser); parser);
}
public void init() {
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
public void cancel() {
canceled = true;
}
@Override @Override
public void onSingleManifestError(IOException exception) { public void onSingleManifestError(IOException exception) {
callback.onRenderersError(exception); if (canceled) {
return;
}
player.onRenderersError(exception);
} }
@Override @Override
public void onSingleManifest(SmoothStreamingManifest manifest) { public void onSingleManifest(SmoothStreamingManifest manifest) {
if (canceled) {
return;
}
Handler mainHandler = player.getMainHandler(); Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
...@@ -112,7 +146,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -112,7 +146,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
DrmSessionManager drmSessionManager = null; DrmSessionManager drmSessionManager = null;
if (manifest.protectionElement != null) { if (manifest.protectionElement != null) {
if (Util.SDK_INT < 18) { if (Util.SDK_INT < 18) {
callback.onRenderersError( player.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)); new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
return; return;
} }
...@@ -120,7 +154,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -120,7 +154,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid, drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid,
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player); player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
} catch (UnsupportedDrmException e) { } catch (UnsupportedDrmException e) {
callback.onRenderersError(e); player.onRenderersError(e);
return; return;
} }
} }
...@@ -147,7 +181,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -147,7 +181,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
videoTrackIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(context, videoTrackIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(context,
Arrays.asList(manifest.streamElements[videoStreamElementIndex].tracks), null, false); Arrays.asList(manifest.streamElements[videoStreamElementIndex].tracks), null, false);
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
callback.onRenderersError(e); player.onRenderersError(e);
return; return;
} }
} }
...@@ -216,8 +250,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -216,8 +250,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
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_TEXT) { if (manifest.streamElements[i].type == StreamElement.TYPE_TEXT) {
textTrackNames[textStreamElementCount] = manifest.streamElements[i].language; textTrackNames[textStreamElementCount] = manifest.streamElements[i].language;
textChunkSources[textStreamElementCount] = new SmoothStreamingChunkSource(manifestFetcher, textChunkSources[textStreamElementCount] = new SmoothStreamingChunkSource(
i, new int[] {0}, ttmlDataSource, ttmlFormatEvaluator, LIVE_EDGE_LATENCY_MS); manifestFetcher, i, new int[] {0}, ttmlDataSource, ttmlFormatEvaluator,
LIVE_EDGE_LATENCY_MS);
textStreamElementCount++; textStreamElementCount++;
} }
} }
...@@ -243,7 +278,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -243,7 +278,9 @@ public class SmoothStreamingRendererBuilder 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(trackNames, multiTrackChunkSources, renderers, bandwidthMeter); player.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