Skip to content

Commit

Permalink
Fix a bug that lead to playback stalling when bufferToKeep is larger …
Browse files Browse the repository at this point in the history
…than bufferTimeAtTopQuality (#4488)
  • Loading branch information
dsilhavy committed May 22, 2024
1 parent 78e0823 commit d6708a1
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/streaming/controllers/BufferController.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ function BufferController(config) {
return clearRanges;
}

// if no target time is provided we clear everyhing
// if no target time is provided we clear everything
if ((!seekTime && seekTime !== 0) || isNaN(seekTime)) {
clearRanges.push({
start: ranges.start(0),
Expand All @@ -597,7 +597,6 @@ function BufferController(config) {

// otherwise we need to calculate the correct pruning range
else {

const behindPruningRange = _getRangeBehindForPruning(seekTime, ranges);
const aheadPruningRange = _getRangeAheadForPruning(seekTime, ranges);

Expand Down Expand Up @@ -926,6 +925,7 @@ function BufferController(config) {
function clearBuffers(ranges) {
return new Promise((resolve, reject) => {
if (!ranges || !sourceBufferSink || ranges.length === 0) {
_updateBufferLevel();
resolve();
return;
}
Expand Down
27 changes: 27 additions & 0 deletions test/functional/adapter/DashJsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,33 @@ class DashJsAdapter {
delayPollInterval = setInterval(_checkDelay, 100);
})
}

async reachedTargetForwardBuffer(timeoutValue, targetBuffer, tolerance) {
return new Promise((resolve) => {
let timeout = null;
let delayPollInterval = null;

const _onComplete = (res) => {
clearTimeout(timeout);
clearInterval(delayPollInterval);
delayPollInterval = null;
timeout = null;
resolve(res);
}
const _onTimeout = () => {
_onComplete(false);
}

const _checkBuffer = () => {
const buffer = this.getBufferLengthByType();
if (buffer >= targetBuffer) {
_onComplete(true);
}
};
timeout = setTimeout(_onTimeout, timeoutValue);
delayPollInterval = setInterval(_checkBuffer, 100);
})
}
}

export default DashJsAdapter;
6 changes: 6 additions & 0 deletions test/functional/src/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const TEST_TIMEOUT_THRESHOLDS = {
EVENT_WAITING_TIME: 10000,
BUFFER_CLEANUP: 45000,
TARGET_DELAY_REACHED: 20000,
TARGET_BUFFER_REACHED: 20000,
ENDED_EVENT_OFFSET: 2000,
IS_FINISHED_OFFSET_TO_DURATION: 5000
};
Expand Down Expand Up @@ -91,6 +92,10 @@ const TEST_INPUTS = {
ATTACH_WITH_POSIX: {
DELAY: 30,
TOLERANCE: 5
},
BUFFER_TO_KEEP_SEEK: {
TOLERANCE: 1,
SEEK_OFFSET: 40
}
};

Expand Down Expand Up @@ -134,6 +139,7 @@ TESTCASES.AUDIO.SWITCH = TESTCASES.CATEGORIES.AUDIO + 'switch-audio';
TESTCASES.BUFFER.CLEANUP = TESTCASES.CATEGORIES.BUFFER + 'buffer-cleanup';
TESTCASES.BUFFER.INITIAL_TARGET = TESTCASES.CATEGORIES.BUFFER + 'initial-buffer-target';
TESTCASES.BUFFER.TARGET = TESTCASES.CATEGORIES.BUFFER + 'buffer-target';
TESTCASES.BUFFER.TO_KEEP_SEEK = TESTCASES.CATEGORIES.BUFFER + 'buffer-to-keep-seek';

TESTCASES.FEATURE_SUPPORT.EMSG_TRIGGERED = TESTCASES.CATEGORIES.FEATURE_SUPPORT + 'emsg-triggered';
TESTCASES.FEATURE_SUPPORT.MPD_PATCHING = TESTCASES.CATEGORIES.FEATURE_SUPPORT + 'mpd-patching';
Expand Down
88 changes: 88 additions & 0 deletions test/functional/test/buffer/buffer-to-keep-seek.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* This test checks if playback resumes after a seek in case bufferToKeep is larger than bufferTimeAtTopQuality
*/
import Constants from '../../src/Constants.js';
import {
checkIsNotProgressing,
checkIsPlaying,
checkIsProgressing,
checkNoCriticalErrors, checkTimeWithinThresholdForDvrWindow,
initializeDashJsAdapter,
reachedTargetForwardBuffer
} from '../common/common.js';

const TESTCASE = Constants.TESTCASES.BUFFER.TO_KEEP_SEEK;

const item = {
url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd',
type: 'vod',
name: 'Segment Template BBB'
}

const mpd = item.url;
describe(`${TESTCASE} - ${item.name} - ${mpd}`, () => {

let playerAdapter;
const TARGET_BUFFER = 10;
const BUFFER_TO_KEEP = TARGET_BUFFER + 100

before(() => {
const settings = {
streaming: {
buffer: {
bufferTimeDefault: TARGET_BUFFER,
bufferTimeAtTopQuality: TARGET_BUFFER,
bufferTimeAtTopQualityLongForm: TARGET_BUFFER,
longFormContentDurationThreshold: 6000,
bufferToKeep: BUFFER_TO_KEEP,
bufferPruningInterval: 10,
},
}
}
playerAdapter = initializeDashJsAdapter(item, mpd, settings);
})

after(() => {
playerAdapter.destroy();
})

it(`Checking playing state`, async () => {
await checkIsPlaying(playerAdapter, true);
})

it(`Checking progressing state`, async () => {
await checkIsProgressing(playerAdapter);
});

it(`Pause the playback`, async () => {
playerAdapter.pause();
await checkIsPlaying(playerAdapter, false);
await checkIsNotProgressing(playerAdapter);
});

it(`Wait for forward buffer to be filled`, async () => {
await reachedTargetForwardBuffer(playerAdapter, TARGET_BUFFER, Constants.TEST_INPUTS.BUFFER_TO_KEEP_SEEK.TOLERANCE);
});

it(`Seek to an unbuffered range`, () => {
const bufferEnd = playerAdapter.getBufferLengthByType() + playerAdapter.getCurrentTime();
const targetTime = Math.min(bufferEnd + Constants.TEST_INPUTS.BUFFER_TO_KEEP_SEEK.SEEK_OFFSET, playerAdapter.getDuration() - 10);
playerAdapter.seek(targetTime);

checkTimeWithinThresholdForDvrWindow(playerAdapter, targetTime, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE);
});

it(`Resume playback`, async () => {
playerAdapter.play()
await checkIsPlaying(playerAdapter, true);
})

it(`Checking progressing state`, async () => {
await checkIsProgressing(playerAdapter);
});

it(`Expect no critical errors to be thrown`, () => {
checkNoCriticalErrors(playerAdapter);
})
})

1 change: 0 additions & 1 deletion test/functional/test/buffer/initial-buffer-target.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import DashJsAdapter from '../../adapter/DashJsAdapter.js';
import Constants from '../../src/Constants.js';
import Utils from '../../src/Utils.js';
import {expect} from 'chai'
Expand Down
5 changes: 5 additions & 0 deletions test/functional/test/common/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export async function checkForEndedEvent(playerAdapter) {
expect(ended).to.be.true;
}

export async function reachedTargetForwardBuffer(playerAdapter, targetBuffer, tolerance) {
const reachedBuffer = await playerAdapter.reachedTargetForwardBuffer(Constants.TEST_TIMEOUT_THRESHOLDS.TARGET_BUFFER_REACHED, targetBuffer, tolerance);
expect(reachedBuffer).to.be.true;
}

export function checkLiveDelay(playerAdapter, lowerThreshold, upperThreshold) {
const liveDelay = playerAdapter.getCurrentLiveLatency();
expect(liveDelay).to.be.at.least(lowerThreshold);
Expand Down

0 comments on commit d6708a1

Please sign in to comment.