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

Fix live gap jump #3467

Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dashjs",
"version": "3.1.4",
"version": "3.2.0",
"description": "A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.",
"main": "build/es5/index.js",
"types": "build/typings/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ function Settings() {
scheduleWhilePaused: true,
fastSwitchEnabled: false,
flushBufferAtTrackSwitch: false,
calcSegmentAvailabilityRangeFromTimeline: true,
calcSegmentAvailabilityRangeFromTimeline: false,
bufferPruningInterval: 10,
bufferToKeep: 20,
jumpGaps: true,
Expand Down
2 changes: 1 addition & 1 deletion src/core/Version.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const VERSION = '3.1.4';
const VERSION = '3.2.0';
export function getVersionString() {
return VERSION;
}
3 changes: 2 additions & 1 deletion src/streaming/controllers/BufferController.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import Errors from '../../core/errors/Errors';
import {HTTPRequest} from '../vo/metrics/HTTPRequest';

const STALL_THRESHOLD = 0.5;
const BUFFERING_COMPLETED_THRESHOLD = 0.1;
const BUFFER_END_THRESHOLD = 0.5;
const BUFFER_RANGE_CALCULATION_THRESHOLD = 0.01;
const QUOTA_EXCEEDED_ERROR_CODE = 22;
Expand Down Expand Up @@ -564,7 +565,7 @@ function BufferController(config) {
// No need to check buffer if type is not audio or video (for example if several errors occur during text parsing, so that the buffer cannot be filled, no error must occur on video playback)
if (type !== Constants.AUDIO && type !== Constants.VIDEO) return;

if (seekClearedBufferingCompleted && !isBufferingCompleted && bufferLevel > 0 && playbackController && playbackController.getTimeToStreamEnd() - bufferLevel < STALL_THRESHOLD) {
if (seekClearedBufferingCompleted && !isBufferingCompleted && bufferLevel > 0 && playbackController && playbackController.getTimeToStreamEnd() - bufferLevel < BUFFERING_COMPLETED_THRESHOLD) {
seekClearedBufferingCompleted = false;
isBufferingCompleted = true;
logger.debug('checkIfSufficientBuffer trigger BUFFERING_COMPLETED for type ' + type);
Expand Down
72 changes: 47 additions & 25 deletions src/streaming/controllers/GapController.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import Events from '../../core/events/Events';
import EventBus from '../../core/EventBus';

const GAP_HANDLER_INTERVAL = 100;
const THRESHOLD_TO_STALLS = 10;
const THRESHOLD_TO_STALLS = 30;
const GAP_THRESHOLD = 0.1;

function GapController() {
Expand All @@ -52,6 +52,7 @@ function GapController() {
videoModel,
timelineConverter,
adapter,
jumpTimeoutHandler,
logger;

function initialize() {
Expand All @@ -74,6 +75,7 @@ function GapController() {
gapHandlerInterval = null;
lastGapJumpPosition = NaN;
wallclockTicked = 0;
jumpTimeoutHandler = null;
}

function setConfig(config) {
Expand Down Expand Up @@ -101,12 +103,14 @@ function GapController() {
}

function registerEvents() {
eventBus.on(Events.WALLCLOCK_TIME_UPDATED, onWallclockTimeUpdated, instance);
eventBus.on(Events.WALLCLOCK_TIME_UPDATED, _onWallclockTimeUpdated, this);
eventBus.on(Events.PLAYBACK_SEEKING, _onPlaybackSeeking, this);
eventBus.on(Events.BYTES_APPENDED_END_FRAGMENT, onBytesAppended, instance);
}

function unregisterEvents() {
eventBus.off(Events.WALLCLOCK_TIME_UPDATED, onWallclockTimeUpdated, instance);
eventBus.off(Events.WALLCLOCK_TIME_UPDATED, _onWallclockTimeUpdated, this);
eventBus.off(Events.PLAYBACK_SEEKING, _onPlaybackSeeking, this);
eventBus.off(Events.BYTES_APPENDED_END_FRAGMENT, onBytesAppended, instance);
}

Expand All @@ -116,13 +120,14 @@ function GapController() {
}
}

function _shouldCheckForGaps() {
return settings.get().streaming.jumpGaps && streamController.getActiveStreamProcessors().length > 0 &&
(!playbackController.isSeeking() || streamController.hasStreamFinishedBuffering(streamController.getActiveStream())) && !playbackController.isPaused() && !streamController.getIsStreamSwitchInProgress() &&
!streamController.getHasMediaOrIntialisationError();
function _onPlaybackSeeking() {
if (jumpTimeoutHandler) {
clearTimeout(jumpTimeoutHandler);
jumpTimeoutHandler = null;
}
}

function onWallclockTimeUpdated(/*e*/) {
function _onWallclockTimeUpdated(/*e*/) {
if (!_shouldCheckForGaps()) {
return;
}
Expand All @@ -140,23 +145,29 @@ function GapController() {
}
}

function getNextRangeStartTime(currentTime) {
function _shouldCheckForGaps() {
return settings.get().streaming.jumpGaps && streamController.getActiveStreamProcessors().length > 0 &&
(!playbackController.isSeeking() || streamController.hasStreamFinishedBuffering(streamController.getActiveStream())) && !playbackController.isPaused() && !streamController.getIsStreamSwitchInProgress() &&
!streamController.getHasMediaOrIntialisationError();
}

function getNextRangeIndex(ranges, currentTime) {
try {
const ranges = videoModel.getBufferRange();

if (!ranges || (ranges.length <= 1 && currentTime > 0)) {
return null;
return NaN;
}
let nextRangeStartTime = null;
let nextRangeIndex = NaN;
let j = 0;

while (!nextRangeStartTime && j < ranges.length) {
while (isNaN(nextRangeIndex) && j < ranges.length) {
const rangeEnd = j > 0 ? ranges.end(j - 1) : 0;
if (currentTime < ranges.start(j) && rangeEnd - currentTime < GAP_THRESHOLD) {
nextRangeStartTime = ranges.start(j);
nextRangeIndex = j;
}
j += 1;
}
return nextRangeStartTime;
return nextRangeIndex;

} catch (e) {
return null;
Expand Down Expand Up @@ -192,35 +203,46 @@ function GapController() {
function jumpGap(currentTime, playbackStalled = false) {
const smallGapLimit = settings.get().streaming.smallGapLimit;
const jumpLargeGaps = settings.get().streaming.jumpLargeGaps;
let nextRangeStartTime = null;
const ranges = videoModel.getBufferRange();
let nextRangeIndex;
let seekToPosition = NaN;
let jumpToStreamEnd = false;


// Get the range just after current time position
nextRangeStartTime = getNextRangeStartTime(currentTime);
nextRangeIndex = getNextRangeIndex(ranges, currentTime);

if (nextRangeStartTime && nextRangeStartTime > 0) {
const gap = nextRangeStartTime - currentTime;
if (!isNaN(nextRangeIndex)) {
const start = ranges.start(nextRangeIndex);
const gap = start - currentTime;
if (gap > 0 && (gap <= smallGapLimit || jumpLargeGaps)) {
seekToPosition = nextRangeStartTime;
seekToPosition = start;
}
}

// Playback has stalled before period end. We seek to the end of the period
const timeToStreamEnd = playbackController.getTimeToStreamEnd();
if (isNaN(seekToPosition) && playbackStalled && isFinite(timeToStreamEnd) && !isNaN(timeToStreamEnd) && ((timeToStreamEnd < smallGapLimit) || streamController.hasStreamFinishedBuffering(streamController.getActiveStream()))) {
if (isNaN(seekToPosition) && playbackStalled && isFinite(timeToStreamEnd) && !isNaN(timeToStreamEnd) && timeToStreamEnd < smallGapLimit) {
seekToPosition = parseFloat(playbackController.getStreamEndTime().toFixed(5));
jumpToStreamEnd = true;
}

if (seekToPosition > 0 && lastGapJumpPosition !== seekToPosition) {
if (seekToPosition > 0 && lastGapJumpPosition !== seekToPosition && seekToPosition > currentTime && !jumpTimeoutHandler) {
const timeUntilGapEnd = seekToPosition - currentTime;

if (jumpToStreamEnd) {
logger.warn(`Jumping to end of stream because of gap from ${currentTime} to ${seekToPosition}. Gap duration: ${seekToPosition - currentTime}`);
logger.warn(`Jumping to end of stream because of gap from ${currentTime} to ${seekToPosition}. Gap duration: ${timeUntilGapEnd}`);
eventBus.trigger(Events.GAP_CAUSED_SEEK_TO_PERIOD_END, { seekTime: seekToPosition });
} else {
logger.warn(`Jumping gap from ${currentTime} to ${seekToPosition}. Gap duration: ${seekToPosition - currentTime}`);
playbackController.seek(seekToPosition, true, true);
const isDynamic = playbackController.getIsDynamic();
const start = nextRangeIndex > 0 ? ranges.end(nextRangeIndex - 1) : currentTime;
const timeToWait = !isDynamic ? 0 : timeUntilGapEnd * 1000;

jumpTimeoutHandler = window.setTimeout(() => {
playbackController.seek(seekToPosition, true, true);
logger.warn(`Jumping gap starting at ${start} and ending at ${seekToPosition}. Jumping by: ${timeUntilGapEnd}`);
jumpTimeoutHandler = null;
}, timeToWait);
}
lastGapJumpPosition = seekToPosition;
}
Expand Down