Skip to content
This repository has been archived by the owner on Jan 12, 2019. It is now read-only.

Seek to a safe position when resuming live playlists #327

Merged
merged 2 commits into from
Jun 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/videojs-hls.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,21 @@ videojs.Hls.prototype.play = function() {
this.mediaIndex = 0;
}

// seek to the latest safe point in the media timeline when first
// playing live streams
if (this.duration() === Infinity &&
this.playlists.media() &&
!this.player().hasClass('vjs-has-started')) {
this.setCurrentTime(this.seekable().end(0));
// we may need to seek to begin playing safely for live playlists
if (this.duration() === Infinity) {

// if this is the first time we're playing the stream or we're
// ahead of the latest safe playback position, seek to the live
// point
if (!this.player().hasClass('vjs-has-started') ||
this.currentTime() > this.seekable().end(0)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about storing seekable end in a variable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe calculating seekable end is expensive enough to worry about caching.

this.setCurrentTime(this.seekable().end(0));

} else if (this.currentTime() < this.seekable().start(0)) {
// if the viewer has paused and we fell out of the live window,
// seek forward to the earliest available position
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case, do we want to seek to the earliest position or to the "3 segments behind live"/"live point" position?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way the viewer misses the minimum amount of content relative to when they paused the broadcast. The choice is a bit subjective but I think this is defensible.

this.setCurrentTime(this.seekable().start(0));
}
}

// delegate back to the Flash implementation
Expand Down Expand Up @@ -356,6 +365,13 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
return 0;
}

// clamp seeks to the available seekable time range
if (currentTime < this.seekable().start(0)) {
currentTime = this.seekable().start(0);
} else if (currentTime > this.seekable().end(0)) {
currentTime = this.seekable().end(0);
}

// save the seek target so currentTime can report it correctly
// while the seek is pending
this.lastSeekedTime_ = currentTime;
Expand Down
81 changes: 81 additions & 0 deletions test/videojs-hls_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,87 @@ test('live playlist starts with correct currentTime value', function() {
'currentTime is updated at playback');
});

test('resets the time to a seekable position when resuming a live stream ' +
'after a long break', function() {
var seekTarget;
player.src({
src: 'live0.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:16\n' +
'#EXTINF:10,\n' +
'16.ts\n');
// mock out the player to simulate a live stream that has been
// playing for awhile
player.addClass('vjs-has-started');
player.hls.seekable = function() {
return {
start: function() {
return 160;
},
end: function() {
return 170;
}
};
};
player.hls.currentTime = function() {
return 0;
};
player.hls.setCurrentTime = function(time) {
if (time !== undefined) {
seekTarget = time;
}
};

player.play();
equal(seekTarget, player.seekable().start(0), 'seeked to the start of seekable');

player.hls.currentTime = function() {
return 180;
};
player.play();
equal(seekTarget, player.seekable().end(0), 'seeked to the end of seekable');
});

test('clamps seeks to the seekable window', function() {
var seekTarget;
player.src({
src: 'live0.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:16\n' +
'#EXTINF:10,\n' +
'16.ts\n');
// mock out a seekable window
player.hls.seekable = function() {
return {
start: function() {
return 160;
},
end: function() {
return 170;
}
};
};
player.hls.fillBuffer = function(time) {
if (time !== undefined) {
seekTarget = time;
}
};

player.currentTime(180);
equal(seekTarget * 0.001, player.seekable().end(0), 'forward seeks are clamped');

player.currentTime(45);
equal(seekTarget * 0.001, player.seekable().start(0), 'backward seeks are clamped');
});

test('mediaIndex is zero before the first segment loads', function() {
window.manifests['first-seg-load'] =
'#EXTM3U\n' +
Expand Down