MediaPlayer.java revision f9d3bb773585959dcfaa506ea0d32eb32fe8ad83
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 17package android.media; 18 19import android.annotation.IntDef; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.app.ActivityThread; 23import android.content.ContentProvider; 24import android.content.ContentResolver; 25import android.content.Context; 26import android.content.res.AssetFileDescriptor; 27import android.net.Uri; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.HandlerThread; 31import android.os.IBinder; 32import android.os.Looper; 33import android.os.Message; 34import android.os.Parcel; 35import android.os.Parcelable; 36import android.os.PersistableBundle; 37import android.os.Process; 38import android.os.PowerManager; 39import android.os.SystemProperties; 40import android.provider.Settings; 41import android.system.ErrnoException; 42import android.system.OsConstants; 43import android.util.Log; 44import android.util.Pair; 45import android.view.Surface; 46import android.view.SurfaceHolder; 47import android.widget.VideoView; 48import android.graphics.SurfaceTexture; 49import android.media.AudioManager; 50import android.media.MediaDrm; 51import android.media.MediaFormat; 52import android.media.MediaTimeProvider; 53import android.media.PlaybackParams; 54import android.media.SubtitleController; 55import android.media.SubtitleController.Anchor; 56import android.media.SubtitleData; 57import android.media.SubtitleTrack.RenderingWidget; 58import android.media.SyncParams; 59 60import com.android.internal.util.Preconditions; 61 62import libcore.io.IoBridge; 63import libcore.io.Libcore; 64import libcore.io.Streams; 65 66import java.io.ByteArrayOutputStream; 67import java.io.File; 68import java.io.FileDescriptor; 69import java.io.FileInputStream; 70import java.io.IOException; 71import java.io.InputStream; 72import java.lang.Runnable; 73import java.lang.annotation.Retention; 74import java.lang.annotation.RetentionPolicy; 75import java.lang.ref.WeakReference; 76import java.net.HttpCookie; 77import java.net.HttpURLConnection; 78import java.net.InetSocketAddress; 79import java.net.URL; 80import java.nio.ByteOrder; 81import java.util.Arrays; 82import java.util.BitSet; 83import java.util.HashMap; 84import java.util.List; 85import java.util.Map; 86import java.util.Scanner; 87import java.util.Set; 88import java.util.UUID; 89import java.util.Vector; 90 91 92/** 93 * MediaPlayer class can be used to control playback 94 * of audio/video files and streams. An example on how to use the methods in 95 * this class can be found in {@link android.widget.VideoView}. 96 * 97 * <p>Topics covered here are: 98 * <ol> 99 * <li><a href="#StateDiagram">State Diagram</a> 100 * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a> 101 * <li><a href="#Permissions">Permissions</a> 102 * <li><a href="#Callbacks">Register informational and error callbacks</a> 103 * </ol> 104 * 105 * <div class="special reference"> 106 * <h3>Developer Guides</h3> 107 * <p>For more information about how to use MediaPlayer, read the 108 * <a href="{@docRoot}guide/topics/media/mediaplayer.html">Media Playback</a> developer guide.</p> 109 * </div> 110 * 111 * <a name="StateDiagram"></a> 112 * <h3>State Diagram</h3> 113 * 114 * <p>Playback control of audio/video files and streams is managed as a state 115 * machine. The following diagram shows the life cycle and the states of a 116 * MediaPlayer object driven by the supported playback control operations. 117 * The ovals represent the states a MediaPlayer object may reside 118 * in. The arcs represent the playback control operations that drive the object 119 * state transition. There are two types of arcs. The arcs with a single arrow 120 * head represent synchronous method calls, while those with 121 * a double arrow head represent asynchronous method calls.</p> 122 * 123 * <p><img src="../../../images/mediaplayer_state_diagram.gif" 124 * alt="MediaPlayer State diagram" 125 * border="0" /></p> 126 * 127 * <p>From this state diagram, one can see that a MediaPlayer object has the 128 * following states:</p> 129 * <ul> 130 * <li>When a MediaPlayer object is just created using <code>new</code> or 131 * after {@link #reset()} is called, it is in the <em>Idle</em> state; and after 132 * {@link #release()} is called, it is in the <em>End</em> state. Between these 133 * two states is the life cycle of the MediaPlayer object. 134 * <ul> 135 * <li>There is a subtle but important difference between a newly constructed 136 * MediaPlayer object and the MediaPlayer object after {@link #reset()} 137 * is called. It is a programming error to invoke methods such 138 * as {@link #getCurrentPosition()}, 139 * {@link #getDuration()}, {@link #getVideoHeight()}, 140 * {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)}, 141 * {@link #setLooping(boolean)}, 142 * {@link #setVolume(float, float)}, {@link #pause()}, {@link #start()}, 143 * {@link #stop()}, {@link #seekTo(int, int)}, {@link #prepare()} or 144 * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these 145 * methods is called right after a MediaPlayer object is constructed, 146 * the user supplied callback method OnErrorListener.onError() won't be 147 * called by the internal player engine and the object state remains 148 * unchanged; but if these methods are called right after {@link #reset()}, 149 * the user supplied callback method OnErrorListener.onError() will be 150 * invoked by the internal player engine and the object will be 151 * transfered to the <em>Error</em> state. </li> 152 * <li>It is also recommended that once 153 * a MediaPlayer object is no longer being used, call {@link #release()} immediately 154 * so that resources used by the internal player engine associated with the 155 * MediaPlayer object can be released immediately. Resource may include 156 * singleton resources such as hardware acceleration components and 157 * failure to call {@link #release()} may cause subsequent instances of 158 * MediaPlayer objects to fallback to software implementations or fail 159 * altogether. Once the MediaPlayer 160 * object is in the <em>End</em> state, it can no longer be used and 161 * there is no way to bring it back to any other state. </li> 162 * <li>Furthermore, 163 * the MediaPlayer objects created using <code>new</code> is in the 164 * <em>Idle</em> state, while those created with one 165 * of the overloaded convenient <code>create</code> methods are <em>NOT</em> 166 * in the <em>Idle</em> state. In fact, the objects are in the <em>Prepared</em> 167 * state if the creation using <code>create</code> method is successful. 168 * </li> 169 * </ul> 170 * </li> 171 * <li>In general, some playback control operation may fail due to various 172 * reasons, such as unsupported audio/video format, poorly interleaved 173 * audio/video, resolution too high, streaming timeout, and the like. 174 * Thus, error reporting and recovery is an important concern under 175 * these circumstances. Sometimes, due to programming errors, invoking a playback 176 * control operation in an invalid state may also occur. Under all these 177 * error conditions, the internal player engine invokes a user supplied 178 * OnErrorListener.onError() method if an OnErrorListener has been 179 * registered beforehand via 180 * {@link #setOnErrorListener(android.media.MediaPlayer.OnErrorListener)}. 181 * <ul> 182 * <li>It is important to note that once an error occurs, the 183 * MediaPlayer object enters the <em>Error</em> state (except as noted 184 * above), even if an error listener has not been registered by the application.</li> 185 * <li>In order to reuse a MediaPlayer object that is in the <em> 186 * Error</em> state and recover from the error, 187 * {@link #reset()} can be called to restore the object to its <em>Idle</em> 188 * state.</li> 189 * <li>It is good programming practice to have your application 190 * register a OnErrorListener to look out for error notifications from 191 * the internal player engine.</li> 192 * <li>IllegalStateException is 193 * thrown to prevent programming errors such as calling {@link #prepare()}, 194 * {@link #prepareAsync()}, or one of the overloaded <code>setDataSource 195 * </code> methods in an invalid state. </li> 196 * </ul> 197 * </li> 198 * <li>Calling 199 * {@link #setDataSource(FileDescriptor)}, or 200 * {@link #setDataSource(String)}, or 201 * {@link #setDataSource(Context, Uri)}, or 202 * {@link #setDataSource(FileDescriptor, long, long)}, or 203 * {@link #setDataSource(MediaDataSource)} transfers a 204 * MediaPlayer object in the <em>Idle</em> state to the 205 * <em>Initialized</em> state. 206 * <ul> 207 * <li>An IllegalStateException is thrown if 208 * setDataSource() is called in any other state.</li> 209 * <li>It is good programming 210 * practice to always look out for <code>IllegalArgumentException</code> 211 * and <code>IOException</code> that may be thrown from the overloaded 212 * <code>setDataSource</code> methods.</li> 213 * </ul> 214 * </li> 215 * <li>A MediaPlayer object must first enter the <em>Prepared</em> state 216 * before playback can be started. 217 * <ul> 218 * <li>There are two ways (synchronous vs. 219 * asynchronous) that the <em>Prepared</em> state can be reached: 220 * either a call to {@link #prepare()} (synchronous) which 221 * transfers the object to the <em>Prepared</em> state once the method call 222 * returns, or a call to {@link #prepareAsync()} (asynchronous) which 223 * first transfers the object to the <em>Preparing</em> state after the 224 * call returns (which occurs almost right way) while the internal 225 * player engine continues working on the rest of preparation work 226 * until the preparation work completes. When the preparation completes or when {@link #prepare()} call returns, 227 * the internal player engine then calls a user supplied callback method, 228 * onPrepared() of the OnPreparedListener interface, if an 229 * OnPreparedListener is registered beforehand via {@link 230 * #setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)}.</li> 231 * <li>It is important to note that 232 * the <em>Preparing</em> state is a transient state, and the behavior 233 * of calling any method with side effect while a MediaPlayer object is 234 * in the <em>Preparing</em> state is undefined.</li> 235 * <li>An IllegalStateException is 236 * thrown if {@link #prepare()} or {@link #prepareAsync()} is called in 237 * any other state.</li> 238 * <li>While in the <em>Prepared</em> state, properties 239 * such as audio/sound volume, screenOnWhilePlaying, looping can be 240 * adjusted by invoking the corresponding set methods.</li> 241 * </ul> 242 * </li> 243 * <li>To start the playback, {@link #start()} must be called. After 244 * {@link #start()} returns successfully, the MediaPlayer object is in the 245 * <em>Started</em> state. {@link #isPlaying()} can be called to test 246 * whether the MediaPlayer object is in the <em>Started</em> state. 247 * <ul> 248 * <li>While in the <em>Started</em> state, the internal player engine calls 249 * a user supplied OnBufferingUpdateListener.onBufferingUpdate() callback 250 * method if a OnBufferingUpdateListener has been registered beforehand 251 * via {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}. 252 * This callback allows applications to keep track of the buffering status 253 * while streaming audio/video.</li> 254 * <li>Calling {@link #start()} has not effect 255 * on a MediaPlayer object that is already in the <em>Started</em> state.</li> 256 * </ul> 257 * </li> 258 * <li>Playback can be paused and stopped, and the current playback position 259 * can be adjusted. Playback can be paused via {@link #pause()}. When the call to 260 * {@link #pause()} returns, the MediaPlayer object enters the 261 * <em>Paused</em> state. Note that the transition from the <em>Started</em> 262 * state to the <em>Paused</em> state and vice versa happens 263 * asynchronously in the player engine. It may take some time before 264 * the state is updated in calls to {@link #isPlaying()}, and it can be 265 * a number of seconds in the case of streamed content. 266 * <ul> 267 * <li>Calling {@link #start()} to resume playback for a paused 268 * MediaPlayer object, and the resumed playback 269 * position is the same as where it was paused. When the call to 270 * {@link #start()} returns, the paused MediaPlayer object goes back to 271 * the <em>Started</em> state.</li> 272 * <li>Calling {@link #pause()} has no effect on 273 * a MediaPlayer object that is already in the <em>Paused</em> state.</li> 274 * </ul> 275 * </li> 276 * <li>Calling {@link #stop()} stops playback and causes a 277 * MediaPlayer in the <em>Started</em>, <em>Paused</em>, <em>Prepared 278 * </em> or <em>PlaybackCompleted</em> state to enter the 279 * <em>Stopped</em> state. 280 * <ul> 281 * <li>Once in the <em>Stopped</em> state, playback cannot be started 282 * until {@link #prepare()} or {@link #prepareAsync()} are called to set 283 * the MediaPlayer object to the <em>Prepared</em> state again.</li> 284 * <li>Calling {@link #stop()} has no effect on a MediaPlayer 285 * object that is already in the <em>Stopped</em> state.</li> 286 * </ul> 287 * </li> 288 * <li>The playback position can be adjusted with a call to 289 * {@link #seekTo(int, int)}. 290 * <ul> 291 * <li>Although the asynchronuous {@link #seekTo(int, int)} 292 * call returns right away, the actual seek operation may take a while to 293 * finish, especially for audio/video being streamed. When the actual 294 * seek operation completes, the internal player engine calls a user 295 * supplied OnSeekComplete.onSeekComplete() if an OnSeekCompleteListener 296 * has been registered beforehand via 297 * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}.</li> 298 * <li>Please 299 * note that {@link #seekTo(int, int)} can also be called in the other states, 300 * such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted 301 * </em> state. When {@link #seekTo(int, int)} is called in those states, 302 * one video frame will be displayed if the stream has video and the requested 303 * position is valid. 304 * </li> 305 * <li>Furthermore, the actual current playback position 306 * can be retrieved with a call to {@link #getCurrentPosition()}, which 307 * is helpful for applications such as a Music player that need to keep 308 * track of the playback progress.</li> 309 * </ul> 310 * </li> 311 * <li>When the playback reaches the end of stream, the playback completes. 312 * <ul> 313 * <li>If the looping mode was being set to <var>true</var>with 314 * {@link #setLooping(boolean)}, the MediaPlayer object shall remain in 315 * the <em>Started</em> state.</li> 316 * <li>If the looping mode was set to <var>false 317 * </var>, the player engine calls a user supplied callback method, 318 * OnCompletion.onCompletion(), if a OnCompletionListener is registered 319 * beforehand via {@link #setOnCompletionListener(OnCompletionListener)}. 320 * The invoke of the callback signals that the object is now in the <em> 321 * PlaybackCompleted</em> state.</li> 322 * <li>While in the <em>PlaybackCompleted</em> 323 * state, calling {@link #start()} can restart the playback from the 324 * beginning of the audio/video source.</li> 325 * </ul> 326 * 327 * 328 * <a name="Valid_and_Invalid_States"></a> 329 * <h3>Valid and invalid states</h3> 330 * 331 * <table border="0" cellspacing="0" cellpadding="0"> 332 * <tr><td>Method Name </p></td> 333 * <td>Valid Sates </p></td> 334 * <td>Invalid States </p></td> 335 * <td>Comments </p></td></tr> 336 * <tr><td>attachAuxEffect </p></td> 337 * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> 338 * <td>{Idle, Error} </p></td> 339 * <td>This method must be called after setDataSource. 340 * Calling it does not change the object state. </p></td></tr> 341 * <tr><td>getAudioSessionId </p></td> 342 * <td>any </p></td> 343 * <td>{} </p></td> 344 * <td>This method can be called in any state and calling it does not change 345 * the object state. </p></td></tr> 346 * <tr><td>getCurrentPosition </p></td> 347 * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 348 * PlaybackCompleted} </p></td> 349 * <td>{Error}</p></td> 350 * <td>Successful invoke of this method in a valid state does not change the 351 * state. Calling this method in an invalid state transfers the object 352 * to the <em>Error</em> state. </p></td></tr> 353 * <tr><td>getDuration </p></td> 354 * <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> 355 * <td>{Idle, Initialized, Error} </p></td> 356 * <td>Successful invoke of this method in a valid state does not change the 357 * state. Calling this method in an invalid state transfers the object 358 * to the <em>Error</em> state. </p></td></tr> 359 * <tr><td>getVideoHeight </p></td> 360 * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 361 * PlaybackCompleted}</p></td> 362 * <td>{Error}</p></td> 363 * <td>Successful invoke of this method in a valid state does not change the 364 * state. Calling this method in an invalid state transfers the object 365 * to the <em>Error</em> state. </p></td></tr> 366 * <tr><td>getVideoWidth </p></td> 367 * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 368 * PlaybackCompleted}</p></td> 369 * <td>{Error}</p></td> 370 * <td>Successful invoke of this method in a valid state does not change 371 * the state. Calling this method in an invalid state transfers the 372 * object to the <em>Error</em> state. </p></td></tr> 373 * <tr><td>isPlaying </p></td> 374 * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 375 * PlaybackCompleted}</p></td> 376 * <td>{Error}</p></td> 377 * <td>Successful invoke of this method in a valid state does not change 378 * the state. Calling this method in an invalid state transfers the 379 * object to the <em>Error</em> state. </p></td></tr> 380 * <tr><td>pause </p></td> 381 * <td>{Started, Paused, PlaybackCompleted}</p></td> 382 * <td>{Idle, Initialized, Prepared, Stopped, Error}</p></td> 383 * <td>Successful invoke of this method in a valid state transfers the 384 * object to the <em>Paused</em> state. Calling this method in an 385 * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> 386 * <tr><td>prepare </p></td> 387 * <td>{Initialized, Stopped} </p></td> 388 * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td> 389 * <td>Successful invoke of this method in a valid state transfers the 390 * object to the <em>Prepared</em> state. Calling this method in an 391 * invalid state throws an IllegalStateException.</p></td></tr> 392 * <tr><td>prepareAsync </p></td> 393 * <td>{Initialized, Stopped} </p></td> 394 * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td> 395 * <td>Successful invoke of this method in a valid state transfers the 396 * object to the <em>Preparing</em> state. Calling this method in an 397 * invalid state throws an IllegalStateException.</p></td></tr> 398 * <tr><td>release </p></td> 399 * <td>any </p></td> 400 * <td>{} </p></td> 401 * <td>After {@link #release()}, the object is no longer available. </p></td></tr> 402 * <tr><td>reset </p></td> 403 * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 404 * PlaybackCompleted, Error}</p></td> 405 * <td>{}</p></td> 406 * <td>After {@link #reset()}, the object is like being just created.</p></td></tr> 407 * <tr><td>seekTo </p></td> 408 * <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td> 409 * <td>{Idle, Initialized, Stopped, Error}</p></td> 410 * <td>Successful invoke of this method in a valid state does not change 411 * the state. Calling this method in an invalid state transfers the 412 * object to the <em>Error</em> state. </p></td></tr> 413 * <tr><td>setAudioAttributes </p></td> 414 * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, 415 * PlaybackCompleted}</p></td> 416 * <td>{Error}</p></td> 417 * <td>Successful invoke of this method does not change the state. In order for the 418 * target audio attributes type to become effective, this method must be called before 419 * prepare() or prepareAsync().</p></td></tr> 420 * <tr><td>setAudioSessionId </p></td> 421 * <td>{Idle} </p></td> 422 * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, 423 * Error} </p></td> 424 * <td>This method must be called in idle state as the audio session ID must be known before 425 * calling setDataSource. Calling it does not change the object state. </p></td></tr> 426 * <tr><td>setAudioStreamType (deprecated)</p></td> 427 * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, 428 * PlaybackCompleted}</p></td> 429 * <td>{Error}</p></td> 430 * <td>Successful invoke of this method does not change the state. In order for the 431 * target audio stream type to become effective, this method must be called before 432 * prepare() or prepareAsync().</p></td></tr> 433 * <tr><td>setAuxEffectSendLevel </p></td> 434 * <td>any</p></td> 435 * <td>{} </p></td> 436 * <td>Calling this method does not change the object state. </p></td></tr> 437 * <tr><td>setDataSource </p></td> 438 * <td>{Idle} </p></td> 439 * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, 440 * Error} </p></td> 441 * <td>Successful invoke of this method in a valid state transfers the 442 * object to the <em>Initialized</em> state. Calling this method in an 443 * invalid state throws an IllegalStateException.</p></td></tr> 444 * <tr><td>setDisplay </p></td> 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> 449 * <tr><td>setSurface </p></td> 450 * <td>any </p></td> 451 * <td>{} </p></td> 452 * <td>This method can be called in any state and calling it does not change 453 * the object state. </p></td></tr> 454 * <tr><td>setVideoScalingMode </p></td> 455 * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> 456 * <td>{Idle, Error}</p></td> 457 * <td>Successful invoke of this method does not change the state.</p></td></tr> 458 * <tr><td>setLooping </p></td> 459 * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, 460 * PlaybackCompleted}</p></td> 461 * <td>{Error}</p></td> 462 * <td>Successful invoke of this method in a valid state does not change 463 * the state. Calling this method in an 464 * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> 465 * <tr><td>isLooping </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>setOnBufferingUpdateListener </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>setOnCompletionListener </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>setOnErrorListener </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>setOnPreparedListener </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> 490 * <tr><td>setOnSeekCompleteListener </p></td> 491 * <td>any </p></td> 492 * <td>{} </p></td> 493 * <td>This method can be called in any state and calling it does not change 494 * the object state. </p></td></tr> 495 * <tr><td>setPlaybackParams</p></td> 496 * <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td> 497 * <td>{Idle, Stopped} </p></td> 498 * <td>This method will change state in some cases, depending on when it's called. 499 * </p></td></tr> 500 * <tr><td>setScreenOnWhilePlaying</></td> 501 * <td>any </p></td> 502 * <td>{} </p></td> 503 * <td>This method can be called in any state and calling it does not change 504 * the object state. </p></td></tr> 505 * <tr><td>setVolume </p></td> 506 * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, 507 * PlaybackCompleted}</p></td> 508 * <td>{Error}</p></td> 509 * <td>Successful invoke of this method does not change the state. 510 * <tr><td>setWakeMode </p></td> 511 * <td>any </p></td> 512 * <td>{} </p></td> 513 * <td>This method can be called in any state and calling it does not change 514 * the object state.</p></td></tr> 515 * <tr><td>start </p></td> 516 * <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td> 517 * <td>{Idle, Initialized, Stopped, Error}</p></td> 518 * <td>Successful invoke of this method in a valid state transfers the 519 * object to the <em>Started</em> state. Calling this method in an 520 * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> 521 * <tr><td>stop </p></td> 522 * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> 523 * <td>{Idle, Initialized, Error}</p></td> 524 * <td>Successful invoke of this method in a valid state transfers the 525 * object to the <em>Stopped</em> state. Calling this method in an 526 * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> 527 * <tr><td>getTrackInfo </p></td> 528 * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> 529 * <td>{Idle, Initialized, Error}</p></td> 530 * <td>Successful invoke of this method does not change the state.</p></td></tr> 531 * <tr><td>addTimedTextSource </p></td> 532 * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> 533 * <td>{Idle, Initialized, Error}</p></td> 534 * <td>Successful invoke of this method does not change the state.</p></td></tr> 535 * <tr><td>selectTrack </p></td> 536 * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> 537 * <td>{Idle, Initialized, Error}</p></td> 538 * <td>Successful invoke of this method does not change the state.</p></td></tr> 539 * <tr><td>deselectTrack </p></td> 540 * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> 541 * <td>{Idle, Initialized, Error}</p></td> 542 * <td>Successful invoke of this method does not change the state.</p></td></tr> 543 * 544 * </table> 545 * 546 * <a name="Permissions"></a> 547 * <h3>Permissions</h3> 548 * <p>One may need to declare a corresponding WAKE_LOCK permission {@link 549 * android.R.styleable#AndroidManifestUsesPermission <uses-permission>} 550 * element. 551 * 552 * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission 553 * when used with network-based content. 554 * 555 * <a name="Callbacks"></a> 556 * <h3>Callbacks</h3> 557 * <p>Applications may want to register for informational and error 558 * events in order to be informed of some internal state update and 559 * possible runtime errors during playback or streaming. Registration for 560 * these events is done by properly setting the appropriate listeners (via calls 561 * to 562 * {@link #setOnPreparedListener(OnPreparedListener)}setOnPreparedListener, 563 * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)}setOnVideoSizeChangedListener, 564 * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}setOnSeekCompleteListener, 565 * {@link #setOnCompletionListener(OnCompletionListener)}setOnCompletionListener, 566 * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}setOnBufferingUpdateListener, 567 * {@link #setOnInfoListener(OnInfoListener)}setOnInfoListener, 568 * {@link #setOnErrorListener(OnErrorListener)}setOnErrorListener, etc). 569 * In order to receive the respective callback 570 * associated with these listeners, applications are required to create 571 * MediaPlayer objects on a thread with its own Looper running (main UI 572 * thread by default has a Looper running). 573 * 574 */ 575public class MediaPlayer extends PlayerBase 576 implements SubtitleController.Listener 577 , VolumeAutomation 578{ 579 /** 580 Constant to retrieve only the new metadata since the last 581 call. 582 // FIXME: unhide. 583 // FIXME: add link to getMetadata(boolean, boolean) 584 {@hide} 585 */ 586 public static final boolean METADATA_UPDATE_ONLY = true; 587 588 /** 589 Constant to retrieve all the metadata. 590 // FIXME: unhide. 591 // FIXME: add link to getMetadata(boolean, boolean) 592 {@hide} 593 */ 594 public static final boolean METADATA_ALL = false; 595 596 /** 597 Constant to enable the metadata filter during retrieval. 598 // FIXME: unhide. 599 // FIXME: add link to getMetadata(boolean, boolean) 600 {@hide} 601 */ 602 public static final boolean APPLY_METADATA_FILTER = true; 603 604 /** 605 Constant to disable the metadata filter during retrieval. 606 // FIXME: unhide. 607 // FIXME: add link to getMetadata(boolean, boolean) 608 {@hide} 609 */ 610 public static final boolean BYPASS_METADATA_FILTER = false; 611 612 static { 613 System.loadLibrary("media_jni"); 614 native_init(); 615 } 616 617 private final static String TAG = "MediaPlayer"; 618 // Name of the remote interface for the media player. Must be kept 619 // in sync with the 2nd parameter of the IMPLEMENT_META_INTERFACE 620 // macro invocation in IMediaPlayer.cpp 621 private final static String IMEDIA_PLAYER = "android.media.IMediaPlayer"; 622 623 private long mNativeContext; // accessed by native methods 624 private long mNativeSurfaceTexture; // accessed by native methods 625 private int mListenerContext; // accessed by native methods 626 private SurfaceHolder mSurfaceHolder; 627 private EventHandler mEventHandler; 628 private PowerManager.WakeLock mWakeLock = null; 629 private boolean mScreenOnWhilePlaying; 630 private boolean mStayAwake; 631 private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; 632 private int mUsage = -1; 633 private boolean mBypassInterruptionPolicy; 634 635 // Modular DRM 636 private UUID mDrmUUID; 637 private final Object mDrmLock = new Object(); 638 private DrmInfo mDrmInfo; 639 private MediaDrm mDrmObj; 640 private byte[] mDrmSessionId; 641 private boolean mDrmInfoResolved; 642 private boolean mActiveDrmScheme; 643 private boolean mDrmConfigAllowed; 644 private boolean mDrmProvisioningInProgress; 645 private boolean mPrepareDrmInProgress; 646 private ProvisioningThread mDrmProvisioningThread; 647 648 /** 649 * Default constructor. Consider using one of the create() methods for 650 * synchronously instantiating a MediaPlayer from a Uri or resource. 651 * <p>When done with the MediaPlayer, you should call {@link #release()}, 652 * to free the resources. If not released, too many MediaPlayer instances may 653 * result in an exception.</p> 654 */ 655 public MediaPlayer() { 656 super(new AudioAttributes.Builder().build(), 657 AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER); 658 659 Looper looper; 660 if ((looper = Looper.myLooper()) != null) { 661 mEventHandler = new EventHandler(this, looper); 662 } else if ((looper = Looper.getMainLooper()) != null) { 663 mEventHandler = new EventHandler(this, looper); 664 } else { 665 mEventHandler = null; 666 } 667 668 mTimeProvider = new TimeProvider(this); 669 mOpenSubtitleSources = new Vector<InputStream>(); 670 671 /* Native setup requires a weak reference to our object. 672 * It's easier to create it here than in C++. 673 */ 674 native_setup(new WeakReference<MediaPlayer>(this)); 675 676 baseRegisterPlayer(); 677 } 678 679 /* 680 * Update the MediaPlayer SurfaceTexture. 681 * Call after setting a new display surface. 682 */ 683 private native void _setVideoSurface(Surface surface); 684 685 /* Do not change these values (starting with INVOKE_ID) without updating 686 * their counterparts in include/media/mediaplayer.h! 687 */ 688 private static final int INVOKE_ID_GET_TRACK_INFO = 1; 689 private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2; 690 private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3; 691 private static final int INVOKE_ID_SELECT_TRACK = 4; 692 private static final int INVOKE_ID_DESELECT_TRACK = 5; 693 private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6; 694 private static final int INVOKE_ID_GET_SELECTED_TRACK = 7; 695 696 /** 697 * Create a request parcel which can be routed to the native media 698 * player using {@link #invoke(Parcel, Parcel)}. The Parcel 699 * returned has the proper InterfaceToken set. The caller should 700 * not overwrite that token, i.e it can only append data to the 701 * Parcel. 702 * 703 * @return A parcel suitable to hold a request for the native 704 * player. 705 * {@hide} 706 */ 707 public Parcel newRequest() { 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 718 * exists. On failure, a RuntimeException is thrown. 719 * 720 * @param request Parcel with the data for the extension. The 721 * caller must use {@link #newRequest()} to get one. 722 * 723 * @param reply Output parcel with the data returned by the 724 * native player. 725 * {@hide} 726 */ 727 public void invoke(Parcel request, Parcel reply) { 728 int retcode = native_invoke(request, reply); 729 reply.setDataPosition(0); 730 if (retcode != 0) { 731 throw new RuntimeException("failure code: " + retcode); 732 } 733 } 734 735 /** 736 * Sets the {@link SurfaceHolder} to use for displaying the video 737 * portion of the media. 738 * 739 * Either a surface holder or surface must be set if a display or video sink 740 * is needed. Not calling this method or {@link #setSurface(Surface)} 741 * when playing back a video will result in only the audio track being played. 742 * A null surface holder or surface will result in only the audio track being 743 * played. 744 * 745 * @param sh the SurfaceHolder to use for video display 746 * @throws IllegalStateException if the internal player engine has not been 747 * initialized or has been released. 748 */ 749 public void setDisplay(SurfaceHolder sh) { 750 mSurfaceHolder = sh; 751 Surface surface; 752 if (sh != null) { 753 surface = sh.getSurface(); 754 } else { 755 surface = null; 756 } 757 _setVideoSurface(surface); 758 updateSurfaceScreenOn(); 759 } 760 761 /** 762 * Sets the {@link Surface} to be used as the sink for the video portion of 763 * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but 764 * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a 765 * Surface will un-set any Surface or SurfaceHolder that was previously set. 766 * A null surface will result in only the audio track being played. 767 * 768 * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps 769 * returned from {@link SurfaceTexture#getTimestamp()} will have an 770 * unspecified zero point. These timestamps cannot be directly compared 771 * between different media sources, different instances of the same media 772 * source, or multiple runs of the same program. The timestamp is normally 773 * monotonically increasing and is unaffected by time-of-day adjustments, 774 * but it is reset when the position is set. 775 * 776 * @param surface The {@link Surface} to be used for the video portion of 777 * the media. 778 * @throws IllegalStateException if the internal player engine has not been 779 * initialized or has been released. 780 */ 781 public void setSurface(Surface surface) { 782 if (mScreenOnWhilePlaying && surface != null) { 783 Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); 784 } 785 mSurfaceHolder = null; 786 _setVideoSurface(surface); 787 updateSurfaceScreenOn(); 788 } 789 790 /* Do not change these video scaling mode values below without updating 791 * their counterparts in system/window.h! Please do not forget to update 792 * {@link #isVideoScalingModeSupported} when new video scaling modes 793 * are added. 794 */ 795 /** 796 * Specifies a video scaling mode. The content is stretched to the 797 * surface rendering area. When the surface has the same aspect ratio 798 * as the content, the aspect ratio of the content is maintained; 799 * otherwise, the aspect ratio of the content is not maintained when video 800 * is being rendered. Unlike {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}, 801 * there is no content cropping with this video scaling mode. 802 */ 803 public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; 804 805 /** 806 * Specifies a video scaling mode. The content is scaled, maintaining 807 * its aspect ratio. The whole surface area is always used. When the 808 * aspect ratio of the content is the same as the surface, no content 809 * is cropped; otherwise, content is cropped to fit the surface. 810 */ 811 public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; 812 /** 813 * Sets video scaling mode. To make the target video scaling mode 814 * effective during playback, this method must be called after 815 * data source is set. If not called, the default video 816 * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}. 817 * 818 * <p> The supported video scaling modes are: 819 * <ul> 820 * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT} 821 * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} 822 * </ul> 823 * 824 * @param mode target video scaling mode. Must be one of the supported 825 * video scaling modes; otherwise, IllegalArgumentException will be thrown. 826 * 827 * @see MediaPlayer#VIDEO_SCALING_MODE_SCALE_TO_FIT 828 * @see MediaPlayer#VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING 829 */ 830 public void setVideoScalingMode(int mode) { 831 if (!isVideoScalingModeSupported(mode)) { 832 final String msg = "Scaling mode " + mode + " is not supported"; 833 throw new IllegalArgumentException(msg); 834 } 835 Parcel request = Parcel.obtain(); 836 Parcel reply = Parcel.obtain(); 837 try { 838 request.writeInterfaceToken(IMEDIA_PLAYER); 839 request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE); 840 request.writeInt(mode); 841 invoke(request, reply); 842 } finally { 843 request.recycle(); 844 reply.recycle(); 845 } 846 } 847 848 /** 849 * Convenience method to create a MediaPlayer for a given Uri. 850 * On success, {@link #prepare()} will already have been called and must not be called again. 851 * <p>When done with the MediaPlayer, you should call {@link #release()}, 852 * to free the resources. If not released, too many MediaPlayer instances will 853 * result in an exception.</p> 854 * <p>Note that since {@link #prepare()} is called automatically in this method, 855 * you cannot change the audio 856 * session ID (see {@link #setAudioSessionId(int)}) or audio attributes 857 * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p> 858 * 859 * @param context the Context to use 860 * @param uri the Uri from which to get the datasource 861 * @return a MediaPlayer object, or null if creation failed 862 */ 863 public static MediaPlayer create(Context context, Uri uri) { 864 return create (context, uri, null); 865 } 866 867 /** 868 * Convenience method to create a MediaPlayer for a given Uri. 869 * On success, {@link #prepare()} will already have been called and must not be called again. 870 * <p>When done with the MediaPlayer, you should call {@link #release()}, 871 * to free the resources. If not released, too many MediaPlayer instances will 872 * result in an exception.</p> 873 * <p>Note that since {@link #prepare()} is called automatically in this method, 874 * you cannot change the audio 875 * session ID (see {@link #setAudioSessionId(int)}) or audio attributes 876 * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p> 877 * 878 * @param context the Context to use 879 * @param uri the Uri from which to get the datasource 880 * @param holder the SurfaceHolder to use for displaying the video 881 * @return a MediaPlayer object, or null if creation failed 882 */ 883 public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder) { 884 int s = AudioSystem.newAudioSessionId(); 885 return create(context, uri, holder, null, s > 0 ? s : 0); 886 } 887 888 /** 889 * Same factory method as {@link #create(Context, Uri, SurfaceHolder)} but that lets you specify 890 * the audio attributes and session ID to be used by the new MediaPlayer instance. 891 * @param context the Context to use 892 * @param uri the Uri from which to get the datasource 893 * @param holder the SurfaceHolder to use for displaying the video, may be null. 894 * @param audioAttributes the {@link AudioAttributes} to be used by the media player. 895 * @param audioSessionId the audio session ID to be used by the media player, 896 * see {@link AudioManager#generateAudioSessionId()} to obtain a new session. 897 * @return a MediaPlayer object, or null if creation failed 898 */ 899 public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder, 900 AudioAttributes audioAttributes, int audioSessionId) { 901 902 try { 903 MediaPlayer mp = new MediaPlayer(); 904 final AudioAttributes aa = audioAttributes != null ? audioAttributes : 905 new AudioAttributes.Builder().build(); 906 mp.setAudioAttributes(aa); 907 mp.setAudioSessionId(audioSessionId); 908 mp.setDataSource(context, uri); 909 if (holder != null) { 910 mp.setDisplay(holder); 911 } 912 mp.prepare(); 913 return mp; 914 } catch (IOException ex) { 915 Log.d(TAG, "create failed:", ex); 916 // fall through 917 } catch (IllegalArgumentException ex) { 918 Log.d(TAG, "create failed:", ex); 919 // fall through 920 } catch (SecurityException ex) { 921 Log.d(TAG, "create failed:", ex); 922 // fall through 923 } 924 925 return null; 926 } 927 928 // Note no convenience method to create a MediaPlayer with SurfaceTexture sink. 929 930 /** 931 * Convenience method to create a MediaPlayer for a given resource id. 932 * On success, {@link #prepare()} will already have been called and must not be called again. 933 * <p>When done with the MediaPlayer, you should call {@link #release()}, 934 * to free the resources. If not released, too many MediaPlayer instances will 935 * result in an exception.</p> 936 * <p>Note that since {@link #prepare()} is called automatically in this method, 937 * you cannot change the audio 938 * session ID (see {@link #setAudioSessionId(int)}) or audio attributes 939 * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p> 940 * 941 * @param context the Context to use 942 * @param resid the raw resource id (<var>R.raw.<something></var>) for 943 * the resource to use as the datasource 944 * @return a MediaPlayer object, or null if creation failed 945 */ 946 public static MediaPlayer create(Context context, int resid) { 947 int s = AudioSystem.newAudioSessionId(); 948 return create(context, resid, null, s > 0 ? s : 0); 949 } 950 951 /** 952 * Same factory method as {@link #create(Context, int)} but that lets you specify the audio 953 * attributes and session ID to be used by the new MediaPlayer instance. 954 * @param context the Context to use 955 * @param resid the raw resource id (<var>R.raw.<something></var>) for 956 * the resource to use as the datasource 957 * @param audioAttributes the {@link AudioAttributes} to be used by the media player. 958 * @param audioSessionId the audio session ID to be used by the media player, 959 * see {@link AudioManager#generateAudioSessionId()} to obtain a new session. 960 * @return a MediaPlayer object, or null if creation failed 961 */ 962 public static MediaPlayer create(Context context, int resid, 963 AudioAttributes audioAttributes, int audioSessionId) { 964 try { 965 AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid); 966 if (afd == null) return null; 967 968 MediaPlayer mp = new MediaPlayer(); 969 970 final AudioAttributes aa = audioAttributes != null ? audioAttributes : 971 new AudioAttributes.Builder().build(); 972 mp.setAudioAttributes(aa); 973 mp.setAudioSessionId(audioSessionId); 974 975 mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 976 afd.close(); 977 mp.prepare(); 978 return mp; 979 } catch (IOException ex) { 980 Log.d(TAG, "create failed:", ex); 981 // fall through 982 } catch (IllegalArgumentException ex) { 983 Log.d(TAG, "create failed:", ex); 984 // fall through 985 } catch (SecurityException ex) { 986 Log.d(TAG, "create failed:", ex); 987 // fall through 988 } 989 return null; 990 } 991 992 /** 993 * Sets the data source as a content Uri. 994 * 995 * @param context the Context to use when resolving the Uri 996 * @param uri the Content URI of the data you want to play 997 * @throws IllegalStateException if it is called in an invalid state 998 */ 999 public void setDataSource(@NonNull Context context, @NonNull Uri uri) 1000 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 1001 setDataSource(context, uri, null, null); 1002 } 1003 1004 /** 1005 * Sets the data source as a content Uri. 1006 * 1007 * @param context the Context to use when resolving the Uri 1008 * @param uri the Content URI of the data you want to play 1009 * @param headers the headers to be sent together with the request for the data 1010 * The headers must not include cookies. Instead, use the cookies param. 1011 * @param cookies the cookies to be sent together with the request 1012 * @throws IllegalStateException if it is called in an invalid state 1013 * 1014 * <p><strong>Note</strong> that the cross domain redirection is allowed by default, 1015 * but that can be changed with key/value pairs through the headers parameter with 1016 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to 1017 * disallow or allow cross domain redirection. 1018 */ 1019 public void setDataSource(@NonNull Context context, @NonNull Uri uri, 1020 @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) 1021 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 1022 // The context and URI usually belong to the calling user. Get a resolver for that user 1023 // and strip out the userId from the URI if present. 1024 final ContentResolver resolver = context.getContentResolver(); 1025 final String scheme = uri.getScheme(); 1026 final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()); 1027 if (ContentResolver.SCHEME_FILE.equals(scheme)) { 1028 setDataSource(uri.getPath()); 1029 return; 1030 } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) 1031 && Settings.AUTHORITY.equals(authority)) { 1032 // Try cached ringtone first since the actual provider may not be 1033 // encryption aware, or it may be stored on CE media storage 1034 final int type = RingtoneManager.getDefaultType(uri); 1035 final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId()); 1036 final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); 1037 if (attemptDataSource(resolver, cacheUri)) { 1038 return; 1039 } else if (attemptDataSource(resolver, actualUri)) { 1040 return; 1041 } else { 1042 setDataSource(uri.toString(), headers, cookies); 1043 } 1044 } else { 1045 // Try requested Uri locally first, or fallback to media server 1046 if (attemptDataSource(resolver, uri)) { 1047 return; 1048 } else { 1049 setDataSource(uri.toString(), headers, cookies); 1050 } 1051 } 1052 } 1053 1054 /** 1055 * Sets the data source as a content Uri. 1056 * 1057 * @param context the Context to use when resolving the Uri 1058 * @param uri the Content URI of the data you want to play 1059 * @param headers the headers to be sent together with the request for the data 1060 * @throws IllegalStateException if it is called in an invalid state 1061 * 1062 * <p><strong>Note</strong> that the cross domain redirection is allowed by default, 1063 * but that can be changed with key/value pairs through the headers parameter with 1064 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to 1065 * disallow or allow cross domain redirection. 1066 */ 1067 public void setDataSource(@NonNull Context context, @NonNull Uri uri, 1068 @Nullable Map<String, String> headers) 1069 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 1070 setDataSource(context, uri, headers, null); 1071 } 1072 1073 private boolean attemptDataSource(ContentResolver resolver, Uri uri) { 1074 try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) { 1075 setDataSource(afd); 1076 return true; 1077 } catch (NullPointerException | SecurityException | IOException ex) { 1078 Log.w(TAG, "Couldn't open " + uri + ": " + ex); 1079 return false; 1080 } 1081 } 1082 1083 /** 1084 * Sets the data source (file-path or http/rtsp URL) to use. 1085 * 1086 * @param path the path of the file, or the http/rtsp URL of the stream you want to play 1087 * @throws IllegalStateException if it is called in an invalid state 1088 * 1089 * <p>When <code>path</code> refers to a local file, the file may actually be opened by a 1090 * process other than the calling application. This implies that the pathname 1091 * should be an absolute path (as any other process runs with unspecified current working 1092 * directory), and that the pathname should reference a world-readable file. 1093 * As an alternative, the application could first open the file for reading, 1094 * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}. 1095 */ 1096 public void setDataSource(String path) 1097 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 1098 setDataSource(path, null, null); 1099 } 1100 1101 /** 1102 * Sets the data source (file-path or http/rtsp URL) to use. 1103 * 1104 * @param path the path of the file, or the http/rtsp URL of the stream you want to play 1105 * @param headers the headers associated with the http request for the stream you want to play 1106 * @throws IllegalStateException if it is called in an invalid state 1107 * @hide pending API council 1108 */ 1109 public void setDataSource(String path, Map<String, String> headers) 1110 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 1111 setDataSource(path, headers, null); 1112 } 1113 1114 private void setDataSource(String path, Map<String, String> headers, List<HttpCookie> cookies) 1115 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException 1116 { 1117 String[] keys = null; 1118 String[] values = null; 1119 1120 if (headers != null) { 1121 keys = new String[headers.size()]; 1122 values = new String[headers.size()]; 1123 1124 int i = 0; 1125 for (Map.Entry<String, String> entry: headers.entrySet()) { 1126 keys[i] = entry.getKey(); 1127 values[i] = entry.getValue(); 1128 ++i; 1129 } 1130 } 1131 setDataSource(path, keys, values, cookies); 1132 } 1133 1134 private void setDataSource(String path, String[] keys, String[] values, 1135 List<HttpCookie> cookies) 1136 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 1137 final Uri uri = Uri.parse(path); 1138 final String scheme = uri.getScheme(); 1139 if ("file".equals(scheme)) { 1140 path = uri.getPath(); 1141 } else if (scheme != null) { 1142 // handle non-file sources 1143 nativeSetDataSource( 1144 MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies), 1145 path, 1146 keys, 1147 values); 1148 return; 1149 } 1150 1151 final File file = new File(path); 1152 if (file.exists()) { 1153 FileInputStream is = new FileInputStream(file); 1154 FileDescriptor fd = is.getFD(); 1155 setDataSource(fd); 1156 is.close(); 1157 } else { 1158 throw new IOException("setDataSource failed."); 1159 } 1160 } 1161 1162 private native void nativeSetDataSource( 1163 IBinder httpServiceBinder, String path, String[] keys, String[] values) 1164 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; 1165 1166 /** 1167 * Sets the data source (AssetFileDescriptor) to use. It is the caller's 1168 * responsibility to close the file descriptor. It is safe to do so as soon 1169 * as this call returns. 1170 * 1171 * @param afd the AssetFileDescriptor for the file you want to play 1172 * @throws IllegalStateException if it is called in an invalid state 1173 * @throws IllegalArgumentException if afd is not a valid AssetFileDescriptor 1174 * @throws IOException if afd can not be read 1175 */ 1176 public void setDataSource(@NonNull AssetFileDescriptor afd) 1177 throws IOException, IllegalArgumentException, IllegalStateException { 1178 Preconditions.checkNotNull(afd); 1179 // Note: using getDeclaredLength so that our behavior is the same 1180 // as previous versions when the content provider is returning 1181 // a full file. 1182 if (afd.getDeclaredLength() < 0) { 1183 setDataSource(afd.getFileDescriptor()); 1184 } else { 1185 setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength()); 1186 } 1187 } 1188 1189 /** 1190 * Sets the data source (FileDescriptor) to use. It is the caller's responsibility 1191 * to close the file descriptor. It is safe to do so as soon as this call returns. 1192 * 1193 * @param fd the FileDescriptor for the file you want to play 1194 * @throws IllegalStateException if it is called in an invalid state 1195 * @throws IllegalArgumentException if fd is not a valid FileDescriptor 1196 * @throws IOException if fd can not be read 1197 */ 1198 public void setDataSource(FileDescriptor fd) 1199 throws IOException, IllegalArgumentException, IllegalStateException { 1200 // intentionally less than LONG_MAX 1201 setDataSource(fd, 0, 0x7ffffffffffffffL); 1202 } 1203 1204 /** 1205 * Sets the data source (FileDescriptor) to use. The FileDescriptor must be 1206 * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility 1207 * to close the file descriptor. It is safe to do so as soon as this call returns. 1208 * 1209 * @param fd the FileDescriptor for the file you want to play 1210 * @param offset the offset into the file where the data to be played starts, in bytes 1211 * @param length the length in bytes of the data to be played 1212 * @throws IllegalStateException if it is called in an invalid state 1213 * @throws IllegalArgumentException if fd is not a valid FileDescriptor 1214 * @throws IOException if fd can not be read 1215 */ 1216 public void setDataSource(FileDescriptor fd, long offset, long length) 1217 throws IOException, IllegalArgumentException, IllegalStateException { 1218 _setDataSource(fd, offset, length); 1219 } 1220 1221 private native void _setDataSource(FileDescriptor fd, long offset, long length) 1222 throws IOException, IllegalArgumentException, IllegalStateException; 1223 1224 /** 1225 * Sets the data source (MediaDataSource) to use. 1226 * 1227 * @param dataSource the MediaDataSource for the media you want to play 1228 * @throws IllegalStateException if it is called in an invalid state 1229 * @throws IllegalArgumentException if dataSource is not a valid MediaDataSource 1230 */ 1231 public void setDataSource(MediaDataSource dataSource) 1232 throws IllegalArgumentException, IllegalStateException { 1233 _setDataSource(dataSource); 1234 } 1235 1236 private native void _setDataSource(MediaDataSource dataSource) 1237 throws IllegalArgumentException, IllegalStateException; 1238 1239 /** 1240 * Prepares the player for playback, synchronously. 1241 * 1242 * After setting the datasource and the display surface, you need to either 1243 * call prepare() or prepareAsync(). For files, it is OK to call prepare(), 1244 * which blocks until MediaPlayer is ready for playback. 1245 * 1246 * @throws IllegalStateException if it is called in an invalid state 1247 */ 1248 public void prepare() throws IOException, IllegalStateException { 1249 _prepare(); 1250 scanInternalSubtitleTracks(); 1251 1252 // DrmInfo, if any, has been resolved by now. 1253 synchronized (mDrmLock) { 1254 mDrmInfoResolved = true; 1255 } 1256 } 1257 1258 private native void _prepare() throws IOException, IllegalStateException; 1259 1260 /** 1261 * Prepares the player for playback, asynchronously. 1262 * 1263 * After setting the datasource and the display surface, you need to either 1264 * call prepare() or prepareAsync(). For streams, you should call prepareAsync(), 1265 * which returns immediately, rather than blocking until enough data has been 1266 * buffered. 1267 * 1268 * @throws IllegalStateException if it is called in an invalid state 1269 */ 1270 public native void prepareAsync() throws IllegalStateException; 1271 1272 /** 1273 * Starts or resumes playback. If playback had previously been paused, 1274 * playback will continue from where it was paused. If playback had 1275 * been stopped, or never started before, playback will start at the 1276 * beginning. 1277 * 1278 * @throws IllegalStateException if it is called in an invalid state 1279 */ 1280 public void start() throws IllegalStateException { 1281 //FIXME use lambda to pass startImpl to superclass 1282 final int delay = getStartDelayMs(); 1283 if (delay == 0) { 1284 startImpl(); 1285 } else { 1286 new Thread() { 1287 public void run() { 1288 try { 1289 Thread.sleep(delay); 1290 } catch (InterruptedException e) { 1291 e.printStackTrace(); 1292 } 1293 baseSetStartDelayMs(0); 1294 try { 1295 startImpl(); 1296 } catch (IllegalStateException e) { 1297 // fail silently for a state exception when it is happening after 1298 // a delayed start, as the player state could have changed between the 1299 // call to start() and the execution of startImpl() 1300 } 1301 } 1302 }.start(); 1303 } 1304 } 1305 1306 private void startImpl() { 1307 baseStart(); 1308 stayAwake(true); 1309 _start(); 1310 } 1311 1312 private native void _start() throws IllegalStateException; 1313 1314 1315 private int getAudioStreamType() { 1316 if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { 1317 mStreamType = _getAudioStreamType(); 1318 } 1319 return mStreamType; 1320 } 1321 1322 private native int _getAudioStreamType() throws IllegalStateException; 1323 1324 /** 1325 * Stops playback after playback has been stopped or paused. 1326 * 1327 * @throws IllegalStateException if the internal player engine has not been 1328 * initialized. 1329 */ 1330 public void stop() throws IllegalStateException { 1331 stayAwake(false); 1332 _stop(); 1333 baseStop(); 1334 } 1335 1336 private native void _stop() throws IllegalStateException; 1337 1338 /** 1339 * Pauses playback. Call start() to resume. 1340 * 1341 * @throws IllegalStateException if the internal player engine has not been 1342 * initialized. 1343 */ 1344 public void pause() throws IllegalStateException { 1345 stayAwake(false); 1346 _pause(); 1347 basePause(); 1348 } 1349 1350 private native void _pause() throws IllegalStateException; 1351 1352 @Override 1353 void playerStart() { 1354 start(); 1355 } 1356 1357 @Override 1358 void playerPause() { 1359 pause(); 1360 } 1361 1362 @Override 1363 void playerStop() { 1364 stop(); 1365 } 1366 1367 @Override 1368 /* package */ int playerApplyVolumeShaper( 1369 @NonNull VolumeShaper.Configuration configuration, 1370 @NonNull VolumeShaper.Operation operation) { 1371 return native_applyVolumeShaper(configuration, operation); 1372 } 1373 1374 @Override 1375 /* package */ @Nullable VolumeShaper.State playerGetVolumeShaperState(int id) { 1376 return native_getVolumeShaperState(id); 1377 } 1378 1379 @Override 1380 public @NonNull VolumeShaper createVolumeShaper( 1381 @NonNull VolumeShaper.Configuration configuration) { 1382 return new VolumeShaper(configuration, this); 1383 } 1384 1385 private native int native_applyVolumeShaper( 1386 @NonNull VolumeShaper.Configuration configuration, 1387 @NonNull VolumeShaper.Operation operation); 1388 1389 private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id); 1390 1391 /** 1392 * Set the low-level power management behavior for this MediaPlayer. This 1393 * can be used when the MediaPlayer is not playing through a SurfaceHolder 1394 * set with {@link #setDisplay(SurfaceHolder)} and thus can use the 1395 * high-level {@link #setScreenOnWhilePlaying(boolean)} feature. 1396 * 1397 * <p>This function has the MediaPlayer access the low-level power manager 1398 * service to control the device's power usage while playing is occurring. 1399 * The parameter is a combination of {@link android.os.PowerManager} wake flags. 1400 * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK} 1401 * permission. 1402 * By default, no attempt is made to keep the device awake during playback. 1403 * 1404 * @param context the Context to use 1405 * @param mode the power/wake mode to set 1406 * @see android.os.PowerManager 1407 */ 1408 public void setWakeMode(Context context, int mode) { 1409 boolean washeld = false; 1410 1411 /* Disable persistant wakelocks in media player based on property */ 1412 if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) { 1413 Log.w(TAG, "IGNORING setWakeMode " + mode); 1414 return; 1415 } 1416 1417 if (mWakeLock != null) { 1418 if (mWakeLock.isHeld()) { 1419 washeld = true; 1420 mWakeLock.release(); 1421 } 1422 mWakeLock = null; 1423 } 1424 1425 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 1426 mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer.class.getName()); 1427 mWakeLock.setReferenceCounted(false); 1428 if (washeld) { 1429 mWakeLock.acquire(); 1430 } 1431 } 1432 1433 /** 1434 * Control whether we should use the attached SurfaceHolder to keep the 1435 * screen on while video playback is occurring. This is the preferred 1436 * method over {@link #setWakeMode} where possible, since it doesn't 1437 * require that the application have permission for low-level wake lock 1438 * access. 1439 * 1440 * @param screenOn Supply true to keep the screen on, false to allow it 1441 * to turn off. 1442 */ 1443 public void setScreenOnWhilePlaying(boolean screenOn) { 1444 if (mScreenOnWhilePlaying != screenOn) { 1445 if (screenOn && mSurfaceHolder == null) { 1446 Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder"); 1447 } 1448 mScreenOnWhilePlaying = screenOn; 1449 updateSurfaceScreenOn(); 1450 } 1451 } 1452 1453 private void stayAwake(boolean awake) { 1454 if (mWakeLock != null) { 1455 if (awake && !mWakeLock.isHeld()) { 1456 mWakeLock.acquire(); 1457 } else if (!awake && mWakeLock.isHeld()) { 1458 mWakeLock.release(); 1459 } 1460 } 1461 mStayAwake = awake; 1462 updateSurfaceScreenOn(); 1463 } 1464 1465 private void updateSurfaceScreenOn() { 1466 if (mSurfaceHolder != null) { 1467 mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake); 1468 } 1469 } 1470 1471 /** 1472 * Returns the width of the video. 1473 * 1474 * @return the width of the video, or 0 if there is no video, 1475 * no display surface was set, or the width has not been determined 1476 * yet. The OnVideoSizeChangedListener can be registered via 1477 * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)} 1478 * to provide a notification when the width is available. 1479 */ 1480 public native int getVideoWidth(); 1481 1482 /** 1483 * Returns the height of the video. 1484 * 1485 * @return the height of the video, or 0 if there is no video, 1486 * no display surface was set, or the height has not been determined 1487 * yet. The OnVideoSizeChangedListener can be registered via 1488 * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)} 1489 * to provide a notification when the height is available. 1490 */ 1491 public native int getVideoHeight(); 1492 1493 /** 1494 * Return Metrics data about the current player. 1495 * 1496 * @return a {@link PersistableBundle} containing the set of attributes and values 1497 * available for the media being handled by this instance of MediaPlayer 1498 * The attributes are descibed in {@link MetricsConstants}. 1499 * 1500 * Additional vendor-specific fields may also be present in 1501 * the return value. 1502 */ 1503 public PersistableBundle getMetrics() { 1504 PersistableBundle bundle = native_getMetrics(); 1505 return bundle; 1506 } 1507 1508 private native PersistableBundle native_getMetrics(); 1509 1510 /** 1511 * Checks whether the MediaPlayer is playing. 1512 * 1513 * @return true if currently playing, false otherwise 1514 * @throws IllegalStateException if the internal player engine has not been 1515 * initialized or has been released. 1516 */ 1517 public native boolean isPlaying(); 1518 1519 /** 1520 * Gets the default buffering management params. 1521 * Calling it only after {@code setDataSource} has been called. 1522 * Each type of data source might have different set of default params. 1523 * 1524 * @return the default buffering management params supported by the source component. 1525 * @throws IllegalStateException if the internal player engine has not been 1526 * initialized, or {@code setDataSource} has not been called. 1527 * @hide 1528 */ 1529 @NonNull 1530 public native BufferingParams getDefaultBufferingParams(); 1531 1532 /** 1533 * Gets the current buffering management params used by the source component. 1534 * Calling it only after {@code setDataSource} has been called. 1535 * 1536 * @return the current buffering management params used by the source component. 1537 * @throws IllegalStateException if the internal player engine has not been 1538 * initialized, or {@code setDataSource} has not been called. 1539 * @hide 1540 */ 1541 @NonNull 1542 public native BufferingParams getBufferingParams(); 1543 1544 /** 1545 * Sets buffering management params. 1546 * The object sets its internal BufferingParams to the input, except that the input is 1547 * invalid or not supported. 1548 * Call it only after {@code setDataSource} has been called. 1549 * Users should only use supported mode returned by {@link #getDefaultBufferingParams()} 1550 * or its downsized version as described in {@link BufferingParams}. 1551 * 1552 * @param params the buffering management params. 1553 * 1554 * @throws IllegalStateException if the internal player engine has not been 1555 * initialized or has been released, or {@code setDataSource} has not been called. 1556 * @throws IllegalArgumentException if params is invalid or not supported. 1557 * @hide 1558 */ 1559 public native void setBufferingParams(@NonNull BufferingParams params); 1560 1561 /** 1562 * Change playback speed of audio by resampling the audio. 1563 * <p> 1564 * Specifies resampling as audio mode for variable rate playback, i.e., 1565 * resample the waveform based on the requested playback rate to get 1566 * a new waveform, and play back the new waveform at the original sampling 1567 * frequency. 1568 * When rate is larger than 1.0, pitch becomes higher. 1569 * When rate is smaller than 1.0, pitch becomes lower. 1570 * 1571 * @hide 1572 */ 1573 public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; 1574 1575 /** 1576 * Change playback speed of audio without changing its pitch. 1577 * <p> 1578 * Specifies time stretching as audio mode for variable rate playback. 1579 * Time stretching changes the duration of the audio samples without 1580 * affecting its pitch. 1581 * <p> 1582 * This mode is only supported for a limited range of playback speed factors, 1583 * e.g. between 1/2x and 2x. 1584 * 1585 * @hide 1586 */ 1587 public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; 1588 1589 /** 1590 * Change playback speed of audio without changing its pitch, and 1591 * possibly mute audio if time stretching is not supported for the playback 1592 * speed. 1593 * <p> 1594 * Try to keep audio pitch when changing the playback rate, but allow the 1595 * system to determine how to change audio playback if the rate is out 1596 * of range. 1597 * 1598 * @hide 1599 */ 1600 public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; 1601 1602 /** @hide */ 1603 @IntDef( 1604 value = { 1605 PLAYBACK_RATE_AUDIO_MODE_DEFAULT, 1606 PLAYBACK_RATE_AUDIO_MODE_STRETCH, 1607 PLAYBACK_RATE_AUDIO_MODE_RESAMPLE, 1608 }) 1609 @Retention(RetentionPolicy.SOURCE) 1610 public @interface PlaybackRateAudioMode {} 1611 1612 /** 1613 * Sets playback rate and audio mode. 1614 * 1615 * @param rate the ratio between desired playback rate and normal one. 1616 * @param audioMode audio playback mode. Must be one of the supported 1617 * audio modes. 1618 * 1619 * @throws IllegalStateException if the internal player engine has not been 1620 * initialized. 1621 * @throws IllegalArgumentException if audioMode is not supported. 1622 * 1623 * @hide 1624 */ 1625 @NonNull 1626 public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) { 1627 PlaybackParams params = new PlaybackParams(); 1628 params.allowDefaults(); 1629 switch (audioMode) { 1630 case PLAYBACK_RATE_AUDIO_MODE_DEFAULT: 1631 params.setSpeed(rate).setPitch(1.0f); 1632 break; 1633 case PLAYBACK_RATE_AUDIO_MODE_STRETCH: 1634 params.setSpeed(rate).setPitch(1.0f) 1635 .setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL); 1636 break; 1637 case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE: 1638 params.setSpeed(rate).setPitch(rate); 1639 break; 1640 default: 1641 final String msg = "Audio playback mode " + audioMode + " is not supported"; 1642 throw new IllegalArgumentException(msg); 1643 } 1644 return params; 1645 } 1646 1647 /** 1648 * Sets playback rate using {@link PlaybackParams}. The object sets its internal 1649 * PlaybackParams to the input, except that the object remembers previous speed 1650 * when input speed is zero. This allows the object to resume at previous speed 1651 * when start() is called. Calling it before the object is prepared does not change 1652 * the object state. After the object is prepared, calling it with zero speed is 1653 * equivalent to calling pause(). After the object is prepared, calling it with 1654 * non-zero speed is equivalent to calling start(). 1655 * 1656 * @param params the playback params. 1657 * 1658 * @throws IllegalStateException if the internal player engine has not been 1659 * initialized or has been released. 1660 * @throws IllegalArgumentException if params is not supported. 1661 */ 1662 public native void setPlaybackParams(@NonNull PlaybackParams params); 1663 1664 /** 1665 * Gets the playback params, containing the current playback rate. 1666 * 1667 * @return the playback params. 1668 * @throws IllegalStateException if the internal player engine has not been 1669 * initialized. 1670 */ 1671 @NonNull 1672 public native PlaybackParams getPlaybackParams(); 1673 1674 /** 1675 * Sets A/V sync mode. 1676 * 1677 * @param params the A/V sync params to apply 1678 * 1679 * @throws IllegalStateException if the internal player engine has not been 1680 * initialized. 1681 * @throws IllegalArgumentException if params are not supported. 1682 */ 1683 public native void setSyncParams(@NonNull SyncParams params); 1684 1685 /** 1686 * Gets the A/V sync mode. 1687 * 1688 * @return the A/V sync params 1689 * 1690 * @throws IllegalStateException if the internal player engine has not been 1691 * initialized. 1692 */ 1693 @NonNull 1694 public native SyncParams getSyncParams(); 1695 1696 /** 1697 * Seek modes used in method seekTo(int, int) to move media position 1698 * to a specified location. 1699 * 1700 * Do not change these mode values without updating their counterparts 1701 * in include/media/IMediaSource.h! 1702 */ 1703 /** 1704 * This mode is used with {@link #seekTo(int, int)} to move media position to 1705 * a sync (or key) frame associated with a data source that is located 1706 * right before or at the given time. 1707 * 1708 * @see #seekTo(int, int) 1709 */ 1710 public static final int SEEK_PREVIOUS_SYNC = 0x00; 1711 /** 1712 * This mode is used with {@link #seekTo(int, int)} to move media position to 1713 * a sync (or key) frame associated with a data source that is located 1714 * right after or at the given time. 1715 * 1716 * @see #seekTo(int, int) 1717 */ 1718 public static final int SEEK_NEXT_SYNC = 0x01; 1719 /** 1720 * This mode is used with {@link #seekTo(int, int)} to move media position to 1721 * a sync (or key) frame associated with a data source that is located 1722 * closest to (in time) or at the given time. 1723 * 1724 * @see #seekTo(int, int) 1725 */ 1726 public static final int SEEK_CLOSEST_SYNC = 0x02; 1727 /** 1728 * This mode is used with {@link #seekTo(int, int)} to move media position to 1729 * a frame (not necessarily a key frame) associated with a data source that 1730 * is located closest to or at the given time. 1731 * 1732 * @see #seekTo(int, int) 1733 */ 1734 public static final int SEEK_CLOSEST = 0x03; 1735 1736 /** @hide */ 1737 @IntDef( 1738 value = { 1739 SEEK_PREVIOUS_SYNC, 1740 SEEK_NEXT_SYNC, 1741 SEEK_CLOSEST_SYNC, 1742 SEEK_CLOSEST, 1743 }) 1744 @Retention(RetentionPolicy.SOURCE) 1745 public @interface SeekMode {} 1746 1747 private native final void _seekTo(int msec, int mode); 1748 1749 /** 1750 * Moves the media to specified time position by considering the given mode. 1751 * <p> 1752 * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user. 1753 * There is at most one active seekTo processed at any time. If there is a to-be-completed 1754 * seekTo, new seekTo requests will be queued in such a way that only the last request 1755 * is kept. When current seekTo is completed, the queued request will be processed if 1756 * that request is different from just-finished seekTo operation, i.e., the requested 1757 * position or mode is different. 1758 * 1759 * @param msec the offset in milliseconds from the start to seek to. 1760 * When seeking to the given time position, there is no guarantee that the data source 1761 * has a frame located at the position. When this happens, a frame nearby will be rendered. 1762 * If msec is negative, time position zero will be used. 1763 * If msec is larger than duration, duration will be used. 1764 * @param mode the mode indicating where exactly to seek to. 1765 * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame 1766 * that has a timestamp earlier than or the same as msec. Use 1767 * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame 1768 * that has a timestamp later than or the same as msec. Use 1769 * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame 1770 * that has a timestamp closest to or the same as msec. Use 1771 * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may 1772 * or may not be a sync frame but is closest to or the same as msec. 1773 * {@link #SEEK_CLOSEST} often has larger performance overhead compared 1774 * to the other options if there is no sync frame located at msec. 1775 * @throws IllegalStateException if the internal player engine has not been 1776 * initialized 1777 * @throws IllegalArgumentException if the mode is invalid. 1778 */ 1779 public void seekTo(int msec, @SeekMode int mode) throws IllegalStateException { 1780 if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) { 1781 final String msg = "Illegal seek mode: " + mode; 1782 throw new IllegalArgumentException(msg); 1783 } 1784 _seekTo(msec, mode); 1785 } 1786 1787 /** 1788 * Seeks to specified time position. 1789 * Same as {@link #seekTo(int, int)} with {@code mode = SEEK_PREVIOUS_SYNC}. 1790 * 1791 * @param msec the offset in milliseconds from the start to seek to 1792 * @throws IllegalStateException if the internal player engine has not been 1793 * initialized 1794 */ 1795 public void seekTo(int msec) throws IllegalStateException { 1796 seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */); 1797 } 1798 1799 /** 1800 * Get current playback position as a {@link MediaTimestamp}. 1801 * <p> 1802 * The MediaTimestamp represents how the media time correlates to the system time in 1803 * a linear fashion using an anchor and a clock rate. During regular playback, the media 1804 * time moves fairly constantly (though the anchor frame may be rebased to a current 1805 * system time, the linear correlation stays steady). Therefore, this method does not 1806 * need to be called often. 1807 * <p> 1808 * To help users get current playback position, this method always anchors the timestamp 1809 * to the current {@link System#nanoTime system time}, so 1810 * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position. 1811 * 1812 * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp 1813 * is available, e.g. because the media player has not been initialized. 1814 * 1815 * @see MediaTimestamp 1816 */ 1817 @Nullable 1818 public MediaTimestamp getTimestamp() 1819 { 1820 try { 1821 // TODO: get the timestamp from native side 1822 return new MediaTimestamp( 1823 getCurrentPosition() * 1000L, 1824 System.nanoTime(), 1825 isPlaying() ? getPlaybackParams().getSpeed() : 0.f); 1826 } catch (IllegalStateException e) { 1827 return null; 1828 } 1829 } 1830 1831 /** 1832 * Gets the current playback position. 1833 * 1834 * @return the current position in milliseconds 1835 */ 1836 public native int getCurrentPosition(); 1837 1838 /** 1839 * Gets the duration of the file. 1840 * 1841 * @return the duration in milliseconds, if no duration is available 1842 * (for example, if streaming live content), -1 is returned. 1843 */ 1844 public native int getDuration(); 1845 1846 /** 1847 * Gets the media metadata. 1848 * 1849 * @param update_only controls whether the full set of available 1850 * metadata is returned or just the set that changed since the 1851 * last call. See {@see #METADATA_UPDATE_ONLY} and {@see 1852 * #METADATA_ALL}. 1853 * 1854 * @param apply_filter if true only metadata that matches the 1855 * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see 1856 * #BYPASS_METADATA_FILTER}. 1857 * 1858 * @return The metadata, possibly empty. null if an error occured. 1859 // FIXME: unhide. 1860 * {@hide} 1861 */ 1862 public Metadata getMetadata(final boolean update_only, 1863 final boolean apply_filter) { 1864 Parcel reply = Parcel.obtain(); 1865 Metadata data = new Metadata(); 1866 1867 if (!native_getMetadata(update_only, apply_filter, reply)) { 1868 reply.recycle(); 1869 return null; 1870 } 1871 1872 // Metadata takes over the parcel, don't recycle it unless 1873 // there is an error. 1874 if (!data.parse(reply)) { 1875 reply.recycle(); 1876 return null; 1877 } 1878 return data; 1879 } 1880 1881 /** 1882 * Set a filter for the metadata update notification and update 1883 * retrieval. The caller provides 2 set of metadata keys, allowed 1884 * and blocked. The blocked set always takes precedence over the 1885 * allowed one. 1886 * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as 1887 * shorthands to allow/block all or no metadata. 1888 * 1889 * By default, there is no filter set. 1890 * 1891 * @param allow Is the set of metadata the client is interested 1892 * in receiving new notifications for. 1893 * @param block Is the set of metadata the client is not interested 1894 * in receiving new notifications for. 1895 * @return The call status code. 1896 * 1897 // FIXME: unhide. 1898 * {@hide} 1899 */ 1900 public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) { 1901 // Do our serialization manually instead of calling 1902 // Parcel.writeArray since the sets are made of the same type 1903 // we avoid paying the price of calling writeValue (used by 1904 // writeArray) which burns an extra int per element to encode 1905 // the type. 1906 Parcel request = newRequest(); 1907 1908 // The parcel starts already with an interface token. There 1909 // are 2 filters. Each one starts with a 4bytes number to 1910 // store the len followed by a number of int (4 bytes as well) 1911 // representing the metadata type. 1912 int capacity = request.dataSize() + 4 * (1 + allow.size() + 1 + block.size()); 1913 1914 if (request.dataCapacity() < capacity) { 1915 request.setDataCapacity(capacity); 1916 } 1917 1918 request.writeInt(allow.size()); 1919 for(Integer t: allow) { 1920 request.writeInt(t); 1921 } 1922 request.writeInt(block.size()); 1923 for(Integer t: block) { 1924 request.writeInt(t); 1925 } 1926 return native_setMetadataFilter(request); 1927 } 1928 1929 /** 1930 * Set the MediaPlayer to start when this MediaPlayer finishes playback 1931 * (i.e. reaches the end of the stream). 1932 * The media framework will attempt to transition from this player to 1933 * the next as seamlessly as possible. The next player can be set at 1934 * any time before completion, but shall be after setDataSource has been 1935 * called successfully. The next player must be prepared by the 1936 * app, and the application should not call start() on it. 1937 * The next MediaPlayer must be different from 'this'. An exception 1938 * will be thrown if next == this. 1939 * The application may call setNextMediaPlayer(null) to indicate no 1940 * next player should be started at the end of playback. 1941 * If the current player is looping, it will keep looping and the next 1942 * player will not be started. 1943 * 1944 * @param next the player to start after this one completes playback. 1945 * 1946 */ 1947 public native void setNextMediaPlayer(MediaPlayer next); 1948 1949 /** 1950 * Releases resources associated with this MediaPlayer object. 1951 * It is considered good practice to call this method when you're 1952 * done using the MediaPlayer. In particular, whenever an Activity 1953 * of an application is paused (its onPause() method is called), 1954 * or stopped (its onStop() method is called), this method should be 1955 * invoked to release the MediaPlayer object, unless the application 1956 * has a special need to keep the object around. In addition to 1957 * unnecessary resources (such as memory and instances of codecs) 1958 * being held, failure to call this method immediately if a 1959 * MediaPlayer object is no longer needed may also lead to 1960 * continuous battery consumption for mobile devices, and playback 1961 * failure for other applications if no multiple instances of the 1962 * same codec are supported on a device. Even if multiple instances 1963 * of the same codec are supported, some performance degradation 1964 * may be expected when unnecessary multiple instances are used 1965 * at the same time. 1966 */ 1967 public void release() { 1968 baseRelease(); 1969 stayAwake(false); 1970 updateSurfaceScreenOn(); 1971 mOnPreparedListener = null; 1972 mOnBufferingUpdateListener = null; 1973 mOnCompletionListener = null; 1974 mOnSeekCompleteListener = null; 1975 mOnErrorListener = null; 1976 mOnInfoListener = null; 1977 mOnVideoSizeChangedListener = null; 1978 mOnTimedTextListener = null; 1979 if (mTimeProvider != null) { 1980 mTimeProvider.close(); 1981 mTimeProvider = null; 1982 } 1983 mOnSubtitleDataListener = null; 1984 1985 // Modular DRM clean up 1986 mOnDrmConfigHelper = null; 1987 mOnDrmInfoHandlerDelegate = null; 1988 mOnDrmPreparedHandlerDelegate = null; 1989 resetDrmState(); 1990 1991 _release(); 1992 } 1993 1994 private native void _release(); 1995 1996 /** 1997 * Resets the MediaPlayer to its uninitialized state. After calling 1998 * this method, you will have to initialize it again by setting the 1999 * data source and calling prepare(). 2000 */ 2001 public void reset() { 2002 mSelectedSubtitleTrackIndex = -1; 2003 synchronized(mOpenSubtitleSources) { 2004 for (final InputStream is: mOpenSubtitleSources) { 2005 try { 2006 is.close(); 2007 } catch (IOException e) { 2008 } 2009 } 2010 mOpenSubtitleSources.clear(); 2011 } 2012 if (mSubtitleController != null) { 2013 mSubtitleController.reset(); 2014 } 2015 if (mTimeProvider != null) { 2016 mTimeProvider.close(); 2017 mTimeProvider = null; 2018 } 2019 2020 stayAwake(false); 2021 _reset(); 2022 // make sure none of the listeners get called anymore 2023 if (mEventHandler != null) { 2024 mEventHandler.removeCallbacksAndMessages(null); 2025 } 2026 2027 synchronized (mIndexTrackPairs) { 2028 mIndexTrackPairs.clear(); 2029 mInbandTrackIndices.clear(); 2030 }; 2031 2032 resetDrmState(); 2033 } 2034 2035 private native void _reset(); 2036 2037 /** 2038 * Sets the audio stream type for this MediaPlayer. See {@link AudioManager} 2039 * for a list of stream types. Must call this method before prepare() or 2040 * prepareAsync() in order for the target stream type to become effective 2041 * thereafter. 2042 * 2043 * @param streamtype the audio stream type 2044 * @deprecated use {@link #setAudioAttributes(AudioAttributes)} 2045 * @see android.media.AudioManager 2046 */ 2047 public void setAudioStreamType(int streamtype) { 2048 deprecateStreamTypeForPlayback(streamtype, "MediaPlayer", "setAudioStreamType()"); 2049 baseUpdateAudioAttributes( 2050 new AudioAttributes.Builder().setInternalLegacyStreamType(streamtype).build()); 2051 _setAudioStreamType(streamtype); 2052 mStreamType = streamtype; 2053 } 2054 2055 private native void _setAudioStreamType(int streamtype); 2056 2057 // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer.h 2058 private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400; 2059 /** 2060 * Sets the parameter indicated by key. 2061 * @param key key indicates the parameter to be set. 2062 * @param value value of the parameter to be set. 2063 * @return true if the parameter is set successfully, false otherwise 2064 * {@hide} 2065 */ 2066 private native boolean setParameter(int key, Parcel value); 2067 2068 /** 2069 * Sets the audio attributes for this MediaPlayer. 2070 * See {@link AudioAttributes} for how to build and configure an instance of this class. 2071 * You must call this method before {@link #prepare()} or {@link #prepareAsync()} in order 2072 * for the audio attributes to become effective thereafter. 2073 * @param attributes a non-null set of audio attributes 2074 */ 2075 public void setAudioAttributes(AudioAttributes attributes) throws IllegalArgumentException { 2076 if (attributes == null) { 2077 final String msg = "Cannot set AudioAttributes to null"; 2078 throw new IllegalArgumentException(msg); 2079 } 2080 baseUpdateAudioAttributes(attributes); 2081 mUsage = attributes.getUsage(); 2082 mBypassInterruptionPolicy = (attributes.getAllFlags() 2083 & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0; 2084 Parcel pattributes = Parcel.obtain(); 2085 attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS); 2086 setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes); 2087 pattributes.recycle(); 2088 } 2089 2090 /** 2091 * Sets the player to be looping or non-looping. 2092 * 2093 * @param looping whether to loop or not 2094 */ 2095 public native void setLooping(boolean looping); 2096 2097 /** 2098 * Checks whether the MediaPlayer is looping or non-looping. 2099 * 2100 * @return true if the MediaPlayer is currently looping, false otherwise 2101 */ 2102 public native boolean isLooping(); 2103 2104 /** 2105 * Sets the volume on this player. 2106 * This API is recommended for balancing the output of audio streams 2107 * within an application. Unless you are writing an application to 2108 * control user settings, this API should be used in preference to 2109 * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of 2110 * a particular type. Note that the passed volume values are raw scalars in range 0.0 to 1.0. 2111 * UI controls should be scaled logarithmically. 2112 * 2113 * @param leftVolume left volume scalar 2114 * @param rightVolume right volume scalar 2115 */ 2116 /* 2117 * FIXME: Merge this into javadoc comment above when setVolume(float) is not @hide. 2118 * The single parameter form below is preferred if the channel volumes don't need 2119 * to be set independently. 2120 */ 2121 public void setVolume(float leftVolume, float rightVolume) { 2122 baseSetVolume(leftVolume, rightVolume); 2123 } 2124 2125 @Override 2126 void playerSetVolume(boolean muting, float leftVolume, float rightVolume) { 2127 _setVolume(muting ? 0.0f : leftVolume, muting ? 0.0f : rightVolume); 2128 } 2129 2130 private native void _setVolume(float leftVolume, float rightVolume); 2131 2132 /** 2133 * Similar, excepts sets volume of all channels to same value. 2134 * @hide 2135 */ 2136 public void setVolume(float volume) { 2137 setVolume(volume, volume); 2138 } 2139 2140 /** 2141 * Sets the audio session ID. 2142 * 2143 * @param sessionId the audio session ID. 2144 * The audio session ID is a system wide unique identifier for the audio stream played by 2145 * this MediaPlayer instance. 2146 * The primary use of the audio session ID is to associate audio effects to a particular 2147 * instance of MediaPlayer: if an audio session ID is provided when creating an audio effect, 2148 * this effect will be applied only to the audio content of media players within the same 2149 * audio session and not to the output mix. 2150 * When created, a MediaPlayer instance automatically generates its own audio session ID. 2151 * However, it is possible to force this player to be part of an already existing audio session 2152 * by calling this method. 2153 * This method must be called before one of the overloaded <code> setDataSource </code> methods. 2154 * @throws IllegalStateException if it is called in an invalid state 2155 */ 2156 public native void setAudioSessionId(int sessionId) throws IllegalArgumentException, IllegalStateException; 2157 2158 /** 2159 * Returns the audio session ID. 2160 * 2161 * @return the audio session ID. {@see #setAudioSessionId(int)} 2162 * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer was contructed. 2163 */ 2164 public native int getAudioSessionId(); 2165 2166 /** 2167 * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation 2168 * effect which can be applied on any sound source that directs a certain amount of its 2169 * energy to this effect. This amount is defined by setAuxEffectSendLevel(). 2170 * See {@link #setAuxEffectSendLevel(float)}. 2171 * <p>After creating an auxiliary effect (e.g. 2172 * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with 2173 * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method 2174 * to attach the player to the effect. 2175 * <p>To detach the effect from the player, call this method with a null effect id. 2176 * <p>This method must be called after one of the overloaded <code> setDataSource </code> 2177 * methods. 2178 * @param effectId system wide unique id of the effect to attach 2179 */ 2180 public native void attachAuxEffect(int effectId); 2181 2182 2183 /** 2184 * Sets the send level of the player to the attached auxiliary effect. 2185 * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. 2186 * <p>By default the send level is 0, so even if an effect is attached to the player 2187 * this method must be called for the effect to be applied. 2188 * <p>Note that the passed level value is a raw scalar. UI controls should be scaled 2189 * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, 2190 * so an appropriate conversion from linear UI input x to level is: 2191 * x == 0 -> level = 0 2192 * 0 < x <= R -> level = 10^(72*(x-R)/20/R) 2193 * @param level send level scalar 2194 */ 2195 public void setAuxEffectSendLevel(float level) { 2196 baseSetAuxEffectSendLevel(level); 2197 } 2198 2199 @Override 2200 int playerSetAuxEffectSendLevel(boolean muting, float level) { 2201 _setAuxEffectSendLevel(muting ? 0.0f : level); 2202 return AudioSystem.SUCCESS; 2203 } 2204 2205 private native void _setAuxEffectSendLevel(float level); 2206 2207 /* 2208 * @param request Parcel destinated to the media player. The 2209 * Interface token must be set to the IMediaPlayer 2210 * one to be routed correctly through the system. 2211 * @param reply[out] Parcel that will contain the reply. 2212 * @return The status code. 2213 */ 2214 private native final int native_invoke(Parcel request, Parcel reply); 2215 2216 2217 /* 2218 * @param update_only If true fetch only the set of metadata that have 2219 * changed since the last invocation of getMetadata. 2220 * The set is built using the unfiltered 2221 * notifications the native player sent to the 2222 * MediaPlayerService during that period of 2223 * time. If false, all the metadatas are considered. 2224 * @param apply_filter If true, once the metadata set has been built based on 2225 * the value update_only, the current filter is applied. 2226 * @param reply[out] On return contains the serialized 2227 * metadata. Valid only if the call was successful. 2228 * @return The status code. 2229 */ 2230 private native final boolean native_getMetadata(boolean update_only, 2231 boolean apply_filter, 2232 Parcel reply); 2233 2234 /* 2235 * @param request Parcel with the 2 serialized lists of allowed 2236 * metadata types followed by the one to be 2237 * dropped. Each list starts with an integer 2238 * indicating the number of metadata type elements. 2239 * @return The status code. 2240 */ 2241 private native final int native_setMetadataFilter(Parcel request); 2242 2243 private static native final void native_init(); 2244 private native final void native_setup(Object mediaplayer_this); 2245 private native final void native_finalize(); 2246 2247 /** 2248 * Class for MediaPlayer to return each audio/video/subtitle track's metadata. 2249 * 2250 * @see android.media.MediaPlayer#getTrackInfo 2251 */ 2252 static public class TrackInfo implements Parcelable { 2253 /** 2254 * Gets the track type. 2255 * @return TrackType which indicates if the track is video, audio, timed text. 2256 */ 2257 public int getTrackType() { 2258 return mTrackType; 2259 } 2260 2261 /** 2262 * Gets the language code of the track. 2263 * @return a language code in either way of ISO-639-1 or ISO-639-2. 2264 * When the language is unknown or could not be determined, 2265 * ISO-639-2 language code, "und", is returned. 2266 */ 2267 public String getLanguage() { 2268 String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); 2269 return language == null ? "und" : language; 2270 } 2271 2272 /** 2273 * Gets the {@link MediaFormat} of the track. If the format is 2274 * unknown or could not be determined, null is returned. 2275 */ 2276 public MediaFormat getFormat() { 2277 if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT 2278 || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 2279 return mFormat; 2280 } 2281 return null; 2282 } 2283 2284 public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; 2285 public static final int MEDIA_TRACK_TYPE_VIDEO = 1; 2286 public static final int MEDIA_TRACK_TYPE_AUDIO = 2; 2287 public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; 2288 public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; 2289 public static final int MEDIA_TRACK_TYPE_METADATA = 5; 2290 2291 final int mTrackType; 2292 final MediaFormat mFormat; 2293 2294 TrackInfo(Parcel in) { 2295 mTrackType = in.readInt(); 2296 // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat 2297 // even for audio/video tracks, meaning we only set the mime and language. 2298 String mime = in.readString(); 2299 String language = in.readString(); 2300 mFormat = MediaFormat.createSubtitleFormat(mime, language); 2301 2302 if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 2303 mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); 2304 mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); 2305 mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); 2306 } 2307 } 2308 2309 /** @hide */ 2310 TrackInfo(int type, MediaFormat format) { 2311 mTrackType = type; 2312 mFormat = format; 2313 } 2314 2315 /** 2316 * {@inheritDoc} 2317 */ 2318 @Override 2319 public int describeContents() { 2320 return 0; 2321 } 2322 2323 /** 2324 * {@inheritDoc} 2325 */ 2326 @Override 2327 public void writeToParcel(Parcel dest, int flags) { 2328 dest.writeInt(mTrackType); 2329 dest.writeString(getLanguage()); 2330 2331 if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { 2332 dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); 2333 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); 2334 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); 2335 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); 2336 } 2337 } 2338 2339 @Override 2340 public String toString() { 2341 StringBuilder out = new StringBuilder(128); 2342 out.append(getClass().getName()); 2343 out.append('{'); 2344 switch (mTrackType) { 2345 case MEDIA_TRACK_TYPE_VIDEO: 2346 out.append("VIDEO"); 2347 break; 2348 case MEDIA_TRACK_TYPE_AUDIO: 2349 out.append("AUDIO"); 2350 break; 2351 case MEDIA_TRACK_TYPE_TIMEDTEXT: 2352 out.append("TIMEDTEXT"); 2353 break; 2354 case MEDIA_TRACK_TYPE_SUBTITLE: 2355 out.append("SUBTITLE"); 2356 break; 2357 default: 2358 out.append("UNKNOWN"); 2359 break; 2360 } 2361 out.append(", " + mFormat.toString()); 2362 out.append("}"); 2363 return out.toString(); 2364 } 2365 2366 /** 2367 * Used to read a TrackInfo from a Parcel. 2368 */ 2369 static final Parcelable.Creator<TrackInfo> CREATOR 2370 = new Parcelable.Creator<TrackInfo>() { 2371 @Override 2372 public TrackInfo createFromParcel(Parcel in) { 2373 return new TrackInfo(in); 2374 } 2375 2376 @Override 2377 public TrackInfo[] newArray(int size) { 2378 return new TrackInfo[size]; 2379 } 2380 }; 2381 2382 }; 2383 2384 // We would like domain specific classes with more informative names than the `first` and `second` 2385 // in generic Pair, but we would also like to avoid creating new/trivial classes. As a compromise 2386 // we document the meanings of `first` and `second` here: 2387 // 2388 // Pair.first - inband track index; non-null iff representing an inband track. 2389 // Pair.second - a SubtitleTrack registered with mSubtitleController; non-null iff representing 2390 // an inband subtitle track or any out-of-band track (subtitle or timedtext). 2391 private Vector<Pair<Integer, SubtitleTrack>> mIndexTrackPairs = new Vector<>(); 2392 private BitSet mInbandTrackIndices = new BitSet(); 2393 2394 /** 2395 * Returns an array of track information. 2396 * 2397 * @return Array of track info. The total number of tracks is the array length. 2398 * Must be called again if an external timed text source has been added after any of the 2399 * addTimedTextSource methods are called. 2400 * @throws IllegalStateException if it is called in an invalid state. 2401 */ 2402 public TrackInfo[] getTrackInfo() throws IllegalStateException { 2403 TrackInfo trackInfo[] = getInbandTrackInfo(); 2404 // add out-of-band tracks 2405 synchronized (mIndexTrackPairs) { 2406 TrackInfo allTrackInfo[] = new TrackInfo[mIndexTrackPairs.size()]; 2407 for (int i = 0; i < allTrackInfo.length; i++) { 2408 Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); 2409 if (p.first != null) { 2410 // inband track 2411 allTrackInfo[i] = trackInfo[p.first]; 2412 } else { 2413 SubtitleTrack track = p.second; 2414 allTrackInfo[i] = new TrackInfo(track.getTrackType(), track.getFormat()); 2415 } 2416 } 2417 return allTrackInfo; 2418 } 2419 } 2420 2421 private TrackInfo[] getInbandTrackInfo() throws IllegalStateException { 2422 Parcel request = Parcel.obtain(); 2423 Parcel reply = Parcel.obtain(); 2424 try { 2425 request.writeInterfaceToken(IMEDIA_PLAYER); 2426 request.writeInt(INVOKE_ID_GET_TRACK_INFO); 2427 invoke(request, reply); 2428 TrackInfo trackInfo[] = reply.createTypedArray(TrackInfo.CREATOR); 2429 return trackInfo; 2430 } finally { 2431 request.recycle(); 2432 reply.recycle(); 2433 } 2434 } 2435 2436 /* Do not change these values without updating their counterparts 2437 * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp! 2438 */ 2439 /** 2440 * MIME type for SubRip (SRT) container. Used in addTimedTextSource APIs. 2441 */ 2442 public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; 2443 2444 /** 2445 * MIME type for WebVTT subtitle data. 2446 * @hide 2447 */ 2448 public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt"; 2449 2450 /** 2451 * MIME type for CEA-608 closed caption data. 2452 * @hide 2453 */ 2454 public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = "text/cea-608"; 2455 2456 /** 2457 * MIME type for CEA-708 closed caption data. 2458 * @hide 2459 */ 2460 public static final String MEDIA_MIMETYPE_TEXT_CEA_708 = "text/cea-708"; 2461 2462 /* 2463 * A helper function to check if the mime type is supported by media framework. 2464 */ 2465 private static boolean availableMimeTypeForExternalSource(String mimeType) { 2466 if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) { 2467 return true; 2468 } 2469 return false; 2470 } 2471 2472 private SubtitleController mSubtitleController; 2473 2474 /** @hide */ 2475 public void setSubtitleAnchor( 2476 SubtitleController controller, 2477 SubtitleController.Anchor anchor) { 2478 // TODO: create SubtitleController in MediaPlayer 2479 mSubtitleController = controller; 2480 mSubtitleController.setAnchor(anchor); 2481 } 2482 2483 /** 2484 * The private version of setSubtitleAnchor is used internally to set mSubtitleController if 2485 * necessary when clients don't provide their own SubtitleControllers using the public version 2486 * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one). 2487 */ 2488 private synchronized void setSubtitleAnchor() { 2489 if ((mSubtitleController == null) && (ActivityThread.currentApplication() != null)) { 2490 final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread"); 2491 thread.start(); 2492 Handler handler = new Handler(thread.getLooper()); 2493 handler.post(new Runnable() { 2494 @Override 2495 public void run() { 2496 Context context = ActivityThread.currentApplication(); 2497 mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer.this); 2498 mSubtitleController.setAnchor(new Anchor() { 2499 @Override 2500 public void setSubtitleWidget(RenderingWidget subtitleWidget) { 2501 } 2502 2503 @Override 2504 public Looper getSubtitleLooper() { 2505 return Looper.getMainLooper(); 2506 } 2507 }); 2508 thread.getLooper().quitSafely(); 2509 } 2510 }); 2511 try { 2512 thread.join(); 2513 } catch (InterruptedException e) { 2514 Thread.currentThread().interrupt(); 2515 Log.w(TAG, "failed to join SetSubtitleAnchorThread"); 2516 } 2517 } 2518 } 2519 2520 private int mSelectedSubtitleTrackIndex = -1; 2521 private Vector<InputStream> mOpenSubtitleSources; 2522 2523 private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() { 2524 @Override 2525 public void onSubtitleData(MediaPlayer mp, SubtitleData data) { 2526 int index = data.getTrackIndex(); 2527 synchronized (mIndexTrackPairs) { 2528 for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { 2529 if (p.first != null && p.first == index && p.second != null) { 2530 // inband subtitle track that owns data 2531 SubtitleTrack track = p.second; 2532 track.onData(data); 2533 } 2534 } 2535 } 2536 } 2537 }; 2538 2539 /** @hide */ 2540 @Override 2541 public void onSubtitleTrackSelected(SubtitleTrack track) { 2542 if (mSelectedSubtitleTrackIndex >= 0) { 2543 try { 2544 selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false); 2545 } catch (IllegalStateException e) { 2546 } 2547 mSelectedSubtitleTrackIndex = -1; 2548 } 2549 setOnSubtitleDataListener(null); 2550 if (track == null) { 2551 return; 2552 } 2553 2554 synchronized (mIndexTrackPairs) { 2555 for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { 2556 if (p.first != null && p.second == track) { 2557 // inband subtitle track that is selected 2558 mSelectedSubtitleTrackIndex = p.first; 2559 break; 2560 } 2561 } 2562 } 2563 2564 if (mSelectedSubtitleTrackIndex >= 0) { 2565 try { 2566 selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true); 2567 } catch (IllegalStateException e) { 2568 } 2569 setOnSubtitleDataListener(mSubtitleDataListener); 2570 } 2571 // no need to select out-of-band tracks 2572 } 2573 2574 /** @hide */ 2575 public void addSubtitleSource(InputStream is, MediaFormat format) 2576 throws IllegalStateException 2577 { 2578 final InputStream fIs = is; 2579 final MediaFormat fFormat = format; 2580 2581 if (is != null) { 2582 // Ensure all input streams are closed. It is also a handy 2583 // way to implement timeouts in the future. 2584 synchronized(mOpenSubtitleSources) { 2585 mOpenSubtitleSources.add(is); 2586 } 2587 } else { 2588 Log.w(TAG, "addSubtitleSource called with null InputStream"); 2589 } 2590 2591 getMediaTimeProvider(); 2592 2593 // process each subtitle in its own thread 2594 final HandlerThread thread = new HandlerThread("SubtitleReadThread", 2595 Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); 2596 thread.start(); 2597 Handler handler = new Handler(thread.getLooper()); 2598 handler.post(new Runnable() { 2599 private int addTrack() { 2600 if (fIs == null || mSubtitleController == null) { 2601 return MEDIA_INFO_UNSUPPORTED_SUBTITLE; 2602 } 2603 2604 SubtitleTrack track = mSubtitleController.addTrack(fFormat); 2605 if (track == null) { 2606 return MEDIA_INFO_UNSUPPORTED_SUBTITLE; 2607 } 2608 2609 // TODO: do the conversion in the subtitle track 2610 Scanner scanner = new Scanner(fIs, "UTF-8"); 2611 String contents = scanner.useDelimiter("\\A").next(); 2612 synchronized(mOpenSubtitleSources) { 2613 mOpenSubtitleSources.remove(fIs); 2614 } 2615 scanner.close(); 2616 synchronized (mIndexTrackPairs) { 2617 mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); 2618 } 2619 Handler h = mTimeProvider.mEventHandler; 2620 int what = TimeProvider.NOTIFY; 2621 int arg1 = TimeProvider.NOTIFY_TRACK_DATA; 2622 Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, contents.getBytes()); 2623 Message m = h.obtainMessage(what, arg1, 0, trackData); 2624 h.sendMessage(m); 2625 return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; 2626 } 2627 2628 public void run() { 2629 int res = addTrack(); 2630 if (mEventHandler != null) { 2631 Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); 2632 mEventHandler.sendMessage(m); 2633 } 2634 thread.getLooper().quitSafely(); 2635 } 2636 }); 2637 } 2638 2639 private void scanInternalSubtitleTracks() { 2640 setSubtitleAnchor(); 2641 2642 populateInbandTracks(); 2643 2644 if (mSubtitleController != null) { 2645 mSubtitleController.selectDefaultTrack(); 2646 } 2647 } 2648 2649 private void populateInbandTracks() { 2650 TrackInfo[] tracks = getInbandTrackInfo(); 2651 synchronized (mIndexTrackPairs) { 2652 for (int i = 0; i < tracks.length; i++) { 2653 if (mInbandTrackIndices.get(i)) { 2654 continue; 2655 } else { 2656 mInbandTrackIndices.set(i); 2657 } 2658 2659 // newly appeared inband track 2660 if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { 2661 SubtitleTrack track = mSubtitleController.addTrack( 2662 tracks[i].getFormat()); 2663 mIndexTrackPairs.add(Pair.create(i, track)); 2664 } else { 2665 mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(i, null)); 2666 } 2667 } 2668 } 2669 } 2670 2671 /* TODO: Limit the total number of external timed text source to a reasonable number. 2672 */ 2673 /** 2674 * Adds an external timed text source file. 2675 * 2676 * Currently supported format is SubRip with the file extension .srt, case insensitive. 2677 * Note that a single external timed text source may contain multiple tracks in it. 2678 * One can find the total number of available tracks using {@link #getTrackInfo()} to see what 2679 * additional tracks become available after this method call. 2680 * 2681 * @param path The file path of external timed text source file. 2682 * @param mimeType The mime type of the file. Must be one of the mime types listed above. 2683 * @throws IOException if the file cannot be accessed or is corrupted. 2684 * @throws IllegalArgumentException if the mimeType is not supported. 2685 * @throws IllegalStateException if called in an invalid state. 2686 */ 2687 public void addTimedTextSource(String path, String mimeType) 2688 throws IOException, IllegalArgumentException, IllegalStateException { 2689 if (!availableMimeTypeForExternalSource(mimeType)) { 2690 final String msg = "Illegal mimeType for timed text source: " + mimeType; 2691 throw new IllegalArgumentException(msg); 2692 } 2693 2694 File file = new File(path); 2695 if (file.exists()) { 2696 FileInputStream is = new FileInputStream(file); 2697 FileDescriptor fd = is.getFD(); 2698 addTimedTextSource(fd, mimeType); 2699 is.close(); 2700 } else { 2701 // We do not support the case where the path is not a file. 2702 throw new IOException(path); 2703 } 2704 } 2705 2706 /** 2707 * Adds an external timed text source file (Uri). 2708 * 2709 * Currently supported format is SubRip with the file extension .srt, case insensitive. 2710 * Note that a single external timed text source may contain multiple tracks in it. 2711 * One can find the total number of available tracks using {@link #getTrackInfo()} to see what 2712 * additional tracks become available after this method call. 2713 * 2714 * @param context the Context to use when resolving the Uri 2715 * @param uri the Content URI of the data you want to play 2716 * @param mimeType The mime type of the file. Must be one of the mime types listed above. 2717 * @throws IOException if the file cannot be accessed or is corrupted. 2718 * @throws IllegalArgumentException if the mimeType is not supported. 2719 * @throws IllegalStateException if called in an invalid state. 2720 */ 2721 public void addTimedTextSource(Context context, Uri uri, String mimeType) 2722 throws IOException, IllegalArgumentException, IllegalStateException { 2723 String scheme = uri.getScheme(); 2724 if(scheme == null || scheme.equals("file")) { 2725 addTimedTextSource(uri.getPath(), mimeType); 2726 return; 2727 } 2728 2729 AssetFileDescriptor fd = null; 2730 try { 2731 ContentResolver resolver = context.getContentResolver(); 2732 fd = resolver.openAssetFileDescriptor(uri, "r"); 2733 if (fd == null) { 2734 return; 2735 } 2736 addTimedTextSource(fd.getFileDescriptor(), mimeType); 2737 return; 2738 } catch (SecurityException ex) { 2739 } catch (IOException ex) { 2740 } finally { 2741 if (fd != null) { 2742 fd.close(); 2743 } 2744 } 2745 } 2746 2747 /** 2748 * Adds an external timed text source file (FileDescriptor). 2749 * 2750 * It is the caller's responsibility to close the file descriptor. 2751 * It is safe to do so as soon as this call returns. 2752 * 2753 * Currently supported format is SubRip. Note that a single external timed text source may 2754 * contain multiple tracks in it. One can find the total number of available tracks 2755 * using {@link #getTrackInfo()} to see what additional tracks become available 2756 * after this method call. 2757 * 2758 * @param fd the FileDescriptor for the file you want to play 2759 * @param mimeType The mime type of the file. Must be one of the mime types listed above. 2760 * @throws IllegalArgumentException if the mimeType is not supported. 2761 * @throws IllegalStateException if called in an invalid state. 2762 */ 2763 public void addTimedTextSource(FileDescriptor fd, String mimeType) 2764 throws IllegalArgumentException, IllegalStateException { 2765 // intentionally less than LONG_MAX 2766 addTimedTextSource(fd, 0, 0x7ffffffffffffffL, mimeType); 2767 } 2768 2769 /** 2770 * Adds an external timed text file (FileDescriptor). 2771 * 2772 * It is the caller's responsibility to close the file descriptor. 2773 * It is safe to do so as soon as this call returns. 2774 * 2775 * Currently supported format is SubRip. Note that a single external timed text source may 2776 * contain multiple tracks in it. One can find the total number of available tracks 2777 * using {@link #getTrackInfo()} to see what additional tracks become available 2778 * after this method call. 2779 * 2780 * @param fd the FileDescriptor for the file you want to play 2781 * @param offset the offset into the file where the data to be played starts, in bytes 2782 * @param length the length in bytes of the data to be played 2783 * @param mime The mime type of the file. Must be one of the mime types listed above. 2784 * @throws IllegalArgumentException if the mimeType is not supported. 2785 * @throws IllegalStateException if called in an invalid state. 2786 */ 2787 public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) 2788 throws IllegalArgumentException, IllegalStateException { 2789 if (!availableMimeTypeForExternalSource(mime)) { 2790 throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime); 2791 } 2792 2793 final FileDescriptor dupedFd; 2794 try { 2795 dupedFd = Libcore.os.dup(fd); 2796 } catch (ErrnoException ex) { 2797 Log.e(TAG, ex.getMessage(), ex); 2798 throw new RuntimeException(ex); 2799 } 2800 2801 final MediaFormat fFormat = new MediaFormat(); 2802 fFormat.setString(MediaFormat.KEY_MIME, mime); 2803 fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1); 2804 2805 // A MediaPlayer created by a VideoView should already have its mSubtitleController set. 2806 if (mSubtitleController == null) { 2807 setSubtitleAnchor(); 2808 } 2809 2810 if (!mSubtitleController.hasRendererFor(fFormat)) { 2811 // test and add not atomic 2812 Context context = ActivityThread.currentApplication(); 2813 mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler)); 2814 } 2815 final SubtitleTrack track = mSubtitleController.addTrack(fFormat); 2816 synchronized (mIndexTrackPairs) { 2817 mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); 2818 } 2819 2820 getMediaTimeProvider(); 2821 2822 final long offset2 = offset; 2823 final long length2 = length; 2824 final HandlerThread thread = new HandlerThread( 2825 "TimedTextReadThread", 2826 Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); 2827 thread.start(); 2828 Handler handler = new Handler(thread.getLooper()); 2829 handler.post(new Runnable() { 2830 private int addTrack() { 2831 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 2832 try { 2833 Libcore.os.lseek(dupedFd, offset2, OsConstants.SEEK_SET); 2834 byte[] buffer = new byte[4096]; 2835 for (long total = 0; total < length2;) { 2836 int bytesToRead = (int) Math.min(buffer.length, length2 - total); 2837 int bytes = IoBridge.read(dupedFd, buffer, 0, bytesToRead); 2838 if (bytes < 0) { 2839 break; 2840 } else { 2841 bos.write(buffer, 0, bytes); 2842 total += bytes; 2843 } 2844 } 2845 Handler h = mTimeProvider.mEventHandler; 2846 int what = TimeProvider.NOTIFY; 2847 int arg1 = TimeProvider.NOTIFY_TRACK_DATA; 2848 Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, bos.toByteArray()); 2849 Message m = h.obtainMessage(what, arg1, 0, trackData); 2850 h.sendMessage(m); 2851 return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; 2852 } catch (Exception e) { 2853 Log.e(TAG, e.getMessage(), e); 2854 return MEDIA_INFO_TIMED_TEXT_ERROR; 2855 } finally { 2856 try { 2857 Libcore.os.close(dupedFd); 2858 } catch (ErrnoException e) { 2859 Log.e(TAG, e.getMessage(), e); 2860 } 2861 } 2862 } 2863 2864 public void run() { 2865 int res = addTrack(); 2866 if (mEventHandler != null) { 2867 Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); 2868 mEventHandler.sendMessage(m); 2869 } 2870 thread.getLooper().quitSafely(); 2871 } 2872 }); 2873 } 2874 2875 /** 2876 * Returns the index of the audio, video, or subtitle track currently selected for playback, 2877 * The return value is an index into the array returned by {@link #getTrackInfo()}, and can 2878 * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. 2879 * 2880 * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, 2881 * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or 2882 * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} 2883 * @return index of the audio, video, or subtitle track currently selected for playback; 2884 * a negative integer is returned when there is no selected track for {@code trackType} or 2885 * when {@code trackType} is not one of audio, video, or subtitle. 2886 * @throws IllegalStateException if called after {@link #release()} 2887 * 2888 * @see #getTrackInfo() 2889 * @see #selectTrack(int) 2890 * @see #deselectTrack(int) 2891 */ 2892 public int getSelectedTrack(int trackType) throws IllegalStateException { 2893 if (mSubtitleController != null 2894 && (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE 2895 || trackType == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT)) { 2896 SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack(); 2897 if (subtitleTrack != null) { 2898 synchronized (mIndexTrackPairs) { 2899 for (int i = 0; i < mIndexTrackPairs.size(); i++) { 2900 Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); 2901 if (p.second == subtitleTrack && subtitleTrack.getTrackType() == trackType) { 2902 return i; 2903 } 2904 } 2905 } 2906 } 2907 } 2908 2909 Parcel request = Parcel.obtain(); 2910 Parcel reply = Parcel.obtain(); 2911 try { 2912 request.writeInterfaceToken(IMEDIA_PLAYER); 2913 request.writeInt(INVOKE_ID_GET_SELECTED_TRACK); 2914 request.writeInt(trackType); 2915 invoke(request, reply); 2916 int inbandTrackIndex = reply.readInt(); 2917 synchronized (mIndexTrackPairs) { 2918 for (int i = 0; i < mIndexTrackPairs.size(); i++) { 2919 Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); 2920 if (p.first != null && p.first == inbandTrackIndex) { 2921 return i; 2922 } 2923 } 2924 } 2925 return -1; 2926 } finally { 2927 request.recycle(); 2928 reply.recycle(); 2929 } 2930 } 2931 2932 /** 2933 * Selects a track. 2934 * <p> 2935 * If a MediaPlayer is in invalid state, it throws an IllegalStateException exception. 2936 * If a MediaPlayer is in <em>Started</em> state, the selected track is presented immediately. 2937 * If a MediaPlayer is not in Started state, it just marks the track to be played. 2938 * </p> 2939 * <p> 2940 * In any valid state, if it is called multiple times on the same type of track (ie. Video, 2941 * Audio, Timed Text), the most recent one will be chosen. 2942 * </p> 2943 * <p> 2944 * The first audio and video tracks are selected by default if available, even though 2945 * this method is not called. However, no timed text track will be selected until 2946 * this function is called. 2947 * </p> 2948 * <p> 2949 * Currently, only timed text tracks or audio tracks can be selected via this method. 2950 * In addition, the support for selecting an audio track at runtime is pretty limited 2951 * in that an audio track can only be selected in the <em>Prepared</em> state. 2952 * </p> 2953 * @param index the index of the track to be selected. The valid range of the index 2954 * is 0..total number of track - 1. The total number of tracks as well as the type of 2955 * each individual track can be found by calling {@link #getTrackInfo()} method. 2956 * @throws IllegalStateException if called in an invalid state. 2957 * 2958 * @see android.media.MediaPlayer#getTrackInfo 2959 */ 2960 public void selectTrack(int index) throws IllegalStateException { 2961 selectOrDeselectTrack(index, true /* select */); 2962 } 2963 2964 /** 2965 * Deselect a track. 2966 * <p> 2967 * Currently, the track must be a timed text track and no audio or video tracks can be 2968 * deselected. If the timed text track identified by index has not been 2969 * selected before, it throws an exception. 2970 * </p> 2971 * @param index the index of the track to be deselected. The valid range of the index 2972 * is 0..total number of tracks - 1. The total number of tracks as well as the type of 2973 * each individual track can be found by calling {@link #getTrackInfo()} method. 2974 * @throws IllegalStateException if called in an invalid state. 2975 * 2976 * @see android.media.MediaPlayer#getTrackInfo 2977 */ 2978 public void deselectTrack(int index) throws IllegalStateException { 2979 selectOrDeselectTrack(index, false /* select */); 2980 } 2981 2982 private void selectOrDeselectTrack(int index, boolean select) 2983 throws IllegalStateException { 2984 // handle subtitle track through subtitle controller 2985 populateInbandTracks(); 2986 2987 Pair<Integer,SubtitleTrack> p = null; 2988 try { 2989 p = mIndexTrackPairs.get(index); 2990 } catch (ArrayIndexOutOfBoundsException e) { 2991 // ignore bad index 2992 return; 2993 } 2994 2995 SubtitleTrack track = p.second; 2996 if (track == null) { 2997 // inband (de)select 2998 selectOrDeselectInbandTrack(p.first, select); 2999 return; 3000 } 3001 3002 if (mSubtitleController == null) { 3003 return; 3004 } 3005 3006 if (!select) { 3007 // out-of-band deselect 3008 if (mSubtitleController.getSelectedTrack() == track) { 3009 mSubtitleController.selectTrack(null); 3010 } else { 3011 Log.w(TAG, "trying to deselect track that was not selected"); 3012 } 3013 return; 3014 } 3015 3016 // out-of-band select 3017 if (track.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) { 3018 int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); 3019 synchronized (mIndexTrackPairs) { 3020 if (ttIndex >= 0 && ttIndex < mIndexTrackPairs.size()) { 3021 Pair<Integer,SubtitleTrack> p2 = mIndexTrackPairs.get(ttIndex); 3022 if (p2.first != null && p2.second == null) { 3023 // deselect inband counterpart 3024 selectOrDeselectInbandTrack(p2.first, false); 3025 } 3026 } 3027 } 3028 } 3029 mSubtitleController.selectTrack(track); 3030 } 3031 3032 private void selectOrDeselectInbandTrack(int index, boolean select) 3033 throws IllegalStateException { 3034 Parcel request = Parcel.obtain(); 3035 Parcel reply = Parcel.obtain(); 3036 try { 3037 request.writeInterfaceToken(IMEDIA_PLAYER); 3038 request.writeInt(select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK); 3039 request.writeInt(index); 3040 invoke(request, reply); 3041 } finally { 3042 request.recycle(); 3043 reply.recycle(); 3044 } 3045 } 3046 3047 3048 /** 3049 * @param reply Parcel with audio/video duration info for battery 3050 tracking usage 3051 * @return The status code. 3052 * {@hide} 3053 */ 3054 public native static int native_pullBatteryData(Parcel reply); 3055 3056 /** 3057 * Sets the target UDP re-transmit endpoint for the low level player. 3058 * Generally, the address portion of the endpoint is an IP multicast 3059 * address, although a unicast address would be equally valid. When a valid 3060 * retransmit endpoint has been set, the media player will not decode and 3061 * render the media presentation locally. Instead, the player will attempt 3062 * to re-multiplex its media data using the Android@Home RTP profile and 3063 * re-transmit to the target endpoint. Receiver devices (which may be 3064 * either the same as the transmitting device or different devices) may 3065 * instantiate, prepare, and start a receiver player using a setDataSource 3066 * URL of the form... 3067 * 3068 * aahRX://<multicastIP>:<port> 3069 * 3070 * to receive, decode and render the re-transmitted content. 3071 * 3072 * setRetransmitEndpoint may only be called before setDataSource has been 3073 * called; while the player is in the Idle state. 3074 * 3075 * @param endpoint the address and UDP port of the re-transmission target or 3076 * null if no re-transmission is to be performed. 3077 * @throws IllegalStateException if it is called in an invalid state 3078 * @throws IllegalArgumentException if the retransmit endpoint is supplied, 3079 * but invalid. 3080 * 3081 * {@hide} pending API council 3082 */ 3083 public void setRetransmitEndpoint(InetSocketAddress endpoint) 3084 throws IllegalStateException, IllegalArgumentException 3085 { 3086 String addrString = null; 3087 int port = 0; 3088 3089 if (null != endpoint) { 3090 addrString = endpoint.getAddress().getHostAddress(); 3091 port = endpoint.getPort(); 3092 } 3093 3094 int ret = native_setRetransmitEndpoint(addrString, port); 3095 if (ret != 0) { 3096 throw new IllegalArgumentException("Illegal re-transmit endpoint; native ret " + ret); 3097 } 3098 } 3099 3100 private native final int native_setRetransmitEndpoint(String addrString, int port); 3101 3102 @Override 3103 protected void finalize() { 3104 baseRelease(); 3105 native_finalize(); 3106 } 3107 3108 /* Do not change these values without updating their counterparts 3109 * in include/media/mediaplayer.h! 3110 */ 3111 private static final int MEDIA_NOP = 0; // interface test message 3112 private static final int MEDIA_PREPARED = 1; 3113 private static final int MEDIA_PLAYBACK_COMPLETE = 2; 3114 private static final int MEDIA_BUFFERING_UPDATE = 3; 3115 private static final int MEDIA_SEEK_COMPLETE = 4; 3116 private static final int MEDIA_SET_VIDEO_SIZE = 5; 3117 private static final int MEDIA_STARTED = 6; 3118 private static final int MEDIA_PAUSED = 7; 3119 private static final int MEDIA_STOPPED = 8; 3120 private static final int MEDIA_SKIPPED = 9; 3121 private static final int MEDIA_TIMED_TEXT = 99; 3122 private static final int MEDIA_ERROR = 100; 3123 private static final int MEDIA_INFO = 200; 3124 private static final int MEDIA_SUBTITLE_DATA = 201; 3125 private static final int MEDIA_META_DATA = 202; 3126 private static final int MEDIA_DRM_INFO = 210; 3127 3128 private TimeProvider mTimeProvider; 3129 3130 /** @hide */ 3131 public MediaTimeProvider getMediaTimeProvider() { 3132 if (mTimeProvider == null) { 3133 mTimeProvider = new TimeProvider(this); 3134 } 3135 return mTimeProvider; 3136 } 3137 3138 private class EventHandler extends Handler 3139 { 3140 private MediaPlayer mMediaPlayer; 3141 3142 public EventHandler(MediaPlayer mp, Looper looper) { 3143 super(looper); 3144 mMediaPlayer = mp; 3145 } 3146 3147 @Override 3148 public void handleMessage(Message msg) { 3149 if (mMediaPlayer.mNativeContext == 0) { 3150 Log.w(TAG, "mediaplayer went away with unhandled events"); 3151 return; 3152 } 3153 switch(msg.what) { 3154 case MEDIA_PREPARED: 3155 try { 3156 scanInternalSubtitleTracks(); 3157 } catch (RuntimeException e) { 3158 // send error message instead of crashing; 3159 // send error message instead of inlining a call to onError 3160 // to avoid code duplication. 3161 Message msg2 = obtainMessage( 3162 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); 3163 sendMessage(msg2); 3164 } 3165 3166 OnPreparedListener onPreparedListener = mOnPreparedListener; 3167 if (onPreparedListener != null) 3168 onPreparedListener.onPrepared(mMediaPlayer); 3169 return; 3170 3171 case MEDIA_DRM_INFO: 3172 Log.v(TAG, "MEDIA_DRM_INFO " + mOnDrmInfoHandlerDelegate); 3173 3174 if (msg.obj == null) { 3175 Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); 3176 } else if (msg.obj instanceof Parcel) { 3177 // The parcel was parsed already in postEventFromNative 3178 DrmInfo drmInfo = null; 3179 3180 OnDrmInfoHandlerDelegate onDrmInfoHandlerDelegate; 3181 synchronized (mDrmLock) { 3182 if (mOnDrmInfoHandlerDelegate != null && mDrmInfo != null) { 3183 drmInfo = mDrmInfo.makeCopy(); 3184 } 3185 // local copy while keeping the lock 3186 onDrmInfoHandlerDelegate = mOnDrmInfoHandlerDelegate; 3187 } 3188 3189 // notifying the client outside the lock 3190 if (onDrmInfoHandlerDelegate != null) { 3191 onDrmInfoHandlerDelegate.notifyClient(drmInfo); 3192 } 3193 } else { 3194 Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj); 3195 } 3196 return; 3197 3198 case MEDIA_PLAYBACK_COMPLETE: 3199 { 3200 mOnCompletionInternalListener.onCompletion(mMediaPlayer); 3201 OnCompletionListener onCompletionListener = mOnCompletionListener; 3202 if (onCompletionListener != null) 3203 onCompletionListener.onCompletion(mMediaPlayer); 3204 } 3205 stayAwake(false); 3206 return; 3207 3208 case MEDIA_STOPPED: 3209 { 3210 TimeProvider timeProvider = mTimeProvider; 3211 if (timeProvider != null) { 3212 timeProvider.onStopped(); 3213 } 3214 } 3215 break; 3216 3217 case MEDIA_STARTED: 3218 case MEDIA_PAUSED: 3219 { 3220 TimeProvider timeProvider = mTimeProvider; 3221 if (timeProvider != null) { 3222 timeProvider.onPaused(msg.what == MEDIA_PAUSED); 3223 } 3224 } 3225 break; 3226 3227 case MEDIA_BUFFERING_UPDATE: 3228 OnBufferingUpdateListener onBufferingUpdateListener = mOnBufferingUpdateListener; 3229 if (onBufferingUpdateListener != null) 3230 onBufferingUpdateListener.onBufferingUpdate(mMediaPlayer, msg.arg1); 3231 return; 3232 3233 case MEDIA_SEEK_COMPLETE: 3234 OnSeekCompleteListener onSeekCompleteListener = mOnSeekCompleteListener; 3235 if (onSeekCompleteListener != null) { 3236 onSeekCompleteListener.onSeekComplete(mMediaPlayer); 3237 } 3238 // fall through 3239 3240 case MEDIA_SKIPPED: 3241 { 3242 TimeProvider timeProvider = mTimeProvider; 3243 if (timeProvider != null) { 3244 timeProvider.onSeekComplete(mMediaPlayer); 3245 } 3246 } 3247 return; 3248 3249 case MEDIA_SET_VIDEO_SIZE: 3250 OnVideoSizeChangedListener onVideoSizeChangedListener = mOnVideoSizeChangedListener; 3251 if (onVideoSizeChangedListener != null) { 3252 onVideoSizeChangedListener.onVideoSizeChanged( 3253 mMediaPlayer, msg.arg1, msg.arg2); 3254 } 3255 return; 3256 3257 case MEDIA_ERROR: 3258 Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); 3259 boolean error_was_handled = false; 3260 OnErrorListener onErrorListener = mOnErrorListener; 3261 if (onErrorListener != null) { 3262 error_was_handled = onErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2); 3263 } 3264 { 3265 mOnCompletionInternalListener.onCompletion(mMediaPlayer); 3266 OnCompletionListener onCompletionListener = mOnCompletionListener; 3267 if (onCompletionListener != null && ! error_was_handled) { 3268 onCompletionListener.onCompletion(mMediaPlayer); 3269 } 3270 } 3271 stayAwake(false); 3272 return; 3273 3274 case MEDIA_INFO: 3275 switch (msg.arg1) { 3276 case MEDIA_INFO_VIDEO_TRACK_LAGGING: 3277 Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); 3278 break; 3279 case MEDIA_INFO_METADATA_UPDATE: 3280 try { 3281 scanInternalSubtitleTracks(); 3282 } catch (RuntimeException e) { 3283 Message msg2 = obtainMessage( 3284 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); 3285 sendMessage(msg2); 3286 } 3287 // fall through 3288 3289 case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: 3290 msg.arg1 = MEDIA_INFO_METADATA_UPDATE; 3291 // update default track selection 3292 if (mSubtitleController != null) { 3293 mSubtitleController.selectDefaultTrack(); 3294 } 3295 break; 3296 case MEDIA_INFO_BUFFERING_START: 3297 case MEDIA_INFO_BUFFERING_END: 3298 TimeProvider timeProvider = mTimeProvider; 3299 if (timeProvider != null) { 3300 timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START); 3301 } 3302 break; 3303 } 3304 3305 OnInfoListener onInfoListener = mOnInfoListener; 3306 if (onInfoListener != null) { 3307 onInfoListener.onInfo(mMediaPlayer, msg.arg1, msg.arg2); 3308 } 3309 // No real default action so far. 3310 return; 3311 case MEDIA_TIMED_TEXT: 3312 OnTimedTextListener onTimedTextListener = mOnTimedTextListener; 3313 if (onTimedTextListener == null) 3314 return; 3315 if (msg.obj == null) { 3316 onTimedTextListener.onTimedText(mMediaPlayer, null); 3317 } else { 3318 if (msg.obj instanceof Parcel) { 3319 Parcel parcel = (Parcel)msg.obj; 3320 TimedText text = new TimedText(parcel); 3321 parcel.recycle(); 3322 onTimedTextListener.onTimedText(mMediaPlayer, text); 3323 } 3324 } 3325 return; 3326 3327 case MEDIA_SUBTITLE_DATA: 3328 OnSubtitleDataListener onSubtitleDataListener = mOnSubtitleDataListener; 3329 if (onSubtitleDataListener == null) { 3330 return; 3331 } 3332 if (msg.obj instanceof Parcel) { 3333 Parcel parcel = (Parcel) msg.obj; 3334 SubtitleData data = new SubtitleData(parcel); 3335 parcel.recycle(); 3336 onSubtitleDataListener.onSubtitleData(mMediaPlayer, data); 3337 } 3338 return; 3339 3340 case MEDIA_META_DATA: 3341 OnTimedMetaDataAvailableListener onTimedMetaDataAvailableListener = 3342 mOnTimedMetaDataAvailableListener; 3343 if (onTimedMetaDataAvailableListener == null) { 3344 return; 3345 } 3346 if (msg.obj instanceof Parcel) { 3347 Parcel parcel = (Parcel) msg.obj; 3348 TimedMetaData data = TimedMetaData.createTimedMetaDataFromParcel(parcel); 3349 parcel.recycle(); 3350 onTimedMetaDataAvailableListener.onTimedMetaDataAvailable(mMediaPlayer, data); 3351 } 3352 return; 3353 3354 case MEDIA_NOP: // interface test message - ignore 3355 break; 3356 3357 default: 3358 Log.e(TAG, "Unknown message type " + msg.what); 3359 return; 3360 } 3361 } 3362 } 3363 3364 /* 3365 * Called from native code when an interesting event happens. This method 3366 * just uses the EventHandler system to post the event back to the main app thread. 3367 * We use a weak reference to the original MediaPlayer object so that the native 3368 * code is safe from the object disappearing from underneath it. (This is 3369 * the cookie passed to native_setup().) 3370 */ 3371 private static void postEventFromNative(Object mediaplayer_ref, 3372 int what, int arg1, int arg2, Object obj) 3373 { 3374 MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get(); 3375 if (mp == null) { 3376 return; 3377 } 3378 3379 switch (what) { 3380 case MEDIA_INFO: 3381 if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) { 3382 // this acquires the wakelock if needed, and sets the client side state 3383 mp.start(); 3384 } 3385 break; 3386 3387 case MEDIA_DRM_INFO: 3388 // We need to derive mDrmInfo before prepare() returns so processing it here 3389 // before the notification is sent to EventHandler below. EventHandler runs in the 3390 // notification looper so its handleMessage might process the event after prepare() 3391 // has returned. 3392 Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO"); 3393 if (obj instanceof Parcel) { 3394 Parcel parcel = (Parcel)obj; 3395 DrmInfo drmInfo = new DrmInfo(parcel); 3396 synchronized (mp.mDrmLock) { 3397 mp.mDrmInfo = drmInfo; 3398 } 3399 } else { 3400 Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj); 3401 } 3402 break; 3403 3404 case MEDIA_PREPARED: 3405 // By this time, we've learned about DrmInfo's presence or absence. This is meant 3406 // mainly for prepareAsync() use case. For prepare(), this still can run to a race 3407 // condition b/c MediaPlayerNative releases the prepare() lock before calling notify 3408 // so we also set mDrmInfoResolved in prepare(). 3409 synchronized (mp.mDrmLock) { 3410 mp.mDrmInfoResolved = true; 3411 } 3412 break; 3413 3414 } 3415 3416 if (mp.mEventHandler != null) { 3417 Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); 3418 mp.mEventHandler.sendMessage(m); 3419 } 3420 } 3421 3422 /** 3423 * Interface definition for a callback to be invoked when the media 3424 * source is ready for playback. 3425 */ 3426 public interface OnPreparedListener 3427 { 3428 /** 3429 * Called when the media file is ready for playback. 3430 * 3431 * @param mp the MediaPlayer that is ready for playback 3432 */ 3433 void onPrepared(MediaPlayer mp); 3434 } 3435 3436 /** 3437 * Register a callback to be invoked when the media source is ready 3438 * for playback. 3439 * 3440 * @param listener the callback that will be run 3441 */ 3442 public void setOnPreparedListener(OnPreparedListener listener) 3443 { 3444 mOnPreparedListener = listener; 3445 } 3446 3447 private OnPreparedListener mOnPreparedListener; 3448 3449 /** 3450 * Interface definition for a callback to be invoked when playback of 3451 * a media source has completed. 3452 */ 3453 public interface OnCompletionListener 3454 { 3455 /** 3456 * Called when the end of a media source is reached during playback. 3457 * 3458 * @param mp the MediaPlayer that reached the end of the file 3459 */ 3460 void onCompletion(MediaPlayer mp); 3461 } 3462 3463 /** 3464 * Register a callback to be invoked when the end of a media source 3465 * has been reached during playback. 3466 * 3467 * @param listener the callback that will be run 3468 */ 3469 public void setOnCompletionListener(OnCompletionListener listener) 3470 { 3471 mOnCompletionListener = listener; 3472 } 3473 3474 private OnCompletionListener mOnCompletionListener; 3475 3476 /** 3477 * @hide 3478 * Internal completion listener to update PlayerBase of the play state. Always "registered". 3479 */ 3480 private final OnCompletionListener mOnCompletionInternalListener = new OnCompletionListener() { 3481 @Override 3482 public void onCompletion(MediaPlayer mp) { 3483 baseStop(); 3484 } 3485 }; 3486 3487 /** 3488 * Interface definition of a callback to be invoked indicating buffering 3489 * status of a media resource being streamed over the network. 3490 */ 3491 public interface OnBufferingUpdateListener 3492 { 3493 /** 3494 * Called to update status in buffering a media stream received through 3495 * progressive HTTP download. The received buffering percentage 3496 * indicates how much of the content has been buffered or played. 3497 * For example a buffering update of 80 percent when half the content 3498 * has already been played indicates that the next 30 percent of the 3499 * content to play has been buffered. 3500 * 3501 * @param mp the MediaPlayer the update pertains to 3502 * @param percent the percentage (0-100) of the content 3503 * that has been buffered or played thus far 3504 */ 3505 void onBufferingUpdate(MediaPlayer mp, int percent); 3506 } 3507 3508 /** 3509 * Register a callback to be invoked when the status of a network 3510 * stream's buffer has changed. 3511 * 3512 * @param listener the callback that will be run. 3513 */ 3514 public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener) 3515 { 3516 mOnBufferingUpdateListener = listener; 3517 } 3518 3519 private OnBufferingUpdateListener mOnBufferingUpdateListener; 3520 3521 /** 3522 * Interface definition of a callback to be invoked indicating 3523 * the completion of a seek operation. 3524 */ 3525 public interface OnSeekCompleteListener 3526 { 3527 /** 3528 * Called to indicate the completion of a seek operation. 3529 * 3530 * @param mp the MediaPlayer that issued the seek operation 3531 */ 3532 public void onSeekComplete(MediaPlayer mp); 3533 } 3534 3535 /** 3536 * Register a callback to be invoked when a seek operation has been 3537 * completed. 3538 * 3539 * @param listener the callback that will be run 3540 */ 3541 public void setOnSeekCompleteListener(OnSeekCompleteListener listener) 3542 { 3543 mOnSeekCompleteListener = listener; 3544 } 3545 3546 private OnSeekCompleteListener mOnSeekCompleteListener; 3547 3548 /** 3549 * Interface definition of a callback to be invoked when the 3550 * video size is first known or updated 3551 */ 3552 public interface OnVideoSizeChangedListener 3553 { 3554 /** 3555 * Called to indicate the video size 3556 * 3557 * The video size (width and height) could be 0 if there was no video, 3558 * no display surface was set, or the value was not determined yet. 3559 * 3560 * @param mp the MediaPlayer associated with this callback 3561 * @param width the width of the video 3562 * @param height the height of the video 3563 */ 3564 public void onVideoSizeChanged(MediaPlayer mp, int width, int height); 3565 } 3566 3567 /** 3568 * Register a callback to be invoked when the video size is 3569 * known or updated. 3570 * 3571 * @param listener the callback that will be run 3572 */ 3573 public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener listener) 3574 { 3575 mOnVideoSizeChangedListener = listener; 3576 } 3577 3578 private OnVideoSizeChangedListener mOnVideoSizeChangedListener; 3579 3580 /** 3581 * Interface definition of a callback to be invoked when a 3582 * timed text is available for display. 3583 */ 3584 public interface OnTimedTextListener 3585 { 3586 /** 3587 * Called to indicate an avaliable timed text 3588 * 3589 * @param mp the MediaPlayer associated with this callback 3590 * @param text the timed text sample which contains the text 3591 * needed to be displayed and the display format. 3592 */ 3593 public void onTimedText(MediaPlayer mp, TimedText text); 3594 } 3595 3596 /** 3597 * Register a callback to be invoked when a timed text is available 3598 * for display. 3599 * 3600 * @param listener the callback that will be run 3601 */ 3602 public void setOnTimedTextListener(OnTimedTextListener listener) 3603 { 3604 mOnTimedTextListener = listener; 3605 } 3606 3607 private OnTimedTextListener mOnTimedTextListener; 3608 3609 /** 3610 * Interface definition of a callback to be invoked when a 3611 * track has data available. 3612 * 3613 * @hide 3614 */ 3615 public interface OnSubtitleDataListener 3616 { 3617 public void onSubtitleData(MediaPlayer mp, SubtitleData data); 3618 } 3619 3620 /** 3621 * Register a callback to be invoked when a track has data available. 3622 * 3623 * @param listener the callback that will be run 3624 * 3625 * @hide 3626 */ 3627 public void setOnSubtitleDataListener(OnSubtitleDataListener listener) 3628 { 3629 mOnSubtitleDataListener = listener; 3630 } 3631 3632 private OnSubtitleDataListener mOnSubtitleDataListener; 3633 3634 /** 3635 * Interface definition of a callback to be invoked when a 3636 * track has timed metadata available. 3637 * 3638 * @see MediaPlayer#setOnTimedMetaDataAvailableListener(OnTimedMetaDataAvailableListener) 3639 */ 3640 public interface OnTimedMetaDataAvailableListener 3641 { 3642 /** 3643 * Called to indicate avaliable timed metadata 3644 * <p> 3645 * This method will be called as timed metadata is extracted from the media, 3646 * in the same order as it occurs in the media. The timing of this event is 3647 * not controlled by the associated timestamp. 3648 * 3649 * @param mp the MediaPlayer associated with this callback 3650 * @param data the timed metadata sample associated with this event 3651 */ 3652 public void onTimedMetaDataAvailable(MediaPlayer mp, TimedMetaData data); 3653 } 3654 3655 /** 3656 * Register a callback to be invoked when a selected track has timed metadata available. 3657 * <p> 3658 * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates 3659 * {@link TimedMetaData}. 3660 * 3661 * @see MediaPlayer#selectTrack(int) 3662 * @see MediaPlayer.OnTimedMetaDataAvailableListener 3663 * @see TimedMetaData 3664 * 3665 * @param listener the callback that will be run 3666 */ 3667 public void setOnTimedMetaDataAvailableListener(OnTimedMetaDataAvailableListener listener) 3668 { 3669 mOnTimedMetaDataAvailableListener = listener; 3670 } 3671 3672 private OnTimedMetaDataAvailableListener mOnTimedMetaDataAvailableListener; 3673 3674 /* Do not change these values without updating their counterparts 3675 * in include/media/mediaplayer.h! 3676 */ 3677 /** Unspecified media player error. 3678 * @see android.media.MediaPlayer.OnErrorListener 3679 */ 3680 public static final int MEDIA_ERROR_UNKNOWN = 1; 3681 3682 /** Media server died. In this case, the application must release the 3683 * MediaPlayer object and instantiate a new one. 3684 * @see android.media.MediaPlayer.OnErrorListener 3685 */ 3686 public static final int MEDIA_ERROR_SERVER_DIED = 100; 3687 3688 /** The video is streamed and its container is not valid for progressive 3689 * playback i.e the video's index (e.g moov atom) is not at the start of the 3690 * file. 3691 * @see android.media.MediaPlayer.OnErrorListener 3692 */ 3693 public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; 3694 3695 /** File or network related operation errors. */ 3696 public static final int MEDIA_ERROR_IO = -1004; 3697 /** Bitstream is not conforming to the related coding standard or file spec. */ 3698 public static final int MEDIA_ERROR_MALFORMED = -1007; 3699 /** Bitstream is conforming to the related coding standard or file spec, but 3700 * the media framework does not support the feature. */ 3701 public static final int MEDIA_ERROR_UNSUPPORTED = -1010; 3702 /** Some operation takes too long to complete, usually more than 3-5 seconds. */ 3703 public static final int MEDIA_ERROR_TIMED_OUT = -110; 3704 3705 /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in 3706 * system/core/include/utils/Errors.h 3707 * @see android.media.MediaPlayer.OnErrorListener 3708 * @hide 3709 */ 3710 public static final int MEDIA_ERROR_SYSTEM = -2147483648; 3711 3712 /** 3713 * Interface definition of a callback to be invoked when there 3714 * has been an error during an asynchronous operation (other errors 3715 * will throw exceptions at method call time). 3716 */ 3717 public interface OnErrorListener 3718 { 3719 /** 3720 * Called to indicate an error. 3721 * 3722 * @param mp the MediaPlayer the error pertains to 3723 * @param what the type of error that has occurred: 3724 * <ul> 3725 * <li>{@link #MEDIA_ERROR_UNKNOWN} 3726 * <li>{@link #MEDIA_ERROR_SERVER_DIED} 3727 * </ul> 3728 * @param extra an extra code, specific to the error. Typically 3729 * implementation dependent. 3730 * <ul> 3731 * <li>{@link #MEDIA_ERROR_IO} 3732 * <li>{@link #MEDIA_ERROR_MALFORMED} 3733 * <li>{@link #MEDIA_ERROR_UNSUPPORTED} 3734 * <li>{@link #MEDIA_ERROR_TIMED_OUT} 3735 * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error. 3736 * </ul> 3737 * @return True if the method handled the error, false if it didn't. 3738 * Returning false, or not having an OnErrorListener at all, will 3739 * cause the OnCompletionListener to be called. 3740 */ 3741 boolean onError(MediaPlayer mp, int what, int extra); 3742 } 3743 3744 /** 3745 * Register a callback to be invoked when an error has happened 3746 * during an asynchronous operation. 3747 * 3748 * @param listener the callback that will be run 3749 */ 3750 public void setOnErrorListener(OnErrorListener listener) 3751 { 3752 mOnErrorListener = listener; 3753 } 3754 3755 private OnErrorListener mOnErrorListener; 3756 3757 3758 /* Do not change these values without updating their counterparts 3759 * in include/media/mediaplayer.h! 3760 */ 3761 /** Unspecified media player info. 3762 * @see android.media.MediaPlayer.OnInfoListener 3763 */ 3764 public static final int MEDIA_INFO_UNKNOWN = 1; 3765 3766 /** The player was started because it was used as the next player for another 3767 * player, which just completed playback. 3768 * @see android.media.MediaPlayer.OnInfoListener 3769 * @hide 3770 */ 3771 public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; 3772 3773 /** The player just pushed the very first video frame for rendering. 3774 * @see android.media.MediaPlayer.OnInfoListener 3775 */ 3776 public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; 3777 3778 /** The video is too complex for the decoder: it can't decode frames fast 3779 * enough. Possibly only the audio plays fine at this stage. 3780 * @see android.media.MediaPlayer.OnInfoListener 3781 */ 3782 public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; 3783 3784 /** MediaPlayer is temporarily pausing playback internally in order to 3785 * buffer more data. 3786 * @see android.media.MediaPlayer.OnInfoListener 3787 */ 3788 public static final int MEDIA_INFO_BUFFERING_START = 701; 3789 3790 /** MediaPlayer is resuming playback after filling buffers. 3791 * @see android.media.MediaPlayer.OnInfoListener 3792 */ 3793 public static final int MEDIA_INFO_BUFFERING_END = 702; 3794 3795 /** Estimated network bandwidth information (kbps) is available; currently this event fires 3796 * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END} 3797 * when playing network files. 3798 * @see android.media.MediaPlayer.OnInfoListener 3799 * @hide 3800 */ 3801 public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; 3802 3803 /** Bad interleaving means that a media has been improperly interleaved or 3804 * not interleaved at all, e.g has all the video samples first then all the 3805 * audio ones. Video is playing but a lot of disk seeks may be happening. 3806 * @see android.media.MediaPlayer.OnInfoListener 3807 */ 3808 public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; 3809 3810 /** The media cannot be seeked (e.g live stream) 3811 * @see android.media.MediaPlayer.OnInfoListener 3812 */ 3813 public static final int MEDIA_INFO_NOT_SEEKABLE = 801; 3814 3815 /** A new set of metadata is available. 3816 * @see android.media.MediaPlayer.OnInfoListener 3817 */ 3818 public static final int MEDIA_INFO_METADATA_UPDATE = 802; 3819 3820 /** A new set of external-only metadata is available. Used by 3821 * JAVA framework to avoid triggering track scanning. 3822 * @hide 3823 */ 3824 public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; 3825 3826 /** Informs that audio is not playing. Note that playback of the video 3827 * is not interrupted. 3828 * @see android.media.MediaPlayer.OnInfoListener 3829 */ 3830 public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; 3831 3832 /** Informs that video is not playing. Note that playback of the audio 3833 * is not interrupted. 3834 * @see android.media.MediaPlayer.OnInfoListener 3835 */ 3836 public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; 3837 3838 /** Failed to handle timed text track properly. 3839 * @see android.media.MediaPlayer.OnInfoListener 3840 * 3841 * {@hide} 3842 */ 3843 public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; 3844 3845 /** Subtitle track was not supported by the media framework. 3846 * @see android.media.MediaPlayer.OnInfoListener 3847 */ 3848 public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; 3849 3850 /** Reading the subtitle track takes too long. 3851 * @see android.media.MediaPlayer.OnInfoListener 3852 */ 3853 public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; 3854 3855 /** 3856 * Interface definition of a callback to be invoked to communicate some 3857 * info and/or warning about the media or its playback. 3858 */ 3859 public interface OnInfoListener 3860 { 3861 /** 3862 * Called to indicate an info or a warning. 3863 * 3864 * @param mp the MediaPlayer the info pertains to. 3865 * @param what the type of info or warning. 3866 * <ul> 3867 * <li>{@link #MEDIA_INFO_UNKNOWN} 3868 * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING} 3869 * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START} 3870 * <li>{@link #MEDIA_INFO_BUFFERING_START} 3871 * <li>{@link #MEDIA_INFO_BUFFERING_END} 3872 * <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> - 3873 * bandwidth information is available (as <code>extra</code> kbps) 3874 * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING} 3875 * <li>{@link #MEDIA_INFO_NOT_SEEKABLE} 3876 * <li>{@link #MEDIA_INFO_METADATA_UPDATE} 3877 * <li>{@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE} 3878 * <li>{@link #MEDIA_INFO_SUBTITLE_TIMED_OUT} 3879 * </ul> 3880 * @param extra an extra code, specific to the info. Typically 3881 * implementation dependent. 3882 * @return True if the method handled the info, false if it didn't. 3883 * Returning false, or not having an OnInfoListener at all, will 3884 * cause the info to be discarded. 3885 */ 3886 boolean onInfo(MediaPlayer mp, int what, int extra); 3887 } 3888 3889 /** 3890 * Register a callback to be invoked when an info/warning is available. 3891 * 3892 * @param listener the callback that will be run 3893 */ 3894 public void setOnInfoListener(OnInfoListener listener) 3895 { 3896 mOnInfoListener = listener; 3897 } 3898 3899 private OnInfoListener mOnInfoListener; 3900 3901 // Modular DRM begin 3902 3903 /** 3904 * Interface definition of a callback to be invoked when the app 3905 * can do DRM configuration (get/set properties) before the session 3906 * is opened. This facilitates configuration of the properties, like 3907 * 'securityLevel', which has to be set after DRM scheme creation but 3908 * before the DRM session is opened. 3909 * 3910 * The only allowed DRM calls in this listener are {@code getDrmPropertyString} 3911 * and {@code setDrmPropertyString}. 3912 * 3913 */ 3914 public interface OnDrmConfigHelper 3915 { 3916 /** 3917 * Called to give the app the opportunity to configure DRM before the session is created 3918 * 3919 * @param mp the {@code MediaPlayer} associated with this callback 3920 */ 3921 public void onDrmConfig(MediaPlayer mp); 3922 } 3923 3924 /** 3925 * Register a callback to be invoked for configuration of the DRM object before 3926 * the session is created. 3927 * The callback will be invoked synchronously during the execution 3928 * of {@link #prepareDrm(UUID uuid)}. 3929 * 3930 * @param listener the callback that will be run 3931 */ 3932 public void setOnDrmConfigHelper(OnDrmConfigHelper listener) 3933 { 3934 synchronized (mDrmLock) { 3935 mOnDrmConfigHelper = listener; 3936 } // synchronized 3937 } 3938 3939 private OnDrmConfigHelper mOnDrmConfigHelper; 3940 3941 /** 3942 * Interface definition of a callback to be invoked when the 3943 * DRM info becomes available 3944 */ 3945 public interface OnDrmInfoListener 3946 { 3947 /** 3948 * Called to indicate DRM info is available 3949 * 3950 * @param mp the {@code MediaPlayer} associated with this callback 3951 * @param drmInfo DRM info of the source including PSSH, and subset 3952 * of crypto schemes supported by this device 3953 */ 3954 public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo); 3955 } 3956 3957 /** 3958 * Register a callback to be invoked when the DRM info is 3959 * known. 3960 * 3961 * @param listener the callback that will be run 3962 */ 3963 public void setOnDrmInfoListener(OnDrmInfoListener listener) 3964 { 3965 setOnDrmInfoListener(listener, null); 3966 } 3967 3968 /** 3969 * Register a callback to be invoked when the DRM info is 3970 * known. 3971 * 3972 * @param listener the callback that will be run 3973 */ 3974 public void setOnDrmInfoListener(OnDrmInfoListener listener, Handler handler) 3975 { 3976 synchronized (mDrmLock) { 3977 if (listener != null) { 3978 mOnDrmInfoHandlerDelegate = new OnDrmInfoHandlerDelegate(this, listener, handler); 3979 } else { 3980 mOnDrmInfoHandlerDelegate = null; 3981 } 3982 } // synchronized 3983 } 3984 3985 private OnDrmInfoHandlerDelegate mOnDrmInfoHandlerDelegate; 3986 3987 3988 /** 3989 * The status codes for {@link OnDrmPreparedListener#onDrmPrepared} listener. 3990 * <p> 3991 * 3992 * DRM preparation has succeeded. 3993 */ 3994 public static final int PREPARE_DRM_STATUS_SUCCESS = 0; 3995 3996 /** 3997 * The device required DRM provisioning but couldn't reach the provisioning server. 3998 */ 3999 public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; 4000 4001 /** 4002 * The device required DRM provisioning but the provisioning server denied the request. 4003 */ 4004 public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; 4005 4006 /** 4007 * The DRM preparation has failed . 4008 */ 4009 public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; 4010 4011 4012 /** @hide */ 4013 @IntDef({ 4014 PREPARE_DRM_STATUS_SUCCESS, 4015 PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, 4016 PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, 4017 PREPARE_DRM_STATUS_PREPARATION_ERROR, 4018 }) 4019 @Retention(RetentionPolicy.SOURCE) 4020 public @interface PrepareDrmStatusCode {} 4021 4022 /** 4023 * Interface definition of a callback to notify the app when the 4024 * DRM is ready for key request/response 4025 */ 4026 public interface OnDrmPreparedListener 4027 { 4028 /** 4029 * Called to notify the app that prepareDrm is finished and ready for key request/response 4030 * 4031 * @param mp the {@code MediaPlayer} associated with this callback 4032 * @param status the result of DRM preparation which can be 4033 * {@link #PREPARE_DRM_STATUS_SUCCESS}, 4034 * {@link #PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR}, 4035 * {@link #PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR}, or 4036 * {@link #PREPARE_DRM_STATUS_PREPARATION_ERROR}. 4037 */ 4038 public void onDrmPrepared(MediaPlayer mp, @PrepareDrmStatusCode int status); 4039 } 4040 4041 /** 4042 * Register a callback to be invoked when the DRM object is prepared. 4043 * 4044 * @param listener the callback that will be run 4045 */ 4046 public void setOnDrmPreparedListener(OnDrmPreparedListener listener) 4047 { 4048 setOnDrmPreparedListener(listener, null); 4049 } 4050 4051 /** 4052 * Register a callback to be invoked when the DRM object is prepared. 4053 * 4054 * @param listener the callback that will be run 4055 * @param handler the Handler that will receive the callback 4056 */ 4057 public void setOnDrmPreparedListener(OnDrmPreparedListener listener, Handler handler) 4058 { 4059 synchronized (mDrmLock) { 4060 if (listener != null) { 4061 mOnDrmPreparedHandlerDelegate = new OnDrmPreparedHandlerDelegate(this, 4062 listener, handler); 4063 } else { 4064 mOnDrmPreparedHandlerDelegate = null; 4065 } 4066 } // synchronized 4067 } 4068 4069 private OnDrmPreparedHandlerDelegate mOnDrmPreparedHandlerDelegate; 4070 4071 4072 private class OnDrmInfoHandlerDelegate { 4073 private MediaPlayer mMediaPlayer; 4074 private OnDrmInfoListener mOnDrmInfoListener; 4075 private Handler mHandler; 4076 4077 OnDrmInfoHandlerDelegate(MediaPlayer mp, OnDrmInfoListener listener, Handler handler) { 4078 mMediaPlayer = mp; 4079 mOnDrmInfoListener = listener; 4080 4081 // find the looper for our new event handler 4082 if (handler != null) { 4083 mHandler = handler; 4084 } else { 4085 // handler == null 4086 // Will let OnDrmInfoListener be called in mEventHandler similar to other 4087 // legacy notifications. This is because MEDIA_DRM_INFO's notification has to be 4088 // sent before MEDIA_PREPARED's (i.e., in the same order they are issued by 4089 // mediaserver). As a result, the callback has to be called directly by 4090 // EventHandler.handleMessage similar to onPrepared. 4091 } 4092 } 4093 4094 void notifyClient(DrmInfo drmInfo) { 4095 if (mHandler != null) { 4096 mHandler.post(new Runnable() { 4097 @Override 4098 public void run() { 4099 mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo); 4100 } 4101 }); 4102 } 4103 else { // no handler: direct call by mEventHandler 4104 mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo); 4105 } 4106 } 4107 } 4108 4109 private class OnDrmPreparedHandlerDelegate { 4110 private MediaPlayer mMediaPlayer; 4111 private OnDrmPreparedListener mOnDrmPreparedListener; 4112 private Handler mHandler; 4113 4114 OnDrmPreparedHandlerDelegate(MediaPlayer mp, OnDrmPreparedListener listener, 4115 Handler handler) { 4116 mMediaPlayer = mp; 4117 mOnDrmPreparedListener = listener; 4118 4119 // find the looper for our new event handler 4120 if (handler != null) { 4121 mHandler = handler; 4122 } else if (mEventHandler != null) { 4123 // Otherwise, use mEventHandler 4124 mHandler = mEventHandler; 4125 } else { 4126 Log.e(TAG, "OnDrmPreparedHandlerDelegate: Unexpected null mEventHandler"); 4127 } 4128 } 4129 4130 void notifyClient(int status) { 4131 if (mHandler != null) { 4132 mHandler.post(new Runnable() { 4133 @Override 4134 public void run() { 4135 mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, status); 4136 } 4137 }); 4138 } else { 4139 Log.e(TAG, "OnDrmPreparedHandlerDelegate:notifyClient: Unexpected null mHandler"); 4140 } 4141 } 4142 } 4143 4144 /** 4145 * Retrieves the DRM Info associated with the current source 4146 * 4147 * @throws IllegalStateException if called before prepare() 4148 */ 4149 public DrmInfo getDrmInfo() 4150 { 4151 DrmInfo drmInfo = null; 4152 4153 // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet; 4154 // regardless below returns drmInfo anyway instead of raising an exception 4155 synchronized (mDrmLock) { 4156 if (!mDrmInfoResolved && mDrmInfo == null) { 4157 final String msg = "The Player has not been prepared yet"; 4158 Log.v(TAG, msg); 4159 throw new IllegalStateException(msg); 4160 } 4161 4162 if (mDrmInfo != null) { 4163 drmInfo = mDrmInfo.makeCopy(); 4164 } 4165 } // synchronized 4166 4167 return drmInfo; 4168 } 4169 4170 4171 /** 4172 * Prepares the DRM for the current source 4173 * <p> 4174 * If {@code OnDrmConfigHelper} is registered, it will be called during 4175 * preparation to allow configuration of the DRM properties before opening the 4176 * DRM session. Note that the callback is called synchronously in the thread that called 4177 * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString} 4178 * and {@code setDrmPropertyString} calls and refrain from any lengthy operation. 4179 * <p> 4180 * If the device has not been provisioned before, this call also provisions the device 4181 * which involves accessing the provisioning server and can take a variable time to 4182 * complete depending on the network connectivity. 4183 * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking 4184 * mode by launching the provisioning in the background and returning. The listener 4185 * will be called when provisioning and preparation has finished. If a 4186 * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning 4187 * and preparation has finished, i.e., runs in blocking mode. 4188 * <p> 4189 * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM 4190 * session being ready. The application should not make any assumption about its call 4191 * sequence (e.g., before or after prepareDrm returns), or the thread context that will 4192 * execute the listener (unless the listener is registered with a handler thread). 4193 * <p> 4194 * 4195 * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved 4196 * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}. 4197 * 4198 * @throws IllegalStateException if called before prepare(), or the DRM was 4199 * prepared already 4200 * @throws UnsupportedSchemeException if the crypto scheme is not supported 4201 * @throws ResourceBusyException if required DRM resources are in use 4202 * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a 4203 * network error 4204 * @throws ProvisioningServerErrorException if provisioning is required but failed due to 4205 * the request denied by the provisioning server 4206 */ 4207 public void prepareDrm(@NonNull UUID uuid) 4208 throws UnsupportedSchemeException, ResourceBusyException, 4209 ProvisioningNetworkErrorException, ProvisioningServerErrorException 4210 { 4211 Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper); 4212 4213 boolean allDoneWithoutProvisioning = false; 4214 // get a snapshot as we'll use them outside the lock 4215 OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate = null; 4216 4217 synchronized (mDrmLock) { 4218 4219 // only allowing if tied to a protected source; might relax for releasing offline keys 4220 if (mDrmInfo == null) { 4221 final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " + 4222 "DRM info be retrieved before this call."; 4223 Log.e(TAG, msg); 4224 throw new IllegalStateException(msg); 4225 } 4226 4227 if (mActiveDrmScheme) { 4228 final String msg = "prepareDrm(): Wrong usage: There is already " + 4229 "an active DRM scheme with " + mDrmUUID; 4230 Log.e(TAG, msg); 4231 throw new IllegalStateException(msg); 4232 } 4233 4234 if (mPrepareDrmInProgress) { 4235 final String msg = "prepareDrm(): Wrong usage: There is already " + 4236 "a pending prepareDrm call."; 4237 Log.e(TAG, msg); 4238 throw new IllegalStateException(msg); 4239 } 4240 4241 if (mDrmProvisioningInProgress) { 4242 final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress."; 4243 Log.e(TAG, msg); 4244 throw new IllegalStateException(msg); 4245 } 4246 4247 // shouldn't need this; just for safeguard 4248 cleanDrmObj(); 4249 4250 mPrepareDrmInProgress = true; 4251 // local copy while the lock is held 4252 onDrmPreparedHandlerDelegate = mOnDrmPreparedHandlerDelegate; 4253 4254 try { 4255 // only creating the DRM object to allow pre-openSession configuration 4256 prepareDrm_createDrmStep(uuid); 4257 } catch (Exception e) { 4258 Log.w(TAG, "prepareDrm(): Exception ", e); 4259 mPrepareDrmInProgress = false; 4260 throw e; 4261 } 4262 4263 mDrmConfigAllowed = true; 4264 } // synchronized 4265 4266 4267 // call the callback outside the lock 4268 if (mOnDrmConfigHelper != null) { 4269 mOnDrmConfigHelper.onDrmConfig(this); 4270 } 4271 4272 synchronized (mDrmLock) { 4273 mDrmConfigAllowed = false; 4274 boolean earlyExit = false; 4275 4276 try { 4277 prepareDrm_openSessionStep(uuid); 4278 4279 mDrmUUID = uuid; 4280 mActiveDrmScheme = true; 4281 4282 allDoneWithoutProvisioning = true; 4283 } catch (IllegalStateException e) { 4284 final String msg = "prepareDrm(): Wrong usage: The player must be " + 4285 "in the prepared state to call prepareDrm()."; 4286 Log.e(TAG, msg); 4287 earlyExit = true; 4288 throw new IllegalStateException(msg); 4289 } catch (NotProvisionedException e) { 4290 Log.w(TAG, "prepareDrm: NotProvisionedException"); 4291 4292 // handle provisioning internally; it'll reset mPrepareDrmInProgress 4293 int result = HandleProvisioninig(uuid); 4294 4295 // if blocking mode, we're already done; 4296 // if non-blocking mode, we attempted to launch background provisioning 4297 if (result != PREPARE_DRM_STATUS_SUCCESS) { 4298 earlyExit = true; 4299 String msg; 4300 4301 switch (result) { 4302 case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR: 4303 msg = "prepareDrm: Provisioning was required but failed " + 4304 "due to a network error."; 4305 Log.e(TAG, msg); 4306 throw new ProvisioningNetworkErrorException(msg); 4307 4308 case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR: 4309 msg = "prepareDrm: Provisioning was required but the request " + 4310 "was denied by the server."; 4311 Log.e(TAG, msg); 4312 throw new ProvisioningServerErrorException(msg); 4313 4314 case PREPARE_DRM_STATUS_PREPARATION_ERROR: 4315 default: // default for safeguard 4316 msg = "prepareDrm: Post-provisioning preparation failed."; 4317 Log.e(TAG, msg); 4318 throw new IllegalStateException(msg); 4319 } 4320 } 4321 // nothing else to do; 4322 // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup 4323 } catch (Exception e) { 4324 Log.e(TAG, "prepareDrm: Exception " + e); 4325 earlyExit = true; 4326 throw e; 4327 } finally { 4328 if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception 4329 mPrepareDrmInProgress = false; 4330 } 4331 if (earlyExit) { // cleaning up object if didn't succeed 4332 cleanDrmObj(); 4333 } 4334 } // finally 4335 } // synchronized 4336 4337 4338 // if finished successfully without provisioning, call the callback outside the lock 4339 if (allDoneWithoutProvisioning) { 4340 if (onDrmPreparedHandlerDelegate != null) 4341 onDrmPreparedHandlerDelegate.notifyClient(PREPARE_DRM_STATUS_SUCCESS); 4342 } 4343 4344 } 4345 4346 4347 private native void _releaseDrm(); 4348 4349 /** 4350 * Releases the DRM session 4351 * <p> 4352 * The player has to have an active DRM session and be in stopped, or prepared 4353 * state before this call is made. 4354 * A {@code reset()} call will release the DRM session implicitly. 4355 * 4356 * @throws NoDrmSchemeException if there is no active DRM session to release 4357 */ 4358 public void releaseDrm() 4359 throws NoDrmSchemeException 4360 { 4361 Log.v(TAG, "releaseDrm:"); 4362 4363 synchronized (mDrmLock) { 4364 if (!mActiveDrmScheme) { 4365 Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); 4366 throw new NoDrmSchemeException("releaseDrm: No active DRM scheme to release."); 4367 } 4368 4369 try { 4370 // we don't have the player's state in this layer. The below call raises 4371 // exception if we're in a non-stopped/prepared state. 4372 4373 // for cleaning native/mediaserver crypto object 4374 _releaseDrm(); 4375 4376 // for cleaning client-side MediaDrm object; only called if above has succeeded 4377 cleanDrmObj(); 4378 4379 mActiveDrmScheme = false; 4380 } catch (IllegalStateException e) { 4381 Log.w(TAG, "releaseDrm: Exception ", e); 4382 throw new IllegalStateException("releaseDrm: The player is not in a valid state."); 4383 } catch (Exception e) { 4384 Log.e(TAG, "releaseDrm: Exception ", e); 4385 } 4386 } // synchronized 4387 } 4388 4389 4390 /** 4391 * A key request/response exchange occurs between the app and a license server 4392 * to obtain or release keys used to decrypt encrypted content. 4393 * <p> 4394 * getKeyRequest() is used to obtain an opaque key request byte array that is 4395 * delivered to the license server. The opaque key request byte array is returned 4396 * in KeyRequest.data. The recommended URL to deliver the key request to is 4397 * returned in KeyRequest.defaultUrl. 4398 * <p> 4399 * After the app has received the key request response from the server, 4400 * it should deliver to the response to the DRM engine plugin using the method 4401 * {@link #provideKeyResponse}. 4402 * 4403 * @param keySetId is the key-set identifier of the offline keys being released when keyType is 4404 * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when 4405 * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 4406 * 4407 * @param initData is the container-specific initialization data when the keyType is 4408 * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is 4409 * interpreted based on the mime type provided in the mimeType parameter. It could 4410 * contain, for example, the content ID, key ID or other data obtained from the content 4411 * metadata that is required in generating the key request. 4412 * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. 4413 * 4414 * @param mimeType identifies the mime type of the content 4415 * 4416 * @param keyType specifies the type of the request. The request may be to acquire 4417 * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content 4418 * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired 4419 * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. 4420 * 4421 * @param optionalParameters are included in the key request message to 4422 * allow a client application to provide additional message parameters to the server. 4423 * This may be {@code null} if no additional parameters are to be sent. 4424 * 4425 * @throws NoDrmSchemeException if there is no active DRM session 4426 */ 4427 @NonNull 4428 public MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData, 4429 @Nullable String mimeType, @MediaDrm.KeyType int keyType, 4430 @Nullable Map<String, String> optionalParameters) 4431 throws NoDrmSchemeException 4432 { 4433 Log.v(TAG, "getKeyRequest: " + 4434 " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + 4435 " keyType: " + keyType + " optionalParameters: " + optionalParameters); 4436 4437 synchronized (mDrmLock) { 4438 if (!mActiveDrmScheme) { 4439 Log.e(TAG, "getKeyRequest NoDrmSchemeException"); 4440 throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first."); 4441 } 4442 4443 try { 4444 byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? 4445 mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE 4446 keySetId; // keySetId for KEY_TYPE_RELEASE 4447 4448 HashMap<String, String> hmapOptionalParameters = 4449 (optionalParameters != null) ? 4450 new HashMap<String, String>(optionalParameters) : 4451 null; 4452 4453 MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType, 4454 keyType, hmapOptionalParameters); 4455 Log.v(TAG, "getKeyRequest: --> request: " + request); 4456 4457 return request; 4458 4459 } catch (NotProvisionedException e) { 4460 Log.w(TAG, "getKeyRequest NotProvisionedException: " + 4461 "Unexpected. Shouldn't have reached here."); 4462 throw new IllegalStateException("getKeyRequest: Unexpected provisioning error."); 4463 } catch (Exception e) { 4464 Log.w(TAG, "getKeyRequest Exception " + e); 4465 throw e; 4466 } 4467 4468 } // synchronized 4469 } 4470 4471 4472 /** 4473 * A key response is received from the license server by the app, then it is 4474 * provided to the DRM engine plugin using provideKeyResponse. When the 4475 * response is for an offline key request, a key-set identifier is returned that 4476 * can be used to later restore the keys to a new session with the method 4477 * {@ link # restoreKeys}. 4478 * When the response is for a streaming or release request, null is returned. 4479 * 4480 * @param keySetId When the response is for a release request, keySetId identifies 4481 * the saved key associated with the release request (i.e., the same keySetId 4482 * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the 4483 * response is for either streaming or offline key requests. 4484 * 4485 * @param response the byte array response from the server 4486 * 4487 * @throws NoDrmSchemeException if there is no active DRM session 4488 * @throws DeniedByServerException if the response indicates that the 4489 * server rejected the request 4490 */ 4491 public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) 4492 throws NoDrmSchemeException, DeniedByServerException 4493 { 4494 Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response); 4495 4496 synchronized (mDrmLock) { 4497 4498 if (!mActiveDrmScheme) { 4499 Log.e(TAG, "getKeyRequest NoDrmSchemeException"); 4500 throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first."); 4501 } 4502 4503 try { 4504 byte[] scope = (keySetId == null) ? 4505 mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE 4506 keySetId; // keySetId for KEY_TYPE_RELEASE 4507 4508 byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); 4509 4510 Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response + 4511 " --> " + keySetResult); 4512 4513 4514 return keySetResult; 4515 4516 } catch (NotProvisionedException e) { 4517 Log.w(TAG, "provideKeyResponse NotProvisionedException: " + 4518 "Unexpected. Shouldn't have reached here."); 4519 throw new IllegalStateException("provideKeyResponse: " + 4520 "Unexpected provisioning error."); 4521 } catch (Exception e) { 4522 Log.w(TAG, "provideKeyResponse Exception " + e); 4523 throw e; 4524 } 4525 } // synchronized 4526 } 4527 4528 4529 /** 4530 * Restore persisted offline keys into a new session. keySetId identifies the 4531 * keys to load, obtained from a prior call to {@link #provideKeyResponse}. 4532 * 4533 * @param keySetId identifies the saved key set to restore 4534 */ 4535 public void restoreKeys(@NonNull byte[] keySetId) 4536 throws NoDrmSchemeException 4537 { 4538 Log.v(TAG, "restoreKeys: keySetId: " + keySetId); 4539 4540 synchronized (mDrmLock) { 4541 4542 if (!mActiveDrmScheme) { 4543 Log.w(TAG, "restoreKeys NoDrmSchemeException"); 4544 throw new NoDrmSchemeException("restoreKeys: Has to set a DRM scheme first."); 4545 } 4546 4547 try { 4548 mDrmObj.restoreKeys(mDrmSessionId, keySetId); 4549 } catch (Exception e) { 4550 Log.w(TAG, "restoreKeys Exception " + e); 4551 throw e; 4552 } 4553 4554 } // synchronized 4555 } 4556 4557 4558 /** 4559 * Read a DRM engine plugin String property value, given the property name string. 4560 * <p> 4561 * @param propertyName the property name 4562 * 4563 * Standard fields names are: 4564 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 4565 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 4566 */ 4567 @NonNull 4568 public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName) 4569 throws NoDrmSchemeException 4570 { 4571 Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); 4572 4573 String value; 4574 synchronized (mDrmLock) { 4575 4576 if (!mActiveDrmScheme && !mDrmConfigAllowed) { 4577 Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); 4578 throw new NoDrmSchemeException("getDrmPropertyString: Has to prepareDrm() first."); 4579 } 4580 4581 try { 4582 value = mDrmObj.getPropertyString(propertyName); 4583 } catch (Exception e) { 4584 Log.w(TAG, "getDrmPropertyString Exception " + e); 4585 throw e; 4586 } 4587 } // synchronized 4588 4589 Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value); 4590 4591 return value; 4592 } 4593 4594 4595 /** 4596 * Set a DRM engine plugin String property value. 4597 * <p> 4598 * @param propertyName the property name 4599 * @param value the property value 4600 * 4601 * Standard fields names are: 4602 * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, 4603 * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} 4604 */ 4605 public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName, 4606 @NonNull String value) 4607 throws NoDrmSchemeException 4608 { 4609 Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); 4610 4611 synchronized (mDrmLock) { 4612 4613 if ( !mActiveDrmScheme && !mDrmConfigAllowed ) { 4614 Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); 4615 throw new NoDrmSchemeException("setDrmPropertyString: Has to prepareDrm() first."); 4616 } 4617 4618 try { 4619 mDrmObj.setPropertyString(propertyName, value); 4620 } catch ( Exception e ) { 4621 Log.w(TAG, "setDrmPropertyString Exception " + e); 4622 throw e; 4623 } 4624 } // synchronized 4625 } 4626 4627 public static final class DrmInfo { 4628 private Map<UUID, byte[]> mapPssh; 4629 private UUID[] supportedSchemes; 4630 4631 public Map<UUID, byte[]> getPssh() { 4632 return mapPssh; 4633 } 4634 public UUID[] getSupportedSchemes() { 4635 return supportedSchemes; 4636 } 4637 4638 private DrmInfo(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) { 4639 mapPssh = Pssh; 4640 supportedSchemes = SupportedSchemes; 4641 } 4642 4643 private DrmInfo(Parcel parcel) { 4644 Log.v(TAG, "DrmInfo(" + parcel + ") size " + parcel.dataSize()); 4645 4646 int psshsize = parcel.readInt(); 4647 byte[] pssh = new byte[psshsize]; 4648 parcel.readByteArray(pssh); 4649 4650 Log.v(TAG, "DrmInfo() PSSH: " + arrToHex(pssh)); 4651 mapPssh = parsePSSH(pssh, psshsize); 4652 Log.v(TAG, "DrmInfo() PSSH: " + mapPssh); 4653 4654 int supportedDRMsCount = parcel.readInt(); 4655 supportedSchemes = new UUID[supportedDRMsCount]; 4656 for (int i = 0; i < supportedDRMsCount; i++) { 4657 byte[] uuid = new byte[16]; 4658 parcel.readByteArray(uuid); 4659 4660 supportedSchemes[i] = bytesToUUID(uuid); 4661 4662 Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " + 4663 supportedSchemes[i]); 4664 } 4665 4666 Log.v(TAG, "DrmInfo() Parcel psshsize: " + psshsize + 4667 " supportedDRMsCount: " + supportedDRMsCount); 4668 } 4669 4670 private DrmInfo makeCopy() { 4671 return new DrmInfo(this.mapPssh, this.supportedSchemes); 4672 } 4673 4674 private String arrToHex(byte[] bytes) { 4675 String out = "0x"; 4676 for (int i = 0; i < bytes.length; i++) { 4677 out += String.format("%02x", bytes[i]); 4678 } 4679 4680 return out; 4681 } 4682 4683 private UUID bytesToUUID(byte[] uuid) { 4684 long msb = 0, lsb = 0; 4685 for (int i = 0; i < 8; i++) { 4686 msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) ); 4687 lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) ); 4688 } 4689 4690 return new UUID(msb, lsb); 4691 } 4692 4693 private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) { 4694 Map<UUID, byte[]> result = new HashMap<UUID, byte[]>(); 4695 4696 final int UUID_SIZE = 16; 4697 final int DATALEN_SIZE = 4; 4698 4699 int len = psshsize; 4700 int numentries = 0; 4701 int i = 0; 4702 4703 while (len > 0) { 4704 if (len < UUID_SIZE) { 4705 Log.w(TAG, String.format("parsePSSH: len is too short to parse " + 4706 "UUID: (%d < 16) pssh: %d", len, psshsize)); 4707 return null; 4708 } 4709 4710 byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE); 4711 UUID uuid = bytesToUUID(subset); 4712 i += UUID_SIZE; 4713 len -= UUID_SIZE; 4714 4715 // get data length 4716 if (len < 4) { 4717 Log.w(TAG, String.format("parsePSSH: len is too short to parse " + 4718 "datalen: (%d < 4) pssh: %d", len, psshsize)); 4719 return null; 4720 } 4721 4722 subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE); 4723 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ? 4724 ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) | 4725 ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) : 4726 ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) | 4727 ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ; 4728 i += DATALEN_SIZE; 4729 len -= DATALEN_SIZE; 4730 4731 if (len < datalen) { 4732 Log.w(TAG, String.format("parsePSSH: len is too short to parse " + 4733 "data: (%d < %d) pssh: %d", len, datalen, psshsize)); 4734 return null; 4735 } 4736 4737 byte[] data = Arrays.copyOfRange(pssh, i, i+datalen); 4738 4739 // skip the data 4740 i += datalen; 4741 len -= datalen; 4742 4743 Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d", 4744 numentries, uuid, arrToHex(data), psshsize)); 4745 numentries++; 4746 result.put(uuid, data); 4747 } 4748 4749 return result; 4750 } 4751 4752 }; // DrmInfo 4753 4754 /** 4755 * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm(). 4756 * Extends MediaDrm.MediaDrmException 4757 */ 4758 public static final class NoDrmSchemeException extends MediaDrmException { 4759 public NoDrmSchemeException(String detailMessage) { 4760 super(detailMessage); 4761 } 4762 } 4763 4764 /** 4765 * Thrown when the device requires DRM provisioning but the provisioning attempt has 4766 * failed due to a network error (Internet reachability, timeout, etc.). 4767 * Extends MediaDrm.MediaDrmException 4768 */ 4769 public static final class ProvisioningNetworkErrorException extends MediaDrmException { 4770 public ProvisioningNetworkErrorException(String detailMessage) { 4771 super(detailMessage); 4772 } 4773 } 4774 4775 /** 4776 * Thrown when the device requires DRM provisioning but the provisioning attempt has 4777 * failed due to the provisioning server denying the request. 4778 * Extends MediaDrm.MediaDrmException 4779 */ 4780 public static final class ProvisioningServerErrorException extends MediaDrmException { 4781 public ProvisioningServerErrorException(String detailMessage) { 4782 super(detailMessage); 4783 } 4784 } 4785 4786 4787 private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId); 4788 4789 // Modular DRM helpers 4790 4791 private void prepareDrm_createDrmStep(@NonNull UUID uuid) 4792 throws UnsupportedSchemeException { 4793 Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); 4794 4795 try { 4796 mDrmObj = new MediaDrm(uuid); 4797 Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); 4798 } catch (Exception e) { // UnsupportedSchemeException 4799 Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); 4800 throw e; 4801 } 4802 } 4803 4804 private void prepareDrm_openSessionStep(@NonNull UUID uuid) 4805 throws NotProvisionedException, ResourceBusyException { 4806 Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); 4807 4808 // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do 4809 // it anyway so it raises provisioning error if needed. We'd rather handle provisioning 4810 // at prepareDrm/openSession rather than getKeyRequest/provideKeyResponse 4811 try { 4812 mDrmSessionId = mDrmObj.openSession(); 4813 Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); 4814 4815 // Sending it down to native/mediaserver to create the crypto object 4816 // This call could simply fail due to bad player state, e.g., after start(). 4817 _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); 4818 Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded"); 4819 4820 } catch (Exception e) { //ResourceBusyException, NotProvisionedException 4821 Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); 4822 throw e; 4823 } 4824 4825 } 4826 4827 private class ProvisioningThread extends Thread 4828 { 4829 public static final int TIMEOUT_MS = 60000; 4830 4831 private UUID uuid; 4832 private String urlStr; 4833 private Object drmLock; 4834 private OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate; 4835 private MediaPlayer mediaPlayer; 4836 private int status; 4837 private boolean finished; 4838 public int status() { 4839 return status; 4840 } 4841 4842 public ProvisioningThread initialize(MediaDrm.ProvisionRequest request, 4843 UUID uuid, MediaPlayer mediaPlayer) { 4844 // lock is held by the caller 4845 drmLock = mediaPlayer.mDrmLock; 4846 onDrmPreparedHandlerDelegate = mediaPlayer.mOnDrmPreparedHandlerDelegate; 4847 this.mediaPlayer = mediaPlayer; 4848 4849 urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); 4850 this.uuid = uuid; 4851 4852 status = PREPARE_DRM_STATUS_PREPARATION_ERROR; 4853 4854 Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr); 4855 return this; 4856 } 4857 4858 public void run() { 4859 4860 byte[] response = null; 4861 boolean provisioningSucceeded = false; 4862 try { 4863 URL url = new URL(urlStr); 4864 final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 4865 try { 4866 connection.setRequestMethod("POST"); 4867 connection.setDoOutput(false); 4868 connection.setDoInput(true); 4869 connection.setConnectTimeout(TIMEOUT_MS); 4870 connection.setReadTimeout(TIMEOUT_MS); 4871 4872 connection.connect(); 4873 response = Streams.readFully(connection.getInputStream()); 4874 4875 Log.v(TAG, "HandleProvisioninig: Thread run: response " + 4876 response.length + " " + response); 4877 } catch (Exception e) { 4878 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; 4879 Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url); 4880 } finally { 4881 connection.disconnect(); 4882 } 4883 } catch (Exception e) { 4884 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; 4885 Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e); 4886 } 4887 4888 if (response != null) { 4889 try { 4890 mDrmObj.provideProvisionResponse(response); 4891 Log.v(TAG, "HandleProvisioninig: Thread run: " + 4892 "provideProvisionResponse SUCCEEDED!"); 4893 4894 provisioningSucceeded = true; 4895 } catch (Exception e) { 4896 status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; 4897 Log.w(TAG, "HandleProvisioninig: Thread run: " + 4898 "provideProvisionResponse " + e); 4899 } 4900 } 4901 4902 boolean succeeded = false; 4903 4904 // non-blocking mode needs the lock 4905 if (onDrmPreparedHandlerDelegate != null) { 4906 4907 synchronized (drmLock) { 4908 // continuing with prepareDrm 4909 if (provisioningSucceeded) { 4910 succeeded = mediaPlayer.resumePrepareDrm(uuid); 4911 status = (succeeded) ? 4912 PREPARE_DRM_STATUS_SUCCESS : 4913 PREPARE_DRM_STATUS_PREPARATION_ERROR; 4914 } 4915 mediaPlayer.mDrmProvisioningInProgress = false; 4916 mediaPlayer.mPrepareDrmInProgress = false; 4917 if (!succeeded) { 4918 cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock 4919 } 4920 } // synchronized 4921 4922 // calling the callback outside the lock 4923 onDrmPreparedHandlerDelegate.notifyClient(status); 4924 } else { // blocking mode already has the lock 4925 4926 // continuing with prepareDrm 4927 if (provisioningSucceeded) { 4928 succeeded = mediaPlayer.resumePrepareDrm(uuid); 4929 status = (succeeded) ? 4930 PREPARE_DRM_STATUS_SUCCESS : 4931 PREPARE_DRM_STATUS_PREPARATION_ERROR; 4932 } 4933 mediaPlayer.mDrmProvisioningInProgress = false; 4934 mediaPlayer.mPrepareDrmInProgress = false; 4935 if (!succeeded) { 4936 cleanDrmObj(); // cleaning up if it hasn't gone through 4937 } 4938 } 4939 4940 finished = true; 4941 } // run() 4942 4943 } // ProvisioningThread 4944 4945 private int HandleProvisioninig(UUID uuid) 4946 { 4947 // the lock is already held by the caller 4948 4949 if (mDrmProvisioningInProgress) { 4950 Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress"); 4951 return PREPARE_DRM_STATUS_PREPARATION_ERROR; 4952 } 4953 4954 MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); 4955 if (provReq == null) { 4956 Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null."); 4957 return PREPARE_DRM_STATUS_PREPARATION_ERROR; 4958 } 4959 4960 Log.v(TAG, "HandleProvisioninig provReq " + 4961 " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); 4962 4963 // networking in a background thread 4964 mDrmProvisioningInProgress = true; 4965 4966 mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this); 4967 mDrmProvisioningThread.start(); 4968 4969 int result; 4970 4971 // non-blocking: this is not the final result 4972 if (mOnDrmPreparedHandlerDelegate != null) { 4973 result = PREPARE_DRM_STATUS_SUCCESS; 4974 } else { 4975 // if blocking mode, wait till provisioning is done 4976 try { 4977 mDrmProvisioningThread.join(); 4978 } catch (Exception e) { 4979 Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e); 4980 } 4981 result = mDrmProvisioningThread.status(); 4982 // no longer need the thread 4983 mDrmProvisioningThread = null; 4984 } 4985 4986 return result; 4987 } 4988 4989 private boolean resumePrepareDrm(UUID uuid) 4990 { 4991 Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); 4992 4993 // mDrmLock is guaranteed to be held 4994 boolean success = false; 4995 try { 4996 // resuming 4997 prepareDrm_openSessionStep(uuid); 4998 4999 mDrmUUID = uuid; 5000 mActiveDrmScheme = true; 5001 5002 success = true; 5003 } catch (Exception e) { 5004 Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e); 5005 // mDrmObj clean up is done by the caller 5006 } 5007 5008 return success; 5009 } 5010 5011 private void resetDrmState() 5012 { 5013 synchronized (mDrmLock) { 5014 Log.v(TAG, "resetDrmState: " + 5015 " mDrmInfo=" + mDrmInfo + 5016 " mDrmProvisioningThread=" + mDrmProvisioningThread + 5017 " mPrepareDrmInProgress=" + mPrepareDrmInProgress + 5018 " mActiveDrmScheme=" + mActiveDrmScheme); 5019 5020 mDrmInfoResolved = false; 5021 mDrmInfo = null; 5022 5023 if (mDrmProvisioningThread != null) { 5024 // timeout; relying on HttpUrlConnection 5025 try { 5026 mDrmProvisioningThread.join(); 5027 } 5028 catch (InterruptedException e) { 5029 Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); 5030 } 5031 mDrmProvisioningThread = null; 5032 } 5033 5034 mPrepareDrmInProgress = false; 5035 mActiveDrmScheme = false; 5036 5037 cleanDrmObj(); 5038 } // synchronized 5039 } 5040 5041 private void cleanDrmObj() 5042 { 5043 // the caller holds mDrmLock 5044 Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); 5045 5046 if (mDrmSessionId != null) { 5047 mDrmObj.closeSession(mDrmSessionId); 5048 mDrmSessionId = null; 5049 } 5050 if (mDrmObj != null) { 5051 mDrmObj.release(); 5052 mDrmObj = null; 5053 } 5054 } 5055 5056 private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { 5057 long msb = uuid.getMostSignificantBits(); 5058 long lsb = uuid.getLeastSignificantBits(); 5059 5060 byte[] uuidBytes = new byte[16]; 5061 for (int i = 0; i < 8; ++i) { 5062 uuidBytes[i] = (byte)(msb >>> (8 * (7 - i))); 5063 uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i))); 5064 } 5065 5066 return uuidBytes; 5067 } 5068 5069 // Modular DRM end 5070 5071 /* 5072 * Test whether a given video scaling mode is supported. 5073 */ 5074 private boolean isVideoScalingModeSupported(int mode) { 5075 return (mode == VIDEO_SCALING_MODE_SCALE_TO_FIT || 5076 mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); 5077 } 5078 5079 /** @hide */ 5080 static class TimeProvider implements MediaPlayer.OnSeekCompleteListener, 5081 MediaTimeProvider { 5082 private static final String TAG = "MTP"; 5083 private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L; 5084 private static final long MAX_EARLY_CALLBACK_US = 1000; 5085 private static final long TIME_ADJUSTMENT_RATE = 2; /* meaning 1/2 */ 5086 private long mLastTimeUs = 0; 5087 private MediaPlayer mPlayer; 5088 private boolean mPaused = true; 5089 private boolean mStopped = true; 5090 private boolean mBuffering; 5091 private long mLastReportedTime; 5092 private long mTimeAdjustment; 5093 // since we are expecting only a handful listeners per stream, there is 5094 // no need for log(N) search performance 5095 private MediaTimeProvider.OnMediaTimeListener mListeners[]; 5096 private long mTimes[]; 5097 private long mLastNanoTime; 5098 private Handler mEventHandler; 5099 private boolean mRefresh = false; 5100 private boolean mPausing = false; 5101 private boolean mSeeking = false; 5102 private static final int NOTIFY = 1; 5103 private static final int NOTIFY_TIME = 0; 5104 private static final int REFRESH_AND_NOTIFY_TIME = 1; 5105 private static final int NOTIFY_STOP = 2; 5106 private static final int NOTIFY_SEEK = 3; 5107 private static final int NOTIFY_TRACK_DATA = 4; 5108 private HandlerThread mHandlerThread; 5109 5110 /** @hide */ 5111 public boolean DEBUG = false; 5112 5113 public TimeProvider(MediaPlayer mp) { 5114 mPlayer = mp; 5115 try { 5116 getCurrentTimeUs(true, false); 5117 } catch (IllegalStateException e) { 5118 // we assume starting position 5119 mRefresh = true; 5120 } 5121 5122 Looper looper; 5123 if ((looper = Looper.myLooper()) == null && 5124 (looper = Looper.getMainLooper()) == null) { 5125 // Create our own looper here in case MP was created without one 5126 mHandlerThread = new HandlerThread("MediaPlayerMTPEventThread", 5127 Process.THREAD_PRIORITY_FOREGROUND); 5128 mHandlerThread.start(); 5129 looper = mHandlerThread.getLooper(); 5130 } 5131 mEventHandler = new EventHandler(looper); 5132 5133 mListeners = new MediaTimeProvider.OnMediaTimeListener[0]; 5134 mTimes = new long[0]; 5135 mLastTimeUs = 0; 5136 mTimeAdjustment = 0; 5137 } 5138 5139 private void scheduleNotification(int type, long delayUs) { 5140 // ignore time notifications until seek is handled 5141 if (mSeeking && 5142 (type == NOTIFY_TIME || type == REFRESH_AND_NOTIFY_TIME)) { 5143 return; 5144 } 5145 5146 if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs); 5147 mEventHandler.removeMessages(NOTIFY); 5148 Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0); 5149 mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000)); 5150 } 5151 5152 /** @hide */ 5153 public void close() { 5154 mEventHandler.removeMessages(NOTIFY); 5155 if (mHandlerThread != null) { 5156 mHandlerThread.quitSafely(); 5157 mHandlerThread = null; 5158 } 5159 } 5160 5161 /** @hide */ 5162 protected void finalize() { 5163 if (mHandlerThread != null) { 5164 mHandlerThread.quitSafely(); 5165 } 5166 } 5167 5168 /** @hide */ 5169 public void onPaused(boolean paused) { 5170 synchronized(this) { 5171 if (DEBUG) Log.d(TAG, "onPaused: " + paused); 5172 if (mStopped) { // handle as seek if we were stopped 5173 mStopped = false; 5174 mSeeking = true; 5175 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 5176 } else { 5177 mPausing = paused; // special handling if player disappeared 5178 mSeeking = false; 5179 scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */); 5180 } 5181 } 5182 } 5183 5184 /** @hide */ 5185 public void onBuffering(boolean buffering) { 5186 synchronized (this) { 5187 if (DEBUG) Log.d(TAG, "onBuffering: " + buffering); 5188 mBuffering = buffering; 5189 scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */); 5190 } 5191 } 5192 5193 /** @hide */ 5194 public void onStopped() { 5195 synchronized(this) { 5196 if (DEBUG) Log.d(TAG, "onStopped"); 5197 mPaused = true; 5198 mStopped = true; 5199 mSeeking = false; 5200 mBuffering = false; 5201 scheduleNotification(NOTIFY_STOP, 0 /* delay */); 5202 } 5203 } 5204 5205 /** @hide */ 5206 @Override 5207 public void onSeekComplete(MediaPlayer mp) { 5208 synchronized(this) { 5209 mStopped = false; 5210 mSeeking = true; 5211 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 5212 } 5213 } 5214 5215 /** @hide */ 5216 public void onNewPlayer() { 5217 if (mRefresh) { 5218 synchronized(this) { 5219 mStopped = false; 5220 mSeeking = true; 5221 mBuffering = false; 5222 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 5223 } 5224 } 5225 } 5226 5227 private synchronized void notifySeek() { 5228 mSeeking = false; 5229 try { 5230 long timeUs = getCurrentTimeUs(true, false); 5231 if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs); 5232 5233 for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { 5234 if (listener == null) { 5235 break; 5236 } 5237 listener.onSeek(timeUs); 5238 } 5239 } catch (IllegalStateException e) { 5240 // we should not be there, but at least signal pause 5241 if (DEBUG) Log.d(TAG, "onSeekComplete but no player"); 5242 mPausing = true; // special handling if player disappeared 5243 notifyTimedEvent(false /* refreshTime */); 5244 } 5245 } 5246 5247 private synchronized void notifyTrackData(Pair<SubtitleTrack, byte[]> trackData) { 5248 SubtitleTrack track = trackData.first; 5249 byte[] data = trackData.second; 5250 track.onData(data, true /* eos */, ~0 /* runID: keep forever */); 5251 } 5252 5253 private synchronized void notifyStop() { 5254 for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { 5255 if (listener == null) { 5256 break; 5257 } 5258 listener.onStop(); 5259 } 5260 } 5261 5262 private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) { 5263 int i = 0; 5264 for (; i < mListeners.length; i++) { 5265 if (mListeners[i] == listener || mListeners[i] == null) { 5266 break; 5267 } 5268 } 5269 5270 // new listener 5271 if (i >= mListeners.length) { 5272 MediaTimeProvider.OnMediaTimeListener[] newListeners = 5273 new MediaTimeProvider.OnMediaTimeListener[i + 1]; 5274 long[] newTimes = new long[i + 1]; 5275 System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length); 5276 System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length); 5277 mListeners = newListeners; 5278 mTimes = newTimes; 5279 } 5280 5281 if (mListeners[i] == null) { 5282 mListeners[i] = listener; 5283 mTimes[i] = MediaTimeProvider.NO_TIME; 5284 } 5285 return i; 5286 } 5287 5288 public void notifyAt( 5289 long timeUs, MediaTimeProvider.OnMediaTimeListener listener) { 5290 synchronized(this) { 5291 if (DEBUG) Log.d(TAG, "notifyAt " + timeUs); 5292 mTimes[registerListener(listener)] = timeUs; 5293 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 5294 } 5295 } 5296 5297 public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) { 5298 synchronized(this) { 5299 if (DEBUG) Log.d(TAG, "scheduleUpdate"); 5300 int i = registerListener(listener); 5301 5302 if (!mStopped) { 5303 mTimes[i] = 0; 5304 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 5305 } 5306 } 5307 } 5308 5309 public void cancelNotifications( 5310 MediaTimeProvider.OnMediaTimeListener listener) { 5311 synchronized(this) { 5312 int i = 0; 5313 for (; i < mListeners.length; i++) { 5314 if (mListeners[i] == listener) { 5315 System.arraycopy(mListeners, i + 1, 5316 mListeners, i, mListeners.length - i - 1); 5317 System.arraycopy(mTimes, i + 1, 5318 mTimes, i, mTimes.length - i - 1); 5319 mListeners[mListeners.length - 1] = null; 5320 mTimes[mTimes.length - 1] = NO_TIME; 5321 break; 5322 } else if (mListeners[i] == null) { 5323 break; 5324 } 5325 } 5326 5327 scheduleNotification(NOTIFY_TIME, 0 /* delay */); 5328 } 5329 } 5330 5331 private synchronized void notifyTimedEvent(boolean refreshTime) { 5332 // figure out next callback 5333 long nowUs; 5334 try { 5335 nowUs = getCurrentTimeUs(refreshTime, true); 5336 } catch (IllegalStateException e) { 5337 // assume we paused until new player arrives 5338 mRefresh = true; 5339 mPausing = true; // this ensures that call succeeds 5340 nowUs = getCurrentTimeUs(refreshTime, true); 5341 } 5342 long nextTimeUs = nowUs; 5343 5344 if (mSeeking) { 5345 // skip timed-event notifications until seek is complete 5346 return; 5347 } 5348 5349 if (DEBUG) { 5350 StringBuilder sb = new StringBuilder(); 5351 sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ") 5352 .append(nowUs).append(") from {"); 5353 boolean first = true; 5354 for (long time: mTimes) { 5355 if (time == NO_TIME) { 5356 continue; 5357 } 5358 if (!first) sb.append(", "); 5359 sb.append(time); 5360 first = false; 5361 } 5362 sb.append("}"); 5363 Log.d(TAG, sb.toString()); 5364 } 5365 5366 Vector<MediaTimeProvider.OnMediaTimeListener> activatedListeners = 5367 new Vector<MediaTimeProvider.OnMediaTimeListener>(); 5368 for (int ix = 0; ix < mTimes.length; ix++) { 5369 if (mListeners[ix] == null) { 5370 break; 5371 } 5372 if (mTimes[ix] <= NO_TIME) { 5373 // ignore, unless we were stopped 5374 } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) { 5375 activatedListeners.add(mListeners[ix]); 5376 if (DEBUG) Log.d(TAG, "removed"); 5377 mTimes[ix] = NO_TIME; 5378 } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) { 5379 nextTimeUs = mTimes[ix]; 5380 } 5381 } 5382 5383 if (nextTimeUs > nowUs && !mPaused) { 5384 // schedule callback at nextTimeUs 5385 if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs); 5386 scheduleNotification(NOTIFY_TIME, nextTimeUs - nowUs); 5387 } else { 5388 mEventHandler.removeMessages(NOTIFY); 5389 // no more callbacks 5390 } 5391 5392 for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) { 5393 listener.onTimedEvent(nowUs); 5394 } 5395 } 5396 5397 private long getEstimatedTime(long nanoTime, boolean monotonic) { 5398 if (mPaused) { 5399 mLastReportedTime = mLastTimeUs + mTimeAdjustment; 5400 } else { 5401 long timeSinceRead = (nanoTime - mLastNanoTime) / 1000; 5402 mLastReportedTime = mLastTimeUs + timeSinceRead; 5403 if (mTimeAdjustment > 0) { 5404 long adjustment = 5405 mTimeAdjustment - timeSinceRead / TIME_ADJUSTMENT_RATE; 5406 if (adjustment <= 0) { 5407 mTimeAdjustment = 0; 5408 } else { 5409 mLastReportedTime += adjustment; 5410 } 5411 } 5412 } 5413 return mLastReportedTime; 5414 } 5415 5416 public long getCurrentTimeUs(boolean refreshTime, boolean monotonic) 5417 throws IllegalStateException { 5418 synchronized (this) { 5419 // we always refresh the time when the paused-state changes, because 5420 // we expect to have received the pause-change event delayed. 5421 if (mPaused && !refreshTime) { 5422 return mLastReportedTime; 5423 } 5424 5425 long nanoTime = System.nanoTime(); 5426 if (refreshTime || 5427 nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) { 5428 try { 5429 mLastTimeUs = mPlayer.getCurrentPosition() * 1000L; 5430 mPaused = !mPlayer.isPlaying() || mBuffering; 5431 if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); 5432 } catch (IllegalStateException e) { 5433 if (mPausing) { 5434 // if we were pausing, get last estimated timestamp 5435 mPausing = false; 5436 getEstimatedTime(nanoTime, monotonic); 5437 mPaused = true; 5438 if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime); 5439 return mLastReportedTime; 5440 } 5441 // TODO get time when prepared 5442 throw e; 5443 } 5444 mLastNanoTime = nanoTime; 5445 if (monotonic && mLastTimeUs < mLastReportedTime) { 5446 /* have to adjust time */ 5447 mTimeAdjustment = mLastReportedTime - mLastTimeUs; 5448 if (mTimeAdjustment > 1000000) { 5449 // schedule seeked event if time jumped significantly 5450 // TODO: do this properly by introducing an exception 5451 mStopped = false; 5452 mSeeking = true; 5453 scheduleNotification(NOTIFY_SEEK, 0 /* delay */); 5454 } 5455 } else { 5456 mTimeAdjustment = 0; 5457 } 5458 } 5459 5460 return getEstimatedTime(nanoTime, monotonic); 5461 } 5462 } 5463 5464 private class EventHandler extends Handler { 5465 public EventHandler(Looper looper) { 5466 super(looper); 5467 } 5468 5469 @Override 5470 public void handleMessage(Message msg) { 5471 if (msg.what == NOTIFY) { 5472 switch (msg.arg1) { 5473 case NOTIFY_TIME: 5474 notifyTimedEvent(false /* refreshTime */); 5475 break; 5476 case REFRESH_AND_NOTIFY_TIME: 5477 notifyTimedEvent(true /* refreshTime */); 5478 break; 5479 case NOTIFY_STOP: 5480 notifyStop(); 5481 break; 5482 case NOTIFY_SEEK: 5483 notifySeek(); 5484 break; 5485 case NOTIFY_TRACK_DATA: 5486 notifyTrackData((Pair<SubtitleTrack, byte[]>)msg.obj); 5487 break; 5488 } 5489 } 5490 } 5491 } 5492 } 5493 5494 public final static class MetricsConstants 5495 { 5496 private MetricsConstants() {} 5497 5498 /** 5499 * Key to extract the MIME type of the video track 5500 * from the {@link MediaPlayer#getMetrics} return value. 5501 * The value is a String. 5502 */ 5503 public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime"; 5504 5505 /** 5506 * Key to extract the codec being used to decode the video track 5507 * from the {@link MediaPlayer#getMetrics} return value. 5508 * The value is a String. 5509 */ 5510 public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec"; 5511 5512 /** 5513 * Key to extract the width (in pixels) of the video track 5514 * from the {@link MediaPlayer#getMetrics} return value. 5515 * The value is an integer. 5516 */ 5517 public static final String WIDTH = "android.media.mediaplayer.width"; 5518 5519 /** 5520 * Key to extract the height (in pixels) of the video track 5521 * from the {@link MediaPlayer#getMetrics} return value. 5522 * The value is an integer. 5523 */ 5524 public static final String HEIGHT = "android.media.mediaplayer.height"; 5525 5526 /** 5527 * Key to extract the count of video frames played 5528 * from the {@link MediaPlayer#getMetrics} return value. 5529 * The value is an integer. 5530 */ 5531 public static final String FRAMES = "android.media.mediaplayer.frames"; 5532 5533 /** 5534 * Key to extract the count of video frames dropped 5535 * from the {@link MediaPlayer#getMetrics} return value. 5536 * The value is an integer. 5537 */ 5538 public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped"; 5539 5540 /** 5541 * Key to extract the MIME type of the audio track 5542 * from the {@link MediaPlayer#getMetrics} return value. 5543 * The value is a String. 5544 */ 5545 public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime"; 5546 5547 /** 5548 * Key to extract the codec being used to decode the audio track 5549 * from the {@link MediaPlayer#getMetrics} return value. 5550 * The value is a String. 5551 */ 5552 public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; 5553 5554 /** 5555 * Key to extract the duration (in milliseconds) of the 5556 * media being played 5557 * from the {@link MediaPlayer#getMetrics} return value. 5558 * The value is a long. 5559 */ 5560 public static final String DURATION = "android.media.mediaplayer.durationMs"; 5561 5562 /** 5563 * Key to extract the playing time (in milliseconds) of the 5564 * media being played 5565 * from the {@link MediaPlayer#getMetrics} return value. 5566 * The value is a long. 5567 */ 5568 public static final String PLAYING = "android.media.mediaplayer.playingMs"; 5569 5570 /** 5571 * Key to extract the count of errors encountered while 5572 * playing the media 5573 * from the {@link MediaPlayer#getMetrics} return value. 5574 * The value is an integer. 5575 */ 5576 public static final String ERRORS = "android.media.mediaplayer.err"; 5577 5578 /** 5579 * Key to extract an (optional) error code detected while 5580 * playing the media 5581 * from the {@link MediaPlayer#getMetrics} return value. 5582 * The value is an integer. 5583 */ 5584 public static final String ERROR_CODE = "android.media.mediaplayer.errcode"; 5585 5586 } 5587} 5588