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