e3c5271b by David LaPalomento

Merge pull request #473 from videojs/firefox-live

Firefox live
2 parents 7462bdce 2286959f
......@@ -360,6 +360,10 @@
return;
}
if (update.uri !== outdated.uri) {
return;
}
// try using precise timing from first segment of the updated
// playlist
if (update.segments.length) {
......
......@@ -165,6 +165,8 @@ videojs.HlsHandler.prototype.src = function(src) {
}
this.playlists = new videojs.Hls.PlaylistLoader(this.source_.src, this.options_.withCredentials);
this.tech_.on('canplay', this.setupFirstPlay.bind(this));
this.playlists.on('loadedmetadata', function() {
oldMediaPlaylist = this.playlists.media();
......@@ -422,7 +424,11 @@ videojs.HlsHandler.prototype.setupFirstPlay = function() {
this.sourceBuffer &&
// 4) the active media playlist is available
media) {
media &&
// 5) the video element or flash player is in a readyState of
// at least HAVE_FUTURE_DATA
this.tech_.readyState >= 3) {
// seek to the latest media position for live videos
seekable = this.seekable();
......@@ -819,11 +825,14 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
var
tech = this.tech_,
currentTime = tech.currentTime(),
hasBufferedContent = (this.tech_.buffered().length !== 0),
currentBuffered = this.findBufferedRange_(),
outsideBufferedRanges = !(currentBuffered && currentBuffered.length),
currentBufferedEnd = 0,
bufferedTime = 0,
segment,
segmentInfo;
segmentInfo,
segmentTimestampOffset;
// if preload is set to "none", do not download segments until playback is requested
if (this.loadingState_ !== 'segments') {
......@@ -906,9 +915,35 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
// the state of the buffer before a segment is appended will be
// stored here so that the actual segment duration can be
// determined after it has been appended
buffered: null
buffered: null,
// The target timestampOffset for this segment when we append it
// to the source buffer
timestampOffset: null
};
if (mediaIndex > 0) {
segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist,
segmentInfo.playlist.mediaSequence + mediaIndex) + this.playlists.expired_;
}
if (this.tech_.seeking() && outsideBufferedRanges) {
// If there are discontinuities in the playlist, we can't be sure of anything
// related to time so we reset the timestamp offset and start appending data
// anew on every seek
if (segmentInfo.playlist.discontinuityStarts.length) {
segmentInfo.timestampOffset = segmentTimestampOffset;
}
} else if (segment.discontinuity && currentBuffered.length) {
// If we aren't seeking and are crossing a discontinuity, we should set
// timestampOffset for new segments to be appended the end of the current
// buffered time-range
segmentInfo.timestampOffset = currentBuffered.end(0);
} else if (!hasBufferedContent && this.tech_.currentTime() > 0.05) {
// If we are trying to play at a position that is not zero but we aren't
// currently seeking according to the video element
segmentInfo.timestampOffset = segmentTimestampOffset;
}
this.loadSegment(segmentInfo);
};
......@@ -1068,7 +1103,7 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
};
videojs.HlsHandler.prototype.drainBuffer = function(event) {
videojs.HlsHandler.prototype.drainBuffer = function() {
var
segmentInfo,
mediaIndex,
......@@ -1077,11 +1112,7 @@ videojs.HlsHandler.prototype.drainBuffer = function(event) {
bytes,
segment,
decrypter,
segIv,
segmentTimestampOffset = 0,
hasBufferedContent = (this.tech_.buffered().length !== 0),
currentBuffered = this.findBufferedRange_(),
outsideBufferedRanges = !(currentBuffered && currentBuffered.length);
segIv;
// if the buffer is empty or the source buffer hasn't been created
// yet, do nothing
......@@ -1109,7 +1140,6 @@ videojs.HlsHandler.prototype.drainBuffer = function(event) {
segment = playlist.segments[mediaIndex];
if (segment.key && !bytes) {
// this is an encrypted segment
// if the key download failed, we want to skip this segment
// but if the key hasn't downloaded yet, we want to try again later
......@@ -1144,36 +1174,11 @@ videojs.HlsHandler.prototype.drainBuffer = function(event) {
}
}
event = event || {};
if (segmentInfo.mediaIndex > 0) {
segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist,
playlist.mediaSequence + segmentInfo.mediaIndex);
}
this.pendingSegment_.buffered = this.tech_.buffered();
// If we have seeked into a non-buffered time-range, remove all buffered
// time-ranges because they could have been incorrectly placed originally
if (this.tech_.seeking() && outsideBufferedRanges) {
// If there are discontinuities in the playlist, we can't be sure of anything
// related to time so we reset the timestamp offset and start appending data
// anew on every seek
if (segmentInfo.playlist.discontinuityStarts.length) {
// Now that the forward buffer is clear, we have to set timestamp offset to
// the start of the buffered region
this.sourceBuffer.timestampOffset = segmentTimestampOffset;
if (segmentInfo.timestampOffset !== null) {
this.sourceBuffer.timestampOffset = segmentInfo.timestampOffset;
}
} else if (segment.discontinuity && currentBuffered.length) {
// If we aren't seeking and are crossing a discontinuity, we should set
// timestampOffset for new segments to be appended the end of the current
// buffered time-range
this.sourceBuffer.timestampOffset = currentBuffered.end(0);
} else if (!hasBufferedContent && this.tech_.currentTime() > 0.05) {
// If we are trying to play at a position that is not zero but we aren't
// currently seeking according to the video element
this.sourceBuffer.timestampOffset = segmentTimestampOffset;
}
this.pendingSegment_.buffered = this.tech_.buffered();
// the segment is asynchronously added to the current buffered data
this.sourceBuffer.appendBuffer(bytes);
......
......@@ -335,6 +335,8 @@ test('autoplay seeks to the live point after playlist load', function() {
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
player.tech_.readyState = 3;
player.tech_.trigger('play');
standardXHRResponse(requests.shift());
clock.tick(1);
......@@ -355,6 +357,8 @@ test('autoplay seeks to the live point after media source open', function() {
clock.tick(1);
standardXHRResponse(requests.shift());
openMediaSource(player);
player.tech_.readyState = 3;
player.tech_.trigger('play');
clock.tick(1);
notEqual(currentTime, 0, 'seeked on autoplay');
......@@ -406,6 +410,7 @@ test('calls `remove` on sourceBuffer to when loading a live segment', function()
player.tech_.hls.playlists.trigger('loadedmetadata');
player.tech_.trigger('canplay');
player.tech_.paused = function() { return false; };
player.tech_.readyState = 3;
player.tech_.trigger('play');
clock.tick(1);
......@@ -1683,6 +1688,7 @@ test('live playlist starts three target durations before live', function() {
equal(requests.length, 0, 'no outstanding segment request');
player.tech_.paused = function() { return false; };
player.tech_.readyState = 3;
player.tech_.trigger('play');
clock.tick(1);
mediaPlaylist = player.tech_.hls.playlists.media();
......@@ -1703,6 +1709,7 @@ test('live playlist starts with correct currentTime value', function() {
player.tech_.hls.playlists.trigger('loadedmetadata');
player.tech_.paused = function() { return false; };
player.tech_.readyState = 3;
player.tech_.trigger('play');
clock.tick(1);
......