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