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.support.annotation.IntDef; 19 import android.text.TextUtils; 20 import com.google.android.exoplayer2.util.Predicate; 21 import com.google.android.exoplayer2.util.Util; 22 import java.io.IOException; 23 import java.lang.annotation.Retention; 24 import java.lang.annotation.RetentionPolicy; 25 import java.util.Collections; 26 import java.util.HashMap; 27 import java.util.List; 28 import java.util.Map; 29 30 /** 31 * An HTTP {@link DataSource}. 32 */ 33 public interface HttpDataSource extends DataSource { 34 35 /** 36 * A factory for {@link HttpDataSource} instances. 37 */ 38 interface Factory extends DataSource.Factory { 39 40 @Override createDataSource()41 HttpDataSource createDataSource(); 42 43 /** 44 * Gets the default request properties used by all {@link HttpDataSource}s created by the 45 * factory. Changes to the properties will be reflected in any future requests made by 46 * {@link HttpDataSource}s created by the factory. 47 * 48 * @return The default request properties of the factory. 49 */ getDefaultRequestProperties()50 RequestProperties getDefaultRequestProperties(); 51 52 /** 53 * Sets a default request header for {@link HttpDataSource} instances created by the factory. 54 * 55 * @deprecated Use {@link #getDefaultRequestProperties} instead. 56 * @param name The name of the header field. 57 * @param value The value of the field. 58 */ 59 @Deprecated setDefaultRequestProperty(String name, String value)60 void setDefaultRequestProperty(String name, String value); 61 62 /** 63 * Clears a default request header for {@link HttpDataSource} instances created by the factory. 64 * 65 * @deprecated Use {@link #getDefaultRequestProperties} instead. 66 * @param name The name of the header field. 67 */ 68 @Deprecated clearDefaultRequestProperty(String name)69 void clearDefaultRequestProperty(String name); 70 71 /** 72 * Clears all default request headers for all {@link HttpDataSource} instances created by the 73 * factory. 74 * 75 * @deprecated Use {@link #getDefaultRequestProperties} instead. 76 */ 77 @Deprecated clearAllDefaultRequestProperties()78 void clearAllDefaultRequestProperties(); 79 80 } 81 82 /** 83 * Stores HTTP request properties (aka HTTP headers) and provides methods to modify the headers 84 * in a thread safe way to avoid the potential of creating snapshots of an inconsistent or 85 * unintended state. 86 */ 87 final class RequestProperties { 88 89 private final Map<String, String> requestProperties; 90 private Map<String, String> requestPropertiesSnapshot; 91 RequestProperties()92 public RequestProperties() { 93 requestProperties = new HashMap<>(); 94 } 95 96 /** 97 * Sets the specified property {@code value} for the specified {@code name}. If a property for 98 * this name previously existed, the old value is replaced by the specified value. 99 * 100 * @param name The name of the request property. 101 * @param value The value of the request property. 102 */ set(String name, String value)103 public synchronized void set(String name, String value) { 104 requestPropertiesSnapshot = null; 105 requestProperties.put(name, value); 106 } 107 108 /** 109 * Sets the keys and values contained in the map. If a property previously existed, the old 110 * value is replaced by the specified value. If a property previously existed and is not in the 111 * map, the property is left unchanged. 112 * 113 * @param properties The request properties. 114 */ set(Map<String, String> properties)115 public synchronized void set(Map<String, String> properties) { 116 requestPropertiesSnapshot = null; 117 requestProperties.putAll(properties); 118 } 119 120 /** 121 * Removes all properties previously existing and sets the keys and values of the map. 122 * 123 * @param properties The request properties. 124 */ clearAndSet(Map<String, String> properties)125 public synchronized void clearAndSet(Map<String, String> properties) { 126 requestPropertiesSnapshot = null; 127 requestProperties.clear(); 128 requestProperties.putAll(properties); 129 } 130 131 /** 132 * Removes a request property by name. 133 * 134 * @param name The name of the request property to remove. 135 */ remove(String name)136 public synchronized void remove(String name) { 137 requestPropertiesSnapshot = null; 138 requestProperties.remove(name); 139 } 140 141 /** 142 * Clears all request properties. 143 */ clear()144 public synchronized void clear() { 145 requestPropertiesSnapshot = null; 146 requestProperties.clear(); 147 } 148 149 /** 150 * Gets a snapshot of the request properties. 151 * 152 * @return A snapshot of the request properties. 153 */ getSnapshot()154 public synchronized Map<String, String> getSnapshot() { 155 if (requestPropertiesSnapshot == null) { 156 requestPropertiesSnapshot = Collections.unmodifiableMap(new HashMap<>(requestProperties)); 157 } 158 return requestPropertiesSnapshot; 159 } 160 161 } 162 163 /** 164 * Base implementation of {@link Factory} that sets default request properties. 165 */ 166 abstract class BaseFactory implements Factory { 167 168 private final RequestProperties defaultRequestProperties; 169 BaseFactory()170 public BaseFactory() { 171 defaultRequestProperties = new RequestProperties(); 172 } 173 174 @Override createDataSource()175 public final HttpDataSource createDataSource() { 176 return createDataSourceInternal(defaultRequestProperties); 177 } 178 179 @Override getDefaultRequestProperties()180 public final RequestProperties getDefaultRequestProperties() { 181 return defaultRequestProperties; 182 } 183 184 @Deprecated 185 @Override setDefaultRequestProperty(String name, String value)186 public final void setDefaultRequestProperty(String name, String value) { 187 defaultRequestProperties.set(name, value); 188 } 189 190 @Deprecated 191 @Override clearDefaultRequestProperty(String name)192 public final void clearDefaultRequestProperty(String name) { 193 defaultRequestProperties.remove(name); 194 } 195 196 @Deprecated 197 @Override clearAllDefaultRequestProperties()198 public final void clearAllDefaultRequestProperties() { 199 defaultRequestProperties.clear(); 200 } 201 202 /** 203 * Called by {@link #createDataSource()} to create a {@link HttpDataSource} instance. 204 * 205 * @param defaultRequestProperties The default {@code RequestProperties} to be used by the 206 * {@link HttpDataSource} instance. 207 * @return A {@link HttpDataSource} instance. 208 */ createDataSourceInternal(RequestProperties defaultRequestProperties)209 protected abstract HttpDataSource createDataSourceInternal(RequestProperties 210 defaultRequestProperties); 211 212 } 213 214 /** 215 * A {@link Predicate} that rejects content types often used for pay-walls. 216 */ 217 Predicate<String> REJECT_PAYWALL_TYPES = new Predicate<String>() { 218 219 @Override 220 public boolean evaluate(String contentType) { 221 contentType = Util.toLowerInvariant(contentType); 222 return !TextUtils.isEmpty(contentType) 223 && (!contentType.contains("text") || contentType.contains("text/vtt")) 224 && !contentType.contains("html") && !contentType.contains("xml"); 225 } 226 227 }; 228 229 /** 230 * Thrown when an error is encountered when trying to read from a {@link HttpDataSource}. 231 */ 232 class HttpDataSourceException extends IOException { 233 234 @Retention(RetentionPolicy.SOURCE) 235 @IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE}) 236 public @interface Type {} 237 public static final int TYPE_OPEN = 1; 238 public static final int TYPE_READ = 2; 239 public static final int TYPE_CLOSE = 3; 240 241 @Type public final int type; 242 243 /** 244 * The {@link DataSpec} associated with the current connection. 245 */ 246 public final DataSpec dataSpec; 247 HttpDataSourceException(DataSpec dataSpec, @Type int type)248 public HttpDataSourceException(DataSpec dataSpec, @Type int type) { 249 super(); 250 this.dataSpec = dataSpec; 251 this.type = type; 252 } 253 HttpDataSourceException(String message, DataSpec dataSpec, @Type int type)254 public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) { 255 super(message); 256 this.dataSpec = dataSpec; 257 this.type = type; 258 } 259 HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type)260 public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) { 261 super(cause); 262 this.dataSpec = dataSpec; 263 this.type = type; 264 } 265 HttpDataSourceException(String message, IOException cause, DataSpec dataSpec, @Type int type)266 public HttpDataSourceException(String message, IOException cause, DataSpec dataSpec, 267 @Type int type) { 268 super(message, cause); 269 this.dataSpec = dataSpec; 270 this.type = type; 271 } 272 273 } 274 275 /** 276 * Thrown when the content type is invalid. 277 */ 278 final class InvalidContentTypeException extends HttpDataSourceException { 279 280 public final String contentType; 281 InvalidContentTypeException(String contentType, DataSpec dataSpec)282 public InvalidContentTypeException(String contentType, DataSpec dataSpec) { 283 super("Invalid content type: " + contentType, dataSpec, TYPE_OPEN); 284 this.contentType = contentType; 285 } 286 287 } 288 289 /** 290 * Thrown when an attempt to open a connection results in a response code not in the 2xx range. 291 */ 292 final class InvalidResponseCodeException extends HttpDataSourceException { 293 294 /** 295 * The response code that was outside of the 2xx range. 296 */ 297 public final int responseCode; 298 299 /** 300 * An unmodifiable map of the response header fields and values. 301 */ 302 public final Map<String, List<String>> headerFields; 303 InvalidResponseCodeException(int responseCode, Map<String, List<String>> headerFields, DataSpec dataSpec)304 public InvalidResponseCodeException(int responseCode, Map<String, List<String>> headerFields, 305 DataSpec dataSpec) { 306 super("Response code: " + responseCode, dataSpec, TYPE_OPEN); 307 this.responseCode = responseCode; 308 this.headerFields = headerFields; 309 } 310 311 } 312 313 @Override open(DataSpec dataSpec)314 long open(DataSpec dataSpec) throws HttpDataSourceException; 315 316 @Override close()317 void close() throws HttpDataSourceException; 318 319 @Override read(byte[] buffer, int offset, int readLength)320 int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException; 321 322 /** 323 * Sets the value of a request header. The value will be used for subsequent connections 324 * established by the source. 325 * 326 * @param name The name of the header field. 327 * @param value The value of the field. 328 */ setRequestProperty(String name, String value)329 void setRequestProperty(String name, String value); 330 331 /** 332 * Clears the value of a request header. The change will apply to subsequent connections 333 * established by the source. 334 * 335 * @param name The name of the header field. 336 */ clearRequestProperty(String name)337 void clearRequestProperty(String name); 338 339 /** 340 * Clears all request headers that were set by {@link #setRequestProperty(String, String)}. 341 */ clearAllRequestProperties()342 void clearAllRequestProperties(); 343 344 /** 345 * Returns the headers provided in the response, or {@code null} if response headers are 346 * unavailable. 347 */ getResponseHeaders()348 Map<String, List<String>> getResponseHeaders(); 349 350 } 351