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