diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java index cf69bae90d..533f5a527d 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer.demo.player; import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.MultiSampleSource; +import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.chunk.ChunkSampleSource; import com.google.android.exoplayer.chunk.ChunkSource; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; @@ -25,27 +26,22 @@ import com.google.android.exoplayer.dash.DashChunkSource; import com.google.android.exoplayer.dash.mpd.AdaptationSet; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser; -import com.google.android.exoplayer.dash.mpd.UtcTimingElement; -import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver; -import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback; import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder; import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.DefaultUriDataSource; -import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.util.ManifestFetcher; import android.content.Context; import android.os.Handler; -import android.util.Log; - -import java.io.IOException; /** * A {@link SourceBuilder} for DASH. */ +// TODO[REFACTOR]: Bring back DRM support. +// TODO[REFACTOR]: Bring back UTC timing element support. public class DashSourceBuilder implements SourceBuilder { private static final String TAG = "DashSourceBuilder"; @@ -61,8 +57,6 @@ public class DashSourceBuilder implements SourceBuilder { private final String url; private final MediaDrmCallback drmCallback; - private AsyncRendererBuilder currentAsyncBuilder; - public DashSourceBuilder(Context context, String userAgent, String url, MediaDrmCallback drmCallback) { this.context = context; @@ -72,172 +66,41 @@ public class DashSourceBuilder implements SourceBuilder { } @Override - public void buildRenderers(DemoPlayer player) { - currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player); - currentAsyncBuilder.init(); - } + public SampleSource buildRenderers(DemoPlayer player) { + MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); + DefaultUriDataSource manifestDataSource = new DefaultUriDataSource(context, userAgent); + ManifestFetcher manifestFetcher = new ManifestFetcher<>(url, + manifestDataSource, parser); - @Override - public void cancel() { - if (currentAsyncBuilder != null) { - currentAsyncBuilder.cancel(); - currentAsyncBuilder = null; - } - } + Handler mainHandler = player.getMainHandler(); + BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); + LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); - private static final class AsyncRendererBuilder - implements ManifestFetcher.ManifestCallback, UtcTimingCallback { + // Build the video renderer. + DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO, + videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, + 0, mainHandler, player, DemoPlayer.TYPE_VIDEO); + ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, + VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); - private final Context context; - private final String userAgent; - private final MediaDrmCallback drmCallback; - private final DemoPlayer player; - private final ManifestFetcher manifestFetcher; - private final UriDataSource manifestDataSource; + // Build the audio renderer. + DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO, + audioDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_AUDIO); + ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, + AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, + DemoPlayer.TYPE_AUDIO); - private boolean canceled; - private MediaPresentationDescription manifest; - private long elapsedRealtimeOffset; + // Build the text renderer. + DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT, + textDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_TEXT); + ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, + TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, + DemoPlayer.TYPE_TEXT); - public AsyncRendererBuilder(Context context, String userAgent, String url, - MediaDrmCallback drmCallback, DemoPlayer player) { - this.context = context; - this.userAgent = userAgent; - this.drmCallback = drmCallback; - this.player = player; - MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); - manifestDataSource = new DefaultUriDataSource(context, userAgent); - manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser); - } - - public void init() { - manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); - } - - public void cancel() { - canceled = true; - } - - @Override - public void onSingleManifest(MediaPresentationDescription manifest) { - if (canceled) { - return; - } - - this.manifest = manifest; - if (manifest.dynamic && manifest.utcTiming != null) { - UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming, - manifestFetcher.getManifestLoadCompleteTimestamp(), this); - } else { - buildRenderers(); - } - } - - @Override - public void onSingleManifestError(IOException e) { - if (canceled) { - return; - } - - player.onSourceBuilderError(e); - } - - @Override - public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) { - if (canceled) { - return; - } - - this.elapsedRealtimeOffset = elapsedRealtimeOffset; - buildRenderers(); - } - - @Override - public void onTimestampError(UtcTimingElement utcTiming, IOException e) { - if (canceled) { - return; - } - - Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e); - // Be optimistic and continue in the hope that the device clock is correct. - buildRenderers(); - } - - private void buildRenderers() { - // TODO[REFACTOR]: Bring back DRM support. - /* - Period period = manifest.getPeriod(0); - boolean hasContentProtection = false; - for (int i = 0; i < period.adaptationSets.size(); i++) { - AdaptationSet adaptationSet = period.adaptationSets.get(i); - if (adaptationSet.type != AdaptationSet.TYPE_UNKNOWN) { - hasContentProtection |= adaptationSet.hasContentProtection(); - } - } - - // Check drm support if necessary. - boolean filterHdContent = false; - StreamingDrmSessionManager drmSessionManager = null; - if (hasContentProtection) { - if (Util.SDK_INT < 18) { - player.onSourceBuilderError( - new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)); - return; - } - try { - drmSessionManager = StreamingDrmSessionManager.newWidevineInstance( - player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player); - filterHdContent = getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1; - } catch (UnsupportedDrmException e) { - player.onSourceBuilderError(e); - return; - } - } - */ - - Handler mainHandler = player.getMainHandler(); - BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); - LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); - - // Build the video renderer. - DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO, - videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, - elapsedRealtimeOffset, mainHandler, player, DemoPlayer.TYPE_VIDEO); - ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, - VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, - DemoPlayer.TYPE_VIDEO); - - // Build the audio renderer. - DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO, - audioDataSource, null, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, mainHandler, player, - DemoPlayer.TYPE_AUDIO); - ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, - AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, - DemoPlayer.TYPE_AUDIO); - - // Build the text renderer. - DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT, - textDataSource, null, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, mainHandler, player, - DemoPlayer.TYPE_TEXT); - ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, - TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, - DemoPlayer.TYPE_TEXT); - - // Invoke the callback. - player.onSource( - new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource)); - } - - /* - private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) { - String securityLevelProperty = sessionManager.getPropertyString("securityLevel"); - return securityLevelProperty.equals("L1") ? SECURITY_LEVEL_1 : securityLevelProperty - .equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN; - } - */ + return new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource); } } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java index 26d7487b84..ddb2024bbc 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java @@ -69,24 +69,16 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi MetadataRenderer>, DebugTextViewHelper.Provider { /** - * Builds renderers for the player. + * Builds a source to play. */ public interface SourceBuilder { /** - * Builds renderers for playback. + * Builds a source to play. * - * @param player The player for which renderers are being built. {@link DemoPlayer#onSource} - * should be invoked once the renderers have been built. If building fails, - * {@link DemoPlayer#onSourceBuilderError} should be invoked. + * @param player The player for which renderers are being built. + * @return SampleSource The source to play. */ - void buildRenderers(DemoPlayer player); - /** - * Cancels the current build operation, if there is one. Else does nothing. - *

