Merge pull request #246 from videojs/live-hlse-fixes
Fix discontinuities
Showing
4 changed files
with
58 additions
and
54 deletions
... | @@ -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> | ... | ... |
... | @@ -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,22 +785,19 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) { | ... | @@ -788,22 +785,19 @@ 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) { |
795 | keyXhr = videojs.Hls.xhr({ | 792 | return function(error) { |
796 | url: this.playlistUriToUrl(key.uri), | ||
797 | responseType: 'arraybuffer', | ||
798 | withCredentials: settings.withCredentials | ||
799 | }, function(err, url) { | ||
800 | keyXhr = null; | 793 | keyXhr = null; |
801 | 794 | ||
802 | if (err || !this.response || this.response.byteLength !== 16) { | 795 | if (error || !this.response || this.response.byteLength !== 16) { |
803 | key.retries = key.retries || 0; | 796 | key.retries = key.retries || 0; |
804 | key.retries++; | 797 | key.retries++; |
805 | if (!this.aborted) { | 798 | if (!this.aborted) { |
806 | tech.fetchKeys(playlist, i); | 799 | // try fetching again |
800 | tech.fetchKeys_(); | ||
807 | } | 801 | } |
808 | return; | 802 | return; |
809 | } | 803 | } |
... | @@ -815,12 +809,31 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) { | ... | @@ -815,12 +809,31 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) { |
815 | view.getUint32(8), | 809 | view.getUint32(8), |
816 | view.getUint32(12) | 810 | view.getUint32(12) |
817 | ]); | 811 | ]); |
818 | tech.fetchKeys(playlist, i++, url); | 812 | |
819 | }); | 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)) { | ||
829 | keyXhr = videojs.Hls.xhr({ | ||
830 | url: this.playlistUriToUrl(key.uri), | ||
831 | responseType: 'arraybuffer', | ||
832 | withCredentials: settings.withCredentials | ||
833 | }, receiveKey(key)); | ||
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 | ... | ... |
This diff is collapsed.
Click to expand it.
-
Please register or sign in to post a comment