Integrate playlist loader
Remove old playlist download code and use the playlist loader. Update test cases.
Showing
4 changed files
with
126 additions
and
189 deletions
... | @@ -47,7 +47,7 @@ | ... | @@ -47,7 +47,7 @@ |
47 | return changed ? result : null; | 47 | return changed ? result : null; |
48 | }, | 48 | }, |
49 | 49 | ||
50 | PlaylistLoader = function(srcUrl) { | 50 | PlaylistLoader = function(srcUrl, withCredentials) { |
51 | var | 51 | var |
52 | loader = this, | 52 | loader = this, |
53 | media, | 53 | media, |
... | @@ -92,6 +92,8 @@ | ... | @@ -92,6 +92,8 @@ |
92 | loader.trigger('mediaupdatetimeout'); | 92 | loader.trigger('mediaupdatetimeout'); |
93 | }, refreshDelay); | 93 | }, refreshDelay); |
94 | } | 94 | } |
95 | |||
96 | loader.trigger('loadedplaylist'); | ||
95 | }; | 97 | }; |
96 | 98 | ||
97 | PlaylistLoader.prototype.init.call(this); | 99 | PlaylistLoader.prototype.init.call(this); |
... | @@ -122,13 +124,6 @@ | ... | @@ -122,13 +124,6 @@ |
122 | if (loader.state === 'HAVE_NOTHING' || loader.state === 'HAVE_MASTER') { | 124 | if (loader.state === 'HAVE_NOTHING' || loader.state === 'HAVE_MASTER') { |
123 | throw new Error('Cannot switch media playlist from ' + loader.state); | 125 | throw new Error('Cannot switch media playlist from ' + loader.state); |
124 | } | 126 | } |
125 | loader.state = 'SWITCHING_MEDIA'; | ||
126 | |||
127 | // abort any outstanding playlist refreshes | ||
128 | if (request) { | ||
129 | request.abort(); | ||
130 | request = null; | ||
131 | } | ||
132 | 127 | ||
133 | // find the playlist object if the target playlist has been | 128 | // find the playlist object if the target playlist has been |
134 | // specified by URI | 129 | // specified by URI |
... | @@ -139,8 +134,24 @@ | ... | @@ -139,8 +134,24 @@ |
139 | playlist = loader.master.playlists[playlist]; | 134 | playlist = loader.master.playlists[playlist]; |
140 | } | 135 | } |
141 | 136 | ||
137 | if (playlist.uri === media.uri) { | ||
138 | // switching to the currently active playlist is a no-op | ||
139 | return; | ||
140 | } | ||
141 | |||
142 | loader.state = 'SWITCHING_MEDIA'; | ||
143 | |||
144 | // abort any outstanding playlist refreshes | ||
145 | if (request) { | ||
146 | request.abort(); | ||
147 | request = null; | ||
148 | } | ||
149 | |||
142 | // request the new playlist | 150 | // request the new playlist |
143 | request = xhr(resolveUrl(loader.master.uri, playlist.uri), function(error) { | 151 | request = xhr({ |
152 | url: resolveUrl(loader.master.uri, playlist.uri), | ||
153 | withCredentials: withCredentials | ||
154 | }, function(error) { | ||
144 | haveMetadata(error, this, playlist.uri); | 155 | haveMetadata(error, this, playlist.uri); |
145 | }); | 156 | }); |
146 | }; | 157 | }; |
... | @@ -153,21 +164,26 @@ | ... | @@ -153,21 +164,26 @@ |
153 | } | 164 | } |
154 | 165 | ||
155 | loader.state = 'HAVE_CURRENT_METADATA'; | 166 | loader.state = 'HAVE_CURRENT_METADATA'; |
156 | request = xhr(resolveUrl(loader.master.uri, loader.media().uri), | 167 | request = xhr({ |
157 | function(error) { | 168 | url: resolveUrl(loader.master.uri, loader.media().uri), |
158 | haveMetadata(error, this, loader.media().uri); | 169 | withCredentials: withCredentials |
159 | }); | 170 | }, function(error) { |
171 | haveMetadata(error, this, loader.media().uri); | ||
172 | }); | ||
160 | }); | 173 | }); |
161 | 174 | ||
162 | // request the specified URL | 175 | // request the specified URL |
163 | xhr(srcUrl, function(error) { | 176 | xhr({ |
177 | url: srcUrl, | ||
178 | withCredentials: withCredentials | ||
179 | }, function(error) { | ||
164 | var parser, i; | 180 | var parser, i; |
165 | 181 | ||
166 | if (error) { | 182 | if (error) { |
167 | loader.error = { | 183 | loader.error = { |
168 | status: this.status, | 184 | status: this.status, |
169 | message: 'HLS playlist request error at URL: ' + srcUrl, | 185 | message: 'HLS playlist request error at URL: ' + srcUrl, |
170 | code: (this.status >= 500) ? 4 : 2 | 186 | code: 2 // MEDIA_ERR_NETWORK |
171 | }; | 187 | }; |
172 | return loader.trigger('error'); | 188 | return loader.trigger('error'); |
173 | } | 189 | } |
... | @@ -189,13 +205,16 @@ | ... | @@ -189,13 +205,16 @@ |
189 | loader.master.playlists[loader.master.playlists[i].uri] = loader.master.playlists[i]; | 205 | loader.master.playlists[loader.master.playlists[i].uri] = loader.master.playlists[i]; |
190 | } | 206 | } |
191 | 207 | ||
192 | request = xhr(resolveUrl(srcUrl, parser.manifest.playlists[0].uri), | 208 | request = xhr({ |
193 | function(error) { | 209 | url: resolveUrl(srcUrl, parser.manifest.playlists[0].uri), |
194 | // pass along the URL specified in the master playlist | 210 | withCredentials: withCredentials |
195 | haveMetadata(error, | 211 | }, function(error) { |
196 | this, | 212 | // pass along the URL specified in the master playlist |
197 | parser.manifest.playlists[0].uri); | 213 | haveMetadata(error, |
198 | }); | 214 | this, |
215 | parser.manifest.playlists[0].uri); | ||
216 | loader.trigger('loadedmetadata'); | ||
217 | }); | ||
199 | return loader.trigger('loadedplaylist'); | 218 | return loader.trigger('loadedplaylist'); |
200 | } | 219 | } |
201 | 220 | ||
... | @@ -208,7 +227,8 @@ | ... | @@ -208,7 +227,8 @@ |
208 | }] | 227 | }] |
209 | }; | 228 | }; |
210 | loader.master.playlists[srcUrl] = loader.master.playlists[0]; | 229 | loader.master.playlists[srcUrl] = loader.master.playlists[0]; |
211 | return haveMetadata(null, this, srcUrl); | 230 | haveMetadata(null, this, srcUrl); |
231 | return loader.trigger('loadedmetadata'); | ||
212 | }); | 232 | }); |
213 | }; | 233 | }; |
214 | PlaylistLoader.prototype = new videojs.hls.Stream(); | 234 | PlaylistLoader.prototype = new videojs.hls.Stream(); | ... | ... |
... | @@ -194,15 +194,20 @@ var | ... | @@ -194,15 +194,20 @@ var |
194 | */ | 194 | */ |
195 | translateMediaIndex = function(mediaIndex, original, update) { | 195 | translateMediaIndex = function(mediaIndex, original, update) { |
196 | var | 196 | var |
197 | i = update.segments.length, | 197 | i, |
198 | originalSegment; | 198 | originalSegment; |
199 | 199 | ||
200 | // no segments have been loaded from the original playlist | 200 | // no segments have been loaded from the original playlist |
201 | if (mediaIndex === 0) { | 201 | if (mediaIndex === 0) { |
202 | return 0; | 202 | return 0; |
203 | } | 203 | } |
204 | if (!(update && update.segments)) { | ||
205 | // let the media index be zero when there are no segments defined | ||
206 | return 0; | ||
207 | } | ||
204 | 208 | ||
205 | // try to sync based on URI | 209 | // try to sync based on URI |
210 | i = update.segments.length; | ||
206 | originalSegment = original.segments[mediaIndex - 1]; | 211 | originalSegment = original.segments[mediaIndex - 1]; |
207 | while (i--) { | 212 | while (i--) { |
208 | if (originalSegment.uri === update.segments[i].uri) { | 213 | if (originalSegment.uri === update.segments[i].uri) { |
... | @@ -292,12 +297,9 @@ var | ... | @@ -292,12 +297,9 @@ var |
292 | player = this, | 297 | player = this, |
293 | srcUrl, | 298 | srcUrl, |
294 | 299 | ||
295 | playlistXhr, | ||
296 | segmentXhr, | 300 | segmentXhr, |
297 | settings, | 301 | settings, |
298 | loadedPlaylist, | ||
299 | fillBuffer, | 302 | fillBuffer, |
300 | updateCurrentPlaylist, | ||
301 | updateDuration; | 303 | updateDuration; |
302 | 304 | ||
303 | // if the video element supports HLS natively, do nothing | 305 | // if the video element supports HLS natively, do nothing |
... | @@ -376,7 +378,8 @@ var | ... | @@ -376,7 +378,8 @@ var |
376 | 378 | ||
377 | player.on('seeking', function() { | 379 | player.on('seeking', function() { |
378 | var currentTime = player.currentTime(); | 380 | var currentTime = player.currentTime(); |
379 | player.hls.mediaIndex = getMediaIndexByTime(player.hls.media, currentTime); | 381 | player.hls.mediaIndex = getMediaIndexByTime(player.hls.playlists.media(), |
382 | currentTime); | ||
380 | 383 | ||
381 | // abort any segments still being decoded | 384 | // abort any segments still being decoded |
382 | player.hls.sourceBuffer.abort(); | 385 | player.hls.sourceBuffer.abort(); |
... | @@ -407,39 +410,6 @@ var | ... | @@ -407,39 +410,6 @@ var |
407 | }; | 410 | }; |
408 | 411 | ||
409 | /** | 412 | /** |
410 | * Determine whether the current media playlist should be changed | ||
411 | * and trigger a switch if necessary. If a sufficiently fresh | ||
412 | * version of the target playlist is available, the switch will take | ||
413 | * effect immediately. Otherwise, the target playlist will be | ||
414 | * refreshed. | ||
415 | */ | ||
416 | updateCurrentPlaylist = function() { | ||
417 | var playlist, mediaSequence; | ||
418 | playlist = player.hls.selectPlaylist(); | ||
419 | mediaSequence = player.hls.mediaIndex + (player.hls.media.mediaSequence || 0); | ||
420 | if (!playlist.segments || | ||
421 | mediaSequence < (playlist.mediaSequence || 0) || | ||
422 | mediaSequence > (playlist.mediaSequence || 0) + playlist.segments.length) { | ||
423 | |||
424 | if (playlistXhr) { | ||
425 | playlistXhr.abort(); | ||
426 | } | ||
427 | playlistXhr = xhr({ | ||
428 | url: resolveUrl(srcUrl, playlist.uri), | ||
429 | withCredentials: settings.withCredentials | ||
430 | }, loadedPlaylist); | ||
431 | } else { | ||
432 | player.hls.mediaIndex = | ||
433 | translateMediaIndex(player.hls.mediaIndex, | ||
434 | player.hls.media, | ||
435 | playlist); | ||
436 | player.hls.media = playlist; | ||
437 | |||
438 | updateDuration(player.hls.media); | ||
439 | } | ||
440 | }; | ||
441 | |||
442 | /** | ||
443 | * Chooses the appropriate media playlist based on the current | 413 | * Chooses the appropriate media playlist based on the current |
444 | * bandwidth estimate and the player size. | 414 | * bandwidth estimate and the player size. |
445 | * @return the highest bitrate playlist less than the currently detected | 415 | * @return the highest bitrate playlist less than the currently detected |
... | @@ -448,7 +418,7 @@ var | ... | @@ -448,7 +418,7 @@ var |
448 | player.hls.selectPlaylist = function () { | 418 | player.hls.selectPlaylist = function () { |
449 | var | 419 | var |
450 | effectiveBitrate, | 420 | effectiveBitrate, |
451 | sortedPlaylists = player.hls.master.playlists.slice(), | 421 | sortedPlaylists = player.hls.playlists.master.playlists.slice(), |
452 | bandwidthPlaylists = [], | 422 | bandwidthPlaylists = [], |
453 | i = sortedPlaylists.length, | 423 | i = sortedPlaylists.length, |
454 | variant, | 424 | variant, |
... | @@ -513,97 +483,6 @@ var | ... | @@ -513,97 +483,6 @@ var |
513 | }; | 483 | }; |
514 | 484 | ||
515 | /** | 485 | /** |
516 | * Callback that is invoked when a media playlist finishes | ||
517 | * downloading. Triggers `loadedmanifest` once for each playlist | ||
518 | * that is downloaded and `loadedmetadata` after at least one | ||
519 | * media playlist has been parsed. | ||
520 | * | ||
521 | * @param error {*} truthy if the request was not successful | ||
522 | * @param url {string} a URL to the M3U8 file to process | ||
523 | */ | ||
524 | loadedPlaylist = function(error, url) { | ||
525 | var i, parser, playlist, playlistUri, refreshDelay; | ||
526 | |||
527 | // clear the current playlist XHR | ||
528 | playlistXhr = null; | ||
529 | |||
530 | if (error) { | ||
531 | player.hls.error = { | ||
532 | status: this.status, | ||
533 | message: 'HLS playlist request error at URL: ' + url, | ||
534 | code: (this.status >= 500) ? 4 : 2 | ||
535 | }; | ||
536 | return player.trigger('error'); | ||
537 | } | ||
538 | |||
539 | parser = new videojs.m3u8.Parser(); | ||
540 | parser.push(this.responseText); | ||
541 | |||
542 | // merge this playlist into the master | ||
543 | i = player.hls.master.playlists.length; | ||
544 | refreshDelay = (parser.manifest.targetDuration || 10) * 1000; | ||
545 | while (i--) { | ||
546 | playlist = player.hls.master.playlists[i]; | ||
547 | playlistUri = resolveUrl(srcUrl, playlist.uri); | ||
548 | if (playlistUri === url) { | ||
549 | // if the playlist is unchanged since the last reload, | ||
550 | // try again after half the target duration | ||
551 | // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4 | ||
552 | if (playlist.segments && | ||
553 | playlist.segments.length === parser.manifest.segments.length) { | ||
554 | refreshDelay /= 2; | ||
555 | } | ||
556 | |||
557 | player.hls.master.playlists[i] = | ||
558 | videojs.util.mergeOptions(playlist, parser.manifest); | ||
559 | |||
560 | if (playlist !== player.hls.media) { | ||
561 | continue; | ||
562 | } | ||
563 | |||
564 | // determine the new mediaIndex if we're updating the | ||
565 | // current media playlist | ||
566 | player.hls.mediaIndex = | ||
567 | translateMediaIndex(player.hls.mediaIndex, | ||
568 | playlist, | ||
569 | parser.manifest); | ||
570 | player.hls.media = parser.manifest; | ||
571 | } | ||
572 | } | ||
573 | |||
574 | // check the playlist for updates if EXT-X-ENDLIST isn't present | ||
575 | if (!parser.manifest.endList) { | ||
576 | window.setTimeout(function() { | ||
577 | if (!playlistXhr && | ||
578 | resolveUrl(srcUrl, player.hls.media.uri) === url) { | ||
579 | playlistXhr = xhr(url, loadedPlaylist); | ||
580 | } | ||
581 | }, refreshDelay); | ||
582 | } | ||
583 | |||
584 | // always start playback with the default rendition | ||
585 | if (!player.hls.media) { | ||
586 | player.hls.media = player.hls.master.playlists[0]; | ||
587 | |||
588 | // update the duration | ||
589 | updateDuration(parser.manifest); | ||
590 | |||
591 | // periodicaly check if the buffer needs to be refilled | ||
592 | player.on('timeupdate', fillBuffer); | ||
593 | |||
594 | player.trigger('loadedmanifest'); | ||
595 | player.trigger('loadedmetadata'); | ||
596 | fillBuffer(); | ||
597 | return; | ||
598 | } | ||
599 | |||
600 | // select a playlist and download its metadata if necessary | ||
601 | updateCurrentPlaylist(); | ||
602 | |||
603 | player.trigger('loadedmanifest'); | ||
604 | }; | ||
605 | |||
606 | /** | ||
607 | * Determines whether there is enough video data currently in the buffer | 486 | * Determines whether there is enough video data currently in the buffer |
608 | * and downloads a new segment if the buffered time is less than the goal. | 487 | * and downloads a new segment if the buffered time is less than the goal. |
609 | * @param offset (optional) {number} the offset into the downloaded segment | 488 | * @param offset (optional) {number} the offset into the downloaded segment |
... | @@ -623,12 +502,12 @@ var | ... | @@ -623,12 +502,12 @@ var |
623 | } | 502 | } |
624 | 503 | ||
625 | // if no segments are available, do nothing | 504 | // if no segments are available, do nothing |
626 | if (!player.hls.media.segments) { | 505 | if (!player.hls.playlists.media().segments) { |
627 | return; | 506 | return; |
628 | } | 507 | } |
629 | 508 | ||
630 | // if the video has finished downloading, stop trying to buffer | 509 | // if the video has finished downloading, stop trying to buffer |
631 | segment = player.hls.media.segments[player.hls.mediaIndex]; | 510 | segment = player.hls.playlists.media().segments[player.hls.mediaIndex]; |
632 | if (!segment) { | 511 | if (!segment) { |
633 | return; | 512 | return; |
634 | } | 513 | } |
... | @@ -644,10 +523,10 @@ var | ... | @@ -644,10 +523,10 @@ var |
644 | } | 523 | } |
645 | 524 | ||
646 | // resolve the segment URL relative to the playlist | 525 | // resolve the segment URL relative to the playlist |
647 | if (player.hls.media.uri === srcUrl) { | 526 | if (player.hls.playlists.media().uri === srcUrl) { |
648 | segmentUri = resolveUrl(srcUrl, segment.uri); | 527 | segmentUri = resolveUrl(srcUrl, segment.uri); |
649 | } else { | 528 | } else { |
650 | segmentUri = resolveUrl(resolveUrl(srcUrl, player.hls.media.uri || ''), | 529 | segmentUri = resolveUrl(resolveUrl(srcUrl, player.hls.playlists.media().uri || ''), |
651 | segment.uri); | 530 | segment.uri); |
652 | } | 531 | } |
653 | 532 | ||
... | @@ -708,49 +587,55 @@ var | ... | @@ -708,49 +587,55 @@ var |
708 | 587 | ||
709 | player.hls.mediaIndex++; | 588 | player.hls.mediaIndex++; |
710 | 589 | ||
711 | if (player.hls.mediaIndex === player.hls.media.segments.length) { | 590 | if (player.hls.mediaIndex === player.hls.playlists.media().segments.length) { |
712 | mediaSource.endOfStream(); | 591 | mediaSource.endOfStream(); |
713 | } | 592 | } |
714 | 593 | ||
715 | // figure out what stream the next segment should be downloaded from | 594 | // figure out what stream the next segment should be downloaded from |
716 | // with the updated bandwidth information | 595 | // with the updated bandwidth information |
717 | updateCurrentPlaylist(); | 596 | player.hls.playlists.media(player.hls.selectPlaylist()); |
718 | }); | 597 | }); |
719 | }; | 598 | }; |
720 | 599 | ||
721 | // load the MediaSource into the player | 600 | // load the MediaSource into the player |
722 | mediaSource.addEventListener('sourceopen', function() { | 601 | mediaSource.addEventListener('sourceopen', function() { |
723 | // construct the video data buffer and set the appropriate MIME type | 602 | // construct the video data buffer and set the appropriate MIME type |
724 | var sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'); | 603 | var |
604 | sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'), | ||
605 | oldMediaPlaylist; | ||
606 | |||
725 | player.hls.sourceBuffer = sourceBuffer; | 607 | player.hls.sourceBuffer = sourceBuffer; |
726 | sourceBuffer.appendBuffer(segmentParser.getFlvHeader()); | 608 | sourceBuffer.appendBuffer(segmentParser.getFlvHeader()); |
727 | 609 | ||
728 | player.hls.mediaIndex = 0; | 610 | player.hls.mediaIndex = 0; |
729 | xhr({ | 611 | player.hls.playlists = |
730 | url: srcUrl, | 612 | new videojs.hls.PlaylistLoader(srcUrl, settings.withCredentials); |
731 | withCredentials: settings.withCredentials | 613 | player.hls.playlists.on('loadedmetadata', function() { |
732 | }, function(error, url) { | 614 | oldMediaPlaylist = player.hls.playlists.media(); |
733 | var uri, parser = new videojs.m3u8.Parser(); | 615 | |
734 | parser.push(this.responseText); | 616 | // periodicaly check if the buffer needs to be refilled |
735 | 617 | fillBuffer(); | |
736 | // master playlists | 618 | player.on('timeupdate', fillBuffer); |
737 | if (parser.manifest.playlists) { | 619 | |
738 | player.hls.master = parser.manifest; | 620 | player.trigger('loadedmetadata'); |
739 | playlistXhr = xhr({ | 621 | }); |
740 | url: resolveUrl(url, parser.manifest.playlists[0].uri), | 622 | player.hls.playlists.on('error', function() { |
741 | withCredentials: settings.withCredentials | 623 | player.hls.error = player.hls.playlists.error; |
742 | }, loadedPlaylist); | 624 | player.trigger('error'); |
743 | return player.trigger('loadedmanifest'); | 625 | }); |
744 | } else { | 626 | player.hls.playlists.on('loadedplaylist', function() { |
745 | // infer a master playlist if a media playlist is loaded directly | 627 | var updatedPlaylist = player.hls.playlists.media(); |
746 | uri = resolveUrl(window.location.href, url); | 628 | |
747 | player.hls.master = { | 629 | if (!updatedPlaylist) { |
748 | playlists: [{ | 630 | // do nothing before an initial media playlist has been activated |
749 | uri: uri | 631 | return; |
750 | }] | ||
751 | }; | ||
752 | loadedPlaylist.call(this, error, uri); | ||
753 | } | 632 | } |
633 | |||
634 | updateDuration(player.hls.playlists.media()); | ||
635 | player.hls.mediaIndex = translateMediaIndex(player.hls.mediaIndex, | ||
636 | oldMediaPlaylist, | ||
637 | updatedPlaylist); | ||
638 | oldMediaPlaylist = updatedPlaylist; | ||
754 | }); | 639 | }); |
755 | }); | 640 | }); |
756 | player.src([{ | 641 | player.src([{ | ... | ... |
... | @@ -65,7 +65,12 @@ | ... | @@ -65,7 +65,12 @@ |
65 | }); | 65 | }); |
66 | 66 | ||
67 | test('jumps to HAVE_METADATA when initialized with a media playlist', function() { | 67 | test('jumps to HAVE_METADATA when initialized with a media playlist', function() { |
68 | var loader = new videojs.hls.PlaylistLoader('media.m3u8'); | 68 | var |
69 | loadedmetadatas = 0, | ||
70 | loader = new videojs.hls.PlaylistLoader('media.m3u8'); | ||
71 | loader.on('loadedmetadata', function() { | ||
72 | loadedmetadatas++; | ||
73 | }); | ||
69 | requests.pop().respond(200, null, | 74 | requests.pop().respond(200, null, |
70 | '#EXTM3U\n' + | 75 | '#EXTM3U\n' + |
71 | '#EXTINF:10,\n' + | 76 | '#EXTINF:10,\n' + |
... | @@ -75,7 +80,8 @@ | ... | @@ -75,7 +80,8 @@ |
75 | ok(loader.media(), 'sets the media playlist'); | 80 | ok(loader.media(), 'sets the media playlist'); |
76 | ok(loader.media().uri, 'sets the media playlist URI'); | 81 | ok(loader.media().uri, 'sets the media playlist URI'); |
77 | strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); | 82 | strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); |
78 | strictEqual(0, requests.length, 'no more requests are made'); | 83 | strictEqual(requests.length, 0, 'no more requests are made'); |
84 | strictEqual(loadedmetadatas, 1, 'fired one loadedmetadata'); | ||
79 | }); | 85 | }); |
80 | 86 | ||
81 | test('jumps to HAVE_METADATA when initialized with a live media playlist', function() { | 87 | test('jumps to HAVE_METADATA when initialized with a live media playlist', function() { |
... | @@ -91,17 +97,22 @@ | ... | @@ -91,17 +97,22 @@ |
91 | 97 | ||
92 | test('moves to HAVE_METADATA after loading a media playlist', function() { | 98 | test('moves to HAVE_METADATA after loading a media playlist', function() { |
93 | var | 99 | var |
94 | loadedPlaylist = false, | 100 | loadedPlaylist = 0, |
101 | loadedMetadata = 0, | ||
95 | loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 102 | loader = new videojs.hls.PlaylistLoader('master.m3u8'); |
96 | loader.on('loadedplaylist', function() { | 103 | loader.on('loadedplaylist', function() { |
97 | loadedPlaylist = true; | 104 | loadedPlaylist++; |
105 | }); | ||
106 | loader.on('loadedmetadata', function() { | ||
107 | loadedMetadata++; | ||
98 | }); | 108 | }); |
99 | requests.pop().respond(200, null, | 109 | requests.pop().respond(200, null, |
100 | '#EXTM3U\n' + | 110 | '#EXTM3U\n' + |
101 | '#EXT-X-STREAM-INF:\n' + | 111 | '#EXT-X-STREAM-INF:\n' + |
102 | 'media.m3u8\n' + | 112 | 'media.m3u8\n' + |
103 | 'alt.m3u8\n'); | 113 | 'alt.m3u8\n'); |
104 | ok(loadedPlaylist, 'loadedplaylist fired'); | 114 | strictEqual(loadedPlaylist, 1, 'fired loadedplaylist once'); |
115 | strictEqual(loadedMetadata, 0, 'did not fire loadedmetadata'); | ||
105 | strictEqual(requests.length, 1, 'requests the media playlist'); | 116 | strictEqual(requests.length, 1, 'requests the media playlist'); |
106 | strictEqual(requests[0].method, 'GET', 'GETs the media playlist'); | 117 | strictEqual(requests[0].method, 'GET', 'GETs the media playlist'); |
107 | strictEqual(requests[0].url, | 118 | strictEqual(requests[0].url, |
... | @@ -114,6 +125,8 @@ | ... | @@ -114,6 +125,8 @@ |
114 | '0.ts\n'); | 125 | '0.ts\n'); |
115 | ok(loader.master, 'sets the master playlist'); | 126 | ok(loader.master, 'sets the master playlist'); |
116 | ok(loader.media(), 'sets the media playlist'); | 127 | ok(loader.media(), 'sets the media playlist'); |
128 | strictEqual(loadedPlaylist, 2, 'fired loadedplaylist twice'); | ||
129 | strictEqual(loadedMetadata, 1, 'fired loadedmetadata once'); | ||
117 | strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); | 130 | strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); |
118 | }); | 131 | }); |
119 | 132 | ||
... | @@ -317,6 +330,25 @@ | ... | @@ -317,6 +330,25 @@ |
317 | strictEqual(loader.state, 'SWITCHING_MEDIA', 'updated the state'); | 330 | strictEqual(loader.state, 'SWITCHING_MEDIA', 'updated the state'); |
318 | }); | 331 | }); |
319 | 332 | ||
333 | test('switching to the active playlist is a no-op', function() { | ||
334 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | ||
335 | requests.pop().respond(200, null, | ||
336 | '#EXTM3U\n' + | ||
337 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + | ||
338 | 'low.m3u8\n' + | ||
339 | '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + | ||
340 | 'high.m3u8\n'); | ||
341 | requests.pop().respond(200, null, | ||
342 | '#EXTM3U\n' + | ||
343 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ||
344 | '#EXTINF:10,\n' + | ||
345 | 'low-0.ts\n' + | ||
346 | '#EXT-X-ENDLIST\n'); | ||
347 | loader.media('low.m3u8'); | ||
348 | |||
349 | strictEqual(requests.length, 0, 'no requests is sent'); | ||
350 | }); | ||
351 | |||
320 | test('throws an error if a media switch is initiated too early', function() { | 352 | test('throws an error if a media switch is initiated too early', function() { |
321 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 353 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); |
322 | 354 | ... | ... |
This diff is collapsed.
Click to expand it.
-
Please register or sign in to post a comment