• 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 @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