The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2006 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 | |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 19 | import android.annotation.IntDef; |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 20 | import android.annotation.NonNull; |
Lajos Molnar | 7f08763 | 2015-04-22 20:21:54 -0700 | [diff] [blame] | 21 | import android.annotation.Nullable; |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 22 | import android.annotation.UnsupportedAppUsage; |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 23 | import android.app.ActivityThread; |
Robin Lee | 9d5b836 | 2016-09-06 20:30:48 +0100 | [diff] [blame] | 24 | import android.content.ContentProvider; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 25 | import android.content.ContentResolver; |
| 26 | import android.content.Context; |
| 27 | import android.content.res.AssetFileDescriptor; |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 28 | import android.graphics.SurfaceTexture; |
| 29 | import android.media.SubtitleController.Anchor; |
| 30 | import android.media.SubtitleTrack.RenderingWidget; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 31 | import android.net.Uri; |
| 32 | import android.os.Handler; |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 33 | import android.os.HandlerThread; |
Andreas Huber | d2506a5 | 2014-01-29 10:32:46 -0800 | [diff] [blame] | 34 | import android.os.IBinder; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 35 | import android.os.Looper; |
| 36 | import android.os.Message; |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 37 | import android.os.Parcel; |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 38 | import android.os.Parcelable; |
Ray Essick | 10353e3 | 2017-04-14 10:22:55 -0700 | [diff] [blame] | 39 | import android.os.PersistableBundle; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 40 | import android.os.PowerManager; |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 41 | import android.os.Process; |
Sridhar Vashist | f604911 | 2015-07-27 13:30:21 -0500 | [diff] [blame] | 42 | import android.os.SystemProperties; |
Jeff Sharkey | 3b566b8 | 2014-11-12 10:39:56 -0800 | [diff] [blame] | 43 | import android.provider.Settings; |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 44 | import android.system.ErrnoException; |
Tobias Thierer | 96aac9b3 | 2017-10-17 20:26:20 +0100 | [diff] [blame] | 45 | import android.system.Os; |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 46 | import android.system.OsConstants; |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 47 | import android.util.ArrayMap; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 48 | import android.util.Log; |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 49 | import android.util.Pair; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 50 | import android.view.Surface; |
| 51 | import android.view.SurfaceHolder; |
Robert Shih | 98b6842 | 2015-05-04 09:38:31 -0700 | [diff] [blame] | 52 | import android.widget.VideoView; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 53 | |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 54 | import com.android.internal.annotations.GuardedBy; |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 55 | import com.android.internal.util.Preconditions; |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 56 | |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 57 | import libcore.io.IoBridge; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 58 | import libcore.io.Streams; |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 59 | |
| 60 | import java.io.ByteArrayOutputStream; |
James Dong | e00b6f3 | 2012-04-11 21:18:43 -0700 | [diff] [blame] | 61 | import java.io.File; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 62 | import java.io.FileDescriptor; |
James Dong | e00b6f3 | 2012-04-11 21:18:43 -0700 | [diff] [blame] | 63 | import java.io.FileInputStream; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 64 | import java.io.IOException; |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 65 | import java.io.InputStream; |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 66 | import java.lang.annotation.Retention; |
| 67 | import java.lang.annotation.RetentionPolicy; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 68 | import java.lang.ref.WeakReference; |
Sungsoo Lim | 09d6202 | 2017-07-24 22:29:27 +0900 | [diff] [blame] | 69 | import java.net.CookieHandler; |
| 70 | import java.net.CookieManager; |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 71 | import java.net.HttpCookie; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 72 | import java.net.HttpURLConnection; |
John Grossman | 720aa28 | 2012-02-22 15:38:35 -0800 | [diff] [blame] | 73 | import java.net.InetSocketAddress; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 74 | import java.net.URL; |
| 75 | import java.nio.ByteOrder; |
| 76 | import java.util.Arrays; |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 77 | import java.util.BitSet; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 78 | import java.util.HashMap; |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 79 | import java.util.List; |
Andreas Huber | 2564300 | 2010-01-28 11:19:57 -0800 | [diff] [blame] | 80 | import java.util.Map; |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 81 | import java.util.Scanner; |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 82 | import java.util.Set; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 83 | import java.util.UUID; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 84 | import java.util.Vector; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 85 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 86 | |
| 87 | /** |
| 88 | * MediaPlayer class can be used to control playback |
| 89 | * of audio/video files and streams. An example on how to use the methods in |
| 90 | * this class can be found in {@link android.widget.VideoView}. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 91 | * |
| 92 | * <p>Topics covered here are: |
| 93 | * <ol> |
| 94 | * <li><a href="#StateDiagram">State Diagram</a> |
| 95 | * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a> |
| 96 | * <li><a href="#Permissions">Permissions</a> |
James Dong | 9ddb788 | 2011-06-14 14:01:57 -0700 | [diff] [blame] | 97 | * <li><a href="#Callbacks">Register informational and error callbacks</a> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 98 | * </ol> |
| 99 | * |
Joe Fernandez | 61fd1e8 | 2011-10-26 13:39:11 -0700 | [diff] [blame] | 100 | * <div class="special reference"> |
| 101 | * <h3>Developer Guides</h3> |
| 102 | * <p>For more information about how to use MediaPlayer, read the |
| 103 | * <a href="{@docRoot}guide/topics/media/mediaplayer.html">Media Playback</a> developer guide.</p> |
| 104 | * </div> |
| 105 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 106 | * <a name="StateDiagram"></a> |
| 107 | * <h3>State Diagram</h3> |
| 108 | * |
| 109 | * <p>Playback control of audio/video files and streams is managed as a state |
| 110 | * machine. The following diagram shows the life cycle and the states of a |
| 111 | * MediaPlayer object driven by the supported playback control operations. |
| 112 | * The ovals represent the states a MediaPlayer object may reside |
| 113 | * in. The arcs represent the playback control operations that drive the object |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 114 | * state transition. There are two types of arcs. The arcs with a single arrow |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 115 | * head represent synchronous method calls, while those with |
| 116 | * a double arrow head represent asynchronous method calls.</p> |
| 117 | * |
| 118 | * <p><img src="../../../images/mediaplayer_state_diagram.gif" |
| 119 | * alt="MediaPlayer State diagram" |
| 120 | * border="0" /></p> |
| 121 | * |
| 122 | * <p>From this state diagram, one can see that a MediaPlayer object has the |
| 123 | * following states:</p> |
| 124 | * <ul> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 125 | * <li>When a MediaPlayer object is just created using <code>new</code> or |
| 126 | * after {@link #reset()} is called, it is in the <em>Idle</em> state; and after |
| 127 | * {@link #release()} is called, it is in the <em>End</em> state. Between these |
| 128 | * two states is the life cycle of the MediaPlayer object. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 129 | * <ul> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 130 | * <li>There is a subtle but important difference between a newly constructed |
| 131 | * MediaPlayer object and the MediaPlayer object after {@link #reset()} |
| 132 | * is called. It is a programming error to invoke methods such |
| 133 | * as {@link #getCurrentPosition()}, |
| 134 | * {@link #getDuration()}, {@link #getVideoHeight()}, |
Jean-Michel Trivi | 2f7511f | 2016-11-28 15:40:27 -0800 | [diff] [blame] | 135 | * {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)}, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 136 | * {@link #setLooping(boolean)}, |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 137 | * {@link #setVolume(float, float)}, {@link #pause()}, {@link #start()}, |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 138 | * {@link #stop()}, {@link #seekTo(long, int)}, {@link #prepare()} or |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 139 | * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these |
| 140 | * methods is called right after a MediaPlayer object is constructed, |
| 141 | * the user supplied callback method OnErrorListener.onError() won't be |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 142 | * called by the internal player engine and the object state remains |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 143 | * unchanged; but if these methods are called right after {@link #reset()}, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 144 | * the user supplied callback method OnErrorListener.onError() will be |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 145 | * invoked by the internal player engine and the object will be |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 146 | * transfered to the <em>Error</em> state. </li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 147 | * <li>It is also recommended that once |
| 148 | * a MediaPlayer object is no longer being used, call {@link #release()} immediately |
| 149 | * so that resources used by the internal player engine associated with the |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 150 | * MediaPlayer object can be released immediately. Resource may include |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 151 | * singleton resources such as hardware acceleration components and |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 152 | * failure to call {@link #release()} may cause subsequent instances of |
| 153 | * MediaPlayer objects to fallback to software implementations or fail |
| 154 | * altogether. Once the MediaPlayer |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 155 | * object is in the <em>End</em> state, it can no longer be used and |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 156 | * there is no way to bring it back to any other state. </li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 157 | * <li>Furthermore, |
| 158 | * the MediaPlayer objects created using <code>new</code> is in the |
| 159 | * <em>Idle</em> state, while those created with one |
| 160 | * of the overloaded convenient <code>create</code> methods are <em>NOT</em> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 161 | * in the <em>Idle</em> state. In fact, the objects are in the <em>Prepared</em> |
| 162 | * state if the creation using <code>create</code> method is successful. |
| 163 | * </li> |
| 164 | * </ul> |
| 165 | * </li> |
| 166 | * <li>In general, some playback control operation may fail due to various |
| 167 | * reasons, such as unsupported audio/video format, poorly interleaved |
| 168 | * audio/video, resolution too high, streaming timeout, and the like. |
| 169 | * Thus, error reporting and recovery is an important concern under |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 170 | * these circumstances. Sometimes, due to programming errors, invoking a playback |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 171 | * control operation in an invalid state may also occur. Under all these |
| 172 | * error conditions, the internal player engine invokes a user supplied |
| 173 | * OnErrorListener.onError() method if an OnErrorListener has been |
| 174 | * registered beforehand via |
| 175 | * {@link #setOnErrorListener(android.media.MediaPlayer.OnErrorListener)}. |
| 176 | * <ul> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 177 | * <li>It is important to note that once an error occurs, the |
| 178 | * MediaPlayer object enters the <em>Error</em> state (except as noted |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 179 | * above), even if an error listener has not been registered by the application.</li> |
| 180 | * <li>In order to reuse a MediaPlayer object that is in the <em> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 181 | * Error</em> state and recover from the error, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 182 | * {@link #reset()} can be called to restore the object to its <em>Idle</em> |
| 183 | * state.</li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 184 | * <li>It is good programming practice to have your application |
| 185 | * register a OnErrorListener to look out for error notifications from |
| 186 | * the internal player engine.</li> |
hugh kennedy | c6f8ea4 | 2010-04-28 15:04:10 -0400 | [diff] [blame] | 187 | * <li>IllegalStateException is |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 188 | * thrown to prevent programming errors such as calling {@link #prepare()}, |
| 189 | * {@link #prepareAsync()}, or one of the overloaded <code>setDataSource |
| 190 | * </code> methods in an invalid state. </li> |
| 191 | * </ul> |
| 192 | * </li> |
| 193 | * <li>Calling |
| 194 | * {@link #setDataSource(FileDescriptor)}, or |
| 195 | * {@link #setDataSource(String)}, or |
| 196 | * {@link #setDataSource(Context, Uri)}, or |
Chris Watkins | 4eaa293 | 2015-03-20 10:31:42 -0700 | [diff] [blame] | 197 | * {@link #setDataSource(FileDescriptor, long, long)}, or |
| 198 | * {@link #setDataSource(MediaDataSource)} transfers a |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 199 | * MediaPlayer object in the <em>Idle</em> state to the |
| 200 | * <em>Initialized</em> state. |
| 201 | * <ul> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 202 | * <li>An IllegalStateException is thrown if |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 203 | * setDataSource() is called in any other state.</li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 204 | * <li>It is good programming |
| 205 | * practice to always look out for <code>IllegalArgumentException</code> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 206 | * and <code>IOException</code> that may be thrown from the overloaded |
| 207 | * <code>setDataSource</code> methods.</li> |
| 208 | * </ul> |
| 209 | * </li> |
| 210 | * <li>A MediaPlayer object must first enter the <em>Prepared</em> state |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 211 | * before playback can be started. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 212 | * <ul> |
| 213 | * <li>There are two ways (synchronous vs. |
| 214 | * asynchronous) that the <em>Prepared</em> state can be reached: |
| 215 | * either a call to {@link #prepare()} (synchronous) which |
| 216 | * transfers the object to the <em>Prepared</em> state once the method call |
| 217 | * returns, or a call to {@link #prepareAsync()} (asynchronous) which |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 218 | * first transfers the object to the <em>Preparing</em> state after the |
kopriva | cce5e4f | 2018-10-04 14:09:04 -0700 | [diff] [blame] | 219 | * call returns (which occurs almost right away) while the internal |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 220 | * player engine continues working on the rest of preparation work |
| 221 | * until the preparation work completes. When the preparation completes or when {@link #prepare()} call returns, |
| 222 | * the internal player engine then calls a user supplied callback method, |
| 223 | * onPrepared() of the OnPreparedListener interface, if an |
| 224 | * OnPreparedListener is registered beforehand via {@link |
| 225 | * #setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)}.</li> |
| 226 | * <li>It is important to note that |
| 227 | * the <em>Preparing</em> state is a transient state, and the behavior |
| 228 | * of calling any method with side effect while a MediaPlayer object is |
| 229 | * in the <em>Preparing</em> state is undefined.</li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 230 | * <li>An IllegalStateException is |
| 231 | * thrown if {@link #prepare()} or {@link #prepareAsync()} is called in |
| 232 | * any other state.</li> |
| 233 | * <li>While in the <em>Prepared</em> state, properties |
| 234 | * such as audio/sound volume, screenOnWhilePlaying, looping can be |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 235 | * adjusted by invoking the corresponding set methods.</li> |
| 236 | * </ul> |
| 237 | * </li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 238 | * <li>To start the playback, {@link #start()} must be called. After |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 239 | * {@link #start()} returns successfully, the MediaPlayer object is in the |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 240 | * <em>Started</em> state. {@link #isPlaying()} can be called to test |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 241 | * whether the MediaPlayer object is in the <em>Started</em> state. |
| 242 | * <ul> |
| 243 | * <li>While in the <em>Started</em> state, the internal player engine calls |
| 244 | * a user supplied OnBufferingUpdateListener.onBufferingUpdate() callback |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 245 | * method if a OnBufferingUpdateListener has been registered beforehand |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 246 | * via {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}. |
| 247 | * This callback allows applications to keep track of the buffering status |
| 248 | * while streaming audio/video.</li> |
| 249 | * <li>Calling {@link #start()} has not effect |
| 250 | * on a MediaPlayer object that is already in the <em>Started</em> state.</li> |
| 251 | * </ul> |
| 252 | * </li> |
| 253 | * <li>Playback can be paused and stopped, and the current playback position |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 254 | * can be adjusted. Playback can be paused via {@link #pause()}. When the call to |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 255 | * {@link #pause()} returns, the MediaPlayer object enters the |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 256 | * <em>Paused</em> state. Note that the transition from the <em>Started</em> |
| 257 | * state to the <em>Paused</em> state and vice versa happens |
| 258 | * asynchronously in the player engine. It may take some time before |
| 259 | * the state is updated in calls to {@link #isPlaying()}, and it can be |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 260 | * a number of seconds in the case of streamed content. |
| 261 | * <ul> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 262 | * <li>Calling {@link #start()} to resume playback for a paused |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 263 | * MediaPlayer object, and the resumed playback |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 264 | * position is the same as where it was paused. When the call to |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 265 | * {@link #start()} returns, the paused MediaPlayer object goes back to |
| 266 | * the <em>Started</em> state.</li> |
| 267 | * <li>Calling {@link #pause()} has no effect on |
| 268 | * a MediaPlayer object that is already in the <em>Paused</em> state.</li> |
| 269 | * </ul> |
| 270 | * </li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 271 | * <li>Calling {@link #stop()} stops playback and causes a |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 272 | * MediaPlayer in the <em>Started</em>, <em>Paused</em>, <em>Prepared |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 273 | * </em> or <em>PlaybackCompleted</em> state to enter the |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 274 | * <em>Stopped</em> state. |
| 275 | * <ul> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 276 | * <li>Once in the <em>Stopped</em> state, playback cannot be started |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 277 | * until {@link #prepare()} or {@link #prepareAsync()} are called to set |
| 278 | * the MediaPlayer object to the <em>Prepared</em> state again.</li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 279 | * <li>Calling {@link #stop()} has no effect on a MediaPlayer |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 280 | * object that is already in the <em>Stopped</em> state.</li> |
| 281 | * </ul> |
| 282 | * </li> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 283 | * <li>The playback position can be adjusted with a call to |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 284 | * {@link #seekTo(long, int)}. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 285 | * <ul> |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 286 | * <li>Although the asynchronuous {@link #seekTo(long, int)} |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 287 | * call returns right away, the actual seek operation may take a while to |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 288 | * finish, especially for audio/video being streamed. When the actual |
| 289 | * seek operation completes, the internal player engine calls a user |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 290 | * supplied OnSeekComplete.onSeekComplete() if an OnSeekCompleteListener |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 291 | * has been registered beforehand via |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 292 | * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}.</li> |
| 293 | * <li>Please |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 294 | * note that {@link #seekTo(long, int)} can also be called in the other states, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 295 | * such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 296 | * </em> state. When {@link #seekTo(long, int)} is called in those states, |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 297 | * one video frame will be displayed if the stream has video and the requested |
| 298 | * position is valid. |
| 299 | * </li> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 300 | * <li>Furthermore, the actual current playback position |
| 301 | * can be retrieved with a call to {@link #getCurrentPosition()}, which |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 302 | * is helpful for applications such as a Music player that need to keep |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 303 | * track of the playback progress.</li> |
| 304 | * </ul> |
| 305 | * </li> |
| 306 | * <li>When the playback reaches the end of stream, the playback completes. |
| 307 | * <ul> |
| 308 | * <li>If the looping mode was being set to <var>true</var>with |
| 309 | * {@link #setLooping(boolean)}, the MediaPlayer object shall remain in |
| 310 | * the <em>Started</em> state.</li> |
| 311 | * <li>If the looping mode was set to <var>false |
| 312 | * </var>, the player engine calls a user supplied callback method, |
| 313 | * OnCompletion.onCompletion(), if a OnCompletionListener is registered |
| 314 | * beforehand via {@link #setOnCompletionListener(OnCompletionListener)}. |
| 315 | * The invoke of the callback signals that the object is now in the <em> |
| 316 | * PlaybackCompleted</em> state.</li> |
| 317 | * <li>While in the <em>PlaybackCompleted</em> |
| 318 | * state, calling {@link #start()} can restart the playback from the |
| 319 | * beginning of the audio/video source.</li> |
| 320 | * </ul> |
| 321 | * |
| 322 | * |
| 323 | * <a name="Valid_and_Invalid_States"></a> |
| 324 | * <h3>Valid and invalid states</h3> |
| 325 | * |
| 326 | * <table border="0" cellspacing="0" cellpadding="0"> |
| 327 | * <tr><td>Method Name </p></td> |
kopriva | cce2d32 | 2018-09-27 15:01:04 -0700 | [diff] [blame] | 328 | * <td>Valid States </p></td> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 329 | * <td>Invalid States </p></td> |
| 330 | * <td>Comments </p></td></tr> |
Eric Laurent | 17cb280 | 2010-08-03 03:50:02 -0700 | [diff] [blame] | 331 | * <tr><td>attachAuxEffect </p></td> |
| 332 | * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> |
| 333 | * <td>{Idle, Error} </p></td> |
| 334 | * <td>This method must be called after setDataSource. |
| 335 | * Calling it does not change the object state. </p></td></tr> |
| 336 | * <tr><td>getAudioSessionId </p></td> |
| 337 | * <td>any </p></td> |
| 338 | * <td>{} </p></td> |
| 339 | * <td>This method can be called in any state and calling it does not change |
| 340 | * the object state. </p></td></tr> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 341 | * <tr><td>getCurrentPosition </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 342 | * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 343 | * PlaybackCompleted} </p></td> |
| 344 | * <td>{Error}</p></td> |
| 345 | * <td>Successful invoke of this method in a valid state does not change the |
| 346 | * state. Calling this method in an invalid state transfers the object |
| 347 | * to the <em>Error</em> state. </p></td></tr> |
| 348 | * <tr><td>getDuration </p></td> |
| 349 | * <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> |
| 350 | * <td>{Idle, Initialized, Error} </p></td> |
| 351 | * <td>Successful invoke of this method in a valid state does not change the |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 352 | * state. Calling this method in an invalid state transfers the object |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 353 | * to the <em>Error</em> state. </p></td></tr> |
| 354 | * <tr><td>getVideoHeight </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 355 | * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 356 | * PlaybackCompleted}</p></td> |
| 357 | * <td>{Error}</p></td> |
| 358 | * <td>Successful invoke of this method in a valid state does not change the |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 359 | * state. Calling this method in an invalid state transfers the object |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 360 | * to the <em>Error</em> state. </p></td></tr> |
| 361 | * <tr><td>getVideoWidth </p></td> |
| 362 | * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, |
| 363 | * PlaybackCompleted}</p></td> |
| 364 | * <td>{Error}</p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 365 | * <td>Successful invoke of this method in a valid state does not change |
| 366 | * the state. Calling this method in an invalid state transfers the |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 367 | * object to the <em>Error</em> state. </p></td></tr> |
| 368 | * <tr><td>isPlaying </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 369 | * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 370 | * PlaybackCompleted}</p></td> |
| 371 | * <td>{Error}</p></td> |
| 372 | * <td>Successful invoke of this method in a valid state does not change |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 373 | * the state. Calling this method in an invalid state transfers the |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 374 | * object to the <em>Error</em> state. </p></td></tr> |
| 375 | * <tr><td>pause </p></td> |
kmccormick | b04c07f | 2013-03-29 15:48:45 -0700 | [diff] [blame] | 376 | * <td>{Started, Paused, PlaybackCompleted}</p></td> |
| 377 | * <td>{Idle, Initialized, Prepared, Stopped, Error}</p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 378 | * <td>Successful invoke of this method in a valid state transfers the |
| 379 | * object to the <em>Paused</em> state. Calling this method in an |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 380 | * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> |
| 381 | * <tr><td>prepare </p></td> |
| 382 | * <td>{Initialized, Stopped} </p></td> |
| 383 | * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 384 | * <td>Successful invoke of this method in a valid state transfers the |
| 385 | * object to the <em>Prepared</em> state. Calling this method in an |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 386 | * invalid state throws an IllegalStateException.</p></td></tr> |
| 387 | * <tr><td>prepareAsync </p></td> |
| 388 | * <td>{Initialized, Stopped} </p></td> |
| 389 | * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 390 | * <td>Successful invoke of this method in a valid state transfers the |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 391 | * object to the <em>Preparing</em> state. Calling this method in an |
| 392 | * invalid state throws an IllegalStateException.</p></td></tr> |
| 393 | * <tr><td>release </p></td> |
| 394 | * <td>any </p></td> |
| 395 | * <td>{} </p></td> |
| 396 | * <td>After {@link #release()}, the object is no longer available. </p></td></tr> |
| 397 | * <tr><td>reset </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 398 | * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 399 | * PlaybackCompleted, Error}</p></td> |
| 400 | * <td>{}</p></td> |
| 401 | * <td>After {@link #reset()}, the object is like being just created.</p></td></tr> |
| 402 | * <tr><td>seekTo </p></td> |
| 403 | * <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td> |
| 404 | * <td>{Idle, Initialized, Stopped, Error}</p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 405 | * <td>Successful invoke of this method in a valid state does not change |
| 406 | * the state. Calling this method in an invalid state transfers the |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 407 | * object to the <em>Error</em> state. </p></td></tr> |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 408 | * <tr><td>setAudioAttributes </p></td> |
| 409 | * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, |
| 410 | * PlaybackCompleted}</p></td> |
| 411 | * <td>{Error}</p></td> |
| 412 | * <td>Successful invoke of this method does not change the state. In order for the |
| 413 | * target audio attributes type to become effective, this method must be called before |
| 414 | * prepare() or prepareAsync().</p></td></tr> |
Eric Laurent | 17cb280 | 2010-08-03 03:50:02 -0700 | [diff] [blame] | 415 | * <tr><td>setAudioSessionId </p></td> |
| 416 | * <td>{Idle} </p></td> |
| 417 | * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, |
| 418 | * Error} </p></td> |
| 419 | * <td>This method must be called in idle state as the audio session ID must be known before |
| 420 | * calling setDataSource. Calling it does not change the object state. </p></td></tr> |
Jean-Michel Trivi | 2f7511f | 2016-11-28 15:40:27 -0800 | [diff] [blame] | 421 | * <tr><td>setAudioStreamType (deprecated)</p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 422 | * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 423 | * PlaybackCompleted}</p></td> |
| 424 | * <td>{Error}</p></td> |
James Dong | 9d96354 | 2010-06-03 10:36:04 -0700 | [diff] [blame] | 425 | * <td>Successful invoke of this method does not change the state. In order for the |
| 426 | * target audio stream type to become effective, this method must be called before |
| 427 | * prepare() or prepareAsync().</p></td></tr> |
Eric Laurent | 17cb280 | 2010-08-03 03:50:02 -0700 | [diff] [blame] | 428 | * <tr><td>setAuxEffectSendLevel </p></td> |
| 429 | * <td>any</p></td> |
| 430 | * <td>{} </p></td> |
| 431 | * <td>Calling this method does not change the object state. </p></td></tr> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 432 | * <tr><td>setDataSource </p></td> |
| 433 | * <td>{Idle} </p></td> |
| 434 | * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, |
| 435 | * Error} </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 436 | * <td>Successful invoke of this method in a valid state transfers the |
| 437 | * object to the <em>Initialized</em> state. Calling this method in an |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 438 | * invalid state throws an IllegalStateException.</p></td></tr> |
| 439 | * <tr><td>setDisplay </p></td> |
| 440 | * <td>any </p></td> |
| 441 | * <td>{} </p></td> |
| 442 | * <td>This method can be called in any state and calling it does not change |
| 443 | * the object state. </p></td></tr> |
Jamie Gennis | b283dc6 | 2011-08-28 16:28:08 -0700 | [diff] [blame] | 444 | * <tr><td>setSurface </p></td> |
Glenn Kasten | cc562a3 | 2011-02-08 17:26:17 -0800 | [diff] [blame] | 445 | * <td>any </p></td> |
| 446 | * <td>{} </p></td> |
| 447 | * <td>This method can be called in any state and calling it does not change |
| 448 | * the object state. </p></td></tr> |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 449 | * <tr><td>setVideoScalingMode </p></td> |
| 450 | * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> |
| 451 | * <td>{Idle, Error}</p></td> |
| 452 | * <td>Successful invoke of this method does not change the state.</p></td></tr> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 453 | * <tr><td>setLooping </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 454 | * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 455 | * PlaybackCompleted}</p></td> |
| 456 | * <td>{Error}</p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 457 | * <td>Successful invoke of this method in a valid state does not change |
| 458 | * the state. Calling this method in an |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 459 | * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> |
| 460 | * <tr><td>isLooping </p></td> |
| 461 | * <td>any </p></td> |
| 462 | * <td>{} </p></td> |
| 463 | * <td>This method can be called in any state and calling it does not change |
| 464 | * the object state. </p></td></tr> |
| 465 | * <tr><td>setOnBufferingUpdateListener </p></td> |
| 466 | * <td>any </p></td> |
| 467 | * <td>{} </p></td> |
| 468 | * <td>This method can be called in any state and calling it does not change |
| 469 | * the object state. </p></td></tr> |
| 470 | * <tr><td>setOnCompletionListener </p></td> |
| 471 | * <td>any </p></td> |
| 472 | * <td>{} </p></td> |
| 473 | * <td>This method can be called in any state and calling it does not change |
| 474 | * the object state. </p></td></tr> |
| 475 | * <tr><td>setOnErrorListener </p></td> |
| 476 | * <td>any </p></td> |
| 477 | * <td>{} </p></td> |
| 478 | * <td>This method can be called in any state and calling it does not change |
| 479 | * the object state. </p></td></tr> |
| 480 | * <tr><td>setOnPreparedListener </p></td> |
| 481 | * <td>any </p></td> |
| 482 | * <td>{} </p></td> |
| 483 | * <td>This method can be called in any state and calling it does not change |
| 484 | * the object state. </p></td></tr> |
| 485 | * <tr><td>setOnSeekCompleteListener </p></td> |
| 486 | * <td>any </p></td> |
| 487 | * <td>{} </p></td> |
| 488 | * <td>This method can be called in any state and calling it does not change |
| 489 | * the object state. </p></td></tr> |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 490 | * <tr><td>setPlaybackParams</p></td> |
Wei Jia | ce4957e | 2016-06-16 15:18:20 -0700 | [diff] [blame] | 491 | * <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td> |
| 492 | * <td>{Idle, Stopped} </p></td> |
| 493 | * <td>This method will change state in some cases, depending on when it's called. |
| 494 | * </p></td></tr> |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 495 | * <tr><td>setScreenOnWhilePlaying</></td> |
| 496 | * <td>any </p></td> |
| 497 | * <td>{} </p></td> |
| 498 | * <td>This method can be called in any state and calling it does not change |
| 499 | * the object state. </p></td></tr> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 500 | * <tr><td>setVolume </p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 501 | * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 502 | * PlaybackCompleted}</p></td> |
| 503 | * <td>{Error}</p></td> |
| 504 | * <td>Successful invoke of this method does not change the state. |
| 505 | * <tr><td>setWakeMode </p></td> |
| 506 | * <td>any </p></td> |
| 507 | * <td>{} </p></td> |
| 508 | * <td>This method can be called in any state and calling it does not change |
| 509 | * the object state.</p></td></tr> |
| 510 | * <tr><td>start </p></td> |
| 511 | * <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td> |
| 512 | * <td>{Idle, Initialized, Stopped, Error}</p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 513 | * <td>Successful invoke of this method in a valid state transfers the |
| 514 | * object to the <em>Started</em> state. Calling this method in an |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 515 | * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> |
| 516 | * <tr><td>stop </p></td> |
| 517 | * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> |
| 518 | * <td>{Idle, Initialized, Error}</p></td> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 519 | * <td>Successful invoke of this method in a valid state transfers the |
| 520 | * object to the <em>Stopped</em> state. Calling this method in an |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 521 | * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 522 | * <tr><td>getTrackInfo </p></td> |
| 523 | * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> |
| 524 | * <td>{Idle, Initialized, Error}</p></td> |
| 525 | * <td>Successful invoke of this method does not change the state.</p></td></tr> |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 526 | * <tr><td>addTimedTextSource </p></td> |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 527 | * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> |
| 528 | * <td>{Idle, Initialized, Error}</p></td> |
| 529 | * <td>Successful invoke of this method does not change the state.</p></td></tr> |
| 530 | * <tr><td>selectTrack </p></td> |
| 531 | * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> |
| 532 | * <td>{Idle, Initialized, Error}</p></td> |
| 533 | * <td>Successful invoke of this method does not change the state.</p></td></tr> |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 534 | * <tr><td>deselectTrack </p></td> |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 535 | * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> |
| 536 | * <td>{Idle, Initialized, Error}</p></td> |
| 537 | * <td>Successful invoke of this method does not change the state.</p></td></tr> |
Eric Laurent | 619346f | 2010-06-21 09:27:30 -0700 | [diff] [blame] | 538 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 539 | * </table> |
| 540 | * |
| 541 | * <a name="Permissions"></a> |
| 542 | * <h3>Permissions</h3> |
| 543 | * <p>One may need to declare a corresponding WAKE_LOCK permission {@link |
| 544 | * android.R.styleable#AndroidManifestUsesPermission <uses-permission>} |
| 545 | * element. |
| 546 | * |
Dave Burke | fc301b0 | 2011-08-30 14:39:17 +0100 | [diff] [blame] | 547 | * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission |
| 548 | * when used with network-based content. |
| 549 | * |
James Dong | 9ddb788 | 2011-06-14 14:01:57 -0700 | [diff] [blame] | 550 | * <a name="Callbacks"></a> |
| 551 | * <h3>Callbacks</h3> |
| 552 | * <p>Applications may want to register for informational and error |
| 553 | * events in order to be informed of some internal state update and |
| 554 | * possible runtime errors during playback or streaming. Registration for |
| 555 | * these events is done by properly setting the appropriate listeners (via calls |
| 556 | * to |
| 557 | * {@link #setOnPreparedListener(OnPreparedListener)}setOnPreparedListener, |
| 558 | * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)}setOnVideoSizeChangedListener, |
| 559 | * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}setOnSeekCompleteListener, |
| 560 | * {@link #setOnCompletionListener(OnCompletionListener)}setOnCompletionListener, |
James Dong | d52ad9c | 2011-06-14 18:20:53 -0700 | [diff] [blame] | 561 | * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}setOnBufferingUpdateListener, |
James Dong | 9ddb788 | 2011-06-14 14:01:57 -0700 | [diff] [blame] | 562 | * {@link #setOnInfoListener(OnInfoListener)}setOnInfoListener, |
| 563 | * {@link #setOnErrorListener(OnErrorListener)}setOnErrorListener, etc). |
| 564 | * In order to receive the respective callback |
| 565 | * associated with these listeners, applications are required to create |
| 566 | * MediaPlayer objects on a thread with its own Looper running (main UI |
| 567 | * thread by default has a Looper running). |
| 568 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 569 | */ |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 570 | public class MediaPlayer extends PlayerBase |
| 571 | implements SubtitleController.Listener |
Andy Hung | fef734c | 2017-02-23 16:21:13 -0800 | [diff] [blame] | 572 | , VolumeAutomation |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 573 | , AudioRouting |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 574 | { |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 575 | /** |
| 576 | Constant to retrieve only the new metadata since the last |
| 577 | call. |
| 578 | // FIXME: unhide. |
| 579 | // FIXME: add link to getMetadata(boolean, boolean) |
| 580 | {@hide} |
| 581 | */ |
| 582 | public static final boolean METADATA_UPDATE_ONLY = true; |
| 583 | |
| 584 | /** |
| 585 | Constant to retrieve all the metadata. |
| 586 | // FIXME: unhide. |
| 587 | // FIXME: add link to getMetadata(boolean, boolean) |
| 588 | {@hide} |
| 589 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 590 | @UnsupportedAppUsage |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 591 | public static final boolean METADATA_ALL = false; |
| 592 | |
| 593 | /** |
| 594 | Constant to enable the metadata filter during retrieval. |
| 595 | // FIXME: unhide. |
| 596 | // FIXME: add link to getMetadata(boolean, boolean) |
| 597 | {@hide} |
| 598 | */ |
| 599 | public static final boolean APPLY_METADATA_FILTER = true; |
| 600 | |
| 601 | /** |
| 602 | Constant to disable the metadata filter during retrieval. |
| 603 | // FIXME: unhide. |
| 604 | // FIXME: add link to getMetadata(boolean, boolean) |
| 605 | {@hide} |
| 606 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 607 | @UnsupportedAppUsage |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 608 | public static final boolean BYPASS_METADATA_FILTER = false; |
| 609 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 610 | static { |
| 611 | System.loadLibrary("media_jni"); |
Marco Nelissen | 4935d05 | 2009-08-03 11:12:58 -0700 | [diff] [blame] | 612 | native_init(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 613 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 614 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 615 | private final static String TAG = "MediaPlayer"; |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 616 | // Name of the remote interface for the media player. Must be kept |
| 617 | // in sync with the 2nd parameter of the IMPLEMENT_META_INTERFACE |
| 618 | // macro invocation in IMediaPlayer.cpp |
| 619 | private final static String IMEDIA_PLAYER = "android.media.IMediaPlayer"; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 620 | |
Ashok Bhat | 075e9a1 | 2014-01-06 13:45:09 +0000 | [diff] [blame] | 621 | private long mNativeContext; // accessed by native methods |
| 622 | private long mNativeSurfaceTexture; // accessed by native methods |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 623 | private int mListenerContext; // accessed by native methods |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 624 | private SurfaceHolder mSurfaceHolder; |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 625 | @UnsupportedAppUsage |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 626 | private EventHandler mEventHandler; |
| 627 | private PowerManager.WakeLock mWakeLock = null; |
| 628 | private boolean mScreenOnWhilePlaying; |
| 629 | private boolean mStayAwake; |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 630 | private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; |
John Spurlock | 7b41467 | 2014-07-18 13:02:39 -0400 | [diff] [blame] | 631 | private int mUsage = -1; |
John Spurlock | bbfd31a | 2015-02-18 11:58:14 -0500 | [diff] [blame] | 632 | private boolean mBypassInterruptionPolicy; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 633 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 634 | // Modular DRM |
| 635 | private UUID mDrmUUID; |
| 636 | private final Object mDrmLock = new Object(); |
| 637 | private DrmInfo mDrmInfo; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 638 | private MediaDrm mDrmObj; |
| 639 | private byte[] mDrmSessionId; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 640 | private boolean mDrmInfoResolved; |
| 641 | private boolean mActiveDrmScheme; |
| 642 | private boolean mDrmConfigAllowed; |
| 643 | private boolean mDrmProvisioningInProgress; |
| 644 | private boolean mPrepareDrmInProgress; |
| 645 | private ProvisioningThread mDrmProvisioningThread; |
| 646 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 647 | /** |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 648 | * Default constructor. Consider using one of the create() methods for |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 649 | * synchronously instantiating a MediaPlayer from a Uri or resource. |
| 650 | * <p>When done with the MediaPlayer, you should call {@link #release()}, |
| 651 | * to free the resources. If not released, too many MediaPlayer instances may |
| 652 | * result in an exception.</p> |
| 653 | */ |
| 654 | public MediaPlayer() { |
Jean-Michel Trivi | 292a6a4 | 2016-12-01 08:32:15 -0800 | [diff] [blame] | 655 | super(new AudioAttributes.Builder().build(), |
| 656 | AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 657 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 658 | Looper looper; |
| 659 | if ((looper = Looper.myLooper()) != null) { |
| 660 | mEventHandler = new EventHandler(this, looper); |
| 661 | } else if ((looper = Looper.getMainLooper()) != null) { |
| 662 | mEventHandler = new EventHandler(this, looper); |
| 663 | } else { |
| 664 | mEventHandler = null; |
| 665 | } |
| 666 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 667 | mTimeProvider = new TimeProvider(this); |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 668 | mOpenSubtitleSources = new Vector<InputStream>(); |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 669 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 670 | /* Native setup requires a weak reference to our object. |
| 671 | * It's easier to create it here than in C++. |
| 672 | */ |
| 673 | native_setup(new WeakReference<MediaPlayer>(this)); |
Jean-Michel Trivi | 9dc22c2 | 2017-01-05 18:06:03 -0800 | [diff] [blame] | 674 | |
| 675 | baseRegisterPlayer(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 676 | } |
Dave Sparks | 8b0b174 | 2009-05-29 09:01:20 -0700 | [diff] [blame] | 677 | |
| 678 | /* |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 679 | * Update the MediaPlayer SurfaceTexture. |
| 680 | * Call after setting a new display surface. |
Dave Sparks | 8b0b174 | 2009-05-29 09:01:20 -0700 | [diff] [blame] | 681 | */ |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 682 | private native void _setVideoSurface(Surface surface); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 683 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 684 | /* Do not change these values (starting with INVOKE_ID) without updating |
| 685 | * their counterparts in include/media/mediaplayer.h! |
| 686 | */ |
| 687 | private static final int INVOKE_ID_GET_TRACK_INFO = 1; |
| 688 | private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2; |
| 689 | private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3; |
| 690 | private static final int INVOKE_ID_SELECT_TRACK = 4; |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 691 | private static final int INVOKE_ID_DESELECT_TRACK = 5; |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 692 | private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6; |
Robert Shih | 464da70 | 2014-05-29 10:54:32 -0700 | [diff] [blame] | 693 | private static final int INVOKE_ID_GET_SELECTED_TRACK = 7; |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 694 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 695 | /** |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 696 | * Create a request parcel which can be routed to the native media |
| 697 | * player using {@link #invoke(Parcel, Parcel)}. The Parcel |
| 698 | * returned has the proper InterfaceToken set. The caller should |
| 699 | * not overwrite that token, i.e it can only append data to the |
| 700 | * Parcel. |
| 701 | * |
| 702 | * @return A parcel suitable to hold a request for the native |
| 703 | * player. |
Marco Nelissen | 2d2cd32 | 2013-06-14 09:42:26 -0700 | [diff] [blame] | 704 | * {@hide} |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 705 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 706 | @UnsupportedAppUsage |
Marco Nelissen | 2d2cd32 | 2013-06-14 09:42:26 -0700 | [diff] [blame] | 707 | public Parcel newRequest() { |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 708 | Parcel parcel = Parcel.obtain(); |
| 709 | parcel.writeInterfaceToken(IMEDIA_PLAYER); |
| 710 | return parcel; |
| 711 | } |
| 712 | |
| 713 | /** |
| 714 | * Invoke a generic method on the native player using opaque |
| 715 | * parcels for the request and reply. Both payloads' format is a |
| 716 | * convention between the java caller and the native player. |
| 717 | * Must be called after setDataSource to make sure a native player |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 718 | * exists. On failure, a RuntimeException is thrown. |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 719 | * |
| 720 | * @param request Parcel with the data for the extension. The |
| 721 | * caller must use {@link #newRequest()} to get one. |
| 722 | * |
Dianne Hackborn | 4a51c20 | 2009-08-21 15:14:02 -0700 | [diff] [blame] | 723 | * @param reply Output parcel with the data returned by the |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 724 | * native player. |
Marco Nelissen | 2d2cd32 | 2013-06-14 09:42:26 -0700 | [diff] [blame] | 725 | * {@hide} |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 726 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 727 | @UnsupportedAppUsage |
Marco Nelissen | 2d2cd32 | 2013-06-14 09:42:26 -0700 | [diff] [blame] | 728 | public void invoke(Parcel request, Parcel reply) { |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 729 | int retcode = native_invoke(request, reply); |
| 730 | reply.setDataPosition(0); |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 731 | if (retcode != 0) { |
| 732 | throw new RuntimeException("failure code: " + retcode); |
| 733 | } |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 734 | } |
| 735 | |
| 736 | /** |
Glenn Kasten | cc562a3 | 2011-02-08 17:26:17 -0800 | [diff] [blame] | 737 | * Sets the {@link SurfaceHolder} to use for displaying the video |
Gloria Wang | 91784c99 | 2011-08-09 15:29:34 -0700 | [diff] [blame] | 738 | * portion of the media. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 739 | * |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 740 | * Either a surface holder or surface must be set if a display or video sink |
Jamie Gennis | b283dc6 | 2011-08-28 16:28:08 -0700 | [diff] [blame] | 741 | * is needed. Not calling this method or {@link #setSurface(Surface)} |
Glenn Kasten | cc562a3 | 2011-02-08 17:26:17 -0800 | [diff] [blame] | 742 | * when playing back a video will result in only the audio track being played. |
Gloria Wang | 91784c99 | 2011-08-09 15:29:34 -0700 | [diff] [blame] | 743 | * A null surface holder or surface will result in only the audio track being |
| 744 | * played. |
Glenn Kasten | 5c2faf3 | 2011-06-27 10:10:19 -0700 | [diff] [blame] | 745 | * |
| 746 | * @param sh the SurfaceHolder to use for video display |
Wei Jia | 77e351f | 2016-06-14 10:53:40 -0700 | [diff] [blame] | 747 | * @throws IllegalStateException if the internal player engine has not been |
| 748 | * initialized or has been released. |
Glenn Kasten | cc562a3 | 2011-02-08 17:26:17 -0800 | [diff] [blame] | 749 | */ |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 750 | public void setDisplay(SurfaceHolder sh) { |
| 751 | mSurfaceHolder = sh; |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 752 | Surface surface; |
Dave Sparks | 8b0b174 | 2009-05-29 09:01:20 -0700 | [diff] [blame] | 753 | if (sh != null) { |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 754 | surface = sh.getSurface(); |
Dave Sparks | 8b0b174 | 2009-05-29 09:01:20 -0700 | [diff] [blame] | 755 | } else { |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 756 | surface = null; |
Dave Sparks | 8b0b174 | 2009-05-29 09:01:20 -0700 | [diff] [blame] | 757 | } |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 758 | _setVideoSurface(surface); |
Glenn Kasten | cc562a3 | 2011-02-08 17:26:17 -0800 | [diff] [blame] | 759 | updateSurfaceScreenOn(); |
| 760 | } |
| 761 | |
| 762 | /** |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 763 | * Sets the {@link Surface} to be used as the sink for the video portion of |
Jamie Gennis | b283dc6 | 2011-08-28 16:28:08 -0700 | [diff] [blame] | 764 | * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but |
| 765 | * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a |
| 766 | * Surface will un-set any Surface or SurfaceHolder that was previously set. |
Gloria Wang | 91784c99 | 2011-08-09 15:29:34 -0700 | [diff] [blame] | 767 | * A null surface will result in only the audio track being played. |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 768 | * |
Jamie Gennis | b283dc6 | 2011-08-28 16:28:08 -0700 | [diff] [blame] | 769 | * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps |
| 770 | * returned from {@link SurfaceTexture#getTimestamp()} will have an |
| 771 | * unspecified zero point. These timestamps cannot be directly compared |
| 772 | * between different media sources, different instances of the same media |
| 773 | * source, or multiple runs of the same program. The timestamp is normally |
| 774 | * monotonically increasing and is unaffected by time-of-day adjustments, |
| 775 | * but it is reset when the position is set. |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 776 | * |
Jamie Gennis | b283dc6 | 2011-08-28 16:28:08 -0700 | [diff] [blame] | 777 | * @param surface The {@link Surface} to be used for the video portion of |
| 778 | * the media. |
Wei Jia | 77e351f | 2016-06-14 10:53:40 -0700 | [diff] [blame] | 779 | * @throws IllegalStateException if the internal player engine has not been |
| 780 | * initialized or has been released. |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 781 | */ |
| 782 | public void setSurface(Surface surface) { |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 783 | if (mScreenOnWhilePlaying && surface != null) { |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 784 | Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); |
| 785 | } |
| 786 | mSurfaceHolder = null; |
Ted Bonkenburg | 1ee6011 | 2011-07-26 09:51:18 -0700 | [diff] [blame] | 787 | _setVideoSurface(surface); |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 788 | updateSurfaceScreenOn(); |
| 789 | } |
| 790 | |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 791 | /* Do not change these video scaling mode values below without updating |
| 792 | * their counterparts in system/window.h! Please do not forget to update |
| 793 | * {@link #isVideoScalingModeSupported} when new video scaling modes |
| 794 | * are added. |
| 795 | */ |
| 796 | /** |
| 797 | * Specifies a video scaling mode. The content is stretched to the |
| 798 | * surface rendering area. When the surface has the same aspect ratio |
| 799 | * as the content, the aspect ratio of the content is maintained; |
| 800 | * otherwise, the aspect ratio of the content is not maintained when video |
Jeff Smith | a45746e | 2012-07-19 14:19:24 -0500 | [diff] [blame] | 801 | * is being rendered. Unlike {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}, |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 802 | * there is no content cropping with this video scaling mode. |
| 803 | */ |
| 804 | public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; |
| 805 | |
| 806 | /** |
| 807 | * Specifies a video scaling mode. The content is scaled, maintaining |
| 808 | * its aspect ratio. The whole surface area is always used. When the |
| 809 | * aspect ratio of the content is the same as the surface, no content |
| 810 | * is cropped; otherwise, content is cropped to fit the surface. |
| 811 | */ |
| 812 | public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; |
| 813 | /** |
| 814 | * Sets video scaling mode. To make the target video scaling mode |
| 815 | * effective during playback, this method must be called after |
| 816 | * data source is set. If not called, the default video |
| 817 | * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}. |
| 818 | * |
| 819 | * <p> The supported video scaling modes are: |
| 820 | * <ul> |
| 821 | * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT} |
| 822 | * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} |
| 823 | * </ul> |
| 824 | * |
Philipp Hasper | 802aa0f | 2015-05-12 09:09:25 +0200 | [diff] [blame] | 825 | * @param mode target video scaling mode. Must be one of the supported |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 826 | * video scaling modes; otherwise, IllegalArgumentException will be thrown. |
| 827 | * |
| 828 | * @see MediaPlayer#VIDEO_SCALING_MODE_SCALE_TO_FIT |
| 829 | * @see MediaPlayer#VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING |
| 830 | */ |
| 831 | public void setVideoScalingMode(int mode) { |
Bryan Mawhinney | 79a9cd4 | 2012-05-18 00:15:56 +0100 | [diff] [blame] | 832 | if (!isVideoScalingModeSupported(mode)) { |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 833 | final String msg = "Scaling mode " + mode + " is not supported"; |
| 834 | throw new IllegalArgumentException(msg); |
| 835 | } |
| 836 | Parcel request = Parcel.obtain(); |
| 837 | Parcel reply = Parcel.obtain(); |
Insun Kang | be0ea96 | 2012-05-04 20:52:57 +0900 | [diff] [blame] | 838 | try { |
| 839 | request.writeInterfaceToken(IMEDIA_PLAYER); |
| 840 | request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE); |
Bryan Mawhinney | c3a5cf9 | 2012-05-27 17:42:46 +0100 | [diff] [blame] | 841 | request.writeInt(mode); |
Insun Kang | be0ea96 | 2012-05-04 20:52:57 +0900 | [diff] [blame] | 842 | invoke(request, reply); |
| 843 | } finally { |
| 844 | request.recycle(); |
| 845 | reply.recycle(); |
| 846 | } |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 847 | } |
| 848 | |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 849 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 850 | * Convenience method to create a MediaPlayer for a given Uri. |
| 851 | * On success, {@link #prepare()} will already have been called and must not be called again. |
| 852 | * <p>When done with the MediaPlayer, you should call {@link #release()}, |
| 853 | * to free the resources. If not released, too many MediaPlayer instances will |
| 854 | * result in an exception.</p> |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 855 | * <p>Note that since {@link #prepare()} is called automatically in this method, |
Jean-Michel Trivi | 2f7511f | 2016-11-28 15:40:27 -0800 | [diff] [blame] | 856 | * you cannot change the audio |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 857 | * session ID (see {@link #setAudioSessionId(int)}) or audio attributes |
| 858 | * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 859 | * |
| 860 | * @param context the Context to use |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 861 | * @param uri the Uri from which to get the datasource |
| 862 | * @return a MediaPlayer object, or null if creation failed |
| 863 | */ |
| 864 | public static MediaPlayer create(Context context, Uri uri) { |
| 865 | return create (context, uri, null); |
| 866 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 867 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 868 | /** |
| 869 | * Convenience method to create a MediaPlayer for a given Uri. |
| 870 | * On success, {@link #prepare()} will already have been called and must not be called again. |
| 871 | * <p>When done with the MediaPlayer, you should call {@link #release()}, |
| 872 | * to free the resources. If not released, too many MediaPlayer instances will |
| 873 | * result in an exception.</p> |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 874 | * <p>Note that since {@link #prepare()} is called automatically in this method, |
Jean-Michel Trivi | 2f7511f | 2016-11-28 15:40:27 -0800 | [diff] [blame] | 875 | * you cannot change the audio |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 876 | * session ID (see {@link #setAudioSessionId(int)}) or audio attributes |
| 877 | * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 878 | * |
| 879 | * @param context the Context to use |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 880 | * @param uri the Uri from which to get the datasource |
| 881 | * @param holder the SurfaceHolder to use for displaying the video |
| 882 | * @return a MediaPlayer object, or null if creation failed |
| 883 | */ |
| 884 | public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder) { |
Jean-Michel Trivi | d70ad7a | 2014-07-15 16:34:17 -0700 | [diff] [blame] | 885 | int s = AudioSystem.newAudioSessionId(); |
| 886 | return create(context, uri, holder, null, s > 0 ? s : 0); |
| 887 | } |
| 888 | |
| 889 | /** |
| 890 | * Same factory method as {@link #create(Context, Uri, SurfaceHolder)} but that lets you specify |
| 891 | * the audio attributes and session ID to be used by the new MediaPlayer instance. |
| 892 | * @param context the Context to use |
| 893 | * @param uri the Uri from which to get the datasource |
| 894 | * @param holder the SurfaceHolder to use for displaying the video, may be null. |
| 895 | * @param audioAttributes the {@link AudioAttributes} to be used by the media player. |
| 896 | * @param audioSessionId the audio session ID to be used by the media player, |
Jean-Michel Trivi | 289cc8e | 2014-07-18 18:45:32 -0700 | [diff] [blame] | 897 | * see {@link AudioManager#generateAudioSessionId()} to obtain a new session. |
Jean-Michel Trivi | d70ad7a | 2014-07-15 16:34:17 -0700 | [diff] [blame] | 898 | * @return a MediaPlayer object, or null if creation failed |
| 899 | */ |
| 900 | public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder, |
| 901 | AudioAttributes audioAttributes, int audioSessionId) { |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 902 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 903 | try { |
| 904 | MediaPlayer mp = new MediaPlayer(); |
Jean-Michel Trivi | d70ad7a | 2014-07-15 16:34:17 -0700 | [diff] [blame] | 905 | final AudioAttributes aa = audioAttributes != null ? audioAttributes : |
| 906 | new AudioAttributes.Builder().build(); |
| 907 | mp.setAudioAttributes(aa); |
| 908 | mp.setAudioSessionId(audioSessionId); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 909 | mp.setDataSource(context, uri); |
| 910 | if (holder != null) { |
| 911 | mp.setDisplay(holder); |
| 912 | } |
| 913 | mp.prepare(); |
| 914 | return mp; |
| 915 | } catch (IOException ex) { |
| 916 | Log.d(TAG, "create failed:", ex); |
| 917 | // fall through |
| 918 | } catch (IllegalArgumentException ex) { |
| 919 | Log.d(TAG, "create failed:", ex); |
| 920 | // fall through |
| 921 | } catch (SecurityException ex) { |
| 922 | Log.d(TAG, "create failed:", ex); |
| 923 | // fall through |
| 924 | } |
| 925 | |
| 926 | return null; |
| 927 | } |
| 928 | |
Glenn Kasten | cc562a3 | 2011-02-08 17:26:17 -0800 | [diff] [blame] | 929 | // Note no convenience method to create a MediaPlayer with SurfaceTexture sink. |
| 930 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 931 | /** |
| 932 | * Convenience method to create a MediaPlayer for a given resource id. |
| 933 | * On success, {@link #prepare()} will already have been called and must not be called again. |
| 934 | * <p>When done with the MediaPlayer, you should call {@link #release()}, |
| 935 | * to free the resources. If not released, too many MediaPlayer instances will |
| 936 | * result in an exception.</p> |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 937 | * <p>Note that since {@link #prepare()} is called automatically in this method, |
Jean-Michel Trivi | 2f7511f | 2016-11-28 15:40:27 -0800 | [diff] [blame] | 938 | * you cannot change the audio |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 939 | * session ID (see {@link #setAudioSessionId(int)}) or audio attributes |
| 940 | * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p> |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 941 | * |
| 942 | * @param context the Context to use |
| 943 | * @param resid the raw resource id (<var>R.raw.<something></var>) for |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 944 | * the resource to use as the datasource |
| 945 | * @return a MediaPlayer object, or null if creation failed |
| 946 | */ |
| 947 | public static MediaPlayer create(Context context, int resid) { |
Jean-Michel Trivi | d70ad7a | 2014-07-15 16:34:17 -0700 | [diff] [blame] | 948 | int s = AudioSystem.newAudioSessionId(); |
| 949 | return create(context, resid, null, s > 0 ? s : 0); |
| 950 | } |
| 951 | |
| 952 | /** |
| 953 | * Same factory method as {@link #create(Context, int)} but that lets you specify the audio |
| 954 | * attributes and session ID to be used by the new MediaPlayer instance. |
| 955 | * @param context the Context to use |
| 956 | * @param resid the raw resource id (<var>R.raw.<something></var>) for |
| 957 | * the resource to use as the datasource |
| 958 | * @param audioAttributes the {@link AudioAttributes} to be used by the media player. |
| 959 | * @param audioSessionId the audio session ID to be used by the media player, |
Jean-Michel Trivi | 289cc8e | 2014-07-18 18:45:32 -0700 | [diff] [blame] | 960 | * see {@link AudioManager#generateAudioSessionId()} to obtain a new session. |
Jean-Michel Trivi | d70ad7a | 2014-07-15 16:34:17 -0700 | [diff] [blame] | 961 | * @return a MediaPlayer object, or null if creation failed |
| 962 | */ |
| 963 | public static MediaPlayer create(Context context, int resid, |
| 964 | AudioAttributes audioAttributes, int audioSessionId) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 965 | try { |
| 966 | AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid); |
| 967 | if (afd == null) return null; |
| 968 | |
| 969 | MediaPlayer mp = new MediaPlayer(); |
Jean-Michel Trivi | d70ad7a | 2014-07-15 16:34:17 -0700 | [diff] [blame] | 970 | |
| 971 | final AudioAttributes aa = audioAttributes != null ? audioAttributes : |
| 972 | new AudioAttributes.Builder().build(); |
| 973 | mp.setAudioAttributes(aa); |
| 974 | mp.setAudioSessionId(audioSessionId); |
| 975 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 976 | mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); |
| 977 | afd.close(); |
| 978 | mp.prepare(); |
| 979 | return mp; |
| 980 | } catch (IOException ex) { |
| 981 | Log.d(TAG, "create failed:", ex); |
| 982 | // fall through |
| 983 | } catch (IllegalArgumentException ex) { |
| 984 | Log.d(TAG, "create failed:", ex); |
| 985 | // fall through |
| 986 | } catch (SecurityException ex) { |
| 987 | Log.d(TAG, "create failed:", ex); |
| 988 | // fall through |
| 989 | } |
| 990 | return null; |
| 991 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 992 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 993 | /** |
| 994 | * Sets the data source as a content Uri. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 995 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 996 | * @param context the Context to use when resolving the Uri |
| 997 | * @param uri the Content URI of the data you want to play |
| 998 | * @throws IllegalStateException if it is called in an invalid state |
| 999 | */ |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1000 | public void setDataSource(@NonNull Context context, @NonNull Uri uri) |
| 1001 | throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1002 | setDataSource(context, uri, null, null); |
Andreas Huber | 2564300 | 2010-01-28 11:19:57 -0800 | [diff] [blame] | 1003 | } |
| 1004 | |
| 1005 | /** |
| 1006 | * Sets the data source as a content Uri. |
| 1007 | * |
Sungsoo Lim | 09d6202 | 2017-07-24 22:29:27 +0900 | [diff] [blame] | 1008 | * To provide cookies for the subsequent HTTP requests, you can install your own default cookie |
| 1009 | * handler and use other variants of setDataSource APIs instead. Alternatively, you can use |
| 1010 | * this API to pass the cookies as a list of HttpCookie. If the app has not installed |
| 1011 | * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with |
| 1012 | * the provided cookies. If the app has installed its own handler already, this API requires the |
| 1013 | * handler to be of CookieManager type such that the API can update the manager’s CookieStore. |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 1014 | * |
| 1015 | * <p><strong>Note</strong> that the cross domain redirection is allowed by default, |
| 1016 | * but that can be changed with key/value pairs through the headers parameter with |
| 1017 | * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to |
| 1018 | * disallow or allow cross domain redirection. |
Sungsoo Lim | 09d6202 | 2017-07-24 22:29:27 +0900 | [diff] [blame] | 1019 | * |
| 1020 | * @param context the Context to use when resolving the Uri |
| 1021 | * @param uri the Content URI of the data you want to play |
| 1022 | * @param headers the headers to be sent together with the request for the data |
| 1023 | * The headers must not include cookies. Instead, use the cookies param. |
| 1024 | * @param cookies the cookies to be sent together with the request |
| 1025 | * @throws IllegalArgumentException if cookies are provided and the installed handler is not |
| 1026 | * a CookieManager |
| 1027 | * @throws IllegalStateException if it is called in an invalid state |
| 1028 | * @throws NullPointerException if context or uri is null |
| 1029 | * @throws IOException if uri has a file scheme and an I/O error occurs |
Andreas Huber | 2564300 | 2010-01-28 11:19:57 -0800 | [diff] [blame] | 1030 | */ |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1031 | public void setDataSource(@NonNull Context context, @NonNull Uri uri, |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1032 | @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) |
Hassan Shojania | 5167ede | 2017-04-19 12:27:05 -0700 | [diff] [blame] | 1033 | throws IOException { |
| 1034 | if (context == null) { |
| 1035 | throw new NullPointerException("context param can not be null."); |
| 1036 | } |
| 1037 | |
| 1038 | if (uri == null) { |
| 1039 | throw new NullPointerException("uri param can not be null."); |
| 1040 | } |
| 1041 | |
Sungsoo Lim | 09d6202 | 2017-07-24 22:29:27 +0900 | [diff] [blame] | 1042 | if (cookies != null) { |
| 1043 | CookieHandler cookieHandler = CookieHandler.getDefault(); |
| 1044 | if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) { |
| 1045 | throw new IllegalArgumentException("The cookie handler has to be of CookieManager " |
| 1046 | + "type when cookies are provided."); |
| 1047 | } |
| 1048 | } |
| 1049 | |
Robin Lee | 9d5b836 | 2016-09-06 20:30:48 +0100 | [diff] [blame] | 1050 | // The context and URI usually belong to the calling user. Get a resolver for that user |
| 1051 | // and strip out the userId from the URI if present. |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1052 | final ContentResolver resolver = context.getContentResolver(); |
Jeff Sharkey | 3b566b8 | 2014-11-12 10:39:56 -0800 | [diff] [blame] | 1053 | final String scheme = uri.getScheme(); |
Robin Lee | 9d5b836 | 2016-09-06 20:30:48 +0100 | [diff] [blame] | 1054 | final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()); |
Jeff Sharkey | 3b566b8 | 2014-11-12 10:39:56 -0800 | [diff] [blame] | 1055 | if (ContentResolver.SCHEME_FILE.equals(scheme)) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1056 | setDataSource(uri.getPath()); |
| 1057 | return; |
Jeff Sharkey | 3b566b8 | 2014-11-12 10:39:56 -0800 | [diff] [blame] | 1058 | } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) |
Robin Lee | 9d5b836 | 2016-09-06 20:30:48 +0100 | [diff] [blame] | 1059 | && Settings.AUTHORITY.equals(authority)) { |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1060 | // Try cached ringtone first since the actual provider may not be |
| 1061 | // encryption aware, or it may be stored on CE media storage |
| 1062 | final int type = RingtoneManager.getDefaultType(uri); |
Andre Lago | 3fa139c | 2016-08-04 13:53:44 +0100 | [diff] [blame] | 1063 | final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId()); |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1064 | final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); |
| 1065 | if (attemptDataSource(resolver, cacheUri)) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1066 | return; |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1067 | } else if (attemptDataSource(resolver, actualUri)) { |
| 1068 | return; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1069 | } else { |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1070 | setDataSource(uri.toString(), headers, cookies); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1071 | } |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1072 | } else { |
| 1073 | // Try requested Uri locally first, or fallback to media server |
| 1074 | if (attemptDataSource(resolver, uri)) { |
| 1075 | return; |
| 1076 | } else { |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1077 | setDataSource(uri.toString(), headers, cookies); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1078 | } |
| 1079 | } |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1080 | } |
Dave Burke | fc301b0 | 2011-08-30 14:39:17 +0100 | [diff] [blame] | 1081 | |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1082 | /** |
| 1083 | * Sets the data source as a content Uri. |
| 1084 | * |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 1085 | * <p><strong>Note</strong> that the cross domain redirection is allowed by default, |
| 1086 | * but that can be changed with key/value pairs through the headers parameter with |
| 1087 | * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to |
| 1088 | * disallow or allow cross domain redirection. |
Sungsoo Lim | 09d6202 | 2017-07-24 22:29:27 +0900 | [diff] [blame] | 1089 | * |
| 1090 | * @param context the Context to use when resolving the Uri |
| 1091 | * @param uri the Content URI of the data you want to play |
| 1092 | * @param headers the headers to be sent together with the request for the data |
| 1093 | * @throws IllegalStateException if it is called in an invalid state |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1094 | */ |
| 1095 | public void setDataSource(@NonNull Context context, @NonNull Uri uri, |
| 1096 | @Nullable Map<String, String> headers) |
| 1097 | throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { |
| 1098 | setDataSource(context, uri, headers, null); |
| 1099 | } |
| 1100 | |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1101 | private boolean attemptDataSource(ContentResolver resolver, Uri uri) { |
| 1102 | try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) { |
| 1103 | setDataSource(afd); |
| 1104 | return true; |
| 1105 | } catch (NullPointerException | SecurityException | IOException ex) { |
| 1106 | Log.w(TAG, "Couldn't open " + uri + ": " + ex); |
| 1107 | return false; |
| 1108 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1109 | } |
| 1110 | |
| 1111 | /** |
| 1112 | * Sets the data source (file-path or http/rtsp URL) to use. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1113 | * |
Glenn Kasten | 55d1eea | 2012-03-09 11:02:31 -0800 | [diff] [blame] | 1114 | * <p>When <code>path</code> refers to a local file, the file may actually be opened by a |
| 1115 | * process other than the calling application. This implies that the pathname |
| 1116 | * should be an absolute path (as any other process runs with unspecified current working |
| 1117 | * directory), and that the pathname should reference a world-readable file. |
| 1118 | * As an alternative, the application could first open the file for reading, |
| 1119 | * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}. |
Sungsoo Lim | 09d6202 | 2017-07-24 22:29:27 +0900 | [diff] [blame] | 1120 | * |
| 1121 | * @param path the path of the file, or the http/rtsp URL of the stream you want to play |
| 1122 | * @throws IllegalStateException if it is called in an invalid state |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1123 | */ |
James Dong | e00b6f3 | 2012-04-11 21:18:43 -0700 | [diff] [blame] | 1124 | public void setDataSource(String path) |
| 1125 | throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { |
| 1126 | setDataSource(path, null, null); |
| 1127 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1128 | |
| 1129 | /** |
Andreas Huber | 2564300 | 2010-01-28 11:19:57 -0800 | [diff] [blame] | 1130 | * Sets the data source (file-path or http/rtsp URL) to use. |
| 1131 | * |
| 1132 | * @param path the path of the file, or the http/rtsp URL of the stream you want to play |
| 1133 | * @param headers the headers associated with the http request for the stream you want to play |
| 1134 | * @throws IllegalStateException if it is called in an invalid state |
| 1135 | * @hide pending API council |
| 1136 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 1137 | @UnsupportedAppUsage |
James Dong | 17524dc | 2011-05-04 13:41:58 -0700 | [diff] [blame] | 1138 | public void setDataSource(String path, Map<String, String> headers) |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1139 | throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { |
| 1140 | setDataSource(path, headers, null); |
| 1141 | } |
| 1142 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 1143 | @UnsupportedAppUsage |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1144 | private void setDataSource(String path, Map<String, String> headers, List<HttpCookie> cookies) |
Dave Burke | fc301b0 | 2011-08-30 14:39:17 +0100 | [diff] [blame] | 1145 | throws IOException, IllegalArgumentException, SecurityException, IllegalStateException |
James Dong | 17524dc | 2011-05-04 13:41:58 -0700 | [diff] [blame] | 1146 | { |
Andreas Huber | e4e7b48 | 2011-05-11 16:54:41 -0700 | [diff] [blame] | 1147 | String[] keys = null; |
| 1148 | String[] values = null; |
| 1149 | |
| 1150 | if (headers != null) { |
| 1151 | keys = new String[headers.size()]; |
| 1152 | values = new String[headers.size()]; |
| 1153 | |
| 1154 | int i = 0; |
| 1155 | for (Map.Entry<String, String> entry: headers.entrySet()) { |
| 1156 | keys[i] = entry.getKey(); |
| 1157 | values[i] = entry.getValue(); |
| 1158 | ++i; |
| 1159 | } |
James Dong | 17524dc | 2011-05-04 13:41:58 -0700 | [diff] [blame] | 1160 | } |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1161 | setDataSource(path, keys, values, cookies); |
James Dong | e00b6f3 | 2012-04-11 21:18:43 -0700 | [diff] [blame] | 1162 | } |
| 1163 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 1164 | @UnsupportedAppUsage |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1165 | private void setDataSource(String path, String[] keys, String[] values, |
| 1166 | List<HttpCookie> cookies) |
James Dong | e00b6f3 | 2012-04-11 21:18:43 -0700 | [diff] [blame] | 1167 | throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { |
Jeff Sharkey | c058a38 | 2013-02-19 14:44:41 -0800 | [diff] [blame] | 1168 | final Uri uri = Uri.parse(path); |
Lajos Molnar | d504ab1 | 2014-08-18 14:26:55 -0700 | [diff] [blame] | 1169 | final String scheme = uri.getScheme(); |
| 1170 | if ("file".equals(scheme)) { |
Jeff Sharkey | c058a38 | 2013-02-19 14:44:41 -0800 | [diff] [blame] | 1171 | path = uri.getPath(); |
Lajos Molnar | d504ab1 | 2014-08-18 14:26:55 -0700 | [diff] [blame] | 1172 | } else if (scheme != null) { |
| 1173 | // handle non-file sources |
| 1174 | nativeSetDataSource( |
Hassan Shojania | a6c969c | 2017-02-15 09:06:48 -0800 | [diff] [blame] | 1175 | MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies), |
Lajos Molnar | d504ab1 | 2014-08-18 14:26:55 -0700 | [diff] [blame] | 1176 | path, |
| 1177 | keys, |
| 1178 | values); |
| 1179 | return; |
Jeff Sharkey | c058a38 | 2013-02-19 14:44:41 -0800 | [diff] [blame] | 1180 | } |
| 1181 | |
| 1182 | final File file = new File(path); |
Jeff Sharkey | a0a955f | 2019-02-13 15:35:37 -0700 | [diff] [blame] | 1183 | try (FileInputStream is = new FileInputStream(file)) { |
| 1184 | setDataSource(is.getFD()); |
James Dong | e00b6f3 | 2012-04-11 21:18:43 -0700 | [diff] [blame] | 1185 | } |
James Dong | 17524dc | 2011-05-04 13:41:58 -0700 | [diff] [blame] | 1186 | } |
| 1187 | |
Andreas Huber | d2506a5 | 2014-01-29 10:32:46 -0800 | [diff] [blame] | 1188 | private native void nativeSetDataSource( |
| 1189 | IBinder httpServiceBinder, String path, String[] keys, String[] values) |
Dave Burke | fc301b0 | 2011-08-30 14:39:17 +0100 | [diff] [blame] | 1190 | throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; |
Andreas Huber | 2564300 | 2010-01-28 11:19:57 -0800 | [diff] [blame] | 1191 | |
| 1192 | /** |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1193 | * Sets the data source (AssetFileDescriptor) to use. It is the caller's |
| 1194 | * responsibility to close the file descriptor. It is safe to do so as soon |
| 1195 | * as this call returns. |
| 1196 | * |
| 1197 | * @param afd the AssetFileDescriptor for the file you want to play |
Marco Nelissen | 59dcf67 | 2016-04-11 14:41:33 -0700 | [diff] [blame] | 1198 | * @throws IllegalStateException if it is called in an invalid state |
| 1199 | * @throws IllegalArgumentException if afd is not a valid AssetFileDescriptor |
| 1200 | * @throws IOException if afd can not be read |
Jeff Sharkey | 413573a | 2016-02-22 17:52:45 -0700 | [diff] [blame] | 1201 | */ |
| 1202 | public void setDataSource(@NonNull AssetFileDescriptor afd) |
| 1203 | throws IOException, IllegalArgumentException, IllegalStateException { |
| 1204 | Preconditions.checkNotNull(afd); |
| 1205 | // Note: using getDeclaredLength so that our behavior is the same |
| 1206 | // as previous versions when the content provider is returning |
| 1207 | // a full file. |
| 1208 | if (afd.getDeclaredLength() < 0) { |
| 1209 | setDataSource(afd.getFileDescriptor()); |
| 1210 | } else { |
| 1211 | setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength()); |
| 1212 | } |
| 1213 | } |
| 1214 | |
| 1215 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1216 | * Sets the data source (FileDescriptor) to use. It is the caller's responsibility |
| 1217 | * to close the file descriptor. It is safe to do so as soon as this call returns. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1218 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1219 | * @param fd the FileDescriptor for the file you want to play |
| 1220 | * @throws IllegalStateException if it is called in an invalid state |
Marco Nelissen | 59dcf67 | 2016-04-11 14:41:33 -0700 | [diff] [blame] | 1221 | * @throws IllegalArgumentException if fd is not a valid FileDescriptor |
| 1222 | * @throws IOException if fd can not be read |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1223 | */ |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1224 | public void setDataSource(FileDescriptor fd) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1225 | throws IOException, IllegalArgumentException, IllegalStateException { |
| 1226 | // intentionally less than LONG_MAX |
| 1227 | setDataSource(fd, 0, 0x7ffffffffffffffL); |
| 1228 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1229 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1230 | /** |
Kenny Root | 6ab180a | 2009-06-13 06:16:23 -0500 | [diff] [blame] | 1231 | * Sets the data source (FileDescriptor) to use. The FileDescriptor must be |
| 1232 | * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1233 | * to close the file descriptor. It is safe to do so as soon as this call returns. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1234 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1235 | * @param fd the FileDescriptor for the file you want to play |
| 1236 | * @param offset the offset into the file where the data to be played starts, in bytes |
| 1237 | * @param length the length in bytes of the data to be played |
| 1238 | * @throws IllegalStateException if it is called in an invalid state |
Marco Nelissen | 59dcf67 | 2016-04-11 14:41:33 -0700 | [diff] [blame] | 1239 | * @throws IllegalArgumentException if fd is not a valid FileDescriptor |
| 1240 | * @throws IOException if fd can not be read |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1241 | */ |
Andreas Huber | d5f9fa5 | 2013-05-28 14:39:39 -0700 | [diff] [blame] | 1242 | public void setDataSource(FileDescriptor fd, long offset, long length) |
| 1243 | throws IOException, IllegalArgumentException, IllegalStateException { |
Andreas Huber | d5f9fa5 | 2013-05-28 14:39:39 -0700 | [diff] [blame] | 1244 | _setDataSource(fd, offset, length); |
| 1245 | } |
| 1246 | |
| 1247 | private native void _setDataSource(FileDescriptor fd, long offset, long length) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1248 | throws IOException, IllegalArgumentException, IllegalStateException; |
| 1249 | |
| 1250 | /** |
Chris Watkins | 4eaa293 | 2015-03-20 10:31:42 -0700 | [diff] [blame] | 1251 | * Sets the data source (MediaDataSource) to use. |
| 1252 | * |
| 1253 | * @param dataSource the MediaDataSource for the media you want to play |
| 1254 | * @throws IllegalStateException if it is called in an invalid state |
Marco Nelissen | 59dcf67 | 2016-04-11 14:41:33 -0700 | [diff] [blame] | 1255 | * @throws IllegalArgumentException if dataSource is not a valid MediaDataSource |
Chris Watkins | 4eaa293 | 2015-03-20 10:31:42 -0700 | [diff] [blame] | 1256 | */ |
| 1257 | public void setDataSource(MediaDataSource dataSource) |
| 1258 | throws IllegalArgumentException, IllegalStateException { |
| 1259 | _setDataSource(dataSource); |
| 1260 | } |
| 1261 | |
| 1262 | private native void _setDataSource(MediaDataSource dataSource) |
| 1263 | throws IllegalArgumentException, IllegalStateException; |
| 1264 | |
| 1265 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1266 | * Prepares the player for playback, synchronously. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1267 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1268 | * After setting the datasource and the display surface, you need to either |
| 1269 | * call prepare() or prepareAsync(). For files, it is OK to call prepare(), |
| 1270 | * which blocks until MediaPlayer is ready for playback. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1271 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1272 | * @throws IllegalStateException if it is called in an invalid state |
| 1273 | */ |
Robert Shih | c42a96d | 2014-07-25 11:08:50 -0700 | [diff] [blame] | 1274 | public void prepare() throws IOException, IllegalStateException { |
| 1275 | _prepare(); |
| 1276 | scanInternalSubtitleTracks(); |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 1277 | |
| 1278 | // DrmInfo, if any, has been resolved by now. |
| 1279 | synchronized (mDrmLock) { |
| 1280 | mDrmInfoResolved = true; |
| 1281 | } |
Robert Shih | c42a96d | 2014-07-25 11:08:50 -0700 | [diff] [blame] | 1282 | } |
| 1283 | |
| 1284 | private native void _prepare() throws IOException, IllegalStateException; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1285 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1286 | /** |
| 1287 | * Prepares the player for playback, asynchronously. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1288 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1289 | * After setting the datasource and the display surface, you need to either |
| 1290 | * call prepare() or prepareAsync(). For streams, you should call prepareAsync(), |
| 1291 | * which returns immediately, rather than blocking until enough data has been |
| 1292 | * buffered. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1293 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1294 | * @throws IllegalStateException if it is called in an invalid state |
| 1295 | */ |
Robert Shih | 07830aa | 2015-05-27 18:11:44 -0700 | [diff] [blame] | 1296 | public native void prepareAsync() throws IllegalStateException; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1297 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1298 | /** |
| 1299 | * Starts or resumes playback. If playback had previously been paused, |
| 1300 | * playback will continue from where it was paused. If playback had |
| 1301 | * been stopped, or never started before, playback will start at the |
| 1302 | * beginning. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1303 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1304 | * @throws IllegalStateException if it is called in an invalid state |
| 1305 | */ |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 1306 | public void start() throws IllegalStateException { |
Jean-Michel Trivi | 99489cc | 2017-01-25 19:08:49 -0800 | [diff] [blame] | 1307 | //FIXME use lambda to pass startImpl to superclass |
| 1308 | final int delay = getStartDelayMs(); |
| 1309 | if (delay == 0) { |
| 1310 | startImpl(); |
| 1311 | } else { |
| 1312 | new Thread() { |
| 1313 | public void run() { |
| 1314 | try { |
| 1315 | Thread.sleep(delay); |
| 1316 | } catch (InterruptedException e) { |
| 1317 | e.printStackTrace(); |
| 1318 | } |
| 1319 | baseSetStartDelayMs(0); |
Jean-Michel Trivi | 10ffc78 | 2017-02-07 10:47:31 -0800 | [diff] [blame] | 1320 | try { |
| 1321 | startImpl(); |
| 1322 | } catch (IllegalStateException e) { |
| 1323 | // fail silently for a state exception when it is happening after |
| 1324 | // a delayed start, as the player state could have changed between the |
| 1325 | // call to start() and the execution of startImpl() |
| 1326 | } |
Jean-Michel Trivi | 99489cc | 2017-01-25 19:08:49 -0800 | [diff] [blame] | 1327 | } |
| 1328 | }.start(); |
| 1329 | } |
| 1330 | } |
| 1331 | |
| 1332 | private void startImpl() { |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 1333 | baseStart(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1334 | stayAwake(true); |
| 1335 | _start(); |
| 1336 | } |
| 1337 | |
| 1338 | private native void _start() throws IllegalStateException; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1339 | |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 1340 | |
| 1341 | private int getAudioStreamType() { |
| 1342 | if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { |
| 1343 | mStreamType = _getAudioStreamType(); |
| 1344 | } |
| 1345 | return mStreamType; |
| 1346 | } |
| 1347 | |
| 1348 | private native int _getAudioStreamType() throws IllegalStateException; |
| 1349 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1350 | /** |
Kevin Rocard | ec5305f | 2017-05-17 19:01:54 -0700 | [diff] [blame] | 1351 | * Stops playback after playback has been started or paused. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1352 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1353 | * @throws IllegalStateException if the internal player engine has not been |
| 1354 | * initialized. |
| 1355 | */ |
| 1356 | public void stop() throws IllegalStateException { |
| 1357 | stayAwake(false); |
| 1358 | _stop(); |
Jean-Michel Trivi | 292a6a4 | 2016-12-01 08:32:15 -0800 | [diff] [blame] | 1359 | baseStop(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1360 | } |
| 1361 | |
| 1362 | private native void _stop() throws IllegalStateException; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1363 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1364 | /** |
| 1365 | * Pauses playback. Call start() to resume. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1366 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1367 | * @throws IllegalStateException if the internal player engine has not been |
| 1368 | * initialized. |
| 1369 | */ |
| 1370 | public void pause() throws IllegalStateException { |
| 1371 | stayAwake(false); |
| 1372 | _pause(); |
Jean-Michel Trivi | 292a6a4 | 2016-12-01 08:32:15 -0800 | [diff] [blame] | 1373 | basePause(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1374 | } |
| 1375 | |
| 1376 | private native void _pause() throws IllegalStateException; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1377 | |
Jean-Michel Trivi | 9dc22c2 | 2017-01-05 18:06:03 -0800 | [diff] [blame] | 1378 | @Override |
| 1379 | void playerStart() { |
| 1380 | start(); |
| 1381 | } |
| 1382 | |
| 1383 | @Override |
| 1384 | void playerPause() { |
| 1385 | pause(); |
| 1386 | } |
| 1387 | |
| 1388 | @Override |
| 1389 | void playerStop() { |
| 1390 | stop(); |
| 1391 | } |
| 1392 | |
Andy Hung | 035d4ec | 2017-01-24 13:45:02 -0800 | [diff] [blame] | 1393 | @Override |
| 1394 | /* package */ int playerApplyVolumeShaper( |
| 1395 | @NonNull VolumeShaper.Configuration configuration, |
| 1396 | @NonNull VolumeShaper.Operation operation) { |
| 1397 | return native_applyVolumeShaper(configuration, operation); |
| 1398 | } |
| 1399 | |
| 1400 | @Override |
| 1401 | /* package */ @Nullable VolumeShaper.State playerGetVolumeShaperState(int id) { |
| 1402 | return native_getVolumeShaperState(id); |
| 1403 | } |
| 1404 | |
Andy Hung | fef734c | 2017-02-23 16:21:13 -0800 | [diff] [blame] | 1405 | @Override |
| 1406 | public @NonNull VolumeShaper createVolumeShaper( |
| 1407 | @NonNull VolumeShaper.Configuration configuration) { |
| 1408 | return new VolumeShaper(configuration, this); |
| 1409 | } |
| 1410 | |
Andy Hung | 035d4ec | 2017-01-24 13:45:02 -0800 | [diff] [blame] | 1411 | private native int native_applyVolumeShaper( |
| 1412 | @NonNull VolumeShaper.Configuration configuration, |
| 1413 | @NonNull VolumeShaper.Operation operation); |
| 1414 | |
| 1415 | private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id); |
| 1416 | |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 1417 | //-------------------------------------------------------------------------- |
| 1418 | // Explicit Routing |
| 1419 | //-------------------- |
| 1420 | private AudioDeviceInfo mPreferredDevice = null; |
| 1421 | |
| 1422 | /** |
| 1423 | * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route |
| 1424 | * the output from this MediaPlayer. |
| 1425 | * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source. |
| 1426 | * If deviceInfo is null, default routing is restored. |
| 1427 | * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and |
| 1428 | * does not correspond to a valid audio device. |
| 1429 | */ |
| 1430 | @Override |
| 1431 | public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) { |
| 1432 | if (deviceInfo != null && !deviceInfo.isSink()) { |
| 1433 | return false; |
| 1434 | } |
| 1435 | int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0; |
| 1436 | boolean status = native_setOutputDevice(preferredDeviceId); |
| 1437 | if (status == true) { |
| 1438 | synchronized (this) { |
| 1439 | mPreferredDevice = deviceInfo; |
| 1440 | } |
| 1441 | } |
| 1442 | return status; |
| 1443 | } |
| 1444 | |
| 1445 | /** |
| 1446 | * Returns the selected output specified by {@link #setPreferredDevice}. Note that this |
| 1447 | * is not guaranteed to correspond to the actual device being used for playback. |
| 1448 | */ |
| 1449 | @Override |
| 1450 | public AudioDeviceInfo getPreferredDevice() { |
| 1451 | synchronized (this) { |
| 1452 | return mPreferredDevice; |
| 1453 | } |
| 1454 | } |
| 1455 | |
| 1456 | /** |
| 1457 | * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer |
| 1458 | * Note: The query is only valid if the MediaPlayer is currently playing. |
| 1459 | * If the player is not playing, the returned device can be null or correspond to previously |
| 1460 | * selected device when the player was last active. |
| 1461 | */ |
| 1462 | @Override |
| 1463 | public AudioDeviceInfo getRoutedDevice() { |
| 1464 | int deviceId = native_getRoutedDeviceId(); |
| 1465 | if (deviceId == 0) { |
| 1466 | return null; |
| 1467 | } |
| 1468 | AudioDeviceInfo[] devices = |
| 1469 | AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); |
| 1470 | for (int i = 0; i < devices.length; i++) { |
| 1471 | if (devices[i].getId() == deviceId) { |
| 1472 | return devices[i]; |
| 1473 | } |
| 1474 | } |
| 1475 | return null; |
| 1476 | } |
| 1477 | |
| 1478 | /* |
| 1479 | * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler. |
| 1480 | */ |
Andreas Gampe | 57d0391 | 2018-02-05 13:27:39 -0800 | [diff] [blame] | 1481 | @GuardedBy("mRoutingChangeListeners") |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 1482 | private void enableNativeRoutingCallbacksLocked(boolean enabled) { |
| 1483 | if (mRoutingChangeListeners.size() == 0) { |
| 1484 | native_enableDeviceCallback(enabled); |
| 1485 | } |
| 1486 | } |
| 1487 | |
| 1488 | /** |
| 1489 | * The list of AudioRouting.OnRoutingChangedListener interfaces added (with |
| 1490 | * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)} |
| 1491 | * by an app to receive (re)routing notifications. |
| 1492 | */ |
| 1493 | @GuardedBy("mRoutingChangeListeners") |
| 1494 | private ArrayMap<AudioRouting.OnRoutingChangedListener, |
| 1495 | NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>(); |
| 1496 | |
| 1497 | /** |
| 1498 | * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing |
| 1499 | * changes on this MediaPlayer. |
| 1500 | * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive |
| 1501 | * notifications of rerouting events. |
| 1502 | * @param handler Specifies the {@link Handler} object for the thread on which to execute |
| 1503 | * the callback. If <code>null</code>, the handler on the main looper will be used. |
| 1504 | */ |
| 1505 | @Override |
| 1506 | public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, |
| 1507 | Handler handler) { |
| 1508 | synchronized (mRoutingChangeListeners) { |
| 1509 | if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { |
| 1510 | enableNativeRoutingCallbacksLocked(true); |
| 1511 | mRoutingChangeListeners.put( |
jiabin | 9070e29 | 2017-11-06 15:36:27 -0800 | [diff] [blame] | 1512 | listener, new NativeRoutingEventHandlerDelegate(this, listener, |
| 1513 | handler != null ? handler : mEventHandler)); |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 1514 | } |
| 1515 | } |
| 1516 | } |
| 1517 | |
| 1518 | /** |
| 1519 | * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added |
| 1520 | * to receive rerouting notifications. |
| 1521 | * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface |
| 1522 | * to remove. |
| 1523 | */ |
| 1524 | @Override |
| 1525 | public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) { |
| 1526 | synchronized (mRoutingChangeListeners) { |
| 1527 | if (mRoutingChangeListeners.containsKey(listener)) { |
| 1528 | mRoutingChangeListeners.remove(listener); |
| 1529 | enableNativeRoutingCallbacksLocked(false); |
| 1530 | } |
| 1531 | } |
| 1532 | } |
| 1533 | |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 1534 | private native final boolean native_setOutputDevice(int deviceId); |
| 1535 | private native final int native_getRoutedDeviceId(); |
| 1536 | private native final void native_enableDeviceCallback(boolean enabled); |
| 1537 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1538 | /** |
| 1539 | * Set the low-level power management behavior for this MediaPlayer. This |
| 1540 | * can be used when the MediaPlayer is not playing through a SurfaceHolder |
| 1541 | * set with {@link #setDisplay(SurfaceHolder)} and thus can use the |
| 1542 | * high-level {@link #setScreenOnWhilePlaying(boolean)} feature. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1543 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1544 | * <p>This function has the MediaPlayer access the low-level power manager |
| 1545 | * service to control the device's power usage while playing is occurring. |
| 1546 | * The parameter is a combination of {@link android.os.PowerManager} wake flags. |
| 1547 | * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK} |
| 1548 | * permission. |
| 1549 | * By default, no attempt is made to keep the device awake during playback. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1550 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1551 | * @param context the Context to use |
| 1552 | * @param mode the power/wake mode to set |
| 1553 | * @see android.os.PowerManager |
| 1554 | */ |
| 1555 | public void setWakeMode(Context context, int mode) { |
| 1556 | boolean washeld = false; |
Sridhar Vashist | f604911 | 2015-07-27 13:30:21 -0500 | [diff] [blame] | 1557 | |
| 1558 | /* Disable persistant wakelocks in media player based on property */ |
| 1559 | if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) { |
| 1560 | Log.w(TAG, "IGNORING setWakeMode " + mode); |
| 1561 | return; |
| 1562 | } |
| 1563 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1564 | if (mWakeLock != null) { |
| 1565 | if (mWakeLock.isHeld()) { |
| 1566 | washeld = true; |
| 1567 | mWakeLock.release(); |
| 1568 | } |
| 1569 | mWakeLock = null; |
| 1570 | } |
| 1571 | |
| 1572 | PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); |
| 1573 | mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer.class.getName()); |
| 1574 | mWakeLock.setReferenceCounted(false); |
| 1575 | if (washeld) { |
| 1576 | mWakeLock.acquire(); |
| 1577 | } |
| 1578 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1579 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1580 | /** |
| 1581 | * Control whether we should use the attached SurfaceHolder to keep the |
| 1582 | * screen on while video playback is occurring. This is the preferred |
| 1583 | * method over {@link #setWakeMode} where possible, since it doesn't |
| 1584 | * require that the application have permission for low-level wake lock |
| 1585 | * access. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1586 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1587 | * @param screenOn Supply true to keep the screen on, false to allow it |
| 1588 | * to turn off. |
| 1589 | */ |
| 1590 | public void setScreenOnWhilePlaying(boolean screenOn) { |
| 1591 | if (mScreenOnWhilePlaying != screenOn) { |
Ted Bonkenburg | 0de171b | 2011-07-15 15:10:10 -0700 | [diff] [blame] | 1592 | if (screenOn && mSurfaceHolder == null) { |
| 1593 | Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder"); |
Glenn Kasten | 817c161 | 2011-04-13 08:11:47 -0700 | [diff] [blame] | 1594 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1595 | mScreenOnWhilePlaying = screenOn; |
| 1596 | updateSurfaceScreenOn(); |
| 1597 | } |
| 1598 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1599 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1600 | private void stayAwake(boolean awake) { |
| 1601 | if (mWakeLock != null) { |
| 1602 | if (awake && !mWakeLock.isHeld()) { |
| 1603 | mWakeLock.acquire(); |
| 1604 | } else if (!awake && mWakeLock.isHeld()) { |
| 1605 | mWakeLock.release(); |
| 1606 | } |
| 1607 | } |
| 1608 | mStayAwake = awake; |
| 1609 | updateSurfaceScreenOn(); |
| 1610 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1611 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1612 | private void updateSurfaceScreenOn() { |
| 1613 | if (mSurfaceHolder != null) { |
| 1614 | mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake); |
| 1615 | } |
| 1616 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1617 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1618 | /** |
| 1619 | * Returns the width of the video. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1620 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1621 | * @return the width of the video, or 0 if there is no video, |
Jean-Baptiste Queru | ea5f767 | 2009-09-16 15:06:25 -0700 | [diff] [blame] | 1622 | * no display surface was set, or the width has not been determined |
| 1623 | * yet. The OnVideoSizeChangedListener can be registered via |
| 1624 | * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)} |
| 1625 | * to provide a notification when the width is available. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1626 | */ |
| 1627 | public native int getVideoWidth(); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1628 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1629 | /** |
| 1630 | * Returns the height of the video. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1631 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1632 | * @return the height of the video, or 0 if there is no video, |
Jean-Baptiste Queru | ea5f767 | 2009-09-16 15:06:25 -0700 | [diff] [blame] | 1633 | * no display surface was set, or the height has not been determined |
| 1634 | * yet. The OnVideoSizeChangedListener can be registered via |
| 1635 | * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)} |
| 1636 | * to provide a notification when the height is available. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1637 | */ |
| 1638 | public native int getVideoHeight(); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1639 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1640 | /** |
Ray Essick | f2d0e40 | 2017-03-09 10:17:51 -0800 | [diff] [blame] | 1641 | * Return Metrics data about the current player. |
Ray Essick | 0e0fee1 | 2017-01-25 18:01:56 -0800 | [diff] [blame] | 1642 | * |
Ray Essick | 10353e3 | 2017-04-14 10:22:55 -0700 | [diff] [blame] | 1643 | * @return a {@link PersistableBundle} containing the set of attributes and values |
Ray Essick | f2d0e40 | 2017-03-09 10:17:51 -0800 | [diff] [blame] | 1644 | * available for the media being handled by this instance of MediaPlayer |
Ray Essick | 10353e3 | 2017-04-14 10:22:55 -0700 | [diff] [blame] | 1645 | * The attributes are descibed in {@link MetricsConstants}. |
Ray Essick | f0f6685 | 2017-03-01 17:23:48 -0800 | [diff] [blame] | 1646 | * |
Ray Essick | f2d0e40 | 2017-03-09 10:17:51 -0800 | [diff] [blame] | 1647 | * Additional vendor-specific fields may also be present in |
Ray Essick | f0f6685 | 2017-03-01 17:23:48 -0800 | [diff] [blame] | 1648 | * the return value. |
Ray Essick | 0e0fee1 | 2017-01-25 18:01:56 -0800 | [diff] [blame] | 1649 | */ |
Ray Essick | 10353e3 | 2017-04-14 10:22:55 -0700 | [diff] [blame] | 1650 | public PersistableBundle getMetrics() { |
| 1651 | PersistableBundle bundle = native_getMetrics(); |
| 1652 | return bundle; |
Ray Essick | f2d0e40 | 2017-03-09 10:17:51 -0800 | [diff] [blame] | 1653 | } |
| 1654 | |
Ray Essick | 10353e3 | 2017-04-14 10:22:55 -0700 | [diff] [blame] | 1655 | private native PersistableBundle native_getMetrics(); |
Ray Essick | 0e0fee1 | 2017-01-25 18:01:56 -0800 | [diff] [blame] | 1656 | |
| 1657 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1658 | * Checks whether the MediaPlayer is playing. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1659 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1660 | * @return true if currently playing, false otherwise |
Scott Main | 099fd80 | 2012-07-03 16:32:11 -0700 | [diff] [blame] | 1661 | * @throws IllegalStateException if the internal player engine has not been |
| 1662 | * initialized or has been released. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1663 | */ |
| 1664 | public native boolean isPlaying(); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1665 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1666 | /** |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1667 | * Change playback speed of audio by resampling the audio. |
| 1668 | * <p> |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1669 | * Specifies resampling as audio mode for variable rate playback, i.e., |
| 1670 | * resample the waveform based on the requested playback rate to get |
| 1671 | * a new waveform, and play back the new waveform at the original sampling |
| 1672 | * frequency. |
| 1673 | * When rate is larger than 1.0, pitch becomes higher. |
| 1674 | * When rate is smaller than 1.0, pitch becomes lower. |
Lajos Molnar | dbc908f | 2015-05-14 12:13:32 -0700 | [diff] [blame] | 1675 | * |
| 1676 | * @hide |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1677 | */ |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1678 | public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1679 | |
| 1680 | /** |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1681 | * Change playback speed of audio without changing its pitch. |
| 1682 | * <p> |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1683 | * Specifies time stretching as audio mode for variable rate playback. |
| 1684 | * Time stretching changes the duration of the audio samples without |
| 1685 | * affecting its pitch. |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1686 | * <p> |
| 1687 | * This mode is only supported for a limited range of playback speed factors, |
| 1688 | * e.g. between 1/2x and 2x. |
Lajos Molnar | dbc908f | 2015-05-14 12:13:32 -0700 | [diff] [blame] | 1689 | * |
| 1690 | * @hide |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1691 | */ |
| 1692 | public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; |
| 1693 | |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1694 | /** |
| 1695 | * Change playback speed of audio without changing its pitch, and |
| 1696 | * possibly mute audio if time stretching is not supported for the playback |
| 1697 | * speed. |
| 1698 | * <p> |
| 1699 | * Try to keep audio pitch when changing the playback rate, but allow the |
| 1700 | * system to determine how to change audio playback if the rate is out |
| 1701 | * of range. |
Lajos Molnar | dbc908f | 2015-05-14 12:13:32 -0700 | [diff] [blame] | 1702 | * |
| 1703 | * @hide |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1704 | */ |
| 1705 | public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; |
| 1706 | |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1707 | /** @hide */ |
| 1708 | @IntDef( |
| 1709 | value = { |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1710 | PLAYBACK_RATE_AUDIO_MODE_DEFAULT, |
| 1711 | PLAYBACK_RATE_AUDIO_MODE_STRETCH, |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1712 | PLAYBACK_RATE_AUDIO_MODE_RESAMPLE, |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1713 | }) |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1714 | @Retention(RetentionPolicy.SOURCE) |
| 1715 | public @interface PlaybackRateAudioMode {} |
| 1716 | |
| 1717 | /** |
| 1718 | * Sets playback rate and audio mode. |
| 1719 | * |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1720 | * @param rate the ratio between desired playback rate and normal one. |
| 1721 | * @param audioMode audio playback mode. Must be one of the supported |
| 1722 | * audio modes. |
| 1723 | * |
| 1724 | * @throws IllegalStateException if the internal player engine has not been |
| 1725 | * initialized. |
| 1726 | * @throws IllegalArgumentException if audioMode is not supported. |
Lajos Molnar | dbc908f | 2015-05-14 12:13:32 -0700 | [diff] [blame] | 1727 | * |
| 1728 | * @hide |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1729 | */ |
Lajos Molnar | dbc908f | 2015-05-14 12:13:32 -0700 | [diff] [blame] | 1730 | @NonNull |
| 1731 | public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) { |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1732 | PlaybackParams params = new PlaybackParams(); |
| 1733 | params.allowDefaults(); |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1734 | switch (audioMode) { |
| 1735 | case PLAYBACK_RATE_AUDIO_MODE_DEFAULT: |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1736 | params.setSpeed(rate).setPitch(1.0f); |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1737 | break; |
| 1738 | case PLAYBACK_RATE_AUDIO_MODE_STRETCH: |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1739 | params.setSpeed(rate).setPitch(1.0f) |
| 1740 | .setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL); |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1741 | break; |
| 1742 | case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE: |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1743 | params.setSpeed(rate).setPitch(rate); |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1744 | break; |
| 1745 | default: |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1746 | final String msg = "Audio playback mode " + audioMode + " is not supported"; |
| 1747 | throw new IllegalArgumentException(msg); |
| 1748 | } |
Lajos Molnar | dbc908f | 2015-05-14 12:13:32 -0700 | [diff] [blame] | 1749 | return params; |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1750 | } |
| 1751 | |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1752 | /** |
Wei Jia | ce4957e | 2016-06-16 15:18:20 -0700 | [diff] [blame] | 1753 | * Sets playback rate using {@link PlaybackParams}. The object sets its internal |
| 1754 | * PlaybackParams to the input, except that the object remembers previous speed |
| 1755 | * when input speed is zero. This allows the object to resume at previous speed |
| 1756 | * when start() is called. Calling it before the object is prepared does not change |
| 1757 | * the object state. After the object is prepared, calling it with zero speed is |
| 1758 | * equivalent to calling pause(). After the object is prepared, calling it with |
| 1759 | * non-zero speed is equivalent to calling start(). |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1760 | * |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1761 | * @param params the playback params. |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1762 | * |
| 1763 | * @throws IllegalStateException if the internal player engine has not been |
Wei Jia | ce4957e | 2016-06-16 15:18:20 -0700 | [diff] [blame] | 1764 | * initialized or has been released. |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1765 | * @throws IllegalArgumentException if params is not supported. |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1766 | */ |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1767 | public native void setPlaybackParams(@NonNull PlaybackParams params); |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1768 | |
| 1769 | /** |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1770 | * Gets the playback params, containing the current playback rate. |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1771 | * |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1772 | * @return the playback params. |
Lajos Molnar | b3d5fd2 | 2015-04-22 13:14:34 -0700 | [diff] [blame] | 1773 | * @throws IllegalStateException if the internal player engine has not been |
| 1774 | * initialized. |
| 1775 | */ |
| 1776 | @NonNull |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1777 | public native PlaybackParams getPlaybackParams(); |
Wei Jia | d93fcf4 | 2015-02-09 16:05:53 -0800 | [diff] [blame] | 1778 | |
| 1779 | /** |
Lajos Molnar | c98f58e | 2015-04-22 19:28:53 -0700 | [diff] [blame] | 1780 | * Sets A/V sync mode. |
| 1781 | * |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1782 | * @param params the A/V sync params to apply |
Lajos Molnar | c98f58e | 2015-04-22 19:28:53 -0700 | [diff] [blame] | 1783 | * |
| 1784 | * @throws IllegalStateException if the internal player engine has not been |
| 1785 | * initialized. |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1786 | * @throws IllegalArgumentException if params are not supported. |
Lajos Molnar | c98f58e | 2015-04-22 19:28:53 -0700 | [diff] [blame] | 1787 | */ |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1788 | public native void setSyncParams(@NonNull SyncParams params); |
Lajos Molnar | c98f58e | 2015-04-22 19:28:53 -0700 | [diff] [blame] | 1789 | |
| 1790 | /** |
| 1791 | * Gets the A/V sync mode. |
| 1792 | * |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1793 | * @return the A/V sync params |
Lajos Molnar | c98f58e | 2015-04-22 19:28:53 -0700 | [diff] [blame] | 1794 | * |
| 1795 | * @throws IllegalStateException if the internal player engine has not been |
| 1796 | * initialized. |
| 1797 | */ |
| 1798 | @NonNull |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1799 | public native SyncParams getSyncParams(); |
Lajos Molnar | c98f58e | 2015-04-22 19:28:53 -0700 | [diff] [blame] | 1800 | |
| 1801 | /** |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1802 | * Seek modes used in method seekTo(long, int) to move media position |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1803 | * to a specified location. |
| 1804 | * |
| 1805 | * Do not change these mode values without updating their counterparts |
| 1806 | * in include/media/IMediaSource.h! |
| 1807 | */ |
| 1808 | /** |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1809 | * This mode is used with {@link #seekTo(long, int)} to move media position to |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1810 | * a sync (or key) frame associated with a data source that is located |
| 1811 | * right before or at the given time. |
| 1812 | * |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1813 | * @see #seekTo(long, int) |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1814 | */ |
| 1815 | public static final int SEEK_PREVIOUS_SYNC = 0x00; |
| 1816 | /** |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1817 | * This mode is used with {@link #seekTo(long, int)} to move media position to |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1818 | * a sync (or key) frame associated with a data source that is located |
| 1819 | * right after or at the given time. |
| 1820 | * |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1821 | * @see #seekTo(long, int) |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1822 | */ |
| 1823 | public static final int SEEK_NEXT_SYNC = 0x01; |
| 1824 | /** |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1825 | * This mode is used with {@link #seekTo(long, int)} to move media position to |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1826 | * a sync (or key) frame associated with a data source that is located |
| 1827 | * closest to (in time) or at the given time. |
| 1828 | * |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1829 | * @see #seekTo(long, int) |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1830 | */ |
| 1831 | public static final int SEEK_CLOSEST_SYNC = 0x02; |
| 1832 | /** |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1833 | * This mode is used with {@link #seekTo(long, int)} to move media position to |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1834 | * a frame (not necessarily a key frame) associated with a data source that |
| 1835 | * is located closest to or at the given time. |
| 1836 | * |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1837 | * @see #seekTo(long, int) |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1838 | */ |
| 1839 | public static final int SEEK_CLOSEST = 0x03; |
| 1840 | |
| 1841 | /** @hide */ |
| 1842 | @IntDef( |
| 1843 | value = { |
| 1844 | SEEK_PREVIOUS_SYNC, |
| 1845 | SEEK_NEXT_SYNC, |
| 1846 | SEEK_CLOSEST_SYNC, |
| 1847 | SEEK_CLOSEST, |
| 1848 | }) |
| 1849 | @Retention(RetentionPolicy.SOURCE) |
| 1850 | public @interface SeekMode {} |
| 1851 | |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1852 | private native final void _seekTo(long msec, int mode); |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1853 | |
| 1854 | /** |
| 1855 | * Moves the media to specified time position by considering the given mode. |
| 1856 | * <p> |
| 1857 | * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user. |
| 1858 | * There is at most one active seekTo processed at any time. If there is a to-be-completed |
| 1859 | * seekTo, new seekTo requests will be queued in such a way that only the last request |
| 1860 | * is kept. When current seekTo is completed, the queued request will be processed if |
| 1861 | * that request is different from just-finished seekTo operation, i.e., the requested |
| 1862 | * position or mode is different. |
| 1863 | * |
| 1864 | * @param msec the offset in milliseconds from the start to seek to. |
| 1865 | * When seeking to the given time position, there is no guarantee that the data source |
| 1866 | * has a frame located at the position. When this happens, a frame nearby will be rendered. |
| 1867 | * If msec is negative, time position zero will be used. |
| 1868 | * If msec is larger than duration, duration will be used. |
| 1869 | * @param mode the mode indicating where exactly to seek to. |
| 1870 | * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame |
| 1871 | * that has a timestamp earlier than or the same as msec. Use |
| 1872 | * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame |
| 1873 | * that has a timestamp later than or the same as msec. Use |
| 1874 | * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame |
| 1875 | * that has a timestamp closest to or the same as msec. Use |
| 1876 | * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may |
| 1877 | * or may not be a sync frame but is closest to or the same as msec. |
| 1878 | * {@link #SEEK_CLOSEST} often has larger performance overhead compared |
| 1879 | * to the other options if there is no sync frame located at msec. |
| 1880 | * @throws IllegalStateException if the internal player engine has not been |
| 1881 | * initialized |
| 1882 | * @throws IllegalArgumentException if the mode is invalid. |
| 1883 | */ |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1884 | public void seekTo(long msec, @SeekMode int mode) { |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1885 | if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) { |
| 1886 | final String msg = "Illegal seek mode: " + mode; |
| 1887 | throw new IllegalArgumentException(msg); |
| 1888 | } |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1889 | // TODO: pass long to native, instead of truncating here. |
| 1890 | if (msec > Integer.MAX_VALUE) { |
| 1891 | Log.w(TAG, "seekTo offset " + msec + " is too large, cap to " + Integer.MAX_VALUE); |
| 1892 | msec = Integer.MAX_VALUE; |
| 1893 | } else if (msec < Integer.MIN_VALUE) { |
| 1894 | Log.w(TAG, "seekTo offset " + msec + " is too small, cap to " + Integer.MIN_VALUE); |
| 1895 | msec = Integer.MIN_VALUE; |
| 1896 | } |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1897 | _seekTo(msec, mode); |
| 1898 | } |
| 1899 | |
| 1900 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1901 | * Seeks to specified time position. |
Wei Jia | bebeaf9 | 2017-04-19 16:22:10 -0700 | [diff] [blame] | 1902 | * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1903 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1904 | * @param msec the offset in milliseconds from the start to seek to |
| 1905 | * @throws IllegalStateException if the internal player engine has not been |
| 1906 | * initialized |
| 1907 | */ |
Wei Jia | ebc2b69 | 2016-11-03 17:44:01 -0700 | [diff] [blame] | 1908 | public void seekTo(int msec) throws IllegalStateException { |
| 1909 | seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */); |
| 1910 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1911 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1912 | /** |
Lajos Molnar | 05a8221 | 2015-05-13 15:30:58 -0700 | [diff] [blame] | 1913 | * Get current playback position as a {@link MediaTimestamp}. |
Lajos Molnar | 7f08763 | 2015-04-22 20:21:54 -0700 | [diff] [blame] | 1914 | * <p> |
| 1915 | * The MediaTimestamp represents how the media time correlates to the system time in |
Lajos Molnar | 05a8221 | 2015-05-13 15:30:58 -0700 | [diff] [blame] | 1916 | * a linear fashion using an anchor and a clock rate. During regular playback, the media |
| 1917 | * time moves fairly constantly (though the anchor frame may be rebased to a current |
| 1918 | * system time, the linear correlation stays steady). Therefore, this method does not |
| 1919 | * need to be called often. |
Lajos Molnar | 7f08763 | 2015-04-22 20:21:54 -0700 | [diff] [blame] | 1920 | * <p> |
Lajos Molnar | 05a8221 | 2015-05-13 15:30:58 -0700 | [diff] [blame] | 1921 | * To help users get current playback position, this method always anchors the timestamp |
| 1922 | * to the current {@link System#nanoTime system time}, so |
| 1923 | * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position. |
Lajos Molnar | 7f08763 | 2015-04-22 20:21:54 -0700 | [diff] [blame] | 1924 | * |
| 1925 | * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp |
| 1926 | * is available, e.g. because the media player has not been initialized. |
Lajos Molnar | 05a8221 | 2015-05-13 15:30:58 -0700 | [diff] [blame] | 1927 | * |
| 1928 | * @see MediaTimestamp |
Lajos Molnar | 7f08763 | 2015-04-22 20:21:54 -0700 | [diff] [blame] | 1929 | */ |
| 1930 | @Nullable |
| 1931 | public MediaTimestamp getTimestamp() |
| 1932 | { |
| 1933 | try { |
| 1934 | // TODO: get the timestamp from native side |
| 1935 | return new MediaTimestamp( |
| 1936 | getCurrentPosition() * 1000L, |
| 1937 | System.nanoTime(), |
Wei Jia | 2d61e2b | 2015-05-08 15:23:28 -0700 | [diff] [blame] | 1938 | isPlaying() ? getPlaybackParams().getSpeed() : 0.f); |
Lajos Molnar | 7f08763 | 2015-04-22 20:21:54 -0700 | [diff] [blame] | 1939 | } catch (IllegalStateException e) { |
| 1940 | return null; |
| 1941 | } |
| 1942 | } |
| 1943 | |
| 1944 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1945 | * Gets the current playback position. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1946 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1947 | * @return the current position in milliseconds |
| 1948 | */ |
| 1949 | public native int getCurrentPosition(); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1950 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1951 | /** |
| 1952 | * Gets the duration of the file. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1953 | * |
Andreas Huber | 7cd281c | 2013-04-11 11:07:03 -0700 | [diff] [blame] | 1954 | * @return the duration in milliseconds, if no duration is available |
| 1955 | * (for example, if streaming live content), -1 is returned. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1956 | */ |
| 1957 | public native int getDuration(); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 1958 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1959 | /** |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 1960 | * Gets the media metadata. |
| 1961 | * |
| 1962 | * @param update_only controls whether the full set of available |
| 1963 | * metadata is returned or just the set that changed since the |
| 1964 | * last call. See {@see #METADATA_UPDATE_ONLY} and {@see |
| 1965 | * #METADATA_ALL}. |
| 1966 | * |
| 1967 | * @param apply_filter if true only metadata that matches the |
| 1968 | * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see |
| 1969 | * #BYPASS_METADATA_FILTER}. |
| 1970 | * |
| 1971 | * @return The metadata, possibly empty. null if an error occured. |
| 1972 | // FIXME: unhide. |
| 1973 | * {@hide} |
| 1974 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 1975 | @UnsupportedAppUsage |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 1976 | public Metadata getMetadata(final boolean update_only, |
| 1977 | final boolean apply_filter) { |
Nicolas Catania | 5d55c71 | 2009-07-09 09:21:33 -0700 | [diff] [blame] | 1978 | Parcel reply = Parcel.obtain(); |
| 1979 | Metadata data = new Metadata(); |
| 1980 | |
| 1981 | if (!native_getMetadata(update_only, apply_filter, reply)) { |
| 1982 | reply.recycle(); |
| 1983 | return null; |
| 1984 | } |
| 1985 | |
| 1986 | // Metadata takes over the parcel, don't recycle it unless |
| 1987 | // there is an error. |
| 1988 | if (!data.parse(reply)) { |
| 1989 | reply.recycle(); |
| 1990 | return null; |
| 1991 | } |
| 1992 | return data; |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 1993 | } |
| 1994 | |
| 1995 | /** |
| 1996 | * Set a filter for the metadata update notification and update |
| 1997 | * retrieval. The caller provides 2 set of metadata keys, allowed |
Nicolas Catania | b2c6939 | 2009-07-08 08:57:42 -0700 | [diff] [blame] | 1998 | * and blocked. The blocked set always takes precedence over the |
| 1999 | * allowed one. |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 2000 | * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as |
Nicolas Catania | b2c6939 | 2009-07-08 08:57:42 -0700 | [diff] [blame] | 2001 | * shorthands to allow/block all or no metadata. |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 2002 | * |
| 2003 | * By default, there is no filter set. |
| 2004 | * |
| 2005 | * @param allow Is the set of metadata the client is interested |
Nicolas Catania | b2c6939 | 2009-07-08 08:57:42 -0700 | [diff] [blame] | 2006 | * in receiving new notifications for. |
| 2007 | * @param block Is the set of metadata the client is not interested |
| 2008 | * in receiving new notifications for. |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 2009 | * @return The call status code. |
| 2010 | * |
| 2011 | // FIXME: unhide. |
| 2012 | * {@hide} |
| 2013 | */ |
Nicolas Catania | b2c6939 | 2009-07-08 08:57:42 -0700 | [diff] [blame] | 2014 | public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) { |
| 2015 | // Do our serialization manually instead of calling |
| 2016 | // Parcel.writeArray since the sets are made of the same type |
| 2017 | // we avoid paying the price of calling writeValue (used by |
| 2018 | // writeArray) which burns an extra int per element to encode |
| 2019 | // the type. |
| 2020 | Parcel request = newRequest(); |
| 2021 | |
| 2022 | // The parcel starts already with an interface token. There |
| 2023 | // are 2 filters. Each one starts with a 4bytes number to |
| 2024 | // store the len followed by a number of int (4 bytes as well) |
| 2025 | // representing the metadata type. |
| 2026 | int capacity = request.dataSize() + 4 * (1 + allow.size() + 1 + block.size()); |
| 2027 | |
| 2028 | if (request.dataCapacity() < capacity) { |
| 2029 | request.setDataCapacity(capacity); |
| 2030 | } |
| 2031 | |
| 2032 | request.writeInt(allow.size()); |
| 2033 | for(Integer t: allow) { |
| 2034 | request.writeInt(t); |
| 2035 | } |
| 2036 | request.writeInt(block.size()); |
| 2037 | for(Integer t: block) { |
| 2038 | request.writeInt(t); |
| 2039 | } |
| 2040 | return native_setMetadataFilter(request); |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 2041 | } |
| 2042 | |
| 2043 | /** |
Marco Nelissen | 84b8320 | 2012-02-28 16:07:44 -0800 | [diff] [blame] | 2044 | * Set the MediaPlayer to start when this MediaPlayer finishes playback |
| 2045 | * (i.e. reaches the end of the stream). |
| 2046 | * The media framework will attempt to transition from this player to |
| 2047 | * the next as seamlessly as possible. The next player can be set at |
Wei Jia | 295e727 | 2016-09-15 14:54:35 -0700 | [diff] [blame] | 2048 | * any time before completion, but shall be after setDataSource has been |
| 2049 | * called successfully. The next player must be prepared by the |
Marco Nelissen | 84b8320 | 2012-02-28 16:07:44 -0800 | [diff] [blame] | 2050 | * app, and the application should not call start() on it. |
| 2051 | * The next MediaPlayer must be different from 'this'. An exception |
| 2052 | * will be thrown if next == this. |
| 2053 | * The application may call setNextMediaPlayer(null) to indicate no |
| 2054 | * next player should be started at the end of playback. |
| 2055 | * If the current player is looping, it will keep looping and the next |
| 2056 | * player will not be started. |
| 2057 | * |
| 2058 | * @param next the player to start after this one completes playback. |
| 2059 | * |
Marco Nelissen | 84b8320 | 2012-02-28 16:07:44 -0800 | [diff] [blame] | 2060 | */ |
| 2061 | public native void setNextMediaPlayer(MediaPlayer next); |
| 2062 | |
| 2063 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2064 | * Releases resources associated with this MediaPlayer object. |
| 2065 | * It is considered good practice to call this method when you're |
James Dong | 89ca698 | 2011-11-14 19:01:26 -0800 | [diff] [blame] | 2066 | * done using the MediaPlayer. In particular, whenever an Activity |
| 2067 | * of an application is paused (its onPause() method is called), |
| 2068 | * or stopped (its onStop() method is called), this method should be |
| 2069 | * invoked to release the MediaPlayer object, unless the application |
| 2070 | * has a special need to keep the object around. In addition to |
| 2071 | * unnecessary resources (such as memory and instances of codecs) |
| 2072 | * being held, failure to call this method immediately if a |
| 2073 | * MediaPlayer object is no longer needed may also lead to |
| 2074 | * continuous battery consumption for mobile devices, and playback |
| 2075 | * failure for other applications if no multiple instances of the |
| 2076 | * same codec are supported on a device. Even if multiple instances |
| 2077 | * of the same codec are supported, some performance degradation |
| 2078 | * may be expected when unnecessary multiple instances are used |
| 2079 | * at the same time. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2080 | */ |
| 2081 | public void release() { |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 2082 | baseRelease(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2083 | stayAwake(false); |
| 2084 | updateSurfaceScreenOn(); |
| 2085 | mOnPreparedListener = null; |
| 2086 | mOnBufferingUpdateListener = null; |
| 2087 | mOnCompletionListener = null; |
| 2088 | mOnSeekCompleteListener = null; |
| 2089 | mOnErrorListener = null; |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 2090 | mOnInfoListener = null; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2091 | mOnVideoSizeChangedListener = null; |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 2092 | mOnTimedTextListener = null; |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 2093 | synchronized (mTimeProviderLock) { |
| 2094 | if (mTimeProvider != null) { |
| 2095 | mTimeProvider.close(); |
| 2096 | mTimeProvider = null; |
| 2097 | } |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 2098 | } |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 2099 | synchronized(this) { |
| 2100 | mSubtitleDataListenerDisabled = false; |
| 2101 | mExtSubtitleDataListener = null; |
| 2102 | mExtSubtitleDataHandler = null; |
| 2103 | mOnMediaTimeDiscontinuityListener = null; |
| 2104 | mOnMediaTimeDiscontinuityHandler = null; |
| 2105 | } |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 2106 | |
| 2107 | // Modular DRM clean up |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 2108 | mOnDrmConfigHelper = null; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 2109 | mOnDrmInfoHandlerDelegate = null; |
| 2110 | mOnDrmPreparedHandlerDelegate = null; |
| 2111 | resetDrmState(); |
| 2112 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2113 | _release(); |
| 2114 | } |
| 2115 | |
| 2116 | private native void _release(); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 2117 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2118 | /** |
| 2119 | * Resets the MediaPlayer to its uninitialized state. After calling |
| 2120 | * this method, you will have to initialize it again by setting the |
| 2121 | * data source and calling prepare(). |
| 2122 | */ |
| 2123 | public void reset() { |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2124 | mSelectedSubtitleTrackIndex = -1; |
| 2125 | synchronized(mOpenSubtitleSources) { |
| 2126 | for (final InputStream is: mOpenSubtitleSources) { |
| 2127 | try { |
| 2128 | is.close(); |
| 2129 | } catch (IOException e) { |
| 2130 | } |
| 2131 | } |
| 2132 | mOpenSubtitleSources.clear(); |
| 2133 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2134 | if (mSubtitleController != null) { |
| 2135 | mSubtitleController.reset(); |
| 2136 | } |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 2137 | synchronized (mTimeProviderLock) { |
| 2138 | if (mTimeProvider != null) { |
| 2139 | mTimeProvider.close(); |
| 2140 | mTimeProvider = null; |
| 2141 | } |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 2142 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2143 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2144 | stayAwake(false); |
| 2145 | _reset(); |
| 2146 | // make sure none of the listeners get called anymore |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 2147 | if (mEventHandler != null) { |
| 2148 | mEventHandler.removeCallbacksAndMessages(null); |
| 2149 | } |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2150 | |
| 2151 | synchronized (mIndexTrackPairs) { |
| 2152 | mIndexTrackPairs.clear(); |
| 2153 | mInbandTrackIndices.clear(); |
| 2154 | }; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 2155 | |
| 2156 | resetDrmState(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2157 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 2158 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2159 | private native void _reset(); |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 2160 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2161 | /** |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 2162 | * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be |
| 2163 | * notified when the presentation time reaches (becomes greater than or equal to) |
| 2164 | * the value specified. |
| 2165 | * |
| 2166 | * @param mediaTimeUs presentation time to get timed event callback at |
| 2167 | * @hide |
| 2168 | */ |
| 2169 | public void notifyAt(long mediaTimeUs) { |
| 2170 | _notifyAt(mediaTimeUs); |
| 2171 | } |
| 2172 | |
| 2173 | private native void _notifyAt(long mediaTimeUs); |
| 2174 | |
| 2175 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2176 | * Sets the audio stream type for this MediaPlayer. See {@link AudioManager} |
James Dong | 9d96354 | 2010-06-03 10:36:04 -0700 | [diff] [blame] | 2177 | * for a list of stream types. Must call this method before prepare() or |
| 2178 | * prepareAsync() in order for the target stream type to become effective |
| 2179 | * thereafter. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 2180 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2181 | * @param streamtype the audio stream type |
Jean-Michel Trivi | 2f7511f | 2016-11-28 15:40:27 -0800 | [diff] [blame] | 2182 | * @deprecated use {@link #setAudioAttributes(AudioAttributes)} |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2183 | * @see android.media.AudioManager |
| 2184 | */ |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 2185 | public void setAudioStreamType(int streamtype) { |
Jean-Michel Trivi | 2f7511f | 2016-11-28 15:40:27 -0800 | [diff] [blame] | 2186 | deprecateStreamTypeForPlayback(streamtype, "MediaPlayer", "setAudioStreamType()"); |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 2187 | baseUpdateAudioAttributes( |
| 2188 | new AudioAttributes.Builder().setInternalLegacyStreamType(streamtype).build()); |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 2189 | _setAudioStreamType(streamtype); |
| 2190 | mStreamType = streamtype; |
| 2191 | } |
| 2192 | |
| 2193 | private native void _setAudioStreamType(int streamtype); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2194 | |
Jean-Michel Trivi | 8df982d | 2014-06-26 12:05:16 -0700 | [diff] [blame] | 2195 | // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer.h |
| 2196 | private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400; |
| 2197 | /** |
| 2198 | * Sets the parameter indicated by key. |
| 2199 | * @param key key indicates the parameter to be set. |
| 2200 | * @param value value of the parameter to be set. |
| 2201 | * @return true if the parameter is set successfully, false otherwise |
| 2202 | * {@hide} |
| 2203 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 2204 | @UnsupportedAppUsage |
Jean-Michel Trivi | 8df982d | 2014-06-26 12:05:16 -0700 | [diff] [blame] | 2205 | private native boolean setParameter(int key, Parcel value); |
| 2206 | |
| 2207 | /** |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 2208 | * Sets the audio attributes for this MediaPlayer. |
| 2209 | * See {@link AudioAttributes} for how to build and configure an instance of this class. |
| 2210 | * You must call this method before {@link #prepare()} or {@link #prepareAsync()} in order |
| 2211 | * for the audio attributes to become effective thereafter. |
Jean-Michel Trivi | 8df982d | 2014-06-26 12:05:16 -0700 | [diff] [blame] | 2212 | * @param attributes a non-null set of audio attributes |
| 2213 | */ |
| 2214 | public void setAudioAttributes(AudioAttributes attributes) throws IllegalArgumentException { |
| 2215 | if (attributes == null) { |
Jean-Michel Trivi | 79f5cd1 | 2014-07-15 15:42:25 -0700 | [diff] [blame] | 2216 | final String msg = "Cannot set AudioAttributes to null"; |
Jean-Michel Trivi | 8df982d | 2014-06-26 12:05:16 -0700 | [diff] [blame] | 2217 | throw new IllegalArgumentException(msg); |
| 2218 | } |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 2219 | baseUpdateAudioAttributes(attributes); |
John Spurlock | 7b41467 | 2014-07-18 13:02:39 -0400 | [diff] [blame] | 2220 | mUsage = attributes.getUsage(); |
Jean-Michel Trivi | d396954 | 2015-05-20 13:58:18 -0700 | [diff] [blame] | 2221 | mBypassInterruptionPolicy = (attributes.getAllFlags() |
John Spurlock | bbfd31a | 2015-02-18 11:58:14 -0500 | [diff] [blame] | 2222 | & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0; |
Jean-Michel Trivi | 8df982d | 2014-06-26 12:05:16 -0700 | [diff] [blame] | 2223 | Parcel pattributes = Parcel.obtain(); |
| 2224 | attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS); |
| 2225 | setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes); |
| 2226 | pattributes.recycle(); |
| 2227 | } |
| 2228 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2229 | /** |
| 2230 | * Sets the player to be looping or non-looping. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 2231 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2232 | * @param looping whether to loop or not |
| 2233 | */ |
| 2234 | public native void setLooping(boolean looping); |
| 2235 | |
| 2236 | /** |
| 2237 | * Checks whether the MediaPlayer is looping or non-looping. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 2238 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2239 | * @return true if the MediaPlayer is currently looping, false otherwise |
| 2240 | */ |
| 2241 | public native boolean isLooping(); |
| 2242 | |
| 2243 | /** |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 2244 | * Sets the volume on this player. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2245 | * This API is recommended for balancing the output of audio streams |
| 2246 | * within an application. Unless you are writing an application to |
| 2247 | * control user settings, this API should be used in preference to |
| 2248 | * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of |
Glenn Kasten | 068225d | 2012-02-27 16:21:04 -0800 | [diff] [blame] | 2249 | * a particular type. Note that the passed volume values are raw scalars in range 0.0 to 1.0. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2250 | * UI controls should be scaled logarithmically. |
| 2251 | * |
| 2252 | * @param leftVolume left volume scalar |
| 2253 | * @param rightVolume right volume scalar |
| 2254 | */ |
Glenn Kasten | 068225d | 2012-02-27 16:21:04 -0800 | [diff] [blame] | 2255 | /* |
| 2256 | * FIXME: Merge this into javadoc comment above when setVolume(float) is not @hide. |
| 2257 | * The single parameter form below is preferred if the channel volumes don't need |
| 2258 | * to be set independently. |
| 2259 | */ |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 2260 | public void setVolume(float leftVolume, float rightVolume) { |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 2261 | baseSetVolume(leftVolume, rightVolume); |
| 2262 | } |
| 2263 | |
| 2264 | @Override |
Jean-Michel Trivi | 8e48c69 | 2016-10-19 11:52:08 -0700 | [diff] [blame] | 2265 | void playerSetVolume(boolean muting, float leftVolume, float rightVolume) { |
| 2266 | _setVolume(muting ? 0.0f : leftVolume, muting ? 0.0f : rightVolume); |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 2267 | } |
| 2268 | |
| 2269 | private native void _setVolume(float leftVolume, float rightVolume); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2270 | |
| 2271 | /** |
Glenn Kasten | 068225d | 2012-02-27 16:21:04 -0800 | [diff] [blame] | 2272 | * Similar, excepts sets volume of all channels to same value. |
| 2273 | * @hide |
| 2274 | */ |
| 2275 | public void setVolume(float volume) { |
| 2276 | setVolume(volume, volume); |
| 2277 | } |
| 2278 | |
| 2279 | /** |
Eric Laurent | 619346f | 2010-06-21 09:27:30 -0700 | [diff] [blame] | 2280 | * Sets the audio session ID. |
| 2281 | * |
Eric Laurent | 17cb280 | 2010-08-03 03:50:02 -0700 | [diff] [blame] | 2282 | * @param sessionId the audio session ID. |
Eric Laurent | 619346f | 2010-06-21 09:27:30 -0700 | [diff] [blame] | 2283 | * The audio session ID is a system wide unique identifier for the audio stream played by |
| 2284 | * this MediaPlayer instance. |
| 2285 | * The primary use of the audio session ID is to associate audio effects to a particular |
| 2286 | * instance of MediaPlayer: if an audio session ID is provided when creating an audio effect, |
| 2287 | * this effect will be applied only to the audio content of media players within the same |
| 2288 | * audio session and not to the output mix. |
| 2289 | * When created, a MediaPlayer instance automatically generates its own audio session ID. |
| 2290 | * However, it is possible to force this player to be part of an already existing audio session |
| 2291 | * by calling this method. |
| 2292 | * This method must be called before one of the overloaded <code> setDataSource </code> methods. |
| 2293 | * @throws IllegalStateException if it is called in an invalid state |
Eric Laurent | 619346f | 2010-06-21 09:27:30 -0700 | [diff] [blame] | 2294 | */ |
| 2295 | public native void setAudioSessionId(int sessionId) throws IllegalArgumentException, IllegalStateException; |
| 2296 | |
| 2297 | /** |
| 2298 | * Returns the audio session ID. |
| 2299 | * |
Eric Laurent | 17cb280 | 2010-08-03 03:50:02 -0700 | [diff] [blame] | 2300 | * @return the audio session ID. {@see #setAudioSessionId(int)} |
Eric Laurent | 619346f | 2010-06-21 09:27:30 -0700 | [diff] [blame] | 2301 | * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer was contructed. |
Eric Laurent | 619346f | 2010-06-21 09:27:30 -0700 | [diff] [blame] | 2302 | */ |
| 2303 | public native int getAudioSessionId(); |
| 2304 | |
| 2305 | /** |
Eric Laurent | 7070b36 | 2010-07-16 07:43:46 -0700 | [diff] [blame] | 2306 | * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation |
| 2307 | * effect which can be applied on any sound source that directs a certain amount of its |
| 2308 | * energy to this effect. This amount is defined by setAuxEffectSendLevel(). |
Ryan Lothian | 1e88cf0 | 2014-11-26 18:04:45 +0000 | [diff] [blame] | 2309 | * See {@link #setAuxEffectSendLevel(float)}. |
Eric Laurent | 1a5149e | 2010-09-21 18:18:20 -0700 | [diff] [blame] | 2310 | * <p>After creating an auxiliary effect (e.g. |
| 2311 | * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with |
| 2312 | * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method |
| 2313 | * to attach the player to the effect. |
Eric Laurent | 7070b36 | 2010-07-16 07:43:46 -0700 | [diff] [blame] | 2314 | * <p>To detach the effect from the player, call this method with a null effect id. |
| 2315 | * <p>This method must be called after one of the overloaded <code> setDataSource </code> |
| 2316 | * methods. |
Eric Laurent | 7070b36 | 2010-07-16 07:43:46 -0700 | [diff] [blame] | 2317 | * @param effectId system wide unique id of the effect to attach |
Eric Laurent | 7070b36 | 2010-07-16 07:43:46 -0700 | [diff] [blame] | 2318 | */ |
| 2319 | public native void attachAuxEffect(int effectId); |
| 2320 | |
Gloria Wang | d01ec6e | 2011-04-25 17:28:22 -0700 | [diff] [blame] | 2321 | |
| 2322 | /** |
Ryan Lothian | 1e88cf0 | 2014-11-26 18:04:45 +0000 | [diff] [blame] | 2323 | * Sets the send level of the player to the attached auxiliary effect. |
| 2324 | * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. |
Eric Laurent | 7070b36 | 2010-07-16 07:43:46 -0700 | [diff] [blame] | 2325 | * <p>By default the send level is 0, so even if an effect is attached to the player |
| 2326 | * this method must be called for the effect to be applied. |
| 2327 | * <p>Note that the passed level value is a raw scalar. UI controls should be scaled |
| 2328 | * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, |
| 2329 | * so an appropriate conversion from linear UI input x to level is: |
| 2330 | * x == 0 -> level = 0 |
| 2331 | * 0 < x <= R -> level = 10^(72*(x-R)/20/R) |
| 2332 | * @param level send level scalar |
Eric Laurent | 7070b36 | 2010-07-16 07:43:46 -0700 | [diff] [blame] | 2333 | */ |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 2334 | public void setAuxEffectSendLevel(float level) { |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 2335 | baseSetAuxEffectSendLevel(level); |
| 2336 | } |
| 2337 | |
| 2338 | @Override |
Jean-Michel Trivi | 8e48c69 | 2016-10-19 11:52:08 -0700 | [diff] [blame] | 2339 | int playerSetAuxEffectSendLevel(boolean muting, float level) { |
| 2340 | _setAuxEffectSendLevel(muting ? 0.0f : level); |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 2341 | return AudioSystem.SUCCESS; |
John Spurlock | 1af30c7 | 2014-03-10 08:33:35 -0400 | [diff] [blame] | 2342 | } |
| 2343 | |
| 2344 | private native void _setAuxEffectSendLevel(float level); |
Eric Laurent | 7070b36 | 2010-07-16 07:43:46 -0700 | [diff] [blame] | 2345 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2346 | /* |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 2347 | * @param request Parcel destinated to the media player. The |
| 2348 | * Interface token must be set to the IMediaPlayer |
| 2349 | * one to be routed correctly through the system. |
Nicolas Catania | 5d55c71 | 2009-07-09 09:21:33 -0700 | [diff] [blame] | 2350 | * @param reply[out] Parcel that will contain the reply. |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 2351 | * @return The status code. |
| 2352 | */ |
| 2353 | private native final int native_invoke(Parcel request, Parcel reply); |
| 2354 | |
Nicolas Catania | 5d55c71 | 2009-07-09 09:21:33 -0700 | [diff] [blame] | 2355 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2356 | /* |
Nicolas Catania | 5d55c71 | 2009-07-09 09:21:33 -0700 | [diff] [blame] | 2357 | * @param update_only If true fetch only the set of metadata that have |
| 2358 | * changed since the last invocation of getMetadata. |
| 2359 | * The set is built using the unfiltered |
| 2360 | * notifications the native player sent to the |
| 2361 | * MediaPlayerService during that period of |
| 2362 | * time. If false, all the metadatas are considered. |
| 2363 | * @param apply_filter If true, once the metadata set has been built based on |
| 2364 | * the value update_only, the current filter is applied. |
| 2365 | * @param reply[out] On return contains the serialized |
| 2366 | * metadata. Valid only if the call was successful. |
| 2367 | * @return The status code. |
| 2368 | */ |
| 2369 | private native final boolean native_getMetadata(boolean update_only, |
| 2370 | boolean apply_filter, |
| 2371 | Parcel reply); |
| 2372 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2373 | /* |
Nicolas Catania | b2c6939 | 2009-07-08 08:57:42 -0700 | [diff] [blame] | 2374 | * @param request Parcel with the 2 serialized lists of allowed |
| 2375 | * metadata types followed by the one to be |
| 2376 | * dropped. Each list starts with an integer |
| 2377 | * indicating the number of metadata type elements. |
| 2378 | * @return The status code. |
| 2379 | */ |
| 2380 | private native final int native_setMetadataFilter(Parcel request); |
| 2381 | |
Marco Nelissen | 4935d05 | 2009-08-03 11:12:58 -0700 | [diff] [blame] | 2382 | private static native final void native_init(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 2383 | private native final void native_setup(Object mediaplayer_this); |
| 2384 | private native final void native_finalize(); |
Nicolas Catania | 20cb94e | 2009-05-12 23:25:55 -0700 | [diff] [blame] | 2385 | |
Gloria Wang | d211f41 | 2011-02-19 18:37:57 -0800 | [diff] [blame] | 2386 | /** |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2387 | * Class for MediaPlayer to return each audio/video/subtitle track's metadata. |
| 2388 | * |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2389 | * @see android.media.MediaPlayer#getTrackInfo |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 2390 | */ |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2391 | static public class TrackInfo implements Parcelable { |
| 2392 | /** |
| 2393 | * Gets the track type. |
| 2394 | * @return TrackType which indicates if the track is video, audio, timed text. |
| 2395 | */ |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2396 | public @TrackType int getTrackType() { |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2397 | return mTrackType; |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 2398 | } |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2399 | |
| 2400 | /** |
| 2401 | * Gets the language code of the track. |
| 2402 | * @return a language code in either way of ISO-639-1 or ISO-639-2. |
| 2403 | * When the language is unknown or could not be determined, |
| 2404 | * ISO-639-2 language code, "und", is returned. |
| 2405 | */ |
| 2406 | public String getLanguage() { |
Lajos Molnar | 1c56a67 | 2013-08-15 12:05:52 -0700 | [diff] [blame] | 2407 | String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); |
| 2408 | return language == null ? "und" : language; |
| 2409 | } |
| 2410 | |
| 2411 | /** |
| 2412 | * Gets the {@link MediaFormat} of the track. If the format is |
| 2413 | * unknown or could not be determined, null is returned. |
| 2414 | */ |
| 2415 | public MediaFormat getFormat() { |
Chong Zhang | f8ca13b | 2013-09-11 13:41:00 -0700 | [diff] [blame] | 2416 | if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT |
| 2417 | || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { |
Lajos Molnar | 1c56a67 | 2013-08-15 12:05:52 -0700 | [diff] [blame] | 2418 | return mFormat; |
| 2419 | } |
| 2420 | return null; |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2421 | } |
| 2422 | |
| 2423 | public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; |
| 2424 | public static final int MEDIA_TRACK_TYPE_VIDEO = 1; |
| 2425 | public static final int MEDIA_TRACK_TYPE_AUDIO = 2; |
| 2426 | public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 2427 | public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 2428 | public static final int MEDIA_TRACK_TYPE_METADATA = 5; |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2429 | |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2430 | /** @hide */ |
| 2431 | @IntDef(flag = false, prefix = "MEDIA_TRACK_TYPE", value = { |
| 2432 | MEDIA_TRACK_TYPE_UNKNOWN, |
| 2433 | MEDIA_TRACK_TYPE_VIDEO, |
| 2434 | MEDIA_TRACK_TYPE_AUDIO, |
| 2435 | MEDIA_TRACK_TYPE_TIMEDTEXT, |
| 2436 | MEDIA_TRACK_TYPE_SUBTITLE, |
| 2437 | MEDIA_TRACK_TYPE_METADATA } |
| 2438 | ) |
| 2439 | @Retention(RetentionPolicy.SOURCE) |
| 2440 | public @interface TrackType {} |
| 2441 | |
| 2442 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2443 | final int mTrackType; |
Lajos Molnar | 1c56a67 | 2013-08-15 12:05:52 -0700 | [diff] [blame] | 2444 | final MediaFormat mFormat; |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2445 | |
| 2446 | TrackInfo(Parcel in) { |
| 2447 | mTrackType = in.readInt(); |
Robert Shih | 29767a8 | 2015-04-30 14:36:48 -0700 | [diff] [blame] | 2448 | // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat |
| 2449 | // even for audio/video tracks, meaning we only set the mime and language. |
| 2450 | String mime = in.readString(); |
Lajos Molnar | 1c56a67 | 2013-08-15 12:05:52 -0700 | [diff] [blame] | 2451 | String language = in.readString(); |
Robert Shih | 29767a8 | 2015-04-30 14:36:48 -0700 | [diff] [blame] | 2452 | mFormat = MediaFormat.createSubtitleFormat(mime, language); |
Lajos Molnar | 1c56a67 | 2013-08-15 12:05:52 -0700 | [diff] [blame] | 2453 | |
Robert Shih | 29767a8 | 2015-04-30 14:36:48 -0700 | [diff] [blame] | 2454 | if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 2455 | mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); |
| 2456 | mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); |
| 2457 | mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); |
Lajos Molnar | 1c56a67 | 2013-08-15 12:05:52 -0700 | [diff] [blame] | 2458 | } |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2459 | } |
| 2460 | |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2461 | /** @hide */ |
| 2462 | TrackInfo(int type, MediaFormat format) { |
| 2463 | mTrackType = type; |
| 2464 | mFormat = format; |
| 2465 | } |
| 2466 | |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2467 | /** |
| 2468 | * {@inheritDoc} |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2469 | */ |
| 2470 | @Override |
| 2471 | public int describeContents() { |
| 2472 | return 0; |
| 2473 | } |
| 2474 | |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2475 | /** |
| 2476 | * {@inheritDoc} |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2477 | */ |
| 2478 | @Override |
| 2479 | public void writeToParcel(Parcel dest, int flags) { |
| 2480 | dest.writeInt(mTrackType); |
Robert Shih | 0d2dc94 | 2018-07-09 13:38:31 -0700 | [diff] [blame] | 2481 | dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); |
Lajos Molnar | 1c56a67 | 2013-08-15 12:05:52 -0700 | [diff] [blame] | 2482 | dest.writeString(getLanguage()); |
Chong Zhang | f8ca13b | 2013-09-11 13:41:00 -0700 | [diff] [blame] | 2483 | |
| 2484 | if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 2485 | dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); |
| 2486 | dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); |
| 2487 | dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); |
Chong Zhang | f8ca13b | 2013-09-11 13:41:00 -0700 | [diff] [blame] | 2488 | } |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2489 | } |
| 2490 | |
Chong Zhang | 079fa96 | 2014-06-11 15:09:13 -0700 | [diff] [blame] | 2491 | @Override |
| 2492 | public String toString() { |
| 2493 | StringBuilder out = new StringBuilder(128); |
| 2494 | out.append(getClass().getName()); |
| 2495 | out.append('{'); |
| 2496 | switch (mTrackType) { |
| 2497 | case MEDIA_TRACK_TYPE_VIDEO: |
| 2498 | out.append("VIDEO"); |
| 2499 | break; |
| 2500 | case MEDIA_TRACK_TYPE_AUDIO: |
| 2501 | out.append("AUDIO"); |
| 2502 | break; |
| 2503 | case MEDIA_TRACK_TYPE_TIMEDTEXT: |
| 2504 | out.append("TIMEDTEXT"); |
| 2505 | break; |
| 2506 | case MEDIA_TRACK_TYPE_SUBTITLE: |
| 2507 | out.append("SUBTITLE"); |
| 2508 | break; |
| 2509 | default: |
| 2510 | out.append("UNKNOWN"); |
| 2511 | break; |
| 2512 | } |
| 2513 | out.append(", " + mFormat.toString()); |
| 2514 | out.append("}"); |
| 2515 | return out.toString(); |
| 2516 | } |
| 2517 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2518 | /** |
| 2519 | * Used to read a TrackInfo from a Parcel. |
| 2520 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 2521 | @UnsupportedAppUsage |
Jeff Sharkey | 9e8f83d | 2019-02-28 12:06:45 -0700 | [diff] [blame^] | 2522 | static final @android.annotation.NonNull Parcelable.Creator<TrackInfo> CREATOR |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2523 | = new Parcelable.Creator<TrackInfo>() { |
| 2524 | @Override |
| 2525 | public TrackInfo createFromParcel(Parcel in) { |
| 2526 | return new TrackInfo(in); |
| 2527 | } |
| 2528 | |
| 2529 | @Override |
| 2530 | public TrackInfo[] newArray(int size) { |
| 2531 | return new TrackInfo[size]; |
| 2532 | } |
| 2533 | }; |
| 2534 | |
| 2535 | }; |
| 2536 | |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2537 | // We would like domain specific classes with more informative names than the `first` and `second` |
| 2538 | // in generic Pair, but we would also like to avoid creating new/trivial classes. As a compromise |
| 2539 | // we document the meanings of `first` and `second` here: |
| 2540 | // |
| 2541 | // Pair.first - inband track index; non-null iff representing an inband track. |
| 2542 | // Pair.second - a SubtitleTrack registered with mSubtitleController; non-null iff representing |
| 2543 | // an inband subtitle track or any out-of-band track (subtitle or timedtext). |
| 2544 | private Vector<Pair<Integer, SubtitleTrack>> mIndexTrackPairs = new Vector<>(); |
| 2545 | private BitSet mInbandTrackIndices = new BitSet(); |
| 2546 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2547 | /** |
| 2548 | * Returns an array of track information. |
| 2549 | * |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2550 | * @return Array of track info. The total number of tracks is the array length. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2551 | * Must be called again if an external timed text source has been added after any of the |
| 2552 | * addTimedTextSource methods are called. |
| 2553 | * @throws IllegalStateException if it is called in an invalid state. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2554 | */ |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2555 | public TrackInfo[] getTrackInfo() throws IllegalStateException { |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2556 | TrackInfo trackInfo[] = getInbandTrackInfo(); |
| 2557 | // add out-of-band tracks |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2558 | synchronized (mIndexTrackPairs) { |
| 2559 | TrackInfo allTrackInfo[] = new TrackInfo[mIndexTrackPairs.size()]; |
| 2560 | for (int i = 0; i < allTrackInfo.length; i++) { |
| 2561 | Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); |
| 2562 | if (p.first != null) { |
| 2563 | // inband track |
| 2564 | allTrackInfo[i] = trackInfo[p.first]; |
| 2565 | } else { |
| 2566 | SubtitleTrack track = p.second; |
| 2567 | allTrackInfo[i] = new TrackInfo(track.getTrackType(), track.getFormat()); |
| 2568 | } |
| 2569 | } |
| 2570 | return allTrackInfo; |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2571 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2572 | } |
| 2573 | |
| 2574 | private TrackInfo[] getInbandTrackInfo() throws IllegalStateException { |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2575 | Parcel request = Parcel.obtain(); |
| 2576 | Parcel reply = Parcel.obtain(); |
Insun Kang | be0ea96 | 2012-05-04 20:52:57 +0900 | [diff] [blame] | 2577 | try { |
| 2578 | request.writeInterfaceToken(IMEDIA_PLAYER); |
| 2579 | request.writeInt(INVOKE_ID_GET_TRACK_INFO); |
| 2580 | invoke(request, reply); |
| 2581 | TrackInfo trackInfo[] = reply.createTypedArray(TrackInfo.CREATOR); |
| 2582 | return trackInfo; |
| 2583 | } finally { |
| 2584 | request.recycle(); |
| 2585 | reply.recycle(); |
| 2586 | } |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2587 | } |
| 2588 | |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2589 | /* Do not change these values without updating their counterparts |
| 2590 | * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp! |
| 2591 | */ |
| 2592 | /** |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2593 | * MIME type for SubRip (SRT) container. Used in addTimedTextSource APIs. |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2594 | * @deprecated use {@link MediaFormat#MIMETYPE_TEXT_SUBRIP} |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2595 | */ |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2596 | public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = MediaFormat.MIMETYPE_TEXT_SUBRIP; |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2597 | |
Chong Zhang | f8ca13b | 2013-09-11 13:41:00 -0700 | [diff] [blame] | 2598 | /** |
| 2599 | * MIME type for WebVTT subtitle data. |
| 2600 | * @hide |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2601 | * @deprecated |
Chong Zhang | f8ca13b | 2013-09-11 13:41:00 -0700 | [diff] [blame] | 2602 | */ |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2603 | public static final String MEDIA_MIMETYPE_TEXT_VTT = MediaFormat.MIMETYPE_TEXT_VTT; |
Chong Zhang | f8ca13b | 2013-09-11 13:41:00 -0700 | [diff] [blame] | 2604 | |
Chong Zhang | bdfd910 | 2014-06-11 15:10:23 -0700 | [diff] [blame] | 2605 | /** |
| 2606 | * MIME type for CEA-608 closed caption data. |
| 2607 | * @hide |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2608 | * @deprecated |
Chong Zhang | bdfd910 | 2014-06-11 15:10:23 -0700 | [diff] [blame] | 2609 | */ |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2610 | public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = MediaFormat.MIMETYPE_TEXT_CEA_608; |
Chong Zhang | bdfd910 | 2014-06-11 15:10:23 -0700 | [diff] [blame] | 2611 | |
Jaesung Chung | 978bf5e | 2015-12-07 21:46:40 +0900 | [diff] [blame] | 2612 | /** |
| 2613 | * MIME type for CEA-708 closed caption data. |
| 2614 | * @hide |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2615 | * @deprecated |
Jaesung Chung | 978bf5e | 2015-12-07 21:46:40 +0900 | [diff] [blame] | 2616 | */ |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 2617 | public static final String MEDIA_MIMETYPE_TEXT_CEA_708 = MediaFormat.MIMETYPE_TEXT_CEA_708; |
Jaesung Chung | 978bf5e | 2015-12-07 21:46:40 +0900 | [diff] [blame] | 2618 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2619 | /* |
| 2620 | * A helper function to check if the mime type is supported by media framework. |
| 2621 | */ |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2622 | private static boolean availableMimeTypeForExternalSource(String mimeType) { |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2623 | if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) { |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2624 | return true; |
| 2625 | } |
| 2626 | return false; |
| 2627 | } |
| 2628 | |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2629 | private SubtitleController mSubtitleController; |
| 2630 | |
| 2631 | /** @hide */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 2632 | @UnsupportedAppUsage |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2633 | public void setSubtitleAnchor( |
| 2634 | SubtitleController controller, |
| 2635 | SubtitleController.Anchor anchor) { |
| 2636 | // TODO: create SubtitleController in MediaPlayer |
| 2637 | mSubtitleController = controller; |
| 2638 | mSubtitleController.setAnchor(anchor); |
| 2639 | } |
| 2640 | |
Robert Shih | 98b6842 | 2015-05-04 09:38:31 -0700 | [diff] [blame] | 2641 | /** |
| 2642 | * The private version of setSubtitleAnchor is used internally to set mSubtitleController if |
| 2643 | * necessary when clients don't provide their own SubtitleControllers using the public version |
| 2644 | * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one). |
| 2645 | */ |
| 2646 | private synchronized void setSubtitleAnchor() { |
Jean-Michel Trivi | 791c484 | 2017-01-03 12:53:36 -0800 | [diff] [blame] | 2647 | if ((mSubtitleController == null) && (ActivityThread.currentApplication() != null)) { |
Toshikazu Saito | 6aee256 | 2016-09-08 21:54:25 +0900 | [diff] [blame] | 2648 | getMediaTimeProvider(); |
Robert Shih | 98b6842 | 2015-05-04 09:38:31 -0700 | [diff] [blame] | 2649 | final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread"); |
| 2650 | thread.start(); |
| 2651 | Handler handler = new Handler(thread.getLooper()); |
| 2652 | handler.post(new Runnable() { |
| 2653 | @Override |
| 2654 | public void run() { |
| 2655 | Context context = ActivityThread.currentApplication(); |
| 2656 | mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer.this); |
| 2657 | mSubtitleController.setAnchor(new Anchor() { |
| 2658 | @Override |
| 2659 | public void setSubtitleWidget(RenderingWidget subtitleWidget) { |
| 2660 | } |
| 2661 | |
| 2662 | @Override |
| 2663 | public Looper getSubtitleLooper() { |
Toshikazu Saito | 6aee256 | 2016-09-08 21:54:25 +0900 | [diff] [blame] | 2664 | return mTimeProvider.mEventHandler.getLooper(); |
Robert Shih | 98b6842 | 2015-05-04 09:38:31 -0700 | [diff] [blame] | 2665 | } |
| 2666 | }); |
| 2667 | thread.getLooper().quitSafely(); |
| 2668 | } |
| 2669 | }); |
| 2670 | try { |
| 2671 | thread.join(); |
| 2672 | } catch (InterruptedException e) { |
| 2673 | Thread.currentThread().interrupt(); |
| 2674 | Log.w(TAG, "failed to join SetSubtitleAnchorThread"); |
| 2675 | } |
| 2676 | } |
| 2677 | } |
| 2678 | |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2679 | private int mSelectedSubtitleTrackIndex = -1; |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2680 | private Vector<InputStream> mOpenSubtitleSources; |
| 2681 | |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 2682 | private final OnSubtitleDataListener mIntSubtitleDataListener = new OnSubtitleDataListener() { |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2683 | @Override |
| 2684 | public void onSubtitleData(MediaPlayer mp, SubtitleData data) { |
| 2685 | int index = data.getTrackIndex(); |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2686 | synchronized (mIndexTrackPairs) { |
| 2687 | for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { |
| 2688 | if (p.first != null && p.first == index && p.second != null) { |
| 2689 | // inband subtitle track that owns data |
| 2690 | SubtitleTrack track = p.second; |
| 2691 | track.onData(data); |
| 2692 | } |
| 2693 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2694 | } |
| 2695 | } |
| 2696 | }; |
| 2697 | |
| 2698 | /** @hide */ |
| 2699 | @Override |
| 2700 | public void onSubtitleTrackSelected(SubtitleTrack track) { |
| 2701 | if (mSelectedSubtitleTrackIndex >= 0) { |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 2702 | try { |
| 2703 | selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false); |
| 2704 | } catch (IllegalStateException e) { |
| 2705 | } |
| 2706 | mSelectedSubtitleTrackIndex = -1; |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2707 | } |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 2708 | synchronized (this) { |
| 2709 | mSubtitleDataListenerDisabled = true; |
| 2710 | } |
Lajos Molnar | 29f5183 | 2013-09-20 08:45:31 -0700 | [diff] [blame] | 2711 | if (track == null) { |
| 2712 | return; |
| 2713 | } |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2714 | |
| 2715 | synchronized (mIndexTrackPairs) { |
| 2716 | for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { |
| 2717 | if (p.first != null && p.second == track) { |
| 2718 | // inband subtitle track that is selected |
| 2719 | mSelectedSubtitleTrackIndex = p.first; |
| 2720 | break; |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 2721 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2722 | } |
| 2723 | } |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2724 | |
| 2725 | if (mSelectedSubtitleTrackIndex >= 0) { |
| 2726 | try { |
| 2727 | selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true); |
| 2728 | } catch (IllegalStateException e) { |
| 2729 | } |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 2730 | synchronized (this) { |
| 2731 | mSubtitleDataListenerDisabled = false; |
| 2732 | } |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2733 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2734 | // no need to select out-of-band tracks |
| 2735 | } |
| 2736 | |
| 2737 | /** @hide */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 2738 | @UnsupportedAppUsage |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2739 | public void addSubtitleSource(InputStream is, MediaFormat format) |
| 2740 | throws IllegalStateException |
| 2741 | { |
| 2742 | final InputStream fIs = is; |
| 2743 | final MediaFormat fFormat = format; |
| 2744 | |
Ronghua Wu | c024de3 | 2015-08-11 13:33:49 -0700 | [diff] [blame] | 2745 | if (is != null) { |
| 2746 | // Ensure all input streams are closed. It is also a handy |
| 2747 | // way to implement timeouts in the future. |
| 2748 | synchronized(mOpenSubtitleSources) { |
| 2749 | mOpenSubtitleSources.add(is); |
| 2750 | } |
| 2751 | } else { |
| 2752 | Log.w(TAG, "addSubtitleSource called with null InputStream"); |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2753 | } |
| 2754 | |
Robert Shih | ed78114 | 2016-05-26 14:02:51 -0700 | [diff] [blame] | 2755 | getMediaTimeProvider(); |
| 2756 | |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2757 | // process each subtitle in its own thread |
| 2758 | final HandlerThread thread = new HandlerThread("SubtitleReadThread", |
| 2759 | Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); |
| 2760 | thread.start(); |
| 2761 | Handler handler = new Handler(thread.getLooper()); |
| 2762 | handler.post(new Runnable() { |
| 2763 | private int addTrack() { |
| 2764 | if (fIs == null || mSubtitleController == null) { |
| 2765 | return MEDIA_INFO_UNSUPPORTED_SUBTITLE; |
| 2766 | } |
| 2767 | |
| 2768 | SubtitleTrack track = mSubtitleController.addTrack(fFormat); |
| 2769 | if (track == null) { |
| 2770 | return MEDIA_INFO_UNSUPPORTED_SUBTITLE; |
| 2771 | } |
| 2772 | |
| 2773 | // TODO: do the conversion in the subtitle track |
| 2774 | Scanner scanner = new Scanner(fIs, "UTF-8"); |
| 2775 | String contents = scanner.useDelimiter("\\A").next(); |
| 2776 | synchronized(mOpenSubtitleSources) { |
| 2777 | mOpenSubtitleSources.remove(fIs); |
| 2778 | } |
| 2779 | scanner.close(); |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2780 | synchronized (mIndexTrackPairs) { |
| 2781 | mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); |
| 2782 | } |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 2783 | synchronized (mTimeProviderLock) { |
| 2784 | if (mTimeProvider != null) { |
| 2785 | Handler h = mTimeProvider.mEventHandler; |
| 2786 | int what = TimeProvider.NOTIFY; |
| 2787 | int arg1 = TimeProvider.NOTIFY_TRACK_DATA; |
| 2788 | Pair<SubtitleTrack, byte[]> trackData = |
| 2789 | Pair.create(track, contents.getBytes()); |
| 2790 | Message m = h.obtainMessage(what, arg1, 0, trackData); |
| 2791 | h.sendMessage(m); |
| 2792 | } |
| 2793 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2794 | return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; |
| 2795 | } |
| 2796 | |
| 2797 | public void run() { |
| 2798 | int res = addTrack(); |
| 2799 | if (mEventHandler != null) { |
| 2800 | Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); |
| 2801 | mEventHandler.sendMessage(m); |
| 2802 | } |
| 2803 | thread.getLooper().quitSafely(); |
| 2804 | } |
| 2805 | }); |
| 2806 | } |
| 2807 | |
| 2808 | private void scanInternalSubtitleTracks() { |
qinzhichao | 30d4a49 | 2017-01-04 14:51:33 +0800 | [diff] [blame] | 2809 | setSubtitleAnchor(); |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2810 | |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2811 | populateInbandTracks(); |
| 2812 | |
| 2813 | if (mSubtitleController != null) { |
| 2814 | mSubtitleController.selectDefaultTrack(); |
| 2815 | } |
| 2816 | } |
| 2817 | |
| 2818 | private void populateInbandTracks() { |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2819 | TrackInfo[] tracks = getInbandTrackInfo(); |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2820 | synchronized (mIndexTrackPairs) { |
| 2821 | for (int i = 0; i < tracks.length; i++) { |
| 2822 | if (mInbandTrackIndices.get(i)) { |
| 2823 | continue; |
| 2824 | } else { |
| 2825 | mInbandTrackIndices.set(i); |
| 2826 | } |
| 2827 | |
Wei Jia | aea5de9 | 2018-05-21 18:48:25 -0700 | [diff] [blame] | 2828 | if (tracks[i] == null) { |
| 2829 | Log.w(TAG, "unexpected NULL track at index " + i); |
| 2830 | } |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2831 | // newly appeared inband track |
Wei Jia | aea5de9 | 2018-05-21 18:48:25 -0700 | [diff] [blame] | 2832 | if (tracks[i] != null |
| 2833 | && tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2834 | SubtitleTrack track = mSubtitleController.addTrack( |
| 2835 | tracks[i].getFormat()); |
| 2836 | mIndexTrackPairs.add(Pair.create(i, track)); |
| 2837 | } else { |
| 2838 | mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(i, null)); |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2839 | } |
| 2840 | } |
| 2841 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 2842 | } |
| 2843 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2844 | /* TODO: Limit the total number of external timed text source to a reasonable number. |
| 2845 | */ |
| 2846 | /** |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2847 | * Adds an external timed text source file. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2848 | * |
| 2849 | * Currently supported format is SubRip with the file extension .srt, case insensitive. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2850 | * Note that a single external timed text source may contain multiple tracks in it. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2851 | * One can find the total number of available tracks using {@link #getTrackInfo()} to see what |
| 2852 | * additional tracks become available after this method call. |
| 2853 | * |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2854 | * @param path The file path of external timed text source file. |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2855 | * @param mimeType The mime type of the file. Must be one of the mime types listed above. |
| 2856 | * @throws IOException if the file cannot be accessed or is corrupted. |
| 2857 | * @throws IllegalArgumentException if the mimeType is not supported. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2858 | * @throws IllegalStateException if called in an invalid state. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2859 | */ |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2860 | public void addTimedTextSource(String path, String mimeType) |
| 2861 | throws IOException, IllegalArgumentException, IllegalStateException { |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2862 | if (!availableMimeTypeForExternalSource(mimeType)) { |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2863 | final String msg = "Illegal mimeType for timed text source: " + mimeType; |
James Dong | c4c0284 | 2012-04-12 19:49:02 -0700 | [diff] [blame] | 2864 | throw new IllegalArgumentException(msg); |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2865 | } |
| 2866 | |
Jeff Sharkey | a0a955f | 2019-02-13 15:35:37 -0700 | [diff] [blame] | 2867 | final File file = new File(path); |
| 2868 | try (FileInputStream is = new FileInputStream(file)) { |
| 2869 | addTimedTextSource(is.getFD(), mimeType); |
James Dong | c4c0284 | 2012-04-12 19:49:02 -0700 | [diff] [blame] | 2870 | } |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 2871 | } |
| 2872 | |
| 2873 | /** |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2874 | * Adds an external timed text source file (Uri). |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2875 | * |
| 2876 | * Currently supported format is SubRip with the file extension .srt, case insensitive. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2877 | * Note that a single external timed text source may contain multiple tracks in it. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2878 | * One can find the total number of available tracks using {@link #getTrackInfo()} to see what |
| 2879 | * additional tracks become available after this method call. |
| 2880 | * |
| 2881 | * @param context the Context to use when resolving the Uri |
| 2882 | * @param uri the Content URI of the data you want to play |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2883 | * @param mimeType The mime type of the file. Must be one of the mime types listed above. |
| 2884 | * @throws IOException if the file cannot be accessed or is corrupted. |
| 2885 | * @throws IllegalArgumentException if the mimeType is not supported. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2886 | * @throws IllegalStateException if called in an invalid state. |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 2887 | */ |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2888 | public void addTimedTextSource(Context context, Uri uri, String mimeType) |
| 2889 | throws IOException, IllegalArgumentException, IllegalStateException { |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2890 | String scheme = uri.getScheme(); |
| 2891 | if(scheme == null || scheme.equals("file")) { |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2892 | addTimedTextSource(uri.getPath(), mimeType); |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2893 | return; |
| 2894 | } |
| 2895 | |
| 2896 | AssetFileDescriptor fd = null; |
| 2897 | try { |
| 2898 | ContentResolver resolver = context.getContentResolver(); |
| 2899 | fd = resolver.openAssetFileDescriptor(uri, "r"); |
| 2900 | if (fd == null) { |
| 2901 | return; |
| 2902 | } |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2903 | addTimedTextSource(fd.getFileDescriptor(), mimeType); |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2904 | return; |
| 2905 | } catch (SecurityException ex) { |
| 2906 | } catch (IOException ex) { |
| 2907 | } finally { |
| 2908 | if (fd != null) { |
| 2909 | fd.close(); |
| 2910 | } |
| 2911 | } |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2912 | } |
| 2913 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2914 | /** |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2915 | * Adds an external timed text source file (FileDescriptor). |
| 2916 | * |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2917 | * It is the caller's responsibility to close the file descriptor. |
| 2918 | * It is safe to do so as soon as this call returns. |
| 2919 | * |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2920 | * Currently supported format is SubRip. Note that a single external timed text source may |
| 2921 | * contain multiple tracks in it. One can find the total number of available tracks |
| 2922 | * using {@link #getTrackInfo()} to see what additional tracks become available |
| 2923 | * after this method call. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2924 | * |
| 2925 | * @param fd the FileDescriptor for the file you want to play |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2926 | * @param mimeType The mime type of the file. Must be one of the mime types listed above. |
| 2927 | * @throws IllegalArgumentException if the mimeType is not supported. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2928 | * @throws IllegalStateException if called in an invalid state. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2929 | */ |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2930 | public void addTimedTextSource(FileDescriptor fd, String mimeType) |
| 2931 | throws IllegalArgumentException, IllegalStateException { |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2932 | // intentionally less than LONG_MAX |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2933 | addTimedTextSource(fd, 0, 0x7ffffffffffffffL, mimeType); |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 2934 | } |
| 2935 | |
| 2936 | /** |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2937 | * Adds an external timed text file (FileDescriptor). |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2938 | * |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2939 | * It is the caller's responsibility to close the file descriptor. |
| 2940 | * It is safe to do so as soon as this call returns. |
| 2941 | * |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2942 | * Currently supported format is SubRip. Note that a single external timed text source may |
| 2943 | * contain multiple tracks in it. One can find the total number of available tracks |
| 2944 | * using {@link #getTrackInfo()} to see what additional tracks become available |
| 2945 | * after this method call. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2946 | * |
| 2947 | * @param fd the FileDescriptor for the file you want to play |
| 2948 | * @param offset the offset into the file where the data to be played starts, in bytes |
| 2949 | * @param length the length in bytes of the data to be played |
Ying Wang | 546f3bf | 2014-07-29 16:23:15 -0700 | [diff] [blame] | 2950 | * @param mime The mime type of the file. Must be one of the mime types listed above. |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 2951 | * @throws IllegalArgumentException if the mimeType is not supported. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2952 | * @throws IllegalStateException if called in an invalid state. |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 2953 | */ |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2954 | public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 2955 | throws IllegalArgumentException, IllegalStateException { |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2956 | if (!availableMimeTypeForExternalSource(mime)) { |
| 2957 | throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime); |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 2958 | } |
James Dong | c4c0284 | 2012-04-12 19:49:02 -0700 | [diff] [blame] | 2959 | |
Andreas Gampe | 8285277 | 2015-03-20 16:45:37 -0700 | [diff] [blame] | 2960 | final FileDescriptor dupedFd; |
Insun Kang | be0ea96 | 2012-05-04 20:52:57 +0900 | [diff] [blame] | 2961 | try { |
Tobias Thierer | 96aac9b3 | 2017-10-17 20:26:20 +0100 | [diff] [blame] | 2962 | dupedFd = Os.dup(fd); |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2963 | } catch (ErrnoException ex) { |
| 2964 | Log.e(TAG, ex.getMessage(), ex); |
| 2965 | throw new RuntimeException(ex); |
Insun Kang | be0ea96 | 2012-05-04 20:52:57 +0900 | [diff] [blame] | 2966 | } |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2967 | |
| 2968 | final MediaFormat fFormat = new MediaFormat(); |
| 2969 | fFormat.setString(MediaFormat.KEY_MIME, mime); |
| 2970 | fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1); |
| 2971 | |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2972 | // A MediaPlayer created by a VideoView should already have its mSubtitleController set. |
| 2973 | if (mSubtitleController == null) { |
Robert Shih | 98b6842 | 2015-05-04 09:38:31 -0700 | [diff] [blame] | 2974 | setSubtitleAnchor(); |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2975 | } |
| 2976 | |
| 2977 | if (!mSubtitleController.hasRendererFor(fFormat)) { |
| 2978 | // test and add not atomic |
Robert Shih | 98b6842 | 2015-05-04 09:38:31 -0700 | [diff] [blame] | 2979 | Context context = ActivityThread.currentApplication(); |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2980 | mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler)); |
| 2981 | } |
| 2982 | final SubtitleTrack track = mSubtitleController.addTrack(fFormat); |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 2983 | synchronized (mIndexTrackPairs) { |
| 2984 | mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); |
| 2985 | } |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2986 | |
Robert Shih | ed78114 | 2016-05-26 14:02:51 -0700 | [diff] [blame] | 2987 | getMediaTimeProvider(); |
| 2988 | |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2989 | final long offset2 = offset; |
| 2990 | final long length2 = length; |
| 2991 | final HandlerThread thread = new HandlerThread( |
| 2992 | "TimedTextReadThread", |
| 2993 | Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); |
| 2994 | thread.start(); |
| 2995 | Handler handler = new Handler(thread.getLooper()); |
| 2996 | handler.post(new Runnable() { |
| 2997 | private int addTrack() { |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 2998 | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| 2999 | try { |
Tobias Thierer | 96aac9b3 | 2017-10-17 20:26:20 +0100 | [diff] [blame] | 3000 | Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET); |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 3001 | byte[] buffer = new byte[4096]; |
Robert Shih | 8c4d53c | 2014-09-05 13:33:31 -0700 | [diff] [blame] | 3002 | for (long total = 0; total < length2;) { |
| 3003 | int bytesToRead = (int) Math.min(buffer.length, length2 - total); |
Andreas Gampe | 8285277 | 2015-03-20 16:45:37 -0700 | [diff] [blame] | 3004 | int bytes = IoBridge.read(dupedFd, buffer, 0, bytesToRead); |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 3005 | if (bytes < 0) { |
| 3006 | break; |
| 3007 | } else { |
| 3008 | bos.write(buffer, 0, bytes); |
| 3009 | total += bytes; |
| 3010 | } |
| 3011 | } |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 3012 | synchronized (mTimeProviderLock) { |
| 3013 | if (mTimeProvider != null) { |
| 3014 | Handler h = mTimeProvider.mEventHandler; |
| 3015 | int what = TimeProvider.NOTIFY; |
| 3016 | int arg1 = TimeProvider.NOTIFY_TRACK_DATA; |
| 3017 | Pair<SubtitleTrack, byte[]> trackData = |
| 3018 | Pair.create(track, bos.toByteArray()); |
| 3019 | Message m = h.obtainMessage(what, arg1, 0, trackData); |
| 3020 | h.sendMessage(m); |
| 3021 | } |
| 3022 | } |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 3023 | return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; |
| 3024 | } catch (Exception e) { |
| 3025 | Log.e(TAG, e.getMessage(), e); |
| 3026 | return MEDIA_INFO_TIMED_TEXT_ERROR; |
| 3027 | } finally { |
Andreas Gampe | 8285277 | 2015-03-20 16:45:37 -0700 | [diff] [blame] | 3028 | try { |
Tobias Thierer | 96aac9b3 | 2017-10-17 20:26:20 +0100 | [diff] [blame] | 3029 | Os.close(dupedFd); |
Andreas Gampe | 8285277 | 2015-03-20 16:45:37 -0700 | [diff] [blame] | 3030 | } catch (ErrnoException e) { |
| 3031 | Log.e(TAG, e.getMessage(), e); |
Robert Shih | 3cdf7c5 | 2014-07-23 17:18:46 -0700 | [diff] [blame] | 3032 | } |
| 3033 | } |
| 3034 | } |
| 3035 | |
| 3036 | public void run() { |
| 3037 | int res = addTrack(); |
| 3038 | if (mEventHandler != null) { |
| 3039 | Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); |
| 3040 | mEventHandler.sendMessage(m); |
| 3041 | } |
| 3042 | thread.getLooper().quitSafely(); |
| 3043 | } |
| 3044 | }); |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3045 | } |
| 3046 | |
| 3047 | /** |
Robert Shih | 464da70 | 2014-05-29 10:54:32 -0700 | [diff] [blame] | 3048 | * Returns the index of the audio, video, or subtitle track currently selected for playback, |
| 3049 | * The return value is an index into the array returned by {@link #getTrackInfo()}, and can |
| 3050 | * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. |
| 3051 | * |
| 3052 | * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, |
| 3053 | * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or |
| 3054 | * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} |
| 3055 | * @return index of the audio, video, or subtitle track currently selected for playback; |
| 3056 | * a negative integer is returned when there is no selected track for {@code trackType} or |
| 3057 | * when {@code trackType} is not one of audio, video, or subtitle. |
| 3058 | * @throws IllegalStateException if called after {@link #release()} |
| 3059 | * |
Ryan Lothian | 1e88cf0 | 2014-11-26 18:04:45 +0000 | [diff] [blame] | 3060 | * @see #getTrackInfo() |
| 3061 | * @see #selectTrack(int) |
| 3062 | * @see #deselectTrack(int) |
Robert Shih | 464da70 | 2014-05-29 10:54:32 -0700 | [diff] [blame] | 3063 | */ |
| 3064 | public int getSelectedTrack(int trackType) throws IllegalStateException { |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 3065 | if (mSubtitleController != null |
| 3066 | && (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE |
| 3067 | || trackType == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT)) { |
Robert Shih | 464da70 | 2014-05-29 10:54:32 -0700 | [diff] [blame] | 3068 | SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack(); |
| 3069 | if (subtitleTrack != null) { |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 3070 | synchronized (mIndexTrackPairs) { |
| 3071 | for (int i = 0; i < mIndexTrackPairs.size(); i++) { |
| 3072 | Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); |
| 3073 | if (p.second == subtitleTrack && subtitleTrack.getTrackType() == trackType) { |
| 3074 | return i; |
| 3075 | } |
| 3076 | } |
Robert Shih | 464da70 | 2014-05-29 10:54:32 -0700 | [diff] [blame] | 3077 | } |
| 3078 | } |
| 3079 | } |
| 3080 | |
| 3081 | Parcel request = Parcel.obtain(); |
| 3082 | Parcel reply = Parcel.obtain(); |
| 3083 | try { |
| 3084 | request.writeInterfaceToken(IMEDIA_PLAYER); |
| 3085 | request.writeInt(INVOKE_ID_GET_SELECTED_TRACK); |
| 3086 | request.writeInt(trackType); |
| 3087 | invoke(request, reply); |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 3088 | int inbandTrackIndex = reply.readInt(); |
| 3089 | synchronized (mIndexTrackPairs) { |
| 3090 | for (int i = 0; i < mIndexTrackPairs.size(); i++) { |
| 3091 | Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); |
| 3092 | if (p.first != null && p.first == inbandTrackIndex) { |
| 3093 | return i; |
| 3094 | } |
| 3095 | } |
| 3096 | } |
| 3097 | return -1; |
Robert Shih | 464da70 | 2014-05-29 10:54:32 -0700 | [diff] [blame] | 3098 | } finally { |
| 3099 | request.recycle(); |
| 3100 | reply.recycle(); |
| 3101 | } |
| 3102 | } |
| 3103 | |
| 3104 | /** |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3105 | * Selects a track. |
| 3106 | * <p> |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3107 | * If a MediaPlayer is in invalid state, it throws an IllegalStateException exception. |
| 3108 | * If a MediaPlayer is in <em>Started</em> state, the selected track is presented immediately. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3109 | * If a MediaPlayer is not in Started state, it just marks the track to be played. |
| 3110 | * </p> |
| 3111 | * <p> |
| 3112 | * In any valid state, if it is called multiple times on the same type of track (ie. Video, |
| 3113 | * Audio, Timed Text), the most recent one will be chosen. |
| 3114 | * </p> |
| 3115 | * <p> |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3116 | * The first audio and video tracks are selected by default if available, even though |
| 3117 | * this method is not called. However, no timed text track will be selected until |
| 3118 | * this function is called. |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3119 | * </p> |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3120 | * <p> |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3121 | * Currently, only timed text, subtitle or audio tracks can be selected via this method. |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3122 | * In addition, the support for selecting an audio track at runtime is pretty limited |
| 3123 | * in that an audio track can only be selected in the <em>Prepared</em> state. |
| 3124 | * </p> |
| 3125 | * @param index the index of the track to be selected. The valid range of the index |
| 3126 | * is 0..total number of track - 1. The total number of tracks as well as the type of |
| 3127 | * each individual track can be found by calling {@link #getTrackInfo()} method. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3128 | * @throws IllegalStateException if called in an invalid state. |
| 3129 | * |
| 3130 | * @see android.media.MediaPlayer#getTrackInfo |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3131 | */ |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3132 | public void selectTrack(int index) throws IllegalStateException { |
| 3133 | selectOrDeselectTrack(index, true /* select */); |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3134 | } |
| 3135 | |
| 3136 | /** |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3137 | * Deselect a track. |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3138 | * <p> |
| 3139 | * Currently, the track must be a timed text track and no audio or video tracks can be |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3140 | * deselected. If the timed text track identified by index has not been |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3141 | * selected before, it throws an exception. |
| 3142 | * </p> |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3143 | * @param index the index of the track to be deselected. The valid range of the index |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3144 | * is 0..total number of tracks - 1. The total number of tracks as well as the type of |
| 3145 | * each individual track can be found by calling {@link #getTrackInfo()} method. |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3146 | * @throws IllegalStateException if called in an invalid state. |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3147 | * |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3148 | * @see android.media.MediaPlayer#getTrackInfo |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3149 | */ |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3150 | public void deselectTrack(int index) throws IllegalStateException { |
| 3151 | selectOrDeselectTrack(index, false /* select */); |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3152 | } |
| 3153 | |
James Dong | 7a9734d | 2012-04-19 13:30:20 -0700 | [diff] [blame] | 3154 | private void selectOrDeselectTrack(int index, boolean select) |
| 3155 | throws IllegalStateException { |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 3156 | // handle subtitle track through subtitle controller |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 3157 | populateInbandTracks(); |
| 3158 | |
| 3159 | Pair<Integer,SubtitleTrack> p = null; |
| 3160 | try { |
| 3161 | p = mIndexTrackPairs.get(index); |
| 3162 | } catch (ArrayIndexOutOfBoundsException e) { |
| 3163 | // ignore bad index |
| 3164 | return; |
Robert Shih | 8c4d53c | 2014-09-05 13:33:31 -0700 | [diff] [blame] | 3165 | } |
| 3166 | |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 3167 | SubtitleTrack track = p.second; |
| 3168 | if (track == null) { |
| 3169 | // inband (de)select |
| 3170 | selectOrDeselectInbandTrack(p.first, select); |
| 3171 | return; |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 3172 | } |
| 3173 | |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 3174 | if (mSubtitleController == null) { |
| 3175 | return; |
| 3176 | } |
| 3177 | |
| 3178 | if (!select) { |
| 3179 | // out-of-band deselect |
| 3180 | if (mSubtitleController.getSelectedTrack() == track) { |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 3181 | mSubtitleController.selectTrack(null); |
| 3182 | } else { |
| 3183 | Log.w(TAG, "trying to deselect track that was not selected"); |
| 3184 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 3185 | return; |
| 3186 | } |
| 3187 | |
Robert Shih | a460ea1 | 2015-01-28 17:50:16 -0800 | [diff] [blame] | 3188 | // out-of-band select |
| 3189 | if (track.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) { |
| 3190 | int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); |
| 3191 | synchronized (mIndexTrackPairs) { |
| 3192 | if (ttIndex >= 0 && ttIndex < mIndexTrackPairs.size()) { |
| 3193 | Pair<Integer,SubtitleTrack> p2 = mIndexTrackPairs.get(ttIndex); |
| 3194 | if (p2.first != null && p2.second == null) { |
| 3195 | // deselect inband counterpart |
| 3196 | selectOrDeselectInbandTrack(p2.first, false); |
| 3197 | } |
| 3198 | } |
| 3199 | } |
| 3200 | } |
| 3201 | mSubtitleController.selectTrack(track); |
Lajos Molnar | d486f96 | 2013-09-11 16:26:18 -0700 | [diff] [blame] | 3202 | } |
| 3203 | |
| 3204 | private void selectOrDeselectInbandTrack(int index, boolean select) |
| 3205 | throws IllegalStateException { |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3206 | Parcel request = Parcel.obtain(); |
| 3207 | Parcel reply = Parcel.obtain(); |
Insun Kang | be0ea96 | 2012-05-04 20:52:57 +0900 | [diff] [blame] | 3208 | try { |
| 3209 | request.writeInterfaceToken(IMEDIA_PLAYER); |
| 3210 | request.writeInt(select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK); |
| 3211 | request.writeInt(index); |
| 3212 | invoke(request, reply); |
| 3213 | } finally { |
| 3214 | request.recycle(); |
| 3215 | reply.recycle(); |
| 3216 | } |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 3217 | } |
| 3218 | |
James Dong | 831f0a9 | 2012-04-18 18:12:12 -0700 | [diff] [blame] | 3219 | |
Gloria Wang | c6091dd | 2011-05-03 15:59:03 -0700 | [diff] [blame] | 3220 | /** |
Gloria Wang | d211f41 | 2011-02-19 18:37:57 -0800 | [diff] [blame] | 3221 | * @param reply Parcel with audio/video duration info for battery |
| 3222 | tracking usage |
| 3223 | * @return The status code. |
| 3224 | * {@hide} |
| 3225 | */ |
| 3226 | public native static int native_pullBatteryData(Parcel reply); |
| 3227 | |
John Grossman | 720aa28 | 2012-02-22 15:38:35 -0800 | [diff] [blame] | 3228 | /** |
| 3229 | * Sets the target UDP re-transmit endpoint for the low level player. |
| 3230 | * Generally, the address portion of the endpoint is an IP multicast |
| 3231 | * address, although a unicast address would be equally valid. When a valid |
| 3232 | * retransmit endpoint has been set, the media player will not decode and |
| 3233 | * render the media presentation locally. Instead, the player will attempt |
| 3234 | * to re-multiplex its media data using the Android@Home RTP profile and |
| 3235 | * re-transmit to the target endpoint. Receiver devices (which may be |
| 3236 | * either the same as the transmitting device or different devices) may |
| 3237 | * instantiate, prepare, and start a receiver player using a setDataSource |
| 3238 | * URL of the form... |
| 3239 | * |
| 3240 | * aahRX://<multicastIP>:<port> |
| 3241 | * |
| 3242 | * to receive, decode and render the re-transmitted content. |
| 3243 | * |
| 3244 | * setRetransmitEndpoint may only be called before setDataSource has been |
| 3245 | * called; while the player is in the Idle state. |
| 3246 | * |
| 3247 | * @param endpoint the address and UDP port of the re-transmission target or |
| 3248 | * null if no re-transmission is to be performed. |
| 3249 | * @throws IllegalStateException if it is called in an invalid state |
| 3250 | * @throws IllegalArgumentException if the retransmit endpoint is supplied, |
| 3251 | * but invalid. |
| 3252 | * |
| 3253 | * {@hide} pending API council |
| 3254 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 3255 | @UnsupportedAppUsage |
John Grossman | 720aa28 | 2012-02-22 15:38:35 -0800 | [diff] [blame] | 3256 | public void setRetransmitEndpoint(InetSocketAddress endpoint) |
| 3257 | throws IllegalStateException, IllegalArgumentException |
| 3258 | { |
| 3259 | String addrString = null; |
| 3260 | int port = 0; |
| 3261 | |
| 3262 | if (null != endpoint) { |
| 3263 | addrString = endpoint.getAddress().getHostAddress(); |
| 3264 | port = endpoint.getPort(); |
| 3265 | } |
| 3266 | |
| 3267 | int ret = native_setRetransmitEndpoint(addrString, port); |
| 3268 | if (ret != 0) { |
| 3269 | throw new IllegalArgumentException("Illegal re-transmit endpoint; native ret " + ret); |
| 3270 | } |
| 3271 | } |
| 3272 | |
| 3273 | private native final int native_setRetransmitEndpoint(String addrString, int port); |
| 3274 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3275 | @Override |
Jean-Michel Trivi | 3c86a34 | 2016-04-08 20:47:02 -0700 | [diff] [blame] | 3276 | protected void finalize() { |
| 3277 | baseRelease(); |
| 3278 | native_finalize(); |
| 3279 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3280 | |
| 3281 | /* Do not change these values without updating their counterparts |
| 3282 | * in include/media/mediaplayer.h! |
| 3283 | */ |
| 3284 | private static final int MEDIA_NOP = 0; // interface test message |
| 3285 | private static final int MEDIA_PREPARED = 1; |
| 3286 | private static final int MEDIA_PLAYBACK_COMPLETE = 2; |
| 3287 | private static final int MEDIA_BUFFERING_UPDATE = 3; |
| 3288 | private static final int MEDIA_SEEK_COMPLETE = 4; |
| 3289 | private static final int MEDIA_SET_VIDEO_SIZE = 5; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3290 | private static final int MEDIA_STARTED = 6; |
| 3291 | private static final int MEDIA_PAUSED = 7; |
| 3292 | private static final int MEDIA_STOPPED = 8; |
Lajos Molnar | d58b122 | 2013-09-24 18:04:23 -0700 | [diff] [blame] | 3293 | private static final int MEDIA_SKIPPED = 9; |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 3294 | private static final int MEDIA_NOTIFY_TIME = 98; |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3295 | private static final int MEDIA_TIMED_TEXT = 99; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3296 | private static final int MEDIA_ERROR = 100; |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 3297 | private static final int MEDIA_INFO = 200; |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3298 | private static final int MEDIA_SUBTITLE_DATA = 201; |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 3299 | private static final int MEDIA_META_DATA = 202; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 3300 | private static final int MEDIA_DRM_INFO = 210; |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 3301 | private static final int MEDIA_TIME_DISCONTINUITY = 211; |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 3302 | private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3303 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3304 | private TimeProvider mTimeProvider; |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 3305 | private final Object mTimeProviderLock = new Object(); |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3306 | |
| 3307 | /** @hide */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 3308 | @UnsupportedAppUsage |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3309 | public MediaTimeProvider getMediaTimeProvider() { |
Wei Jia | ab71516 | 2019-01-03 14:02:55 -0800 | [diff] [blame] | 3310 | synchronized (mTimeProviderLock) { |
| 3311 | if (mTimeProvider == null) { |
| 3312 | mTimeProvider = new TimeProvider(this); |
| 3313 | } |
| 3314 | return mTimeProvider; |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 3315 | } |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3316 | } |
| 3317 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3318 | private class EventHandler extends Handler |
| 3319 | { |
| 3320 | private MediaPlayer mMediaPlayer; |
| 3321 | |
| 3322 | public EventHandler(MediaPlayer mp, Looper looper) { |
| 3323 | super(looper); |
| 3324 | mMediaPlayer = mp; |
| 3325 | } |
| 3326 | |
| 3327 | @Override |
| 3328 | public void handleMessage(Message msg) { |
| 3329 | if (mMediaPlayer.mNativeContext == 0) { |
| 3330 | Log.w(TAG, "mediaplayer went away with unhandled events"); |
| 3331 | return; |
| 3332 | } |
| 3333 | switch(msg.what) { |
| 3334 | case MEDIA_PREPARED: |
Robert Shih | 07830aa | 2015-05-27 18:11:44 -0700 | [diff] [blame] | 3335 | try { |
| 3336 | scanInternalSubtitleTracks(); |
| 3337 | } catch (RuntimeException e) { |
| 3338 | // send error message instead of crashing; |
| 3339 | // send error message instead of inlining a call to onError |
| 3340 | // to avoid code duplication. |
| 3341 | Message msg2 = obtainMessage( |
| 3342 | MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); |
| 3343 | sendMessage(msg2); |
Robert Shih | 410239b | 2015-05-21 15:15:50 -0700 | [diff] [blame] | 3344 | } |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 3345 | |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3346 | OnPreparedListener onPreparedListener = mOnPreparedListener; |
| 3347 | if (onPreparedListener != null) |
| 3348 | onPreparedListener.onPrepared(mMediaPlayer); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3349 | return; |
| 3350 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 3351 | case MEDIA_DRM_INFO: |
| 3352 | Log.v(TAG, "MEDIA_DRM_INFO " + mOnDrmInfoHandlerDelegate); |
| 3353 | |
| 3354 | if (msg.obj == null) { |
| 3355 | Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); |
| 3356 | } else if (msg.obj instanceof Parcel) { |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 3357 | // The parcel was parsed already in postEventFromNative |
| 3358 | DrmInfo drmInfo = null; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 3359 | |
| 3360 | OnDrmInfoHandlerDelegate onDrmInfoHandlerDelegate; |
| 3361 | synchronized (mDrmLock) { |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 3362 | if (mOnDrmInfoHandlerDelegate != null && mDrmInfo != null) { |
| 3363 | drmInfo = mDrmInfo.makeCopy(); |
| 3364 | } |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 3365 | // local copy while keeping the lock |
| 3366 | onDrmInfoHandlerDelegate = mOnDrmInfoHandlerDelegate; |
| 3367 | } |
| 3368 | |
| 3369 | // notifying the client outside the lock |
| 3370 | if (onDrmInfoHandlerDelegate != null) { |
| 3371 | onDrmInfoHandlerDelegate.notifyClient(drmInfo); |
| 3372 | } |
| 3373 | } else { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 3374 | Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 3375 | } |
| 3376 | return; |
| 3377 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3378 | case MEDIA_PLAYBACK_COMPLETE: |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3379 | { |
Jean-Michel Trivi | 292a6a4 | 2016-12-01 08:32:15 -0800 | [diff] [blame] | 3380 | mOnCompletionInternalListener.onCompletion(mMediaPlayer); |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3381 | OnCompletionListener onCompletionListener = mOnCompletionListener; |
| 3382 | if (onCompletionListener != null) |
| 3383 | onCompletionListener.onCompletion(mMediaPlayer); |
| 3384 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3385 | stayAwake(false); |
| 3386 | return; |
| 3387 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3388 | case MEDIA_STOPPED: |
Wei Jia | af2d2ac | 2015-03-30 11:16:29 -0700 | [diff] [blame] | 3389 | { |
| 3390 | TimeProvider timeProvider = mTimeProvider; |
| 3391 | if (timeProvider != null) { |
| 3392 | timeProvider.onStopped(); |
| 3393 | } |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3394 | } |
| 3395 | break; |
| 3396 | |
| 3397 | case MEDIA_STARTED: |
| 3398 | case MEDIA_PAUSED: |
Wei Jia | af2d2ac | 2015-03-30 11:16:29 -0700 | [diff] [blame] | 3399 | { |
| 3400 | TimeProvider timeProvider = mTimeProvider; |
| 3401 | if (timeProvider != null) { |
| 3402 | timeProvider.onPaused(msg.what == MEDIA_PAUSED); |
| 3403 | } |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 3404 | } |
| 3405 | break; |
| 3406 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3407 | case MEDIA_BUFFERING_UPDATE: |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3408 | OnBufferingUpdateListener onBufferingUpdateListener = mOnBufferingUpdateListener; |
| 3409 | if (onBufferingUpdateListener != null) |
| 3410 | onBufferingUpdateListener.onBufferingUpdate(mMediaPlayer, msg.arg1); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3411 | return; |
| 3412 | |
| 3413 | case MEDIA_SEEK_COMPLETE: |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3414 | OnSeekCompleteListener onSeekCompleteListener = mOnSeekCompleteListener; |
| 3415 | if (onSeekCompleteListener != null) { |
| 3416 | onSeekCompleteListener.onSeekComplete(mMediaPlayer); |
Wei Jia | af2d2ac | 2015-03-30 11:16:29 -0700 | [diff] [blame] | 3417 | } |
| 3418 | // fall through |
Lajos Molnar | d58b122 | 2013-09-24 18:04:23 -0700 | [diff] [blame] | 3419 | |
| 3420 | case MEDIA_SKIPPED: |
Wei Jia | af2d2ac | 2015-03-30 11:16:29 -0700 | [diff] [blame] | 3421 | { |
| 3422 | TimeProvider timeProvider = mTimeProvider; |
| 3423 | if (timeProvider != null) { |
| 3424 | timeProvider.onSeekComplete(mMediaPlayer); |
| 3425 | } |
| 3426 | } |
| 3427 | return; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3428 | |
| 3429 | case MEDIA_SET_VIDEO_SIZE: |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3430 | OnVideoSizeChangedListener onVideoSizeChangedListener = mOnVideoSizeChangedListener; |
| 3431 | if (onVideoSizeChangedListener != null) { |
| 3432 | onVideoSizeChangedListener.onVideoSizeChanged( |
Wei Jia | af2d2ac | 2015-03-30 11:16:29 -0700 | [diff] [blame] | 3433 | mMediaPlayer, msg.arg1, msg.arg2); |
| 3434 | } |
| 3435 | return; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3436 | |
| 3437 | case MEDIA_ERROR: |
| 3438 | Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); |
| 3439 | boolean error_was_handled = false; |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3440 | OnErrorListener onErrorListener = mOnErrorListener; |
| 3441 | if (onErrorListener != null) { |
| 3442 | error_was_handled = onErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3443 | } |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3444 | { |
Jean-Michel Trivi | 292a6a4 | 2016-12-01 08:32:15 -0800 | [diff] [blame] | 3445 | mOnCompletionInternalListener.onCompletion(mMediaPlayer); |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3446 | OnCompletionListener onCompletionListener = mOnCompletionListener; |
| 3447 | if (onCompletionListener != null && ! error_was_handled) { |
| 3448 | onCompletionListener.onCompletion(mMediaPlayer); |
| 3449 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3450 | } |
| 3451 | stayAwake(false); |
| 3452 | return; |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 3453 | |
| 3454 | case MEDIA_INFO: |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 3455 | switch (msg.arg1) { |
| 3456 | case MEDIA_INFO_VIDEO_TRACK_LAGGING: |
Andreas Huber | 52c7832 | 2011-01-11 15:05:28 -0800 | [diff] [blame] | 3457 | Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 3458 | break; |
| 3459 | case MEDIA_INFO_METADATA_UPDATE: |
Robert Shih | 07830aa | 2015-05-27 18:11:44 -0700 | [diff] [blame] | 3460 | try { |
| 3461 | scanInternalSubtitleTracks(); |
| 3462 | } catch (RuntimeException e) { |
| 3463 | Message msg2 = obtainMessage( |
| 3464 | MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); |
| 3465 | sendMessage(msg2); |
| 3466 | } |
Lajos Molnar | 9d48089 | 2013-09-11 22:10:01 -0700 | [diff] [blame] | 3467 | // fall through |
| 3468 | |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 3469 | case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: |
| 3470 | msg.arg1 = MEDIA_INFO_METADATA_UPDATE; |
Lajos Molnar | 9d48089 | 2013-09-11 22:10:01 -0700 | [diff] [blame] | 3471 | // update default track selection |
Chong Zhang | 7026020 | 2014-06-24 10:53:26 -0700 | [diff] [blame] | 3472 | if (mSubtitleController != null) { |
| 3473 | mSubtitleController.selectDefaultTrack(); |
| 3474 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 3475 | break; |
Chong Zhang | 1e6be72 | 2015-05-06 13:14:59 -0700 | [diff] [blame] | 3476 | case MEDIA_INFO_BUFFERING_START: |
| 3477 | case MEDIA_INFO_BUFFERING_END: |
| 3478 | TimeProvider timeProvider = mTimeProvider; |
| 3479 | if (timeProvider != null) { |
| 3480 | timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START); |
| 3481 | } |
| 3482 | break; |
Andreas Huber | 52c7832 | 2011-01-11 15:05:28 -0800 | [diff] [blame] | 3483 | } |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 3484 | |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3485 | OnInfoListener onInfoListener = mOnInfoListener; |
| 3486 | if (onInfoListener != null) { |
| 3487 | onInfoListener.onInfo(mMediaPlayer, msg.arg1, msg.arg2); |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 3488 | } |
| 3489 | // No real default action so far. |
| 3490 | return; |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 3491 | |
| 3492 | case MEDIA_NOTIFY_TIME: |
| 3493 | TimeProvider timeProvider = mTimeProvider; |
| 3494 | if (timeProvider != null) { |
| 3495 | timeProvider.onNotifyTime(); |
| 3496 | } |
| 3497 | return; |
| 3498 | |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3499 | case MEDIA_TIMED_TEXT: |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3500 | OnTimedTextListener onTimedTextListener = mOnTimedTextListener; |
| 3501 | if (onTimedTextListener == null) |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3502 | return; |
| 3503 | if (msg.obj == null) { |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3504 | onTimedTextListener.onTimedText(mMediaPlayer, null); |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3505 | } else { |
Insun Kang | 8902097 | 2012-05-01 14:13:19 +0900 | [diff] [blame] | 3506 | if (msg.obj instanceof Parcel) { |
| 3507 | Parcel parcel = (Parcel)msg.obj; |
| 3508 | TimedText text = new TimedText(parcel); |
Insun Kang | 333c099 | 2012-07-10 12:47:03 +0900 | [diff] [blame] | 3509 | parcel.recycle(); |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3510 | onTimedTextListener.onTimedText(mMediaPlayer, text); |
Gloria Wang | eaa5d8f | 2011-05-31 16:08:47 -0700 | [diff] [blame] | 3511 | } |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3512 | } |
| 3513 | return; |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 3514 | |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3515 | case MEDIA_SUBTITLE_DATA: |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 3516 | final OnSubtitleDataListener extSubtitleListener; |
| 3517 | final Handler extSubtitleHandler; |
| 3518 | synchronized(this) { |
| 3519 | if (mSubtitleDataListenerDisabled) { |
| 3520 | return; |
| 3521 | } |
| 3522 | extSubtitleListener = mExtSubtitleDataListener; |
| 3523 | extSubtitleHandler = mExtSubtitleDataHandler; |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3524 | } |
| 3525 | if (msg.obj instanceof Parcel) { |
| 3526 | Parcel parcel = (Parcel) msg.obj; |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 3527 | final SubtitleData data = new SubtitleData(parcel); |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3528 | parcel.recycle(); |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 3529 | |
| 3530 | mIntSubtitleDataListener.onSubtitleData(mMediaPlayer, data); |
| 3531 | |
| 3532 | if (extSubtitleListener != null) { |
| 3533 | if (extSubtitleHandler == null) { |
| 3534 | extSubtitleListener.onSubtitleData(mMediaPlayer, data); |
| 3535 | } else { |
| 3536 | extSubtitleHandler.post(new Runnable() { |
| 3537 | @Override |
| 3538 | public void run() { |
| 3539 | extSubtitleListener.onSubtitleData(mMediaPlayer, data); |
| 3540 | } |
| 3541 | }); |
| 3542 | } |
| 3543 | } |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3544 | } |
| 3545 | return; |
| 3546 | |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 3547 | case MEDIA_META_DATA: |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3548 | OnTimedMetaDataAvailableListener onTimedMetaDataAvailableListener = |
| 3549 | mOnTimedMetaDataAvailableListener; |
| 3550 | if (onTimedMetaDataAvailableListener == null) { |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 3551 | return; |
| 3552 | } |
| 3553 | if (msg.obj instanceof Parcel) { |
| 3554 | Parcel parcel = (Parcel) msg.obj; |
| 3555 | TimedMetaData data = TimedMetaData.createTimedMetaDataFromParcel(parcel); |
| 3556 | parcel.recycle(); |
Wei Jia | a2239b7 | 2016-03-03 10:02:19 -0800 | [diff] [blame] | 3557 | onTimedMetaDataAvailableListener.onTimedMetaDataAvailable(mMediaPlayer, data); |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 3558 | } |
| 3559 | return; |
| 3560 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3561 | case MEDIA_NOP: // interface test message - ignore |
| 3562 | break; |
| 3563 | |
jiabin | 6e5a628 | 2017-10-06 09:34:23 -0700 | [diff] [blame] | 3564 | case MEDIA_AUDIO_ROUTING_CHANGED: |
| 3565 | AudioManager.resetAudioPortGeneration(); |
| 3566 | synchronized (mRoutingChangeListeners) { |
| 3567 | for (NativeRoutingEventHandlerDelegate delegate |
| 3568 | : mRoutingChangeListeners.values()) { |
| 3569 | delegate.notifyClient(); |
| 3570 | } |
| 3571 | } |
| 3572 | return; |
| 3573 | |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 3574 | case MEDIA_TIME_DISCONTINUITY: |
| 3575 | final OnMediaTimeDiscontinuityListener mediaTimeListener; |
| 3576 | final Handler mediaTimeHandler; |
| 3577 | synchronized(this) { |
| 3578 | mediaTimeListener = mOnMediaTimeDiscontinuityListener; |
| 3579 | mediaTimeHandler = mOnMediaTimeDiscontinuityHandler; |
| 3580 | } |
| 3581 | if (mediaTimeListener == null) { |
| 3582 | return; |
| 3583 | } |
| 3584 | if (msg.obj instanceof Parcel) { |
| 3585 | Parcel parcel = (Parcel) msg.obj; |
| 3586 | parcel.setDataPosition(0); |
| 3587 | long anchorMediaUs = parcel.readLong(); |
| 3588 | long anchorRealUs = parcel.readLong(); |
| 3589 | float playbackRate = parcel.readFloat(); |
| 3590 | parcel.recycle(); |
| 3591 | final MediaTimestamp timestamp; |
| 3592 | if (anchorMediaUs != -1 && anchorRealUs != -1) { |
| 3593 | timestamp = new MediaTimestamp( |
| 3594 | anchorMediaUs /*Us*/, anchorRealUs * 1000 /*Ns*/, playbackRate); |
| 3595 | } else { |
| 3596 | timestamp = MediaTimestamp.TIMESTAMP_UNKNOWN; |
| 3597 | } |
| 3598 | if (mediaTimeHandler == null) { |
| 3599 | mediaTimeListener.onMediaTimeDiscontinuity(mMediaPlayer, timestamp); |
| 3600 | } else { |
| 3601 | mediaTimeHandler.post(new Runnable() { |
| 3602 | @Override |
| 3603 | public void run() { |
| 3604 | mediaTimeListener.onMediaTimeDiscontinuity(mMediaPlayer, timestamp); |
| 3605 | } |
| 3606 | }); |
| 3607 | } |
| 3608 | } |
| 3609 | return; |
| 3610 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3611 | default: |
| 3612 | Log.e(TAG, "Unknown message type " + msg.what); |
| 3613 | return; |
| 3614 | } |
| 3615 | } |
| 3616 | } |
| 3617 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 3618 | /* |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3619 | * Called from native code when an interesting event happens. This method |
| 3620 | * just uses the EventHandler system to post the event back to the main app thread. |
| 3621 | * We use a weak reference to the original MediaPlayer object so that the native |
| 3622 | * code is safe from the object disappearing from underneath it. (This is |
| 3623 | * the cookie passed to native_setup().) |
| 3624 | */ |
| 3625 | private static void postEventFromNative(Object mediaplayer_ref, |
| 3626 | int what, int arg1, int arg2, Object obj) |
| 3627 | { |
Wonsik Kim | e8b4c97 | 2017-08-30 16:28:01 -0700 | [diff] [blame] | 3628 | final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3629 | if (mp == null) { |
| 3630 | return; |
| 3631 | } |
| 3632 | |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 3633 | switch (what) { |
| 3634 | case MEDIA_INFO: |
| 3635 | if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) { |
Wonsik Kim | e8b4c97 | 2017-08-30 16:28:01 -0700 | [diff] [blame] | 3636 | new Thread(new Runnable() { |
| 3637 | @Override |
| 3638 | public void run() { |
| 3639 | // this acquires the wakelock if needed, and sets the client side state |
| 3640 | mp.start(); |
| 3641 | } |
| 3642 | }).start(); |
| 3643 | Thread.yield(); |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 3644 | } |
| 3645 | break; |
| 3646 | |
| 3647 | case MEDIA_DRM_INFO: |
| 3648 | // We need to derive mDrmInfo before prepare() returns so processing it here |
| 3649 | // before the notification is sent to EventHandler below. EventHandler runs in the |
| 3650 | // notification looper so its handleMessage might process the event after prepare() |
| 3651 | // has returned. |
| 3652 | Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO"); |
| 3653 | if (obj instanceof Parcel) { |
| 3654 | Parcel parcel = (Parcel)obj; |
| 3655 | DrmInfo drmInfo = new DrmInfo(parcel); |
| 3656 | synchronized (mp.mDrmLock) { |
| 3657 | mp.mDrmInfo = drmInfo; |
| 3658 | } |
| 3659 | } else { |
| 3660 | Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj); |
| 3661 | } |
| 3662 | break; |
| 3663 | |
| 3664 | case MEDIA_PREPARED: |
| 3665 | // By this time, we've learned about DrmInfo's presence or absence. This is meant |
| 3666 | // mainly for prepareAsync() use case. For prepare(), this still can run to a race |
| 3667 | // condition b/c MediaPlayerNative releases the prepare() lock before calling notify |
| 3668 | // so we also set mDrmInfoResolved in prepare(). |
| 3669 | synchronized (mp.mDrmLock) { |
| 3670 | mp.mDrmInfoResolved = true; |
| 3671 | } |
| 3672 | break; |
| 3673 | |
Marco Nelissen | 84b8320 | 2012-02-28 16:07:44 -0800 | [diff] [blame] | 3674 | } |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 3675 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3676 | if (mp.mEventHandler != null) { |
| 3677 | Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); |
| 3678 | mp.mEventHandler.sendMessage(m); |
| 3679 | } |
| 3680 | } |
| 3681 | |
| 3682 | /** |
| 3683 | * Interface definition for a callback to be invoked when the media |
| 3684 | * source is ready for playback. |
| 3685 | */ |
| 3686 | public interface OnPreparedListener |
| 3687 | { |
| 3688 | /** |
| 3689 | * Called when the media file is ready for playback. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3690 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3691 | * @param mp the MediaPlayer that is ready for playback |
| 3692 | */ |
| 3693 | void onPrepared(MediaPlayer mp); |
| 3694 | } |
| 3695 | |
| 3696 | /** |
| 3697 | * Register a callback to be invoked when the media source is ready |
| 3698 | * for playback. |
| 3699 | * |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3700 | * @param listener the callback that will be run |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3701 | */ |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3702 | public void setOnPreparedListener(OnPreparedListener listener) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3703 | { |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3704 | mOnPreparedListener = listener; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3705 | } |
| 3706 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 3707 | @UnsupportedAppUsage |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3708 | private OnPreparedListener mOnPreparedListener; |
| 3709 | |
| 3710 | /** |
| 3711 | * Interface definition for a callback to be invoked when playback of |
| 3712 | * a media source has completed. |
| 3713 | */ |
| 3714 | public interface OnCompletionListener |
| 3715 | { |
| 3716 | /** |
| 3717 | * Called when the end of a media source is reached during playback. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3718 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3719 | * @param mp the MediaPlayer that reached the end of the file |
| 3720 | */ |
| 3721 | void onCompletion(MediaPlayer mp); |
| 3722 | } |
| 3723 | |
| 3724 | /** |
| 3725 | * Register a callback to be invoked when the end of a media source |
| 3726 | * has been reached during playback. |
| 3727 | * |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3728 | * @param listener the callback that will be run |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3729 | */ |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3730 | public void setOnCompletionListener(OnCompletionListener listener) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3731 | { |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3732 | mOnCompletionListener = listener; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3733 | } |
| 3734 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 3735 | @UnsupportedAppUsage |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3736 | private OnCompletionListener mOnCompletionListener; |
| 3737 | |
| 3738 | /** |
Jean-Michel Trivi | 292a6a4 | 2016-12-01 08:32:15 -0800 | [diff] [blame] | 3739 | * @hide |
| 3740 | * Internal completion listener to update PlayerBase of the play state. Always "registered". |
| 3741 | */ |
| 3742 | private final OnCompletionListener mOnCompletionInternalListener = new OnCompletionListener() { |
| 3743 | @Override |
| 3744 | public void onCompletion(MediaPlayer mp) { |
| 3745 | baseStop(); |
| 3746 | } |
| 3747 | }; |
| 3748 | |
| 3749 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3750 | * Interface definition of a callback to be invoked indicating buffering |
| 3751 | * status of a media resource being streamed over the network. |
| 3752 | */ |
| 3753 | public interface OnBufferingUpdateListener |
| 3754 | { |
| 3755 | /** |
Jean-Michel Trivi | ea76306 | 2011-04-06 10:34:09 -0700 | [diff] [blame] | 3756 | * Called to update status in buffering a media stream received through |
| 3757 | * progressive HTTP download. The received buffering percentage |
| 3758 | * indicates how much of the content has been buffered or played. |
| 3759 | * For example a buffering update of 80 percent when half the content |
| 3760 | * has already been played indicates that the next 30 percent of the |
| 3761 | * content to play has been buffered. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3762 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3763 | * @param mp the MediaPlayer the update pertains to |
Jean-Michel Trivi | ea76306 | 2011-04-06 10:34:09 -0700 | [diff] [blame] | 3764 | * @param percent the percentage (0-100) of the content |
| 3765 | * that has been buffered or played thus far |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3766 | */ |
| 3767 | void onBufferingUpdate(MediaPlayer mp, int percent); |
| 3768 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3769 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3770 | /** |
| 3771 | * Register a callback to be invoked when the status of a network |
| 3772 | * stream's buffer has changed. |
| 3773 | * |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3774 | * @param listener the callback that will be run. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3775 | */ |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3776 | public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3777 | { |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3778 | mOnBufferingUpdateListener = listener; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3779 | } |
| 3780 | |
| 3781 | private OnBufferingUpdateListener mOnBufferingUpdateListener; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3782 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3783 | /** |
| 3784 | * Interface definition of a callback to be invoked indicating |
| 3785 | * the completion of a seek operation. |
| 3786 | */ |
| 3787 | public interface OnSeekCompleteListener |
| 3788 | { |
| 3789 | /** |
| 3790 | * Called to indicate the completion of a seek operation. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3791 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3792 | * @param mp the MediaPlayer that issued the seek operation |
| 3793 | */ |
| 3794 | public void onSeekComplete(MediaPlayer mp); |
| 3795 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3796 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3797 | /** |
| 3798 | * Register a callback to be invoked when a seek operation has been |
| 3799 | * completed. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3800 | * |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3801 | * @param listener the callback that will be run |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3802 | */ |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3803 | public void setOnSeekCompleteListener(OnSeekCompleteListener listener) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3804 | { |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3805 | mOnSeekCompleteListener = listener; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3806 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3807 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 3808 | @UnsupportedAppUsage |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3809 | private OnSeekCompleteListener mOnSeekCompleteListener; |
| 3810 | |
| 3811 | /** |
| 3812 | * Interface definition of a callback to be invoked when the |
| 3813 | * video size is first known or updated |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3814 | */ |
| 3815 | public interface OnVideoSizeChangedListener |
| 3816 | { |
| 3817 | /** |
| 3818 | * Called to indicate the video size |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3819 | * |
James Dong | ca402cd | 2011-12-06 17:55:38 -0800 | [diff] [blame] | 3820 | * The video size (width and height) could be 0 if there was no video, |
| 3821 | * no display surface was set, or the value was not determined yet. |
| 3822 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3823 | * @param mp the MediaPlayer associated with this callback |
| 3824 | * @param width the width of the video |
| 3825 | * @param height the height of the video |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3826 | */ |
| 3827 | public void onVideoSizeChanged(MediaPlayer mp, int width, int height); |
| 3828 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3829 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3830 | /** |
| 3831 | * Register a callback to be invoked when the video size is |
| 3832 | * known or updated. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3833 | * |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3834 | * @param listener the callback that will be run |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3835 | */ |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3836 | public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener listener) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3837 | { |
The Android Open Source Project | 1059253 | 2009-03-18 17:39:46 -0700 | [diff] [blame] | 3838 | mOnVideoSizeChangedListener = listener; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3839 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 3840 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 3841 | private OnVideoSizeChangedListener mOnVideoSizeChangedListener; |
| 3842 | |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3843 | /** |
| 3844 | * Interface definition of a callback to be invoked when a |
| 3845 | * timed text is available for display. |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3846 | */ |
| 3847 | public interface OnTimedTextListener |
| 3848 | { |
| 3849 | /** |
Gloria Wang | eaa5d8f | 2011-05-31 16:08:47 -0700 | [diff] [blame] | 3850 | * Called to indicate an avaliable timed text |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3851 | * |
| 3852 | * @param mp the MediaPlayer associated with this callback |
Gloria Wang | eaa5d8f | 2011-05-31 16:08:47 -0700 | [diff] [blame] | 3853 | * @param text the timed text sample which contains the text |
| 3854 | * needed to be displayed and the display format. |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3855 | */ |
Gloria Wang | eaa5d8f | 2011-05-31 16:08:47 -0700 | [diff] [blame] | 3856 | public void onTimedText(MediaPlayer mp, TimedText text); |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3857 | } |
| 3858 | |
| 3859 | /** |
| 3860 | * Register a callback to be invoked when a timed text is available |
| 3861 | * for display. |
| 3862 | * |
| 3863 | * @param listener the callback that will be run |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3864 | */ |
| 3865 | public void setOnTimedTextListener(OnTimedTextListener listener) |
| 3866 | { |
| 3867 | mOnTimedTextListener = listener; |
| 3868 | } |
| 3869 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 3870 | @UnsupportedAppUsage |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 3871 | private OnTimedTextListener mOnTimedTextListener; |
| 3872 | |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3873 | /** |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3874 | * Interface definition of a callback to be invoked when a player subtitle track has new |
| 3875 | * subtitle data available. |
| 3876 | * See the {@link MediaPlayer#setOnSubtitleDataListener(OnSubtitleDataListener, Handler)} |
| 3877 | * method for the description of which track will report data through this listener. |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3878 | */ |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3879 | public interface OnSubtitleDataListener { |
| 3880 | /** |
| 3881 | * Method called when new subtitle data is available |
| 3882 | * @param mp the player that reports the new subtitle data |
| 3883 | * @param data the subtitle data |
| 3884 | */ |
| 3885 | public void onSubtitleData(@NonNull MediaPlayer mp, @NonNull SubtitleData data); |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3886 | } |
| 3887 | |
| 3888 | /** |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3889 | * Sets the listener to be invoked when a subtitle track has new data available. |
| 3890 | * The subtitle data comes from a subtitle track previously selected with |
| 3891 | * {@link #selectTrack(int)}. Use {@link #getTrackInfo()} to determine which tracks are |
| 3892 | * subtitles (of type {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}), Subtitle track encodings |
| 3893 | * can be determined by {@link TrackInfo#getFormat()}).<br> |
| 3894 | * See {@link SubtitleData} for an example of querying subtitle encoding. |
| 3895 | * @param listener the listener called when new data is available |
| 3896 | * @param handler the {@link Handler} that receives the listener events |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3897 | */ |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3898 | public void setOnSubtitleDataListener(@NonNull OnSubtitleDataListener listener, |
| 3899 | @NonNull Handler handler) { |
| 3900 | if (listener == null) { |
| 3901 | throw new IllegalArgumentException("Illegal null listener"); |
| 3902 | } |
| 3903 | if (handler == null) { |
| 3904 | throw new IllegalArgumentException("Illegal null handler"); |
| 3905 | } |
| 3906 | setOnSubtitleDataListenerInt(listener, handler); |
| 3907 | } |
| 3908 | /** |
| 3909 | * Sets the listener to be invoked when a subtitle track has new data available. |
| 3910 | * The subtitle data comes from a subtitle track previously selected with |
| 3911 | * {@link #selectTrack(int)}. Use {@link #getTrackInfo()} to determine which tracks are |
| 3912 | * subtitles (of type {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}), Subtitle track encodings |
| 3913 | * can be determined by {@link TrackInfo#getFormat()}).<br> |
| 3914 | * See {@link SubtitleData} for an example of querying subtitle encoding.<br> |
| 3915 | * The listener will be called on the same thread as the one in which the MediaPlayer was |
| 3916 | * created. |
| 3917 | * @param listener the listener called when new data is available |
| 3918 | */ |
| 3919 | public void setOnSubtitleDataListener(@NonNull OnSubtitleDataListener listener) |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3920 | { |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3921 | if (listener == null) { |
| 3922 | throw new IllegalArgumentException("Illegal null listener"); |
| 3923 | } |
| 3924 | setOnSubtitleDataListenerInt(listener, null); |
| 3925 | } |
| 3926 | |
| 3927 | /** |
| 3928 | * Clears the listener previously set with |
| 3929 | * {@link #setOnSubtitleDataListener(OnSubtitleDataListener)} or |
| 3930 | * {@link #setOnSubtitleDataListener(OnSubtitleDataListener, Handler)}. |
| 3931 | */ |
| 3932 | public void clearOnSubtitleDataListener() { |
| 3933 | setOnSubtitleDataListenerInt(null, null); |
| 3934 | } |
| 3935 | |
| 3936 | private void setOnSubtitleDataListenerInt( |
| 3937 | @Nullable OnSubtitleDataListener listener, @Nullable Handler handler) { |
| 3938 | synchronized (this) { |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 3939 | mExtSubtitleDataListener = listener; |
| 3940 | mExtSubtitleDataHandler = handler; |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3941 | } |
Chong Zhang | 83ddaf6 | 2013-08-06 09:43:22 -0700 | [diff] [blame] | 3942 | } |
| 3943 | |
Dongwon Kang | 2fab5bf | 2018-03-27 16:57:38 -0700 | [diff] [blame] | 3944 | private boolean mSubtitleDataListenerDisabled; |
| 3945 | /** External OnSubtitleDataListener, the one set by {@link #setOnSubtitleDataListener}. */ |
| 3946 | private OnSubtitleDataListener mExtSubtitleDataListener; |
| 3947 | private Handler mExtSubtitleDataHandler; |
Jean-Michel Trivi | aabf284 | 2018-03-27 15:29:08 -0700 | [diff] [blame] | 3948 | |
| 3949 | /** |
| 3950 | * Interface definition of a callback to be invoked when discontinuity in the normal progression |
| 3951 | * of the media time is detected. |
| 3952 | * The "normal progression" of media time is defined as the expected increase of the playback |
| 3953 | * position when playing media, relative to the playback speed (for instance every second, media |
| 3954 | * time increases by two seconds when playing at 2x).<br> |
| 3955 | * Discontinuities are encountered in the following cases: |
| 3956 | * <ul> |
| 3957 | * <li>when the player is starved for data and cannot play anymore</li> |
| 3958 | * <li>when the player encounters a playback error</li> |
| 3959 | * <li>when the a seek operation starts, and when it's completed</li> |
| 3960 | * <li>when the playback speed changes</li> |
| 3961 | * <li>when the playback state changes</li> |
| 3962 | * <li>when the player is reset</li> |
| 3963 | * </ul> |
| 3964 | * See the |
| 3965 | * {@link MediaPlayer#setOnMediaTimeDiscontinuityListener(OnMediaTimeDiscontinuityListener, Handler)} |
| 3966 | * method to set a listener for these events. |
| 3967 | */ |
| 3968 | public interface OnMediaTimeDiscontinuityListener { |
| 3969 | /** |
| 3970 | * Called to indicate a time discontinuity has occured. |
| 3971 | * @param mp the MediaPlayer for which the discontinuity has occured. |
| 3972 | * @param mts the timestamp that correlates media time, system time and clock rate, |
| 3973 | * or {@link MediaTimestamp#TIMESTAMP_UNKNOWN} in an error case. |
| 3974 | */ |
| 3975 | public void onMediaTimeDiscontinuity(@NonNull MediaPlayer mp, @NonNull MediaTimestamp mts); |
| 3976 | } |
| 3977 | |
| 3978 | /** |
| 3979 | * Sets the listener to be invoked when a media time discontinuity is encountered. |
| 3980 | * @param listener the listener called after a discontinuity |
| 3981 | * @param handler the {@link Handler} that receives the listener events |
| 3982 | */ |
| 3983 | public void setOnMediaTimeDiscontinuityListener( |
| 3984 | @NonNull OnMediaTimeDiscontinuityListener listener, @NonNull Handler handler) { |
| 3985 | if (listener == null) { |
| 3986 | throw new IllegalArgumentException("Illegal null listener"); |
| 3987 | } |
| 3988 | if (handler == null) { |
| 3989 | throw new IllegalArgumentException("Illegal null handler"); |
| 3990 | } |
| 3991 | setOnMediaTimeDiscontinuityListenerInt(listener, handler); |
| 3992 | } |
| 3993 | |
| 3994 | /** |
| 3995 | * Sets the listener to be invoked when a media time discontinuity is encountered. |
| 3996 | * The listener will be called on the same thread as the one in which the MediaPlayer was |
| 3997 | * created. |
| 3998 | * @param listener the listener called after a discontinuity |
| 3999 | */ |
| 4000 | public void setOnMediaTimeDiscontinuityListener( |
| 4001 | @NonNull OnMediaTimeDiscontinuityListener listener) |
| 4002 | { |
| 4003 | if (listener == null) { |
| 4004 | throw new IllegalArgumentException("Illegal null listener"); |
| 4005 | } |
| 4006 | setOnMediaTimeDiscontinuityListenerInt(listener, null); |
| 4007 | } |
| 4008 | |
| 4009 | /** |
| 4010 | * Clears the listener previously set with |
| 4011 | * {@link #setOnMediaTimeDiscontinuityListener(OnMediaTimeDiscontinuityListener)} |
| 4012 | * or {@link #setOnMediaTimeDiscontinuityListener(OnMediaTimeDiscontinuityListener, Handler)} |
| 4013 | */ |
| 4014 | public void clearOnMediaTimeDiscontinuityListener() { |
| 4015 | setOnMediaTimeDiscontinuityListenerInt(null, null); |
| 4016 | } |
| 4017 | |
| 4018 | private void setOnMediaTimeDiscontinuityListenerInt( |
| 4019 | @Nullable OnMediaTimeDiscontinuityListener listener, @Nullable Handler handler) { |
| 4020 | synchronized (this) { |
| 4021 | mOnMediaTimeDiscontinuityListener = listener; |
| 4022 | mOnMediaTimeDiscontinuityHandler = handler; |
| 4023 | } |
| 4024 | } |
| 4025 | |
| 4026 | private OnMediaTimeDiscontinuityListener mOnMediaTimeDiscontinuityListener; |
| 4027 | private Handler mOnMediaTimeDiscontinuityHandler; |
Gloria Wang | 162ee49 | 2011-04-11 17:23:27 -0700 | [diff] [blame] | 4028 | |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4029 | /** |
| 4030 | * Interface definition of a callback to be invoked when a |
| 4031 | * track has timed metadata available. |
| 4032 | * |
Robert Shih | b7514ef | 2015-05-13 15:02:25 -0700 | [diff] [blame] | 4033 | * @see MediaPlayer#setOnTimedMetaDataAvailableListener(OnTimedMetaDataAvailableListener) |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4034 | */ |
Robert Shih | b7514ef | 2015-05-13 15:02:25 -0700 | [diff] [blame] | 4035 | public interface OnTimedMetaDataAvailableListener |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4036 | { |
| 4037 | /** |
| 4038 | * Called to indicate avaliable timed metadata |
| 4039 | * <p> |
| 4040 | * This method will be called as timed metadata is extracted from the media, |
| 4041 | * in the same order as it occurs in the media. The timing of this event is |
| 4042 | * not controlled by the associated timestamp. |
| 4043 | * |
| 4044 | * @param mp the MediaPlayer associated with this callback |
| 4045 | * @param data the timed metadata sample associated with this event |
| 4046 | */ |
Robert Shih | b7514ef | 2015-05-13 15:02:25 -0700 | [diff] [blame] | 4047 | public void onTimedMetaDataAvailable(MediaPlayer mp, TimedMetaData data); |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4048 | } |
| 4049 | |
| 4050 | /** |
| 4051 | * Register a callback to be invoked when a selected track has timed metadata available. |
| 4052 | * <p> |
| 4053 | * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates |
| 4054 | * {@link TimedMetaData}. |
| 4055 | * |
| 4056 | * @see MediaPlayer#selectTrack(int) |
Robert Shih | b7514ef | 2015-05-13 15:02:25 -0700 | [diff] [blame] | 4057 | * @see MediaPlayer.OnTimedMetaDataAvailableListener |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4058 | * @see TimedMetaData |
| 4059 | * |
| 4060 | * @param listener the callback that will be run |
| 4061 | */ |
Robert Shih | b7514ef | 2015-05-13 15:02:25 -0700 | [diff] [blame] | 4062 | public void setOnTimedMetaDataAvailableListener(OnTimedMetaDataAvailableListener listener) |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4063 | { |
Robert Shih | b7514ef | 2015-05-13 15:02:25 -0700 | [diff] [blame] | 4064 | mOnTimedMetaDataAvailableListener = listener; |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4065 | } |
| 4066 | |
Robert Shih | b7514ef | 2015-05-13 15:02:25 -0700 | [diff] [blame] | 4067 | private OnTimedMetaDataAvailableListener mOnTimedMetaDataAvailableListener; |
Robert Shih | ac033f0 | 2015-04-10 11:15:02 -0700 | [diff] [blame] | 4068 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4069 | /* Do not change these values without updating their counterparts |
| 4070 | * in include/media/mediaplayer.h! |
| 4071 | */ |
| 4072 | /** Unspecified media player error. |
| 4073 | * @see android.media.MediaPlayer.OnErrorListener |
| 4074 | */ |
| 4075 | public static final int MEDIA_ERROR_UNKNOWN = 1; |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4076 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4077 | /** Media server died. In this case, the application must release the |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 4078 | * MediaPlayer object and instantiate a new one. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4079 | * @see android.media.MediaPlayer.OnErrorListener |
| 4080 | */ |
| 4081 | public static final int MEDIA_ERROR_SERVER_DIED = 100; |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 4082 | |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4083 | /** The video is streamed and its container is not valid for progressive |
| 4084 | * playback i.e the video's index (e.g moov atom) is not at the start of the |
| 4085 | * file. |
Niko Catania | 4a0029f | 2009-03-24 19:51:09 -0700 | [diff] [blame] | 4086 | * @see android.media.MediaPlayer.OnErrorListener |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4087 | */ |
| 4088 | public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4089 | |
James Dong | 2adac49 | 2012-08-26 13:00:30 -0700 | [diff] [blame] | 4090 | /** File or network related operation errors. */ |
| 4091 | public static final int MEDIA_ERROR_IO = -1004; |
| 4092 | /** Bitstream is not conforming to the related coding standard or file spec. */ |
| 4093 | public static final int MEDIA_ERROR_MALFORMED = -1007; |
| 4094 | /** Bitstream is conforming to the related coding standard or file spec, but |
| 4095 | * the media framework does not support the feature. */ |
| 4096 | public static final int MEDIA_ERROR_UNSUPPORTED = -1010; |
| 4097 | /** Some operation takes too long to complete, usually more than 3-5 seconds. */ |
| 4098 | public static final int MEDIA_ERROR_TIMED_OUT = -110; |
| 4099 | |
Robert Shih | 706ebb3 | 2015-06-19 11:14:00 -0700 | [diff] [blame] | 4100 | /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in |
| 4101 | * system/core/include/utils/Errors.h |
| 4102 | * @see android.media.MediaPlayer.OnErrorListener |
| 4103 | * @hide |
| 4104 | */ |
| 4105 | public static final int MEDIA_ERROR_SYSTEM = -2147483648; |
| 4106 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4107 | /** |
| 4108 | * Interface definition of a callback to be invoked when there |
| 4109 | * has been an error during an asynchronous operation (other errors |
| 4110 | * will throw exceptions at method call time). |
| 4111 | */ |
| 4112 | public interface OnErrorListener |
| 4113 | { |
| 4114 | /** |
| 4115 | * Called to indicate an error. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 4116 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4117 | * @param mp the MediaPlayer the error pertains to |
| 4118 | * @param what the type of error that has occurred: |
| 4119 | * <ul> |
| 4120 | * <li>{@link #MEDIA_ERROR_UNKNOWN} |
| 4121 | * <li>{@link #MEDIA_ERROR_SERVER_DIED} |
| 4122 | * </ul> |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4123 | * @param extra an extra code, specific to the error. Typically |
James Dong | b10ec1f | 2012-07-30 15:43:14 -0700 | [diff] [blame] | 4124 | * implementation dependent. |
James Dong | 2adac49 | 2012-08-26 13:00:30 -0700 | [diff] [blame] | 4125 | * <ul> |
| 4126 | * <li>{@link #MEDIA_ERROR_IO} |
| 4127 | * <li>{@link #MEDIA_ERROR_MALFORMED} |
| 4128 | * <li>{@link #MEDIA_ERROR_UNSUPPORTED} |
| 4129 | * <li>{@link #MEDIA_ERROR_TIMED_OUT} |
Robert Shih | 706ebb3 | 2015-06-19 11:14:00 -0700 | [diff] [blame] | 4130 | * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error. |
James Dong | 2adac49 | 2012-08-26 13:00:30 -0700 | [diff] [blame] | 4131 | * </ul> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4132 | * @return True if the method handled the error, false if it didn't. |
| 4133 | * Returning false, or not having an OnErrorListener at all, will |
| 4134 | * cause the OnCompletionListener to be called. |
| 4135 | */ |
| 4136 | boolean onError(MediaPlayer mp, int what, int extra); |
| 4137 | } |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 4138 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4139 | /** |
| 4140 | * Register a callback to be invoked when an error has happened |
| 4141 | * during an asynchronous operation. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 4142 | * |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4143 | * @param listener the callback that will be run |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4144 | */ |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4145 | public void setOnErrorListener(OnErrorListener listener) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4146 | { |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4147 | mOnErrorListener = listener; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4148 | } |
| 4149 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 4150 | @UnsupportedAppUsage |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 4151 | private OnErrorListener mOnErrorListener; |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4152 | |
| 4153 | |
| 4154 | /* Do not change these values without updating their counterparts |
| 4155 | * in include/media/mediaplayer.h! |
| 4156 | */ |
| 4157 | /** Unspecified media player info. |
| 4158 | * @see android.media.MediaPlayer.OnInfoListener |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4159 | */ |
| 4160 | public static final int MEDIA_INFO_UNKNOWN = 1; |
| 4161 | |
Marco Nelissen | 84b8320 | 2012-02-28 16:07:44 -0800 | [diff] [blame] | 4162 | /** The player was started because it was used as the next player for another |
| 4163 | * player, which just completed playback. |
Dongwon Kang | 2492c00 | 2018-03-29 15:00:43 -0700 | [diff] [blame] | 4164 | * @see android.media.MediaPlayer#setNextMediaPlayer(MediaPlayer) |
Marco Nelissen | 84b8320 | 2012-02-28 16:07:44 -0800 | [diff] [blame] | 4165 | * @see android.media.MediaPlayer.OnInfoListener |
Marco Nelissen | 84b8320 | 2012-02-28 16:07:44 -0800 | [diff] [blame] | 4166 | */ |
| 4167 | public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; |
| 4168 | |
James Dong | 15a89e6 | 2012-07-23 15:00:37 -0700 | [diff] [blame] | 4169 | /** The player just pushed the very first video frame for rendering. |
| 4170 | * @see android.media.MediaPlayer.OnInfoListener |
| 4171 | */ |
| 4172 | public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; |
| 4173 | |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4174 | /** The video is too complex for the decoder: it can't decode frames fast |
| 4175 | * enough. Possibly only the audio plays fine at this stage. |
| 4176 | * @see android.media.MediaPlayer.OnInfoListener |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4177 | */ |
| 4178 | public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; |
| 4179 | |
Andreas Huber | 4d61f60 | 2010-06-10 11:17:50 -0700 | [diff] [blame] | 4180 | /** MediaPlayer is temporarily pausing playback internally in order to |
| 4181 | * buffer more data. |
James Dong | c36b3c6 | 2011-03-29 12:16:59 -0700 | [diff] [blame] | 4182 | * @see android.media.MediaPlayer.OnInfoListener |
Andreas Huber | 4d61f60 | 2010-06-10 11:17:50 -0700 | [diff] [blame] | 4183 | */ |
| 4184 | public static final int MEDIA_INFO_BUFFERING_START = 701; |
| 4185 | |
| 4186 | /** MediaPlayer is resuming playback after filling buffers. |
James Dong | c36b3c6 | 2011-03-29 12:16:59 -0700 | [diff] [blame] | 4187 | * @see android.media.MediaPlayer.OnInfoListener |
Andreas Huber | 4d61f60 | 2010-06-10 11:17:50 -0700 | [diff] [blame] | 4188 | */ |
| 4189 | public static final int MEDIA_INFO_BUFFERING_END = 702; |
| 4190 | |
Robert Shih | 706ebb3 | 2015-06-19 11:14:00 -0700 | [diff] [blame] | 4191 | /** Estimated network bandwidth information (kbps) is available; currently this event fires |
| 4192 | * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END} |
| 4193 | * when playing network files. |
| 4194 | * @see android.media.MediaPlayer.OnInfoListener |
| 4195 | * @hide |
| 4196 | */ |
| 4197 | public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; |
| 4198 | |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4199 | /** Bad interleaving means that a media has been improperly interleaved or |
| 4200 | * not interleaved at all, e.g has all the video samples first then all the |
| 4201 | * audio ones. Video is playing but a lot of disk seeks may be happening. |
| 4202 | * @see android.media.MediaPlayer.OnInfoListener |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4203 | */ |
| 4204 | public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; |
| 4205 | |
| 4206 | /** The media cannot be seeked (e.g live stream) |
| 4207 | * @see android.media.MediaPlayer.OnInfoListener |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4208 | */ |
| 4209 | public static final int MEDIA_INFO_NOT_SEEKABLE = 801; |
| 4210 | |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 4211 | /** A new set of metadata is available. |
| 4212 | * @see android.media.MediaPlayer.OnInfoListener |
| 4213 | */ |
| 4214 | public static final int MEDIA_INFO_METADATA_UPDATE = 802; |
| 4215 | |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 4216 | /** A new set of external-only metadata is available. Used by |
| 4217 | * JAVA framework to avoid triggering track scanning. |
| 4218 | * @hide |
| 4219 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 4220 | @UnsupportedAppUsage |
Lajos Molnar | 484ff7a9 | 2013-08-15 11:37:47 -0700 | [diff] [blame] | 4221 | public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; |
| 4222 | |
Wei Jia | ae3e706 | 2017-04-04 16:00:03 -0700 | [diff] [blame] | 4223 | /** Informs that audio is not playing. Note that playback of the video |
| 4224 | * is not interrupted. |
| 4225 | * @see android.media.MediaPlayer.OnInfoListener |
| 4226 | */ |
| 4227 | public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; |
| 4228 | |
| 4229 | /** Informs that video is not playing. Note that playback of the audio |
| 4230 | * is not interrupted. |
| 4231 | * @see android.media.MediaPlayer.OnInfoListener |
| 4232 | */ |
| 4233 | public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; |
| 4234 | |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 4235 | /** Failed to handle timed text track properly. |
| 4236 | * @see android.media.MediaPlayer.OnInfoListener |
| 4237 | * |
| 4238 | * {@hide} |
| 4239 | */ |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 4240 | @UnsupportedAppUsage |
Insun Kang | 41f3f71 | 2012-02-16 20:28:27 +0900 | [diff] [blame] | 4241 | public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; |
| 4242 | |
Lajos Molnar | af309824 | 2013-08-15 20:56:53 -0700 | [diff] [blame] | 4243 | /** Subtitle track was not supported by the media framework. |
| 4244 | * @see android.media.MediaPlayer.OnInfoListener |
| 4245 | */ |
| 4246 | public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; |
| 4247 | |
| 4248 | /** Reading the subtitle track takes too long. |
| 4249 | * @see android.media.MediaPlayer.OnInfoListener |
| 4250 | */ |
| 4251 | public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; |
| 4252 | |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4253 | /** |
| 4254 | * Interface definition of a callback to be invoked to communicate some |
| 4255 | * info and/or warning about the media or its playback. |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4256 | */ |
| 4257 | public interface OnInfoListener |
| 4258 | { |
| 4259 | /** |
| 4260 | * Called to indicate an info or a warning. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 4261 | * |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4262 | * @param mp the MediaPlayer the info pertains to. |
| 4263 | * @param what the type of info or warning. |
| 4264 | * <ul> |
| 4265 | * <li>{@link #MEDIA_INFO_UNKNOWN} |
| 4266 | * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING} |
James Dong | 15a89e6 | 2012-07-23 15:00:37 -0700 | [diff] [blame] | 4267 | * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START} |
James Dong | c36b3c6 | 2011-03-29 12:16:59 -0700 | [diff] [blame] | 4268 | * <li>{@link #MEDIA_INFO_BUFFERING_START} |
| 4269 | * <li>{@link #MEDIA_INFO_BUFFERING_END} |
Robert Shih | 706ebb3 | 2015-06-19 11:14:00 -0700 | [diff] [blame] | 4270 | * <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> - |
| 4271 | * bandwidth information is available (as <code>extra</code> kbps) |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4272 | * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING} |
| 4273 | * <li>{@link #MEDIA_INFO_NOT_SEEKABLE} |
Nicolas Catania | 9193e08 | 2009-07-06 12:43:36 -0700 | [diff] [blame] | 4274 | * <li>{@link #MEDIA_INFO_METADATA_UPDATE} |
Lajos Molnar | af309824 | 2013-08-15 20:56:53 -0700 | [diff] [blame] | 4275 | * <li>{@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE} |
| 4276 | * <li>{@link #MEDIA_INFO_SUBTITLE_TIMED_OUT} |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4277 | * </ul> |
| 4278 | * @param extra an extra code, specific to the info. Typically |
James Dong | b10ec1f | 2012-07-30 15:43:14 -0700 | [diff] [blame] | 4279 | * implementation dependent. |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4280 | * @return True if the method handled the info, false if it didn't. |
Wei Jia | d973c00 | 2016-07-12 14:38:50 -0700 | [diff] [blame] | 4281 | * Returning false, or not having an OnInfoListener at all, will |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4282 | * cause the info to be discarded. |
| 4283 | */ |
| 4284 | boolean onInfo(MediaPlayer mp, int what, int extra); |
| 4285 | } |
| 4286 | |
| 4287 | /** |
| 4288 | * Register a callback to be invoked when an info/warning is available. |
Nicolas Catania | 32f8277 | 2009-06-11 16:33:49 -0700 | [diff] [blame] | 4289 | * |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4290 | * @param listener the callback that will be run |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4291 | */ |
| 4292 | public void setOnInfoListener(OnInfoListener listener) |
| 4293 | { |
| 4294 | mOnInfoListener = listener; |
| 4295 | } |
| 4296 | |
Mathew Inwood | 473b9d5 | 2018-08-17 09:07:01 +0100 | [diff] [blame] | 4297 | @UnsupportedAppUsage |
The Android Open Source Project | c39a6e0 | 2009-03-11 12:11:56 -0700 | [diff] [blame] | 4298 | private OnInfoListener mOnInfoListener; |
Marco Nelissen | c39d2e3 | 2009-09-20 10:42:13 -0700 | [diff] [blame] | 4299 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4300 | // Modular DRM begin |
| 4301 | |
| 4302 | /** |
| 4303 | * Interface definition of a callback to be invoked when the app |
| 4304 | * can do DRM configuration (get/set properties) before the session |
| 4305 | * is opened. This facilitates configuration of the properties, like |
| 4306 | * 'securityLevel', which has to be set after DRM scheme creation but |
| 4307 | * before the DRM session is opened. |
| 4308 | * |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4309 | * The only allowed DRM calls in this listener are {@code getDrmPropertyString} |
| 4310 | * and {@code setDrmPropertyString}. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4311 | * |
| 4312 | */ |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4313 | public interface OnDrmConfigHelper |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4314 | { |
| 4315 | /** |
| 4316 | * Called to give the app the opportunity to configure DRM before the session is created |
| 4317 | * |
| 4318 | * @param mp the {@code MediaPlayer} associated with this callback |
| 4319 | */ |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4320 | public void onDrmConfig(MediaPlayer mp); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4321 | } |
| 4322 | |
| 4323 | /** |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4324 | * Register a callback to be invoked for configuration of the DRM object before |
| 4325 | * the session is created. |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4326 | * The callback will be invoked synchronously during the execution |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4327 | * of {@link #prepareDrm(UUID uuid)}. |
| 4328 | * |
| 4329 | * @param listener the callback that will be run |
| 4330 | */ |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4331 | public void setOnDrmConfigHelper(OnDrmConfigHelper listener) |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4332 | { |
| 4333 | synchronized (mDrmLock) { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4334 | mOnDrmConfigHelper = listener; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4335 | } // synchronized |
| 4336 | } |
| 4337 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4338 | private OnDrmConfigHelper mOnDrmConfigHelper; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4339 | |
| 4340 | /** |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4341 | * Interface definition of a callback to be invoked when the |
| 4342 | * DRM info becomes available |
| 4343 | */ |
| 4344 | public interface OnDrmInfoListener |
| 4345 | { |
| 4346 | /** |
| 4347 | * Called to indicate DRM info is available |
| 4348 | * |
| 4349 | * @param mp the {@code MediaPlayer} associated with this callback |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4350 | * @param drmInfo DRM info of the source including PSSH, and subset |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4351 | * of crypto schemes supported by this device |
| 4352 | */ |
| 4353 | public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo); |
| 4354 | } |
| 4355 | |
| 4356 | /** |
| 4357 | * Register a callback to be invoked when the DRM info is |
| 4358 | * known. |
| 4359 | * |
| 4360 | * @param listener the callback that will be run |
| 4361 | */ |
| 4362 | public void setOnDrmInfoListener(OnDrmInfoListener listener) |
| 4363 | { |
| 4364 | setOnDrmInfoListener(listener, null); |
| 4365 | } |
| 4366 | |
| 4367 | /** |
| 4368 | * Register a callback to be invoked when the DRM info is |
| 4369 | * known. |
| 4370 | * |
| 4371 | * @param listener the callback that will be run |
| 4372 | */ |
| 4373 | public void setOnDrmInfoListener(OnDrmInfoListener listener, Handler handler) |
| 4374 | { |
| 4375 | synchronized (mDrmLock) { |
| 4376 | if (listener != null) { |
| 4377 | mOnDrmInfoHandlerDelegate = new OnDrmInfoHandlerDelegate(this, listener, handler); |
| 4378 | } else { |
| 4379 | mOnDrmInfoHandlerDelegate = null; |
| 4380 | } |
| 4381 | } // synchronized |
| 4382 | } |
| 4383 | |
| 4384 | private OnDrmInfoHandlerDelegate mOnDrmInfoHandlerDelegate; |
| 4385 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4386 | |
| 4387 | /** |
| 4388 | * The status codes for {@link OnDrmPreparedListener#onDrmPrepared} listener. |
| 4389 | * <p> |
| 4390 | * |
| 4391 | * DRM preparation has succeeded. |
| 4392 | */ |
| 4393 | public static final int PREPARE_DRM_STATUS_SUCCESS = 0; |
| 4394 | |
| 4395 | /** |
| 4396 | * The device required DRM provisioning but couldn't reach the provisioning server. |
| 4397 | */ |
| 4398 | public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; |
| 4399 | |
| 4400 | /** |
| 4401 | * The device required DRM provisioning but the provisioning server denied the request. |
| 4402 | */ |
| 4403 | public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; |
| 4404 | |
| 4405 | /** |
| 4406 | * The DRM preparation has failed . |
| 4407 | */ |
| 4408 | public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; |
| 4409 | |
| 4410 | |
| 4411 | /** @hide */ |
| 4412 | @IntDef({ |
| 4413 | PREPARE_DRM_STATUS_SUCCESS, |
| 4414 | PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, |
| 4415 | PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, |
| 4416 | PREPARE_DRM_STATUS_PREPARATION_ERROR, |
| 4417 | }) |
| 4418 | @Retention(RetentionPolicy.SOURCE) |
| 4419 | public @interface PrepareDrmStatusCode {} |
| 4420 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4421 | /** |
| 4422 | * Interface definition of a callback to notify the app when the |
| 4423 | * DRM is ready for key request/response |
| 4424 | */ |
| 4425 | public interface OnDrmPreparedListener |
| 4426 | { |
| 4427 | /** |
| 4428 | * Called to notify the app that prepareDrm is finished and ready for key request/response |
| 4429 | * |
| 4430 | * @param mp the {@code MediaPlayer} associated with this callback |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4431 | * @param status the result of DRM preparation which can be |
| 4432 | * {@link #PREPARE_DRM_STATUS_SUCCESS}, |
| 4433 | * {@link #PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR}, |
| 4434 | * {@link #PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR}, or |
| 4435 | * {@link #PREPARE_DRM_STATUS_PREPARATION_ERROR}. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4436 | */ |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4437 | public void onDrmPrepared(MediaPlayer mp, @PrepareDrmStatusCode int status); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4438 | } |
| 4439 | |
| 4440 | /** |
| 4441 | * Register a callback to be invoked when the DRM object is prepared. |
| 4442 | * |
| 4443 | * @param listener the callback that will be run |
| 4444 | */ |
| 4445 | public void setOnDrmPreparedListener(OnDrmPreparedListener listener) |
| 4446 | { |
| 4447 | setOnDrmPreparedListener(listener, null); |
| 4448 | } |
| 4449 | |
| 4450 | /** |
| 4451 | * Register a callback to be invoked when the DRM object is prepared. |
| 4452 | * |
| 4453 | * @param listener the callback that will be run |
| 4454 | * @param handler the Handler that will receive the callback |
| 4455 | */ |
| 4456 | public void setOnDrmPreparedListener(OnDrmPreparedListener listener, Handler handler) |
| 4457 | { |
| 4458 | synchronized (mDrmLock) { |
| 4459 | if (listener != null) { |
| 4460 | mOnDrmPreparedHandlerDelegate = new OnDrmPreparedHandlerDelegate(this, |
| 4461 | listener, handler); |
| 4462 | } else { |
| 4463 | mOnDrmPreparedHandlerDelegate = null; |
| 4464 | } |
| 4465 | } // synchronized |
| 4466 | } |
| 4467 | |
| 4468 | private OnDrmPreparedHandlerDelegate mOnDrmPreparedHandlerDelegate; |
| 4469 | |
| 4470 | |
| 4471 | private class OnDrmInfoHandlerDelegate { |
| 4472 | private MediaPlayer mMediaPlayer; |
| 4473 | private OnDrmInfoListener mOnDrmInfoListener; |
| 4474 | private Handler mHandler; |
| 4475 | |
| 4476 | OnDrmInfoHandlerDelegate(MediaPlayer mp, OnDrmInfoListener listener, Handler handler) { |
| 4477 | mMediaPlayer = mp; |
| 4478 | mOnDrmInfoListener = listener; |
| 4479 | |
| 4480 | // find the looper for our new event handler |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4481 | if (handler != null) { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4482 | mHandler = handler; |
| 4483 | } else { |
| 4484 | // handler == null |
| 4485 | // Will let OnDrmInfoListener be called in mEventHandler similar to other |
| 4486 | // legacy notifications. This is because MEDIA_DRM_INFO's notification has to be |
| 4487 | // sent before MEDIA_PREPARED's (i.e., in the same order they are issued by |
| 4488 | // mediaserver). As a result, the callback has to be called directly by |
| 4489 | // EventHandler.handleMessage similar to onPrepared. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4490 | } |
| 4491 | } |
| 4492 | |
| 4493 | void notifyClient(DrmInfo drmInfo) { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4494 | if (mHandler != null) { |
| 4495 | mHandler.post(new Runnable() { |
| 4496 | @Override |
| 4497 | public void run() { |
| 4498 | mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo); |
| 4499 | } |
| 4500 | }); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4501 | } |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4502 | else { // no handler: direct call by mEventHandler |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4503 | mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo); |
| 4504 | } |
| 4505 | } |
| 4506 | } |
| 4507 | |
| 4508 | private class OnDrmPreparedHandlerDelegate { |
| 4509 | private MediaPlayer mMediaPlayer; |
| 4510 | private OnDrmPreparedListener mOnDrmPreparedListener; |
| 4511 | private Handler mHandler; |
| 4512 | |
| 4513 | OnDrmPreparedHandlerDelegate(MediaPlayer mp, OnDrmPreparedListener listener, |
| 4514 | Handler handler) { |
| 4515 | mMediaPlayer = mp; |
| 4516 | mOnDrmPreparedListener = listener; |
| 4517 | |
| 4518 | // find the looper for our new event handler |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4519 | if (handler != null) { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4520 | mHandler = handler; |
| 4521 | } else if (mEventHandler != null) { |
| 4522 | // Otherwise, use mEventHandler |
| 4523 | mHandler = mEventHandler; |
| 4524 | } else { |
| 4525 | Log.e(TAG, "OnDrmPreparedHandlerDelegate: Unexpected null mEventHandler"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4526 | } |
| 4527 | } |
| 4528 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4529 | void notifyClient(int status) { |
| 4530 | if (mHandler != null) { |
| 4531 | mHandler.post(new Runnable() { |
| 4532 | @Override |
| 4533 | public void run() { |
| 4534 | mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, status); |
| 4535 | } |
| 4536 | }); |
| 4537 | } else { |
| 4538 | Log.e(TAG, "OnDrmPreparedHandlerDelegate:notifyClient: Unexpected null mHandler"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4539 | } |
| 4540 | } |
| 4541 | } |
| 4542 | |
| 4543 | /** |
| 4544 | * Retrieves the DRM Info associated with the current source |
| 4545 | * |
| 4546 | * @throws IllegalStateException if called before prepare() |
| 4547 | */ |
| 4548 | public DrmInfo getDrmInfo() |
| 4549 | { |
| 4550 | DrmInfo drmInfo = null; |
| 4551 | |
| 4552 | // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet; |
| 4553 | // regardless below returns drmInfo anyway instead of raising an exception |
| 4554 | synchronized (mDrmLock) { |
| 4555 | if (!mDrmInfoResolved && mDrmInfo == null) { |
| 4556 | final String msg = "The Player has not been prepared yet"; |
| 4557 | Log.v(TAG, msg); |
| 4558 | throw new IllegalStateException(msg); |
| 4559 | } |
| 4560 | |
| 4561 | if (mDrmInfo != null) { |
| 4562 | drmInfo = mDrmInfo.makeCopy(); |
| 4563 | } |
| 4564 | } // synchronized |
| 4565 | |
| 4566 | return drmInfo; |
| 4567 | } |
| 4568 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4569 | |
| 4570 | /** |
| 4571 | * Prepares the DRM for the current source |
| 4572 | * <p> |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4573 | * If {@code OnDrmConfigHelper} is registered, it will be called during |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4574 | * preparation to allow configuration of the DRM properties before opening the |
| 4575 | * DRM session. Note that the callback is called synchronously in the thread that called |
| 4576 | * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString} |
| 4577 | * and {@code setDrmPropertyString} calls and refrain from any lengthy operation. |
| 4578 | * <p> |
| 4579 | * If the device has not been provisioned before, this call also provisions the device |
| 4580 | * which involves accessing the provisioning server and can take a variable time to |
| 4581 | * complete depending on the network connectivity. |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 4582 | * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4583 | * mode by launching the provisioning in the background and returning. The listener |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4584 | * will be called when provisioning and preparation has finished. If a |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 4585 | * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4586 | * and preparation has finished, i.e., runs in blocking mode. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4587 | * <p> |
Hassan Shojania | 8606c33 | 2017-02-27 13:34:48 -0800 | [diff] [blame] | 4588 | * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM |
| 4589 | * session being ready. The application should not make any assumption about its call |
| 4590 | * sequence (e.g., before or after prepareDrm returns), or the thread context that will |
| 4591 | * execute the listener (unless the listener is registered with a handler thread). |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4592 | * <p> |
| 4593 | * |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4594 | * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved |
| 4595 | * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4596 | * |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4597 | * @throws IllegalStateException if called before prepare(), or the DRM was |
| 4598 | * prepared already |
| 4599 | * @throws UnsupportedSchemeException if the crypto scheme is not supported |
| 4600 | * @throws ResourceBusyException if required DRM resources are in use |
| 4601 | * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a |
| 4602 | * network error |
| 4603 | * @throws ProvisioningServerErrorException if provisioning is required but failed due to |
| 4604 | * the request denied by the provisioning server |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4605 | */ |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4606 | public void prepareDrm(@NonNull UUID uuid) |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4607 | throws UnsupportedSchemeException, ResourceBusyException, |
| 4608 | ProvisioningNetworkErrorException, ProvisioningServerErrorException |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4609 | { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4610 | Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper); |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4611 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4612 | boolean allDoneWithoutProvisioning = false; |
| 4613 | // get a snapshot as we'll use them outside the lock |
| 4614 | OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate = null; |
| 4615 | |
| 4616 | synchronized (mDrmLock) { |
| 4617 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4618 | // only allowing if tied to a protected source; might relax for releasing offline keys |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4619 | if (mDrmInfo == null) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4620 | final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " + |
| 4621 | "DRM info be retrieved before this call."; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4622 | Log.e(TAG, msg); |
| 4623 | throw new IllegalStateException(msg); |
| 4624 | } |
| 4625 | |
| 4626 | if (mActiveDrmScheme) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4627 | final String msg = "prepareDrm(): Wrong usage: There is already " + |
| 4628 | "an active DRM scheme with " + mDrmUUID; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4629 | Log.e(TAG, msg); |
| 4630 | throw new IllegalStateException(msg); |
| 4631 | } |
| 4632 | |
| 4633 | if (mPrepareDrmInProgress) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4634 | final String msg = "prepareDrm(): Wrong usage: There is already " + |
| 4635 | "a pending prepareDrm call."; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4636 | Log.e(TAG, msg); |
| 4637 | throw new IllegalStateException(msg); |
| 4638 | } |
| 4639 | |
| 4640 | if (mDrmProvisioningInProgress) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4641 | final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress."; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4642 | Log.e(TAG, msg); |
| 4643 | throw new IllegalStateException(msg); |
| 4644 | } |
| 4645 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4646 | // shouldn't need this; just for safeguard |
| 4647 | cleanDrmObj(); |
| 4648 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4649 | mPrepareDrmInProgress = true; |
| 4650 | // local copy while the lock is held |
| 4651 | onDrmPreparedHandlerDelegate = mOnDrmPreparedHandlerDelegate; |
| 4652 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4653 | try { |
| 4654 | // only creating the DRM object to allow pre-openSession configuration |
| 4655 | prepareDrm_createDrmStep(uuid); |
| 4656 | } catch (Exception e) { |
| 4657 | Log.w(TAG, "prepareDrm(): Exception ", e); |
| 4658 | mPrepareDrmInProgress = false; |
| 4659 | throw e; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4660 | } |
| 4661 | |
| 4662 | mDrmConfigAllowed = true; |
| 4663 | } // synchronized |
| 4664 | |
| 4665 | |
| 4666 | // call the callback outside the lock |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4667 | if (mOnDrmConfigHelper != null) { |
| 4668 | mOnDrmConfigHelper.onDrmConfig(this); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4669 | } |
| 4670 | |
| 4671 | synchronized (mDrmLock) { |
| 4672 | mDrmConfigAllowed = false; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4673 | boolean earlyExit = false; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4674 | |
| 4675 | try { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4676 | prepareDrm_openSessionStep(uuid); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4677 | |
| 4678 | mDrmUUID = uuid; |
| 4679 | mActiveDrmScheme = true; |
| 4680 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4681 | allDoneWithoutProvisioning = true; |
| 4682 | } catch (IllegalStateException e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4683 | final String msg = "prepareDrm(): Wrong usage: The player must be " + |
| 4684 | "in the prepared state to call prepareDrm()."; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4685 | Log.e(TAG, msg); |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4686 | earlyExit = true; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4687 | throw new IllegalStateException(msg); |
| 4688 | } catch (NotProvisionedException e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4689 | Log.w(TAG, "prepareDrm: NotProvisionedException"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4690 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4691 | // handle provisioning internally; it'll reset mPrepareDrmInProgress |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4692 | int result = HandleProvisioninig(uuid); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4693 | |
| 4694 | // if blocking mode, we're already done; |
| 4695 | // if non-blocking mode, we attempted to launch background provisioning |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4696 | if (result != PREPARE_DRM_STATUS_SUCCESS) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4697 | earlyExit = true; |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4698 | String msg; |
| 4699 | |
| 4700 | switch (result) { |
| 4701 | case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR: |
| 4702 | msg = "prepareDrm: Provisioning was required but failed " + |
| 4703 | "due to a network error."; |
| 4704 | Log.e(TAG, msg); |
| 4705 | throw new ProvisioningNetworkErrorException(msg); |
| 4706 | |
| 4707 | case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR: |
| 4708 | msg = "prepareDrm: Provisioning was required but the request " + |
| 4709 | "was denied by the server."; |
| 4710 | Log.e(TAG, msg); |
| 4711 | throw new ProvisioningServerErrorException(msg); |
| 4712 | |
| 4713 | case PREPARE_DRM_STATUS_PREPARATION_ERROR: |
| 4714 | default: // default for safeguard |
| 4715 | msg = "prepareDrm: Post-provisioning preparation failed."; |
| 4716 | Log.e(TAG, msg); |
| 4717 | throw new IllegalStateException(msg); |
| 4718 | } |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4719 | } |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4720 | // nothing else to do; |
| 4721 | // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup |
| 4722 | } catch (Exception e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4723 | Log.e(TAG, "prepareDrm: Exception " + e); |
| 4724 | earlyExit = true; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4725 | throw e; |
| 4726 | } finally { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4727 | if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception |
| 4728 | mPrepareDrmInProgress = false; |
| 4729 | } |
| 4730 | if (earlyExit) { // cleaning up object if didn't succeed |
| 4731 | cleanDrmObj(); |
| 4732 | } |
| 4733 | } // finally |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4734 | } // synchronized |
| 4735 | |
| 4736 | |
| 4737 | // if finished successfully without provisioning, call the callback outside the lock |
| 4738 | if (allDoneWithoutProvisioning) { |
| 4739 | if (onDrmPreparedHandlerDelegate != null) |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4740 | onDrmPreparedHandlerDelegate.notifyClient(PREPARE_DRM_STATUS_SUCCESS); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4741 | } |
| 4742 | |
| 4743 | } |
| 4744 | |
| 4745 | |
| 4746 | private native void _releaseDrm(); |
| 4747 | |
| 4748 | /** |
| 4749 | * Releases the DRM session |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4750 | * <p> |
| 4751 | * The player has to have an active DRM session and be in stopped, or prepared |
| 4752 | * state before this call is made. |
| 4753 | * A {@code reset()} call will release the DRM session implicitly. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4754 | * |
| 4755 | * @throws NoDrmSchemeException if there is no active DRM session to release |
| 4756 | */ |
| 4757 | public void releaseDrm() |
| 4758 | throws NoDrmSchemeException |
| 4759 | { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4760 | Log.v(TAG, "releaseDrm:"); |
| 4761 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4762 | synchronized (mDrmLock) { |
| 4763 | if (!mActiveDrmScheme) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4764 | Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4765 | throw new NoDrmSchemeException("releaseDrm: No active DRM scheme to release."); |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4766 | } |
| 4767 | |
| 4768 | try { |
| 4769 | // we don't have the player's state in this layer. The below call raises |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4770 | // exception if we're in a non-stopped/prepared state. |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4771 | |
| 4772 | // for cleaning native/mediaserver crypto object |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4773 | _releaseDrm(); |
| 4774 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4775 | // for cleaning client-side MediaDrm object; only called if above has succeeded |
| 4776 | cleanDrmObj(); |
| 4777 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4778 | mActiveDrmScheme = false; |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4779 | } catch (IllegalStateException e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4780 | Log.w(TAG, "releaseDrm: Exception ", e); |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4781 | throw new IllegalStateException("releaseDrm: The player is not in a valid state."); |
| 4782 | } catch (Exception e) { |
| 4783 | Log.e(TAG, "releaseDrm: Exception ", e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4784 | } |
| 4785 | } // synchronized |
| 4786 | } |
| 4787 | |
| 4788 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4789 | /** |
| 4790 | * A key request/response exchange occurs between the app and a license server |
| 4791 | * to obtain or release keys used to decrypt encrypted content. |
| 4792 | * <p> |
| 4793 | * getKeyRequest() is used to obtain an opaque key request byte array that is |
| 4794 | * delivered to the license server. The opaque key request byte array is returned |
| 4795 | * in KeyRequest.data. The recommended URL to deliver the key request to is |
| 4796 | * returned in KeyRequest.defaultUrl. |
| 4797 | * <p> |
| 4798 | * After the app has received the key request response from the server, |
| 4799 | * it should deliver to the response to the DRM engine plugin using the method |
| 4800 | * {@link #provideKeyResponse}. |
| 4801 | * |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4802 | * @param keySetId is the key-set identifier of the offline keys being released when keyType is |
| 4803 | * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when |
| 4804 | * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. |
| 4805 | * |
| 4806 | * @param initData is the container-specific initialization data when the keyType is |
| 4807 | * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is |
| 4808 | * interpreted based on the mime type provided in the mimeType parameter. It could |
| 4809 | * contain, for example, the content ID, key ID or other data obtained from the content |
| 4810 | * metadata that is required in generating the key request. |
| 4811 | * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4812 | * |
| 4813 | * @param mimeType identifies the mime type of the content |
| 4814 | * |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4815 | * @param keyType specifies the type of the request. The request may be to acquire |
| 4816 | * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content |
| 4817 | * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired |
| 4818 | * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4819 | * |
| 4820 | * @param optionalParameters are included in the key request message to |
| 4821 | * allow a client application to provide additional message parameters to the server. |
| 4822 | * This may be {@code null} if no additional parameters are to be sent. |
| 4823 | * |
| 4824 | * @throws NoDrmSchemeException if there is no active DRM session |
| 4825 | */ |
| 4826 | @NonNull |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4827 | public MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData, |
| 4828 | @Nullable String mimeType, @MediaDrm.KeyType int keyType, |
| 4829 | @Nullable Map<String, String> optionalParameters) |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4830 | throws NoDrmSchemeException |
| 4831 | { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4832 | Log.v(TAG, "getKeyRequest: " + |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4833 | " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4834 | " keyType: " + keyType + " optionalParameters: " + optionalParameters); |
| 4835 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4836 | synchronized (mDrmLock) { |
| 4837 | if (!mActiveDrmScheme) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4838 | Log.e(TAG, "getKeyRequest NoDrmSchemeException"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4839 | throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first."); |
| 4840 | } |
| 4841 | |
| 4842 | try { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4843 | byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? |
| 4844 | mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE |
| 4845 | keySetId; // keySetId for KEY_TYPE_RELEASE |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4846 | |
| 4847 | HashMap<String, String> hmapOptionalParameters = |
| 4848 | (optionalParameters != null) ? |
| 4849 | new HashMap<String, String>(optionalParameters) : |
| 4850 | null; |
| 4851 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4852 | MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType, |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4853 | keyType, hmapOptionalParameters); |
| 4854 | Log.v(TAG, "getKeyRequest: --> request: " + request); |
| 4855 | |
| 4856 | return request; |
| 4857 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4858 | } catch (NotProvisionedException e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4859 | Log.w(TAG, "getKeyRequest NotProvisionedException: " + |
| 4860 | "Unexpected. Shouldn't have reached here."); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4861 | throw new IllegalStateException("getKeyRequest: Unexpected provisioning error."); |
| 4862 | } catch (Exception e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4863 | Log.w(TAG, "getKeyRequest Exception " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4864 | throw e; |
| 4865 | } |
| 4866 | |
| 4867 | } // synchronized |
| 4868 | } |
| 4869 | |
| 4870 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4871 | /** |
| 4872 | * A key response is received from the license server by the app, then it is |
| 4873 | * provided to the DRM engine plugin using provideKeyResponse. When the |
| 4874 | * response is for an offline key request, a key-set identifier is returned that |
| 4875 | * can be used to later restore the keys to a new session with the method |
| 4876 | * {@ link # restoreKeys}. |
| 4877 | * When the response is for a streaming or release request, null is returned. |
| 4878 | * |
| 4879 | * @param keySetId When the response is for a release request, keySetId identifies |
| 4880 | * the saved key associated with the release request (i.e., the same keySetId |
| 4881 | * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the |
| 4882 | * response is for either streaming or offline key requests. |
| 4883 | * |
| 4884 | * @param response the byte array response from the server |
| 4885 | * |
| 4886 | * @throws NoDrmSchemeException if there is no active DRM session |
| 4887 | * @throws DeniedByServerException if the response indicates that the |
| 4888 | * server rejected the request |
| 4889 | */ |
| 4890 | public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) |
| 4891 | throws NoDrmSchemeException, DeniedByServerException |
| 4892 | { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4893 | Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response); |
| 4894 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4895 | synchronized (mDrmLock) { |
| 4896 | |
| 4897 | if (!mActiveDrmScheme) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4898 | Log.e(TAG, "getKeyRequest NoDrmSchemeException"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4899 | throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first."); |
| 4900 | } |
| 4901 | |
| 4902 | try { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4903 | byte[] scope = (keySetId == null) ? |
| 4904 | mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE |
| 4905 | keySetId; // keySetId for KEY_TYPE_RELEASE |
| 4906 | |
| 4907 | byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); |
| 4908 | |
| 4909 | Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response + |
| 4910 | " --> " + keySetResult); |
| 4911 | |
| 4912 | |
| 4913 | return keySetResult; |
| 4914 | |
| 4915 | } catch (NotProvisionedException e) { |
| 4916 | Log.w(TAG, "provideKeyResponse NotProvisionedException: " + |
| 4917 | "Unexpected. Shouldn't have reached here."); |
| 4918 | throw new IllegalStateException("provideKeyResponse: " + |
| 4919 | "Unexpected provisioning error."); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4920 | } catch (Exception e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4921 | Log.w(TAG, "provideKeyResponse Exception " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4922 | throw e; |
| 4923 | } |
| 4924 | } // synchronized |
| 4925 | } |
| 4926 | |
| 4927 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4928 | /** |
| 4929 | * Restore persisted offline keys into a new session. keySetId identifies the |
| 4930 | * keys to load, obtained from a prior call to {@link #provideKeyResponse}. |
| 4931 | * |
| 4932 | * @param keySetId identifies the saved key set to restore |
| 4933 | */ |
| 4934 | public void restoreKeys(@NonNull byte[] keySetId) |
| 4935 | throws NoDrmSchemeException |
| 4936 | { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4937 | Log.v(TAG, "restoreKeys: keySetId: " + keySetId); |
| 4938 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4939 | synchronized (mDrmLock) { |
| 4940 | |
| 4941 | if (!mActiveDrmScheme) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4942 | Log.w(TAG, "restoreKeys NoDrmSchemeException"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4943 | throw new NoDrmSchemeException("restoreKeys: Has to set a DRM scheme first."); |
| 4944 | } |
| 4945 | |
| 4946 | try { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4947 | mDrmObj.restoreKeys(mDrmSessionId, keySetId); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4948 | } catch (Exception e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4949 | Log.w(TAG, "restoreKeys Exception " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4950 | throw e; |
| 4951 | } |
| 4952 | |
| 4953 | } // synchronized |
| 4954 | } |
| 4955 | |
| 4956 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4957 | /** |
| 4958 | * Read a DRM engine plugin String property value, given the property name string. |
| 4959 | * <p> |
| 4960 | * @param propertyName the property name |
| 4961 | * |
| 4962 | * Standard fields names are: |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 4963 | * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, |
| 4964 | * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4965 | */ |
| 4966 | @NonNull |
| 4967 | public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName) |
| 4968 | throws NoDrmSchemeException |
| 4969 | { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4970 | Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); |
| 4971 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4972 | String value; |
| 4973 | synchronized (mDrmLock) { |
| 4974 | |
| 4975 | if (!mActiveDrmScheme && !mDrmConfigAllowed) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4976 | Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4977 | throw new NoDrmSchemeException("getDrmPropertyString: Has to prepareDrm() first."); |
| 4978 | } |
| 4979 | |
| 4980 | try { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4981 | value = mDrmObj.getPropertyString(propertyName); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4982 | } catch (Exception e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4983 | Log.w(TAG, "getDrmPropertyString Exception " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4984 | throw e; |
| 4985 | } |
| 4986 | } // synchronized |
| 4987 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 4988 | Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value); |
| 4989 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4990 | return value; |
| 4991 | } |
| 4992 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 4993 | |
| 4994 | /** |
| 4995 | * Set a DRM engine plugin String property value. |
| 4996 | * <p> |
| 4997 | * @param propertyName the property name |
| 4998 | * @param value the property value |
| 4999 | * |
| 5000 | * Standard fields names are: |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5001 | * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, |
| 5002 | * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5003 | */ |
| 5004 | public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName, |
| 5005 | @NonNull String value) |
| 5006 | throws NoDrmSchemeException |
| 5007 | { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5008 | Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); |
| 5009 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5010 | synchronized (mDrmLock) { |
| 5011 | |
| 5012 | if ( !mActiveDrmScheme && !mDrmConfigAllowed ) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5013 | Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5014 | throw new NoDrmSchemeException("setDrmPropertyString: Has to prepareDrm() first."); |
| 5015 | } |
| 5016 | |
| 5017 | try { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5018 | mDrmObj.setPropertyString(propertyName, value); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5019 | } catch ( Exception e ) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5020 | Log.w(TAG, "setDrmPropertyString Exception " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5021 | throw e; |
| 5022 | } |
| 5023 | } // synchronized |
| 5024 | } |
| 5025 | |
Hassan Shojania | 5167ede | 2017-04-19 12:27:05 -0700 | [diff] [blame] | 5026 | /** |
| 5027 | * Encapsulates the DRM properties of the source. |
| 5028 | */ |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5029 | public static final class DrmInfo { |
| 5030 | private Map<UUID, byte[]> mapPssh; |
| 5031 | private UUID[] supportedSchemes; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5032 | |
Hassan Shojania | 5167ede | 2017-04-19 12:27:05 -0700 | [diff] [blame] | 5033 | /** |
| 5034 | * Returns the PSSH info of the data source for each supported DRM scheme. |
| 5035 | */ |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5036 | public Map<UUID, byte[]> getPssh() { |
| 5037 | return mapPssh; |
| 5038 | } |
Hassan Shojania | 5167ede | 2017-04-19 12:27:05 -0700 | [diff] [blame] | 5039 | |
| 5040 | /** |
| 5041 | * Returns the intersection of the data source and the device DRM schemes. |
| 5042 | * It effectively identifies the subset of the source's DRM schemes which |
| 5043 | * are supported by the device too. |
| 5044 | */ |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5045 | public UUID[] getSupportedSchemes() { |
| 5046 | return supportedSchemes; |
| 5047 | } |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5048 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5049 | private DrmInfo(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) { |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5050 | mapPssh = Pssh; |
| 5051 | supportedSchemes = SupportedSchemes; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5052 | } |
| 5053 | |
| 5054 | private DrmInfo(Parcel parcel) { |
| 5055 | Log.v(TAG, "DrmInfo(" + parcel + ") size " + parcel.dataSize()); |
| 5056 | |
| 5057 | int psshsize = parcel.readInt(); |
| 5058 | byte[] pssh = new byte[psshsize]; |
| 5059 | parcel.readByteArray(pssh); |
| 5060 | |
| 5061 | Log.v(TAG, "DrmInfo() PSSH: " + arrToHex(pssh)); |
| 5062 | mapPssh = parsePSSH(pssh, psshsize); |
| 5063 | Log.v(TAG, "DrmInfo() PSSH: " + mapPssh); |
| 5064 | |
| 5065 | int supportedDRMsCount = parcel.readInt(); |
| 5066 | supportedSchemes = new UUID[supportedDRMsCount]; |
| 5067 | for (int i = 0; i < supportedDRMsCount; i++) { |
| 5068 | byte[] uuid = new byte[16]; |
| 5069 | parcel.readByteArray(uuid); |
| 5070 | |
| 5071 | supportedSchemes[i] = bytesToUUID(uuid); |
| 5072 | |
| 5073 | Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " + |
| 5074 | supportedSchemes[i]); |
| 5075 | } |
| 5076 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5077 | Log.v(TAG, "DrmInfo() Parcel psshsize: " + psshsize + |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5078 | " supportedDRMsCount: " + supportedDRMsCount); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5079 | } |
| 5080 | |
| 5081 | private DrmInfo makeCopy() { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5082 | return new DrmInfo(this.mapPssh, this.supportedSchemes); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5083 | } |
| 5084 | |
| 5085 | private String arrToHex(byte[] bytes) { |
| 5086 | String out = "0x"; |
| 5087 | for (int i = 0; i < bytes.length; i++) { |
| 5088 | out += String.format("%02x", bytes[i]); |
| 5089 | } |
| 5090 | |
| 5091 | return out; |
| 5092 | } |
| 5093 | |
| 5094 | private UUID bytesToUUID(byte[] uuid) { |
| 5095 | long msb = 0, lsb = 0; |
| 5096 | for (int i = 0; i < 8; i++) { |
| 5097 | msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) ); |
| 5098 | lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) ); |
| 5099 | } |
| 5100 | |
| 5101 | return new UUID(msb, lsb); |
| 5102 | } |
| 5103 | |
| 5104 | private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) { |
| 5105 | Map<UUID, byte[]> result = new HashMap<UUID, byte[]>(); |
| 5106 | |
| 5107 | final int UUID_SIZE = 16; |
| 5108 | final int DATALEN_SIZE = 4; |
| 5109 | |
| 5110 | int len = psshsize; |
| 5111 | int numentries = 0; |
| 5112 | int i = 0; |
| 5113 | |
| 5114 | while (len > 0) { |
| 5115 | if (len < UUID_SIZE) { |
| 5116 | Log.w(TAG, String.format("parsePSSH: len is too short to parse " + |
| 5117 | "UUID: (%d < 16) pssh: %d", len, psshsize)); |
| 5118 | return null; |
| 5119 | } |
| 5120 | |
| 5121 | byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE); |
| 5122 | UUID uuid = bytesToUUID(subset); |
| 5123 | i += UUID_SIZE; |
| 5124 | len -= UUID_SIZE; |
| 5125 | |
| 5126 | // get data length |
| 5127 | if (len < 4) { |
| 5128 | Log.w(TAG, String.format("parsePSSH: len is too short to parse " + |
| 5129 | "datalen: (%d < 4) pssh: %d", len, psshsize)); |
| 5130 | return null; |
| 5131 | } |
| 5132 | |
| 5133 | subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE); |
| 5134 | int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ? |
| 5135 | ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) | |
| 5136 | ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) : |
| 5137 | ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) | |
| 5138 | ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ; |
| 5139 | i += DATALEN_SIZE; |
| 5140 | len -= DATALEN_SIZE; |
| 5141 | |
| 5142 | if (len < datalen) { |
| 5143 | Log.w(TAG, String.format("parsePSSH: len is too short to parse " + |
| 5144 | "data: (%d < %d) pssh: %d", len, datalen, psshsize)); |
| 5145 | return null; |
| 5146 | } |
| 5147 | |
| 5148 | byte[] data = Arrays.copyOfRange(pssh, i, i+datalen); |
| 5149 | |
| 5150 | // skip the data |
| 5151 | i += datalen; |
| 5152 | len -= datalen; |
| 5153 | |
| 5154 | Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d", |
| 5155 | numentries, uuid, arrToHex(data), psshsize)); |
| 5156 | numentries++; |
| 5157 | result.put(uuid, data); |
| 5158 | } |
| 5159 | |
| 5160 | return result; |
| 5161 | } |
| 5162 | |
| 5163 | }; // DrmInfo |
| 5164 | |
| 5165 | /** |
| 5166 | * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm(). |
| 5167 | * Extends MediaDrm.MediaDrmException |
| 5168 | */ |
| 5169 | public static final class NoDrmSchemeException extends MediaDrmException { |
| 5170 | public NoDrmSchemeException(String detailMessage) { |
| 5171 | super(detailMessage); |
| 5172 | } |
| 5173 | } |
| 5174 | |
| 5175 | /** |
| 5176 | * Thrown when the device requires DRM provisioning but the provisioning attempt has |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5177 | * failed due to a network error (Internet reachability, timeout, etc.). |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5178 | * Extends MediaDrm.MediaDrmException |
| 5179 | */ |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5180 | public static final class ProvisioningNetworkErrorException extends MediaDrmException { |
| 5181 | public ProvisioningNetworkErrorException(String detailMessage) { |
| 5182 | super(detailMessage); |
| 5183 | } |
| 5184 | } |
| 5185 | |
| 5186 | /** |
| 5187 | * Thrown when the device requires DRM provisioning but the provisioning attempt has |
| 5188 | * failed due to the provisioning server denying the request. |
| 5189 | * Extends MediaDrm.MediaDrmException |
| 5190 | */ |
| 5191 | public static final class ProvisioningServerErrorException extends MediaDrmException { |
| 5192 | public ProvisioningServerErrorException(String detailMessage) { |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5193 | super(detailMessage); |
| 5194 | } |
| 5195 | } |
| 5196 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5197 | |
| 5198 | private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId); |
| 5199 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5200 | // Modular DRM helpers |
| 5201 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5202 | private void prepareDrm_createDrmStep(@NonNull UUID uuid) |
| 5203 | throws UnsupportedSchemeException { |
| 5204 | Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); |
| 5205 | |
| 5206 | try { |
| 5207 | mDrmObj = new MediaDrm(uuid); |
| 5208 | Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); |
| 5209 | } catch (Exception e) { // UnsupportedSchemeException |
| 5210 | Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); |
| 5211 | throw e; |
| 5212 | } |
| 5213 | } |
| 5214 | |
| 5215 | private void prepareDrm_openSessionStep(@NonNull UUID uuid) |
| 5216 | throws NotProvisionedException, ResourceBusyException { |
| 5217 | Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); |
| 5218 | |
| 5219 | // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do |
| 5220 | // it anyway so it raises provisioning error if needed. We'd rather handle provisioning |
| 5221 | // at prepareDrm/openSession rather than getKeyRequest/provideKeyResponse |
| 5222 | try { |
| 5223 | mDrmSessionId = mDrmObj.openSession(); |
| 5224 | Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); |
| 5225 | |
| 5226 | // Sending it down to native/mediaserver to create the crypto object |
| 5227 | // This call could simply fail due to bad player state, e.g., after start(). |
| 5228 | _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); |
| 5229 | Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded"); |
| 5230 | |
| 5231 | } catch (Exception e) { //ResourceBusyException, NotProvisionedException |
| 5232 | Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); |
| 5233 | throw e; |
| 5234 | } |
| 5235 | |
| 5236 | } |
| 5237 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5238 | private class ProvisioningThread extends Thread |
| 5239 | { |
| 5240 | public static final int TIMEOUT_MS = 60000; |
| 5241 | |
| 5242 | private UUID uuid; |
| 5243 | private String urlStr; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5244 | private Object drmLock; |
| 5245 | private OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate; |
| 5246 | private MediaPlayer mediaPlayer; |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5247 | private int status; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5248 | private boolean finished; |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5249 | public int status() { |
| 5250 | return status; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5251 | } |
| 5252 | |
| 5253 | public ProvisioningThread initialize(MediaDrm.ProvisionRequest request, |
| 5254 | UUID uuid, MediaPlayer mediaPlayer) { |
| 5255 | // lock is held by the caller |
| 5256 | drmLock = mediaPlayer.mDrmLock; |
| 5257 | onDrmPreparedHandlerDelegate = mediaPlayer.mOnDrmPreparedHandlerDelegate; |
| 5258 | this.mediaPlayer = mediaPlayer; |
| 5259 | |
| 5260 | urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); |
| 5261 | this.uuid = uuid; |
| 5262 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5263 | status = PREPARE_DRM_STATUS_PREPARATION_ERROR; |
| 5264 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5265 | Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5266 | return this; |
| 5267 | } |
| 5268 | |
| 5269 | public void run() { |
| 5270 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5271 | byte[] response = null; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5272 | boolean provisioningSucceeded = false; |
| 5273 | try { |
| 5274 | URL url = new URL(urlStr); |
| 5275 | final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| 5276 | try { |
| 5277 | connection.setRequestMethod("POST"); |
| 5278 | connection.setDoOutput(false); |
| 5279 | connection.setDoInput(true); |
| 5280 | connection.setConnectTimeout(TIMEOUT_MS); |
| 5281 | connection.setReadTimeout(TIMEOUT_MS); |
| 5282 | |
| 5283 | connection.connect(); |
| 5284 | response = Streams.readFully(connection.getInputStream()); |
| 5285 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5286 | Log.v(TAG, "HandleProvisioninig: Thread run: response " + |
| 5287 | response.length + " " + response); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5288 | } catch (Exception e) { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5289 | status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5290 | Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5291 | } finally { |
| 5292 | connection.disconnect(); |
| 5293 | } |
| 5294 | } catch (Exception e) { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5295 | status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5296 | Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5297 | } |
| 5298 | |
| 5299 | if (response != null) { |
| 5300 | try { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5301 | mDrmObj.provideProvisionResponse(response); |
| 5302 | Log.v(TAG, "HandleProvisioninig: Thread run: " + |
| 5303 | "provideProvisionResponse SUCCEEDED!"); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5304 | |
| 5305 | provisioningSucceeded = true; |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5306 | } catch (Exception e) { |
| 5307 | status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5308 | Log.w(TAG, "HandleProvisioninig: Thread run: " + |
| 5309 | "provideProvisionResponse " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5310 | } |
| 5311 | } |
| 5312 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5313 | boolean succeeded = false; |
| 5314 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5315 | // non-blocking mode needs the lock |
| 5316 | if (onDrmPreparedHandlerDelegate != null) { |
| 5317 | |
| 5318 | synchronized (drmLock) { |
| 5319 | // continuing with prepareDrm |
| 5320 | if (provisioningSucceeded) { |
| 5321 | succeeded = mediaPlayer.resumePrepareDrm(uuid); |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5322 | status = (succeeded) ? |
| 5323 | PREPARE_DRM_STATUS_SUCCESS : |
| 5324 | PREPARE_DRM_STATUS_PREPARATION_ERROR; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5325 | } |
| 5326 | mediaPlayer.mDrmProvisioningInProgress = false; |
| 5327 | mediaPlayer.mPrepareDrmInProgress = false; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5328 | if (!succeeded) { |
| 5329 | cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock |
| 5330 | } |
| 5331 | } // synchronized |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5332 | |
| 5333 | // calling the callback outside the lock |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5334 | onDrmPreparedHandlerDelegate.notifyClient(status); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5335 | } else { // blocking mode already has the lock |
| 5336 | |
| 5337 | // continuing with prepareDrm |
| 5338 | if (provisioningSucceeded) { |
| 5339 | succeeded = mediaPlayer.resumePrepareDrm(uuid); |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5340 | status = (succeeded) ? |
| 5341 | PREPARE_DRM_STATUS_SUCCESS : |
| 5342 | PREPARE_DRM_STATUS_PREPARATION_ERROR; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5343 | } |
| 5344 | mediaPlayer.mDrmProvisioningInProgress = false; |
| 5345 | mediaPlayer.mPrepareDrmInProgress = false; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5346 | if (!succeeded) { |
| 5347 | cleanDrmObj(); // cleaning up if it hasn't gone through |
| 5348 | } |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5349 | } |
| 5350 | |
| 5351 | finished = true; |
| 5352 | } // run() |
| 5353 | |
| 5354 | } // ProvisioningThread |
| 5355 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5356 | private int HandleProvisioninig(UUID uuid) |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5357 | { |
| 5358 | // the lock is already held by the caller |
| 5359 | |
| 5360 | if (mDrmProvisioningInProgress) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5361 | Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress"); |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5362 | return PREPARE_DRM_STATUS_PREPARATION_ERROR; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5363 | } |
| 5364 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5365 | MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); |
| 5366 | if (provReq == null) { |
| 5367 | Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null."); |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5368 | return PREPARE_DRM_STATUS_PREPARATION_ERROR; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5369 | } |
| 5370 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5371 | Log.v(TAG, "HandleProvisioninig provReq " + |
| 5372 | " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5373 | |
| 5374 | // networking in a background thread |
| 5375 | mDrmProvisioningInProgress = true; |
| 5376 | |
| 5377 | mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this); |
| 5378 | mDrmProvisioningThread.start(); |
| 5379 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5380 | int result; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5381 | |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5382 | // non-blocking: this is not the final result |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5383 | if (mOnDrmPreparedHandlerDelegate != null) { |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5384 | result = PREPARE_DRM_STATUS_SUCCESS; |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5385 | } else { |
| 5386 | // if blocking mode, wait till provisioning is done |
| 5387 | try { |
| 5388 | mDrmProvisioningThread.join(); |
| 5389 | } catch (Exception e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5390 | Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5391 | } |
Hassan Shojania | f9d3bb7 | 2017-04-15 14:26:44 -0700 | [diff] [blame] | 5392 | result = mDrmProvisioningThread.status(); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5393 | // no longer need the thread |
| 5394 | mDrmProvisioningThread = null; |
| 5395 | } |
| 5396 | |
| 5397 | return result; |
| 5398 | } |
| 5399 | |
| 5400 | private boolean resumePrepareDrm(UUID uuid) |
| 5401 | { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5402 | Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); |
| 5403 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5404 | // mDrmLock is guaranteed to be held |
| 5405 | boolean success = false; |
| 5406 | try { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5407 | // resuming |
| 5408 | prepareDrm_openSessionStep(uuid); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5409 | |
| 5410 | mDrmUUID = uuid; |
| 5411 | mActiveDrmScheme = true; |
| 5412 | |
| 5413 | success = true; |
| 5414 | } catch (Exception e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5415 | Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e); |
| 5416 | // mDrmObj clean up is done by the caller |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5417 | } |
| 5418 | |
| 5419 | return success; |
| 5420 | } |
| 5421 | |
| 5422 | private void resetDrmState() |
| 5423 | { |
| 5424 | synchronized (mDrmLock) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5425 | Log.v(TAG, "resetDrmState: " + |
| 5426 | " mDrmInfo=" + mDrmInfo + |
| 5427 | " mDrmProvisioningThread=" + mDrmProvisioningThread + |
| 5428 | " mPrepareDrmInProgress=" + mPrepareDrmInProgress + |
| 5429 | " mActiveDrmScheme=" + mActiveDrmScheme); |
| 5430 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5431 | mDrmInfoResolved = false; |
| 5432 | mDrmInfo = null; |
| 5433 | |
| 5434 | if (mDrmProvisioningThread != null) { |
| 5435 | // timeout; relying on HttpUrlConnection |
| 5436 | try { |
| 5437 | mDrmProvisioningThread.join(); |
| 5438 | } |
| 5439 | catch (InterruptedException e) { |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5440 | Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5441 | } |
| 5442 | mDrmProvisioningThread = null; |
| 5443 | } |
| 5444 | |
| 5445 | mPrepareDrmInProgress = false; |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5446 | mActiveDrmScheme = false; |
| 5447 | |
| 5448 | cleanDrmObj(); |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5449 | } // synchronized |
| 5450 | } |
| 5451 | |
Hassan Shojania | 06b25fb | 2017-02-06 21:09:42 -0800 | [diff] [blame] | 5452 | private void cleanDrmObj() |
| 5453 | { |
| 5454 | // the caller holds mDrmLock |
| 5455 | Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); |
| 5456 | |
| 5457 | if (mDrmSessionId != null) { |
| 5458 | mDrmObj.closeSession(mDrmSessionId); |
| 5459 | mDrmSessionId = null; |
| 5460 | } |
| 5461 | if (mDrmObj != null) { |
| 5462 | mDrmObj.release(); |
| 5463 | mDrmObj = null; |
| 5464 | } |
| 5465 | } |
| 5466 | |
Hassan Shojania | 0b52e95 | 2017-01-23 09:06:31 -0800 | [diff] [blame] | 5467 | private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { |
| 5468 | long msb = uuid.getMostSignificantBits(); |
| 5469 | long lsb = uuid.getLeastSignificantBits(); |
| 5470 | |
| 5471 | byte[] uuidBytes = new byte[16]; |
| 5472 | for (int i = 0; i < 8; ++i) { |
| 5473 | uuidBytes[i] = (byte)(msb >>> (8 * (7 - i))); |
| 5474 | uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i))); |
| 5475 | } |
| 5476 | |
| 5477 | return uuidBytes; |
| 5478 | } |
| 5479 | |
| 5480 | // Modular DRM end |
| 5481 | |
James Dong | 454014e | 2012-04-28 16:03:55 -0700 | [diff] [blame] | 5482 | /* |
| 5483 | * Test whether a given video scaling mode is supported. |
| 5484 | */ |
| 5485 | private boolean isVideoScalingModeSupported(int mode) { |
| 5486 | return (mode == VIDEO_SCALING_MODE_SCALE_TO_FIT || |
| 5487 | mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); |
| 5488 | } |
Andreas Huber | d5f9fa5 | 2013-05-28 14:39:39 -0700 | [diff] [blame] | 5489 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5490 | /** @hide */ |
| 5491 | static class TimeProvider implements MediaPlayer.OnSeekCompleteListener, |
| 5492 | MediaTimeProvider { |
| 5493 | private static final String TAG = "MTP"; |
| 5494 | private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L; |
| 5495 | private static final long MAX_EARLY_CALLBACK_US = 1000; |
| 5496 | private static final long TIME_ADJUSTMENT_RATE = 2; /* meaning 1/2 */ |
| 5497 | private long mLastTimeUs = 0; |
| 5498 | private MediaPlayer mPlayer; |
| 5499 | private boolean mPaused = true; |
| 5500 | private boolean mStopped = true; |
Chong Zhang | 1e6be72 | 2015-05-06 13:14:59 -0700 | [diff] [blame] | 5501 | private boolean mBuffering; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5502 | private long mLastReportedTime; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5503 | // since we are expecting only a handful listeners per stream, there is |
| 5504 | // no need for log(N) search performance |
| 5505 | private MediaTimeProvider.OnMediaTimeListener mListeners[]; |
| 5506 | private long mTimes[]; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5507 | private Handler mEventHandler; |
| 5508 | private boolean mRefresh = false; |
| 5509 | private boolean mPausing = false; |
Lajos Molnar | d58b122 | 2013-09-24 18:04:23 -0700 | [diff] [blame] | 5510 | private boolean mSeeking = false; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5511 | private static final int NOTIFY = 1; |
| 5512 | private static final int NOTIFY_TIME = 0; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5513 | private static final int NOTIFY_STOP = 2; |
| 5514 | private static final int NOTIFY_SEEK = 3; |
Robert Shih | ed78114 | 2016-05-26 14:02:51 -0700 | [diff] [blame] | 5515 | private static final int NOTIFY_TRACK_DATA = 4; |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 5516 | private HandlerThread mHandlerThread; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5517 | |
| 5518 | /** @hide */ |
| 5519 | public boolean DEBUG = false; |
| 5520 | |
| 5521 | public TimeProvider(MediaPlayer mp) { |
| 5522 | mPlayer = mp; |
| 5523 | try { |
| 5524 | getCurrentTimeUs(true, false); |
| 5525 | } catch (IllegalStateException e) { |
| 5526 | // we assume starting position |
| 5527 | mRefresh = true; |
| 5528 | } |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 5529 | |
| 5530 | Looper looper; |
| 5531 | if ((looper = Looper.myLooper()) == null && |
| 5532 | (looper = Looper.getMainLooper()) == null) { |
| 5533 | // Create our own looper here in case MP was created without one |
| 5534 | mHandlerThread = new HandlerThread("MediaPlayerMTPEventThread", |
| 5535 | Process.THREAD_PRIORITY_FOREGROUND); |
| 5536 | mHandlerThread.start(); |
| 5537 | looper = mHandlerThread.getLooper(); |
| 5538 | } |
| 5539 | mEventHandler = new EventHandler(looper); |
| 5540 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5541 | mListeners = new MediaTimeProvider.OnMediaTimeListener[0]; |
| 5542 | mTimes = new long[0]; |
| 5543 | mLastTimeUs = 0; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5544 | } |
| 5545 | |
| 5546 | private void scheduleNotification(int type, long delayUs) { |
Lajos Molnar | d58b122 | 2013-09-24 18:04:23 -0700 | [diff] [blame] | 5547 | // ignore time notifications until seek is handled |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5548 | if (mSeeking && type == NOTIFY_TIME) { |
Lajos Molnar | d58b122 | 2013-09-24 18:04:23 -0700 | [diff] [blame] | 5549 | return; |
| 5550 | } |
| 5551 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5552 | if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs); |
| 5553 | mEventHandler.removeMessages(NOTIFY); |
| 5554 | Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0); |
| 5555 | mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000)); |
| 5556 | } |
| 5557 | |
| 5558 | /** @hide */ |
| 5559 | public void close() { |
| 5560 | mEventHandler.removeMessages(NOTIFY); |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 5561 | if (mHandlerThread != null) { |
| 5562 | mHandlerThread.quitSafely(); |
| 5563 | mHandlerThread = null; |
| 5564 | } |
| 5565 | } |
| 5566 | |
| 5567 | /** @hide */ |
| 5568 | protected void finalize() { |
| 5569 | if (mHandlerThread != null) { |
| 5570 | mHandlerThread.quitSafely(); |
| 5571 | } |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5572 | } |
| 5573 | |
| 5574 | /** @hide */ |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5575 | public void onNotifyTime() { |
| 5576 | synchronized (this) { |
| 5577 | if (DEBUG) Log.d(TAG, "onNotifyTime: "); |
| 5578 | scheduleNotification(NOTIFY_TIME, 0 /* delay */); |
| 5579 | } |
| 5580 | } |
| 5581 | |
| 5582 | /** @hide */ |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5583 | public void onPaused(boolean paused) { |
| 5584 | synchronized(this) { |
| 5585 | if (DEBUG) Log.d(TAG, "onPaused: " + paused); |
| 5586 | if (mStopped) { // handle as seek if we were stopped |
Lajos Molnar | 4de8e7c | 2013-10-28 07:16:41 -0700 | [diff] [blame] | 5587 | mStopped = false; |
| 5588 | mSeeking = true; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5589 | scheduleNotification(NOTIFY_SEEK, 0 /* delay */); |
| 5590 | } else { |
| 5591 | mPausing = paused; // special handling if player disappeared |
Lajos Molnar | 4de8e7c | 2013-10-28 07:16:41 -0700 | [diff] [blame] | 5592 | mSeeking = false; |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5593 | scheduleNotification(NOTIFY_TIME, 0 /* delay */); |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5594 | } |
| 5595 | } |
| 5596 | } |
| 5597 | |
| 5598 | /** @hide */ |
Chong Zhang | 1e6be72 | 2015-05-06 13:14:59 -0700 | [diff] [blame] | 5599 | public void onBuffering(boolean buffering) { |
| 5600 | synchronized (this) { |
| 5601 | if (DEBUG) Log.d(TAG, "onBuffering: " + buffering); |
| 5602 | mBuffering = buffering; |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5603 | scheduleNotification(NOTIFY_TIME, 0 /* delay */); |
Chong Zhang | 1e6be72 | 2015-05-06 13:14:59 -0700 | [diff] [blame] | 5604 | } |
| 5605 | } |
| 5606 | |
| 5607 | /** @hide */ |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5608 | public void onStopped() { |
| 5609 | synchronized(this) { |
| 5610 | if (DEBUG) Log.d(TAG, "onStopped"); |
| 5611 | mPaused = true; |
Lajos Molnar | 4de8e7c | 2013-10-28 07:16:41 -0700 | [diff] [blame] | 5612 | mStopped = true; |
| 5613 | mSeeking = false; |
Chong Zhang | 1e6be72 | 2015-05-06 13:14:59 -0700 | [diff] [blame] | 5614 | mBuffering = false; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5615 | scheduleNotification(NOTIFY_STOP, 0 /* delay */); |
| 5616 | } |
| 5617 | } |
| 5618 | |
| 5619 | /** @hide */ |
| 5620 | @Override |
| 5621 | public void onSeekComplete(MediaPlayer mp) { |
| 5622 | synchronized(this) { |
Lajos Molnar | 4de8e7c | 2013-10-28 07:16:41 -0700 | [diff] [blame] | 5623 | mStopped = false; |
| 5624 | mSeeking = true; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5625 | scheduleNotification(NOTIFY_SEEK, 0 /* delay */); |
| 5626 | } |
| 5627 | } |
| 5628 | |
| 5629 | /** @hide */ |
| 5630 | public void onNewPlayer() { |
| 5631 | if (mRefresh) { |
| 5632 | synchronized(this) { |
Lajos Molnar | 4de8e7c | 2013-10-28 07:16:41 -0700 | [diff] [blame] | 5633 | mStopped = false; |
| 5634 | mSeeking = true; |
Chong Zhang | 1e6be72 | 2015-05-06 13:14:59 -0700 | [diff] [blame] | 5635 | mBuffering = false; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5636 | scheduleNotification(NOTIFY_SEEK, 0 /* delay */); |
| 5637 | } |
| 5638 | } |
| 5639 | } |
| 5640 | |
| 5641 | private synchronized void notifySeek() { |
Lajos Molnar | d58b122 | 2013-09-24 18:04:23 -0700 | [diff] [blame] | 5642 | mSeeking = false; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5643 | try { |
| 5644 | long timeUs = getCurrentTimeUs(true, false); |
| 5645 | if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs); |
| 5646 | |
| 5647 | for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { |
| 5648 | if (listener == null) { |
| 5649 | break; |
| 5650 | } |
| 5651 | listener.onSeek(timeUs); |
| 5652 | } |
| 5653 | } catch (IllegalStateException e) { |
| 5654 | // we should not be there, but at least signal pause |
| 5655 | if (DEBUG) Log.d(TAG, "onSeekComplete but no player"); |
| 5656 | mPausing = true; // special handling if player disappeared |
| 5657 | notifyTimedEvent(false /* refreshTime */); |
| 5658 | } |
| 5659 | } |
| 5660 | |
Robert Shih | ed78114 | 2016-05-26 14:02:51 -0700 | [diff] [blame] | 5661 | private synchronized void notifyTrackData(Pair<SubtitleTrack, byte[]> trackData) { |
| 5662 | SubtitleTrack track = trackData.first; |
| 5663 | byte[] data = trackData.second; |
| 5664 | track.onData(data, true /* eos */, ~0 /* runID: keep forever */); |
| 5665 | } |
| 5666 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5667 | private synchronized void notifyStop() { |
| 5668 | for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { |
| 5669 | if (listener == null) { |
| 5670 | break; |
| 5671 | } |
| 5672 | listener.onStop(); |
| 5673 | } |
| 5674 | } |
| 5675 | |
| 5676 | private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) { |
| 5677 | int i = 0; |
| 5678 | for (; i < mListeners.length; i++) { |
| 5679 | if (mListeners[i] == listener || mListeners[i] == null) { |
| 5680 | break; |
| 5681 | } |
| 5682 | } |
| 5683 | |
| 5684 | // new listener |
| 5685 | if (i >= mListeners.length) { |
| 5686 | MediaTimeProvider.OnMediaTimeListener[] newListeners = |
| 5687 | new MediaTimeProvider.OnMediaTimeListener[i + 1]; |
| 5688 | long[] newTimes = new long[i + 1]; |
| 5689 | System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length); |
| 5690 | System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length); |
| 5691 | mListeners = newListeners; |
| 5692 | mTimes = newTimes; |
| 5693 | } |
| 5694 | |
| 5695 | if (mListeners[i] == null) { |
| 5696 | mListeners[i] = listener; |
| 5697 | mTimes[i] = MediaTimeProvider.NO_TIME; |
| 5698 | } |
| 5699 | return i; |
| 5700 | } |
| 5701 | |
| 5702 | public void notifyAt( |
| 5703 | long timeUs, MediaTimeProvider.OnMediaTimeListener listener) { |
| 5704 | synchronized(this) { |
| 5705 | if (DEBUG) Log.d(TAG, "notifyAt " + timeUs); |
| 5706 | mTimes[registerListener(listener)] = timeUs; |
| 5707 | scheduleNotification(NOTIFY_TIME, 0 /* delay */); |
| 5708 | } |
| 5709 | } |
| 5710 | |
| 5711 | public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) { |
| 5712 | synchronized(this) { |
| 5713 | if (DEBUG) Log.d(TAG, "scheduleUpdate"); |
| 5714 | int i = registerListener(listener); |
| 5715 | |
Robert Shih | 8c4d53c | 2014-09-05 13:33:31 -0700 | [diff] [blame] | 5716 | if (!mStopped) { |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5717 | mTimes[i] = 0; |
| 5718 | scheduleNotification(NOTIFY_TIME, 0 /* delay */); |
| 5719 | } |
| 5720 | } |
| 5721 | } |
| 5722 | |
| 5723 | public void cancelNotifications( |
| 5724 | MediaTimeProvider.OnMediaTimeListener listener) { |
| 5725 | synchronized(this) { |
| 5726 | int i = 0; |
| 5727 | for (; i < mListeners.length; i++) { |
| 5728 | if (mListeners[i] == listener) { |
| 5729 | System.arraycopy(mListeners, i + 1, |
| 5730 | mListeners, i, mListeners.length - i - 1); |
| 5731 | System.arraycopy(mTimes, i + 1, |
| 5732 | mTimes, i, mTimes.length - i - 1); |
| 5733 | mListeners[mListeners.length - 1] = null; |
| 5734 | mTimes[mTimes.length - 1] = NO_TIME; |
| 5735 | break; |
| 5736 | } else if (mListeners[i] == null) { |
| 5737 | break; |
| 5738 | } |
| 5739 | } |
| 5740 | |
| 5741 | scheduleNotification(NOTIFY_TIME, 0 /* delay */); |
| 5742 | } |
| 5743 | } |
| 5744 | |
| 5745 | private synchronized void notifyTimedEvent(boolean refreshTime) { |
| 5746 | // figure out next callback |
| 5747 | long nowUs; |
| 5748 | try { |
| 5749 | nowUs = getCurrentTimeUs(refreshTime, true); |
| 5750 | } catch (IllegalStateException e) { |
| 5751 | // assume we paused until new player arrives |
| 5752 | mRefresh = true; |
| 5753 | mPausing = true; // this ensures that call succeeds |
| 5754 | nowUs = getCurrentTimeUs(refreshTime, true); |
| 5755 | } |
| 5756 | long nextTimeUs = nowUs; |
| 5757 | |
Lajos Molnar | d58b122 | 2013-09-24 18:04:23 -0700 | [diff] [blame] | 5758 | if (mSeeking) { |
| 5759 | // skip timed-event notifications until seek is complete |
| 5760 | return; |
| 5761 | } |
| 5762 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5763 | if (DEBUG) { |
| 5764 | StringBuilder sb = new StringBuilder(); |
| 5765 | sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ") |
| 5766 | .append(nowUs).append(") from {"); |
| 5767 | boolean first = true; |
| 5768 | for (long time: mTimes) { |
| 5769 | if (time == NO_TIME) { |
| 5770 | continue; |
| 5771 | } |
| 5772 | if (!first) sb.append(", "); |
| 5773 | sb.append(time); |
| 5774 | first = false; |
| 5775 | } |
| 5776 | sb.append("}"); |
| 5777 | Log.d(TAG, sb.toString()); |
| 5778 | } |
| 5779 | |
| 5780 | Vector<MediaTimeProvider.OnMediaTimeListener> activatedListeners = |
| 5781 | new Vector<MediaTimeProvider.OnMediaTimeListener>(); |
| 5782 | for (int ix = 0; ix < mTimes.length; ix++) { |
| 5783 | if (mListeners[ix] == null) { |
| 5784 | break; |
| 5785 | } |
| 5786 | if (mTimes[ix] <= NO_TIME) { |
| 5787 | // ignore, unless we were stopped |
| 5788 | } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) { |
| 5789 | activatedListeners.add(mListeners[ix]); |
| 5790 | if (DEBUG) Log.d(TAG, "removed"); |
| 5791 | mTimes[ix] = NO_TIME; |
| 5792 | } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) { |
| 5793 | nextTimeUs = mTimes[ix]; |
| 5794 | } |
| 5795 | } |
| 5796 | |
| 5797 | if (nextTimeUs > nowUs && !mPaused) { |
| 5798 | // schedule callback at nextTimeUs |
| 5799 | if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs); |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5800 | mPlayer.notifyAt(nextTimeUs); |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5801 | } else { |
| 5802 | mEventHandler.removeMessages(NOTIFY); |
| 5803 | // no more callbacks |
| 5804 | } |
| 5805 | |
| 5806 | for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) { |
| 5807 | listener.onTimedEvent(nowUs); |
| 5808 | } |
| 5809 | } |
| 5810 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5811 | public long getCurrentTimeUs(boolean refreshTime, boolean monotonic) |
| 5812 | throws IllegalStateException { |
| 5813 | synchronized (this) { |
| 5814 | // we always refresh the time when the paused-state changes, because |
| 5815 | // we expect to have received the pause-change event delayed. |
| 5816 | if (mPaused && !refreshTime) { |
| 5817 | return mLastReportedTime; |
| 5818 | } |
| 5819 | |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5820 | try { |
| 5821 | mLastTimeUs = mPlayer.getCurrentPosition() * 1000L; |
| 5822 | mPaused = !mPlayer.isPlaying() || mBuffering; |
| 5823 | if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); |
| 5824 | } catch (IllegalStateException e) { |
| 5825 | if (mPausing) { |
| 5826 | // if we were pausing, get last estimated timestamp |
| 5827 | mPausing = false; |
| 5828 | if (!monotonic || mLastReportedTime < mLastTimeUs) { |
| 5829 | mLastReportedTime = mLastTimeUs; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5830 | } |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5831 | mPaused = true; |
| 5832 | if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime); |
| 5833 | return mLastReportedTime; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5834 | } |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5835 | // TODO get time when prepared |
| 5836 | throw e; |
| 5837 | } |
| 5838 | if (monotonic && mLastTimeUs < mLastReportedTime) { |
| 5839 | /* have to adjust time */ |
| 5840 | if (mLastReportedTime - mLastTimeUs > 1000000) { |
| 5841 | // schedule seeked event if time jumped significantly |
| 5842 | // TODO: do this properly by introducing an exception |
| 5843 | mStopped = false; |
| 5844 | mSeeking = true; |
| 5845 | scheduleNotification(NOTIFY_SEEK, 0 /* delay */); |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5846 | } |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5847 | } else { |
| 5848 | mLastReportedTime = mLastTimeUs; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5849 | } |
| 5850 | |
Wei Jia | c02f09d | 2017-09-13 18:19:48 -0700 | [diff] [blame] | 5851 | return mLastReportedTime; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5852 | } |
| 5853 | } |
| 5854 | |
| 5855 | private class EventHandler extends Handler { |
Lajos Molnar | a67a443 | 2013-09-06 06:30:35 -0700 | [diff] [blame] | 5856 | public EventHandler(Looper looper) { |
| 5857 | super(looper); |
| 5858 | } |
| 5859 | |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5860 | @Override |
| 5861 | public void handleMessage(Message msg) { |
| 5862 | if (msg.what == NOTIFY) { |
| 5863 | switch (msg.arg1) { |
| 5864 | case NOTIFY_TIME: |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5865 | notifyTimedEvent(true /* refreshTime */); |
| 5866 | break; |
| 5867 | case NOTIFY_STOP: |
| 5868 | notifyStop(); |
| 5869 | break; |
| 5870 | case NOTIFY_SEEK: |
| 5871 | notifySeek(); |
| 5872 | break; |
Robert Shih | ed78114 | 2016-05-26 14:02:51 -0700 | [diff] [blame] | 5873 | case NOTIFY_TRACK_DATA: |
| 5874 | notifyTrackData((Pair<SubtitleTrack, byte[]>)msg.obj); |
| 5875 | break; |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5876 | } |
| 5877 | } |
| 5878 | } |
| 5879 | } |
Lajos Molnar | 3d99856 | 2013-08-15 17:05:05 -0700 | [diff] [blame] | 5880 | } |
Ray Essick | 10353e3 | 2017-04-14 10:22:55 -0700 | [diff] [blame] | 5881 | |
| 5882 | public final static class MetricsConstants |
| 5883 | { |
| 5884 | private MetricsConstants() {} |
| 5885 | |
| 5886 | /** |
| 5887 | * Key to extract the MIME type of the video track |
| 5888 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5889 | * The value is a String. |
| 5890 | */ |
| 5891 | public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime"; |
| 5892 | |
| 5893 | /** |
| 5894 | * Key to extract the codec being used to decode the video track |
| 5895 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5896 | * The value is a String. |
| 5897 | */ |
| 5898 | public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec"; |
| 5899 | |
| 5900 | /** |
| 5901 | * Key to extract the width (in pixels) of the video track |
| 5902 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5903 | * The value is an integer. |
| 5904 | */ |
| 5905 | public static final String WIDTH = "android.media.mediaplayer.width"; |
| 5906 | |
| 5907 | /** |
| 5908 | * Key to extract the height (in pixels) of the video track |
| 5909 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5910 | * The value is an integer. |
| 5911 | */ |
| 5912 | public static final String HEIGHT = "android.media.mediaplayer.height"; |
| 5913 | |
| 5914 | /** |
| 5915 | * Key to extract the count of video frames played |
| 5916 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5917 | * The value is an integer. |
| 5918 | */ |
| 5919 | public static final String FRAMES = "android.media.mediaplayer.frames"; |
| 5920 | |
| 5921 | /** |
| 5922 | * Key to extract the count of video frames dropped |
| 5923 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5924 | * The value is an integer. |
| 5925 | */ |
| 5926 | public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped"; |
| 5927 | |
| 5928 | /** |
| 5929 | * Key to extract the MIME type of the audio track |
| 5930 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5931 | * The value is a String. |
| 5932 | */ |
| 5933 | public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime"; |
| 5934 | |
| 5935 | /** |
| 5936 | * Key to extract the codec being used to decode the audio track |
| 5937 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5938 | * The value is a String. |
| 5939 | */ |
| 5940 | public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; |
| 5941 | |
| 5942 | /** |
| 5943 | * Key to extract the duration (in milliseconds) of the |
| 5944 | * media being played |
| 5945 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5946 | * The value is a long. |
| 5947 | */ |
| 5948 | public static final String DURATION = "android.media.mediaplayer.durationMs"; |
| 5949 | |
| 5950 | /** |
| 5951 | * Key to extract the playing time (in milliseconds) of the |
| 5952 | * media being played |
| 5953 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5954 | * The value is a long. |
| 5955 | */ |
| 5956 | public static final String PLAYING = "android.media.mediaplayer.playingMs"; |
| 5957 | |
| 5958 | /** |
| 5959 | * Key to extract the count of errors encountered while |
| 5960 | * playing the media |
| 5961 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5962 | * The value is an integer. |
| 5963 | */ |
| 5964 | public static final String ERRORS = "android.media.mediaplayer.err"; |
| 5965 | |
| 5966 | /** |
| 5967 | * Key to extract an (optional) error code detected while |
| 5968 | * playing the media |
| 5969 | * from the {@link MediaPlayer#getMetrics} return value. |
| 5970 | * The value is an integer. |
| 5971 | */ |
| 5972 | public static final String ERROR_CODE = "android.media.mediaplayer.errcode"; |
| 5973 | |
| 5974 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 5975 | } |