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