1 /*
2  * Copyright (C) 2011 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.cts;
17 
18 import android.content.Context;
19 import android.content.pm.PackageManager;
20 import android.content.res.AssetFileDescriptor;
21 import android.media.MediaPlayer;
22 import android.media.cts.TestUtils.Monitor;
23 import android.net.Uri;
24 import android.os.ParcelFileDescriptor;
25 import android.os.PersistableBundle;
26 import android.test.ActivityInstrumentationTestCase2;
27 
28 import com.android.compatibility.common.util.MediaUtils;
29 
30 import java.io.File;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.net.HttpCookie;
34 import java.util.List;
35 import java.util.logging.Logger;
36 import java.util.Map;
37 import java.util.Set;
38 
39 /**
40  * Base class for tests which use MediaPlayer to play audio or video.
41  */
42 public class MediaPlayerTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> {
43     private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName());
44 
45     static final String mInpPrefix = WorkDir.getMediaDirString();
46 
47     protected static final int SLEEP_TIME = 1000;
48     protected static final int LONG_SLEEP_TIME = 6000;
49     protected static final int STREAM_RETRIES = 20;
50     protected static boolean sUseScaleToFitMode = false;
51 
52     protected Monitor mOnVideoSizeChangedCalled = new Monitor();
53     protected Monitor mOnVideoRenderingStartCalled = new Monitor();
54     protected Monitor mOnBufferingUpdateCalled = new Monitor();
55     protected Monitor mOnPrepareCalled = new Monitor();
56     protected Monitor mOnSeekCompleteCalled = new Monitor();
57     protected Monitor mOnCompletionCalled = new Monitor();
58     protected Monitor mOnInfoCalled = new Monitor();
59     protected Monitor mOnErrorCalled = new Monitor();
60 
61     protected Context mContext;
62 
63     protected MediaPlayer mMediaPlayer = null;
64     protected MediaPlayer mMediaPlayer2 = null;
65     protected MediaStubActivity mActivity;
66 
MediaPlayerTestBase()67     public MediaPlayerTestBase() {
68         super(MediaStubActivity.class);
69     }
70 
71     @Override
setUp()72     protected void setUp() throws Exception {
73         super.setUp();
74         mActivity = getActivity();
75         getInstrumentation().waitForIdleSync();
76         try {
77             runTestOnUiThread(new Runnable() {
78                 public void run() {
79                     mMediaPlayer = new MediaPlayer();
80                     mMediaPlayer2 = new MediaPlayer();
81                 }
82             });
83         } catch (Throwable e) {
84             e.printStackTrace();
85             fail();
86         }
87         mContext = getInstrumentation().getTargetContext();
88     }
89 
90     @Override
tearDown()91     protected void tearDown() throws Exception {
92         if (mMediaPlayer != null) {
93             mMediaPlayer.release();
94             mMediaPlayer = null;
95         }
96         if (mMediaPlayer2 != null) {
97             mMediaPlayer2.release();
98             mMediaPlayer2 = null;
99         }
100         mActivity = null;
101         super.tearDown();
102     }
103 
getAssetFileDescriptorFor(final String res)104     protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
105             throws FileNotFoundException {
106         Preconditions.assertTestFileExists(mInpPrefix + res);
107         File inpFile = new File(mInpPrefix + res);
108         ParcelFileDescriptor parcelFD =
109                 ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
110         return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
111     }
112 
113     // returns true on success
loadResource(final String res)114     protected boolean loadResource(final String res) throws Exception {
115         Preconditions.assertTestFileExists(mInpPrefix + res);
116         if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
117             return false;
118         }
119 
120         AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
121         try {
122             mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
123                     afd.getLength());
124 
125             // Although it is only meant for video playback, it should not
126             // cause issues for audio-only playback.
127             int videoScalingMode = sUseScaleToFitMode?
128                                     MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT
129                                   : MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING;
130 
131             mMediaPlayer.setVideoScalingMode(videoScalingMode);
132         } finally {
133             afd.close();
134         }
135         sUseScaleToFitMode = !sUseScaleToFitMode;  // Alternate the scaling mode
136         return true;
137     }
138 
checkLoadResource(String res)139     protected boolean checkLoadResource(String res) throws Exception {
140         return MediaUtils.check(loadResource(res), "no decoder found");
141     }
142 
loadSubtitleSource(String res)143     protected void loadSubtitleSource(String res) throws Exception {
144         AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
145         try {
146             mMediaPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(),
147                       afd.getLength(), MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
148         } finally {
149             afd.close();
150         }
151     }
152 
playLiveVideoTest(String path, int playTime)153     protected void playLiveVideoTest(String path, int playTime) throws Exception {
154         playVideoWithRetries(path, null, null, playTime);
155     }
156 
playLiveAudioOnlyTest(String path, int playTime)157     protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception {
158         playVideoWithRetries(path, -1, -1, playTime);
159     }
160 
playVideoTest(String path, int width, int height)161     protected void playVideoTest(String path, int width, int height) throws Exception {
162         playVideoWithRetries(path, width, height, 0);
163     }
164 
playVideoWithRetries(String path, Integer width, Integer height, int playTime)165     protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime)
166             throws Exception {
167         boolean playedSuccessfully = false;
168         for (int i = 0; i < STREAM_RETRIES; i++) {
169           try {
170             mMediaPlayer.reset();
171             mMediaPlayer.setDataSource(path);
172             playLoadedVideo(width, height, playTime);
173             playedSuccessfully = true;
174             break;
175           } catch (PrepareFailedException e) {
176             // prepare() can fail because of network issues, so try again
177             LOG.warning("prepare() failed on try " + i + ", trying playback again");
178           }
179         }
180         assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
181     }
182 
playLoadedVideoTest(final String res, int width, int height)183     protected void playLoadedVideoTest(final String res, int width, int height) throws Exception {
184         if (!checkLoadResource(res)) {
185             return; // skip
186         }
187 
188         playLoadedVideo(width, height, 0);
189     }
190 
playLiveVideoTest( Uri uri, Map<String, String> headers, List<HttpCookie> cookies, int playTime)191     protected void playLiveVideoTest(
192             Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
193             int playTime) throws Exception {
194         playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime);
195     }
196 
playLiveAudioOnlyTest( Uri uri, Map<String, String> headers, List<HttpCookie> cookies, int playTime)197     protected void playLiveAudioOnlyTest(
198             Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
199             int playTime) throws Exception {
200         playVideoWithRetries(uri, headers, cookies, -1 /* width */, -1 /* height */, playTime);
201     }
202 
playVideoWithRetries( Uri uri, Map<String, String> headers, List<HttpCookie> cookies, Integer width, Integer height, int playTime)203     protected void playVideoWithRetries(
204             Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
205             Integer width, Integer height, int playTime) throws Exception {
206         boolean playedSuccessfully = false;
207         for (int i = 0; i < STREAM_RETRIES; i++) {
208             try {
209                 mMediaPlayer.reset();
210                 mMediaPlayer.setDataSource(getInstrumentation().getTargetContext(),
211                         uri, headers, cookies);
212                 playLoadedVideo(width, height, playTime);
213                 playedSuccessfully = true;
214                 break;
215             } catch (PrepareFailedException e) {
216                 // prepare() can fail because of network issues, so try again
217                 // playLoadedVideo already has reset the player so we can try again safely.
218                 LOG.warning("prepare() failed on try " + i + ", trying playback again");
219             }
220         }
221         assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
222     }
223 
224     /**
225      * Play a video which has already been loaded with setDataSource().
226      *
227      * @param width width of the video to verify, or null to skip verification
228      * @param height height of the video to verify, or null to skip verification
229      * @param playTime length of time to play video, or 0 to play entire video.
230      * with a non-negative value, this method stops the playback after the length of
231      * time or the duration the video is elapsed. With a value of -1,
232      * this method simply starts the video and returns immediately without
233      * stoping the video playback.
234      */
playLoadedVideo(final Integer width, final Integer height, int playTime)235     protected void playLoadedVideo(final Integer width, final Integer height, int playTime)
236             throws Exception {
237         final float leftVolume = 0.5f;
238         final float rightVolume = 0.5f;
239 
240         boolean audioOnly = (width != null && width.intValue() == -1) ||
241                 (height != null && height.intValue() == -1);
242 
243         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
244         mMediaPlayer.setScreenOnWhilePlaying(true);
245         mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
246             @Override
247             public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
248                 if (w == 0 && h == 0) {
249                     // A size of 0x0 can be sent initially one time when using NuPlayer.
250                     assertFalse(mOnVideoSizeChangedCalled.isSignalled());
251                     return;
252                 }
253                 mOnVideoSizeChangedCalled.signal();
254                 if (width != null) {
255                     assertEquals(width.intValue(), w);
256                 }
257                 if (height != null) {
258                     assertEquals(height.intValue(), h);
259                 }
260             }
261         });
262         mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
263             @Override
264             public boolean onError(MediaPlayer mp, int what, int extra) {
265                 fail("Media player had error " + what + " playing video");
266                 return true;
267             }
268         });
269         mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
270             @Override
271             public boolean onInfo(MediaPlayer mp, int what, int extra) {
272                 if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
273                     mOnVideoRenderingStartCalled.signal();
274                 }
275                 return true;
276             }
277         });
278         try {
279           mMediaPlayer.prepare();
280         } catch (IOException e) {
281           mMediaPlayer.reset();
282           throw new PrepareFailedException();
283         }
284 
285         mMediaPlayer.start();
286         if (!audioOnly) {
287             mOnVideoSizeChangedCalled.waitForSignal();
288             mOnVideoRenderingStartCalled.waitForSignal();
289         }
290         mMediaPlayer.setVolume(leftVolume, rightVolume);
291 
292         // waiting to complete
293         if (playTime == -1) {
294             return;
295         } else if (playTime == 0) {
296             while (mMediaPlayer.isPlaying()) {
297                 Thread.sleep(SLEEP_TIME);
298             }
299         } else {
300             Thread.sleep(playTime);
301         }
302 
303         // validate a few MediaMetrics.
304         PersistableBundle metrics = mMediaPlayer.getMetrics();
305         if (metrics == null) {
306             fail("MediaPlayer.getMetrics() returned null metrics");
307         } else if (metrics.isEmpty()) {
308             fail("MediaPlayer.getMetrics() returned empty metrics");
309         } else {
310 
311             int size = metrics.size();
312             Set<String> keys = metrics.keySet();
313 
314             if (keys == null) {
315                 fail("MediaMetricsSet returned no keys");
316             } else if (keys.size() != size) {
317                 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
318             }
319 
320             // we played something; so one of these should be non-null
321             String vmime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_VIDEO, null);
322             String amime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_AUDIO, null);
323             if (vmime == null && amime == null) {
324                 fail("getMetrics() returned neither video nor audio mime value");
325             }
326 
327             long duration = metrics.getLong(MediaPlayer.MetricsConstants.DURATION, -2);
328             if (duration == -2) {
329                 fail("getMetrics() didn't return a duration");
330             }
331             long playing = metrics.getLong(MediaPlayer.MetricsConstants.PLAYING, -2);
332             if (playing == -2) {
333                 fail("getMetrics() didn't return a playing time");
334             }
335             if (!keys.contains(MediaPlayer.MetricsConstants.PLAYING)) {
336                 fail("MediaMetricsSet.keys() missing: " + MediaPlayer.MetricsConstants.PLAYING);
337             }
338         }
339 
340         mMediaPlayer.stop();
341     }
342 
343     private static class PrepareFailedException extends Exception {}
344 
isTv()345     public boolean isTv() {
346         PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
347         return pm.hasSystemFeature(pm.FEATURE_TELEVISION)
348                 && pm.hasSystemFeature(pm.FEATURE_LEANBACK);
349     }
350 
checkTv()351     public boolean checkTv() {
352         return MediaUtils.check(isTv(), "not a TV");
353     }
354 
setOnErrorListener()355     protected void setOnErrorListener() {
356         mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
357             @Override
358             public boolean onError(MediaPlayer mp, int what, int extra) {
359                 mOnErrorCalled.signal();
360                 return false;
361             }
362         });
363     }
364 }
365