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