4624510d by David LaPalomento

Merge pull request #246 from videojs/live-hlse-fixes

Fix discontinuities
2 parents 021896e3 4222a1f8
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
70 type="application/x-mpegURL"> 70 type="application/x-mpegURL">
71 </video> 71 </video>
72 <script> 72 <script>
73 videojs.options.flash.swf = 'node_modules/video.js/dist/video-js/video-js.swf'; 73 videojs.options.flash.swf = 'node_modules/videojs-swf/dist/video-js.swf';
74 // initialize the player 74 // initialize the player
75 var player = videojs('video'); 75 var player = videojs('video');
76 </script> 76 </script>
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
43 }, 43 },
44 "dependencies": { 44 "dependencies": {
45 "pkcs7": "^0.2.2", 45 "pkcs7": "^0.2.2",
46 "videojs-contrib-media-sources": "^0.3.0" 46 "videojs-contrib-media-sources": "^0.3.0",
47 "videojs-swf": "^4.6.0"
47 } 48 }
48 } 49 }
......
...@@ -221,14 +221,13 @@ videojs.Hls.prototype.src = function(src) { ...@@ -221,14 +221,13 @@ videojs.Hls.prototype.src = function(src) {
221 this.mediaIndex = videojs.Hls.translateMediaIndex(this.mediaIndex, oldMediaPlaylist, updatedPlaylist); 221 this.mediaIndex = videojs.Hls.translateMediaIndex(this.mediaIndex, oldMediaPlaylist, updatedPlaylist);
222 oldMediaPlaylist = updatedPlaylist; 222 oldMediaPlaylist = updatedPlaylist;
223 223
224 this.fetchKeys(updatedPlaylist, this.mediaIndex); 224 this.fetchKeys_();
225 })); 225 }));
226 226
227 this.playlists.on('mediachange', videojs.bind(this, function() { 227 this.playlists.on('mediachange', videojs.bind(this, function() {
228 // abort outstanding key requests and check if new keys need to be retrieved 228 // abort outstanding key requests and check if new keys need to be retrieved
229 if (keyXhr) { 229 if (keyXhr) {
230 this.cancelKeyXhr(); 230 this.cancelKeyXhr();
231 this.fetchKeys(this.playlists.media(), this.mediaIndex);
232 } 231 }
233 232
234 player.trigger('mediachange'); 233 player.trigger('mediachange');
...@@ -330,11 +329,10 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { ...@@ -330,11 +329,10 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
330 // cancel outstanding requests and buffer appends 329 // cancel outstanding requests and buffer appends
331 this.cancelSegmentXhr(); 330 this.cancelSegmentXhr();
332 331
333 // fetch new encryption keys, if necessary 332 // abort outstanding key requests, if necessary
334 if (keyXhr) { 333 if (keyXhr) {
335 keyXhr.aborted = true; 334 keyXhr.aborted = true;
336 this.cancelKeyXhr(); 335 this.cancelKeyXhr();
337 this.fetchKeys(this.playlists.media(), this.mediaIndex);
338 } 336 }
339 337
340 // clear out any buffered segments 338 // clear out any buffered segments
...@@ -659,6 +657,7 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { ...@@ -659,6 +657,7 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
659 offset: offset, 657 offset: offset,
660 bytes: new Uint8Array(this.response) 658 bytes: new Uint8Array(this.response)
661 }); 659 });
660 player.trigger('progress');
662 tech.drainBuffer(); 661 tech.drainBuffer();
663 662
664 tech.mediaIndex++; 663 tech.mediaIndex++;
...@@ -700,7 +699,8 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -700,7 +699,8 @@ videojs.Hls.prototype.drainBuffer = function(event) {
700 if (keyFailed(segment.key)) { 699 if (keyFailed(segment.key)) {
701 return segmentBuffer.shift(); 700 return segmentBuffer.shift();
702 } else if (!segment.key.bytes) { 701 } else if (!segment.key.bytes) {
703 return; 702 // trigger a key request if one is not already in-flight
703 return this.fetchKeys_();
704 } else { 704 } else {
705 // if the media sequence is greater than 2^32, the IV will be incorrect 705 // if the media sequence is greater than 2^32, the IV will be incorrect
706 // assuming 10s segments, that would be about 1300 years 706 // assuming 10s segments, that would be about 1300 years
...@@ -714,23 +714,6 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -714,23 +714,6 @@ videojs.Hls.prototype.drainBuffer = function(event) {
714 event = event || {}; 714 event = event || {};
715 segmentOffset = videojs.Hls.getPlaylistDuration(playlist, 0, mediaIndex) * 1000; 715 segmentOffset = videojs.Hls.getPlaylistDuration(playlist, 0, mediaIndex) * 1000;
716 716
717 // abort() clears any data queued in the source buffer so wait
718 // until it empties before calling it when a discontinuity is
719 // next in the buffer
720 if (segment.discontinuity) {
721 if (event.type === 'waiting') {
722 this.sourceBuffer.abort();
723 // tell the SWF where playback is continuing in the stitched timeline
724 this.el().vjs_setProperty('currentTime', segmentOffset * 0.001);
725 } else if (event.type === 'timeupdate') {
726 return;
727 } else if (typeof offset !== 'number') {
728 //if the discontinuity is reached under normal conditions, ie not a seek,
729 //the buffer already contains data and does not need to be refilled,
730 return;
731 }
732 }
733
734 // transmux the segment data from MP2T to FLV 717 // transmux the segment data from MP2T to FLV
735 this.segmentParser_.parseSegmentBinaryData(bytes); 718 this.segmentParser_.parseSegmentBinaryData(bytes);
736 this.segmentParser_.flushTags(); 719 this.segmentParser_.flushTags();
...@@ -758,6 +741,12 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -758,6 +741,12 @@ videojs.Hls.prototype.drainBuffer = function(event) {
758 this.lastSeekedTime_ = null; 741 this.lastSeekedTime_ = null;
759 } 742 }
760 743
744 // when we're crossing a discontinuity, inject metadata to indicate
745 // that the decoder should be reset appropriately
746 if (segment.discontinuity && tags.length) {
747 this.el().vjs_discontinuity();
748 }
749
761 for (i = 0; i < tags.length; i++) { 750 for (i = 0; i < tags.length; i++) {
762 // queue up the bytes to be appended to the SourceBuffer 751 // queue up the bytes to be appended to the SourceBuffer
763 // the queue gives control back to the browser between tags 752 // the queue gives control back to the browser between tags
...@@ -776,11 +765,19 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -776,11 +765,19 @@ videojs.Hls.prototype.drainBuffer = function(event) {
776 } 765 }
777 }; 766 };
778 767
779 videojs.Hls.prototype.fetchKeys = function(playlist, index) { 768 /**
780 var i, key, tech, player, settings, view; 769 * Attempt to retrieve keys starting at a particular media
770 * segment. This method has no effect if segments are not yet
771 * available or a key request is already in progress.
772 *
773 * @param playlist {object} the media playlist to fetch keys for
774 * @param index {number} the media segment index to start from
775 */
776 videojs.Hls.prototype.fetchKeys_ = function() {
777 var i, key, tech, player, settings, segment, view, receiveKey;
781 778
782 // if there is a pending XHR or no segments, don't do anything 779 // if there is a pending XHR or no segments, don't do anything
783 if (keyXhr || !playlist.segments) { 780 if (keyXhr || !this.segmentBuffer_.length) {
784 return; 781 return;
785 } 782 }
786 783
...@@ -788,39 +785,55 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) { ...@@ -788,39 +785,55 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) {
788 player = this.player(); 785 player = this.player();
789 settings = player.options().hls || {}; 786 settings = player.options().hls || {};
790 787
791 // jshint -W083 788 /**
792 for (i = index; i < playlist.segments.length; i++) { 789 * Handle a key XHR response. This function needs to lookup the
793 key = playlist.segments[i].key; 790 */
794 if (key && !key.bytes && !keyFailed(key)) { 791 receiveKey = function(key) {
792 return function(error) {
793 keyXhr = null;
794
795 if (error || !this.response || this.response.byteLength !== 16) {
796 key.retries = key.retries || 0;
797 key.retries++;
798 if (!this.aborted) {
799 // try fetching again
800 tech.fetchKeys_();
801 }
802 return;
803 }
804
805 view = new DataView(this.response);
806 key.bytes = new Uint32Array([
807 view.getUint32(0),
808 view.getUint32(4),
809 view.getUint32(8),
810 view.getUint32(12)
811 ]);
812
813 // check to see if this allows us to make progress buffering now
814 tech.checkBuffer_();
815 };
816 };
817
818 for (i = 0; i < tech.segmentBuffer_.length; i++) {
819 segment = tech.segmentBuffer_[i].playlist.segments[tech.segmentBuffer_[i].mediaIndex];
820 key = segment.key;
821
822 // continue looking if this segment is unencrypted
823 if (!key) {
824 continue;
825 }
826
827 // request the key if the retry limit hasn't been reached
828 if (!key.bytes && !keyFailed(key)) {
795 keyXhr = videojs.Hls.xhr({ 829 keyXhr = videojs.Hls.xhr({
796 url: this.playlistUriToUrl(key.uri), 830 url: this.playlistUriToUrl(key.uri),
797 responseType: 'arraybuffer', 831 responseType: 'arraybuffer',
798 withCredentials: settings.withCredentials 832 withCredentials: settings.withCredentials
799 }, function(err, url) { 833 }, receiveKey(key));
800 keyXhr = null;
801
802 if (err || !this.response || this.response.byteLength !== 16) {
803 key.retries = key.retries || 0;
804 key.retries++;
805 if (!this.aborted) {
806 tech.fetchKeys(playlist, i);
807 }
808 return;
809 }
810
811 view = new DataView(this.response);
812 key.bytes = new Uint32Array([
813 view.getUint32(0),
814 view.getUint32(4),
815 view.getUint32(8),
816 view.getUint32(12)
817 ]);
818 tech.fetchKeys(playlist, i++, url);
819 });
820 break; 834 break;
821 } 835 }
822 } 836 }
823 // jshint +W083
824 }; 837 };
825 838
826 /** 839 /**
...@@ -925,9 +938,7 @@ videojs.Hls.getPlaylistTotalDuration = function(playlist) { ...@@ -925,9 +938,7 @@ videojs.Hls.getPlaylistTotalDuration = function(playlist) {
925 * playlist 938 * playlist
926 */ 939 */
927 videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) { 940 videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) {
928 var i, 941 var translatedMediaIndex;
929 originalSegment,
930 translatedMediaIndex;
931 942
932 // no segments have been loaded from the original playlist 943 // no segments have been loaded from the original playlist
933 if (mediaIndex === 0) { 944 if (mediaIndex === 0) {
...@@ -939,15 +950,8 @@ videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) { ...@@ -939,15 +950,8 @@ videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) {
939 return 0; 950 return 0;
940 } 951 }
941 952
942 // try to sync based on URI 953 // translate based on media sequence numbers. syncing up across
943 i = update.segments.length; 954 // bitrate switches should be happening here.
944 originalSegment = original.segments[mediaIndex - 1];
945 while (i--) {
946 if (originalSegment.uri === update.segments[i].uri) {
947 return i + 1;
948 }
949 }
950
951 translatedMediaIndex = (mediaIndex + (original.mediaSequence - update.mediaSequence)); 955 translatedMediaIndex = (mediaIndex + (original.mediaSequence - update.mediaSequence));
952 956
953 if (translatedMediaIndex >= update.segments.length || translatedMediaIndex < 0) { 957 if (translatedMediaIndex >= update.segments.length || translatedMediaIndex < 0) {
...@@ -955,7 +959,6 @@ videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) { ...@@ -955,7 +959,6 @@ videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) {
955 return videojs.Hls.getMediaIndexForLive_(update) + 1; 959 return videojs.Hls.getMediaIndexForLive_(update) + 1;
956 } 960 }
957 961
958 // sync on media sequence
959 return translatedMediaIndex; 962 return translatedMediaIndex;
960 }; 963 };
961 964
......