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