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.media.MediaFormat; 19 import android.media.MediaPlayer; 20 import android.media.MediaPlayer.TrackInfo; 21 import android.media.TimedMetaData; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.os.Looper; 25 import android.os.PowerManager; 26 import android.os.SystemClock; 27 import android.platform.test.annotations.AppModeFull; 28 import android.test.InstrumentationTestRunner; 29 import android.util.Log; 30 import android.webkit.cts.CtsTestServer; 31 32 import com.android.compatibility.common.util.DynamicConfigDeviceSide; 33 import com.android.compatibility.common.util.MediaUtils; 34 35 import java.io.IOException; 36 import java.io.InterruptedIOException; 37 import java.net.HttpCookie; 38 import java.net.Socket; 39 import java.util.concurrent.atomic.AtomicInteger; 40 import org.apache.http.impl.DefaultHttpServerConnection; 41 import org.apache.http.impl.io.SocketOutputBuffer; 42 import org.apache.http.io.SessionOutputBuffer; 43 import org.apache.http.params.HttpParams; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 48 /** 49 * Tests of MediaPlayer streaming capabilities. 50 */ 51 @NonMediaMainlineTest 52 @AppModeFull(reason = "TODO: evaluate and port to instant") 53 public class StreamingMediaPlayerTest extends MediaPlayerTestBase { 54 55 private static final String TAG = "StreamingMediaPlayerTest"; 56 static final String mInpPrefix = WorkDir.getMediaDirString() + "assets/"; 57 58 private static final String HTTP_H263_AMR_VIDEO_1_KEY = 59 "streaming_media_player_test_http_h263_amr_video1"; 60 private static final String HTTP_H263_AMR_VIDEO_2_KEY = 61 "streaming_media_player_test_http_h263_amr_video2"; 62 private static final String HTTP_H264_BASE_AAC_VIDEO_1_KEY = 63 "streaming_media_player_test_http_h264_base_aac_video1"; 64 private static final String HTTP_H264_BASE_AAC_VIDEO_2_KEY = 65 "streaming_media_player_test_http_h264_base_aac_video2"; 66 private static final String HTTP_MPEG4_SP_AAC_VIDEO_1_KEY = 67 "streaming_media_player_test_http_mpeg4_sp_aac_video1"; 68 private static final String HTTP_MPEG4_SP_AAC_VIDEO_2_KEY = 69 "streaming_media_player_test_http_mpeg4_sp_aac_video2"; 70 private static final String MODULE_NAME = "CtsMediaTestCases"; 71 72 private static final int LOCAL_HLS_BITS_PER_MS = 100 * 1000; 73 74 private DynamicConfigDeviceSide dynamicConfig; 75 76 private CtsTestServer mServer; 77 78 private String mInputUrl; 79 80 @Override setUp()81 protected void setUp() throws Exception { 82 // if launched with InstrumentationTestRunner to pass a command line argument 83 if (getInstrumentation() instanceof InstrumentationTestRunner) { 84 InstrumentationTestRunner testRunner = 85 (InstrumentationTestRunner)getInstrumentation(); 86 87 Bundle arguments = testRunner.getArguments(); 88 mInputUrl = arguments.getString("url"); 89 Log.v(TAG, "setUp: arguments: " + arguments); 90 if (mInputUrl != null) { 91 Log.v(TAG, "setUp: arguments[url] " + mInputUrl); 92 } 93 } 94 95 super.setUp(); 96 dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME); 97 } 98 99 /* RTSP tests are more flaky and vulnerable to network condition. 100 Disable until better solution is available 101 // Streaming RTSP video from YouTube 102 public void testRTSP_H263_AMR_Video1() throws Exception { 103 playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e" 104 + "&fmt=13&user=android-device-test", 176, 144); 105 } 106 public void testRTSP_H263_AMR_Video2() throws Exception { 107 playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617" 108 + "&fmt=13&user=android-device-test", 176, 144); 109 } 110 111 public void testRTSP_MPEG4SP_AAC_Video1() throws Exception { 112 playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e" 113 + "&fmt=17&user=android-device-test", 176, 144); 114 } 115 public void testRTSP_MPEG4SP_AAC_Video2() throws Exception { 116 playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617" 117 + "&fmt=17&user=android-device-test", 176, 144); 118 } 119 120 public void testRTSP_H264Base_AAC_Video1() throws Exception { 121 playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e" 122 + "&fmt=18&user=android-device-test", 480, 270); 123 } 124 public void testRTSP_H264Base_AAC_Video2() throws Exception { 125 playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617" 126 + "&fmt=18&user=android-device-test", 480, 270); 127 } 128 */ 129 // Streaming HTTP video from YouTube testHTTP_H263_AMR_Video1()130 public void testHTTP_H263_AMR_Video1() throws Exception { 131 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { 132 return; // skip 133 } 134 135 String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_1_KEY); 136 playVideoTest(urlString, 176, 144); 137 } 138 testHTTP_H263_AMR_Video2()139 public void testHTTP_H263_AMR_Video2() throws Exception { 140 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { 141 return; // skip 142 } 143 144 String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_2_KEY); 145 playVideoTest(urlString, 176, 144); 146 } 147 testHTTP_MPEG4SP_AAC_Video1()148 public void testHTTP_MPEG4SP_AAC_Video1() throws Exception { 149 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { 150 return; // skip 151 } 152 153 String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_1_KEY); 154 playVideoTest(urlString, 176, 144); 155 } 156 testHTTP_MPEG4SP_AAC_Video2()157 public void testHTTP_MPEG4SP_AAC_Video2() throws Exception { 158 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { 159 return; // skip 160 } 161 162 String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_2_KEY); 163 playVideoTest(urlString, 176, 144); 164 } 165 testHTTP_H264Base_AAC_Video1()166 public void testHTTP_H264Base_AAC_Video1() throws Exception { 167 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 168 return; // skip 169 } 170 171 String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_1_KEY); 172 playVideoTest(urlString, 640, 360); 173 } 174 testHTTP_H264Base_AAC_Video2()175 public void testHTTP_H264Base_AAC_Video2() throws Exception { 176 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 177 return; // skip 178 } 179 180 String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_2_KEY); 181 playVideoTest(urlString, 640, 360); 182 } 183 184 // Streaming HLS video downloaded from YouTube testHLS()185 public void testHLS() throws Exception { 186 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 187 return; // skip 188 } 189 190 // Play stream for 60 seconds 191 // limit rate to workaround multiplication overflow in framework 192 localHlsTest("hls_variant/index.m3u8", 60 * 1000, LOCAL_HLS_BITS_PER_MS, false /*isAudioOnly*/); 193 } 194 testHlsWithHeadersCookies()195 public void testHlsWithHeadersCookies() throws Exception { 196 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 197 return; // skip 198 } 199 200 // TODO: fake values for headers/cookies till we find a server that actually needs them 201 HashMap<String, String> headers = new HashMap<>(); 202 headers.put("header0", "value0"); 203 headers.put("header1", "value1"); 204 205 String cookieName = "auth_1234567"; 206 String cookieValue = "0123456789ABCDEF0123456789ABCDEF"; 207 HttpCookie cookie = new HttpCookie(cookieName, cookieValue); 208 cookie.setHttpOnly(true); 209 cookie.setDomain("www.youtube.com"); 210 cookie.setPath("/"); // all paths 211 cookie.setSecure(false); 212 cookie.setDiscard(false); 213 cookie.setMaxAge(24 * 3600); // 24hrs 214 215 java.util.Vector<HttpCookie> cookies = new java.util.Vector<HttpCookie>(); 216 cookies.add(cookie); 217 218 // Play stream for 60 seconds 219 // limit rate to workaround multiplication overflow in framework 220 localHlsTest("hls_variant/index.m3u8", 60 * 1000, LOCAL_HLS_BITS_PER_MS, false /*isAudioOnly*/); 221 } 222 testHlsSampleAes_bbb_audio_only_overridable()223 public void testHlsSampleAes_bbb_audio_only_overridable() throws Exception { 224 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 225 return; // skip 226 } 227 228 // Play stream for 60 seconds 229 if (mInputUrl != null) { 230 // if url override provided 231 playLiveAudioOnlyTest(mInputUrl, 60 * 1000); 232 } else { 233 localHlsTest("audio_only/index.m3u8", 60 * 1000, -1, true /*isAudioOnly*/); 234 } 235 236 } 237 testHlsSampleAes_bbb_unmuxed_1500k()238 public void testHlsSampleAes_bbb_unmuxed_1500k() throws Exception { 239 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 240 return; // skip 241 } 242 MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080); 243 String[] decoderNames = MediaUtils.getDecoderNames(false, format); 244 245 if (decoderNames.length == 0) { 246 MediaUtils.skipTest("No decoders for " + format); 247 } else { 248 // Play stream for 60 seconds 249 localHlsTest("unmuxed_1500k/index.m3u8", 60 * 1000, -1, false /*isAudioOnly*/); 250 } 251 } 252 253 254 // Streaming audio from local HTTP server testPlayMp3Stream1()255 public void testPlayMp3Stream1() throws Throwable { 256 localHttpAudioStreamTest("ringer.mp3", false, false); 257 } testPlayMp3Stream2()258 public void testPlayMp3Stream2() throws Throwable { 259 localHttpAudioStreamTest("ringer.mp3", false, false); 260 } testPlayMp3StreamRedirect()261 public void testPlayMp3StreamRedirect() throws Throwable { 262 localHttpAudioStreamTest("ringer.mp3", true, false); 263 } testPlayMp3StreamNoLength()264 public void testPlayMp3StreamNoLength() throws Throwable { 265 localHttpAudioStreamTest("noiseandchirps.mp3", false, true); 266 } testPlayOggStream()267 public void testPlayOggStream() throws Throwable { 268 localHttpAudioStreamTest("noiseandchirps.ogg", false, false); 269 } testPlayOggStreamRedirect()270 public void testPlayOggStreamRedirect() throws Throwable { 271 localHttpAudioStreamTest("noiseandchirps.ogg", true, false); 272 } testPlayOggStreamNoLength()273 public void testPlayOggStreamNoLength() throws Throwable { 274 localHttpAudioStreamTest("noiseandchirps.ogg", false, true); 275 } testPlayMp3Stream1Ssl()276 public void testPlayMp3Stream1Ssl() throws Throwable { 277 localHttpsAudioStreamTest("ringer.mp3", false, false); 278 } 279 localHttpAudioStreamTest(final String name, boolean redirect, boolean nolength)280 private void localHttpAudioStreamTest(final String name, boolean redirect, boolean nolength) 281 throws Throwable { 282 mServer = new CtsTestServer(mContext); 283 Preconditions.assertTestFileExists(mInpPrefix + name); 284 try { 285 String stream_url = null; 286 if (redirect) { 287 // Stagefright doesn't have a limit, but we can't test support of infinite redirects 288 // Up to 4 redirects seems reasonable though. 289 stream_url = mServer.getRedirectingAssetUrl(mInpPrefix + name, 4); 290 } else { 291 stream_url = mServer.getAssetUrl(mInpPrefix + name); 292 } 293 if (nolength) { 294 stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX; 295 } 296 297 if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) { 298 return; // skip 299 } 300 301 mMediaPlayer.setDataSource(stream_url); 302 303 mMediaPlayer.setDisplay(getActivity().getSurfaceHolder()); 304 mMediaPlayer.setScreenOnWhilePlaying(true); 305 306 mOnBufferingUpdateCalled.reset(); 307 mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { 308 @Override 309 public void onBufferingUpdate(MediaPlayer mp, int percent) { 310 mOnBufferingUpdateCalled.signal(); 311 } 312 }); 313 mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 314 @Override 315 public boolean onError(MediaPlayer mp, int what, int extra) { 316 fail("Media player had error " + what + " playing " + name); 317 return true; 318 } 319 }); 320 321 assertFalse(mOnBufferingUpdateCalled.isSignalled()); 322 mMediaPlayer.prepare(); 323 324 if (nolength) { 325 mMediaPlayer.start(); 326 Thread.sleep(LONG_SLEEP_TIME); 327 assertFalse(mMediaPlayer.isPlaying()); 328 } else { 329 mOnBufferingUpdateCalled.waitForSignal(); 330 mMediaPlayer.start(); 331 Thread.sleep(SLEEP_TIME); 332 } 333 mMediaPlayer.stop(); 334 mMediaPlayer.reset(); 335 } finally { 336 mServer.shutdown(); 337 } 338 } localHttpsAudioStreamTest(final String name, boolean redirect, boolean nolength)339 private void localHttpsAudioStreamTest(final String name, boolean redirect, boolean nolength) 340 throws Throwable { 341 mServer = new CtsTestServer(mContext, true); 342 Preconditions.assertTestFileExists(mInpPrefix + name); 343 try { 344 String stream_url = null; 345 if (redirect) { 346 // Stagefright doesn't have a limit, but we can't test support of infinite redirects 347 // Up to 4 redirects seems reasonable though. 348 stream_url = mServer.getRedirectingAssetUrl(mInpPrefix + name, 4); 349 } else { 350 stream_url = mServer.getAssetUrl(mInpPrefix + name); 351 } 352 if (nolength) { 353 stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX; 354 } 355 356 mMediaPlayer.setDataSource(stream_url); 357 358 mMediaPlayer.setDisplay(getActivity().getSurfaceHolder()); 359 mMediaPlayer.setScreenOnWhilePlaying(true); 360 361 mOnBufferingUpdateCalled.reset(); 362 mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { 363 @Override 364 public void onBufferingUpdate(MediaPlayer mp, int percent) { 365 mOnBufferingUpdateCalled.signal(); 366 } 367 }); 368 mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 369 @Override 370 public boolean onError(MediaPlayer mp, int what, int extra) { 371 fail("Media player had error " + what + " playing " + name); 372 return true; 373 } 374 }); 375 376 assertFalse(mOnBufferingUpdateCalled.isSignalled()); 377 try { 378 mMediaPlayer.prepare(); 379 } catch (Exception ex) { 380 return; 381 } 382 fail("https playback should have failed"); 383 } finally { 384 mServer.shutdown(); 385 } 386 } 387 testPlayHlsStream()388 public void testPlayHlsStream() throws Throwable { 389 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 390 return; // skip 391 } 392 localHlsTest("hls.m3u8", false, false, false /*isAudioOnly*/); 393 } 394 testPlayHlsStreamWithQueryString()395 public void testPlayHlsStreamWithQueryString() throws Throwable { 396 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 397 return; // skip 398 } 399 localHlsTest("hls.m3u8", true, false, false /*isAudioOnly*/); 400 } 401 testPlayHlsStreamWithRedirect()402 public void testPlayHlsStreamWithRedirect() throws Throwable { 403 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 404 return; // skip 405 } 406 localHlsTest("hls.m3u8", false, true, false /*isAudioOnly*/); 407 } 408 testPlayHlsStreamWithTimedId3()409 public void testPlayHlsStreamWithTimedId3() throws Throwable { 410 if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) { 411 Log.d(TAG, "Device doesn't have video codec, skipping test"); 412 return; 413 } 414 415 mServer = new CtsTestServer(mContext); 416 Preconditions.assertTestFileExists(mInpPrefix + "prog_index.m3u8"); 417 try { 418 // counter must be final if we want to access it inside onTimedMetaData; 419 // use AtomicInteger so we can have a final counter object with mutable integer value. 420 final AtomicInteger counter = new AtomicInteger(); 421 String stream_url = mServer.getAssetUrl(mInpPrefix + "prog_index.m3u8"); 422 mMediaPlayer.setDataSource(stream_url); 423 mMediaPlayer.setDisplay(getActivity().getSurfaceHolder()); 424 mMediaPlayer.setScreenOnWhilePlaying(true); 425 mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK); 426 mMediaPlayer.setOnTimedMetaDataAvailableListener(new MediaPlayer.OnTimedMetaDataAvailableListener() { 427 @Override 428 public void onTimedMetaDataAvailable(MediaPlayer mp, TimedMetaData md) { 429 counter.incrementAndGet(); 430 int pos = mp.getCurrentPosition(); 431 long timeUs = md.getTimestamp(); 432 byte[] rawData = md.getMetaData(); 433 // Raw data contains an id3 tag holding the decimal string representation of 434 // the associated time stamp rounded to the closest half second. 435 436 int offset = 0; 437 offset += 3; // "ID3" 438 offset += 2; // version 439 offset += 1; // flags 440 offset += 4; // size 441 offset += 4; // "TXXX" 442 offset += 4; // frame size 443 offset += 2; // frame flags 444 offset += 1; // "\x03" : UTF-8 encoded Unicode 445 offset += 1; // "\x00" : null-terminated empty description 446 447 int length = rawData.length; 448 length -= offset; 449 length -= 1; // "\x00" : terminating null 450 451 String data = new String(rawData, offset, length); 452 int dataTimeUs = Integer.parseInt(data); 453 assertTrue("Timed ID3 timestamp does not match content", 454 Math.abs(dataTimeUs - timeUs) < 500000); 455 assertTrue("Timed ID3 arrives after timestamp", pos * 1000 < timeUs); 456 } 457 }); 458 459 final Object completion = new Object(); 460 mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 461 int run; 462 @Override 463 public void onCompletion(MediaPlayer mp) { 464 if (run++ == 0) { 465 mMediaPlayer.seekTo(0); 466 mMediaPlayer.start(); 467 } else { 468 mMediaPlayer.stop(); 469 synchronized (completion) { 470 completion.notify(); 471 } 472 } 473 } 474 }); 475 476 mMediaPlayer.prepare(); 477 mMediaPlayer.start(); 478 assertTrue("MediaPlayer not playing", mMediaPlayer.isPlaying()); 479 480 int i = -1; 481 TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo(); 482 for (i = 0; i < trackInfos.length; i++) { 483 TrackInfo trackInfo = trackInfos[i]; 484 if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_METADATA) { 485 break; 486 } 487 } 488 assertTrue("Stream has no timed ID3 track", i >= 0); 489 mMediaPlayer.selectTrack(i); 490 491 synchronized (completion) { 492 completion.wait(); 493 } 494 495 // There are a total of 19 metadata access units in the test stream; every one of them 496 // should be received twice: once before the seek and once after. 497 assertTrue("Incorrect number of timed ID3s recieved", counter.get() == 38); 498 } finally { 499 mServer.shutdown(); 500 } 501 } 502 503 private static class WorkerWithPlayer implements Runnable { 504 private final Object mLock = new Object(); 505 private Looper mLooper; 506 private MediaPlayer mMediaPlayer; 507 508 /** 509 * Creates a worker thread with the given name. The thread 510 * then runs a {@link android.os.Looper}. 511 * @param name A name for the new thread 512 */ WorkerWithPlayer(String name)513 WorkerWithPlayer(String name) { 514 Thread t = new Thread(null, this, name); 515 t.setPriority(Thread.MIN_PRIORITY); 516 t.start(); 517 synchronized (mLock) { 518 while (mLooper == null) { 519 try { 520 mLock.wait(); 521 } catch (InterruptedException ex) { 522 } 523 } 524 } 525 } 526 getPlayer()527 public MediaPlayer getPlayer() { 528 return mMediaPlayer; 529 } 530 531 @Override run()532 public void run() { 533 synchronized (mLock) { 534 Looper.prepare(); 535 mLooper = Looper.myLooper(); 536 mMediaPlayer = new MediaPlayer(); 537 mLock.notifyAll(); 538 } 539 Looper.loop(); 540 } 541 quit()542 public void quit() { 543 mLooper.quit(); 544 mMediaPlayer.release(); 545 } 546 } 547 testBlockingReadRelease()548 public void testBlockingReadRelease() throws Throwable { 549 550 mServer = new CtsTestServer(mContext); 551 552 WorkerWithPlayer worker = new WorkerWithPlayer("player"); 553 final MediaPlayer mp = worker.getPlayer(); 554 555 Preconditions.assertTestFileExists(mInpPrefix + "noiseandchirps.ogg"); 556 try { 557 String path = mServer.getDelayedAssetUrl(mInpPrefix + "noiseandchirps.ogg", 15000); 558 mp.setDataSource(path); 559 mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 560 @Override 561 public void onPrepared(MediaPlayer mp) { 562 fail("prepare should not succeed"); 563 } 564 }); 565 mp.prepareAsync(); 566 Thread.sleep(1000); 567 long start = SystemClock.elapsedRealtime(); 568 mp.release(); 569 long end = SystemClock.elapsedRealtime(); 570 long releaseDuration = (end - start); 571 assertTrue("release took too long: " + releaseDuration, releaseDuration < 1000); 572 } catch (IllegalArgumentException e) { 573 fail(e.getMessage()); 574 } catch (SecurityException e) { 575 fail(e.getMessage()); 576 } catch (IllegalStateException e) { 577 fail(e.getMessage()); 578 } catch (IOException e) { 579 fail(e.getMessage()); 580 } catch (InterruptedException e) { 581 fail(e.getMessage()); 582 } finally { 583 mServer.shutdown(); 584 } 585 586 // give the worker a bit of time to start processing the message before shutting it down 587 Thread.sleep(5000); 588 worker.quit(); 589 } 590 localHlsTest(final String name, boolean appendQueryString, boolean redirect, boolean isAudioOnly)591 private void localHlsTest(final String name, boolean appendQueryString, 592 boolean redirect, boolean isAudioOnly) throws Exception { 593 localHlsTest(name, null, null, appendQueryString, redirect, 10, -1, isAudioOnly); 594 } 595 localHlsTest(final String name, int playTime, int bitsPerMs, boolean isAudioOnly)596 private void localHlsTest(final String name, int playTime, int bitsPerMs, boolean isAudioOnly) 597 throws Exception { 598 localHlsTest(name, null, null, false, false, playTime, bitsPerMs, isAudioOnly); 599 } 600 localHlsTest(String name, Map<String, String> headers, List<HttpCookie> cookies, boolean appendQueryString, boolean redirect, int playTime, int bitsPerMs, boolean isAudioOnly)601 private void localHlsTest(String name, Map<String, String> headers, List<HttpCookie> cookies, 602 boolean appendQueryString, boolean redirect, int playTime, int bitsPerMs, 603 boolean isAudioOnly) throws Exception { 604 if (bitsPerMs >= 0) { 605 mServer = new CtsTestServer(mContext) { 606 @Override 607 protected DefaultHttpServerConnection createHttpServerConnection() { 608 return new RateLimitHttpServerConnection(bitsPerMs); 609 } 610 }; 611 } else { 612 mServer = new CtsTestServer(mContext); 613 } 614 Preconditions.assertTestFileExists(mInpPrefix + name); 615 try { 616 String stream_url = null; 617 if (redirect) { 618 stream_url = mServer.getQueryRedirectingAssetUrl(mInpPrefix + name); 619 } else { 620 stream_url = mServer.getAssetUrl(mInpPrefix + name); 621 } 622 if (appendQueryString) { 623 stream_url += "?foo=bar/baz"; 624 } 625 if (isAudioOnly) { 626 playLiveAudioOnlyTest(Uri.parse(stream_url), headers, cookies, playTime); 627 } else { 628 playLiveVideoTest(Uri.parse(stream_url), headers, cookies, playTime); 629 } 630 } finally { 631 mServer.shutdown(); 632 } 633 } 634 635 private static final class RateLimitHttpServerConnection extends DefaultHttpServerConnection { 636 637 private final int mBytesPerMs; 638 private int mBytesWritten; 639 RateLimitHttpServerConnection(int bitsPerMs)640 public RateLimitHttpServerConnection(int bitsPerMs) { 641 mBytesPerMs = bitsPerMs / 8; 642 } 643 644 @Override createHttpDataTransmitter( Socket socket, int buffersize, HttpParams params)645 protected SessionOutputBuffer createHttpDataTransmitter( 646 Socket socket, int buffersize, HttpParams params) throws IOException { 647 return createSessionOutputBuffer(socket, buffersize, params); 648 } 649 createSessionOutputBuffer( Socket socket, int buffersize, HttpParams params)650 SessionOutputBuffer createSessionOutputBuffer( 651 Socket socket, int buffersize, HttpParams params) throws IOException { 652 return new SocketOutputBuffer(socket, buffersize, params) { 653 @Override 654 public void write(int b) throws IOException { 655 write(new byte[] {(byte)b}); 656 } 657 658 @Override 659 public void write(byte[] b) throws IOException { 660 write(b, 0, b.length); 661 } 662 663 @Override 664 public synchronized void write(byte[] b, int off, int len) throws IOException { 665 mBytesWritten += len; 666 if (mBytesWritten >= mBytesPerMs * 10) { 667 int r = mBytesWritten % mBytesPerMs; 668 int nano = 999999 * r / mBytesPerMs; 669 delay(mBytesWritten / mBytesPerMs, nano); 670 mBytesWritten = 0; 671 } 672 super.write(b, off, len); 673 } 674 675 private void delay(long millis, int nanos) throws IOException { 676 try { 677 Thread.sleep(millis, nanos); 678 flush(); 679 } catch (InterruptedException e) { 680 throw new InterruptedIOException(); 681 } 682 } 683 684 }; 685 } 686 } 687 688 } 689