Skip to content

Commit

Permalink
Live seeking (#3324)
Browse files Browse the repository at this point in the history
* PlaybackController: re-set seek time in seeking handler since for some browsers/devices (in case of live streams), setting current time on video element fails when there is no buffered data at requested time

* BufferController: fix seeking for live streams for browser/devices on which seek can't be done at unavalaible data range

* update code comments

* Avoid seek if seeking at current time or current seeking target

* revert code removed

* BufferController: fix seeking for live streams for browser/devices on which seek can't be done at unavalaible data range

* remove logs

* BufferController: update adjustSeekTarget()

* Check if appended segment is not anterior to seek target
  • Loading branch information
Bertrand Berthelot committed Jul 15, 2020
1 parent 3ad63aa commit 2ef6520
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 37 deletions.
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 @@ -307,6 +301,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 @@ -442,7 +468,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 @@ -654,9 +680,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 @@ -683,7 +706,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

0 comments on commit 2ef6520

Please sign in to comment.