blob: ff0d43e41350e204f30dca446adc0f12dae0785a [file] [log] [blame]
Sungsoo Lim728e13b2018-12-18 11:58:08 +09001/*
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 static android.media.MediaMetadata.METADATA_KEY_MEDIA_ID;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.text.TextUtils;
26import android.util.Log;
27import android.util.Pair;
28
29import com.android.internal.annotations.GuardedBy;
30
31import java.util.ArrayList;
32import java.util.List;
33import java.util.concurrent.Executor;
34
35/**
Sungsoo Limf894f772018-12-28 13:47:08 +090036 * A class with information on a single media item with the metadata information.
Sungsoo Lim728e13b2018-12-18 11:58:08 +090037 * <p>
38 * This API is not generally intended for third party application developers.
Sungsoo Lim938e8e92018-12-28 13:37:29 +090039 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
40 * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
41 * for consistent behavior across all devices.
Sungsoo Limf894f772018-12-28 13:47:08 +090042 * <p>
Sungsoo Lim728e13b2018-12-18 11:58:08 +090043 */
Sungsoo Limd922e0d2019-01-04 15:01:07 +090044public final class MediaItem2 implements Parcelable {
Sungsoo Lim728e13b2018-12-18 11:58:08 +090045 private static final String TAG = "MediaItem2";
46
47 // intentionally less than long.MAX_VALUE.
48 // Declare this first to avoid 'illegal forward reference'.
49 static final long LONG_MAX = 0x7ffffffffffffffL;
50
51 /**
52 * Used when a position is unknown.
53 *
54 * @see #getEndPosition()
55 */
56 public static final long POSITION_UNKNOWN = LONG_MAX;
57
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -070058 public static final @android.annotation.NonNull Parcelable.Creator<MediaItem2> CREATOR =
Sungsoo Lim728e13b2018-12-18 11:58:08 +090059 new Parcelable.Creator<MediaItem2>() {
60 @Override
61 public MediaItem2 createFromParcel(Parcel in) {
62 return new MediaItem2(in);
63 }
64
65 @Override
66 public MediaItem2[] newArray(int size) {
67 return new MediaItem2[size];
68 }
69 };
70
Sungsoo Lim728e13b2018-12-18 11:58:08 +090071 private static final long UNKNOWN_TIME = -1;
72
73 private final long mStartPositionMs;
74 private final long mEndPositionMs;
75
76 private final Object mLock = new Object();
77
78 @GuardedBy("mLock")
79 private MediaMetadata mMetadata;
80 @GuardedBy("mLock")
81 private final List<Pair<OnMetadataChangedListener, Executor>> mListeners = new ArrayList<>();
82
83 /**
84 * Used by {@link MediaItem2.Builder}.
85 */
86 // Note: Needs to be protected when we want to allow 3rd party player to define customized
87 // MediaItem2.
88 @SuppressWarnings("WeakerAccess") /* synthetic access */
89 MediaItem2(Builder builder) {
90 this(builder.mMetadata, builder.mStartPositionMs, builder.mEndPositionMs);
91 }
92
93 /**
94 * Used by Parcelable.Creator.
95 */
96 // Note: Needs to be protected when we want to allow 3rd party player to define customized
97 // MediaItem2.
98 @SuppressWarnings("WeakerAccess") /* synthetic access */
99 MediaItem2(Parcel in) {
100 this(in.readParcelable(MediaItem2.class.getClassLoader()), in.readLong(), in.readLong());
101 }
102
103 @SuppressWarnings("WeakerAccess") /* synthetic access */
104 MediaItem2(MediaItem2 item) {
105 this(item.mMetadata, item.mStartPositionMs, item.mEndPositionMs);
106 }
107
108 @SuppressWarnings("WeakerAccess") /* synthetic access */
109 MediaItem2(@Nullable MediaMetadata metadata, long startPositionMs, long endPositionMs) {
110 if (startPositionMs > endPositionMs) {
111 throw new IllegalArgumentException("Illegal start/end position: "
112 + startPositionMs + " : " + endPositionMs);
113 }
114 if (metadata != null && metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
115 long durationMs = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
116 if (durationMs != UNKNOWN_TIME && endPositionMs != POSITION_UNKNOWN
117 && endPositionMs > durationMs) {
118 throw new IllegalArgumentException("endPositionMs shouldn't be greater than"
119 + " duration in the metdata, endPositionMs=" + endPositionMs
120 + ", durationMs=" + durationMs);
121 }
122 }
123 mMetadata = metadata;
124 mStartPositionMs = startPositionMs;
125 mEndPositionMs = endPositionMs;
126 }
127
128 @Override
129 public String toString() {
130 final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
131 synchronized (mLock) {
132 sb.append("{mMetadata=").append(mMetadata);
133 sb.append(", mStartPositionMs=").append(mStartPositionMs);
134 sb.append(", mEndPositionMs=").append(mEndPositionMs);
135 sb.append('}');
136 }
137 return sb.toString();
138 }
139
140 /**
141 * Sets metadata. If the metadata is not {@code null}, its id should be matched with this
142 * instance's media id.
143 *
144 * @param metadata metadata to update
145 * @see MediaMetadata#METADATA_KEY_MEDIA_ID
146 */
147 public void setMetadata(@Nullable MediaMetadata metadata) {
148 List<Pair<OnMetadataChangedListener, Executor>> listeners = new ArrayList<>();
149 synchronized (mLock) {
150 if (mMetadata != null && metadata != null
151 && !TextUtils.equals(getMediaId(), metadata.getString(METADATA_KEY_MEDIA_ID))) {
152 Log.d(TAG, "MediaItem2's media ID shouldn't be changed");
153 return;
154 }
155 mMetadata = metadata;
156 listeners.addAll(mListeners);
157 }
158
159 for (Pair<OnMetadataChangedListener, Executor> pair : listeners) {
160 final OnMetadataChangedListener listener = pair.first;
161 pair.second.execute(new Runnable() {
162 @Override
163 public void run() {
164 listener.onMetadataChanged(MediaItem2.this);
165 }
166 });
167 }
168 }
169
170 /**
171 * Gets the metadata of the media.
172 *
173 * @return metadata from the session
174 */
175 public @Nullable MediaMetadata getMetadata() {
176 synchronized (mLock) {
177 return mMetadata;
178 }
179 }
180
181 /**
182 * Return the position in milliseconds at which the playback will start.
183 * @return the position in milliseconds at which the playback will start
184 */
185 public long getStartPosition() {
186 return mStartPositionMs;
187 }
188
189 /**
190 * Return the position in milliseconds at which the playback will end.
191 * {@link #POSITION_UNKNOWN} means ending at the end of source content.
192 * @return the position in milliseconds at which the playback will end
193 */
194 public long getEndPosition() {
195 return mEndPositionMs;
196 }
197
198 @Override
199 public int describeContents() {
200 return 0;
201 }
202
203 @Override
204 public void writeToParcel(Parcel dest, int flags) {
205 dest.writeParcelable(mMetadata, 0);
206 dest.writeLong(mStartPositionMs);
207 dest.writeLong(mEndPositionMs);
208 }
209
210 /**
211 * Gets the media id for this item. If it's not {@code null}, it's a persistent unique key
212 * for the underlying media content.
213 *
214 * @return media Id from the session
215 */
216 @Nullable String getMediaId() {
217 synchronized (mLock) {
218 return mMetadata != null
219 ? mMetadata.getString(METADATA_KEY_MEDIA_ID) : null;
220 }
221 }
222
223 void addOnMetadataChangedListener(Executor executor, OnMetadataChangedListener listener) {
224 synchronized (mLock) {
225 for (Pair<OnMetadataChangedListener, Executor> pair : mListeners) {
226 if (pair.first == listener) {
227 return;
228 }
229 }
230 mListeners.add(new Pair<>(listener, executor));
231 }
232 }
233
234 void removeOnMetadataChangedListener(OnMetadataChangedListener listener) {
235 synchronized (mLock) {
236 for (int i = mListeners.size() - 1; i >= 0; i--) {
237 if (mListeners.get(i).first == listener) {
238 mListeners.remove(i);
239 return;
240 }
241 }
242 }
243 }
244
245 /**
246 * Builder for {@link MediaItem2}.
247 */
Sungsoo Lim215d24a2019-03-05 15:01:24 +0900248 public static final class Builder {
Sungsoo Lim728e13b2018-12-18 11:58:08 +0900249 @SuppressWarnings("WeakerAccess") /* synthetic access */
250 MediaMetadata mMetadata;
251 @SuppressWarnings("WeakerAccess") /* synthetic access */
252 long mStartPositionMs = 0;
253 @SuppressWarnings("WeakerAccess") /* synthetic access */
254 long mEndPositionMs = POSITION_UNKNOWN;
255
256 /**
257 * Set the metadata of this instance. {@code null} for unset.
258 *
259 * @param metadata metadata
260 * @return this instance for chaining
261 */
262 public @NonNull Builder setMetadata(@Nullable MediaMetadata metadata) {
263 mMetadata = metadata;
264 return this;
265 }
266
267 /**
268 * Sets the start position in milliseconds at which the playback will start.
269 * Any negative number is treated as 0.
270 *
271 * @param position the start position in milliseconds at which the playback will start
272 * @return the same Builder instance.
273 */
274 public @NonNull Builder setStartPosition(long position) {
275 if (position < 0) {
276 position = 0;
277 }
278 mStartPositionMs = position;
279 return this;
280 }
281
282 /**
283 * Sets the end position in milliseconds at which the playback will end.
284 * Any negative number is treated as maximum length of the media item.
285 *
286 * @param position the end position in milliseconds at which the playback will end
287 * @return the same Builder instance.
288 */
289 public @NonNull Builder setEndPosition(long position) {
290 if (position < 0) {
291 position = POSITION_UNKNOWN;
292 }
293 mEndPositionMs = position;
294 return this;
295 }
296
297 /**
298 * Build {@link MediaItem2}.
299 *
300 * @return a new {@link MediaItem2}.
301 */
302 public @NonNull MediaItem2 build() {
303 return new MediaItem2(this);
304 }
305 }
306
307 interface OnMetadataChangedListener {
308 void onMetadataChanged(MediaItem2 item);
309 }
310}