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