Compare the old playlist to the update to determine the new media index
When refreshing a playlist, determine the new media index by comparing segment URIs. Consolidate playlist update logic. "Sliding window" live streams are now working.
Showing
4 changed files
with
167 additions
and
218 deletions
... | @@ -35,8 +35,7 @@ | ... | @@ -35,8 +35,7 @@ |
35 | Stream = videojs.hls.Stream, | 35 | Stream = videojs.hls.Stream, |
36 | LineStream, | 36 | LineStream, |
37 | ParseStream, | 37 | ParseStream, |
38 | Parser, | 38 | Parser; |
39 | merge; | ||
40 | 39 | ||
41 | /** | 40 | /** |
42 | * A stream that buffers string input and generates a `data` event for each | 41 | * A stream that buffers string input and generates a `data` event for each |
... | @@ -456,52 +455,9 @@ | ... | @@ -456,52 +455,9 @@ |
456 | this.lineStream.push('\n'); | 455 | this.lineStream.push('\n'); |
457 | }; | 456 | }; |
458 | 457 | ||
459 | /** | ||
460 | * Merges two versions of a media playlist. | ||
461 | * @param base {object} the earlier version of the media playlist. | ||
462 | * @param update {object} the updates to apply to the base playlist. | ||
463 | * @return {object} a new media playlist object that combines the | ||
464 | * information in the two arguments. | ||
465 | */ | ||
466 | merge = function(base, update) { | ||
467 | var | ||
468 | result = mergeOptions({}, base), | ||
469 | uri = update.segments[0].uri, | ||
470 | i = base.segments ? base.segments.length : 0, | ||
471 | byterange, | ||
472 | segment; | ||
473 | |||
474 | result = mergeOptions(result, update); | ||
475 | |||
476 | // align and apply the updated segments | ||
477 | while (i--) { | ||
478 | segment = base.segments[i]; | ||
479 | if (uri === segment.uri) { | ||
480 | // if there is no byterange information, match by URI | ||
481 | if (!segment.byterange) { | ||
482 | result.segments = base.segments.slice(0, i).concat(update.segments); | ||
483 | break; | ||
484 | } | ||
485 | // if a byterange is specified, make sure the segments match exactly | ||
486 | byterange = update.segments[0].byterange || {}; | ||
487 | if (segment.byterange.offset === byterange.offset && | ||
488 | segment.byterange.length === byterange.length) { | ||
489 | result.segments = base.segments.slice(0, i).concat(update.segments); | ||
490 | break; | ||
491 | } | ||
492 | } | ||
493 | } | ||
494 | // concatenate the two arrays if there was no overlap | ||
495 | if (i < 0) { | ||
496 | result.segments = (base.segments || []).concat(update.segments); | ||
497 | } | ||
498 | return result; | ||
499 | }; | ||
500 | |||
501 | window.videojs.m3u8 = { | 458 | window.videojs.m3u8 = { |
502 | LineStream: LineStream, | 459 | LineStream: LineStream, |
503 | ParseStream: ParseStream, | 460 | ParseStream: ParseStream, |
504 | Parser: Parser, | 461 | Parser: Parser |
505 | merge: merge | ||
506 | }; | 462 | }; |
507 | })(window.videojs, window.parseInt, window.isFinite, window.videojs.util.mergeOptions); | 463 | })(window.videojs, window.parseInt, window.isFinite, window.videojs.util.mergeOptions); | ... | ... |
... | @@ -132,6 +132,12 @@ var | ... | @@ -132,6 +132,12 @@ var |
132 | duration = 0, | 132 | duration = 0, |
133 | i = playlist.segments.length, | 133 | i = playlist.segments.length, |
134 | segment; | 134 | segment; |
135 | |||
136 | // if present, use the duration specified in the playlist | ||
137 | if (playlist.totalDuration) { | ||
138 | return playlist.totalDuration; | ||
139 | } | ||
140 | |||
135 | // duration should be Infinity for live playlists | 141 | // duration should be Infinity for live playlists |
136 | if (!playlist.endList) { | 142 | if (!playlist.endList) { |
137 | return window.Infinity; | 143 | return window.Infinity; |
... | @@ -205,7 +211,8 @@ var | ... | @@ -205,7 +211,8 @@ var |
205 | 211 | ||
206 | segmentXhr, | 212 | segmentXhr, |
207 | downloadPlaylist, | 213 | downloadPlaylist, |
208 | fillBuffer; | 214 | fillBuffer, |
215 | updateCurrentPlaylist; | ||
209 | 216 | ||
210 | // if the video element supports HLS natively, do nothing | 217 | // if the video element supports HLS natively, do nothing |
211 | if (videojs.hls.supportsNativeHls) { | 218 | if (videojs.hls.supportsNativeHls) { |
... | @@ -293,6 +300,28 @@ var | ... | @@ -293,6 +300,28 @@ var |
293 | fillBuffer(currentTime * 1000); | 300 | fillBuffer(currentTime * 1000); |
294 | }); | 301 | }); |
295 | 302 | ||
303 | /** | ||
304 | * Determine whether the current media playlist should be changed | ||
305 | * and trigger a switch if necessary. If a sufficiently fresh | ||
306 | * version of the target playlist is available, the switch will take | ||
307 | * effect immediately. Otherwise, the target playlist will be | ||
308 | * refreshed. | ||
309 | */ | ||
310 | updateCurrentPlaylist = function() { | ||
311 | var playlist, mediaSequence; | ||
312 | playlist = player.hls.selectPlaylist(); | ||
313 | mediaSequence = player.hls.mediaIndex + (player.hls.media.mediaSequence || 0); | ||
314 | if (!playlist.segments || | ||
315 | mediaSequence < (playlist.mediaSequence || 0) || | ||
316 | mediaSequence > (playlist.mediaSequence || 0) + playlist.segments.length) { | ||
317 | downloadPlaylist(resolveUrl(srcUrl, playlist.uri)); | ||
318 | } else { | ||
319 | player.hls.media = playlist; | ||
320 | |||
321 | // update the duration | ||
322 | player.duration(totalDuration(player.hls.media)); | ||
323 | } | ||
324 | }; | ||
296 | 325 | ||
297 | /** | 326 | /** |
298 | * Chooses the appropriate media playlist based on the current | 327 | * Chooses the appropriate media playlist based on the current |
... | @@ -382,7 +411,27 @@ var | ... | @@ -382,7 +411,27 @@ var |
382 | var xhr = new window.XMLHttpRequest(); | 411 | var xhr = new window.XMLHttpRequest(); |
383 | xhr.open('GET', url); | 412 | xhr.open('GET', url); |
384 | xhr.onreadystatechange = function() { | 413 | xhr.onreadystatechange = function() { |
385 | var i, parser, playlist, playlistUri, refreshDelay; | 414 | var i, parser, playlist, playlistUri, refreshDelay, |
415 | updateMediaIndex = function(original, update) { | ||
416 | var | ||
417 | i = update.segments.length, | ||
418 | updatedIndex = 0, | ||
419 | originalSegment; | ||
420 | |||
421 | // no segments have been loaded from the original playlist | ||
422 | if (player.hls.mediaIndex === 0) { | ||
423 | return; | ||
424 | } | ||
425 | |||
426 | originalSegment = original.segments[player.hls.mediaIndex - 1]; | ||
427 | while (i--) { | ||
428 | if (originalSegment.uri === update.segments[i].uri) { | ||
429 | updatedIndex = i + 1; | ||
430 | break; | ||
431 | } | ||
432 | } | ||
433 | player.hls.mediaIndex = updatedIndex; | ||
434 | }; | ||
386 | 435 | ||
387 | // wait until the request completes | 436 | // wait until the request completes |
388 | if (xhr.readyState !== 4) { | 437 | if (xhr.readyState !== 4) { |
... | @@ -430,7 +479,15 @@ var | ... | @@ -430,7 +479,15 @@ var |
430 | } | 479 | } |
431 | 480 | ||
432 | player.hls.master.playlists[i] = | 481 | player.hls.master.playlists[i] = |
433 | videojs.m3u8.merge(playlist, parser.manifest); | 482 | videojs.util.mergeOptions(playlist, parser.manifest); |
483 | |||
484 | if (playlist !== player.hls.media) { | ||
485 | continue; | ||
486 | } | ||
487 | |||
488 | // determine the new mediaIndex if we're updating the | ||
489 | // current media playlist | ||
490 | updateMediaIndex(playlist, parser.manifest); | ||
434 | } | 491 | } |
435 | } | 492 | } |
436 | } else { | 493 | } else { |
... | @@ -453,11 +510,7 @@ var | ... | @@ -453,11 +510,7 @@ var |
453 | player.hls.media = player.hls.master.playlists[0]; | 510 | player.hls.media = player.hls.master.playlists[0]; |
454 | 511 | ||
455 | // update the duration | 512 | // update the duration |
456 | if (parser.manifest.totalDuration) { | 513 | player.duration(totalDuration(parser.manifest)); |
457 | player.duration(parser.manifest.totalDuration); | ||
458 | } else { | ||
459 | player.duration(totalDuration(parser.manifest)); | ||
460 | } | ||
461 | 514 | ||
462 | // periodicaly check if the buffer needs to be refilled | 515 | // periodicaly check if the buffer needs to be refilled |
463 | player.on('timeupdate', fillBuffer); | 516 | player.on('timeupdate', fillBuffer); |
... | @@ -469,19 +522,7 @@ var | ... | @@ -469,19 +522,7 @@ var |
469 | } | 522 | } |
470 | 523 | ||
471 | // select a playlist and download its metadata if necessary | 524 | // select a playlist and download its metadata if necessary |
472 | playlist = player.hls.selectPlaylist(); | 525 | updateCurrentPlaylist(); |
473 | if (!playlist.segments) { | ||
474 | downloadPlaylist(resolveUrl(srcUrl, playlist.uri)); | ||
475 | } else { | ||
476 | player.hls.media = playlist; | ||
477 | |||
478 | // update the duration | ||
479 | if (player.hls.media.totalDuration) { | ||
480 | player.duration(player.hls.media.totalDuration); | ||
481 | } else { | ||
482 | player.duration(totalDuration(player.hls.media)); | ||
483 | } | ||
484 | } | ||
485 | 526 | ||
486 | player.trigger('loadedmanifest'); | 527 | player.trigger('loadedmanifest'); |
487 | }; | 528 | }; |
... | @@ -535,8 +576,6 @@ var | ... | @@ -535,8 +576,6 @@ var |
535 | segmentXhr.open('GET', segmentUri); | 576 | segmentXhr.open('GET', segmentUri); |
536 | segmentXhr.responseType = 'arraybuffer'; | 577 | segmentXhr.responseType = 'arraybuffer'; |
537 | segmentXhr.onreadystatechange = function() { | 578 | segmentXhr.onreadystatechange = function() { |
538 | var playlist; | ||
539 | |||
540 | // wait until the request completes | 579 | // wait until the request completes |
541 | if (this.readyState !== 4) { | 580 | if (this.readyState !== 4) { |
542 | return; | 581 | return; |
... | @@ -591,12 +630,7 @@ var | ... | @@ -591,12 +630,7 @@ var |
591 | 630 | ||
592 | // figure out what stream the next segment should be downloaded from | 631 | // figure out what stream the next segment should be downloaded from |
593 | // with the updated bandwidth information | 632 | // with the updated bandwidth information |
594 | playlist = player.hls.selectPlaylist(); | 633 | updateCurrentPlaylist(); |
595 | if (!playlist.segments) { | ||
596 | downloadPlaylist(resolveUrl(srcUrl, playlist.uri)); | ||
597 | } else { | ||
598 | player.hls.media = playlist; | ||
599 | } | ||
600 | }; | 634 | }; |
601 | startTime = +new Date(); | 635 | startTime = +new Date(); |
602 | segmentXhr.send(null); | 636 | segmentXhr.send(null); | ... | ... |
... | @@ -513,131 +513,6 @@ | ... | @@ -513,131 +513,6 @@ |
513 | notStrictEqual(new Parser(), undefined, 'parser is defined'); | 513 | notStrictEqual(new Parser(), undefined, 'parser is defined'); |
514 | }); | 514 | }); |
515 | 515 | ||
516 | test('merges a manifest that strictly adds to an earlier one', function() { | ||
517 | var key, base, manifest, mid; | ||
518 | for (key in window.manifests) { | ||
519 | if (window.expected[key]) { | ||
520 | manifest = window.manifests[key]; | ||
521 | // parse the first half of the manifest | ||
522 | mid = manifest.length / 2; | ||
523 | parser = new Parser(); | ||
524 | parser.push(manifest.substring(0, mid)); | ||
525 | base = parser.manifest; | ||
526 | if (!base.segments) { | ||
527 | // only test merges for media playlists | ||
528 | continue; | ||
529 | } | ||
530 | |||
531 | // attach the partial manifest to a new parser | ||
532 | parser = new Parser(); | ||
533 | parser.push(manifest); | ||
534 | |||
535 | // merge the manifests together | ||
536 | deepEqual(m3u8.merge(base, parser.manifest), | ||
537 | window.expected[key], | ||
538 | key + '.m3u8 was parsed correctly'); | ||
539 | } | ||
540 | } | ||
541 | }); | ||
542 | |||
543 | test('merges overlapping segments without media sequences', function() { | ||
544 | var base; | ||
545 | parser = new Parser(); | ||
546 | parser.push('#EXTM3U\n'); | ||
547 | parser.push('#EXTINF:10,\n'); | ||
548 | parser.push('0.ts\n'); | ||
549 | parser.push('#EXTINF:10,\n'); | ||
550 | parser.push('1.ts\n'); | ||
551 | base = parser.manifest; | ||
552 | |||
553 | parser = new Parser(); | ||
554 | parser.push('#EXTM3U\n'); | ||
555 | parser.push('#EXTINF:10,\n'); | ||
556 | parser.push('1.ts\n'); | ||
557 | parser.push('#EXTINF:10,\n'); | ||
558 | parser.push('2.ts\n'); | ||
559 | |||
560 | deepEqual({ | ||
561 | allowCache: true, | ||
562 | mediaSequence: 0, | ||
563 | segments: [{ duration: 10, uri: '0.ts'}, | ||
564 | { duration: 10, uri: '1.ts' }, | ||
565 | { duration: 10, uri: '2.ts' }] | ||
566 | }, m3u8.merge(base, parser.manifest), 'merges segment additions'); | ||
567 | }); | ||
568 | |||
569 | test('appends non-overlapping segments without media sequences', function() { | ||
570 | var base; | ||
571 | parser = new Parser(); | ||
572 | parser.push('#EXTM3U\n'); | ||
573 | parser.push('#EXTINF:10,\n'); | ||
574 | parser.push('0.ts\n'); | ||
575 | base = parser.manifest; | ||
576 | |||
577 | parser = new Parser(); | ||
578 | parser.push('#EXTM3U\n'); | ||
579 | parser.push('#EXTINF:10,\n'); | ||
580 | parser.push('1.ts\n'); | ||
581 | |||
582 | deepEqual({ | ||
583 | allowCache: true, | ||
584 | mediaSequence: 0, | ||
585 | segments: [{ duration: 10, uri: '0.ts'}, | ||
586 | { duration: 10, uri: '1.ts' }] | ||
587 | }, m3u8.merge(base, parser.manifest), 'appends segment additions'); | ||
588 | }); | ||
589 | |||
590 | test('replaces segments when merging with a higher media sequence number', function() { | ||
591 | var base; | ||
592 | parser = new Parser(); | ||
593 | parser.push('#EXTM3U\n'); | ||
594 | parser.push('#EXT-X-MEDIA-SEQUENCE:3\n'); | ||
595 | parser.push('#EXTINF:10,\n'); | ||
596 | parser.push('3.ts\n'); | ||
597 | base = parser.manifest; | ||
598 | |||
599 | parser = new Parser(); | ||
600 | parser.push('#EXTM3U\n'); | ||
601 | parser.push('#EXT-X-MEDIA-SEQUENCE:7\n'); | ||
602 | parser.push('#EXTINF:10,\n'); | ||
603 | parser.push('7.ts\n'); | ||
604 | base = parser.manifest; | ||
605 | |||
606 | deepEqual({ | ||
607 | allowCache: true, | ||
608 | mediaSequence: 7, | ||
609 | segments: [{ duration: 10, uri: '7.ts' }] | ||
610 | }, m3u8.merge(base, parser.manifest), 'replaces segments'); | ||
611 | }); | ||
612 | |||
613 | test('replaces overlapping segments when media sequence is present', function() { | ||
614 | var base; | ||
615 | parser = new Parser(); | ||
616 | parser.push('#EXTM3U\n'); | ||
617 | parser.push('#EXT-X-MEDIA-SEQUENCE:3\n'); | ||
618 | parser.push('#EXTINF:10,\n'); | ||
619 | parser.push('3.ts\n'); | ||
620 | parser.push('#EXTINF:10,\n'); | ||
621 | parser.push('4.ts\n'); | ||
622 | base = parser.manifest; | ||
623 | |||
624 | parser = new Parser(); | ||
625 | parser.push('#EXTM3U\n'); | ||
626 | parser.push('#EXT-X-MEDIA-SEQUENCE:4\n'); | ||
627 | parser.push('#EXTINF:10,\n'); | ||
628 | parser.push('4.ts\n'); | ||
629 | parser.push('#EXTINF:10,\n'); | ||
630 | parser.push('5.ts\n'); | ||
631 | base = parser.manifest; | ||
632 | |||
633 | deepEqual({ | ||
634 | allowCache: true, | ||
635 | mediaSequence: 4, | ||
636 | segments: [{ duration: 10, uri: '4.ts' }, | ||
637 | { duration: 10, uri: '5.ts' }] | ||
638 | }, m3u8.merge(base, parser.manifest), 'replaces segments'); | ||
639 | }); | ||
640 | |||
641 | module('m3u8s'); | 516 | module('m3u8s'); |
642 | 517 | ||
643 | test('parses static manifests as expected', function() { | 518 | test('parses static manifests as expected', function() { | ... | ... |
... | @@ -170,7 +170,7 @@ test('sets the duration if one is available on the playlist', function() { | ... | @@ -170,7 +170,7 @@ test('sets the duration if one is available on the playlist', function() { |
170 | type: 'sourceopen' | 170 | type: 'sourceopen' |
171 | }); | 171 | }); |
172 | 172 | ||
173 | strictEqual(1, calls, 'duration is set'); | 173 | strictEqual(calls, 2, 'duration is set'); |
174 | }); | 174 | }); |
175 | 175 | ||
176 | test('calculates the duration if needed', function() { | 176 | test('calculates the duration if needed', function() { |
... | @@ -186,7 +186,7 @@ test('calculates the duration if needed', function() { | ... | @@ -186,7 +186,7 @@ test('calculates the duration if needed', function() { |
186 | type: 'sourceopen' | 186 | type: 'sourceopen' |
187 | }); | 187 | }); |
188 | 188 | ||
189 | strictEqual(durations.length, 1, 'duration is set'); | 189 | strictEqual(durations.length, 2, 'duration is set'); |
190 | strictEqual(durations[0], | 190 | strictEqual(durations[0], |
191 | player.hls.media.segments.length * 10, | 191 | player.hls.media.segments.length * 10, |
192 | 'duration is calculated'); | 192 | 'duration is calculated'); |
... | @@ -402,15 +402,12 @@ test('downloads additional playlists if required', function() { | ... | @@ -402,15 +402,12 @@ test('downloads additional playlists if required', function() { |
402 | called = true; | 402 | called = true; |
403 | return playlist; | 403 | return playlist; |
404 | } | 404 | } |
405 | playlist.segments = []; | 405 | playlist.segments = [1, 1, 1]; |
406 | return playlist; | 406 | return playlist; |
407 | }; | 407 | }; |
408 | xhrUrls = []; | 408 | xhrUrls = []; |
409 | 409 | ||
410 | // the playlist selection is revisited after a new segment is downloaded | 410 | // the playlist selection is revisited after a new segment is downloaded |
411 | player.currentTime = function() { | ||
412 | return 1; | ||
413 | }; | ||
414 | player.trigger('timeupdate'); | 411 | player.trigger('timeupdate'); |
415 | 412 | ||
416 | strictEqual(2, xhrUrls.length, 'requests were made'); | 413 | strictEqual(2, xhrUrls.length, 'requests were made'); |
... | @@ -869,7 +866,7 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { | ... | @@ -869,7 +866,7 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { |
869 | 866 | ||
870 | test('has no effect if native HLS is available', function() { | 867 | test('has no effect if native HLS is available', function() { |
871 | videojs.hls.supportsNativeHls = true; | 868 | videojs.hls.supportsNativeHls = true; |
872 | player.hls('manifest/master.m3u8'); | 869 | player.hls('http://example.com/manifest/master.m3u8'); |
873 | 870 | ||
874 | ok(!(player.currentSrc() in videojs.mediaSources), | 871 | ok(!(player.currentSrc() in videojs.mediaSources), |
875 | 'no media source was opened'); | 872 | 'no media source was opened'); |
... | @@ -881,7 +878,7 @@ test('reloads live playlists', function() { | ... | @@ -881,7 +878,7 @@ test('reloads live playlists', function() { |
881 | window.setTimeout = function(callback, timeout) { | 878 | window.setTimeout = function(callback, timeout) { |
882 | callbacks.push({ callback: callback, timeout: timeout }); | 879 | callbacks.push({ callback: callback, timeout: timeout }); |
883 | }; | 880 | }; |
884 | player.hls('manifest/missingEndlist.m3u8'); | 881 | player.hls('http://example.com/manifest/missingEndlist.m3u8'); |
885 | videojs.mediaSources[player.currentSrc()].trigger({ | 882 | videojs.mediaSources[player.currentSrc()].trigger({ |
886 | type: 'sourceopen' | 883 | type: 'sourceopen' |
887 | }); | 884 | }); |
... | @@ -893,7 +890,7 @@ test('reloads live playlists', function() { | ... | @@ -893,7 +890,7 @@ test('reloads live playlists', function() { |
893 | }); | 890 | }); |
894 | 891 | ||
895 | test('duration is Infinity for live playlists', function() { | 892 | test('duration is Infinity for live playlists', function() { |
896 | player.hls('manifest/missingEndlist.m3u8'); | 893 | player.hls('http://example.com/manifest/missingEndlist.m3u8'); |
897 | videojs.mediaSources[player.currentSrc()].trigger({ | 894 | videojs.mediaSources[player.currentSrc()].trigger({ |
898 | type: 'sourceopen' | 895 | type: 'sourceopen' |
899 | }); | 896 | }); |
... | @@ -943,27 +940,114 @@ test('reloads a live playlist after half a target duration if it has not ' + | ... | @@ -943,27 +940,114 @@ test('reloads a live playlist after half a target duration if it has not ' + |
943 | 940 | ||
944 | test('merges playlist reloads', function() { | 941 | test('merges playlist reloads', function() { |
945 | var | 942 | var |
946 | realMerge = videojs.m3u8.merge, | 943 | oldPlaylist, |
947 | merges = 0, | ||
948 | callback; | 944 | callback; |
949 | // capture timeouts and playlist merges | 945 | // capture timeouts |
950 | window.setTimeout = function(cb) { | 946 | window.setTimeout = function(cb) { |
951 | callback = cb; | 947 | callback = cb; |
952 | }; | 948 | }; |
953 | videojs.m3u8.merge = function(base, update) { | ||
954 | merges++; | ||
955 | return update; | ||
956 | }; | ||
957 | 949 | ||
958 | player.hls('http://example.com/manifest/missingEndlist.m3u8'); | 950 | player.hls('http://example.com/manifest/missingEndlist.m3u8'); |
959 | videojs.mediaSources[player.currentSrc()].trigger({ | 951 | videojs.mediaSources[player.currentSrc()].trigger({ |
960 | type: 'sourceopen' | 952 | type: 'sourceopen' |
961 | }); | 953 | }); |
954 | oldPlaylist = player.hls.media; | ||
955 | |||
956 | callback(); | ||
957 | ok(oldPlaylist !== player.hls.media, 'player.hls.media was updated'); | ||
958 | }); | ||
959 | |||
960 | test('updates the media index when a playlist reloads', function() { | ||
961 | var callback; | ||
962 | window.setTimeout = function(cb) { | ||
963 | callback = cb; | ||
964 | }; | ||
965 | // the initial playlist | ||
966 | window.manifests['live-updating'] = | ||
967 | '#EXTM3U\n' + | ||
968 | '#EXTINF:10,\n' + | ||
969 | '0.ts\n' + | ||
970 | '#EXTINF:10,\n' + | ||
971 | '1.ts\n' + | ||
972 | '#EXTINF:10,\n' + | ||
973 | '2.ts\n'; | ||
974 | |||
975 | player.hls('http://example.com/live-updating.m3u8'); | ||
976 | videojs.mediaSources[player.currentSrc()].trigger({ | ||
977 | type: 'sourceopen' | ||
978 | }); | ||
962 | 979 | ||
980 | // play the stream until 2.ts is playing | ||
981 | player.hls.mediaIndex = 3; | ||
982 | |||
983 | // reload the updated playlist | ||
984 | window.manifests['live-updating'] = | ||
985 | '#EXTM3U\n' + | ||
986 | '#EXTINF:10,\n' + | ||
987 | '1.ts\n' + | ||
988 | '#EXTINF:10,\n' + | ||
989 | '2.ts\n' + | ||
990 | '#EXTINF:10,\n' + | ||
991 | '3.ts\n'; | ||
963 | callback(); | 992 | callback(); |
964 | strictEqual(1, merges, 'reloaded playlist was merged'); | ||
965 | 993 | ||
966 | videojs.m3u8.merge = realMerge; | 994 | strictEqual(2, player.hls.mediaIndex, 'mediaIndex is updated after the reload'); |
995 | }); | ||
996 | |||
997 | test('mediaIndex is zero before the first segment loads', function() { | ||
998 | window.manifests['first-seg-load'] = | ||
999 | '#EXTM3U\n' + | ||
1000 | '#EXTINF:10,\n' + | ||
1001 | '0.ts\n'; | ||
1002 | window.XMLHttpRequest = function() { | ||
1003 | this.open = function() {}; | ||
1004 | this.send = function() {}; | ||
1005 | }; | ||
1006 | player.hls('http://example.com/first-seg-load.m3u8'); | ||
1007 | videojs.mediaSources[player.currentSrc()].trigger({ | ||
1008 | type: 'sourceopen' | ||
1009 | }); | ||
1010 | |||
1011 | strictEqual(player.hls.mediaIndex, 0, 'mediaIndex is zero'); | ||
1012 | }); | ||
1013 | |||
1014 | test('reloads out-of-date live playlists when switching variants', function() { | ||
1015 | var callback; | ||
1016 | // capture timeouts | ||
1017 | window.setTimeout = function(cb) { | ||
1018 | callback = cb; | ||
1019 | }; | ||
1020 | |||
1021 | player.hls('http://example.com/master.m3u8'); | ||
1022 | videojs.mediaSources[player.currentSrc()].trigger({ | ||
1023 | type: 'sourceopen' | ||
1024 | }); | ||
1025 | |||
1026 | // playing segment 15 on playlist 0 | ||
1027 | player.hls.master = { | ||
1028 | playlists: [{ | ||
1029 | mediaSequence: 15, | ||
1030 | segments: [{}, {}] | ||
1031 | }, { | ||
1032 | uri: 'http://example.com/variant-update.m3u8', | ||
1033 | mediaSequence: 0, | ||
1034 | segments: [{}, {}] | ||
1035 | }] | ||
1036 | }; | ||
1037 | player.hls.media = player.hls.master.playlists[0]; | ||
1038 | player.mediaIndex = 0; | ||
1039 | window.manifests['variant-update'] = '#EXTM3U\n' + | ||
1040 | '#EXT-X-MEDIA-SEQUENCE:16\n' + | ||
1041 | '#EXTINF:10,\n' + | ||
1042 | '16.ts\n'; | ||
1043 | |||
1044 | // switch playlists | ||
1045 | player.hls.selectPlaylist = function() { | ||
1046 | return player.hls.master.playlists[1]; | ||
1047 | }; | ||
1048 | player.trigger('timeupdate'); | ||
1049 | |||
1050 | ok(callback, 'reload is scheduled'); | ||
967 | }); | 1051 | }); |
968 | 1052 | ||
969 | })(window, window.videojs); | 1053 | })(window, window.videojs); | ... | ... |
-
Please register or sign in to post a comment