Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live seeking #3324

Merged
merged 9 commits into from Jul 15, 2020
1 change: 0 additions & 1 deletion src/streaming/StreamProcessor.js
Expand Up @@ -229,7 +229,6 @@ function StreamProcessor(config) {

function onDataUpdateCompleted(e) {
if (e.sender.getType() !== getType() || e.sender.getStreamId() !== streamInfo.id) return;
logger.info('DataUpdateCompleted');

if (!e.error) {
// Update representation if no error
Expand Down
60 changes: 41 additions & 19 deletions src/streaming/controllers/BufferController.js
Expand Up @@ -241,7 +241,7 @@ function BufferController(config) {
function showBufferRanges(ranges) {
if (ranges && ranges.length > 0) {
for (let i = 0, len = ranges.length; i < len; i++) {
logger.debug('Buffered Range', ranges.start(i), ' - ', ranges.end(i), ' currentTime = ', playbackController.getTime());
logger.debug('Buffered range: ' + ranges.start(i) + ' - ' + ranges.end(i) + ', currentTime = ', playbackController.getTime());
}
}
}
Expand Down Expand Up @@ -281,19 +281,13 @@ function BufferController(config) {
if (appendedBytesInfo.segmentType === HTTPRequest.MEDIA_SEGMENT_TYPE) {
showBufferRanges(ranges);
onPlaybackProgression();

// If seeking, seek video model to range start in case appended segment starts beyond seek target
if (!isNaN(seekTarget) &&
(playbackController.getTime() === 0 || playbackController.getTime() < ranges.start(0))) {
playbackController.seek(ranges.start(0), true, true);
seekTarget = NaN;
}
} else {
if (replacingBuffer) {
const currentTime = playbackController.getTime();
logger.debug('AppendToBuffer seek target should be ' + currentTime);
triggerEvent(Events.SEEK_TARGET, {time: currentTime});
}
adjustSeekTarget();
} else if (replacingBuffer) {
// When replacing buffer due to switch track, and once new initialization segment has been appended
// (and previous buffered data removed) then seek stream to current time
const currentTime = playbackController.getTime();
logger.debug('AppendToBuffer seek target should be ' + currentTime);
triggerEvent(Events.SEEK_TARGET, {time: currentTime});
}

if (appendedBytesInfo) {
Expand All @@ -306,6 +300,38 @@ function BufferController(config) {
}
}

function adjustSeekTarget () {
// Check buffered data only for audio and video
if (type !== Constants.AUDIO && type !== Constants.VIDEO) return;
if (isNaN(seekTarget)) return;

// Check if current buffered range already contains seek target (and current video element time)
const currentTime = playbackController.getTime();
let range = getRangeAt(seekTarget, 0);
if (currentTime === seekTarget && range) return;

// Get buffered range corresponding to the seek target
const segmentDuration = representationController.getCurrentRepresentation().segmentDuration;
range = getRangeAt(seekTarget, segmentDuration);
if (!range) return;

if (Math.abs(currentTime - seekTarget) > segmentDuration) {
// If current video model time is decorrelated from seek target (and appended buffer) then seek video element
// (in case of live streams on some browsers/devices for which we can't set video element time at unavalaible range)

// Check if appended segment is not anterior from seek target (segments timeline/template tolerance)
if (seekTarget <= range.end) {
// Seek video element to seek target or range start if appended buffer starts after seek target (segments timeline/template tolerance)
playbackController.seek(Math.max(seekTarget, range.start), false, true);
seekTarget = NaN;
}
} else if (currentTime < range.start) {
// If appended buffer starts after seek target (segments timeline/template tolerance) then seek to range start
playbackController.seek(range.start, false, true);
seekTarget = NaN;
}
}

function onQualityChanged(e) {
if (e.streamInfo.id != streamInfo.id || e.mediaType !== type || requiredQuality === e.newQuality) return;

Expand Down Expand Up @@ -441,7 +467,7 @@ function BufferController(config) {
let len,
i;

const toler = (tolerance || 0.15);
const toler = !isNaN(tolerance) ? tolerance : 0.15;

if (ranges !== null && ranges !== undefined) {
for (i = 0, len = ranges.length; i < len; i++) {
Expand Down Expand Up @@ -653,9 +679,6 @@ function BufferController(config) {
if (currentTime < range.end) {
isBufferingCompleted = false;
maxAppendedIndex = 0;
if (!replacingBuffer) {
triggerEvent(Events.SEEK_TARGET, {time: currentTime});
}
}

buffer.remove(range.start, range.end, range.force);
Expand All @@ -682,7 +705,6 @@ function BufferController(config) {
clearNextRange();
} else {
if (!replacingBuffer) {
logger.debug('onRemoved : call updateBufferLevel');
updateBufferLevel();
} else {
replacingBuffer = false;
Expand Down
42 changes: 27 additions & 15 deletions src/streaming/controllers/PlaybackController.js
Expand Up @@ -59,6 +59,7 @@ function PlaybackController() {
playOnceInitialized,
lastLivePlaybackTime,
availabilityStartTime,
seekTarget,
isLowLatencySeekingInProgress,
playbackStalled,
minPlaybackRateChange,
Expand Down Expand Up @@ -178,20 +179,22 @@ function PlaybackController() {
}

function seek(time, stickToBuffered, internalSeek) {
if (streamInfo && videoModel) {
if (internalSeek === true) {
if (time !== videoModel.getTime()) {
// Internal seek = seek video model only (disable 'seeking' listener),
// buffer(s) are already appended at given time (see onBytesAppended())
videoModel.removeEventListener('seeking', onPlaybackSeeking);
logger.info('Requesting internal seek to time: ' + time);
videoModel.setCurrentTime(time, stickToBuffered);
}
} else {
eventBus.trigger(Events.PLAYBACK_SEEK_ASKED);
logger.info('Requesting seek to time: ' + time);
videoModel.setCurrentTime(time, stickToBuffered);
}
if (!streamInfo || !videoModel) return;

let currentTime = !isNaN(seekTarget) ? seekTarget : videoModel.getTime();
if (time === currentTime) return;

if (internalSeek === true) {
// Internal seek = seek video model only (disable 'seeking' listener)
// buffer(s) are already appended at requested time
videoModel.removeEventListener('seeking', onPlaybackSeeking);
logger.info('Requesting internal seek to time: ' + time);
videoModel.setCurrentTime(time, stickToBuffered);
} else {
seekTarget = time;
eventBus.trigger(Events.PLAYBACK_SEEK_ASKED);
logger.info('Requesting seek to time: ' + time);
videoModel.setCurrentTime(time, stickToBuffered);
}
}

Expand Down Expand Up @@ -322,6 +325,7 @@ function PlaybackController() {
streamSeekTime = NaN;
liveDelay = 0;
availabilityStartTime = 0;
seekTarget = NaN;
if (videoModel) {
eventBus.off(Events.STREAM_INITIALIZED, onStreamInitialized, this);
eventBus.off(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, this);
Expand Down Expand Up @@ -486,7 +490,15 @@ function PlaybackController() {
}

function onPlaybackSeeking() {
const seekTime = getTime();
let seekTime = getTime();

// On some browsers/devices, in case of live streams, setting current time on video element fails when there is no buffered data at requested time
// Then re-set seek target time and video element will be seeked afterwhile once data is buffered (see BufferContoller)
if (!isNaN(seekTarget) && seekTarget !== seekTime) {
seekTime = seekTarget;
}
seekTarget = NaN;

logger.info('Seeking to: ' + seekTime);
startUpdatingWallclockTime();
eventBus.trigger(Events.PLAYBACK_SEEKING, {
Expand Down
5 changes: 3 additions & 2 deletions src/streaming/controllers/ScheduleController.js
Expand Up @@ -458,11 +458,12 @@ function ScheduleController(config) {
latency: latency
});

//if, during the seek command, the scheduleController is waiting : stop waiting, request chunk as soon as possible
if (!isFragmentProcessingInProgress) {
// Restart scheduler if in pending state
startScheduleTimer(0);
} else {
logger.debug('onPlaybackSeeking, call fragmentModel.abortRequests in order to seek quicker');
// Abort current requests
logger.debug('Abort requests');
fragmentModel.abortRequests();
}
}
Expand Down