diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index e824ed0115..5dd1eb8d32 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -233,19 +233,19 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, audioCapabilities); case DemoUtil.TYPE_M4A: // There are no file format differences between M4A and MP4. case DemoUtil.TYPE_MP4: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new Mp4Extractor()); case DemoUtil.TYPE_MP3: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new Mp3Extractor()); case DemoUtil.TYPE_TS: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new TsExtractor(0, audioCapabilities)); case DemoUtil.TYPE_AAC: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new AdtsExtractor()); case DemoUtil.TYPE_WEBM: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new WebmExtractor()); default: throw new IllegalStateException("Unsupported type: " + contentType); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java index 2b0de68c3d..bde74928a0 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java @@ -130,7 +130,7 @@ public class DashRendererBuilder implements RendererBuilder, this.player = player; this.callback = callback; MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); - manifestDataSource = new DefaultUriDataSource(userAgent, null); + manifestDataSource = new DefaultUriDataSource(context, userAgent); manifestFetcher = new ManifestFetcher(url, manifestDataSource, parser); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); @@ -232,10 +232,10 @@ public class DashRendererBuilder implements RendererBuilder, videoRenderer = null; debugRenderer = null; } else { - DataSource videoDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter); - ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, videoAdaptationSetIndex, - videoRepresentationIndices, videoDataSource, new AdaptiveEvaluator(bandwidthMeter), - LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset); + DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, + videoAdaptationSetIndex, videoRepresentationIndices, videoDataSource, + new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset); ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, DemoPlayer.TYPE_VIDEO); @@ -249,7 +249,7 @@ public class DashRendererBuilder implements RendererBuilder, List audioChunkSourceList = new ArrayList(); List audioTrackNameList = new ArrayList(); if (audioAdaptationSet != null) { - DataSource audioDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter); + DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator(); List audioRepresentations = audioAdaptationSet.representations; List codecs = new ArrayList(); @@ -304,7 +304,7 @@ public class DashRendererBuilder implements RendererBuilder, } // Build the text chunk sources. - DataSource textDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter); + DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); FormatEvaluator textEvaluator = new FormatEvaluator.FixedEvaluator(); List textChunkSourceList = new ArrayList(); List textTrackNameList = new ArrayList(); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java index c9cff47697..3437678e04 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DefaultUriDataSource; +import android.content.Context; import android.media.MediaCodec; import android.net.Uri; import android.widget.TextView; @@ -36,13 +37,15 @@ public class ExtractorRendererBuilder implements RendererBuilder { private static final int BUFFER_SIZE = 10 * 1024 * 1024; + private final Context context; private final String userAgent; private final Uri uri; private final TextView debugTextView; private final Extractor extractor; - public ExtractorRendererBuilder(String userAgent, Uri uri, TextView debugTextView, - Extractor extractor) { + public ExtractorRendererBuilder(Context context, String userAgent, Uri uri, + TextView debugTextView, Extractor extractor) { + this.context = context; this.userAgent = userAgent; this.uri = uri; this.debugTextView = debugTextView; @@ -52,7 +55,7 @@ public class ExtractorRendererBuilder implements RendererBuilder { @Override public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { // Build the video and audio renderers. - DataSource dataSource = new DefaultUriDataSource(userAgent, null); + DataSource dataSource = new DefaultUriDataSource(context, userAgent); ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, extractor, 2, BUFFER_SIZE); MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java index 171433c0d8..8eb762e218 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java @@ -76,8 +76,8 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback playlistFetcher = - new ManifestFetcher(url, new DefaultUriDataSource(userAgent, null), parser); + ManifestFetcher playlistFetcher = new ManifestFetcher(url, + new DefaultUriDataSource(context, userAgent), parser); playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); } @@ -103,7 +103,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback + *
  • http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4). + *
  • file: For fetching data from a local file (e.g. file:///path/to/media/media.mp4). + *
  • asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4). + * */ public final class DefaultUriDataSource implements UriDataSource { - private static final String FILE_URI_SCHEME = "file"; + /** + * Thrown when a {@link DefaultUriDataSource} is opened for a URI with an unsupported scheme. + */ + public static final class UnsupportedSchemeException extends IOException { + + /** + * The unsupported scheme. + */ + public final String scheme; + + /** + * @param scheme The unsupported scheme. + */ + public UnsupportedSchemeException(String scheme) { + super("Unsupported URI scheme: " + scheme); + this.scheme = scheme; + } + + } + + private static final String SCHEME_HTTP = "http"; + private static final String SCHEME_HTTPS = "https"; + private static final String SCHEME_FILE = "file"; + private static final String SCHEME_ASSET = "asset"; - private final UriDataSource fileDataSource; private final UriDataSource httpDataSource; + private final UriDataSource fileDataSource; + private final UriDataSource assetDataSource; /** * {@code null} if no data source is open. Otherwise, equal to {@link #fileDataSource} if the open @@ -36,54 +68,86 @@ public final class DefaultUriDataSource implements UriDataSource { private UriDataSource dataSource; /** - * Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and a - * {@link DefaultHttpDataSource} for other URIs. + * Constructs a new instance. *

    * The constructed instance will not follow cross-protocol redirects (i.e. redirects from HTTP to * HTTPS or vice versa) when fetching remote data. Cross-protocol redirects can be enabled by - * using the {@link #DefaultUriDataSource(String, TransferListener, boolean)} constructor and - * passing {@code true} as the final argument. + * using {@link #DefaultUriDataSource(Context, TransferListener, String, boolean)} and passing + * {@code true} as the final argument. * + * @param context A context. * @param userAgent The User-Agent string that should be used when requesting remote data. - * @param transferListener An optional listener. */ - public DefaultUriDataSource(String userAgent, TransferListener transferListener) { - this(userAgent, transferListener, false); + public DefaultUriDataSource(Context context, String userAgent) { + this(context, null, userAgent, false); } /** - * Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and a - * {@link DefaultHttpDataSource} for other URIs. + * Constructs a new instance. + *

    + * The constructed instance will not follow cross-protocol redirects (i.e. redirects from HTTP to + * HTTPS or vice versa) when fetching remote data. Cross-protocol redirects can be enabled by + * using {@link #DefaultUriDataSource(Context, TransferListener, String, boolean)} and passing + * {@code true} as the final argument. * + * @param context A context. + * @param listener An optional {@link TransferListener}. + * @param userAgent The User-Agent string that should be used when requesting remote data. + */ + public DefaultUriDataSource(Context context, TransferListener listener, String userAgent) { + this(context, listener, userAgent, false); + } + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional {@link TransferListener}. * @param userAgent The User-Agent string that should be used when requesting remote data. - * @param transferListener An optional listener. * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP * to HTTPS and vice versa) are enabled when fetching remote data.. */ - public DefaultUriDataSource(String userAgent, TransferListener transferListener, + public DefaultUriDataSource(Context context, TransferListener listener, String userAgent, boolean allowCrossProtocolRedirects) { - this(new FileDataSource(transferListener), - new DefaultHttpDataSource(userAgent, null, transferListener, + this(context, listener, + new DefaultHttpDataSource(userAgent, null, listener, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, allowCrossProtocolRedirects)); } /** - * Constructs a new data source using {@code fileDataSource} for file URIs, and - * {@code httpDataSource} for non-file URIs. + * Constructs a new instance, using a provided {@link HttpDataSource} for fetching remote data. * - * @param fileDataSource {@link UriDataSource} to use for file URIs. + * @param context A context. + * @param listener An optional {@link TransferListener}. * @param httpDataSource {@link UriDataSource} to use for non-file URIs. */ - public DefaultUriDataSource(UriDataSource fileDataSource, UriDataSource httpDataSource) { - this.fileDataSource = Assertions.checkNotNull(fileDataSource); + public DefaultUriDataSource(Context context, TransferListener listener, + UriDataSource httpDataSource) { this.httpDataSource = Assertions.checkNotNull(httpDataSource); + this.fileDataSource = new FileDataSource(listener); + this.assetDataSource = new AssetDataSource(context, listener); } @Override public long open(DataSpec dataSpec) throws IOException { Assertions.checkState(dataSource == null); - dataSource = FILE_URI_SCHEME.equals(dataSpec.uri.getScheme()) ? fileDataSource : httpDataSource; + // Choose the correct source for the scheme. + String scheme = dataSpec.uri.getScheme(); + if (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme)) { + dataSource = httpDataSource; + } else if (SCHEME_FILE.equals(scheme)) { + if (dataSpec.uri.getPath().startsWith("/android_asset/")) { + dataSource = assetDataSource; + } else { + dataSource = fileDataSource; + } + } else if (SCHEME_ASSET.equals(scheme)) { + dataSource = assetDataSource; + } else { + throw new UnsupportedSchemeException(scheme); + } + // Open the source and return. return dataSource.open(dataSpec); }