From ce324f1ca928ca8ee846853652996b95593498b8 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 28 Jan 2016 07:14:27 -0800 Subject: [PATCH] ExoPlayer V2 Refactor - Step 7 (partial) This change removes the need for SourceBuilders to load their manifests before building their sources. This is done by pushing initial manifest loads into the ChunkSource classes. This simplifies the SourceBuilders a lot, and also DemoPlayer. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=113259259 --- .../demo/player/DashSourceBuilder.java | 203 +++--------------- .../exoplayer/demo/player/DemoPlayer.java | 92 +------- .../demo/player/ExtractorSourceBuilder.java | 11 +- .../demo/player/HlsSourceBuilder.java | 111 ++-------- .../player/SmoothStreamingSourceBuilder.java | 144 +++---------- .../exoplayer/dash/DashChunkSourceTest.java | 29 +-- .../exoplayer/chunk/ChunkSampleSource.java | 1 - .../android/exoplayer/chunk/ChunkSource.java | 9 +- .../exoplayer/dash/DashChunkSource.java | 126 +++-------- .../android/exoplayer/hls/HlsChunkSource.java | 78 ++++--- .../exoplayer/hls/HlsSampleSource.java | 1 - .../SmoothStreamingChunkSource.java | 98 ++++----- .../exoplayer/util/ManifestFetcher.java | 111 +--------- 13 files changed, 217 insertions(+), 797 deletions(-) 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(); - } - - } - }