6e4ebe73 by David LaPalomento

Pull out XHR helper

Create a helper method for creating XMLHttpRequests and migrate downloadPlaylist to using it.
1 parent c30a0cfa
...@@ -94,6 +94,35 @@ var ...@@ -94,6 +94,35 @@ var
94 } 94 }
95 }, 95 },
96 96
97 xhr = function(url, callback) {
98 var
99 options = {
100 method: 'GET'
101 },
102 request;
103 if (typeof url === 'object') {
104 options = videojs.util.mergeOptions(options, url);
105 url = options.url;
106 }
107 request = new window.XMLHttpRequest();
108 request.open(options.method, url);
109 request.onreadystatechange = function() {
110 // wait until the request completes
111 if (this.readyState !== 4) {
112 return;
113 }
114
115 // request error
116 if (this.status >= 400 || this.status === 0) {
117 return callback.call(this, true, url);
118 }
119
120 return callback.call(this, false, url);
121 };
122 request.send(null);
123 return request;
124 },
125
97 /** 126 /**
98 * TODO - Document this great feature. 127 * TODO - Document this great feature.
99 * 128 *
...@@ -350,7 +379,7 @@ var ...@@ -350,7 +379,7 @@ var
350 if (!playlist.segments || 379 if (!playlist.segments ||
351 mediaSequence < (playlist.mediaSequence || 0) || 380 mediaSequence < (playlist.mediaSequence || 0) ||
352 mediaSequence > (playlist.mediaSequence || 0) + playlist.segments.length) { 381 mediaSequence > (playlist.mediaSequence || 0) + playlist.segments.length) {
353 downloadPlaylist(resolveUrl(srcUrl, playlist.uri)); 382 xhr(resolveUrl(srcUrl, playlist.uri), downloadPlaylist);
354 } else { 383 } else {
355 player.hls.mediaIndex = 384 player.hls.mediaIndex =
356 findCorrespondingMediaIndex(player.hls.mediaIndex, 385 findCorrespondingMediaIndex(player.hls.mediaIndex,
...@@ -437,120 +466,112 @@ var ...@@ -437,120 +466,112 @@ var
437 }; 466 };
438 467
439 /** 468 /**
440 * Download an M3U8 and update the current manifest object. If the provided 469 * Callback that is invoked when a playlist finishes
441 * URL is a master playlist, the default variant will be downloaded and 470 * downloading. If the response is a master playlist, the default
442 * parsed as well. Triggers `loadedmanifest` once for each playlist that is 471 * variant will be downloaded and parsed as well. Triggers
443 * downloaded and `loadedmetadata` after at least one media playlist has 472 * `loadedmanifest` once for each playlist that is downloaded and
444 * been parsed. Whether multiple playlists were downloaded or not, when 473 * `loadedmetadata` after at least one media playlist has been
445 * `loadedmetadata` fires a parsed or inferred master playlist object will 474 * parsed. Whether multiple playlists were downloaded or not, when
446 * be available as `player.hls.master`. 475 * `loadedmetadata` fires a parsed or inferred master playlist
476 * object will be available as `player.hls.master`.
447 * 477 *
478 * @param error {*} truthy if the request was not successful
448 * @param url {string} a URL to the M3U8 file to process 479 * @param url {string} a URL to the M3U8 file to process
449 */ 480 */
450 downloadPlaylist = function(url) { 481 downloadPlaylist = function(error, url) {
451 var xhr = new window.XMLHttpRequest(); 482 var i, parser, playlist, playlistUri, refreshDelay;
452 xhr.open('GET', url); 483
453 xhr.onreadystatechange = function() { 484 if (error) {
454 var i, parser, playlist, playlistUri, refreshDelay; 485 player.hls.error = {
486 status: this.status,
487 message: 'HLS playlist request error at URL: ' + url,
488 code: (this.status >= 500) ? 4 : 2
489 };
490 player.trigger('error');
491 return;
492 }
455 493
456 // wait until the request completes 494 // readystate DONE
457 if (xhr.readyState !== 4) { 495 parser = new videojs.m3u8.Parser();
458 return; 496 parser.push(this.responseText);
459 }
460 497
461 if (xhr.status >= 400 || this.status === 0) { 498 // master playlists
462 player.hls.error = { 499 if (parser.manifest.playlists) {
463 status: xhr.status, 500 player.hls.master = parser.manifest;
464 message: 'HLS playlist request error at URL: ' + url, 501 xhr(resolveUrl(url, parser.manifest.playlists[0].uri), downloadPlaylist);
465 code: (xhr.status >= 500) ? 4 : 2 502 player.trigger('loadedmanifest');
466 }; 503 return;
467 player.trigger('error'); 504 }
468 return;
469 }
470 505
471 // readystate DONE 506 // media playlists
472 parser = new videojs.m3u8.Parser(); 507 refreshDelay = (parser.manifest.targetDuration || 10) * 1000;
473 parser.push(xhr.responseText); 508 if (player.hls.master) {
509 // merge this playlist into the master
510 i = player.hls.master.playlists.length;
511
512 while (i--) {
513 playlist = player.hls.master.playlists[i];
514 playlistUri = resolveUrl(srcUrl, playlist.uri);
515 if (playlistUri === url) {
516 // if the playlist is unchanged since the last reload,
517 // try again after half the target duration
518 // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4
519 if (playlist.segments &&
520 playlist.segments.length === parser.manifest.segments.length) {
521 refreshDelay /= 2;
522 }
474 523
475 // master playlists 524 player.hls.master.playlists[i] =
476 if (parser.manifest.playlists) { 525 videojs.util.mergeOptions(playlist, parser.manifest);
477 player.hls.master = parser.manifest;
478 downloadPlaylist(resolveUrl(url, parser.manifest.playlists[0].uri));
479 player.trigger('loadedmanifest');
480 return;
481 }
482 526
483 // media playlists 527 if (playlist !== player.hls.media) {
484 refreshDelay = (parser.manifest.targetDuration || 10) * 1000; 528 continue;
485 if (player.hls.master) {
486 // merge this playlist into the master
487 i = player.hls.master.playlists.length;
488
489 while (i--) {
490 playlist = player.hls.master.playlists[i];
491 playlistUri = resolveUrl(srcUrl, playlist.uri);
492 if (playlistUri === url) {
493 // if the playlist is unchanged since the last reload,
494 // try again after half the target duration
495 // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4
496 if (playlist.segments &&
497 playlist.segments.length === parser.manifest.segments.length) {
498 refreshDelay /= 2;
499 }
500
501 player.hls.master.playlists[i] =
502 videojs.util.mergeOptions(playlist, parser.manifest);
503
504 if (playlist !== player.hls.media) {
505 continue;
506 }
507
508 // determine the new mediaIndex if we're updating the
509 // current media playlist
510 player.hls.mediaIndex =
511 findCorrespondingMediaIndex(player.hls.mediaIndex,
512 playlist,
513 parser.manifest);
514 player.hls.media = parser.manifest;
515 } 529 }
530
531 // determine the new mediaIndex if we're updating the
532 // current media playlist
533 player.hls.mediaIndex =
534 findCorrespondingMediaIndex(player.hls.mediaIndex,
535 playlist,
536 parser.manifest);
537 player.hls.media = parser.manifest;
516 } 538 }
517 } else {
518 // infer a master playlist if none was previously requested
519 player.hls.master = {
520 playlists: [parser.manifest]
521 };
522 parser.manifest.uri = url;
523 } 539 }
540 } else {
541 // infer a master playlist if none was previously requested
542 player.hls.master = {
543 playlists: [parser.manifest]
544 };
545 parser.manifest.uri = url;
546 }
524 547
525 // check the playlist for updates if EXT-X-ENDLIST isn't present 548 // check the playlist for updates if EXT-X-ENDLIST isn't present
526 if (!parser.manifest.endList) { 549 if (!parser.manifest.endList) {
527 window.setTimeout(function() { 550 window.setTimeout(function() {
528 downloadPlaylist(url); 551 xhr(url, downloadPlaylist);
529 }, refreshDelay); 552 }, refreshDelay);
530 } 553 }
531 554
532 // always start playback with the default rendition 555 // always start playback with the default rendition
533 if (!player.hls.media) { 556 if (!player.hls.media) {
534 player.hls.media = player.hls.master.playlists[0]; 557 player.hls.media = player.hls.master.playlists[0];
535 558
536 // update the duration 559 // update the duration
537 player.duration(totalDuration(parser.manifest)); 560 player.duration(totalDuration(parser.manifest));
538 561
539 // periodicaly check if the buffer needs to be refilled 562 // periodicaly check if the buffer needs to be refilled
540 player.on('timeupdate', fillBuffer); 563 player.on('timeupdate', fillBuffer);
541 564
542 player.trigger('loadedmanifest'); 565 player.trigger('loadedmanifest');
543 player.trigger('loadedmetadata'); 566 player.trigger('loadedmetadata');
544 fillBuffer(); 567 fillBuffer();
545 return; 568 return;
546 } 569 }
547 570
548 // select a playlist and download its metadata if necessary 571 // select a playlist and download its metadata if necessary
549 updateCurrentPlaylist(); 572 updateCurrentPlaylist();
550 573
551 player.trigger('loadedmanifest'); 574 player.trigger('loadedmanifest');
552 };
553 xhr.send(null);
554 }; 575 };
555 576
556 /** 577 /**
...@@ -668,7 +689,7 @@ var ...@@ -668,7 +689,7 @@ var
668 sourceBuffer.appendBuffer(segmentParser.getFlvHeader()); 689 sourceBuffer.appendBuffer(segmentParser.getFlvHeader());
669 690
670 player.hls.mediaIndex = 0; 691 player.hls.mediaIndex = 0;
671 downloadPlaylist(srcUrl); 692 xhr(srcUrl, downloadPlaylist);
672 }); 693 });
673 player.src([{ 694 player.src([{
674 src: videojs.URL.createObjectURL(mediaSource), 695 src: videojs.URL.createObjectURL(mediaSource),
......