1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.media; 17 18 import static android.content.Context.KEYGUARD_SERVICE; 19 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertFalse; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.app.Instrumentation; 26 import android.app.KeyguardManager; 27 import android.content.Context; 28 import android.content.res.AssetFileDescriptor; 29 import android.content.res.Resources; 30 import android.media.AudioManager; 31 import android.media.MediaTimestamp; 32 import android.media.SubtitleData; 33 import android.media.TimedMetaData; 34 import android.net.Uri; 35 import android.os.PersistableBundle; 36 import android.os.PowerManager; 37 import android.support.test.InstrumentationRegistry; 38 import android.support.test.rule.ActivityTestRule; 39 import android.view.SurfaceHolder; 40 import android.view.WindowManager; 41 42 import androidx.annotation.CallSuper; 43 44 import org.junit.After; 45 import org.junit.Before; 46 import org.junit.Rule; 47 48 import java.io.IOException; 49 import java.net.HttpCookie; 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.concurrent.ExecutorService; 55 import java.util.concurrent.Executors; 56 import java.util.logging.Logger; 57 58 /** 59 * Base class for tests which use MediaPlayer2 to play audio or video. 60 */ 61 public class MediaPlayer2TestBase { 62 private static final Logger LOG = Logger.getLogger(MediaPlayer2TestBase.class.getName()); 63 64 protected static final int SLEEP_TIME = 1000; 65 protected static final int LONG_SLEEP_TIME = 6000; 66 protected static final int STREAM_RETRIES = 20; 67 68 protected Monitor mOnVideoSizeChangedCalled = new Monitor(); 69 protected Monitor mOnVideoRenderingStartCalled = new Monitor(); 70 protected Monitor mOnBufferingUpdateCalled = new Monitor(); 71 protected Monitor mOnPrepareCalled = new Monitor(); 72 protected Monitor mOnPlayCalled = new Monitor(); 73 protected Monitor mOnDeselectTrackCalled = new Monitor(); 74 protected Monitor mOnSeekCompleteCalled = new Monitor(); 75 protected Monitor mOnCompletionCalled = new Monitor(); 76 protected Monitor mOnInfoCalled = new Monitor(); 77 protected Monitor mOnErrorCalled = new Monitor(); 78 protected Monitor mOnMediaTimeDiscontinuityCalled = new Monitor(); 79 protected int mCallStatus; 80 81 protected Context mContext; 82 protected Resources mResources; 83 84 protected ExecutorService mExecutor; 85 86 protected MediaPlayer2 mPlayer = null; 87 protected MediaPlayer2 mPlayer2 = null; 88 protected MediaStubActivity mActivity; 89 protected Instrumentation mInstrumentation; 90 91 protected final Object mEventCbLock = new Object(); 92 protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks = new ArrayList<>(); 93 protected final Object mEventCbLock2 = new Object(); 94 protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks2 = new ArrayList<>(); 95 96 @Rule 97 public ActivityTestRule<MediaStubActivity> mActivityRule = 98 new ActivityTestRule<>(MediaStubActivity.class); 99 public PowerManager.WakeLock mScreenLock; 100 private KeyguardManager mKeyguardManager; 101 102 // convenience functions to create MediaPlayer2 103 protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri) { 104 return createMediaPlayer2(context, uri, null); 105 } 106 107 protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri, 108 SurfaceHolder holder) { 109 AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 110 int s = am.generateAudioSessionId(); 111 return createMediaPlayer2(context, uri, holder, null, s > 0 ? s : 0); 112 } 113 114 protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri, SurfaceHolder holder, 115 AudioAttributesCompat audioAttributes, int audioSessionId) { 116 try { 117 MediaPlayer2 mp = createMediaPlayer2OnUiThread(); 118 final AudioAttributesCompat aa = audioAttributes != null ? audioAttributes : 119 new AudioAttributesCompat.Builder().build(); 120 mp.setAudioAttributes(aa); 121 mp.setAudioSessionId(audioSessionId); 122 mp.setDataSource(new DataSourceDesc.Builder() 123 .setDataSource(context, uri) 124 .build()); 125 if (holder != null) { 126 mp.setSurface(holder.getSurface()); 127 } 128 final Monitor onPrepareCalled = new Monitor(); 129 ExecutorService executor = Executors.newFixedThreadPool(1); 130 MediaPlayer2.MediaPlayer2EventCallback ecb = 131 new MediaPlayer2.MediaPlayer2EventCallback() { 132 @Override 133 public void onInfo( 134 MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 135 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) { 136 onPrepareCalled.signal(); 137 } 138 } 139 }; 140 mp.setMediaPlayer2EventCallback(executor, ecb); 141 mp.prepare(); 142 onPrepareCalled.waitForSignal(); 143 mp.clearMediaPlayer2EventCallback(); 144 executor.shutdown(); 145 return mp; 146 } catch (IllegalArgumentException ex) { 147 LOG.warning("create failed:" + ex); 148 // fall through 149 } catch (SecurityException ex) { 150 LOG.warning("create failed:" + ex); 151 // fall through 152 } catch (InterruptedException ex) { 153 LOG.warning("create failed:" + ex); 154 // fall through 155 } 156 return null; 157 } 158 159 protected MediaPlayer2 createMediaPlayer2(Context context, int resid) { 160 AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 161 int s = am.generateAudioSessionId(); 162 return createMediaPlayer2(context, resid, null, s > 0 ? s : 0); 163 } 164 165 protected MediaPlayer2 createMediaPlayer2(Context context, int resid, 166 AudioAttributesCompat audioAttributes, int audioSessionId) { 167 try { 168 AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid); 169 if (afd == null) { 170 return null; 171 } 172 173 MediaPlayer2 mp = createMediaPlayer2OnUiThread(); 174 175 final AudioAttributesCompat aa = audioAttributes != null ? audioAttributes : 176 new AudioAttributesCompat.Builder().build(); 177 mp.setAudioAttributes(aa); 178 mp.setAudioSessionId(audioSessionId); 179 180 mp.setDataSource(new DataSourceDesc.Builder() 181 .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()) 182 .build()); 183 184 final Monitor onPrepareCalled = new Monitor(); 185 ExecutorService executor = Executors.newFixedThreadPool(1); 186 MediaPlayer2.MediaPlayer2EventCallback ecb = 187 new MediaPlayer2.MediaPlayer2EventCallback() { 188 @Override 189 public void onInfo( 190 MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 191 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) { 192 onPrepareCalled.signal(); 193 } 194 } 195 }; 196 mp.setMediaPlayer2EventCallback(executor, ecb); 197 mp.prepare(); 198 onPrepareCalled.waitForSignal(); 199 mp.clearMediaPlayer2EventCallback(); 200 afd.close(); 201 executor.shutdown(); 202 return mp; 203 } catch (IOException ex) { 204 LOG.warning("create failed:" + ex); 205 // fall through 206 } catch (IllegalArgumentException ex) { 207 LOG.warning("create failed:" + ex); 208 // fall through 209 } catch (SecurityException ex) { 210 LOG.warning("create failed:" + ex); 211 // fall through 212 } catch (InterruptedException ex) { 213 LOG.warning("create failed:" + ex); 214 // fall through 215 } 216 return null; 217 } 218 219 private MediaPlayer2 createMediaPlayer2OnUiThread() { 220 final MediaPlayer2[] mp = new MediaPlayer2[1]; 221 try { 222 mActivityRule.runOnUiThread(new Runnable() { 223 public void run() { 224 mp[0] = MediaPlayer2.create(); 225 } 226 }); 227 } catch (Throwable throwable) { 228 fail("Failed to create MediaPlayer2 instance on UI thread."); 229 } 230 return mp[0]; 231 } 232 233 public static class Monitor { 234 private int mNumSignal; 235 236 public synchronized void reset() { 237 mNumSignal = 0; 238 } 239 240 public synchronized void signal() { 241 mNumSignal++; 242 notifyAll(); 243 } 244 245 public synchronized boolean waitForSignal() throws InterruptedException { 246 return waitForCountedSignals(1) > 0; 247 } 248 249 public synchronized int waitForCountedSignals(int targetCount) throws InterruptedException { 250 while (mNumSignal < targetCount) { 251 wait(); 252 } 253 return mNumSignal; 254 } 255 256 public synchronized boolean waitForSignal(long timeoutMs) throws InterruptedException { 257 return waitForCountedSignals(1, timeoutMs) > 0; 258 } 259 260 public synchronized int waitForCountedSignals(int targetCount, long timeoutMs) 261 throws InterruptedException { 262 if (timeoutMs == 0) { 263 return waitForCountedSignals(targetCount); 264 } 265 long deadline = System.currentTimeMillis() + timeoutMs; 266 while (mNumSignal < targetCount) { 267 long delay = deadline - System.currentTimeMillis(); 268 if (delay <= 0) { 269 break; 270 } 271 wait(delay); 272 } 273 return mNumSignal; 274 } 275 276 public synchronized boolean isSignalled() { 277 return mNumSignal >= 1; 278 } 279 280 public synchronized int getNumSignal() { 281 return mNumSignal; 282 } 283 } 284 285 @Before 286 @CallSuper 287 public void setUp() throws Throwable { 288 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 289 mKeyguardManager = (KeyguardManager) 290 mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE); 291 mActivity = mActivityRule.getActivity(); 292 mActivityRule.runOnUiThread(new Runnable() { 293 @Override 294 public void run() { 295 // Keep screen on while testing. 296 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 297 mActivity.setTurnScreenOn(true); 298 mActivity.setShowWhenLocked(true); 299 mKeyguardManager.requestDismissKeyguard(mActivity, null); 300 } 301 }); 302 mInstrumentation.waitForIdleSync(); 303 304 try { 305 mActivityRule.runOnUiThread(new Runnable() { 306 public void run() { 307 mPlayer = MediaPlayer2.create(); 308 mPlayer2 = MediaPlayer2.create(); 309 } 310 }); 311 } catch (Throwable e) { 312 e.printStackTrace(); 313 fail(); 314 } 315 mContext = mActivityRule.getActivity(); 316 mResources = mContext.getResources(); 317 mExecutor = Executors.newFixedThreadPool(1); 318 319 setUpMP2ECb(mPlayer, mEventCbLock, mEventCallbacks); 320 setUpMP2ECb(mPlayer2, mEventCbLock2, mEventCallbacks2); 321 } 322 323 @After 324 @CallSuper 325 public void tearDown() throws Exception { 326 if (mPlayer != null) { 327 mPlayer.close(); 328 mPlayer = null; 329 } 330 if (mPlayer2 != null) { 331 mPlayer2.close(); 332 mPlayer2 = null; 333 } 334 mExecutor.shutdown(); 335 mActivity = null; 336 } 337 338 protected void setUpMP2ECb(MediaPlayer2 mp, final Object cbLock, 339 final List<MediaPlayer2.MediaPlayer2EventCallback> ecbs) { 340 mp.setMediaPlayer2EventCallback(mExecutor, new MediaPlayer2.MediaPlayer2EventCallback() { 341 @Override 342 public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) { 343 synchronized (cbLock) { 344 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 345 ecb.onVideoSizeChanged(mp, dsd, w, h); 346 } 347 } 348 } 349 350 @Override 351 public void onTimedMetaDataAvailable(MediaPlayer2 mp, DataSourceDesc dsd, 352 TimedMetaData data) { 353 synchronized (cbLock) { 354 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 355 ecb.onTimedMetaDataAvailable(mp, dsd, data); 356 } 357 } 358 } 359 360 @Override 361 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 362 synchronized (cbLock) { 363 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 364 ecb.onError(mp, dsd, what, extra); 365 } 366 } 367 } 368 369 @Override 370 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 371 synchronized (cbLock) { 372 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 373 ecb.onInfo(mp, dsd, what, extra); 374 } 375 } 376 } 377 378 @Override 379 public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) { 380 synchronized (cbLock) { 381 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 382 ecb.onCallCompleted(mp, dsd, what, status); 383 } 384 } 385 } 386 387 @Override 388 public void onMediaTimeDiscontinuity(MediaPlayer2 mp, DataSourceDesc dsd, 389 MediaTimestamp timestamp) { 390 synchronized (cbLock) { 391 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 392 ecb.onMediaTimeDiscontinuity(mp, dsd, timestamp); 393 } 394 } 395 } 396 397 @Override 398 public void onCommandLabelReached(MediaPlayer2 mp, Object label) { 399 synchronized (cbLock) { 400 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 401 ecb.onCommandLabelReached(mp, label); 402 } 403 } 404 } 405 @Override 406 public void onSubtitleData(MediaPlayer2 mp, DataSourceDesc dsd, 407 final SubtitleData data) { 408 synchronized (cbLock) { 409 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 410 ecb.onSubtitleData(mp, dsd, data); 411 } 412 } 413 } 414 }); 415 } 416 417 // returns true on success 418 protected boolean loadResource(int resid) throws Exception { 419 /* FIXME: ensure device has capability. 420 if (!MediaUtils.hasCodecsForResource(mContext, resid)) { 421 return false; 422 } 423 */ 424 425 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 426 try { 427 mPlayer.setDataSource(new DataSourceDesc.Builder() 428 .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()) 429 .build()); 430 } finally { 431 // TODO: close afd only after setDataSource is confirmed. 432 // afd.close(); 433 } 434 return true; 435 } 436 437 protected DataSourceDesc createDataSourceDesc(int resid) throws Exception { 438 /* FIXME: ensure device has capability. 439 if (!MediaUtils.hasCodecsForResource(mContext, resid)) { 440 return null; 441 } 442 */ 443 444 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 445 return new DataSourceDesc.Builder() 446 .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()) 447 .build(); 448 } 449 450 protected boolean checkLoadResource(int resid) throws Exception { 451 return loadResource(resid); 452 453 /* FIXME: ensure device has capability. 454 return MediaUtils.check(loadResource(resid), "no decoder found"); 455 */ 456 } 457 458 protected void playLiveVideoTest(String path, int playTime) throws Exception { 459 playVideoWithRetries(path, null, null, playTime); 460 } 461 462 protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception { 463 playVideoWithRetries(path, -1, -1, playTime); 464 } 465 466 protected void playVideoTest(String path, int width, int height) throws Exception { 467 playVideoWithRetries(path, width, height, 0); 468 } 469 470 protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime) 471 throws Exception { 472 boolean playedSuccessfully = false; 473 final Uri uri = Uri.parse(path); 474 for (int i = 0; i < STREAM_RETRIES; i++) { 475 try { 476 mPlayer.setDataSource(new DataSourceDesc.Builder() 477 .setDataSource(mContext, uri) 478 .build()); 479 playLoadedVideo(width, height, playTime); 480 playedSuccessfully = true; 481 break; 482 } catch (PrepareFailedException e) { 483 // prepare() can fail because of network issues, so try again 484 LOG.warning("prepare() failed on try " + i + ", trying playback again"); 485 } 486 } 487 assertTrue("Stream did not play successfully after all attempts", playedSuccessfully); 488 } 489 490 protected void playVideoTest(int resid, int width, int height) throws Exception { 491 if (!checkLoadResource(resid)) { 492 return; // skip 493 } 494 495 playLoadedVideo(width, height, 0); 496 } 497 498 protected void playLiveVideoTest( 499 Uri uri, Map<String, String> headers, List<HttpCookie> cookies, 500 int playTime) throws Exception { 501 playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime); 502 } 503 504 protected void playVideoWithRetries( 505 Uri uri, Map<String, String> headers, List<HttpCookie> cookies, 506 Integer width, Integer height, int playTime) throws Exception { 507 boolean playedSuccessfully = false; 508 for (int i = 0; i < STREAM_RETRIES; i++) { 509 try { 510 mPlayer.setDataSource(new DataSourceDesc.Builder() 511 .setDataSource(mContext, 512 uri, headers, cookies) 513 .build()); 514 playLoadedVideo(width, height, playTime); 515 playedSuccessfully = true; 516 break; 517 } catch (PrepareFailedException e) { 518 // prepare() can fail because of network issues, so try again 519 // playLoadedVideo already has reset the player so we can try again safely. 520 LOG.warning("prepare() failed on try " + i + ", trying playback again"); 521 } 522 } 523 assertTrue("Stream did not play successfully after all attempts", playedSuccessfully); 524 } 525 526 /** 527 * Play a video which has already been loaded with setDataSource(). 528 * 529 * @param width width of the video to verify, or null to skip verification 530 * @param height height of the video to verify, or null to skip verification 531 * @param playTime length of time to play video, or 0 to play entire video. 532 * with a non-negative value, this method stops the playback after the length of 533 * time or the duration the video is elapsed. With a value of -1, 534 * this method simply starts the video and returns immediately without 535 * stoping the video playback. 536 */ 537 protected void playLoadedVideo(final Integer width, final Integer height, int playTime) 538 throws Exception { 539 final float volume = 0.5f; 540 541 boolean audioOnly = (width != null && width.intValue() == -1) 542 || (height != null && height.intValue() == -1); 543 mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface()); 544 545 synchronized (mEventCbLock) { 546 mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() { 547 @Override 548 public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) { 549 if (w == 0 && h == 0) { 550 // A size of 0x0 can be sent initially one time when using NuPlayer. 551 assertFalse(mOnVideoSizeChangedCalled.isSignalled()); 552 return; 553 } 554 mOnVideoSizeChangedCalled.signal(); 555 if (width != null) { 556 assertEquals(width.intValue(), w); 557 } 558 if (height != null) { 559 assertEquals(height.intValue(), h); 560 } 561 } 562 563 @Override 564 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 565 fail("Media player had error " + what + " playing video"); 566 } 567 568 @Override 569 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 570 if (what == MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START) { 571 mOnVideoRenderingStartCalled.signal(); 572 } else if (what == MediaPlayer2.MEDIA_INFO_PREPARED) { 573 mOnPrepareCalled.signal(); 574 } 575 } 576 577 @Override 578 public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, 579 int what, int status) { 580 if (what == MediaPlayer2.CALL_COMPLETED_PLAY) { 581 mOnPlayCalled.signal(); 582 } 583 } 584 }); 585 } 586 try { 587 mOnPrepareCalled.reset(); 588 mPlayer.prepare(); 589 mOnPrepareCalled.waitForSignal(); 590 } catch (Exception e) { 591 mPlayer.reset(); 592 throw new PrepareFailedException(); 593 } 594 595 mOnPlayCalled.reset(); 596 mPlayer.play(); 597 mOnPlayCalled.waitForSignal(); 598 if (!audioOnly) { 599 mOnVideoSizeChangedCalled.waitForSignal(); 600 mOnVideoRenderingStartCalled.waitForSignal(); 601 } 602 mPlayer.setPlayerVolume(volume); 603 604 // waiting to complete 605 if (playTime == -1) { 606 return; 607 } else if (playTime == 0) { 608 while (mPlayer.getMediaPlayer2State() == MediaPlayer2.MEDIAPLAYER2_STATE_PLAYING) { 609 Thread.sleep(SLEEP_TIME); 610 } 611 } else { 612 Thread.sleep(playTime); 613 } 614 615 // validate a few MediaMetrics. 616 PersistableBundle metrics = mPlayer.getMetrics(); 617 if (metrics == null) { 618 fail("MediaPlayer.getMetrics() returned null metrics"); 619 } else if (metrics.isEmpty()) { 620 fail("MediaPlayer.getMetrics() returned empty metrics"); 621 } else { 622 623 int size = metrics.size(); 624 Set<String> keys = metrics.keySet(); 625 626 if (keys == null) { 627 fail("MediaMetricsSet returned no keys"); 628 } else if (keys.size() != size) { 629 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()"); 630 } 631 632 // we played something; so one of these should be non-null 633 String vmime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_VIDEO, null); 634 String amime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_AUDIO, null); 635 if (vmime == null && amime == null) { 636 fail("getMetrics() returned neither video nor audio mime value"); 637 } 638 639 long duration = metrics.getLong(MediaPlayer2.MetricsConstants.DURATION, -2); 640 if (duration == -2) { 641 fail("getMetrics() didn't return a duration"); 642 } 643 long playing = metrics.getLong(MediaPlayer2.MetricsConstants.PLAYING, -2); 644 if (playing == -2) { 645 fail("getMetrics() didn't return a playing time"); 646 } 647 if (!keys.contains(MediaPlayer2.MetricsConstants.PLAYING)) { 648 fail("MediaMetricsSet.keys() missing: " + MediaPlayer2.MetricsConstants.PLAYING); 649 } 650 } 651 mPlayer.reset(); 652 } 653 654 private static class PrepareFailedException extends Exception {} 655 656 protected void setOnErrorListener() { 657 synchronized (mEventCbLock) { 658 mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() { 659 @Override 660 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 661 mOnErrorCalled.signal(); 662 } 663 }); 664 } 665 } 666 } 667