9aeee3ee by David LaPalomento

Reload playlists when EXT-X-ENDLIST isn't present

Modify the parser to include an attribute when the endlist tag shows up in a media playlist. Update test playlist parses to add the attribute where appropriate. Trigger a playlist reload and merge if endlist isn't present in the parse. Clean up some formatting.
1 parent cd134feb
...@@ -345,6 +345,9 @@ ...@@ -345,6 +345,9 @@
345 byterange.offset = entry.offset; 345 byterange.offset = entry.offset;
346 } 346 }
347 }, 347 },
348 'endlist': function() {
349 this.manifest.endList = true;
350 },
348 'inf': function() { 351 'inf': function() {
349 if (!('mediaSequence' in this.manifest)) { 352 if (!('mediaSequence' in this.manifest)) {
350 this.manifest.mediaSequence = 0; 353 this.manifest.mediaSequence = 0;
...@@ -462,12 +465,14 @@ ...@@ -462,12 +465,14 @@
462 */ 465 */
463 merge = function(base, update) { 466 merge = function(base, update) {
464 var 467 var
465 result = mergeOptions({}, base, update), 468 result = mergeOptions({}, base),
466 uri = update.segments[0].uri, 469 uri = update.segments[0].uri,
467 i = base.segments.length, 470 i = base.segments ? base.segments.length : 0,
468 byterange, 471 byterange,
469 segment; 472 segment;
470 473
474 result = mergeOptions(result, update);
475
471 // align and apply the updated segments 476 // align and apply the updated segments
472 while (i--) { 477 while (i--) {
473 segment = base.segments[i]; 478 segment = base.segments[i];
...@@ -488,7 +493,7 @@ ...@@ -488,7 +493,7 @@
488 } 493 }
489 // concatenate the two arrays if there was no overlap 494 // concatenate the two arrays if there was no overlap
490 if (i < 0) { 495 if (i < 0) {
491 result.segments = base.segments.concat(update.segments); 496 result.segments = (base.segments || []).concat(update.segments);
492 } 497 }
493 return result; 498 return result;
494 }; 499 };
......
...@@ -341,8 +341,10 @@ var ...@@ -341,8 +341,10 @@ var
341 variant = bandwidthPlaylists[i]; 341 variant = bandwidthPlaylists[i];
342 342
343 // ignore playlists without resolution information 343 // ignore playlists without resolution information
344 if (!variant.attributes || !variant.attributes.RESOLUTION || 344 if (!variant.attributes ||
345 !variant.attributes.RESOLUTION.width || !variant.attributes.RESOLUTION.height) { 345 !variant.attributes.RESOLUTION ||
346 !variant.attributes.RESOLUTION.width ||
347 !variant.attributes.RESOLUTION.height) {
346 continue; 348 continue;
347 } 349 }
348 350
...@@ -350,7 +352,7 @@ var ...@@ -350,7 +352,7 @@ var
350 // dimensions less than or equal to the player size is the 352 // dimensions less than or equal to the player size is the
351 // best 353 // best
352 if (variant.attributes.RESOLUTION.width <= player.width() && 354 if (variant.attributes.RESOLUTION.width <= player.width() &&
353 variant.attributes.RESOLUTION.height <= player.height()) { 355 variant.attributes.RESOLUTION.height <= player.height()) {
354 resolutionBestVariant = variant; 356 resolutionBestVariant = variant;
355 break; 357 break;
356 } 358 }
...@@ -375,88 +377,107 @@ var ...@@ -375,88 +377,107 @@ var
375 var xhr = new window.XMLHttpRequest(); 377 var xhr = new window.XMLHttpRequest();
376 xhr.open('GET', url); 378 xhr.open('GET', url);
377 xhr.onreadystatechange = function() { 379 xhr.onreadystatechange = function() {
378 var i, parser, playlist, playlistUri; 380 var i, parser, playlist, playlistUri, refreshDelay;
379
380 if (xhr.readyState === 4) {
381 if (xhr.status >= 400 || this.status === 0) {
382 player.hls.error = {
383 status: xhr.status,
384 message: 'HLS playlist request error at URL: ' + url,
385 code: (xhr.status >= 500) ? 4 : 2
386 };
387 player.trigger('error');
388 return;
389 }
390 381
391 // readystate DONE 382 // wait until the request completes
392 parser = new videojs.m3u8.Parser(); 383 if (xhr.readyState !== 4) {
393 parser.push(xhr.responseText); 384 return;
385 }
394 386
395 // master playlists 387 if (xhr.status >= 400 || this.status === 0) {
396 if (parser.manifest.playlists) { 388 player.hls.error = {
397 player.hls.master = parser.manifest; 389 status: xhr.status,
398 downloadPlaylist(resolveUrl(url, parser.manifest.playlists[0].uri)); 390 message: 'HLS playlist request error at URL: ' + url,
399 player.trigger('loadedmanifest'); 391 code: (xhr.status >= 500) ? 4 : 2
400 return; 392 };
401 } 393 player.trigger('error');
394 return;
395 }
402 396
403 // media playlists 397 // readystate DONE
404 if (player.hls.master) { 398 parser = new videojs.m3u8.Parser();
405 // merge this playlist into the master 399 parser.push(xhr.responseText);
406 i = player.hls.master.playlists.length; 400
407 401 // master playlists
408 while (i--) { 402 if (parser.manifest.playlists) {
409 playlist = player.hls.master.playlists[i]; 403 player.hls.master = parser.manifest;
410 playlistUri = resolveUrl(srcUrl, playlist.uri); 404 downloadPlaylist(resolveUrl(url, parser.manifest.playlists[0].uri));
411 if (playlistUri === url) { 405 player.trigger('loadedmanifest');
412 player.hls.master.playlists[i] = 406 return;
413 videojs.util.mergeOptions(playlist, parser.manifest); 407 }
414 }
415 }
416 } else {
417 // infer a master playlist if none was previously requested
418 player.hls.master = {
419 playlists: [parser.manifest]
420 };
421 }
422 408
423 // always start playback with the default rendition 409 // media playlists
424 if (!player.hls.media) { 410 refreshDelay = (parser.manifest.targetDuration || 10) * 1000;
425 player.hls.media = player.hls.master.playlists[0]; 411 if (player.hls.master) {
412 // merge this playlist into the master
413 i = player.hls.master.playlists.length;
414
415 while (i--) {
416 playlist = player.hls.master.playlists[i];
417 playlistUri = resolveUrl(srcUrl, playlist.uri);
418 if (playlistUri === url) {
419 // if the playlist is unchanged since the last reload,
420 // try again after half the target duration
421 // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4
422 if (playlist.segments &&
423 playlist.segments.length === parser.manifest.segments.length) {
424 refreshDelay /= 2;
425 }
426 426
427 // update the duration 427 player.hls.master.playlists[i] =
428 if (parser.manifest.totalDuration) { 428 videojs.m3u8.merge(playlist, parser.manifest);
429 player.duration(parser.manifest.totalDuration);
430 } else {
431 player.duration(totalDuration(parser.manifest));
432 } 429 }
430 }
431 } else {
432 // infer a master playlist if none was previously requested
433 player.hls.master = {
434 playlists: [parser.manifest]
435 };
436 }
433 437
434 // periodicaly check if the buffer needs to be refilled 438 // check the playlist for updates if EXT-X-ENDLIST isn't present
435 player.on('timeupdate', fillBuffer); 439 if (!parser.manifest.endList) {
440 window.setTimeout(function() {
441 downloadPlaylist(url);
442 }, refreshDelay);
443 }
436 444
437 player.trigger('loadedmanifest'); 445 // always start playback with the default rendition
438 player.trigger('loadedmetadata'); 446 if (!player.hls.media) {
439 fillBuffer(); 447 player.hls.media = player.hls.master.playlists[0];
440 return;
441 }
442 448
443 // select a playlist and download its metadata if necessary 449 // update the duration
444 playlist = player.hls.selectPlaylist(); 450 if (parser.manifest.totalDuration) {
445 if (!playlist.segments) { 451 player.duration(parser.manifest.totalDuration);
446 downloadPlaylist(resolveUrl(srcUrl, playlist.uri));
447 } else { 452 } else {
448 player.hls.media = playlist; 453 player.duration(totalDuration(parser.manifest));
449
450 // update the duration
451 if (player.hls.media.totalDuration) {
452 player.duration(player.hls.media.totalDuration);
453 } else {
454 player.duration(totalDuration(player.hls.media));
455 }
456 } 454 }
457 455
456 // periodicaly check if the buffer needs to be refilled
457 player.on('timeupdate', fillBuffer);
458
458 player.trigger('loadedmanifest'); 459 player.trigger('loadedmanifest');
460 player.trigger('loadedmetadata');
461 fillBuffer();
462 return;
459 } 463 }
464
465 // select a playlist and download its metadata if necessary
466 playlist = player.hls.selectPlaylist();
467 if (!playlist.segments) {
468 downloadPlaylist(resolveUrl(srcUrl, playlist.uri));
469 } else {
470 player.hls.media = playlist;
471
472 // update the duration
473 if (player.hls.media.totalDuration) {
474 player.duration(player.hls.media.totalDuration);
475 } else {
476 player.duration(totalDuration(player.hls.media));
477 }
478 }
479
480 player.trigger('loadedmanifest');
460 }; 481 };
461 xhr.send(null); 482 xhr.send(null);
462 }; 483 };
......
...@@ -514,7 +514,7 @@ ...@@ -514,7 +514,7 @@
514 }); 514 });
515 515
516 test('merges a manifest that strictly adds to an earlier one', function() { 516 test('merges a manifest that strictly adds to an earlier one', function() {
517 var key, base, mid, parsed; 517 var key, base, manifest, mid;
518 for (key in window.manifests) { 518 for (key in window.manifests) {
519 if (window.expected[key]) { 519 if (window.expected[key]) {
520 manifest = window.manifests[key]; 520 manifest = window.manifests[key];
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "http://example.com/00004.ts" 20 "uri": "http://example.com/00004.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 10 23 "targetDuration": 10,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -140,5 +140,6 @@ ...@@ -140,5 +140,6 @@
140 "uri": "hls_450k_video.ts" 140 "uri": "hls_450k_video.ts"
141 } 141 }
142 ], 142 ],
143 "targetDuration": 10 143 "targetDuration": 10,
144 "endList": true
144 } 145 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,5 +12,6 @@ ...@@ -12,5 +12,6 @@
12 "uri": "hls_450k_video.ts" 12 "uri": "hls_450k_video.ts"
13 } 13 }
14 ], 14 ],
15 "targetDuration": 10 15 "targetDuration": 10,
16 "endList": true
16 } 17 }
...\ No newline at end of file ...\ No newline at end of file
......
1 { 1 {
2 "allowCache": true, 2 "allowCache": true,
3 "playlists": [{ 3 "playlists": [
4 "attributes": { 4 {
5 "PROGRAM-ID": 1, 5 "attributes": {
6 "BANDWIDTH": 240000, 6 "PROGRAM-ID": 1,
7 "RESOLUTION": { 7 "BANDWIDTH": 240000,
8 "width": 396, 8 "RESOLUTION": {
9 "height": 224 9 "width": 396,
10 } 10 "height": 224
11 }
12 },
13 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001"
11 }, 14 },
12 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001" 15 {
13 }, { 16 "attributes": {
14 "attributes": { 17 "PROGRAM-ID": 1,
15 "PROGRAM-ID": 1, 18 "BANDWIDTH": 40000
16 "BANDWIDTH": 40000 19 },
20 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001"
17 }, 21 },
18 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001" 22 {
19 }, { 23 "attributes": {
20 "attributes": { 24 "PROGRAM-ID": 1,
21 "PROGRAM-ID": 1, 25 "BANDWIDTH": 440000,
22 "BANDWIDTH": 440000, 26 "RESOLUTION": {
23 "RESOLUTION": { 27 "width": 396,
24 "width": 396, 28 "height": 224
25 "height": 224 29 }
26 } 30 },
31 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001"
27 }, 32 },
28 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001" 33 {
29 }, { 34 "attributes": {
30 "attributes": { 35 "PROGRAM-ID": 1,
31 "PROGRAM-ID": 1, 36 "BANDWIDTH": 1928000,
32 "BANDWIDTH": 1928000, 37 "RESOLUTION": {
33 "RESOLUTION": { 38 "width": 960,
34 "width": 960, 39 "height": 540
35 "height": 540 40 }
36 } 41 },
37 }, 42 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
38 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" 43 }
39 }] 44 ]
40 } 45 }
......
...@@ -136,5 +136,6 @@ ...@@ -136,5 +136,6 @@
136 "uri": "hls_450k_video.ts" 136 "uri": "hls_450k_video.ts"
137 } 137 }
138 ], 138 ],
139 "targetDuration": 10 139 "targetDuration": 10,
140 "endList": true
140 } 141 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,5 +12,6 @@ ...@@ -12,5 +12,6 @@
12 "uri": "hls_450k_video.ts" 12 "uri": "hls_450k_video.ts"
13 } 13 }
14 ], 14 ],
15 "targetDuration": 10 15 "targetDuration": 10,
16 "endList": true
16 } 17 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/00004.ts" 20 "uri": "/00004.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 10 23 "targetDuration": 10,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,5 +12,6 @@ ...@@ -12,5 +12,6 @@
12 "uri": "hls_450k_video.ts" 12 "uri": "hls_450k_video.ts"
13 } 13 }
14 ], 14 ],
15 "targetDuration": 10 15 "targetDuration": 10,
16 "endList": true
16 } 17 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" 20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 8 23 "targetDuration": 8,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -27,5 +27,6 @@ ...@@ -27,5 +27,6 @@
27 "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" 27 "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts"
28 } 28 }
29 ], 29 ],
30 "targetDuration": 10 30 "targetDuration": 10,
31 } 31 "endList": true
32 }
...\ No newline at end of file ...\ No newline at end of file
......
1 { 1 {
2 "allowCache": true, 2 "allowCache": true,
3 "playlists": [{ 3 "playlists": [
4 "attributes": { 4 {
5 "PROGRAM-ID": 1, 5 "attributes": {
6 "BANDWIDTH": 240000, 6 "PROGRAM-ID": 1,
7 "RESOLUTION": { 7 "BANDWIDTH": 240000,
8 "width": 396, 8 "RESOLUTION": {
9 "height": 224 9 "width": 396,
10 } 10 "height": 224
11 }
12 },
13 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001"
11 }, 14 },
12 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001" 15 {
13 }, { 16 "attributes": {
14 "attributes": { 17 "PROGRAM-ID": 1,
15 "PROGRAM-ID": 1, 18 "BANDWIDTH": 40000
16 "BANDWIDTH": 40000 19 },
20 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001"
17 }, 21 },
18 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001" 22 {
19 }, { 23 "attributes": {
20 "attributes": { 24 "PROGRAM-ID": 1,
21 "PROGRAM-ID": 1, 25 "BANDWIDTH": 440000,
22 "BANDWIDTH": 440000, 26 "RESOLUTION": {
23 "RESOLUTION": { 27 "width": 396,
24 "width": 396, 28 "height": 224
25 "height": 224 29 }
26 } 30 },
31 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001"
27 }, 32 },
28 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001" 33 {
29 }, { 34 "attributes": {
30 "attributes": { 35 "PROGRAM-ID": 1,
31 "PROGRAM-ID": 1, 36 "BANDWIDTH": 1928000,
32 "BANDWIDTH": 1928000, 37 "RESOLUTION": {
33 "RESOLUTION": { 38 "width": 960,
34 "width": 960, 39 "height": 540
35 "height": 540 40 }
36 } 41 },
37 }, 42 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
38 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" 43 }
39 }] 44 ]
40 } 45 }
......
...@@ -28,5 +28,6 @@ ...@@ -28,5 +28,6 @@
28 "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" 28 "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts"
29 } 29 }
30 ], 30 ],
31 "targetDuration": 10 31 "targetDuration": 10,
32 "endList": true
32 } 33 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -7,5 +7,6 @@ ...@@ -7,5 +7,6 @@
7 "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" 7 "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts"
8 } 8 }
9 ], 9 ],
10 "targetDuration": 8 10 "targetDuration": 8,
11 } 11 "endList": true
12 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -140,5 +140,6 @@ ...@@ -140,5 +140,6 @@
140 "uri": "hls_450k_video.ts" 140 "uri": "hls_450k_video.ts"
141 } 141 }
142 ], 142 ],
143 "targetDuration": 10 143 "targetDuration": 10,
144 } 144 "endList": true
145 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,5 +12,6 @@ ...@@ -12,5 +12,6 @@
12 "uri": "hls_450k_video.ts" 12 "uri": "hls_450k_video.ts"
13 } 13 }
14 ], 14 ],
15 "targetDuration": 10 15 "targetDuration": 10,
16 "endList": true
16 } 17 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" 20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 8 23 "targetDuration": 8,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -27,5 +27,6 @@ ...@@ -27,5 +27,6 @@
27 "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" 27 "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts"
28 } 28 }
29 ], 29 ],
30 "targetDuration": 10 30 "targetDuration": 10,
31 } 31 "endList": true
32 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -139,5 +139,6 @@ ...@@ -139,5 +139,6 @@
139 "duration": 1.4167, 139 "duration": 1.4167,
140 "uri": "hls_450k_video.ts" 140 "uri": "hls_450k_video.ts"
141 } 141 }
142 ] 142 ],
143 "endList": true
143 } 144 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -6,5 +6,6 @@ ...@@ -6,5 +6,6 @@
6 "duration": 10, 6 "duration": 10,
7 "uri": "/test/ts-files/zencoder/gogo/00001.ts" 7 "uri": "/test/ts-files/zencoder/gogo/00001.ts"
8 } 8 }
9 ] 9 ],
10 } 10 "endList": true
11 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -23,5 +23,6 @@ ...@@ -23,5 +23,6 @@
23 "uri": "/test/ts-files/zencoder/gogo/00005.ts" 23 "uri": "/test/ts-files/zencoder/gogo/00005.ts"
24 } 24 }
25 ], 25 ],
26 "targetDuration": 10 26 "targetDuration": 10,
27 } 27 "endList": true
28 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -7,5 +7,6 @@ ...@@ -7,5 +7,6 @@
7 "uri": "/test/ts-files/zencoder/gogo/00001.ts" 7 "uri": "/test/ts-files/zencoder/gogo/00001.ts"
8 } 8 }
9 ], 9 ],
10 "targetDuration": 10 10 "targetDuration": 10,
11 } 11 "endList": true
12 }
...\ No newline at end of file ...\ No newline at end of file
......
1 { 1 {
2 "allowCache": true, 2 "allowCache": true,
3 "playlists": [{ 3 "playlists": [
4 "attributes": { 4 {
5 "PROGRAM-ID": 1, 5 "attributes": {
6 "BANDWIDTH": 240000, 6 "PROGRAM-ID": 1,
7 "RESOLUTION": { 7 "BANDWIDTH": 240000,
8 "width": 396, 8 "RESOLUTION": {
9 "height": 224 9 "width": 396,
10 } 10 "height": 224
11 }
12 },
13 "uri": "media.m3u8"
11 }, 14 },
12 "uri": "media.m3u8" 15 {
13 }, { 16 "attributes": {
14 "attributes": { 17 "PROGRAM-ID": 1,
15 "PROGRAM-ID": 1, 18 "BANDWIDTH": 40000
16 "BANDWIDTH": 40000 19 },
20 "uri": "media1.m3u8"
17 }, 21 },
18 "uri": "media1.m3u8" 22 {
19 }, { 23 "attributes": {
20 "attributes": { 24 "PROGRAM-ID": 1,
21 "PROGRAM-ID": 1, 25 "BANDWIDTH": 440000,
22 "BANDWIDTH": 440000, 26 "RESOLUTION": {
23 "RESOLUTION": { 27 "width": 396,
24 "width": 396, 28 "height": 224
25 "height": 224 29 }
26 } 30 },
31 "uri": "media2.m3u8"
27 }, 32 },
28 "uri": "media2.m3u8" 33 {
29 }, { 34 "attributes": {
30 "attributes": { 35 "PROGRAM-ID": 1,
31 "PROGRAM-ID": 1, 36 "BANDWIDTH": 1928000,
32 "BANDWIDTH": 1928000, 37 "RESOLUTION": {
33 "RESOLUTION": { 38 "width": 960,
34 "width": 960, 39 "height": 540
35 "height": 540 40 }
36 } 41 },
37 }, 42 "uri": "media3.m3u8"
38 "uri": "media3.m3u8" 43 }
39 }] 44 ]
40 } 45 }
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "00004.ts" 20 "uri": "00004.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 10 23 "targetDuration": 10,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" 20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 8 23 "targetDuration": 8,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
1 {
2 "allowCache": true,
3 "mediaSequence": 0,
4 "segments": [
5 {
6 "duration": 10,
7 "uri": "00001.ts"
8 },
9 {
10 "duration": 10,
11 "uri": "00002.ts"
12 }
13 ],
14 "targetDuration": 10
15 }
1 #EXTM3U
2 #EXT-X-TARGETDURATION:10
3 #EXTINF:10,
4 00001.ts
5 #EXTINF:10,
6 00002.ts
...@@ -16,5 +16,6 @@ ...@@ -16,5 +16,6 @@
16 "uri": "hls_450k_video.ts" 16 "uri": "hls_450k_video.ts"
17 } 17 }
18 ], 18 ],
19 "targetDuration": 10 19 "targetDuration": 10,
20 } 20 "endList": true
21 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" 20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 8 23 "targetDuration": 8,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" 20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 8 23 "targetDuration": 8,
24 } 24 "endList": true
25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,16 +2,21 @@ ...@@ -2,16 +2,21 @@
2 "allowCache": true, 2 "allowCache": true,
3 "mediaSequence": 0, 3 "mediaSequence": 0,
4 "targetDuration": 10, 4 "targetDuration": 10,
5 "segments": [{ 5 "segments": [
6 "uri": "001.ts" 6 {
7 }, { 7 "uri": "001.ts"
8 "uri": "002.ts", 8 },
9 "duration": 9 9 {
10 }, { 10 "uri": "002.ts",
11 "uri": "003.ts", 11 "duration": 9
12 "duration": 7 12 },
13 }, { 13 {
14 "uri": "004.ts", 14 "uri": "003.ts",
15 "duration": 10 15 "duration": 7
16 }] 16 },
17 {
18 "uri": "004.ts",
19 "duration": 10
20 }
21 ]
17 } 22 }
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" 20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 8 23 "targetDuration": 8,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -140,5 +140,6 @@ ...@@ -140,5 +140,6 @@
140 "uri": "hls_450k_video.ts" 140 "uri": "hls_450k_video.ts"
141 } 141 }
142 ], 142 ],
143 "targetDuration": 10 143 "targetDuration": 10,
144 "endList": true
144 } 145 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -8,5 +8,6 @@ ...@@ -8,5 +8,6 @@
8 "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" 8 "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts"
9 } 9 }
10 ], 10 ],
11 "targetDuration": 8 11 "targetDuration": 8,
12 "endList": true
12 } 13 }
...\ No newline at end of file ...\ No newline at end of file
......
1 { 1 {
2 "allowCache": true, 2 "allowCache": true,
3 "playlists": [ 3 "playlists": [
4 { 4 {
5 "attributes": { 5 "attributes": {
6 "PROGRAM-ID": 1 6 "PROGRAM-ID": 1
7 },
8 "uri": "media.m3u8"
7 }, 9 },
8 "uri": "media.m3u8" 10 {
9 }, 11 "uri": "media1.m3u8"
10 { 12 }
11 "uri": "media1.m3u8"
12 }
13 ] 13 ]
14 } 14 }
......
...@@ -20,5 +20,6 @@ ...@@ -20,5 +20,6 @@
20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" 20 "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts"
21 } 21 }
22 ], 22 ],
23 "targetDuration": 8 23 "targetDuration": 8,
24 "endList": true
24 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -8,5 +8,6 @@ ...@@ -8,5 +8,6 @@
8 "uri": "hls_450k_video.ts" 8 "uri": "hls_450k_video.ts"
9 } 9 }
10 ], 10 ],
11 "targetDuration": 10 11 "targetDuration": 10,
12 "endList": true
12 } 13 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -181,7 +181,7 @@ test('calculates the duration if needed', function() { ...@@ -181,7 +181,7 @@ test('calculates the duration if needed', function() {
181 } 181 }
182 durations.push(duration); 182 durations.push(duration);
183 }; 183 };
184 player.hls('manifest/liveMissingSegmentDuration.m3u8'); 184 player.hls('http://example.com/manifest/liveMissingSegmentDuration.m3u8');
185 videojs.mediaSources[player.currentSrc()].trigger({ 185 videojs.mediaSources[player.currentSrc()].trigger({
186 type: 'sourceopen' 186 type: 'sourceopen'
187 }); 187 });
...@@ -873,4 +873,87 @@ test('has no effect if native HLS is available', function() { ...@@ -873,4 +873,87 @@ test('has no effect if native HLS is available', function() {
873 'no media source was opened'); 873 'no media source was opened');
874 }); 874 });
875 875
876 test('reloads live playlists', function() {
877 var callbacks = [];
878 // capture timeouts
879 window.setTimeout = function(callback, timeout) {
880 callbacks.push({ callback: callback, timeout: timeout });
881 };
882 player.hls('manifest/missingEndlist.m3u8');
883 videojs.mediaSources[player.currentSrc()].trigger({
884 type: 'sourceopen'
885 });
886
887 strictEqual(1, callbacks.length, 'refresh was scheduled');
888 strictEqual(player.hls.media.targetDuration * 1000,
889 callbacks[0].timeout,
890 'waited one target duration');
891 });
892
893 test('does not reload playlists with an endlist tag', function() {
894 var callbacks = [];
895 // capture timeouts
896 window.setTimeout = function(callback, timeout) {
897 callbacks.push({ callback: callback, timeout: timeout });
898 };
899 player.hls('manifest/media.m3u8');
900 videojs.mediaSources[player.currentSrc()].trigger({
901 type: 'sourceopen'
902 });
903
904 strictEqual(0, callbacks.length, 'no refresh was scheduled');
905 });
906
907 test('reloads a live playlist after half a target duration if it has not ' +
908 'changed since the last request', function() {
909 var callbacks = [];
910 // capture timeouts
911 window.setTimeout = function(callback, timeout) {
912 callbacks.push({ callback: callback, timeout: timeout });
913 };
914 player.hls('http://example.com/manifest/missingEndlist.m3u8');
915
916 // an identical manifest has already been parsed
917 player.hls.media = videojs.util.mergeOptions({}, window.expected['missingEndlist']);
918 player.hls.media.uri = 'http://example.com/manifest/missingEndlist.m3u8';
919 player.hls.master = {
920 playlists: [player.hls.media]
921 };
922
923 videojs.mediaSources[player.currentSrc()].trigger({
924 type: 'sourceopen'
925 });
926
927 strictEqual(1, callbacks.length, 'refresh was scheduled');
928 strictEqual(player.hls.media.targetDuration / 2 * 1000,
929 callbacks[0].timeout,
930 'waited half a target duration');
931 });
932
933 test('merges playlist reloads', function() {
934 var
935 realMerge = videojs.m3u8.merge,
936 merges = 0,
937 callback;
938 // capture timeouts and playlist merges
939 window.setTimeout = function(cb) {
940 callback = cb;
941 };
942 videojs.m3u8.merge = function(base, update) {
943 merges++;
944 return update;
945 };
946
947 player.hls('http://example.com/manifest/missingEndlist.m3u8');
948 videojs.mediaSources[player.currentSrc()].trigger({
949 type: 'sourceopen'
950 });
951 player.hls.media.uri = 'http://example.com/manifest/missingEndlist.m3u8';
952
953 callback();
954 strictEqual(1, merges, 'reloaded playlist was merged');
955
956 videojs.m3u8.merge = realMerge;
957 });
958
876 })(window, window.videojs); 959 })(window, window.videojs);
......