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