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