- * A canceled build operation must not invoke {@link DemoPlayer#onSource} or - * {@link DemoPlayer#onSourceBuilderError} on the player, which may have been released. - */ - void cancel(); + SampleSource buildRenderers(DemoPlayer player); } /** @@ -164,10 +156,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi public static final int TYPE_TEXT = 2; public static final int TYPE_METADATA = 3; - private static final int SOURCE_BUILDING_STATE_IDLE = 1; - private static final int SOURCE_BUILDING_STATE_BUILDING = 2; - private static final int SOURCE_BUILDING_STATE_BUILT = 3; - private final ExoPlayer player; private final SourceBuilder sourceBuilder; private final BandwidthMeter bandwidthMeter; @@ -176,10 +164,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi private final Handler mainHandler; private final CopyOnWriteArrayList listeners; - private int sourceBuildingState; - private int lastReportedPlaybackState; - private boolean lastReportedPlayWhenReady; - private Surface surface; private Format videoFormat; private int videoTrackToRestore; @@ -215,8 +199,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi playerControl = new PlayerControl(player); // Set initial state, with the text renderer initially disabled. - lastReportedPlaybackState = STATE_IDLE; - sourceBuildingState = SOURCE_BUILDING_STATE_IDLE; player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED); } @@ -300,39 +282,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi } public void prepare() { - if (sourceBuildingState == SOURCE_BUILDING_STATE_BUILT) { - player.stop(); - } - sourceBuilder.cancel(); - sourceBuildingState = SOURCE_BUILDING_STATE_BUILDING; - maybeReportPlayerState(); - sourceBuilder.buildRenderers(this); - } - - /** - * Invoked with the results from a {@link SourceBuilder}. - * - * @param source The {@link SampleSource} to play. - */ - /* package */ void onSource(SampleSource source) { - player.prepare(source); - sourceBuildingState = SOURCE_BUILDING_STATE_BUILT; - } - - /** - * Invoked if a {@link SourceBuilder} encounters an error. - * - * @param e Describes the error. - */ - /* package */ void onSourceBuilderError(Exception e) { - if (internalErrorListener != null) { - internalErrorListener.onRendererInitializationError(e); - } - for (Listener listener : listeners) { - listener.onError(e); - } - sourceBuildingState = SOURCE_BUILDING_STATE_IDLE; - maybeReportPlayerState(); + player.prepare(sourceBuilder.buildRenderers(this)); } public void setPlayWhenReady(boolean playWhenReady) { @@ -344,23 +294,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi } public void release() { - sourceBuilder.cancel(); - sourceBuildingState = SOURCE_BUILDING_STATE_IDLE; surface = null; player.release(); } public int getPlaybackState() { - if (sourceBuildingState == SOURCE_BUILDING_STATE_BUILDING) { - return STATE_PREPARING; - } - int playerState = player.getPlaybackState(); - if (sourceBuildingState == SOURCE_BUILDING_STATE_BUILT && playerState == STATE_IDLE) { - // This is an edge case where the renderers are built, but are still being passed to the - // player's playback thread. - return STATE_PREPARING; - } - return playerState; + return player.getPlaybackState(); } @Override @@ -401,12 +340,13 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi @Override public void onPlayerStateChanged(boolean playWhenReady, int state) { - maybeReportPlayerState(); + for (Listener listener : listeners) { + listener.onStateChanged(playWhenReady, state); + } } @Override public void onPlayerError(ExoPlaybackException exception) { - sourceBuildingState = SOURCE_BUILDING_STATE_IDLE; for (Listener listener : listeners) { listener.onError(exception); } @@ -569,18 +509,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi // Do nothing. } - private void maybeReportPlayerState() { - boolean playWhenReady = player.getPlayWhenReady(); - int playbackState = getPlaybackState(); - if (lastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) { - for (Listener listener : listeners) { - listener.onStateChanged(playWhenReady, playbackState); - } - lastReportedPlayWhenReady = playWhenReady; - lastReportedPlaybackState = playbackState; - } - } - private void pushSurface(boolean blockForSurfacePush) { if (blockForSurfacePush) { player.blockingSendMessage( diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorSourceBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorSourceBuilder.java index a037c7f562..ccc7a45407 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorSourceBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorSourceBuilder.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer.demo.player; +import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorSampleSource; @@ -45,18 +46,12 @@ public class ExtractorSourceBuilder implements SourceBuilder { } @Override - public void buildRenderers(DemoPlayer player) { + public SampleSource buildRenderers(DemoPlayer player) { Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE); DataSource dataSource = new DefaultUriDataSource(context, player.getBandwidthMeter(), userAgent); - ExtractorSampleSource source = new ExtractorSampleSource(uri, dataSource, allocator, + return new ExtractorSampleSource(uri, dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE); - player.onSource(source); - } - - @Override - public void cancel() { - // Do nothing. } } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsSourceBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsSourceBuilder.java index 60652be5c5..804ef20fa4 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsSourceBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsSourceBuilder.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer.demo.player; import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.LoadControl; +import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder; import com.google.android.exoplayer.hls.HlsChunkSource; import com.google.android.exoplayer.hls.HlsPlaylist; @@ -28,16 +29,14 @@ import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.DefaultUriDataSource; import com.google.android.exoplayer.util.ManifestFetcher; -import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import android.content.Context; import android.os.Handler; -import java.io.IOException; - /** * A {@link SourceBuilder} for HLS. */ +// TODO[REFACTOR]: Bring back caption support. public class HlsSourceBuilder implements SourceBuilder { private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; @@ -47,8 +46,6 @@ public class HlsSourceBuilder implements SourceBuilder { private final String userAgent; private final String url; - private AsyncRendererBuilder currentAsyncBuilder; - public HlsSourceBuilder(Context context, String userAgent, String url) { this.context = context; this.userAgent = userAgent; @@ -56,98 +53,22 @@ public class HlsSourceBuilder implements SourceBuilder { } @Override - public void buildRenderers(DemoPlayer player) { - currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, player); - currentAsyncBuilder.init(); - } + public SampleSource buildRenderers(DemoPlayer player) { + HlsPlaylistParser parser = new HlsPlaylistParser(); + DefaultUriDataSource manifestDataSource = new DefaultUriDataSource(context, userAgent); + ManifestFetcher manifestFetcher = new ManifestFetcher<>(url, + manifestDataSource, parser); - @Override - public void cancel() { - if (currentAsyncBuilder != null) { - currentAsyncBuilder.cancel(); - currentAsyncBuilder = null; - } - } - - private static final class AsyncRendererBuilder implements ManifestCallback { - - private final Context context; - private final String userAgent; - private final String url; - private final DemoPlayer player; - private final ManifestFetcher playlistFetcher; - - private boolean canceled; - - public AsyncRendererBuilder(Context context, String userAgent, String url, DemoPlayer player) { - this.context = context; - this.userAgent = userAgent; - this.url = url; - this.player = player; - HlsPlaylistParser parser = new HlsPlaylistParser(); - playlistFetcher = new ManifestFetcher<>(url, new DefaultUriDataSource(context, userAgent), - parser); - } - - public void init() { - playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); - } - - public void cancel() { - canceled = true; - } - - @Override - public void onSingleManifestError(IOException e) { - if (canceled) { - return; - } - - player.onSourceBuilderError(e); - } - - @Override - public void onSingleManifest(HlsPlaylist manifest) { - if (canceled) { - return; - } - - Handler mainHandler = player.getMainHandler(); - BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); - LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); - PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); - - // Build the video/audio/metadata renderers. - DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - HlsChunkSource chunkSource = new HlsChunkSource(HlsChunkSource.TYPE_DEFAULT, dataSource, url, - manifest, bandwidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE); - HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, - MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); - - // TODO[REFACTOR]: Bring back caption support. - // Build the text renderer, preferring Webvtt where available. - /* - boolean preferWebvtt = false; - if (manifest instanceof HlsMasterPlaylist) { - preferWebvtt = !((HlsMasterPlaylist) manifest).subtitles.isEmpty(); - } - TrackRenderer textRenderer; - if (preferWebvtt) { - DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - HlsChunkSource textChunkSource = new HlsChunkSource(false, textDataSource, - url, manifest, DefaultHlsTrackSelector.newVttInstance(), bandwidthMeter, - timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE); - HlsSampleSource textSampleSource = new HlsSampleSource(textChunkSource, loadControl, - TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT); - textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper()); - } else { - textRenderer = new Eia608TrackRenderer(sampleSource, player, mainHandler.getLooper()); - } - */ - - player.onSource(sampleSource); - } + Handler mainHandler = player.getMainHandler(); + BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); + LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); + PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); + DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + HlsChunkSource chunkSource = new HlsChunkSource(manifestFetcher, HlsChunkSource.TYPE_DEFAULT, + dataSource, bandwidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE); + return new HlsSampleSource(chunkSource, loadControl, + MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); } } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingSourceBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingSourceBuilder.java index 25aa9e99c6..389c0fb2a2 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingSourceBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingSourceBuilder.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer.demo.player; import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.MultiSampleSource; +import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.chunk.ChunkSampleSource; import com.google.android.exoplayer.chunk.ChunkSource; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; @@ -37,11 +38,10 @@ import com.google.android.exoplayer.util.Util; import android.content.Context; import android.os.Handler; -import java.io.IOException; - /** * A {@link SourceBuilder} for SmoothStreaming. */ +// TODO[REFACTOR]: Bring back DRM support. public class SmoothStreamingSourceBuilder implements SourceBuilder { private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; @@ -55,8 +55,6 @@ public class SmoothStreamingSourceBuilder implements SourceBuilder { private final String url; private final MediaDrmCallback drmCallback; - private AsyncRendererBuilder currentAsyncBuilder; - public SmoothStreamingSourceBuilder(Context context, String userAgent, String url, MediaDrmCallback drmCallback) { this.context = context; @@ -66,118 +64,40 @@ public class SmoothStreamingSourceBuilder implements SourceBuilder { } @Override - public void buildRenderers(DemoPlayer player) { - currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player); - currentAsyncBuilder.init(); - } + public SampleSource buildRenderers(DemoPlayer player) { + SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser(); + ManifestFetcher manifestFetcher = new ManifestFetcher<>(url, + new DefaultHttpDataSource(userAgent, null), parser); + Handler mainHandler = player.getMainHandler(); + BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); + LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); - @Override - public void cancel() { - if (currentAsyncBuilder != null) { - currentAsyncBuilder.cancel(); - currentAsyncBuilder = null; - } - } + // Build the video renderer. + DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher, + SmoothStreamingManifest.StreamElement.TYPE_VIDEO, + videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS); + ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, + VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, + DemoPlayer.TYPE_VIDEO); - private static final class AsyncRendererBuilder - implements ManifestFetcher.ManifestCallback { + // Build the audio renderer. + DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher, + SmoothStreamingManifest.StreamElement.TYPE_AUDIO, audioDataSource, null, + LIVE_EDGE_LATENCY_MS); + ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, + AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_AUDIO); - private final Context context; - private final String userAgent; - private final MediaDrmCallback drmCallback; - private final DemoPlayer player; - private final ManifestFetcher 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(); - manifestFetcher = new ManifestFetcher<>(url, new DefaultHttpDataSource(userAgent, null), - parser); - } - - public void init() { - manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); - } - - public void cancel() { - canceled = true; - } - - @Override - public void onSingleManifestError(IOException exception) { - if (canceled) { - return; - } - - player.onSourceBuilderError(exception); - } - - @Override - public void onSingleManifest(SmoothStreamingManifest manifest) { - if (canceled) { - return; - } - - // TODO[REFACTOR]: Bring back DRM support. - /* - // Check drm support if necessary. - DrmSessionManager drmSessionManager = null; - if (manifest.protectionElement != null) { - if (Util.SDK_INT < 18) { - player.onSourceBuilderError( - new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)); - return; - } - try { - drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid, - player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player); - } catch (UnsupportedDrmException e) { - player.onSourceBuilderError(e); - return; - } - } - */ - - Handler mainHandler = player.getMainHandler(); - BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); - LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); - - // Build the video renderer. - DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher, - SmoothStreamingManifest.StreamElement.TYPE_VIDEO, - videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS); - ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, - VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, - DemoPlayer.TYPE_VIDEO); - - // Build the audio renderer. - DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher, - SmoothStreamingManifest.StreamElement.TYPE_AUDIO, audioDataSource, null, - LIVE_EDGE_LATENCY_MS); - ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, - AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_AUDIO); - - // Build the text renderer. - DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); - ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher, - SmoothStreamingManifest.StreamElement.TYPE_TEXT, textDataSource, null, - LIVE_EDGE_LATENCY_MS); - ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, - TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT); - - // Invoke the callback. - player.onSource( - new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource)); - } + // Build the text renderer. + DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher, + SmoothStreamingManifest.StreamElement.TYPE_TEXT, textDataSource, null, + LIVE_EDGE_LATENCY_MS); + ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, + TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT); + return new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource); } } diff --git a/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java b/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java index 01b61ab299..35192648a0 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java @@ -15,37 +15,11 @@ */ package com.google.android.exoplayer.dash; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.google.android.exoplayer.TimeRange; -import com.google.android.exoplayer.chunk.ChunkOperationHolder; import com.google.android.exoplayer.chunk.Format; -import com.google.android.exoplayer.chunk.InitializationChunk; -import com.google.android.exoplayer.chunk.MediaChunk; -import com.google.android.exoplayer.dash.mpd.AdaptationSet; -import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription; -import com.google.android.exoplayer.dash.mpd.Period; -import com.google.android.exoplayer.dash.mpd.RangedUri; -import com.google.android.exoplayer.dash.mpd.Representation; -import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase; -import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList; -import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate; -import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement; -import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; -import com.google.android.exoplayer.dash.mpd.UrlTemplate; import com.google.android.exoplayer.testutil.TestUtil; -import com.google.android.exoplayer.upstream.DataSource; -import com.google.android.exoplayer.util.FakeClock; -import com.google.android.exoplayer.util.ManifestFetcher; -import com.google.android.exoplayer.util.Util; import android.test.InstrumentationTestCase; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - /** * Tests {@link DashChunkSource}. */ @@ -80,6 +54,8 @@ public class DashChunkSourceTest extends InstrumentationTestCase { TestUtil.setUpMockito(this); } + // TODO[REFACTOR]: Restore tests. + /* public void testGetAvailableRangeOnVod() { DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(), AdaptationSet.TYPE_VIDEO, null, null); @@ -490,5 +466,6 @@ public class DashChunkSourceTest extends InstrumentationTestCase { chunkSource.getChunkOperation(queue, seekPositionMs * 1000, out); assertNull(out.chunk); } + */ } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index 4d2e415030..5bcac4eb95 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -150,7 +150,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call return true; } if (!chunkSource.prepare()) { - maybeThrowError(); return false; } durationUs = C.UNKNOWN_TIME_US; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java index 7672703ed9..dc4bb47dff 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java @@ -31,8 +31,10 @@ import java.util.List; public interface ChunkSource { /** - * If the source is currently having difficulty preparing or providing chunks, then this method - * throws the underlying error. Otherwise does nothing. + * If the source is currently having difficulty providing chunks, then this method throws the + * underlying error. Otherwise does nothing. + *

+ * This method should only be called after the source has been prepared. * * @throws IOException The underlying error. */ @@ -44,8 +46,9 @@ public interface ChunkSource { * The method can be called repeatedly until the return value indicates success. * * @return True if the source was prepared, false otherwise. + * @throws IOException If an error occurs preparing the source. */ - boolean prepare(); + boolean prepare() throws IOException; /** * Gets the group of tracks provided by the source. diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index 7f99b71a2c..cc7f8ca3d9 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -57,7 +57,6 @@ import android.util.SparseArray; import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -118,10 +117,10 @@ public class DashChunkSource implements ChunkSource { private final long liveEdgeLatencyUs; private final long elapsedRealtimeOffsetUs; private final long[] availableRangeValues; - private final boolean live; private final int eventSourceId; - private boolean prepareCalled; + private boolean manifestFetcherEnabled; + private boolean live; private MediaPresentationDescription currentManifest; private MediaPresentationDescription processedManifest; private int nextPeriodHolderIndex; @@ -141,63 +140,7 @@ public class DashChunkSource implements ChunkSource { private int adaptiveMaxHeight; /** - * Lightweight constructor to use for fixed duration content. - * - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - * @param durationMs The duration of the content. - * @param adaptationSetType The type of the adaptation set to which the representations belong. - * One of {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and - * {@link AdaptationSet#TYPE_TEXT}. - * @param representations The representations to be considered by the source. - */ - public DashChunkSource(DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, - long durationMs, int adaptationSetType, Representation... representations) { - this(dataSource, adaptiveFormatEvaluator, durationMs, adaptationSetType, - Arrays.asList(representations)); - } - - /** - * Lightweight constructor to use for fixed duration content. - * - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - * @param durationMs The duration of the content. - * @param adaptationSetType The type of the adaptation set to which the representations belong. - * One of {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and - * {@link AdaptationSet#TYPE_TEXT}. - * @param representations The representations to be considered by the source. - */ - public DashChunkSource(DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, - long durationMs, int adaptationSetType, List representations) { - this(buildManifest(durationMs, adaptationSetType, representations), adaptationSetType, - dataSource, adaptiveFormatEvaluator); - } - - /** - * Constructor to use for fixed duration content. - * - * @param manifest The manifest. - * @param adaptationSetType The type of the adaptation set exposed by this source. One of - * {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and - * {@link AdaptationSet#TYPE_TEXT}. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - */ - public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetType, - DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) { - this(null, manifest, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(), - 0, 0, false, null, null, 0); - } - - /** - * Constructor to use for live streaming. - *

- * May also be used for fixed duration content, in which case the call is equivalent to calling - * the other constructor, passing {@code manifestFetcher.getManifest()} is the first argument. - * - * @param manifestFetcher A fetcher for the manifest, which must have already successfully - * completed an initial load. + * @param manifestFetcher A fetcher for the manifest. * @param adaptationSetType The type of the adaptation set exposed by this source. One of * {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and * {@link AdaptationSet#TYPE_TEXT}. @@ -220,16 +163,15 @@ public class DashChunkSource implements ChunkSource { int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, Handler eventHandler, EventListener eventListener, int eventSourceId) { - this(manifestFetcher, manifestFetcher.getManifest(), adaptationSetType, - dataSource, adaptiveFormatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000, - elapsedRealtimeOffsetMs * 1000, true, eventHandler, eventListener, eventSourceId); + this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(), + liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, true, eventHandler, eventListener, + eventSourceId); } /** * Constructor to use for live DVR streaming. * - * @param manifestFetcher A fetcher for the manifest, which must have already successfully - * completed an initial load. + * @param manifestFetcher A fetcher for the manifest. * @param adaptationSetType The type of the adaptation set exposed by this source. One of * {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and * {@link AdaptationSet#TYPE_TEXT}. @@ -254,20 +196,17 @@ public class DashChunkSource implements ChunkSource { int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener, int eventSourceId) { - this(manifestFetcher, manifestFetcher.getManifest(), adaptationSetType, - dataSource, adaptiveFormatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000, - elapsedRealtimeOffsetMs * 1000, startAtLiveEdge, eventHandler, eventListener, - eventSourceId); + this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(), + liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, startAtLiveEdge, eventHandler, + eventListener, eventSourceId); } /* package */ DashChunkSource(ManifestFetcher manifestFetcher, - MediaPresentationDescription initialManifest, int adaptationSetType, - DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, + int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs, boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener, int eventSourceId) { this.manifestFetcher = manifestFetcher; - this.currentManifest = initialManifest; this.adaptationSetType = adaptationSetType; this.dataSource = dataSource; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; @@ -281,7 +220,6 @@ public class DashChunkSource implements ChunkSource { this.evaluation = new Evaluation(); this.availableRangeValues = new long[2]; periodHolders = new SparseArray<>(); - live = initialManifest.dynamic; } // ChunkSource implementation. @@ -290,16 +228,28 @@ public class DashChunkSource implements ChunkSource { public void maybeThrowError() throws IOException { if (fatalError != null) { throw fatalError; - } else if (manifestFetcher != null) { + } else if (live) { manifestFetcher.maybeThrowError(); } } @Override - public boolean prepare() { - if (!prepareCalled) { - prepareCalled = true; - selectTracks(currentManifest, 0); + public boolean prepare() throws IOException { + if (!manifestFetcherEnabled) { + // TODO[REFACTOR]: We need to disable this at some point. + manifestFetcher.enable(); + manifestFetcherEnabled = true; + } + if (currentManifest == null) { + currentManifest = manifestFetcher.getManifest(); + if (currentManifest == null) { + manifestFetcher.maybeThrowError(); + manifestFetcher.requestRefresh(); + return false; + } else { + live = currentManifest.dynamic; + selectTracks(currentManifest, 0); + } } return true; } @@ -328,17 +278,12 @@ public class DashChunkSource implements ChunkSource { adaptiveMaxWidth = -1; adaptiveMaxHeight = -1; } - if (manifestFetcher != null) { - manifestFetcher.enable(); - processManifest(manifestFetcher.getManifest()); - } else { - processManifest(currentManifest); - } + processManifest(manifestFetcher.getManifest()); } @Override public void continueBuffering(long playbackPositionUs) { - if (manifestFetcher == null || !currentManifest.dynamic || fatalError != null) { + if (!currentManifest.dynamic || fatalError != null) { return; } @@ -543,9 +488,6 @@ public class DashChunkSource implements ChunkSource { if (enabledFormats.length > 1) { adaptiveFormatEvaluator.disable(); } - if (manifestFetcher != null) { - manifestFetcher.disable(); - } periodHolders.clear(); evaluation.format = null; availableRange = null; @@ -602,14 +544,6 @@ public class DashChunkSource implements ChunkSource { return availableRange; } - private static MediaPresentationDescription buildManifest(long durationMs, - int adaptationSetType, List representations) { - AdaptationSet adaptationSet = new AdaptationSet(0, adaptationSetType, representations); - Period period = new Period(null, 0, Collections.singletonList(adaptationSet)); - return new MediaPresentationDescription(-1, durationMs, -1, false, -1, -1, null, null, - Collections.singletonList(period)); - } - private static MediaFormat getTrackFormat(int adaptationSetType, Format format, String mediaMimeType, long durationUs) { switch (adaptationSetType) { diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 30150a56df..932c213224 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.UriUtil; import com.google.android.exoplayer.util.Util; @@ -123,22 +124,24 @@ public class HlsChunkSource { private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; private static final float BANDWIDTH_FRACTION = 0.8f; + private final ManifestFetcher manifestFetcher; private final int type; private final DataSource dataSource; private final HlsPlaylistParser playlistParser; - private final HlsMasterPlaylist masterPlaylist; private final BandwidthMeter bandwidthMeter; private final PtsTimestampAdjusterProvider timestampAdjusterProvider; private final int adaptiveMode; - private final String baseUri; + private final long minBufferDurationToSwitchUpUs; private final long maxBufferDurationToSwitchDownUs; - private boolean prepareCalled; + private boolean manifestFetcherEnabled; private byte[] scratchSpace; private boolean live; private long durationUs; private IOException fatalError; + private HlsMasterPlaylist masterPlaylist; + private String baseUri; private Uri encryptionKeyUri; private byte[] encryptionKey; @@ -158,11 +161,10 @@ public class HlsChunkSource { private int selectedVariantIndex; /** + * @param manifestFetcher A fetcher for the playlist. * @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and * {@link #TYPE_VTT}. * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param playlistUrl The playlist URL. - * @param playlist The hls playlist. * @param bandwidthMeter Provides an estimate of the currently available bandwidth. * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the @@ -171,19 +173,18 @@ public class HlsChunkSource { * {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and * {@link #ADAPTIVE_MODE_SPLICE}. */ - public HlsChunkSource(int type, DataSource dataSource, String playlistUrl, HlsPlaylist playlist, - BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider, - int adaptiveMode) { - this(type, dataSource, playlistUrl, playlist, bandwidthMeter, timestampAdjusterProvider, + public HlsChunkSource(ManifestFetcher manifestFetcher, int type, + DataSource dataSource, BandwidthMeter bandwidthMeter, + PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode) { + this(manifestFetcher, type, dataSource, bandwidthMeter, timestampAdjusterProvider, adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS); } /** + * @param manifestFetcher A fetcher for the playlist. * @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and * {@link #TYPE_VTT}. * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param playlistUrl The playlist URL. - * @param playlist The hls playlist. * @param bandwidthMeter Provides an estimate of the currently available bandwidth. * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the @@ -196,9 +197,11 @@ public class HlsChunkSource { * @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered * for a switch to a lower quality variant to be considered. */ - public HlsChunkSource(int type, DataSource dataSource, String playlistUrl, HlsPlaylist playlist, - BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider, - int adaptiveMode, long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) { + public HlsChunkSource(ManifestFetcher manifestFetcher, int type, + DataSource dataSource, BandwidthMeter bandwidthMeter, + PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode, + long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) { + this.manifestFetcher = manifestFetcher; this.type = type; this.dataSource = dataSource; this.bandwidthMeter = bandwidthMeter; @@ -206,18 +209,7 @@ public class HlsChunkSource { this.adaptiveMode = adaptiveMode; minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000; maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000; - baseUri = playlist.baseUri; playlistParser = new HlsPlaylistParser(); - if (playlist.type == HlsPlaylist.TYPE_MASTER) { - masterPlaylist = (HlsMasterPlaylist) playlist; - } else { - Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null, - null); - List variants = new ArrayList<>(); - variants.add(new Variant(playlistUrl, format)); - masterPlaylist = new HlsMasterPlaylist(playlistUrl, variants, - Collections.emptyList()); - } } /** @@ -237,12 +229,34 @@ public class HlsChunkSource { * * @return True if the source was prepared, false otherwise. */ - public boolean prepare() { - if (!prepareCalled) { - prepareCalled = true; - processMasterPlaylist(masterPlaylist); - // TODO[REFACTOR]: Come up with a sane default here. - selectTracks(new int[] {0}); + public boolean prepare() throws IOException { + if (!manifestFetcherEnabled) { + // TODO[REFACTOR]: We need to disable this at some point. + manifestFetcher.enable(); + manifestFetcherEnabled = true; + } + if (masterPlaylist == null) { + HlsPlaylist playlist = manifestFetcher.getManifest(); + if (playlist == null) { + manifestFetcher.maybeThrowError(); + manifestFetcher.requestRefresh(); + return false; + } else { + baseUri = playlist.baseUri; + if (playlist.type == HlsPlaylist.TYPE_MASTER) { + masterPlaylist = (HlsMasterPlaylist) playlist; + } else { + Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null, + null); + List variants = new ArrayList<>(); + variants.add(new Variant(baseUri, format)); + masterPlaylist = new HlsMasterPlaylist(baseUri, variants, + Collections.emptyList()); + } + processMasterPlaylist(masterPlaylist); + // TODO[REFACTOR]: Come up with a sane default here. + selectTracks(new int[] {0}); + } } return true; } @@ -602,7 +616,7 @@ public class HlsChunkSource { } // Type is TYPE_DEFAULT. - List enabledVariantList = playlist.variants; + List enabledVariantList = new ArrayList<>(playlist.variants); ArrayList definiteVideoVariants = new ArrayList<>(); ArrayList definiteAudioOnlyVariants = new ArrayList<>(); for (int i = 0; i < enabledVariantList.size(); i++) { diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index 347c144ed2..dbc056a5eb 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -133,7 +133,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { if (prepared) { return true; } else if (!chunkSource.prepare()) { - maybeThrowError(); return false; } if (!extractors.isEmpty()) { diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index 8802132fd1..c4dd86792e 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -67,17 +67,16 @@ public class SmoothStreamingChunkSource implements ChunkSource { private final DataSource dataSource; private final Evaluation evaluation; private final long liveEdgeLatencyUs; - private final TrackEncryptionBox[] trackEncryptionBoxes; private final ManifestFetcher manifestFetcher; - private final DrmInitData.Mapped drmInitData; private final FormatEvaluator adaptiveFormatEvaluator; - private final boolean live; - private boolean prepareCalled; + private boolean manifestFetcherEnabled; + private boolean live; + private TrackEncryptionBox[] trackEncryptionBoxes; + private DrmInitData.Mapped drmInitData; private SmoothStreamingManifest currentManifest; private int currentManifestChunkOffset; private boolean needManifestRefresh; - private IOException fatalError; // Properties of exposed tracks. private int elementIndex; @@ -93,14 +92,10 @@ public class SmoothStreamingChunkSource implements ChunkSource { private final SparseArray extractorWrappers; private final SparseArray mediaFormats; + private IOException fatalError; + /** - * Constructor to use for live streaming. - *

- * May also be used for fixed duration content, in which case the call is equivalent to calling - * the other constructor, passing {@code manifestFetcher.getManifest()} is the first argument. - * - * @param manifestFetcher A fetcher for the manifest, which must have already successfully - * completed an initial load. + * @param manifestFetcher A fetcher for the manifest. * @param streamElementType The type of stream element exposed by this source. One of * {@link StreamElement#TYPE_VIDEO}, {@link StreamElement#TYPE_AUDIO} and * {@link StreamElement#TYPE_TEXT}. @@ -115,30 +110,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { public SmoothStreamingChunkSource(ManifestFetcher manifestFetcher, int streamElementType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs) { - this(manifestFetcher, manifestFetcher.getManifest(), streamElementType, dataSource, - adaptiveFormatEvaluator, liveEdgeLatencyMs); - } - - /** - * Constructor to use for fixed duration content. - * - * @param manifest The manifest parsed from {@code baseUrl + "/Manifest"}. - * @param streamElementType The type of stream element exposed by this source. One of - * {@link StreamElement#TYPE_VIDEO}, {@link StreamElement#TYPE_AUDIO} and - * {@link StreamElement#TYPE_TEXT}. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - */ - public SmoothStreamingChunkSource(SmoothStreamingManifest manifest, int streamElementType, - DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) { - this(null, manifest, streamElementType, dataSource, adaptiveFormatEvaluator, 0); - } - - private SmoothStreamingChunkSource(ManifestFetcher manifestFetcher, - SmoothStreamingManifest initialManifest, int streamElementType, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs) { this.manifestFetcher = manifestFetcher; - this.currentManifest = initialManifest; this.streamElementType = streamElementType; this.dataSource = dataSource; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; @@ -146,20 +118,6 @@ public class SmoothStreamingChunkSource implements ChunkSource { evaluation = new Evaluation(); extractorWrappers = new SparseArray<>(); mediaFormats = new SparseArray<>(); - live = initialManifest.isLive; - - ProtectionElement protectionElement = initialManifest.protectionElement; - if (protectionElement != null) { - byte[] keyId = getProtectionElementKeyId(protectionElement.data); - trackEncryptionBoxes = new TrackEncryptionBox[1]; - trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); - drmInitData = new DrmInitData.Mapped(); - drmInitData.put(protectionElement.uuid, - new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data)); - } else { - trackEncryptionBoxes = null; - drmInitData = null; - } } // ChunkSource implementation. @@ -168,16 +126,40 @@ public class SmoothStreamingChunkSource implements ChunkSource { public void maybeThrowError() throws IOException { if (fatalError != null) { throw fatalError; - } else { + } else if (live) { manifestFetcher.maybeThrowError(); } } @Override - public boolean prepare() { - if (!prepareCalled) { - selectTracks(currentManifest); - prepareCalled = true; + public boolean prepare() throws IOException { + if (!manifestFetcherEnabled) { + // TODO[REFACTOR]: We need to disable this at some point. + manifestFetcher.enable(); + manifestFetcherEnabled = true; + } + if (currentManifest == null) { + currentManifest = manifestFetcher.getManifest(); + if (currentManifest == null) { + manifestFetcher.maybeThrowError(); + manifestFetcher.requestRefresh(); + return false; + } else { + live = currentManifest.isLive; + ProtectionElement protectionElement = currentManifest.protectionElement; + if (protectionElement != null) { + byte[] keyId = getProtectionElementKeyId(protectionElement.data); + trackEncryptionBoxes = new TrackEncryptionBox[1]; + trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); + drmInitData = new DrmInitData.Mapped(); + drmInitData.put(protectionElement.uuid, + new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data)); + } else { + trackEncryptionBoxes = null; + drmInitData = null; + } + selectTracks(currentManifest); + } } return true; } @@ -206,14 +188,11 @@ public class SmoothStreamingChunkSource implements ChunkSource { adaptiveMaxWidth = -1; adaptiveMaxHeight = -1; } - if (manifestFetcher != null) { - manifestFetcher.enable(); - } } @Override public void continueBuffering(long playbackPositionUs) { - if (manifestFetcher == null || !currentManifest.isLive || fatalError != null) { + if (!currentManifest.isLive || fatalError != null) { return; } @@ -350,9 +329,6 @@ public class SmoothStreamingChunkSource implements ChunkSource { if (enabledFormats.length > 1) { adaptiveFormatEvaluator.disable(); } - if (manifestFetcher != null) { - manifestFetcher.disable(); - } evaluation.format = null; fatalError = null; } diff --git a/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java b/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java index c34e0c4f8e..2c1dc1e795 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java +++ b/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java @@ -21,28 +21,14 @@ import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriLoadable; import android.os.Handler; -import android.os.Looper; import android.os.SystemClock; import android.text.TextUtils; import android.util.Pair; import java.io.IOException; -import java.util.concurrent.CancellationException; /** - * Performs both single and repeated loads of media manifests. - *

- * Client code is responsible for ensuring that only one load is taking place at any one time. - * Typical usage of this class is as follows: - *

    - *
  1. Create an instance.
  2. - *
  3. Obtain an initial manifest by calling {@link #singleLoad(Looper, ManifestCallback)} and - * waiting for the callback to be invoked.
  4. - *
  5. For on-demand playbacks, the loader is no longer required. For live playbacks, the loader - * may be required to periodically refresh the manifest. In this case it is injected into any - * components that require it. These components will call {@link #requestRefresh()} on the - * loader whenever a refresh is required.
  6. - *
+ * Loads and refreshes a media manifest. * * @param The type of manifest. */ @@ -69,29 +55,6 @@ public class ManifestFetcher implements Loader.Callback { } - /** - * Callback for the result of a single load. - * - * @param The type of manifest. - */ - public interface ManifestCallback { - - /** - * Invoked when the load has successfully completed. - * - * @param manifest The loaded manifest. - */ - void onSingleManifest(T manifest); - - /** - * Invoked when the load has failed. - * - * @param e The cause of the failure. - */ - void onSingleManifestError(IOException e); - - } - /** * Interface for manifests that are able to specify that subsequent loads should use a different * URI. @@ -162,19 +125,6 @@ public class ManifestFetcher implements Loader.Callback { this.manifestUri = manifestUri; } - /** - * Performs a single manifest load. - * - * @param callbackLooper The looper associated with the thread on which the callback should be - * invoked. - * @param callback The callback to receive the result. - */ - public void singleLoad(Looper callbackLooper, final ManifestCallback callback) { - SingleFetchHelper fetchHelper = new SingleFetchHelper( - new UriLoadable<>(manifestUri, uriDataSource, parser), callbackLooper, callback); - fetchHelper.startLoading(); - } - /** * Gets a {@link Pair} containing the most recently loaded manifest together with the timestamp * at which the load completed. @@ -348,63 +298,4 @@ public class ManifestFetcher implements Loader.Callback { } } - private class SingleFetchHelper implements Loader.Callback { - - private final UriLoadable singleUseLoadable; - private final Looper callbackLooper; - private final ManifestCallback wrappedCallback; - private final Loader singleUseLoader; - - private long loadStartTimestamp; - - public SingleFetchHelper(UriLoadable singleUseLoadable, Looper callbackLooper, - ManifestCallback wrappedCallback) { - this.singleUseLoadable = singleUseLoadable; - this.callbackLooper = callbackLooper; - this.wrappedCallback = wrappedCallback; - singleUseLoader = new Loader("manifestLoader:single"); - } - - public void startLoading() { - loadStartTimestamp = SystemClock.elapsedRealtime(); - singleUseLoader.startLoading(callbackLooper, singleUseLoadable, this); - } - - @Override - public void onLoadCompleted(Loadable loadable) { - try { - T result = singleUseLoadable.getResult(); - onSingleFetchCompleted(result, loadStartTimestamp); - wrappedCallback.onSingleManifest(result); - } finally { - releaseLoader(); - } - } - - @Override - public void onLoadCanceled(Loadable loadable) { - // This shouldn't ever happen, but handle it anyway. - try { - IOException exception = new ManifestIOException(new CancellationException()); - wrappedCallback.onSingleManifestError(exception); - } finally { - releaseLoader(); - } - } - - @Override - public void onLoadError(Loadable loadable, IOException exception) { - try { - wrappedCallback.onSingleManifestError(exception); - } finally { - releaseLoader(); - } - } - - private void releaseLoader() { - singleUseLoader.release(); - } - - } - }