1
0
mirror of https://github.com/videojs/video.js.git synced 2025-07-15 01:34:23 +02:00

feat: Queue playback events when the playback rate is zero and we are seeking (#5024)

SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).

From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
This commit is contained in:
Chuck Wilson
2018-04-17 15:28:05 -04:00
committed by Gary Katsevman
parent 62c1477615
commit a2851fe4bd
4 changed files with 198 additions and 34 deletions

View File

@ -14,6 +14,20 @@ import TextTrackList from '../../../src/js/tracks/text-track-list';
import sinon from 'sinon';
import log from '../../../src/js/utils/log.js';
function stubbedSourceHandler(handler) {
return {
canPlayType() {
return true;
},
canHandleSource() {
return true;
},
handleSource(source, tech, options) {
return handler;
}
};
}
QUnit.module('Media Tech', {
beforeEach(assert) {
this.noop = function() {};
@ -474,34 +488,40 @@ QUnit.test('should track whether a video has played', function(assert) {
assert.equal(tech.played().length, 1, 'has length after playing');
});
QUnit.test('delegates seekable to the source handler', function(assert) {
QUnit.test('delegates deferrables to the source handler', function(assert) {
const MyTech = extendFn(Tech, {
seekable() {
throw new Error('You should not be calling me!');
},
seeking() {
throw new Error('You should not be calling me!');
},
duration() {
throw new Error('You should not be calling me!');
}
});
Tech.withSourceHandlers(MyTech);
let seekableCount = 0;
let seekingCount = 0;
let durationCount = 0;
const handler = {
seekable() {
seekableCount++;
return createTimeRange(0, 0);
},
seeking() {
seekingCount++;
return false;
},
duration() {
durationCount++;
return 0;
}
};
MyTech.registerSourceHandler({
canPlayType() {
return true;
},
canHandleSource() {
return true;
},
handleSource(source, tech, options) {
return handler;
}
});
MyTech.registerSourceHandler(stubbedSourceHandler(handler));
const tech = new MyTech();
@ -510,7 +530,57 @@ QUnit.test('delegates seekable to the source handler', function(assert) {
type: 'video/mp4'
});
tech.seekable();
tech.seeking();
tech.duration();
assert.equal(seekableCount, 1, 'called the source handler');
assert.equal(seekingCount, 1, 'called the source handler');
assert.equal(durationCount, 1, 'called the source handler');
});
QUnit.test('delegates only deferred deferrables to the source handler', function(assert) {
let seekingCount = 0;
const MyTech = extendFn(Tech, {
seekable() {
throw new Error('You should not be calling me!');
},
seeking() {
seekingCount++;
return false;
},
duration() {
throw new Error('You should not be calling me!');
}
});
Tech.withSourceHandlers(MyTech);
let seekableCount = 0;
let durationCount = 0;
const handler = {
seekable() {
seekableCount++;
return createTimeRange(0, 0);
},
duration() {
durationCount++;
return 0;
}
};
MyTech.registerSourceHandler(stubbedSourceHandler(handler));
const tech = new MyTech();
tech.setSource({
src: 'example.mp4',
type: 'video/mp4'
});
tech.seekable();
tech.seeking();
tech.duration();
assert.equal(seekableCount, 1, 'called the source handler');
assert.equal(seekingCount, 1, 'called the tech itself');
assert.equal(durationCount, 1, 'called the source handler');
});
QUnit.test('Tech.isTech returns correct answers for techs and components', function(assert) {