1/* 2 * Copyright 2018 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 17package androidx.media; 18 19import android.content.Context; 20import android.net.Uri; 21 22import androidx.annotation.NonNull; 23import androidx.annotation.Nullable; 24import androidx.core.util.Preconditions; 25 26import java.io.FileDescriptor; 27import java.net.CookieHandler; 28import java.net.CookieManager; 29import java.net.HttpCookie; 30import java.util.ArrayList; 31import java.util.HashMap; 32import java.util.List; 33import java.util.Map; 34 35/** 36 * Structure for data source descriptor. Used by {@link MediaItem2}. 37 * <p> 38 * Users should use {@link Builder} to change {@link DataSourceDesc}. 39 * 40 * @see MediaItem2 41 */ 42public final class DataSourceDesc { 43 /* No data source has been set yet */ 44 public static final int TYPE_NONE = 0; 45 /* data source is type of MediaDataSource */ 46 public static final int TYPE_CALLBACK = 1; 47 /* data source is type of FileDescriptor */ 48 public static final int TYPE_FD = 2; 49 /* data source is type of Uri */ 50 public static final int TYPE_URI = 3; 51 52 // intentionally less than long.MAX_VALUE. 53 // Declare this first to avoid 'illegal forward reference'. 54 private static final long LONG_MAX = 0x7ffffffffffffffL; 55 56 /** 57 * Used when a position is unknown. 58 * 59 * @see #getEndPosition() 60 */ 61 public static final long POSITION_UNKNOWN = LONG_MAX; 62 63 /** 64 * Used when the length of file descriptor is unknown. 65 * 66 * @see #getFileDescriptorLength() 67 */ 68 public static final long FD_LENGTH_UNKNOWN = LONG_MAX; 69 70 private int mType = TYPE_NONE; 71 72 private Media2DataSource mMedia2DataSource; 73 74 private FileDescriptor mFD; 75 private long mFDOffset = 0; 76 private long mFDLength = FD_LENGTH_UNKNOWN; 77 78 private Uri mUri; 79 private Map<String, String> mUriHeader; 80 private List<HttpCookie> mUriCookies; 81 private Context mUriContext; 82 83 private String mMediaId; 84 private long mStartPositionMs = 0; 85 private long mEndPositionMs = POSITION_UNKNOWN; 86 87 private DataSourceDesc() { 88 } 89 90 /** 91 * Return the media Id of data source. 92 * @return the media Id of data source 93 */ 94 public @Nullable String getMediaId() { 95 return mMediaId; 96 } 97 98 /** 99 * Return the position in milliseconds at which the playback will start. 100 * @return the position in milliseconds at which the playback will start 101 */ 102 public long getStartPosition() { 103 return mStartPositionMs; 104 } 105 106 /** 107 * Return the position in milliseconds at which the playback will end. 108 * {@link #POSITION_UNKNOWN} means ending at the end of source content. 109 * @return the position in milliseconds at which the playback will end 110 */ 111 public long getEndPosition() { 112 return mEndPositionMs; 113 } 114 115 /** 116 * Return the type of data source. 117 * @return the type of data source 118 */ 119 public int getType() { 120 return mType; 121 } 122 123 /** 124 * Return the Media2DataSource of this data source. 125 * It's meaningful only when {@code getType} returns {@link #TYPE_CALLBACK}. 126 * @return the Media2DataSource of this data source 127 */ 128 public @Nullable Media2DataSource getMedia2DataSource() { 129 return mMedia2DataSource; 130 } 131 132 /** 133 * Return the FileDescriptor of this data source. 134 * It's meaningful only when {@code getType} returns {@link #TYPE_FD}. 135 * @return the FileDescriptor of this data source 136 */ 137 public @Nullable FileDescriptor getFileDescriptor() { 138 return mFD; 139 } 140 141 /** 142 * Return the offset associated with the FileDescriptor of this data source. 143 * It's meaningful only when {@code getType} returns {@link #TYPE_FD} and it has 144 * been set by the {@link Builder}. 145 * @return the offset associated with the FileDescriptor of this data source 146 */ 147 public long getFileDescriptorOffset() { 148 return mFDOffset; 149 } 150 151 /** 152 * Return the content length associated with the FileDescriptor of this data source. 153 * It's meaningful only when {@code getType} returns {@link #TYPE_FD}. 154 * {@link #FD_LENGTH_UNKNOWN} means same as the length of source content. 155 * @return the content length associated with the FileDescriptor of this data source 156 */ 157 public long getFileDescriptorLength() { 158 return mFDLength; 159 } 160 161 /** 162 * Return the Uri of this data source. 163 * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. 164 * @return the Uri of this data source 165 */ 166 public @Nullable Uri getUri() { 167 return mUri; 168 } 169 170 /** 171 * Return the Uri headers of this data source. 172 * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. 173 * @return the Uri headers of this data source 174 */ 175 public @Nullable Map<String, String> getUriHeaders() { 176 if (mUriHeader == null) { 177 return null; 178 } 179 return new HashMap<String, String>(mUriHeader); 180 } 181 182 /** 183 * Return the Uri cookies of this data source. 184 * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. 185 * @return the Uri cookies of this data source 186 */ 187 public @Nullable List<HttpCookie> getUriCookies() { 188 if (mUriCookies == null) { 189 return null; 190 } 191 return new ArrayList<HttpCookie>(mUriCookies); 192 } 193 194 /** 195 * Return the Context used for resolving the Uri of this data source. 196 * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. 197 * @return the Context used for resolving the Uri of this data source 198 */ 199 public @Nullable Context getUriContext() { 200 return mUriContext; 201 } 202 203 /** 204 * Builder class for {@link DataSourceDesc} objects. 205 */ 206 public static class Builder { 207 private int mType = TYPE_NONE; 208 209 private Media2DataSource mMedia2DataSource; 210 211 private FileDescriptor mFD; 212 private long mFDOffset = 0; 213 private long mFDLength = FD_LENGTH_UNKNOWN; 214 215 private Uri mUri; 216 private Map<String, String> mUriHeader; 217 private List<HttpCookie> mUriCookies; 218 private Context mUriContext; 219 220 private String mMediaId; 221 private long mStartPositionMs = 0; 222 private long mEndPositionMs = POSITION_UNKNOWN; 223 224 /** 225 * Constructs a new Builder with the defaults. 226 */ 227 public Builder() { 228 } 229 230 /** 231 * Constructs a new Builder from a given {@link DataSourceDesc} instance 232 * @param dsd the {@link DataSourceDesc} object whose data will be reused 233 * in the new Builder. 234 */ 235 public Builder(@NonNull DataSourceDesc dsd) { 236 mType = dsd.mType; 237 mMedia2DataSource = dsd.mMedia2DataSource; 238 mFD = dsd.mFD; 239 mFDOffset = dsd.mFDOffset; 240 mFDLength = dsd.mFDLength; 241 mUri = dsd.mUri; 242 mUriHeader = dsd.mUriHeader; 243 mUriCookies = dsd.mUriCookies; 244 mUriContext = dsd.mUriContext; 245 246 mMediaId = dsd.mMediaId; 247 mStartPositionMs = dsd.mStartPositionMs; 248 mEndPositionMs = dsd.mEndPositionMs; 249 } 250 251 /** 252 * Combines all of the fields that have been set and return a new 253 * {@link DataSourceDesc} object. <code>IllegalStateException</code> will be 254 * thrown if there is conflict between fields. 255 * 256 * @return a new {@link DataSourceDesc} object 257 */ 258 public @NonNull DataSourceDesc build() { 259 if (mType != TYPE_CALLBACK 260 && mType != TYPE_FD 261 && mType != TYPE_URI) { 262 throw new IllegalStateException("Illegal type: " + mType); 263 } 264 if (mStartPositionMs > mEndPositionMs) { 265 throw new IllegalStateException("Illegal start/end position: " 266 + mStartPositionMs + " : " + mEndPositionMs); 267 } 268 269 DataSourceDesc dsd = new DataSourceDesc(); 270 dsd.mType = mType; 271 dsd.mMedia2DataSource = mMedia2DataSource; 272 dsd.mFD = mFD; 273 dsd.mFDOffset = mFDOffset; 274 dsd.mFDLength = mFDLength; 275 dsd.mUri = mUri; 276 dsd.mUriHeader = mUriHeader; 277 dsd.mUriCookies = mUriCookies; 278 dsd.mUriContext = mUriContext; 279 280 dsd.mMediaId = mMediaId; 281 dsd.mStartPositionMs = mStartPositionMs; 282 dsd.mEndPositionMs = mEndPositionMs; 283 284 return dsd; 285 } 286 287 /** 288 * Sets the media Id of this data source. 289 * 290 * @param mediaId the media Id of this data source 291 * @return the same Builder instance. 292 */ 293 public @NonNull Builder setMediaId(String mediaId) { 294 mMediaId = mediaId; 295 return this; 296 } 297 298 /** 299 * Sets the start position in milliseconds at which the playback will start. 300 * Any negative number is treated as 0. 301 * 302 * @param position the start position in milliseconds at which the playback will start 303 * @return the same Builder instance. 304 * 305 */ 306 public @NonNull Builder setStartPosition(long position) { 307 if (position < 0) { 308 position = 0; 309 } 310 mStartPositionMs = position; 311 return this; 312 } 313 314 /** 315 * Sets the end position in milliseconds at which the playback will end. 316 * Any negative number is treated as maximum length of the data source. 317 * 318 * @param position the end position in milliseconds at which the playback will end 319 * @return the same Builder instance. 320 */ 321 public @NonNull Builder setEndPosition(long position) { 322 if (position < 0) { 323 position = POSITION_UNKNOWN; 324 } 325 mEndPositionMs = position; 326 return this; 327 } 328 329 /** 330 * Sets the data source (Media2DataSource) to use. 331 * 332 * @param m2ds the Media2DataSource for the media you want to play 333 * @return the same Builder instance. 334 * @throws NullPointerException if m2ds is null. 335 */ 336 public @NonNull Builder setDataSource(@NonNull Media2DataSource m2ds) { 337 Preconditions.checkNotNull(m2ds); 338 resetDataSource(); 339 mType = TYPE_CALLBACK; 340 mMedia2DataSource = m2ds; 341 return this; 342 } 343 344 /** 345 * Sets the data source (FileDescriptor) to use. The FileDescriptor must be 346 * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility 347 * to close the file descriptor after the source has been used. 348 * 349 * @param fd the FileDescriptor for the file you want to play 350 * @return the same Builder instance. 351 * @throws NullPointerException if fd is null. 352 */ 353 public @NonNull Builder setDataSource(@NonNull FileDescriptor fd) { 354 Preconditions.checkNotNull(fd); 355 resetDataSource(); 356 mType = TYPE_FD; 357 mFD = fd; 358 return this; 359 } 360 361 /** 362 * Sets the data source (FileDescriptor) to use. The FileDescriptor must be 363 * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility 364 * to close the file descriptor after the source has been used. 365 * 366 * Any negative number for offset is treated as 0. 367 * Any negative number for length is treated as maximum length of the data source. 368 * 369 * @param fd the FileDescriptor for the file you want to play 370 * @param offset the offset into the file where the data to be played starts, in bytes 371 * @param length the length in bytes of the data to be played 372 * @return the same Builder instance. 373 * @throws NullPointerException if fd is null. 374 */ 375 public @NonNull Builder setDataSource(@NonNull FileDescriptor fd, long offset, 376 long length) { 377 Preconditions.checkNotNull(fd); 378 if (offset < 0) { 379 offset = 0; 380 } 381 if (length < 0) { 382 length = FD_LENGTH_UNKNOWN; 383 } 384 resetDataSource(); 385 mType = TYPE_FD; 386 mFD = fd; 387 mFDOffset = offset; 388 mFDLength = length; 389 return this; 390 } 391 392 /** 393 * Sets the data source as a content Uri. 394 * 395 * @param context the Context to use when resolving the Uri 396 * @param uri the Content URI of the data you want to play 397 * @return the same Builder instance. 398 * @throws NullPointerException if context or uri is null. 399 */ 400 public @NonNull Builder setDataSource(@NonNull Context context, @NonNull Uri uri) { 401 Preconditions.checkNotNull(context, "context cannot be null"); 402 Preconditions.checkNotNull(uri, "uri cannot be null"); 403 resetDataSource(); 404 mType = TYPE_URI; 405 mUri = uri; 406 mUriContext = context; 407 return this; 408 } 409 410 /** 411 * Sets the data source as a content Uri. 412 * 413 * To provide cookies for the subsequent HTTP requests, you can install your own default 414 * cookie handler and use other variants of setDataSource APIs instead. 415 * 416 * <p><strong>Note</strong> that the cross domain redirection is allowed by default, 417 * but that can be changed with key/value pairs through the headers parameter with 418 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to 419 * disallow or allow cross domain redirection. 420 * 421 * @param context the Context to use when resolving the Uri 422 * @param uri the Content URI of the data you want to play 423 * @param headers the headers to be sent together with the request for the data 424 * The headers must not include cookies. Instead, use the cookies param. 425 * @param cookies the cookies to be sent together with the request 426 * @return the same Builder instance. 427 * @throws NullPointerException if context or uri is null. 428 * @throws IllegalArgumentException if the cookie handler is not of CookieManager type 429 * when cookies are provided. 430 */ 431 public @NonNull Builder setDataSource(@NonNull Context context, @NonNull Uri uri, 432 @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) { 433 Preconditions.checkNotNull(context, "context cannot be null"); 434 Preconditions.checkNotNull(uri); 435 if (cookies != null) { 436 CookieHandler cookieHandler = CookieHandler.getDefault(); 437 if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) { 438 throw new IllegalArgumentException( 439 "The cookie handler has to be of CookieManager type " 440 + "when cookies are provided."); 441 } 442 } 443 444 resetDataSource(); 445 mType = TYPE_URI; 446 mUri = uri; 447 if (headers != null) { 448 mUriHeader = new HashMap<String, String>(headers); 449 } 450 if (cookies != null) { 451 mUriCookies = new ArrayList<HttpCookie>(cookies); 452 } 453 mUriContext = context; 454 return this; 455 } 456 457 private void resetDataSource() { 458 mType = TYPE_NONE; 459 mMedia2DataSource = null; 460 mFD = null; 461 mFDOffset = 0; 462 mFDLength = FD_LENGTH_UNKNOWN; 463 mUri = null; 464 mUriHeader = null; 465 mUriCookies = null; 466 mUriContext = null; 467 } 468 } 469}