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.NonNull; 20 import android.annotation.Nullable; 21 import android.net.Uri; 22 import android.os.ParcelFileDescriptor; 23 24 import java.net.CookieHandler; 25 import java.net.CookieManager; 26 import java.net.HttpCookie; 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.List; 30 import java.util.Map; 31 32 /** 33 * Data source descriptor. 34 * 35 * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and 36 * {@link MediaPlayer2#setNextDataSources} to set data source for playback. 37 * 38 * @hide 39 */ 40 public class DataSourceDesc { 41 // intentionally less than long.MAX_VALUE 42 static final long LONG_MAX = 0x7ffffffffffffffL; 43 44 // keep consistent with native code 45 public static final long LONG_MAX_TIME_MS = LONG_MAX / 1000; 46 /** 47 * @hide 48 */ 49 public static final long LONG_MAX_TIME_US = LONG_MAX_TIME_MS * 1000; 50 51 public static final long POSITION_UNKNOWN = LONG_MAX_TIME_MS; 52 53 private String mMediaId; 54 private long mStartPositionMs = 0; 55 private long mEndPositionMs = POSITION_UNKNOWN; 56 DataSourceDesc(String mediaId, long startPositionMs, long endPositionMs)57 DataSourceDesc(String mediaId, long startPositionMs, long endPositionMs) { 58 mMediaId = mediaId; 59 mStartPositionMs = startPositionMs; 60 mEndPositionMs = endPositionMs; 61 } 62 63 /** 64 * Releases the resources held by this {@code DataSourceDesc} object. 65 */ close()66 void close() { 67 } 68 69 // Have to declare protected for finalize() since it is protected 70 // in the base class Object. 71 @Override finalize()72 protected void finalize() throws Throwable { 73 close(); 74 } 75 76 /** 77 * Return the media Id of data source. 78 * @return the media Id of data source 79 */ getMediaId()80 public @Nullable String getMediaId() { 81 return mMediaId; 82 } 83 84 /** 85 * Return the position in milliseconds at which the playback will start. 86 * @return the position in milliseconds at which the playback will start 87 */ getStartPosition()88 public long getStartPosition() { 89 return mStartPositionMs; 90 } 91 92 /** 93 * Return the position in milliseconds at which the playback will end. 94 * {@link #POSITION_UNKNOWN} means ending at the end of source content. 95 * @return the position in milliseconds at which the playback will end 96 */ getEndPosition()97 public long getEndPosition() { 98 return mEndPositionMs; 99 } 100 101 @Override toString()102 public String toString() { 103 final StringBuilder sb = new StringBuilder("DataSourceDesc{"); 104 sb.append("mMediaId=").append(mMediaId); 105 sb.append(", mStartPositionMs=").append(mStartPositionMs); 106 sb.append(", mEndPositionMs=").append(mEndPositionMs); 107 sb.append('}'); 108 return sb.toString(); 109 } 110 111 /** 112 * Builder for {@link DataSourceDesc}. 113 * <p> 114 * Here is an example where <code>Builder</code> is used to define the 115 * {@link DataSourceDesc} to be used by a {@link MediaPlayer2} instance: 116 * 117 * <pre class="prettyprint"> 118 * DataSourceDesc newDSD = new DataSourceDesc.Builder() 119 * .setDataSource(context, uri, headers, cookies) 120 * .setStartPosition(1000) 121 * .setEndPosition(15000) 122 * .build(); 123 * mediaplayer2.setDataSourceDesc(newDSD); 124 * </pre> 125 */ 126 public static final class Builder { 127 private static final int SOURCE_TYPE_UNKNOWN = 0; 128 private static final int SOURCE_TYPE_URI = 1; 129 private static final int SOURCE_TYPE_FILE = 2; 130 131 private int mSourceType = SOURCE_TYPE_UNKNOWN; 132 private String mMediaId; 133 private long mStartPositionMs = 0; 134 private long mEndPositionMs = POSITION_UNKNOWN; 135 136 // For UriDataSourceDesc 137 private Uri mUri; 138 private Map<String, String> mHeader; 139 private List<HttpCookie> mCookies; 140 141 // For FileDataSourceDesc 142 private ParcelFileDescriptor mPFD; 143 private long mOffset = 0; 144 private long mLength = FileDataSourceDesc.FD_LENGTH_UNKNOWN; 145 146 /** 147 * Constructs a new BuilderBase with the defaults. 148 */ Builder()149 public Builder() { 150 } 151 152 /** 153 * Constructs a new BuilderBase from a given {@link DataSourceDesc} instance 154 * @param dsd the {@link DataSourceDesc} object whose data will be reused 155 * in the new BuilderBase. 156 */ Builder(@ullable DataSourceDesc dsd)157 public Builder(@Nullable DataSourceDesc dsd) { 158 if (dsd == null) { 159 return; 160 } 161 mMediaId = dsd.mMediaId; 162 mStartPositionMs = dsd.mStartPositionMs; 163 mEndPositionMs = dsd.mEndPositionMs; 164 if (dsd instanceof FileDataSourceDesc) { 165 mSourceType = SOURCE_TYPE_FILE; 166 mPFD = ((FileDataSourceDesc) dsd).getParcelFileDescriptor(); 167 mOffset = ((FileDataSourceDesc) dsd).getOffset(); 168 mLength = ((FileDataSourceDesc) dsd).getLength(); 169 } else if (dsd instanceof UriDataSourceDesc) { 170 mSourceType = SOURCE_TYPE_URI; 171 mUri = ((UriDataSourceDesc) dsd).getUri(); 172 mHeader = ((UriDataSourceDesc) dsd).getHeaders(); 173 mCookies = ((UriDataSourceDesc) dsd).getCookies(); 174 } else { 175 throw new IllegalStateException("Unknown source type:" + mSourceType); 176 } 177 } 178 179 /** 180 * Sets all fields that have been set in the {@link DataSourceDesc} object. 181 * <code>IllegalStateException</code> will be thrown if there is conflict between fields. 182 * 183 * @return {@link DataSourceDesc} 184 */ 185 @NonNull build()186 public DataSourceDesc build() { 187 if (mSourceType == SOURCE_TYPE_UNKNOWN) { 188 throw new IllegalStateException("Source is not set."); 189 } 190 if (mStartPositionMs > mEndPositionMs) { 191 throw new IllegalStateException("Illegal start/end position: " 192 + mStartPositionMs + " : " + mEndPositionMs); 193 } 194 195 DataSourceDesc desc; 196 if (mSourceType == SOURCE_TYPE_FILE) { 197 desc = new FileDataSourceDesc( 198 mMediaId, mStartPositionMs, mEndPositionMs, mPFD, mOffset, mLength); 199 } else if (mSourceType == SOURCE_TYPE_URI) { 200 desc = new UriDataSourceDesc( 201 mMediaId, mStartPositionMs, mEndPositionMs, mUri, mHeader, mCookies); 202 } else { 203 throw new IllegalStateException("Unknown source type:" + mSourceType); 204 } 205 return desc; 206 } 207 208 /** 209 * Sets the media Id of this data source. 210 * 211 * @param mediaId the media Id of this data source 212 * @return the same Builder instance. 213 */ 214 @NonNull setMediaId(@ullable String mediaId)215 public Builder setMediaId(@Nullable String mediaId) { 216 mMediaId = mediaId; 217 return this; 218 } 219 220 /** 221 * Sets the start position in milliseconds at which the playback will start. 222 * Any negative number is treated as 0. 223 * 224 * @param position the start position in milliseconds at which the playback will start 225 * @return the same Builder instance. 226 * 227 */ 228 @NonNull setStartPosition(long position)229 public Builder setStartPosition(long position) { 230 if (position < 0) { 231 position = 0; 232 } 233 mStartPositionMs = position; 234 return this; 235 } 236 237 /** 238 * Sets the end position in milliseconds at which the playback will end. 239 * Any negative number is treated as maximum duration {@link #LONG_MAX_TIME_MS} 240 * of the data source 241 * 242 * @param position the end position in milliseconds at which the playback will end 243 * @return the same Builder instance. 244 */ 245 @NonNull setEndPosition(long position)246 public Builder setEndPosition(long position) { 247 if (position < 0) { 248 position = LONG_MAX_TIME_MS; 249 } 250 mEndPositionMs = position; 251 return this; 252 } 253 254 /** 255 * Sets the data source as a content Uri. 256 * 257 * @param uri the Content URI of the data you want to play 258 * @return the same Builder instance. 259 * @throws NullPointerException if context or uri is null. 260 */ 261 @NonNull setDataSource(@onNull Uri uri)262 public Builder setDataSource(@NonNull Uri uri) { 263 setSourceType(SOURCE_TYPE_URI); 264 Media2Utils.checkArgument(uri != null, "uri cannot be null"); 265 mUri = uri; 266 return this; 267 } 268 269 /** 270 * Sets the data source as a content Uri. 271 * 272 * To provide cookies for the subsequent HTTP requests, you can install your own default 273 * cookie handler and use other variants of setDataSource APIs instead. Alternatively, you 274 * can use this API to pass the cookies as a list of HttpCookie. If the app has not 275 * installed a CookieHandler already, {@link MediaPlayer2} will create a CookieManager 276 * and populates its CookieStore with the provided cookies when this data source is passed 277 * to {@link MediaPlayer2}. If the app has installed its own handler already, the handler 278 * is required to be of CookieManager type such that {@link MediaPlayer2} can update the 279 * manager’s CookieStore. 280 * 281 * <p><strong>Note</strong> that the cross domain redirection is allowed by default, 282 * but that can be changed with key/value pairs through the headers parameter with 283 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to 284 * disallow or allow cross domain redirection. 285 * 286 * @param uri the Content URI of the data you want to play 287 * @param headers the headers to be sent together with the request for the data 288 * The headers must not include cookies. Instead, use the cookies param. 289 * @param cookies the cookies to be sent together with the request 290 * @return the same Builder instance. 291 * @throws NullPointerException if context or uri is null. 292 * @throws IllegalArgumentException if the cookie handler is not of CookieManager type 293 * when cookies are provided. 294 */ 295 @NonNull setDataSource(@onNull Uri uri, @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)296 public Builder setDataSource(@NonNull Uri uri, @Nullable Map<String, String> headers, 297 @Nullable List<HttpCookie> cookies) { 298 setSourceType(SOURCE_TYPE_URI); 299 Media2Utils.checkArgument(uri != null, "uri cannot be null"); 300 if (cookies != null) { 301 CookieHandler cookieHandler = CookieHandler.getDefault(); 302 if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) { 303 throw new IllegalArgumentException( 304 "The cookie handler has to be of CookieManager type " 305 + "when cookies are provided."); 306 } 307 } 308 309 mUri = uri; 310 if (headers != null) { 311 mHeader = new HashMap<String, String>(headers); 312 } 313 if (cookies != null) { 314 mCookies = new ArrayList<HttpCookie>(cookies); 315 } 316 return this; 317 } 318 319 /** 320 * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be 321 * seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc} 322 * created by this builder is passed to {@link MediaPlayer2} via 323 * {@link MediaPlayer2#setDataSource}, 324 * {@link MediaPlayer2#setNextDataSource} or 325 * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will 326 * close the ParcelFileDescriptor. 327 * 328 * @param pfd the ParcelFileDescriptor for the file to play 329 * @return the same Builder instance. 330 * @throws NullPointerException if pfd is null. 331 */ 332 @NonNull setDataSource(@onNull ParcelFileDescriptor pfd)333 public Builder setDataSource(@NonNull ParcelFileDescriptor pfd) { 334 setSourceType(SOURCE_TYPE_FILE); 335 Media2Utils.checkArgument(pfd != null, "pfd cannot be null."); 336 mPFD = pfd; 337 return this; 338 } 339 340 /** 341 * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be 342 * seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc} 343 * created by this builder is passed to {@link MediaPlayer2} via 344 * {@link MediaPlayer2#setDataSource}, 345 * {@link MediaPlayer2#setNextDataSource} or 346 * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will 347 * close the ParcelFileDescriptor. 348 * 349 * Any negative number for offset is treated as 0. 350 * Any negative number for length is treated as maximum length of the data source. 351 * 352 * @param pfd the ParcelFileDescriptor for the file to play 353 * @param offset the offset into the file where the data to be played starts, in bytes 354 * @param length the length in bytes of the data to be played 355 * @return the same Builder instance. 356 * @throws NullPointerException if pfd is null. 357 */ 358 @NonNull setDataSource( @onNull ParcelFileDescriptor pfd, long offset, long length)359 public Builder setDataSource( 360 @NonNull ParcelFileDescriptor pfd, long offset, long length) { 361 setSourceType(SOURCE_TYPE_FILE); 362 if (pfd == null) { 363 throw new NullPointerException("pfd cannot be null."); 364 } 365 if (offset < 0) { 366 offset = 0; 367 } 368 if (length < 0) { 369 length = FileDataSourceDesc.FD_LENGTH_UNKNOWN; 370 } 371 mPFD = pfd; 372 mOffset = offset; 373 mLength = length; 374 return this; 375 } 376 setSourceType(int type)377 private void setSourceType(int type) { 378 if (mSourceType != SOURCE_TYPE_UNKNOWN) { 379 throw new IllegalStateException("Source is already set. type=" + mSourceType); 380 } 381 mSourceType = type; 382 } 383 } 384 } 385