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