• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.exoplayer2.upstream;
17 
18 import android.content.Context;
19 import android.net.Uri;
20 import androidx.annotation.Nullable;
21 import com.google.android.exoplayer2.util.Assertions;
22 import com.google.android.exoplayer2.util.Log;
23 import com.google.android.exoplayer2.util.Util;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Map;
29 
30 /**
31  * A {@link DataSource} that supports multiple URI schemes. The supported schemes are:
32  *
33  * <ul>
34  *   <li>file: For fetching data from a local file (e.g. file:///path/to/media/media.mp4, or just
35  *       /path/to/media/media.mp4 because the implementation assumes that a URI without a scheme is
36  *       a local file URI).
37  *   <li>asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4).
38  *   <li>rawresource: For fetching data from a raw resource in the application's apk (e.g.
39  *       rawresource:///resourceId, where rawResourceId is the integer identifier of the raw
40  *       resource).
41  *   <li>content: For fetching data from a content URI (e.g. content://authority/path/123).
42  *   <li>rtmp: For fetching data over RTMP. Only supported if the project using ExoPlayer has an
43  *       explicit dependency on ExoPlayer's RTMP extension.
44  *   <li>data: For parsing data inlined in the URI as defined in RFC 2397.
45  *   <li>udp: For fetching data over UDP (e.g. udp://something.com/media).
46  *   <li>http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4),
47  *       if constructed using {@link #DefaultDataSource(Context, String, boolean)}, or any other
48  *       schemes supported by a base data source if constructed using {@link
49  *       #DefaultDataSource(Context, DataSource)}.
50  * </ul>
51  */
52 public final class DefaultDataSource implements DataSource {
53 
54   private static final String TAG = "DefaultDataSource";
55 
56   private static final String SCHEME_ASSET = "asset";
57   private static final String SCHEME_CONTENT = "content";
58   private static final String SCHEME_RTMP = "rtmp";
59   private static final String SCHEME_UDP = "udp";
60   private static final String SCHEME_RAW = RawResourceDataSource.RAW_RESOURCE_SCHEME;
61 
62   private final Context context;
63   private final List<TransferListener> transferListeners;
64   private final DataSource baseDataSource;
65 
66   // Lazily initialized.
67   @Nullable private DataSource fileDataSource;
68   @Nullable private DataSource assetDataSource;
69   @Nullable private DataSource contentDataSource;
70   @Nullable private DataSource rtmpDataSource;
71   @Nullable private DataSource udpDataSource;
72   @Nullable private DataSource dataSchemeDataSource;
73   @Nullable private DataSource rawResourceDataSource;
74 
75   @Nullable private DataSource dataSource;
76 
77   /**
78    * Constructs a new instance, optionally configured to follow cross-protocol redirects.
79    *
80    * @param context A context.
81    * @param userAgent The User-Agent to use when requesting remote data.
82    * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
83    *     to HTTPS and vice versa) are enabled when fetching remote data.
84    */
DefaultDataSource(Context context, String userAgent, boolean allowCrossProtocolRedirects)85   public DefaultDataSource(Context context, String userAgent, boolean allowCrossProtocolRedirects) {
86     this(
87         context,
88         userAgent,
89         DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
90         DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
91         allowCrossProtocolRedirects);
92   }
93 
94   /**
95    * Constructs a new instance, optionally configured to follow cross-protocol redirects.
96    *
97    * @param context A context.
98    * @param userAgent The User-Agent to use when requesting remote data.
99    * @param connectTimeoutMillis The connection timeout that should be used when requesting remote
100    *     data, in milliseconds. A timeout of zero is interpreted as an infinite timeout.
101    * @param readTimeoutMillis The read timeout that should be used when requesting remote data, in
102    *     milliseconds. A timeout of zero is interpreted as an infinite timeout.
103    * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
104    *     to HTTPS and vice versa) are enabled when fetching remote data.
105    */
DefaultDataSource( Context context, String userAgent, int connectTimeoutMillis, int readTimeoutMillis, boolean allowCrossProtocolRedirects)106   public DefaultDataSource(
107       Context context,
108       String userAgent,
109       int connectTimeoutMillis,
110       int readTimeoutMillis,
111       boolean allowCrossProtocolRedirects) {
112     this(
113         context,
114         new DefaultHttpDataSource(
115             userAgent,
116             connectTimeoutMillis,
117             readTimeoutMillis,
118             allowCrossProtocolRedirects,
119             /* defaultRequestProperties= */ null));
120   }
121 
122   /**
123    * Constructs a new instance that delegates to a provided {@link DataSource} for URI schemes other
124    * than file, asset and content.
125    *
126    * @param context A context.
127    * @param baseDataSource A {@link DataSource} to use for URI schemes other than file, asset and
128    *     content. This {@link DataSource} should normally support at least http(s).
129    */
DefaultDataSource(Context context, DataSource baseDataSource)130   public DefaultDataSource(Context context, DataSource baseDataSource) {
131     this.context = context.getApplicationContext();
132     this.baseDataSource = Assertions.checkNotNull(baseDataSource);
133     transferListeners = new ArrayList<>();
134   }
135 
136   @Override
addTransferListener(TransferListener transferListener)137   public void addTransferListener(TransferListener transferListener) {
138     baseDataSource.addTransferListener(transferListener);
139     transferListeners.add(transferListener);
140     maybeAddListenerToDataSource(fileDataSource, transferListener);
141     maybeAddListenerToDataSource(assetDataSource, transferListener);
142     maybeAddListenerToDataSource(contentDataSource, transferListener);
143     maybeAddListenerToDataSource(rtmpDataSource, transferListener);
144     maybeAddListenerToDataSource(udpDataSource, transferListener);
145     maybeAddListenerToDataSource(dataSchemeDataSource, transferListener);
146     maybeAddListenerToDataSource(rawResourceDataSource, transferListener);
147   }
148 
149   @Override
open(DataSpec dataSpec)150   public long open(DataSpec dataSpec) throws IOException {
151     Assertions.checkState(dataSource == null);
152     // Choose the correct source for the scheme.
153     String scheme = dataSpec.uri.getScheme();
154     if (Util.isLocalFileUri(dataSpec.uri)) {
155       String uriPath = dataSpec.uri.getPath();
156       if (uriPath != null && uriPath.startsWith("/android_asset/")) {
157         dataSource = getAssetDataSource();
158       } else {
159         dataSource = getFileDataSource();
160       }
161     } else if (SCHEME_ASSET.equals(scheme)) {
162       dataSource = getAssetDataSource();
163     } else if (SCHEME_CONTENT.equals(scheme)) {
164       dataSource = getContentDataSource();
165     } else if (SCHEME_RTMP.equals(scheme)) {
166       dataSource = getRtmpDataSource();
167     } else if (SCHEME_UDP.equals(scheme)) {
168       dataSource = getUdpDataSource();
169     } else if (DataSchemeDataSource.SCHEME_DATA.equals(scheme)) {
170       dataSource = getDataSchemeDataSource();
171     } else if (SCHEME_RAW.equals(scheme)) {
172       dataSource = getRawResourceDataSource();
173     } else {
174       dataSource = baseDataSource;
175     }
176     // Open the source and return.
177     return dataSource.open(dataSpec);
178   }
179 
180   @Override
read(byte[] buffer, int offset, int readLength)181   public int read(byte[] buffer, int offset, int readLength) throws IOException {
182     return Assertions.checkNotNull(dataSource).read(buffer, offset, readLength);
183   }
184 
185   @Override
186   @Nullable
getUri()187   public Uri getUri() {
188     return dataSource == null ? null : dataSource.getUri();
189   }
190 
191   @Override
getResponseHeaders()192   public Map<String, List<String>> getResponseHeaders() {
193     return dataSource == null ? Collections.emptyMap() : dataSource.getResponseHeaders();
194   }
195 
196   @Override
close()197   public void close() throws IOException {
198     if (dataSource != null) {
199       try {
200         dataSource.close();
201       } finally {
202         dataSource = null;
203       }
204     }
205   }
206 
getUdpDataSource()207   private DataSource getUdpDataSource() {
208     if (udpDataSource == null) {
209       udpDataSource = new UdpDataSource();
210       addListenersToDataSource(udpDataSource);
211     }
212     return udpDataSource;
213   }
214 
getFileDataSource()215   private DataSource getFileDataSource() {
216     if (fileDataSource == null) {
217       fileDataSource = new FileDataSource();
218       addListenersToDataSource(fileDataSource);
219     }
220     return fileDataSource;
221   }
222 
getAssetDataSource()223   private DataSource getAssetDataSource() {
224     if (assetDataSource == null) {
225       assetDataSource = new AssetDataSource(context);
226       addListenersToDataSource(assetDataSource);
227     }
228     return assetDataSource;
229   }
230 
getContentDataSource()231   private DataSource getContentDataSource() {
232     if (contentDataSource == null) {
233       contentDataSource = new ContentDataSource(context);
234       addListenersToDataSource(contentDataSource);
235     }
236     return contentDataSource;
237   }
238 
getRtmpDataSource()239   private DataSource getRtmpDataSource() {
240     if (rtmpDataSource == null) {
241       try {
242         // LINT.IfChange
243         Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.rtmp.RtmpDataSource");
244         rtmpDataSource = (DataSource) clazz.getConstructor().newInstance();
245         // LINT.ThenChange(../../../../../../../../proguard-rules.txt)
246         addListenersToDataSource(rtmpDataSource);
247       } catch (ClassNotFoundException e) {
248         // Expected if the app was built without the RTMP extension.
249         Log.w(TAG, "Attempting to play RTMP stream without depending on the RTMP extension");
250       } catch (Exception e) {
251         // The RTMP extension is present, but instantiation failed.
252         throw new RuntimeException("Error instantiating RTMP extension", e);
253       }
254       if (rtmpDataSource == null) {
255         rtmpDataSource = baseDataSource;
256       }
257     }
258     return rtmpDataSource;
259   }
260 
getDataSchemeDataSource()261   private DataSource getDataSchemeDataSource() {
262     if (dataSchemeDataSource == null) {
263       dataSchemeDataSource = new DataSchemeDataSource();
264       addListenersToDataSource(dataSchemeDataSource);
265     }
266     return dataSchemeDataSource;
267   }
268 
getRawResourceDataSource()269   private DataSource getRawResourceDataSource() {
270     if (rawResourceDataSource == null) {
271       rawResourceDataSource = new RawResourceDataSource(context);
272       addListenersToDataSource(rawResourceDataSource);
273     }
274     return rawResourceDataSource;
275   }
276 
addListenersToDataSource(DataSource dataSource)277   private void addListenersToDataSource(DataSource dataSource) {
278     for (int i = 0; i < transferListeners.size(); i++) {
279       dataSource.addTransferListener(transferListeners.get(i));
280     }
281   }
282 
maybeAddListenerToDataSource( @ullable DataSource dataSource, TransferListener listener)283   private void maybeAddListenerToDataSource(
284       @Nullable DataSource dataSource, TransferListener listener) {
285     if (dataSource != null) {
286       dataSource.addTransferListener(listener);
287     }
288   }
289 }
290