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: more conservative stalled download check, better logging #884

Merged
merged 7 commits into from
Jul 9, 2020
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
18 changes: 10 additions & 8 deletions src/playback-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export default class PlaybackWatcher {
const loader = this.masterPlaylistController_[`${type}SegmentLoader_`];

if (this[`${type}StalledDownloads_`] > 0) {
this.logger_(`resetting stalled downloads for ${type} loader`);
this.logger_(`resetting possible stalled download count for ${type} loader`);
}
this[`${type}StalledDownloads_`] = 0;
this[`${type}Buffered_`] = loader.buffered_();
Expand Down Expand Up @@ -204,16 +204,18 @@ export default class PlaybackWatcher {

this[`${type}StalledDownloads_`]++;

this.logger_(`found stalled download #${this[`${type}StalledDownloads_`]} for ${type} loader`);
// We will technically get past this on the fourth bad append
// rather than the third. As the first will almost always cause
// buffered to change which means that StalledDownloads_ will
// not be incremented
if (this[`${type}StalledDownloads_`] < 3) {
this.logger_(`found #${this[`${type}StalledDownloads_`]} ${type} appends that did not increase buffer (possible stalled download)`, {
playlistId: loader.playlist_ && loader.playlist_.id,
buffered: Ranges.timeRangesToArray(buffered)

});

// after 10 possibly stalled appends with no reset, exclude
if (this[`${type}StalledDownloads_`] < 10) {
return;
}

this.logger_(`${type} loader download exclusion`);
this.logger_(`${type} loader stalled download exclusion`);
this.resetSegmentDownloads_(type);
this.tech_.trigger({type: 'usage', name: `vhs-${type}-download-exclusion`});

Expand Down
3 changes: 2 additions & 1 deletion src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2481,7 +2481,6 @@ export default class SegmentLoader extends videojs.EventTarget {
* @private
*/
handleAppendsDone_() {
this.trigger('appendsdone');
if (!this.pendingSegment_) {
this.state = 'READY';
// TODO should this move into this.checkForAbort to speed up requests post abort in
Expand All @@ -2492,6 +2491,8 @@ export default class SegmentLoader extends videojs.EventTarget {
return;
}

this.trigger('appendsdone');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't report aborted appends as appends

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you know in what instances we will abort appends? It seems like it would only happen on dispose of the source buffers, otherwise appends will continue even if the current segment loader process is aborted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's the request itself in this case that is aborted, rather than an actual append. I was seeing it with aggressive network throttling and changing renditions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Might be worth us adding a test for this case.


const segmentInfo = this.pendingSegment_;

// Now that the end of the segment has been reached, we can set the end time. It's
Expand Down
42 changes: 22 additions & 20 deletions test/playback-watcher.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,8 @@ QUnit.test('jumps to buffered content if seeking just before', function(assert)

const loaderTypes = ['audio', 'main', 'subtitle'];

const EXCLUDE_APPEND_COUNT = 10;

QUnit.module('PlaybackWatcher download detection', {
beforeEach(assert) {
this.env = useFakeEnvironment(assert);
Expand Down Expand Up @@ -872,17 +874,15 @@ loaderTypes.forEach(function(type) {
}

this.setBuffered(videojs.createTimeRanges([[0, 30]]));
loader.trigger('appendsdone');
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 0, '1st append 0 stalled downloads');

loader.trigger('appendsdone');
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 1, '2nd append 1 stalled downloads');

loader.trigger('appendsdone');
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 2, '3rd append 2 stalled downloads');

loader.trigger('appendsdone');
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 0, '4th append 0 stalled downloads');
for (let i = 0; i <= EXCLUDE_APPEND_COUNT; i++) {
loader.trigger('appendsdone');
if (i === EXCLUDE_APPEND_COUNT) {
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 0, `append #${i} resets stalled downloads to 0`);
} else {
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], i, `append #${i + 1} ${i} stalled downloads`);
}
}

const expectedUsage = {};

Expand Down Expand Up @@ -927,16 +927,18 @@ loaderTypes.forEach(function(type) {
const loader = this.mpc[`${type}SegmentLoader_`];
const playlists = this.mpc.master().playlists;
const excludeAndVerify = () => {
loader.trigger('appendsdone');
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 1, '1st append 1 stalled downloads');

loader.trigger('appendsdone');
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 2, '2nd append 1 stalled downloads');

const oldPlaylist = this.mpc.media();

loader.trigger('appendsdone');
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 0, '3rd append 0 stalled downloads');
let oldPlaylist;
// this test only needs 9 appends, since we do an intial append

for (let i = 0; i < EXCLUDE_APPEND_COUNT; i++) {
oldPlaylist = this.mpc.media();
loader.trigger('appendsdone');
if (i === EXCLUDE_APPEND_COUNT - 1) {
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], 0, `append #${i} resets stalled downloads to 0`);
} else {
assert.equal(this.playbackWatcher[`${type}StalledDownloads_`], i + 1, `append #${i + 1} ${i + 1} stalled downloads`);
}
}

const expectedUsage = {};

Expand Down
77 changes: 77 additions & 0 deletions test/segment-loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,83 @@ QUnit.module('SegmentLoader', function(hooks) {
});
});

QUnit.test('appendsdone happens after appends complete', function(assert) {
const done = assert.async();

return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
let appendsdone = false;

loader.playlist(playlistWithDuration(20));
loader.load();
this.clock.tick(1);

standardXHRResponse(this.requests.shift(), muxedSegment());
loader.one('appendsdone', () => {
appendsdone = true;
});

let appends = 0;

const finish = function() {
appends++;

if (appends < 2) {
return;
}

assert.ok(appendsdone, 'appendsdone triggered');
done();
};

loader.sourceUpdater_.videoBuffer.addEventListener('updateend', () => {
loader.sourceUpdater_.videoQueueCallback(finish);
});

loader.sourceUpdater_.audioBuffer.addEventListener('updateend', () => {
loader.sourceUpdater_.audioQueueCallback(finish);
});
});
});

QUnit.test('appendsdone does not happen after abort during append', function(assert) {
const done = assert.async();

return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
let appendsdone = false;

loader.playlist(playlistWithDuration(20));
loader.load();
this.clock.tick(1);

standardXHRResponse(this.requests.shift(), muxedSegment());
loader.one('appendsdone', () => {
appendsdone = true;
});

loader.one('appending', () => {
loader.abort();
});

let appends = 0;

const finish = function() {
appends++;

if (appends < 2) {
return;
}

assert.notOk(appendsdone, 'appendsdone triggered');
done();
};

loader.sourceUpdater_.videoBuffer.addEventListener('updateend', () => {
loader.sourceUpdater_.videoQueueCallback(finish);
loader.sourceUpdater_.audioQueueCallback(finish);
});
});
});

QUnit.test('audio loader waits to request segment until it has enough info', function(assert) {
loader.dispose();
loader = new SegmentLoader(LoaderCommonSettings.call(this, {
Expand Down