• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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