1 /*
2  * Copyright (C) 2009 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 android.media.player.cts;
17 
18 import static junit.framework.TestCase.assertEquals;
19 import static junit.framework.TestCase.assertFalse;
20 import static junit.framework.TestCase.assertNotNull;
21 import static junit.framework.TestCase.assertSame;
22 import static junit.framework.TestCase.assertTrue;
23 import static junit.framework.TestCase.fail;
24 
25 import static org.junit.Assert.assertThrows;
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.content.ContentProvider;
29 import android.content.Context;
30 import android.content.pm.PackageManager;
31 import android.content.res.AssetFileDescriptor;
32 import android.graphics.Rect;
33 import android.hardware.Camera;
34 import android.media.AudioManager;
35 import android.media.CamcorderProfile;
36 import android.media.MediaDataSource;
37 import android.media.MediaFormat;
38 import android.media.MediaMetadataRetriever;
39 import android.media.MediaPlayer;
40 import android.media.MediaPlayer.OnSeekCompleteListener;
41 import android.media.MediaPlayer.OnTimedTextListener;
42 import android.media.MediaRecorder;
43 import android.media.MediaTimestamp;
44 import android.media.PlaybackParams;
45 import android.media.SyncParams;
46 import android.media.TimedText;
47 import android.media.audiofx.AudioEffect;
48 import android.media.audiofx.Visualizer;
49 import android.media.cts.MediaPlayerTestBase;
50 import android.media.cts.TestMediaDataSource;
51 import android.media.cts.TestUtils.Monitor;
52 import android.media.cts.Utils;
53 import android.net.Uri;
54 import android.os.Bundle;
55 import android.os.Environment;
56 import android.os.ParcelFileDescriptor;
57 import android.os.PowerManager;
58 import android.os.SystemClock;
59 import android.platform.test.annotations.AppModeFull;
60 import android.platform.test.annotations.Presubmit;
61 import android.platform.test.annotations.RequiresDevice;
62 import android.util.Log;
63 
64 import androidx.core.content.FileProvider;
65 import androidx.test.InstrumentationRegistry;
66 import androidx.test.ext.junit.runners.AndroidJUnit4;
67 import androidx.test.filters.SmallTest;
68 
69 import com.android.compatibility.common.util.FrameworkSpecificTest;
70 import com.android.compatibility.common.util.MediaUtils;
71 import com.android.compatibility.common.util.NonMainlineTest;
72 import com.android.compatibility.common.util.Preconditions;
73 
74 import junit.framework.AssertionFailedError;
75 
76 import org.junit.After;
77 import org.junit.Before;
78 import org.junit.Test;
79 import org.junit.runner.RunWith;
80 
81 import java.io.BufferedReader;
82 import java.io.File;
83 import java.io.FileInputStream;
84 import java.io.FileNotFoundException;
85 import java.io.IOException;
86 import java.io.InputStream;
87 import java.io.InputStreamReader;
88 import java.nio.file.Files;
89 import java.nio.file.StandardCopyOption;
90 import java.util.ArrayList;
91 import java.util.List;
92 import java.util.StringTokenizer;
93 import java.util.UUID;
94 import java.util.Vector;
95 import java.util.concurrent.BlockingDeque;
96 import java.util.concurrent.Callable;
97 import java.util.concurrent.CountDownLatch;
98 import java.util.concurrent.LinkedBlockingDeque;
99 import java.util.concurrent.atomic.AtomicInteger;
100 import java.util.stream.Collectors;
101 import java.util.stream.Stream;
102 
103 /**
104  * Tests for the MediaPlayer API and local video/audio playback.
105  *
106  */
107 @SmallTest
108 @RequiresDevice
109 @FrameworkSpecificTest
110 @NonMainlineTest
111 @AppModeFull(reason = "TODO: evaluate and port to instant")
112 @RunWith(AndroidJUnit4.class)
113 public class MediaPlayerTest extends MediaPlayerTestBase {
114 
115     private String RECORDED_FILE;
116     private static final String LOG_TAG = "MediaPlayerTest";
117 
118     static final String mInpPrefix = WorkDir.getMediaDirString();
119 
120     private static final int  RECORDED_VIDEO_WIDTH  = 176;
121     private static final int  RECORDED_VIDEO_HEIGHT = 144;
122     private static final long RECORDED_DURATION_MS  = 3000;
123     private static final float FLOAT_TOLERANCE = .0001f;
124     private static final int PLAYBACK_DURATION_MS  = 10000;
125     private static final int ANR_DETECTION_TIME_MS  = 20000;
126 
127     private final Vector<Integer> mTimedTextTrackIndex = new Vector<>();
128     private final Monitor mOnTimedTextCalled = new Monitor();
129     private int mSelectedTimedTextIndex;
130 
131     private final Vector<Integer> mSubtitleTrackIndex = new Vector<>();
132     private final Monitor mOnSubtitleDataCalled = new Monitor();
133     private int mSelectedSubtitleIndex;
134 
135     private final Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
136 
137     private File mOutFile;
138 
139     private int mBoundsCount;
140 
141     @Override
142     @Before
setUp()143     public void setUp() throws Throwable {
144         super.setUp();
145         RECORDED_FILE = new File(Environment.getExternalStorageDirectory(),
146                 "mediaplayer_record.out").getAbsolutePath();
147         mOutFile = new File(RECORDED_FILE);
148     }
149 
150     @Override
151     @After
tearDown()152     public void tearDown() {
153         if (mOutFile != null && mOutFile.exists()) {
154             mOutFile.delete();
155         }
156         super.tearDown();
157     }
158 
159     @Presubmit
160     @Test
testFlacHeapOverflow()161     public void testFlacHeapOverflow() throws Exception {
162         testIfMediaServerDied("heap_oob_flac.mp3");
163     }
164 
getAssetFileDescriptorFor(final String res)165     private static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
166             throws FileNotFoundException {
167         Preconditions.assertTestFileExists(mInpPrefix + res);
168         File inpFile = new File(mInpPrefix + res);
169         ParcelFileDescriptor parcelFD =
170                 ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
171         return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
172     }
173 
loadSubtitleSource(String res)174     private void loadSubtitleSource(String res) throws Exception {
175         try (AssetFileDescriptor afd = getAssetFileDescriptorFor(res)) {
176             mMediaPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(),
177                     afd.getLength(), MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
178         }
179     }
180 
181     // returns true on success
loadResource(final String res)182     private boolean loadResource(final String res) throws Exception {
183         Preconditions.assertTestFileExists(mInpPrefix + res);
184         if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
185             return false;
186         }
187 
188         try (AssetFileDescriptor afd = getAssetFileDescriptorFor(res)) {
189             mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
190                     afd.getLength());
191 
192             // Although it is only meant for video playback, it should not
193             // cause issues for audio-only playback.
194             int videoScalingMode = sUseScaleToFitMode ?
195                     MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT
196                     : MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING;
197 
198             mMediaPlayer.setVideoScalingMode(videoScalingMode);
199         }
200         sUseScaleToFitMode = !sUseScaleToFitMode;  // Alternate the scaling mode
201         return true;
202     }
203 
checkLoadResource(String res)204     private boolean checkLoadResource(String res) throws Exception {
205         return MediaUtils.check(loadResource(res), "no decoder found");
206     }
207 
playLoadedVideoTest(final String res, int width, int height)208     private void playLoadedVideoTest(final String res, int width, int height) throws Exception {
209         if (!checkLoadResource(res)) {
210             return; // skip
211         }
212 
213         playLoadedVideo(width, height, 0);
214     }
215 
testIfMediaServerDied(final String res)216     private void testIfMediaServerDied(final String res) throws Exception {
217         mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
218             assertSame(mp, mMediaPlayer);
219             assertTrue("mediaserver process died", what != MediaPlayer.MEDIA_ERROR_SERVER_DIED);
220             Log.w(LOG_TAG, "onError " + what);
221             return false;
222         });
223 
224         mMediaPlayer.setOnCompletionListener(mp -> {
225             assertSame(mp, mMediaPlayer);
226             mOnCompletionCalled.signal();
227         });
228 
229         AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
230         mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
231         afd.close();
232         try {
233             mMediaPlayer.prepare();
234             mMediaPlayer.start();
235             if (!mOnCompletionCalled.waitForSignal(5000)) {
236                 Log.w(LOG_TAG, "testIfMediaServerDied: Timed out waiting for Error/Completion");
237             }
238         } catch (Exception e) {
239             Log.w(LOG_TAG, "playback failed", e);
240         } finally {
241             mMediaPlayer.release();
242         }
243     }
244 
245     // Bug 13652927
246     @Test
testVorbisCrash()247     public void testVorbisCrash() throws Exception {
248         MediaPlayer mp = mMediaPlayer;
249         MediaPlayer mp2 = mMediaPlayer2;
250         AssetFileDescriptor afd2 = getAssetFileDescriptorFor("testmp3_2.mp3");
251         mp2.setDataSource(afd2.getFileDescriptor(), afd2.getStartOffset(), afd2.getLength());
252         afd2.close();
253         mp2.prepare();
254         mp2.setLooping(true);
255         mp2.start();
256 
257         for (int i = 0; i < 20; i++) {
258             try {
259                 AssetFileDescriptor afd = getAssetFileDescriptorFor("bug13652927.ogg");
260                 mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
261                 afd.close();
262                 mp.prepare();
263                 fail("shouldn't be here");
264             } catch (Exception e) {
265                 // expected to fail
266                 Log.i("@@@", "failed: " + e);
267             }
268             Thread.sleep(500);
269             assertTrue("media server died", mp2.isPlaying());
270             mp.reset();
271         }
272     }
273 
274     @Presubmit
275     @Test
testPlayNullSourcePath()276     public void testPlayNullSourcePath() throws Exception {
277         try {
278             mMediaPlayer.setDataSource((String) null);
279             fail("Null path was accepted");
280         } catch (RuntimeException e) {
281             // expected
282         }
283     }
284 
285     @Test
testPlayContentUri()286     public void testPlayContentUri() throws Exception {
287         String testFile = "testmp3_2.mp3";
288         File localFile;
289         try (AssetFileDescriptor mediaFd = getAssetFileDescriptorFor(testFile)) {
290             Environment.getExternalStorageDirectory();
291             File externalFilesDir = mContext.getExternalFilesDir(/* type= */ null);
292             localFile = new File(externalFilesDir, "test_files/" + testFile);
293             localFile.mkdirs();
294             Files.copy(
295                     mediaFd.createInputStream(),
296                     localFile.toPath(),
297                     StandardCopyOption.REPLACE_EXISTING);
298         }
299 
300         MediaPlayer mediaPlayer = new MediaPlayer();
301         try {
302             Uri contentUri = FileProvider.getUriForFile(
303                     mContext, /* authority= */ "com.android.media.player.cts.provider", localFile);
304             mediaPlayer.setDataSource(mContext, contentUri);
305             mediaPlayer.prepare();
306 
307             assertFalse(mediaPlayer.isPlaying());
308             mediaPlayer.start();
309             assertTrue(mediaPlayer.isPlaying());
310 
311             // waiting to complete
312             while (mediaPlayer.isPlaying()) {
313                 Thread.sleep(SLEEP_TIME);
314             }
315         } finally {
316             mediaPlayer.release();
317             localFile.delete();
318         }
319     }
320 
321     @Test
testPlayAudioFromDataURI()322     public void testPlayAudioFromDataURI() throws Exception {
323         final int mp3Duration = 34909;
324         final int tolerance = 70;
325         final int seekDuration = 100;
326 
327         // This is "testmp3_2.raw", base64-encoded.
328         final String res = "testmp3_3.raw";
329 
330         Preconditions.assertTestFileExists(mInpPrefix + res);
331         InputStream is = new FileInputStream(mInpPrefix + res);
332         BufferedReader reader = new BufferedReader(new InputStreamReader(is));
333 
334         Uri uri = Uri.parse("data:;base64," + reader.readLine());
335 
336         MediaPlayer mp = MediaPlayer.create(mContext, uri);
337 
338         try {
339             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
340             mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
341 
342             assertFalse(mp.isPlaying());
343             mp.start();
344             assertTrue(mp.isPlaying());
345 
346             assertFalse(mp.isLooping());
347             mp.setLooping(true);
348             assertTrue(mp.isLooping());
349 
350             assertEquals(mp3Duration, mp.getDuration(), tolerance);
351             int pos = mp.getCurrentPosition();
352             assertTrue(pos >= 0);
353             assertTrue(pos < mp3Duration - seekDuration);
354 
355             mp.seekTo(pos + seekDuration);
356             assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
357 
358             // test pause and restart
359             mp.pause();
360             Thread.sleep(SLEEP_TIME);
361             assertFalse(mp.isPlaying());
362             mp.start();
363             assertTrue(mp.isPlaying());
364 
365             // test stop and restart
366             mp.stop();
367             mp.reset();
368             mp.setDataSource(mContext, uri);
369             mp.prepare();
370             assertFalse(mp.isPlaying());
371             mp.start();
372             assertTrue(mp.isPlaying());
373 
374             // waiting to complete
375             while(mp.isPlaying()) {
376                 Thread.sleep(SLEEP_TIME);
377             }
378         } finally {
379             mp.release();
380         }
381     }
382 
383     @Test
384     public void testPlayAudioMp3() throws Exception {
385         internalTestPlayAudio("testmp3_2.mp3",
386                 34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
387     }
388 
389     @Test
390     public void testPlayAudioOpus() throws Exception {
391         internalTestPlayAudio("testopus.opus",
392                 34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
393     }
394 
395     @Test
396     public void testPlayAudioAmr() throws Exception {
397         internalTestPlayAudio("testamr.amr",
398                 34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
399     }
400 
401     private void internalTestPlayAudio(final String res,
402             int mp3Duration, int tolerance, int seekDuration) throws Exception {
403         String filePath = mInpPrefix + res;
404         Preconditions.assertTestFileExists(filePath);
405         assumeTrue("codecs not found for " +  filePath, MediaUtils.hasCodecsForResource(filePath));
406         MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(filePath)));
407         try {
408             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
409             mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
410 
411             assertFalse(mp.isPlaying());
412             mp.start();
413             assertTrue(mp.isPlaying());
414 
415             assertFalse(mp.isLooping());
416             mp.setLooping(true);
417             assertTrue(mp.isLooping());
418 
419             assertEquals(mp3Duration, mp.getDuration(), tolerance);
420             int pos = mp.getCurrentPosition();
421             assertTrue(pos >= 0);
422             assertTrue(pos < mp3Duration - seekDuration);
423 
424             mp.seekTo(pos + seekDuration);
425             assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
426 
427             // test pause and restart
428             mp.pause();
429             Thread.sleep(SLEEP_TIME);
430             assertFalse(mp.isPlaying());
431             mp.start();
432             assertTrue(mp.isPlaying());
433 
434             // test stop and restart
435             mp.stop();
436             mp.reset();
437             AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
438             mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
439             afd.close();
440             mp.prepare();
441             assertFalse(mp.isPlaying());
442             mp.start();
443             assertTrue(mp.isPlaying());
444 
445             // waiting to complete
446             while(mp.isPlaying()) {
447                 Thread.sleep(SLEEP_TIME);
448             }
449         } finally {
450             mp.release();
451         }
452     }
453 
454     @Test
455     public void testConcurrentPlayAudio() throws Exception {
456         final String res = "test1m1s.mp3"; // MP3 longer than 1m are usualy offloaded
457         final int recommendedTolerance = 70;
458         final List<Integer> offsets = new ArrayList<>();
459 
460         Preconditions.assertTestFileExists(mInpPrefix + res);
461         List<MediaPlayer> mps = Stream.generate(
462                 () -> MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res))))
463                                       .limit(5).collect(Collectors.toList());
464 
465         try {
466             for (MediaPlayer mp : mps) {
467                 mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
468                 mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
469 
470                 assertFalse(mp.isPlaying());
471                 mp.start();
472                 assertTrue(mp.isPlaying());
473 
474                 assertFalse(mp.isLooping());
475                 mp.setLooping(true);
476                 assertTrue(mp.isLooping());
477 
478                 int pos = mp.getCurrentPosition();
479                 assertTrue(pos >= 0);
480 
481                 Thread.sleep(SLEEP_TIME); // Delay each track to be able to hear them
482             }
483 
484             // Check that all mp3 are playing concurrently here
485             // Record the offsets between streams, but don't enforce them
486             for (MediaPlayer mp : mps) {
487                 int pos = mp.getCurrentPosition();
488                 Thread.sleep(SLEEP_TIME);
489                 offsets.add(Math.abs(pos + SLEEP_TIME - mp.getCurrentPosition()));
490             }
491 
492             if (offsets.stream().anyMatch(offset -> offset > recommendedTolerance)) {
493                 Log.w(LOG_TAG, "testConcurrentPlayAudio: some concurrent playing offsets "
494                         + offsets + " are above the recommended tolerance of "
495                         + recommendedTolerance + "ms.");
496             } else {
497                 Log.i(LOG_TAG, "testConcurrentPlayAudio: all concurrent playing offsets "
498                         + offsets + " are under the recommended tolerance of "
499                         + recommendedTolerance + "ms.");
500             }
501         } finally {
502             mps.forEach(MediaPlayer::release);
503         }
504     }
505 
506     @Test
testPlayAudioLooping()507     public void testPlayAudioLooping() throws Exception {
508         final String res = "testmp3.mp3";
509 
510         Preconditions.assertTestFileExists(mInpPrefix + res);
511         MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
512         try {
513             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
514             mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
515             mp.setLooping(true);
516             mOnCompletionCalled.reset();
517             mp.setOnCompletionListener(mp1 -> {
518                 Log.i("@@@", "got oncompletion");
519                 mOnCompletionCalled.signal();
520             });
521 
522             assertFalse(mp.isPlaying());
523             mp.start();
524             assertTrue(mp.isPlaying());
525 
526             long duration = mp.getDuration();
527             Thread.sleep(duration * 4); // allow for several loops
528             assertTrue(mp.isPlaying());
529             assertEquals("wrong number of completion signals", 0, mOnCompletionCalled.getNumSignal());
530             mp.setLooping(false);
531 
532             // wait for playback to finish
533             while(mp.isPlaying()) {
534                 Thread.sleep(SLEEP_TIME);
535             }
536             assertEquals("wrong number of completion signals", 1, mOnCompletionCalled.getNumSignal());
537         } finally {
538             mp.release();
539         }
540     }
541 
542     @Test
testPlayMidi()543     public void testPlayMidi() throws Exception {
544         runMidiTest("midi8sec.mid", 8000 /* duration */);
545         runMidiTest("testrtttl.rtttl", 30000 /* duration */);
546         runMidiTest("testimy.imy", 5625 /* duration */);
547         runMidiTest("testota.ota", 5906 /* duration */);
548         runMidiTest("testmxmf.mxmf", 29095 /* duration */);
549     }
550 
runMidiTest(final String res, int midiDuration)551     private void runMidiTest(final String res, int midiDuration) throws Exception {
552         final int tolerance = 70;
553         final int seekDuration = 1000;
554 
555         Preconditions.assertTestFileExists(mInpPrefix + res);
556         MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
557         try {
558             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
559             mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
560 
561             mp.start();
562 
563             assertFalse(mp.isLooping());
564             mp.setLooping(true);
565             assertTrue(mp.isLooping());
566 
567             assertEquals(midiDuration, mp.getDuration(), tolerance);
568             int pos = mp.getCurrentPosition();
569             assertTrue(pos >= 0);
570             assertTrue(pos < midiDuration - seekDuration);
571 
572             mp.seekTo(pos + seekDuration);
573             assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
574 
575             // test stop and restart
576             mp.stop();
577             mp.reset();
578             AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
579             mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
580             afd.close();
581             mp.prepare();
582             mp.start();
583 
584             Thread.sleep(SLEEP_TIME);
585         } finally {
586             mp.release();
587         }
588     }
589 
590     private final class VerifyAndSignalTimedText implements MediaPlayer.OnTimedTextListener {
591 
592         final boolean mCheckStartTimeIncrease;
593         final int mTargetSignalCount;
594         int mPrevStartMs = -1;
595 
596         VerifyAndSignalTimedText() {
597             this(Integer.MAX_VALUE, false);
598         }
599 
600         VerifyAndSignalTimedText(int targetSignalCount, boolean checkStartTimeIncrease) {
601             mTargetSignalCount = targetSignalCount;
602             mCheckStartTimeIncrease = checkStartTimeIncrease;
603         }
604 
605         void reset() {
606             mPrevStartMs = -1;
607         }
608 
609         @Override
610         public void onTimedText(MediaPlayer mp, TimedText text) {
611             final int toleranceMs = 500;
612             final int durationMs = 500;
613             int posMs = mMediaPlayer.getCurrentPosition();
614             if (text != null) {
615                 text.getText();
616                 String plainText = text.getText();
617                 if (plainText != null) {
618                     StringTokenizer tokens = new StringTokenizer(plainText.trim(), ":");
619                     int subtitleTrackIndex = Integer.parseInt(tokens.nextToken());
620                     int startMs = Integer.parseInt(tokens.nextToken());
621                     Log.d(LOG_TAG, "text: " + plainText.trim() +
622                           ", trackId: " + subtitleTrackIndex + ", posMs: " + posMs);
623                     assertTrue("The diff between subtitle's start time " + startMs +
624                                " and current time " + posMs +
625                                " is over tolerance " + toleranceMs,
626                                (posMs >= startMs - toleranceMs) &&
627                                (posMs < startMs + durationMs + toleranceMs) );
628                     assertEquals("Expected track: " + mSelectedTimedTextIndex +
629                                  ", actual track: " + subtitleTrackIndex,
630                                  mSelectedTimedTextIndex, subtitleTrackIndex);
631                     assertTrue("timed text start time did not increase; current: " + startMs +
632                                ", previous: " + mPrevStartMs,
633                                !mCheckStartTimeIncrease || startMs > mPrevStartMs);
634                     mPrevStartMs = startMs;
635                     mOnTimedTextCalled.signal();
636                     if (mTargetSignalCount >= mOnTimedTextCalled.getNumSignal()) {
637                         reset();
638                     }
639                 }
640                 Rect bounds = text.getBounds();
641                 if (bounds != null) {
642                     Log.d(LOG_TAG, "bounds: " + bounds);
643                     mBoundsCount++;
644                     Rect expected = new Rect(0, 0, 352, 288);
645                     assertEquals("wrong bounds", expected, bounds);
646                 }
647             }
648         }
649 
650     }
651 
652     static class OutputListener {
653         AudioEffect mVc;
654         Visualizer mVis;
655         boolean mSoundDetected;
OutputListener(int session)656         OutputListener(int session) {
657             // creating a volume controller on output mix ensures that ro.audio.silent mutes
658             // audio after the effects and not before
659             mVc = new AudioEffect(
660                     AudioEffect.EFFECT_TYPE_NULL,
661                     UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
662                     0,
663                     session);
664             mVc.setEnabled(true);
665             mVis = new Visualizer(session);
666             int size = 256;
667             int[] range = Visualizer.getCaptureSizeRange();
668             if (size < range[0]) {
669                 size = range[0];
670             }
671             if (size > range[1]) {
672                 size = range[1];
673             }
674             assertEquals(Visualizer.SUCCESS, mVis.setCaptureSize(size));
675 
676             Visualizer.OnDataCaptureListener onDataCaptureListener =
677                     new Visualizer.OnDataCaptureListener() {
678                         @Override
679                         public void onWaveFormDataCapture(Visualizer visualizer,
680                                 byte[] waveform, int samplingRate) {
681                             if (!mSoundDetected) {
682                                 for (byte b : waveform) {
683                                     // 8 bit unsigned PCM, zero level is at 128, which is -128 when
684                                     // seen as a signed byte
685                                     if (b != -128) {
686                                         mSoundDetected = true;
687                                         break;
688                                     }
689                                 }
690                             }
691                         }
692 
693                         @Override
694                         public void onFftDataCapture(
695                                 Visualizer visualizer, byte[] fft, int samplingRate) {}
696                     };
697 
698             mVis.setDataCaptureListener(
699                     onDataCaptureListener,
700                     /* rate= */ 10000, // In milliHertz.
701                     /* waveform= */ true, // Is PCM.
702                     /* fft= */ false); // Do not request a frequency capture.
703             assertEquals(Visualizer.SUCCESS, mVis.setEnabled(true));
704         }
705 
reset()706         void reset() {
707             mSoundDetected = false;
708         }
709 
heardSound()710         boolean heardSound() {
711             return mSoundDetected;
712         }
713 
release()714         void release() {
715             mVis.release();
716             mVc.release();
717         }
718     }
719 
720     @Test
testPlayAudioTwice()721     public void testPlayAudioTwice() throws Exception {
722 
723         final String res = "camera_click.ogg";
724         AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
725         int oldVolume = Integer.MIN_VALUE;
726 
727         Preconditions.assertTestFileExists(mInpPrefix + res);
728         MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
729         try {
730             // Test requires that the device is not muted. Store the current volume before setting
731             // it to a non-zero value and restore it at the end of the test.
732             oldVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
733             am.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
734 
735             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
736             mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
737 
738             OutputListener listener = new OutputListener(mp.getAudioSessionId());
739 
740             Thread.sleep(SLEEP_TIME);
741             assertFalse("noise heard before test started", listener.heardSound());
742 
743             mp.start();
744             Thread.sleep(SLEEP_TIME);
745             assertFalse("player was still playing after " + SLEEP_TIME + " ms", mp.isPlaying());
746             assertTrue("nothing heard while test ran", listener.heardSound());
747             listener.reset();
748             mp.seekTo(0);
749             mp.start();
750             Thread.sleep(SLEEP_TIME);
751             assertTrue("nothing heard when sound was replayed", listener.heardSound());
752             listener.release();
753         } finally {
754             mp.release();
755             if (oldVolume != Integer.MIN_VALUE) {
756                 am.setStreamVolume(AudioManager.STREAM_MUSIC, oldVolume, 0);
757             }
758         }
759     }
760 
761     @Test
testPlayVideo()762     public void testPlayVideo() throws Exception {
763         playLoadedVideoTest("testvideo.3gp", 352, 288);
764     }
765 
initMediaPlayer(MediaPlayer player)766     private void initMediaPlayer(MediaPlayer player) throws Exception {
767         try (AssetFileDescriptor afd = getAssetFileDescriptorFor("test1m1s.mp3")) {
768             player.reset();
769             player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
770             player.prepare();
771             // Test needs the mediaplayer to playback at least about 5 seconds of content.
772             // Clip used here has a duration of 61 seconds, given PLAYBACK_DURATION_MS for play.
773             // This leaves enough remaining time, with gapless enabled or disabled,
774             player.seekTo(player.getDuration() - PLAYBACK_DURATION_MS);
775         }
776     }
777 
778     @Presubmit
779     @Test
testSetNextMediaPlayerWithReset()780     public void testSetNextMediaPlayerWithReset() throws Exception {
781 
782         initMediaPlayer(mMediaPlayer);
783 
784         try {
785             initMediaPlayer(mMediaPlayer2);
786             mMediaPlayer2.reset();
787             mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
788             fail("setNextMediaPlayer() succeeded with unprepared player");
789         } catch (RuntimeException e) {
790             // expected
791         } finally {
792             mMediaPlayer.reset();
793         }
794     }
795 
796     @Presubmit
797     @Test
testSetNextMediaPlayerWithRelease()798     public void testSetNextMediaPlayerWithRelease() throws Exception {
799 
800         initMediaPlayer(mMediaPlayer);
801 
802         try {
803             initMediaPlayer(mMediaPlayer2);
804             mMediaPlayer2.release();
805             mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
806             fail("setNextMediaPlayer() succeeded with unprepared player");
807         } catch (RuntimeException e) {
808             // expected
809         } finally {
810             mMediaPlayer.reset();
811         }
812     }
813 
814     @Test
testSetNextMediaPlayer()815     public void testSetNextMediaPlayer() throws Exception {
816         final int ITERATIONS = 3;
817         // the +1 is for the trailing test of setNextMediaPlayer(null)
818         final int TOTAL_TIMEOUT_MS = PLAYBACK_DURATION_MS * (ITERATIONS + 1)
819                         + ANR_DETECTION_TIME_MS + 5000 /* listener latency(ms) */;
820         initMediaPlayer(mMediaPlayer);
821 
822         final Monitor mTestCompleted = new Monitor();
823 
824         Thread timer = new Thread(() -> {
825             long startTime = SystemClock.elapsedRealtime();
826             while(true) {
827                 SystemClock.sleep(SLEEP_TIME);
828                 if (mTestCompleted.isSignalled()) {
829                     // done
830                     return;
831                 }
832                 long now = SystemClock.elapsedRealtime();
833                 if ((now - startTime) > TOTAL_TIMEOUT_MS) {
834                     // We've been running beyond TOTAL_TIMEOUT and still aren't done,
835                     // so we're stuck somewhere. Signal ourselves to dump the thread stacks.
836                     android.os.Process.sendSignal(android.os.Process.myPid(), 3);
837                     SystemClock.sleep(2000);
838                     fail("Test is stuck, see ANR stack trace for more info. You may need to" +
839                             " create /data/anr first");
840                     return;
841                 }
842             }
843         });
844 
845         timer.start();
846 
847         try {
848             for (int i = 0; i < ITERATIONS; i++) {
849 
850                 initMediaPlayer(mMediaPlayer2);
851                 mOnCompletionCalled.reset();
852                 mOnInfoCalled.reset();
853                 mMediaPlayer.setOnCompletionListener(mp -> {
854                     assertEquals(mMediaPlayer, mp);
855                     mOnCompletionCalled.signal();
856                 });
857                 mMediaPlayer2.setOnInfoListener((mp, what, extra) -> {
858                     assertEquals(mMediaPlayer2, mp);
859                     if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
860                         mOnInfoCalled.signal();
861                     }
862                     return false;
863                 });
864 
865                 mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
866                 mMediaPlayer.start();
867                 assertTrue(mMediaPlayer.isPlaying());
868                 assertFalse(mOnCompletionCalled.isSignalled());
869                 assertFalse(mMediaPlayer2.isPlaying());
870                 assertFalse(mOnInfoCalled.isSignalled());
871                 while(mMediaPlayer.isPlaying()) {
872                     Thread.sleep(SLEEP_TIME);
873                 }
874                 // wait a little longer in case the callbacks haven't quite made it through yet
875                 Thread.sleep(100);
876                 assertTrue(mMediaPlayer2.isPlaying());
877                 assertTrue(mOnCompletionCalled.isSignalled());
878                 assertTrue(mOnInfoCalled.isSignalled());
879 
880                 // At this point the 1st player is done, and the 2nd one is playing.
881                 // Now swap them, and go through the loop again.
882                 MediaPlayer tmp = mMediaPlayer;
883                 mMediaPlayer = mMediaPlayer2;
884                 mMediaPlayer2 = tmp;
885             }
886 
887             // Now test that setNextMediaPlayer(null) works. 1 is still playing, 2 is done
888             // this is the final "+1" in our time calculations above
889             mOnCompletionCalled.reset();
890             mOnInfoCalled.reset();
891             initMediaPlayer(mMediaPlayer2);
892             mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
893 
894             mMediaPlayer.setOnCompletionListener(mp -> {
895                 assertEquals(mMediaPlayer, mp);
896                 mOnCompletionCalled.signal();
897             });
898             mMediaPlayer2.setOnInfoListener((mp, what, extra) -> {
899                 assertEquals(mMediaPlayer2, mp);
900                 if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
901                     mOnInfoCalled.signal();
902                 }
903                 return false;
904             });
905             assertTrue(mMediaPlayer.isPlaying());
906             assertFalse(mOnCompletionCalled.isSignalled());
907             assertFalse(mMediaPlayer2.isPlaying());
908             assertFalse(mOnInfoCalled.isSignalled());
909             Thread.sleep(SLEEP_TIME);
910             mMediaPlayer.setNextMediaPlayer(null);
911             while(mMediaPlayer.isPlaying()) {
912                 Thread.sleep(SLEEP_TIME);
913             }
914             // wait a little longer in case the callbacks haven't quite made it through yet
915             Thread.sleep(100);
916             assertFalse(mMediaPlayer.isPlaying());
917             assertFalse(mMediaPlayer2.isPlaying());
918             assertTrue(mOnCompletionCalled.isSignalled());
919             assertFalse(mOnInfoCalled.isSignalled());
920 
921         } finally {
922             mMediaPlayer.reset();
923             mMediaPlayer2.reset();
924         }
925         mTestCompleted.signal();
926 
927     }
928 
929     // The following tests are all a bit flaky, which is why they're retried a
930     // few times in a loop.
931 
932     // This test uses one mp3 that is silent but has a strong positive DC offset,
933     // and a second mp3 that is also silent but has a strong negative DC offset.
934     // If the two are played back overlapped, they will cancel each other out,
935     // and result in zeroes being detected. If there is a gap in playback, that
936     // will also result in zeroes being detected.
937     // Note that this test does NOT guarantee that the correct data is played
938     @Test
testGapless1()939     public void testGapless1() throws Exception {
940         flakyTestWrapper("monodcpos.mp3", "monodcneg.mp3");
941     }
942 
943     // This test is similar, but uses two identical m4a files that have some noise
944     // with a strong positive DC offset. This is used to detect if there is
945     // a gap in playback
946     // Note that this test does NOT guarantee that the correct data is played
947     @Test
testGapless2()948     public void testGapless2() throws Exception {
949         flakyTestWrapper("stereonoisedcpos.m4a", "stereonoisedcpos.m4a");
950     }
951 
952     // same as above, but with a mono file
953     @Test
testGapless3()954     public void testGapless3() throws Exception {
955         flakyTestWrapper("mononoisedcpos.m4a", "mononoisedcpos.m4a");
956     }
957 
flakyTestWrapper(final String res1, final String res2)958     private void flakyTestWrapper(final String res1, final String res2) throws Exception {
959         boolean success = false;
960         // test usually succeeds within a few tries, but occasionally may fail
961         // many times in a row, so be aggressive and try up to 20 times
962         for (int i = 0; i < 20 && !success; i++) {
963             try {
964                 testGapless(res1, res2);
965                 success = true;
966             } catch (Throwable t) {
967                 SystemClock.sleep(1000);
968             }
969         }
970         // Try one more time. If this succeeds, we'll consider the test a success,
971         // otherwise the exception gets thrown
972         if (!success) {
973             testGapless(res1, res2);
974         }
975     }
976 
testGapless(final String res1, final String res2)977     private void testGapless(final String res1, final String res2) throws Exception {
978         MediaPlayer mp1 = null;
979         MediaPlayer mp2 = null;
980         AudioEffect vc = null;
981         Visualizer vis = null;
982         AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
983         int oldRingerMode = Integer.MIN_VALUE;
984         int oldVolume = Integer.MIN_VALUE;
985         try {
986             if (am.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
987                 Utils.toggleNotificationPolicyAccess(
988                         mContext.getPackageName(), getInstrumentation(), true /* on */);
989             }
990 
991             mp1 = new MediaPlayer(mContext);
992             mp1.setAudioStreamType(AudioManager.STREAM_MUSIC);
993 
994             AssetFileDescriptor afd = getAssetFileDescriptorFor(res1);
995             mp1.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
996             afd.close();
997             mp1.prepare();
998 
999             int session = mp1.getAudioSessionId();
1000 
1001             mp2 = new MediaPlayer(mContext);
1002             mp2.setAudioSessionId(session);
1003             mp2.setAudioStreamType(AudioManager.STREAM_MUSIC);
1004 
1005             afd = getAssetFileDescriptorFor(res2);
1006             mp2.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
1007             afd.close();
1008             mp2.prepare();
1009 
1010             // creating a volume controller on output mix ensures that ro.audio.silent mutes
1011             // audio after the effects and not before
1012             vc = new AudioEffect(
1013                             AudioEffect.EFFECT_TYPE_NULL,
1014                             UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
1015                             0,
1016                             session);
1017             vc.setEnabled(true);
1018             int captureintervalms = mp1.getDuration() + mp2.getDuration() - 2000;
1019             int size = 256;
1020             int[] range = Visualizer.getCaptureSizeRange();
1021             if (size < range[0]) {
1022                 size = range[0];
1023             }
1024             if (size > range[1]) {
1025                 size = range[1];
1026             }
1027             byte[] vizdata = new byte[size];
1028 
1029             vis = new Visualizer(session);
1030 
1031             oldRingerMode = am.getRingerMode();
1032             // make sure we aren't in silent mode
1033             if (am.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
1034                 am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
1035             }
1036             oldVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
1037             am.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
1038 
1039             assertEquals("setCaptureSize failed",
1040                     Visualizer.SUCCESS, vis.setCaptureSize(vizdata.length));
1041             assertEquals("setEnabled failed", Visualizer.SUCCESS, vis.setEnabled(true));
1042 
1043             mp1.setNextMediaPlayer(mp2);
1044             mp1.start();
1045             assertTrue(mp1.isPlaying());
1046             assertFalse(mp2.isPlaying());
1047             // allow playback to get started
1048             Thread.sleep(SLEEP_TIME);
1049             long start = SystemClock.elapsedRealtime();
1050             // there should be no consecutive zeroes (-128) in the capture buffer
1051             // when going to the next file. If silence is detected right away, then
1052             // the volume is probably turned all the way down (visualizer data
1053             // is captured after volume adjustment).
1054             boolean first = true;
1055             while((SystemClock.elapsedRealtime() - start) < captureintervalms) {
1056                 assertEquals(Visualizer.SUCCESS, vis.getWaveForm(vizdata));
1057                 for (int i = 0; i < vizdata.length - 1; i++) {
1058                     if (vizdata[i] == -128 && vizdata[i + 1] == -128) {
1059                         if (first) {
1060                             fail("silence detected, please increase volume and rerun test");
1061                         } else {
1062                             fail("gap or overlap detected at t=" +
1063                                     (SLEEP_TIME + SystemClock.elapsedRealtime() - start) +
1064                                     ", offset " + i);
1065                         }
1066                         break;
1067                     }
1068                 }
1069                 first = false;
1070             }
1071         } finally {
1072             if (mp1 != null) {
1073                 mp1.release();
1074             }
1075             if (mp2 != null) {
1076                 mp2.release();
1077             }
1078             if (vis != null) {
1079                 vis.release();
1080             }
1081             if (vc != null) {
1082                 vc.release();
1083             }
1084             if (oldRingerMode != Integer.MIN_VALUE) {
1085                 am.setRingerMode(oldRingerMode);
1086             }
1087             if (oldVolume != Integer.MIN_VALUE) {
1088                 am.setStreamVolume(AudioManager.STREAM_MUSIC, oldVolume, 0);
1089             }
1090             Utils.toggleNotificationPolicyAccess(
1091                     mContext.getPackageName(), getInstrumentation(), false  /* on == false */);
1092         }
1093     }
1094 
1095     /**
1096      * Test for reseting a surface during video playback
1097      * After reseting, the video should continue playing
1098      * from the time setDisplay() was called
1099      */
1100     @Test
testVideoSurfaceResetting()1101     public void testVideoSurfaceResetting() throws Exception {
1102         final int tolerance = 150;
1103         final int audioLatencyTolerance = 1000;  /* covers audio path latency variability */
1104         final int seekPos = 4760;  // This is the I-frame position
1105 
1106         final CountDownLatch seekDone = new CountDownLatch(1);
1107 
1108         mMediaPlayer.setOnSeekCompleteListener(mp -> seekDone.countDown());
1109 
1110         if (!checkLoadResource("testvideo.3gp")) {
1111             return; // skip;
1112         }
1113         playLoadedVideo(352, 288, -1);
1114 
1115         Thread.sleep(SLEEP_TIME);
1116 
1117         int posBefore = mMediaPlayer.getCurrentPosition();
1118         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder2());
1119         int posAfter = mMediaPlayer.getCurrentPosition();
1120 
1121         /* temporarily disable timestamp checking because MediaPlayer now seeks to I-frame
1122          * position, instead of requested position. setDisplay invovles a seek operation
1123          * internally.
1124          */
1125         // TODO: uncomment out line below when MediaPlayer can seek to requested position.
1126         // assertEquals(posAfter, posBefore, tolerance);
1127         assertTrue(mMediaPlayer.isPlaying());
1128 
1129         Thread.sleep(SLEEP_TIME);
1130 
1131         mMediaPlayer.seekTo(seekPos);
1132         seekDone.await();
1133         posAfter = mMediaPlayer.getCurrentPosition();
1134         assertEquals(seekPos, posAfter, tolerance + audioLatencyTolerance);
1135 
1136         Thread.sleep(SLEEP_TIME / 2);
1137         posBefore = mMediaPlayer.getCurrentPosition();
1138         mMediaPlayer.setDisplay(null);
1139         posAfter = mMediaPlayer.getCurrentPosition();
1140         // TODO: uncomment out line below when MediaPlayer can seek to requested position.
1141         // assertEquals(posAfter, posBefore, tolerance);
1142         assertTrue(mMediaPlayer.isPlaying());
1143 
1144         Thread.sleep(SLEEP_TIME);
1145 
1146         posBefore = mMediaPlayer.getCurrentPosition();
1147         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
1148         posAfter = mMediaPlayer.getCurrentPosition();
1149 
1150         // TODO: uncomment out line below when MediaPlayer can seek to requested position.
1151         // assertEquals(posAfter, posBefore, tolerance);
1152         assertTrue(mMediaPlayer.isPlaying());
1153 
1154         Thread.sleep(SLEEP_TIME);
1155     }
1156 
1157     @Test
testRecordedVideoPlayback0()1158     public void testRecordedVideoPlayback0() throws Exception {
1159         testRecordedVideoPlaybackWithAngle(0);
1160     }
1161 
1162     @Test
testRecordedVideoPlayback90()1163     public void testRecordedVideoPlayback90() throws Exception {
1164         testRecordedVideoPlaybackWithAngle(90);
1165     }
1166 
1167     @Test
testRecordedVideoPlayback180()1168     public void testRecordedVideoPlayback180() throws Exception {
1169         testRecordedVideoPlaybackWithAngle(180);
1170     }
1171 
1172     @Test
testRecordedVideoPlayback270()1173     public void testRecordedVideoPlayback270() throws Exception {
1174         testRecordedVideoPlaybackWithAngle(270);
1175     }
1176 
hasCamera()1177     private boolean hasCamera() {
1178         return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
1179     }
1180 
testRecordedVideoPlaybackWithAngle(int angle)1181     private void testRecordedVideoPlaybackWithAngle(int angle) throws Exception {
1182         int width = RECORDED_VIDEO_WIDTH;
1183         int height = RECORDED_VIDEO_HEIGHT;
1184         final String file = RECORDED_FILE;
1185         final long durationMs = RECORDED_DURATION_MS;
1186 
1187         if (!hasCamera()) {
1188             return;
1189         }
1190 
1191         boolean isSupported = false;
1192         Camera camera = Camera.open(0);
1193         Camera.Parameters parameters = camera.getParameters();
1194         List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes();
1195         // getSupportedVideoSizes returns null when separate video/preview size
1196         // is not supported.
1197         if (videoSizes == null) {
1198             // If we have CamcorderProfile use it instead of Preview size.
1199             if (CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_LOW)) {
1200                 CamcorderProfile profile = CamcorderProfile.get(0, CamcorderProfile.QUALITY_LOW);
1201                 videoSizes = new ArrayList<>();
1202                 videoSizes.add(camera.new Size(profile.videoFrameWidth, profile.videoFrameHeight));
1203             } else {
1204                 videoSizes = parameters.getSupportedPreviewSizes();
1205             }
1206         }
1207         for (Camera.Size size : videoSizes)
1208         {
1209             if (size.width == width && size.height == height) {
1210                 isSupported = true;
1211                 break;
1212             }
1213         }
1214         camera.release();
1215         if (!isSupported) {
1216             width = videoSizes.get(0).width;
1217             height = videoSizes.get(0).height;
1218         }
1219         checkOrientation(angle);
1220         recordVideo(width, height, angle, file, durationMs);
1221         checkDisplayedVideoSize(width, height, angle, file);
1222         checkVideoRotationAngle(angle, file);
1223     }
1224 
checkOrientation(int angle)1225     private void checkOrientation(int angle) throws Exception {
1226         assertTrue(angle >= 0);
1227         assertTrue(angle < 360);
1228         assertEquals(0, (angle % 90));
1229     }
1230 
1231     private void recordVideo(
1232             int w, int h, int angle, String file, long durationMs) throws Exception {
1233 
1234         MediaRecorder recorder = new MediaRecorder();
1235         recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1236         recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
1237         recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
1238         recorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
1239         recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
1240         recorder.setOutputFile(file);
1241         recorder.setOrientationHint(angle);
1242         recorder.setVideoSize(w, h);
1243         recorder.setPreviewDisplay(getActivity().getSurfaceHolder2().getSurface());
1244         recorder.prepare();
1245         recorder.start();
1246         Thread.sleep(durationMs);
1247         recorder.stop();
1248         recorder.release();
1249     }
1250 
1251     private void checkDisplayedVideoSize(
1252             int w, int h, int angle, String file) throws Exception {
1253 
1254         int displayWidth  = w;
1255         int displayHeight = h;
1256         if ((angle % 180) != 0) {
1257             displayWidth  = h;
1258             displayHeight = w;
1259         }
1260         playVideoTest(file, displayWidth, displayHeight);
1261     }
1262 
1263     private void checkVideoRotationAngle(int angle, String file) throws IOException {
1264         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
1265         retriever.setDataSource(file);
1266         String rotation = retriever.extractMetadata(
1267                 MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
1268         retriever.release();
1269         assertNotNull(rotation);
1270         assertEquals(Integer.parseInt(rotation), angle);
1271     }
1272 
1273     // setPlaybackParams() with non-zero speed should start playback.
1274     @Test
1275     public void testSetPlaybackParamsPositiveSpeed() throws Exception {
1276         if (!checkLoadResource(
1277                 "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
1278             return; // skip
1279         }
1280 
1281         mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
1282         mOnCompletionCalled.reset();
1283         mMediaPlayer.setOnCompletionListener(mp -> mOnCompletionCalled.signal());
1284         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
1285 
1286         mMediaPlayer.prepare();
1287 
1288         mOnSeekCompleteCalled.reset();
1289         mMediaPlayer.seekTo(0);
1290         mOnSeekCompleteCalled.waitForSignal();
1291 
1292         final float playbackRate = 1.0f;
1293 
1294         int playTime = 2000;  // The testing clip is about 10 second long.
1295         mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
1296         assertTrue("MediaPlayer should be playing", mMediaPlayer.isPlaying());
1297         Thread.sleep(playTime);
1298         assertTrue("MediaPlayer should still be playing",
1299                 mMediaPlayer.getCurrentPosition() > 0);
1300 
1301         int duration = mMediaPlayer.getDuration();
1302         mOnSeekCompleteCalled.reset();
1303         mMediaPlayer.seekTo(duration - 1000);
1304         mOnSeekCompleteCalled.waitForSignal();
1305 
1306         mOnCompletionCalled.waitForSignal();
1307         assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
1308         int eosPosition = mMediaPlayer.getCurrentPosition();
1309 
1310         mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
1311         assertTrue("MediaPlayer should be playing after EOS", mMediaPlayer.isPlaying());
1312         Thread.sleep(playTime);
1313         int position = mMediaPlayer.getCurrentPosition();
1314         assertTrue("MediaPlayer should still be playing after EOS",
1315                 position > 0 && position < eosPosition);
1316 
1317         mMediaPlayer.stop();
1318     }
1319 
1320     // setPlaybackParams() with zero speed should pause playback.
1321     @Test
testSetPlaybackParamsZeroSpeed()1322     public void testSetPlaybackParamsZeroSpeed() throws Exception {
1323         if (!checkLoadResource(
1324                 "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
1325             return; // skip
1326         }
1327 
1328         mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
1329         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
1330 
1331         mMediaPlayer.prepare();
1332 
1333         mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.0f));
1334         assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
1335 
1336         int playTime = 2000;  // The testing clip is about 10 second long.
1337         mOnSeekCompleteCalled.reset();
1338         mMediaPlayer.seekTo(0);
1339         mOnSeekCompleteCalled.waitForSignal();
1340         Thread.sleep(playTime);
1341         assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
1342         int positionAtStart = mMediaPlayer.getCurrentPosition();
1343         // Allow both 0 and 23 (the timestamp of the second audio sample) to avoid flaky failures
1344         // on builds that don't include http://r.android.com/2700283.
1345         if (positionAtStart != 0 && positionAtStart != 23) {
1346             fail("MediaPlayer position should be 0 or 23");
1347         }
1348 
1349         mMediaPlayer.start();
1350         Thread.sleep(playTime);
1351         assertTrue("MediaPlayer should be playing", mMediaPlayer.isPlaying());
1352         assertTrue("MediaPlayer position should be > 0", mMediaPlayer.getCurrentPosition() > 0);
1353 
1354         mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.0f));
1355         assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
1356         Thread.sleep(1000);
1357         int position = mMediaPlayer.getCurrentPosition();
1358         Thread.sleep(playTime);
1359         assertEquals("MediaPlayer should be paused", mMediaPlayer.getCurrentPosition(), position);
1360 
1361         mMediaPlayer.stop();
1362     }
1363 
1364     @Test
testPlaybackRate()1365     public void testPlaybackRate() throws Exception {
1366         final int toleranceMs = 1000;
1367         if (!checkLoadResource(
1368                 "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
1369             return; // skip
1370         }
1371 
1372         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
1373         mMediaPlayer.prepare();
1374         SyncParams sync = new SyncParams().allowDefaults();
1375         mMediaPlayer.setSyncParams(sync);
1376         sync = mMediaPlayer.getSyncParams();
1377 
1378         float[] rates = { 0.25f, 0.5f, 1.0f, 2.0f };
1379         for (float playbackRate : rates) {
1380             mMediaPlayer.seekTo(0);
1381             Thread.sleep(1000);
1382             int playTime = 4000;  // The testing clip is about 10 second long.
1383             mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
1384             mMediaPlayer.start();
1385             Thread.sleep(playTime);
1386             PlaybackParams pbp = mMediaPlayer.getPlaybackParams();
1387             assertEquals(
1388                     playbackRate, pbp.getSpeed(),
1389                     FLOAT_TOLERANCE + playbackRate * sync.getTolerance());
1390             assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
1391 
1392             int playedMediaDurationMs = mMediaPlayer.getCurrentPosition();
1393             int diff = Math.abs((int)(playedMediaDurationMs / playbackRate) - playTime);
1394             if (diff > toleranceMs) {
1395                 fail("Media player had error in playback rate " + playbackRate
1396                      + ", play time is " + playTime + " vs expected " + playedMediaDurationMs);
1397             }
1398             mMediaPlayer.pause();
1399             pbp = mMediaPlayer.getPlaybackParams();
1400             assertEquals(0.f, pbp.getSpeed(), FLOAT_TOLERANCE);
1401         }
1402         mMediaPlayer.stop();
1403     }
1404 
1405     @Presubmit
1406     @Test
testSeekModes()1407     public void testSeekModes() throws Exception {
1408         // This clip has 2 I frames at 66687us and 4299687us.
1409         if (!checkLoadResource(
1410                 "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4")) {
1411             return; // skip
1412         }
1413 
1414         mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
1415         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
1416         mMediaPlayer.prepare();
1417         mOnSeekCompleteCalled.reset();
1418         mMediaPlayer.start();
1419 
1420         final int seekPosMs = 3000;
1421         final int timeToleranceMs = 100;
1422         final int syncTime1Ms = 67;
1423         final int syncTime2Ms = 4300;
1424 
1425         // TODO: tighten checking range. For now, ensure mediaplayer doesn't
1426         // seek to previous sync or next sync.
1427         int cp = runSeekMode(MediaPlayer.SEEK_CLOSEST, seekPosMs);
1428         assertTrue("MediaPlayer did not seek to closest position",
1429                 cp > seekPosMs && cp < syncTime2Ms);
1430 
1431         // TODO: tighten checking range. For now, ensure mediaplayer doesn't
1432         // seek to closest position or next sync.
1433         cp = runSeekMode(MediaPlayer.SEEK_PREVIOUS_SYNC, seekPosMs);
1434         assertTrue("MediaPlayer did not seek to preivous sync position",
1435                 cp < seekPosMs - timeToleranceMs);
1436 
1437         // TODO: tighten checking range. For now, ensure mediaplayer doesn't
1438         // seek to closest position or previous sync.
1439         cp = runSeekMode(MediaPlayer.SEEK_NEXT_SYNC, seekPosMs);
1440         assertTrue("MediaPlayer did not seek to next sync position",
1441                 cp > syncTime2Ms - timeToleranceMs);
1442 
1443         // TODO: tighten checking range. For now, ensure mediaplayer doesn't
1444         // seek to closest position or previous sync.
1445         cp = runSeekMode(MediaPlayer.SEEK_CLOSEST_SYNC, seekPosMs);
1446         assertTrue("MediaPlayer did not seek to closest sync position",
1447                 cp > syncTime2Ms - timeToleranceMs);
1448 
1449         mMediaPlayer.stop();
1450     }
1451 
runSeekMode(int seekMode, int seekPosMs)1452     private int runSeekMode(int seekMode, int seekPosMs) throws Exception {
1453         final int sleepIntervalMs = 100;
1454         int timeRemainedMs = 10000;  // total time for testing
1455         final int timeToleranceMs = 100;
1456 
1457         mMediaPlayer.seekTo(seekPosMs, seekMode);
1458         mOnSeekCompleteCalled.waitForSignal();
1459         mOnSeekCompleteCalled.reset();
1460         int cp = -seekPosMs;
1461         while (timeRemainedMs > 0) {
1462             cp = mMediaPlayer.getCurrentPosition();
1463             // Wait till MediaPlayer starts rendering since MediaPlayer caches
1464             // seek position as current position.
1465             if (cp < seekPosMs - timeToleranceMs || cp > seekPosMs + timeToleranceMs) {
1466                 break;
1467             }
1468             timeRemainedMs -= sleepIntervalMs;
1469             Thread.sleep(sleepIntervalMs);
1470         }
1471         assertTrue("MediaPlayer did not finish seeking in time for mode " + seekMode,
1472                 timeRemainedMs > 0);
1473         return cp;
1474     }
1475 
1476     @Test
testGetTimestamp()1477     public void testGetTimestamp() throws Exception {
1478         final int toleranceUs = 100000;
1479         final float playbackRate = 1.0f;
1480         if (!checkLoadResource(
1481                 "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
1482             return; // skip
1483         }
1484 
1485         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
1486         mMediaPlayer.prepare();
1487         mMediaPlayer.start();
1488         mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
1489         Thread.sleep(SLEEP_TIME);  // let player get into stable state.
1490         long nt1 = System.nanoTime();
1491         MediaTimestamp ts1 = mMediaPlayer.getTimestamp();
1492         long nt2 = System.nanoTime();
1493         assertNotNull("Media player should return a valid time stamp", ts1);
1494         assertEquals("MediaPlayer had error in clockRate " + ts1.getMediaClockRate(),
1495                 playbackRate, ts1.getMediaClockRate(), 0.001f);
1496         assertTrue("The nanoTime of Media timestamp should be taken when getTimestamp is called.",
1497                 nt1 <= ts1.getAnchorSystemNanoTime() && ts1.getAnchorSystemNanoTime() <= nt2);
1498 
1499         mMediaPlayer.pause();
1500         ts1 = mMediaPlayer.getTimestamp();
1501         assertNotNull("Media player should return a valid time stamp", ts1);
1502         assertEquals("Media player should have play rate of 0.0f when paused", 0.0f,
1503                 ts1.getMediaClockRate());
1504 
1505         mMediaPlayer.seekTo(0);
1506         mMediaPlayer.start();
1507         Thread.sleep(SLEEP_TIME);  // let player get into stable state.
1508         int playTime = 4000;  // The testing clip is about 10 second long.
1509         ts1 = mMediaPlayer.getTimestamp();
1510         assertNotNull("Media player should return a valid time stamp", ts1);
1511         Thread.sleep(playTime);
1512         MediaTimestamp ts2 = mMediaPlayer.getTimestamp();
1513         assertNotNull("Media player should return a valid time stamp", ts2);
1514         assertEquals("The clockRate should not be changed.", ts1.getMediaClockRate(),
1515                 ts2.getMediaClockRate());
1516         assertEquals("MediaPlayer had error in timestamp.",
1517                 ts1.getAnchorMediaTimeUs() + (long)(playTime * ts1.getMediaClockRate() * 1000),
1518                 ts2.getAnchorMediaTimeUs(), toleranceUs);
1519 
1520         mMediaPlayer.stop();
1521     }
1522 
1523     @Test
testMediaTimeDiscontinuity()1524     public void testMediaTimeDiscontinuity() throws Exception {
1525         if (!checkLoadResource(
1526                 "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4")) {
1527             return; // skip
1528         }
1529 
1530         mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
1531         final BlockingDeque<MediaTimestamp> timestamps = new LinkedBlockingDeque<>();
1532         mMediaPlayer.setOnMediaTimeDiscontinuityListener(
1533                 (mp, timestamp) -> {
1534                     mOnMediaTimeDiscontinuityCalled.signal();
1535                     timestamps.add(timestamp);
1536                 });
1537         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
1538         mMediaPlayer.prepare();
1539 
1540         // Timestamp needs to be reported when playback starts.
1541         mOnMediaTimeDiscontinuityCalled.reset();
1542         mMediaPlayer.start();
1543         do {
1544             assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
1545         } while (timestamps.getLast().getMediaClockRate() != 1.0f);
1546 
1547         // Timestamp needs to be reported when seeking is done.
1548         mOnSeekCompleteCalled.reset();
1549         mOnMediaTimeDiscontinuityCalled.reset();
1550         mMediaPlayer.seekTo(3000);
1551         mOnSeekCompleteCalled.waitForSignal();
1552         do {
1553             assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
1554         } while (timestamps.getLast().getMediaClockRate() != 1.0f);
1555 
1556         // Timestamp needs to be updated when playback rate changes.
1557         mOnMediaTimeDiscontinuityCalled.reset();
1558         mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.5f));
1559         do {
1560             assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
1561         } while (timestamps.getLast().getMediaClockRate() != 0.5f);
1562 
1563         // Timestamp needs to be updated when player is paused.
1564         mOnMediaTimeDiscontinuityCalled.reset();
1565         mMediaPlayer.pause();
1566         do {
1567             assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
1568         } while (timestamps.getLast().getMediaClockRate() != 0.0f);
1569 
1570         // Check if there is no more notification after clearing listener.
1571         mMediaPlayer.clearOnMediaTimeDiscontinuityListener();
1572         mMediaPlayer.start();
1573         mOnMediaTimeDiscontinuityCalled.reset();
1574         Thread.sleep(1000);
1575         assertEquals(0, mOnMediaTimeDiscontinuityCalled.getNumSignal());
1576 
1577         mMediaPlayer.reset();
1578     }
1579 
1580     @Test
testLocalVideo_MKV_H265_1280x720_500kbps_25fps_AAC_Stereo_128kbps_44100Hz()1581     public void testLocalVideo_MKV_H265_1280x720_500kbps_25fps_AAC_Stereo_128kbps_44100Hz()
1582             throws Exception {
1583         playLoadedVideoTest("video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv",
1584                 1280, 720);
1585     }
1586     @Test
testLocalVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz()1587     public void testLocalVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz()
1588             throws Exception {
1589         playLoadedVideoTest("video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
1590                 480, 360);
1591     }
1592 
1593     @Test
testLocalVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz()1594     public void testLocalVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz()
1595             throws Exception {
1596         playLoadedVideoTest("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
1597                 480, 360);
1598     }
1599 
1600     @Test
testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz()1601     public void testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz()
1602             throws Exception {
1603         playLoadedVideoTest("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
1604                 480, 360);
1605     }
1606 
1607     @Test
testLocalVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz()1608     public void testLocalVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz()
1609             throws Exception {
1610         playLoadedVideoTest("video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
1611                 480, 360);
1612     }
1613 
1614     @Test
testLocalVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz()1615     public void testLocalVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz()
1616             throws Exception {
1617         playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
1618                 480, 360);
1619     }
1620 
1621     @Test
testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz()1622     public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz()
1623             throws Exception {
1624         playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
1625                 480, 360);
1626     }
1627 
1628     @Test
testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz_frag()1629     public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz_frag()
1630             throws Exception {
1631         playLoadedVideoTest(
1632                 "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4",
1633                 480, 360);
1634     }
1635 
1636 
1637     @Test
testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()1638     public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()
1639             throws Exception {
1640         playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4",
1641                 480, 360);
1642     }
1643 
1644     @Test
testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz()1645     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz()
1646             throws Exception {
1647         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176,
1648                 144);
1649     }
1650 
1651     @Test
testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz()1652     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz()
1653             throws Exception {
1654         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz.3gp", 176,
1655                 144);
1656     }
1657 
1658     @Test
testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz()1659     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz()
1660             throws Exception {
1661         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz.3gp",
1662                 176, 144);
1663     }
1664 
1665     @Test
testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()1666     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()
1667             throws Exception {
1668         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_22050hz.3gp",
1669                 176, 144);
1670     }
1671 
1672     @Test
testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()1673     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()
1674             throws Exception {
1675         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz.3gp",
1676                 176, 144);
1677     }
1678 
1679     @Test
testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()1680     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()
1681             throws Exception {
1682         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
1683                 176, 144);
1684     }
1685 
1686     @Test
testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()1687     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()
1688             throws Exception {
1689         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz.3gp", 176,
1690                 144);
1691     }
1692 
1693     @Test
testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz()1694     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz()
1695             throws Exception {
1696         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz.3gp", 176,
1697                 144);
1698     }
1699 
1700     @Test
testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz()1701     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz()
1702             throws Exception {
1703         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz.3gp",
1704                 176, 144);
1705     }
1706 
1707     @Test
testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()1708     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()
1709             throws Exception {
1710         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_22050hz.3gp",
1711                 176, 144);
1712     }
1713 
1714     @Test
testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()1715     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()
1716             throws Exception {
1717         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz.3gp",
1718                 176, 144);
1719     }
1720 
1721     @Test
testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()1722     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()
1723             throws Exception {
1724         playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_22050hz.3gp",
1725                 176, 144);
1726     }
1727 
1728     @Test
testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()1729     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()
1730             throws Exception {
1731         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176,
1732                 144);
1733     }
1734 
1735     @Test
testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz()1736     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz()
1737             throws Exception {
1738         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz.3gp", 176,
1739                 144);
1740     }
1741 
1742     @Test
testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz()1743     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz()
1744             throws Exception {
1745         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz.3gp",
1746                 176, 144);
1747     }
1748 
1749     @Test
testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()1750     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()
1751             throws Exception {
1752         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_22050hz.3gp",
1753                 176, 144);
1754     }
1755 
1756     @Test
testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()1757     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()
1758             throws Exception {
1759         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz.3gp",
1760                 176, 144);
1761     }
1762 
1763     @Test
testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()1764     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()
1765             throws Exception {
1766         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
1767                 176, 144);
1768     }
1769 
1770     @Test
testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()1771     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()
1772             throws Exception {
1773         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz.3gp", 176,
1774                 144);
1775     }
1776 
1777     @Test
testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz()1778     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz()
1779             throws Exception {
1780         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz.3gp", 176,
1781                 144);
1782     }
1783 
1784     @Test
testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz()1785     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz()
1786             throws Exception {
1787         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz.3gp",
1788                 176, 144);
1789     }
1790 
1791     @Test
testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()1792     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()
1793             throws Exception {
1794         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_22050hz.3gp",
1795                 176, 144);
1796     }
1797 
1798     @Test
testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()1799     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()
1800             throws Exception {
1801         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp",
1802                 176, 144);
1803     }
1804 
1805     @Test
testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz()1806     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz()
1807             throws Exception {
1808         playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp",
1809                 176, 144);
1810     }
1811 
1812     @Test
testLocalVideo_cp1251_3_a_ms_acm_mp3()1813     public void testLocalVideo_cp1251_3_a_ms_acm_mp3() throws Exception {
1814         playLoadedVideoTest("cp1251_3_a_ms_acm_mp3.mkv", -1, -1);
1815     }
1816 
1817     @Test
testLocalVideo_mkv_audio_pcm_be()1818     public void testLocalVideo_mkv_audio_pcm_be() throws Exception {
1819         playLoadedVideoTest("mkv_audio_pcms16be.mkv", -1, -1);
1820     }
1821 
1822     @Test
testLocalVideo_mkv_audio_pcm_le()1823     public void testLocalVideo_mkv_audio_pcm_le() throws Exception {
1824         playLoadedVideoTest("mkv_audio_pcms16le.mkv", -1, -1);
1825     }
1826 
1827     @Test
testLocalVideo_segment000001_m2ts()1828     public void testLocalVideo_segment000001_m2ts()
1829             throws Exception {
1830         if (checkLoadResource("segment000001.ts")) {
1831             mMediaPlayer.stop();
1832             assertTrue(checkLoadResource("segment000001_m2ts.mp4"));
1833             playLoadedVideo(320, 240, 0);
1834         } else {
1835             MediaUtils.skipTest("no mp2 support, skipping m2ts");
1836         }
1837     }
1838 
readSubtitleTracks()1839     private void readSubtitleTracks() throws Exception {
1840         mSubtitleTrackIndex.clear();
1841         MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
1842         if (trackInfos == null || trackInfos.length == 0) {
1843             return;
1844         }
1845 
1846         Vector<Integer> subtitleTrackIndex = new Vector<>();
1847         for (int i = 0; i < trackInfos.length; ++i) {
1848             assertNotNull(trackInfos[i]);
1849             if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
1850                 subtitleTrackIndex.add(i);
1851             }
1852         }
1853 
1854         mSubtitleTrackIndex.addAll(subtitleTrackIndex);
1855     }
1856 
selectSubtitleTrack(int index)1857     private void selectSubtitleTrack(int index) throws Exception {
1858         int trackIndex = mSubtitleTrackIndex.get(index);
1859         mMediaPlayer.selectTrack(trackIndex);
1860         mSelectedSubtitleIndex = index;
1861     }
1862 
deselectSubtitleTrack(int index)1863     private void deselectSubtitleTrack(int index) throws Exception {
1864         int trackIndex = mSubtitleTrackIndex.get(index);
1865         mMediaPlayer.deselectTrack(trackIndex);
1866         if (mSelectedSubtitleIndex == index) {
1867             mSelectedSubtitleIndex = -1;
1868         }
1869     }
1870 
1871     @Test
testDeselectTrackForSubtitleTracks()1872     public void testDeselectTrackForSubtitleTracks() throws Throwable {
1873         if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
1874             return; // skip;
1875         }
1876 
1877         getInstrumentation().waitForIdleSync();
1878 
1879         mMediaPlayer.setOnSubtitleDataListener((mp, data) -> {
1880             if (data != null && data.getData() != null) {
1881                 mOnSubtitleDataCalled.signal();
1882             }
1883         });
1884         mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
1885             if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
1886                 mOnInfoCalled.signal();
1887             }
1888             return false;
1889         });
1890 
1891         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
1892         mMediaPlayer.setScreenOnWhilePlaying(true);
1893         mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
1894 
1895         mMediaPlayer.prepare();
1896         mMediaPlayer.start();
1897         assertTrue(mMediaPlayer.isPlaying());
1898 
1899         // Closed caption tracks are in-band.
1900         // So, those tracks will be found after processing a number of frames.
1901         mOnInfoCalled.waitForSignal(1500);
1902 
1903         mOnInfoCalled.reset();
1904         mOnInfoCalled.waitForSignal(1500);
1905 
1906         readSubtitleTracks();
1907 
1908         // Run twice to check if repeated selection-deselection on the same track works well.
1909         for (int i = 0; i < 2; i++) {
1910             // Waits until at least one subtitle is fired. Timeout is 2.5 seconds.
1911             selectSubtitleTrack(i);
1912             mOnSubtitleDataCalled.reset();
1913             assertTrue(mOnSubtitleDataCalled.waitForSignal(2500));
1914 
1915             // Try deselecting track.
1916             deselectSubtitleTrack(i);
1917             mOnSubtitleDataCalled.reset();
1918             assertFalse(mOnSubtitleDataCalled.waitForSignal(1500));
1919         }
1920 
1921         try {
1922             deselectSubtitleTrack(0);
1923             fail("Deselecting unselected track: expected RuntimeException, " +
1924                  "but no exception has been triggered.");
1925         } catch (RuntimeException e) {
1926             // expected
1927         }
1928 
1929         mMediaPlayer.stop();
1930     }
1931 
1932     @Test
testChangeSubtitleTrack()1933     public void testChangeSubtitleTrack() throws Throwable {
1934         if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
1935             return; // skip;
1936         }
1937 
1938         mMediaPlayer.setOnSubtitleDataListener((mp, data) -> {
1939             if (data != null && data.getData() != null) {
1940                 mOnSubtitleDataCalled.signal();
1941             }
1942         });
1943         mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
1944             if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
1945                 mOnInfoCalled.signal();
1946             }
1947             return false;
1948         });
1949 
1950         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
1951         mMediaPlayer.setScreenOnWhilePlaying(true);
1952         mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
1953 
1954         mMediaPlayer.prepare();
1955         mMediaPlayer.start();
1956         assertTrue(mMediaPlayer.isPlaying());
1957 
1958         // Closed caption tracks are in-band.
1959         // So, those tracks will be found after processing a number of frames.
1960         mOnInfoCalled.waitForSignal(1500);
1961 
1962         mOnInfoCalled.reset();
1963         mOnInfoCalled.waitForSignal(1500);
1964 
1965         readSubtitleTracks();
1966 
1967         // Waits until at least two captions are fired. Timeout is 2.5 sec.
1968         selectSubtitleTrack(0);
1969         assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
1970 
1971         mOnSubtitleDataCalled.reset();
1972         selectSubtitleTrack(1);
1973         assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
1974 
1975         mMediaPlayer.stop();
1976     }
1977 
1978     @Test
testOnSubtitleDataListener()1979     public void testOnSubtitleDataListener() throws Throwable {
1980         if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
1981             return; // skip;
1982         }
1983 
1984         mMediaPlayer.setOnSubtitleDataListener((mp, data) -> {
1985             if (data != null && data.getData() != null
1986                     && data.getTrackIndex() == mSubtitleTrackIndex.get(0)) {
1987                 mOnSubtitleDataCalled.signal();
1988             }
1989         });
1990         mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
1991             if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
1992                 mOnInfoCalled.signal();
1993             }
1994             return false;
1995         });
1996 
1997         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
1998         mMediaPlayer.setScreenOnWhilePlaying(true);
1999         mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
2000 
2001         mMediaPlayer.prepare();
2002         mMediaPlayer.start();
2003         assertTrue(mMediaPlayer.isPlaying());
2004 
2005         // Closed caption tracks are in-band.
2006         // So, those tracks will be found after processing a number of frames.
2007         mOnInfoCalled.waitForSignal(1500);
2008 
2009         mOnInfoCalled.reset();
2010         mOnInfoCalled.waitForSignal(1500);
2011 
2012         readSubtitleTracks();
2013 
2014         // Waits until at least two captions are fired. Timeout is 2.5 sec.
2015         selectSubtitleTrack(0);
2016         assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
2017 
2018         // Check if there is no more notification after clearing listener.
2019         mMediaPlayer.clearOnSubtitleDataListener();
2020         mMediaPlayer.seekTo(0);
2021         mMediaPlayer.start();
2022         mOnSubtitleDataCalled.reset();
2023         Thread.sleep(2500);
2024         assertEquals(0, mOnSubtitleDataCalled.getNumSignal());
2025 
2026         mMediaPlayer.stop();
2027     }
2028 
2029     @Presubmit
2030     @Test
testGetTrackInfoForVideoWithSubtitleTracks()2031     public void testGetTrackInfoForVideoWithSubtitleTracks() throws Throwable {
2032         if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
2033             return; // skip;
2034         }
2035 
2036         getInstrumentation().waitForIdleSync();
2037 
2038         mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
2039             if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
2040                 mOnInfoCalled.signal();
2041             }
2042             return false;
2043         });
2044 
2045         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
2046         mMediaPlayer.setScreenOnWhilePlaying(true);
2047         mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
2048 
2049         mMediaPlayer.prepare();
2050         mMediaPlayer.start();
2051         assertTrue(mMediaPlayer.isPlaying());
2052 
2053         // The media metadata will be changed while playing since closed caption tracks are in-band
2054         // and those tracks will be found after processing a number of frames. These tracks will be
2055         // found within one second.
2056         mOnInfoCalled.waitForSignal(1500);
2057 
2058         mOnInfoCalled.reset();
2059         mOnInfoCalled.waitForSignal(1500);
2060 
2061         readSubtitleTracks();
2062         assertEquals(2, mSubtitleTrackIndex.size());
2063 
2064         mMediaPlayer.stop();
2065     }
2066 
readTimedTextTracks()2067     private void readTimedTextTracks() throws Exception {
2068         mTimedTextTrackIndex.clear();
2069         MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
2070         if (trackInfos == null || trackInfos.length == 0) {
2071             return;
2072         }
2073 
2074         Vector<Integer> externalTrackIndex = new Vector<>();
2075         for (int i = 0; i < trackInfos.length; ++i) {
2076             assertNotNull(trackInfos[i]);
2077             if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
2078                 MediaFormat format = trackInfos[i].getFormat();
2079                 String mime = format.getString(MediaFormat.KEY_MIME);
2080                 if (MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mime)) {
2081                     externalTrackIndex.add(i);
2082                 } else {
2083                     mTimedTextTrackIndex.add(i);
2084                 }
2085             }
2086         }
2087 
2088         mTimedTextTrackIndex.addAll(externalTrackIndex);
2089     }
2090 
getTimedTextTrackCount()2091     private int getTimedTextTrackCount() {
2092         return mTimedTextTrackIndex.size();
2093     }
2094 
selectTimedTextTrack(int index)2095     private void selectTimedTextTrack(int index) throws Exception {
2096         int trackIndex = mTimedTextTrackIndex.get(index);
2097         mMediaPlayer.selectTrack(trackIndex);
2098         mSelectedTimedTextIndex = index;
2099     }
2100 
deselectTimedTextTrack(int index)2101     private void deselectTimedTextTrack(int index) throws Exception {
2102         int trackIndex = mTimedTextTrackIndex.get(index);
2103         mMediaPlayer.deselectTrack(trackIndex);
2104         if (mSelectedTimedTextIndex == index) {
2105             mSelectedTimedTextIndex = -1;
2106         }
2107     }
2108 
2109     @Test
testDeselectTrackForTimedTextTrack()2110     public void testDeselectTrackForTimedTextTrack() throws Throwable {
2111         if (!checkLoadResource("testvideo_with_2_timedtext_tracks.3gp")) {
2112             return; // skip;
2113         }
2114         runOnUiThread(() -> {
2115             try {
2116                 loadSubtitleSource("test_subtitle1_srt.3gp");
2117             } catch (Exception e) {
2118                 throw new AssertionFailedError(e.getMessage());
2119             }
2120         });
2121         getInstrumentation().waitForIdleSync();
2122 
2123         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
2124         mMediaPlayer.setScreenOnWhilePlaying(true);
2125         mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
2126         mMediaPlayer.setOnTimedTextListener((mp, text) -> {
2127             if (text != null) {
2128                 String plainText = text.getText();
2129                 if (plainText != null) {
2130                     mOnTimedTextCalled.signal();
2131                     Log.d(LOG_TAG, "text: " + plainText.trim());
2132                 }
2133             }
2134         });
2135         mMediaPlayer.prepare();
2136         readTimedTextTracks();
2137         assertEquals(getTimedTextTrackCount(), 3);
2138 
2139         mMediaPlayer.start();
2140         assertTrue(mMediaPlayer.isPlaying());
2141 
2142         // Run twice to check if repeated selection-deselection on the same track works well.
2143         for (int i = 0; i < 2; i++) {
2144             // Waits until at least one subtitle is fired. Timeout is 1.5 sec.
2145             selectTimedTextTrack(0);
2146             mOnTimedTextCalled.reset();
2147             assertTrue(mOnTimedTextCalled.waitForSignal(1500));
2148 
2149             // Try deselecting track.
2150             deselectTimedTextTrack(0);
2151             mOnTimedTextCalled.reset();
2152             assertFalse(mOnTimedTextCalled.waitForSignal(1500));
2153         }
2154 
2155         // Run the same test for external subtitle track.
2156         for (int i = 0; i < 2; i++) {
2157             selectTimedTextTrack(2);
2158             mOnTimedTextCalled.reset();
2159             assertTrue(mOnTimedTextCalled.waitForSignal(1500));
2160 
2161             // Try deselecting track.
2162             deselectTimedTextTrack(2);
2163             mOnTimedTextCalled.reset();
2164             assertFalse(mOnTimedTextCalled.waitForSignal(1500));
2165         }
2166 
2167         try {
2168             deselectTimedTextTrack(0);
2169             fail("Deselecting unselected track: expected RuntimeException, " +
2170                  "but no exception has been triggered.");
2171         } catch (RuntimeException e) {
2172             // expected
2173         }
2174 
2175         mMediaPlayer.stop();
2176     }
2177 
2178     @Test
testChangeTimedTextTrack()2179     public void testChangeTimedTextTrack() throws Throwable {
2180         testChangeTimedTextTrackWithSpeed(1.0f);
2181     }
2182 
2183     @Test
testChangeTimedTextTrackFast()2184     public void testChangeTimedTextTrackFast() throws Throwable {
2185         testChangeTimedTextTrackWithSpeed(2.0f);
2186     }
2187 
testChangeTimedTextTrackWithSpeed(float speed)2188     private void testChangeTimedTextTrackWithSpeed(float speed) throws Throwable {
2189         testTimedText("testvideo_with_2_timedtext_tracks.3gp", 2,
2190                 new String[] {"test_subtitle1_srt.3gp", "test_subtitle2_srt.3gp"},
2191                 new VerifyAndSignalTimedText(),
2192                 () -> {
2193                     selectTimedTextTrack(0);
2194                     mOnTimedTextCalled.reset();
2195 
2196                     mMediaPlayer.start();
2197                     if (speed != 1.0f) {
2198                         mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(speed));
2199                     }
2200 
2201                     assertTrue(mMediaPlayer.isPlaying());
2202 
2203                     // Waits until at least two subtitles are fired. Timeout is 2.5 sec.
2204                     // Please refer the test srt files:
2205                     // test_subtitle1_srt.3gp and test_subtitle2_srt.3gp
2206                     assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
2207 
2208                     selectTimedTextTrack(1);
2209                     mOnTimedTextCalled.reset();
2210                     assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
2211 
2212                     selectTimedTextTrack(2);
2213                     mOnTimedTextCalled.reset();
2214                     assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
2215 
2216                     selectTimedTextTrack(3);
2217                     mOnTimedTextCalled.reset();
2218                     assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
2219                     mMediaPlayer.stop();
2220 
2221                     assertEquals("Wrong bounds count", 2, mBoundsCount);
2222                     return null;
2223                 });
2224     }
2225 
2226     @Test
testSeekWithTimedText()2227     public void testSeekWithTimedText() throws Throwable {
2228         AtomicInteger iteration = new AtomicInteger(5);
2229         AtomicInteger num = new AtomicInteger(10);
2230         try {
2231             Bundle args = InstrumentationRegistry.getArguments();
2232             num.set(Integer.parseInt(args.getString("num", "10")));
2233             iteration.set(Integer.parseInt(args.getString("iteration", "5")));
2234         } catch (Exception e) {
2235             Log.w(LOG_TAG, "bad num/iteration arguments, using default", e);
2236         }
2237         testTimedText("testvideo_with_2_timedtext_tracks.3gp", 2, new String [] {},
2238                 new VerifyAndSignalTimedText(num.get(), true),
2239                 () -> {
2240                     selectTimedTextTrack(0);
2241                     mOnSeekCompleteCalled.reset();
2242                     mOnTimedTextCalled.reset();
2243                     OnSeekCompleteListener seekListener = mp -> mOnSeekCompleteCalled.signal();
2244                     mMediaPlayer.setOnSeekCompleteListener(seekListener);
2245                     mMediaPlayer.start();
2246                     assertTrue(mMediaPlayer.isPlaying());
2247                     int n = num.get();
2248                     for (int i = 0; i < iteration.get(); ++i) {
2249                         assertEquals(n, mOnTimedTextCalled.waitForCountedSignals(n, 15000));
2250                         mOnTimedTextCalled.reset();
2251                         mMediaPlayer.seekTo(0);
2252                         mOnSeekCompleteCalled.waitForSignal();
2253                         mOnSeekCompleteCalled.reset();
2254                     }
2255                     mMediaPlayer.stop();
2256                     return null;
2257                 });
2258     }
2259 
testTimedText( String resource, int numInternalTracks, String[] subtitleResources, OnTimedTextListener onTimedTextListener, Callable<?> testBody)2260     private void testTimedText(
2261             String resource, int numInternalTracks, String[] subtitleResources,
2262             OnTimedTextListener onTimedTextListener, Callable<?> testBody) throws Throwable {
2263         if (!checkLoadResource(resource)) {
2264             return; // skip;
2265         }
2266 
2267         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
2268         mMediaPlayer.setScreenOnWhilePlaying(true);
2269         mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
2270         mMediaPlayer.setOnTimedTextListener(onTimedTextListener);
2271         mBoundsCount = 0;
2272 
2273         mMediaPlayer.prepare();
2274         assertFalse(mMediaPlayer.isPlaying());
2275         runOnUiThread(() -> {
2276             try {
2277                 readTimedTextTracks();
2278             } catch (Exception e) {
2279                 throw new AssertionFailedError(e.getMessage());
2280             }
2281         });
2282         getInstrumentation().waitForIdleSync();
2283         assertEquals(getTimedTextTrackCount(), numInternalTracks);
2284 
2285         runOnUiThread(() -> {
2286             try {
2287                 // Adds two more external subtitle files.
2288                 for (String subRes : subtitleResources) {
2289                     loadSubtitleSource(subRes);
2290                 }
2291                 readTimedTextTracks();
2292             } catch (Exception e) {
2293                 throw new AssertionFailedError(e.getMessage());
2294             }
2295         });
2296         getInstrumentation().waitForIdleSync();
2297         assertEquals(getTimedTextTrackCount(), numInternalTracks + subtitleResources.length);
2298 
2299         testBody.call();
2300     }
2301 
2302     @Presubmit
2303     @Test
testGetTrackInfoForVideoWithTimedText()2304     public void testGetTrackInfoForVideoWithTimedText() throws Throwable {
2305         if (!checkLoadResource("testvideo_with_2_timedtext_tracks.3gp")) {
2306             return; // skip;
2307         }
2308         runOnUiThread(() -> {
2309             try {
2310                 loadSubtitleSource("test_subtitle1_srt.3gp");
2311                 loadSubtitleSource("test_subtitle2_srt.3gp");
2312             } catch (Exception e) {
2313                 throw new AssertionFailedError(e.getMessage());
2314             }
2315         });
2316         getInstrumentation().waitForIdleSync();
2317         mMediaPlayer.prepare();
2318         mMediaPlayer.start();
2319 
2320         readTimedTextTracks();
2321         selectTimedTextTrack(2);
2322 
2323         int count = 0;
2324         MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
2325         assertTrue(trackInfos != null && trackInfos.length != 0);
2326         for (MediaPlayer.TrackInfo trackInfo : trackInfos) {
2327             assertNotNull(trackInfo);
2328             if (trackInfo.getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
2329                 String trackLanguage = trackInfo.getLanguage();
2330                 assertNotNull(trackLanguage);
2331                 trackLanguage = trackLanguage.trim();
2332                 Log.d(LOG_TAG, "track info lang: " + trackLanguage);
2333                 assertTrue("Should not see empty track language with our test data.",
2334                         trackLanguage.length() > 0);
2335                 count++;
2336             }
2337         }
2338         // There are 4 subtitle tracks in total in our test data.
2339         assertEquals(4, count);
2340     }
2341 
2342     /*
2343      *  This test assumes the resources being tested are between 8 and 14 seconds long
2344      *  The ones being used here are 10 seconds long.
2345      */
2346     @Test
testResumeAtEnd()2347     public void testResumeAtEnd() throws Throwable {
2348         int testsRun =
2349             testResumeAtEnd("loudsoftmp3.mp3") +
2350             testResumeAtEnd("loudsoftwav.wav") +
2351             testResumeAtEnd("loudsoftogg.ogg") +
2352             testResumeAtEnd("loudsoftitunes.m4a") +
2353             testResumeAtEnd("loudsoftfaac.m4a") +
2354             testResumeAtEnd("loudsoftaac.aac");
2355         if (testsRun == 0) {
2356             MediaUtils.skipTest("no decoder found");
2357         }
2358     }
2359 
2360     // returns 1 if test was run, 0 otherwise
testResumeAtEnd(final String res)2361     private int testResumeAtEnd(final String res) throws Throwable {
2362         if (!loadResource(res)) {
2363             Log.i(LOG_TAG, "testResumeAtEnd: No decoder found for " + res + " --- skipping.");
2364             return 0; // skip
2365         }
2366         mMediaPlayer.prepare();
2367         mOnCompletionCalled.reset();
2368         mMediaPlayer.setOnCompletionListener(mp -> {
2369             mOnCompletionCalled.signal();
2370             mMediaPlayer.start();
2371         });
2372         // skip the first part of the file so we reach EOF sooner
2373         mMediaPlayer.seekTo(5000);
2374         mMediaPlayer.start();
2375         // sleep long enough that we restart playback at least once, but no more
2376         Thread.sleep(10000);
2377         assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
2378         mMediaPlayer.reset();
2379         assertEquals("wrong number of repetitions", 1, mOnCompletionCalled.getNumSignal());
2380         return 1;
2381     }
2382 
2383     @Test
testPositionAtEnd()2384     public void testPositionAtEnd() throws Throwable {
2385         int testsRun =
2386             testPositionAtEnd("test1m1shighstereo.mp3") +
2387             testPositionAtEnd("loudsoftmp3.mp3") +
2388             testPositionAtEnd("loudsoftwav.wav") +
2389             testPositionAtEnd("loudsoftogg.ogg") +
2390             testPositionAtEnd("loudsoftitunes.m4a") +
2391             testPositionAtEnd("loudsoftfaac.m4a") +
2392             testPositionAtEnd("loudsoftaac.aac");
2393         if (testsRun == 0) {
2394             MediaUtils.skipTest(LOG_TAG, "no decoder found");
2395         }
2396     }
2397 
testPositionAtEnd(final String res)2398     private int testPositionAtEnd(final String res) throws Throwable {
2399         if (!loadResource(res)) {
2400             Log.i(LOG_TAG, "testPositionAtEnd: No decoder found for " + res + " --- skipping.");
2401             return 0; // skip
2402         }
2403         mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
2404         mMediaPlayer.prepare();
2405         int duration = mMediaPlayer.getDuration();
2406         assertTrue("resource too short", duration > 6000);
2407         mOnCompletionCalled.reset();
2408         mMediaPlayer.setOnCompletionListener(mp -> mOnCompletionCalled.signal());
2409         mMediaPlayer.seekTo(duration - 5000);
2410         mMediaPlayer.start();
2411         while (mMediaPlayer.isPlaying()) {
2412             Log.i("@@@@", "position: " + mMediaPlayer.getCurrentPosition());
2413             Thread.sleep(500);
2414         }
2415         Log.i("@@@@", "final position: " + mMediaPlayer.getCurrentPosition());
2416         assertTrue(mMediaPlayer.getCurrentPosition() > duration - 1000);
2417         mMediaPlayer.reset();
2418         return 1;
2419     }
2420 
2421     @Test
testCallback()2422     public void testCallback() throws Throwable {
2423         final int mp4Duration = 8484;
2424 
2425         if (!checkLoadResource("testvideo.3gp")) {
2426             return; // skip;
2427         }
2428 
2429         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
2430         mMediaPlayer.setScreenOnWhilePlaying(true);
2431 
2432         mMediaPlayer.setOnVideoSizeChangedListener(
2433                 (mp, width, height) -> mOnVideoSizeChangedCalled.signal());
2434 
2435         mMediaPlayer.setOnPreparedListener(mp -> mOnPrepareCalled.signal());
2436 
2437         mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
2438 
2439         mOnCompletionCalled.reset();
2440         mMediaPlayer.setOnCompletionListener(mp -> mOnCompletionCalled.signal());
2441 
2442         mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
2443             mOnErrorCalled.signal();
2444             return false;
2445         });
2446 
2447         mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
2448             mOnInfoCalled.signal();
2449             return false;
2450         });
2451 
2452         assertFalse(mOnPrepareCalled.isSignalled());
2453         assertFalse(mOnVideoSizeChangedCalled.isSignalled());
2454         mMediaPlayer.prepare();
2455         mOnPrepareCalled.waitForSignal();
2456         mOnVideoSizeChangedCalled.waitForSignal();
2457         mOnSeekCompleteCalled.reset();
2458         mMediaPlayer.seekTo(mp4Duration >> 1);
2459         mOnSeekCompleteCalled.waitForSignal();
2460         assertFalse(mOnCompletionCalled.isSignalled());
2461         mMediaPlayer.start();
2462         while(mMediaPlayer.isPlaying()) {
2463             Thread.sleep(SLEEP_TIME);
2464         }
2465         assertFalse(mMediaPlayer.isPlaying());
2466         mOnCompletionCalled.waitForSignal();
2467         assertFalse(mOnErrorCalled.isSignalled());
2468         mMediaPlayer.stop();
2469         mMediaPlayer.start();
2470         mOnErrorCalled.waitForSignal();
2471     }
2472 
2473     @Test
testRecordAndPlay()2474     public void testRecordAndPlay() throws Exception {
2475         if (!hasMicrophone()) {
2476             MediaUtils.skipTest(LOG_TAG, "no microphone");
2477             return;
2478         }
2479         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
2480                 || !MediaUtils.checkEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
2481             return; // skip
2482         }
2483         File outputFile = new File(Environment.getExternalStorageDirectory(),
2484                 "record_and_play.3gp");
2485         String outputFileLocation = outputFile.getAbsolutePath();
2486         try {
2487             recordMedia(outputFileLocation);
2488             MediaPlayer mp = new MediaPlayer();
2489             try {
2490                 mp.setDataSource(outputFileLocation);
2491                 mp.prepareAsync();
2492                 Thread.sleep(SLEEP_TIME);
2493                 playAndStop(mp);
2494             } finally {
2495                 mp.release();
2496             }
2497 
2498             Uri uri = Uri.parse(outputFileLocation);
2499             mp = new MediaPlayer();
2500             try {
2501                 mp.setDataSource(mContext, uri);
2502                 mp.prepareAsync();
2503                 Thread.sleep(SLEEP_TIME);
2504                 playAndStop(mp);
2505             } finally {
2506                 mp.release();
2507             }
2508 
2509             try {
2510                 mp = MediaPlayer.create(mContext, uri);
2511                 playAndStop(mp);
2512             } finally {
2513                 if (mp != null) {
2514                     mp.release();
2515                 }
2516             }
2517 
2518             try {
2519                 mp = MediaPlayer.create(mContext, uri, getActivity().getSurfaceHolder());
2520                 playAndStop(mp);
2521             } finally {
2522                 if (mp != null) {
2523                     mp.release();
2524                 }
2525             }
2526         } finally {
2527             outputFile.delete();
2528         }
2529     }
2530 
playAndStop(MediaPlayer mp)2531     private void playAndStop(MediaPlayer mp) throws Exception {
2532         mp.start();
2533         Thread.sleep(SLEEP_TIME);
2534         mp.stop();
2535     }
2536 
recordMedia(String outputFile)2537     private void recordMedia(String outputFile) throws Exception {
2538         MediaRecorder mr = new MediaRecorder();
2539         try {
2540             mr.setAudioSource(MediaRecorder.AudioSource.MIC);
2541             mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
2542             mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
2543             mr.setOutputFile(outputFile);
2544 
2545             mr.prepare();
2546             mr.start();
2547             Thread.sleep(SLEEP_TIME);
2548             mr.stop();
2549         } finally {
2550             mr.release();
2551         }
2552     }
2553 
hasMicrophone()2554     private boolean hasMicrophone() {
2555         return getActivity().getPackageManager().hasSystemFeature(
2556                 PackageManager.FEATURE_MICROPHONE);
2557     }
2558 
2559     // Smoke test playback from a MediaDataSource.
2560     @Test
testPlaybackFromAMediaDataSource()2561     public void testPlaybackFromAMediaDataSource() throws Exception {
2562         final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
2563         final int duration = 10000;
2564 
2565         Preconditions.assertTestFileExists(mInpPrefix + res);
2566         if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
2567             return;
2568         }
2569 
2570         TestMediaDataSource dataSource =
2571                 TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
2572         // Test returning -1 from getSize() to indicate unknown size.
2573         dataSource.returnFromGetSize(-1);
2574         mMediaPlayer.setDataSource(dataSource);
2575         playLoadedVideo(null, null, -1);
2576         assertTrue(mMediaPlayer.isPlaying());
2577 
2578         // Test pause and restart.
2579         mMediaPlayer.pause();
2580         Thread.sleep(SLEEP_TIME);
2581         assertFalse(mMediaPlayer.isPlaying());
2582         mMediaPlayer.start();
2583         assertTrue(mMediaPlayer.isPlaying());
2584 
2585         // Test reset.
2586         mMediaPlayer.stop();
2587         mMediaPlayer.reset();
2588         mMediaPlayer.setDataSource(dataSource);
2589         mMediaPlayer.prepare();
2590         mMediaPlayer.start();
2591         assertTrue(mMediaPlayer.isPlaying());
2592 
2593         // Test seek. Note: the seek position is cached and returned as the
2594         // current position so there's no point in comparing them.
2595         mMediaPlayer.seekTo(duration - SLEEP_TIME);
2596         while (mMediaPlayer.isPlaying()) {
2597             Thread.sleep(SLEEP_TIME);
2598         }
2599     }
2600 
2601     @Presubmit
2602     @Test
testNullMediaDataSourceIsRejected()2603     public void testNullMediaDataSourceIsRejected() throws Exception {
2604         try {
2605             mMediaPlayer.setDataSource((MediaDataSource) null);
2606             fail("Null MediaDataSource was accepted");
2607         } catch (IllegalArgumentException e) {
2608             // expected
2609         }
2610     }
2611 
2612     @Presubmit
2613     @Test
testMediaDataSourceIsClosedOnReset()2614     public void testMediaDataSourceIsClosedOnReset() throws Exception {
2615         TestMediaDataSource dataSource = new TestMediaDataSource(new byte[0]);
2616         mMediaPlayer.setDataSource(dataSource);
2617         mMediaPlayer.reset();
2618         assertTrue(dataSource.isClosed());
2619     }
2620 
2621     @Presubmit
2622     @Test
testPlaybackFailsIfMediaDataSourceThrows()2623     public void testPlaybackFailsIfMediaDataSourceThrows() throws Exception {
2624         final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
2625         Preconditions.assertTestFileExists(mInpPrefix + res);
2626         if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
2627             return;
2628         }
2629 
2630         setOnErrorListener();
2631         TestMediaDataSource dataSource =
2632                 TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
2633         mMediaPlayer.setDataSource(dataSource);
2634         mMediaPlayer.prepare();
2635 
2636         dataSource.throwFromReadAt();
2637         mMediaPlayer.start();
2638         assertTrue(mOnErrorCalled.waitForSignal());
2639     }
2640 
2641     @Presubmit
2642     @Test
testPlaybackFailsIfMediaDataSourceReturnsAnError()2643     public void testPlaybackFailsIfMediaDataSourceReturnsAnError() throws Exception {
2644         final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
2645         Preconditions.assertTestFileExists(mInpPrefix + res);
2646         if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
2647             return;
2648         }
2649 
2650         setOnErrorListener();
2651         TestMediaDataSource dataSource =
2652                 TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
2653         mMediaPlayer.setDataSource(dataSource);
2654         mMediaPlayer.prepare();
2655 
2656         dataSource.returnFromReadAt(-2);
2657         mMediaPlayer.start();
2658         assertTrue(mOnErrorCalled.waitForSignal());
2659     }
2660 
2661     @Presubmit
2662     @Test
testSetOnRtpRxNoticeListenerWithoutPermission()2663     public void testSetOnRtpRxNoticeListenerWithoutPermission() {
2664         try {
2665             mMediaPlayer.setOnRtpRxNoticeListener(
2666                     mContext, Runnable::run, (mp, noticeType, params) -> {});
2667             fail();
2668         } catch (IllegalArgumentException e) {
2669             // Expected. We don't have the required permission.
2670         }
2671     }
2672 
2673     @Presubmit
2674     @Test
testSetOnRtpRxNoticeListenerWithPermission()2675     public void testSetOnRtpRxNoticeListenerWithPermission() {
2676         try {
2677             getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
2678             mMediaPlayer.setOnRtpRxNoticeListener(
2679                     mContext, Runnable::run, (mp, noticeType, params) -> {});
2680         } finally {
2681             getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
2682         }
2683     }
2684 
2685     @Presubmit
2686     @Test
testConstructorWithNullContextFails()2687     public void testConstructorWithNullContextFails() {
2688         assertThrows(NullPointerException.class, () -> new MediaPlayer(/*context=*/null));
2689     }
2690 
2691     /** {@link ContentProvider} implementation which serves local files using content:// URIs. */
2692     public static final class TestFileProvider extends FileProvider {
TestFileProvider()2693         public TestFileProvider() {
2694             super(R.xml.media_player_test_content_path);
2695         }
2696     }
2697 
2698 }
2699