mirror of
https://github.com/videojs/video.js.git
synced 2024-12-12 11:15:04 +02:00
feat: Greater text track precision using requestVideoFrameCallback (#7633)
This commit is contained in:
parent
64e55f5492
commit
1179826cbc
@ -741,6 +741,32 @@ class Html5 extends Tech {
|
|||||||
return this.el_.requestPictureInPicture();
|
return this.el_.requestPictureInPicture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native requestVideoFrameCallback if supported by browser/tech, or fallback
|
||||||
|
*
|
||||||
|
* @param {function} cb function to call
|
||||||
|
* @return {number} id of request
|
||||||
|
*/
|
||||||
|
requestVideoFrameCallback(cb) {
|
||||||
|
if (this.featuresVideoFrameCallback) {
|
||||||
|
return this.el_.requestVideoFrameCallback(cb);
|
||||||
|
}
|
||||||
|
return super.requestVideoFrameCallback(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native or fallback requestVideoFrameCallback
|
||||||
|
*
|
||||||
|
* @param {number} id request id to cancel
|
||||||
|
*/
|
||||||
|
cancelVideoFrameCallback(id) {
|
||||||
|
if (this.featuresVideoFrameCallback) {
|
||||||
|
this.el_.cancelVideoFrameCallback(id);
|
||||||
|
} else {
|
||||||
|
super.cancelVideoFrameCallback(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A getter/setter for the `Html5` Tech's source object.
|
* A getter/setter for the `Html5` Tech's source object.
|
||||||
* > Note: Please use {@link Html5#setSource}
|
* > Note: Please use {@link Html5#setSource}
|
||||||
@ -1299,6 +1325,13 @@ Html5.prototype.featuresProgressEvents = true;
|
|||||||
*/
|
*/
|
||||||
Html5.prototype.featuresTimeupdateEvents = true;
|
Html5.prototype.featuresTimeupdateEvents = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the HTML5 el supports `requestVideoFrameCallback`
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
Html5.prototype.featuresVideoFrameCallback = !!(Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback);
|
||||||
|
|
||||||
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
||||||
let canPlayType;
|
let canPlayType;
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import {isPlain} from '../utils/obj';
|
|||||||
import * as TRACK_TYPES from '../tracks/track-types';
|
import * as TRACK_TYPES from '../tracks/track-types';
|
||||||
import {toTitleCase, toLowerCase} from '../utils/string-cases.js';
|
import {toTitleCase, toLowerCase} from '../utils/string-cases.js';
|
||||||
import vtt from 'videojs-vtt.js';
|
import vtt from 'videojs-vtt.js';
|
||||||
|
import * as Guid from '../utils/guid.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
|
* An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
|
||||||
@ -103,6 +104,8 @@ class Tech extends Component {
|
|||||||
this.stopTrackingCurrentTime_ = (e) => this.stopTrackingCurrentTime(e);
|
this.stopTrackingCurrentTime_ = (e) => this.stopTrackingCurrentTime(e);
|
||||||
this.disposeSourceHandler_ = (e) => this.disposeSourceHandler(e);
|
this.disposeSourceHandler_ = (e) => this.disposeSourceHandler(e);
|
||||||
|
|
||||||
|
this.queuedHanders_ = new Set();
|
||||||
|
|
||||||
// keep track of whether the current source has played at all to
|
// keep track of whether the current source has played at all to
|
||||||
// implement a very limited played()
|
// implement a very limited played()
|
||||||
this.hasStarted_ = false;
|
this.hasStarted_ = false;
|
||||||
@ -857,6 +860,43 @@ class Tech extends Component {
|
|||||||
*/
|
*/
|
||||||
setDisablePictureInPicture() {}
|
setDisablePictureInPicture() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fallback implementation of requestVideoFrameCallback using requestAnimationFrame
|
||||||
|
*
|
||||||
|
* @param {function} cb
|
||||||
|
* @return {number} request id
|
||||||
|
*/
|
||||||
|
requestVideoFrameCallback(cb) {
|
||||||
|
const id = Guid.newGUID();
|
||||||
|
|
||||||
|
if (this.paused()) {
|
||||||
|
this.queuedHanders_.add(id);
|
||||||
|
this.one('playing', () => {
|
||||||
|
if (this.queuedHanders_.has(id)) {
|
||||||
|
this.queuedHanders_.delete(id);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.requestNamedAnimationFrame(id, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fallback implementation of cancelVideoFrameCallback
|
||||||
|
*
|
||||||
|
* @param {number} id id of callback to be cancelled
|
||||||
|
*/
|
||||||
|
cancelVideoFrameCallback(id) {
|
||||||
|
if (this.queuedHanders_.has(id)) {
|
||||||
|
this.queuedHanders_.delete(id);
|
||||||
|
} else {
|
||||||
|
this.cancelNamedAnimationFrame(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A method to set a poster from a `Tech`.
|
* A method to set a poster from a `Tech`.
|
||||||
*
|
*
|
||||||
@ -1171,6 +1211,14 @@ Tech.prototype.featuresTimeupdateEvents = false;
|
|||||||
*/
|
*/
|
||||||
Tech.prototype.featuresNativeTextTracks = false;
|
Tech.prototype.featuresNativeTextTracks = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean indicating whether the `Tech` supports `requestVideoFrameCallback`.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
Tech.prototype.featuresVideoFrameCallback = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A functional mixin for techs that want to use the Source Handler pattern.
|
* A functional mixin for techs that want to use the Source Handler pattern.
|
||||||
* Source handlers are scripts for handling specific formats.
|
* Source handlers are scripts for handling specific formats.
|
||||||
|
@ -183,11 +183,18 @@ class TextTrack extends Track {
|
|||||||
const cues = new TextTrackCueList(this.cues_);
|
const cues = new TextTrackCueList(this.cues_);
|
||||||
const activeCues = new TextTrackCueList(this.activeCues_);
|
const activeCues = new TextTrackCueList(this.activeCues_);
|
||||||
let changed = false;
|
let changed = false;
|
||||||
const timeupdateHandler = Fn.bind(this, function() {
|
|
||||||
if (!this.tech_.isReady_ || this.tech_.isDisposed()) {
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
this.timeupdateHandler = Fn.bind(this, function() {
|
||||||
|
if (this.tech_.isDisposed()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.tech_.isReady_) {
|
||||||
|
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Accessing this.activeCues for the side-effects of updating itself
|
// Accessing this.activeCues for the side-effects of updating itself
|
||||||
// due to its nature as a getter function. Do not remove or cues will
|
// due to its nature as a getter function. Do not remove or cues will
|
||||||
// stop updating!
|
// stop updating!
|
||||||
@ -197,15 +204,18 @@ class TextTrack extends Track {
|
|||||||
this.trigger('cuechange');
|
this.trigger('cuechange');
|
||||||
changed = false;
|
changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const disposeHandler = () => {
|
const disposeHandler = () => {
|
||||||
this.tech_.off('timeupdate', timeupdateHandler);
|
this.stopTracking();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.tech_.one('dispose', disposeHandler);
|
this.tech_.one('dispose', disposeHandler);
|
||||||
if (mode !== 'disabled') {
|
if (mode !== 'disabled') {
|
||||||
this.tech_.on('timeupdate', timeupdateHandler);
|
this.startTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
@ -251,10 +261,10 @@ class TextTrack extends Track {
|
|||||||
// On-demand load.
|
// On-demand load.
|
||||||
loadTrack(this.src, this);
|
loadTrack(this.src, this);
|
||||||
}
|
}
|
||||||
this.tech_.off('timeupdate', timeupdateHandler);
|
this.stopTracking();
|
||||||
|
|
||||||
if (mode !== 'disabled') {
|
if (mode !== 'disabled') {
|
||||||
this.tech_.on('timeupdate', timeupdateHandler);
|
this.startTracking();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* An event that fires when mode changes on this track. This allows
|
* An event that fires when mode changes on this track. This allows
|
||||||
@ -357,6 +367,17 @@ class TextTrack extends Track {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTracking() {
|
||||||
|
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTracking() {
|
||||||
|
if (this.rvf_) {
|
||||||
|
this.tech_.cancelVideoFrameCallback(this.rvf_);
|
||||||
|
this.rvf_ = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a cue to the internal list of cues.
|
* Add a cue to the internal list of cues.
|
||||||
*
|
*
|
||||||
|
@ -255,6 +255,7 @@ QUnit.test('can only remove one cue at a time', function(assert) {
|
|||||||
|
|
||||||
QUnit.test('does not fire cuechange before Tech is ready', function(assert) {
|
QUnit.test('does not fire cuechange before Tech is ready', function(assert) {
|
||||||
const done = assert.async();
|
const done = assert.async();
|
||||||
|
const clock = sinon.useFakeTimers();
|
||||||
const player = TestHelpers.makePlayer({techfaker: {autoReady: false}});
|
const player = TestHelpers.makePlayer({techfaker: {autoReady: false}});
|
||||||
let changes = 0;
|
let changes = 0;
|
||||||
const tt = new TextTrack({
|
const tt = new TextTrack({
|
||||||
@ -278,7 +279,7 @@ QUnit.test('does not fire cuechange before Tech is ready', function(assert) {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
assert.equal(changes, 0, 'a cuechange event is not triggered');
|
assert.equal(changes, 0, 'a cuechange event is not triggered');
|
||||||
|
|
||||||
player.tech_.on('ready', function() {
|
player.tech_.on('ready', function() {
|
||||||
@ -286,15 +287,18 @@ QUnit.test('does not fire cuechange before Tech is ready', function(assert) {
|
|||||||
return 0.2;
|
return 0.2;
|
||||||
};
|
};
|
||||||
|
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
clock.tick(1);
|
||||||
|
|
||||||
assert.equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');
|
assert.equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');
|
||||||
|
|
||||||
tt.off();
|
tt.off();
|
||||||
player.dispose();
|
player.dispose();
|
||||||
|
clock.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
player.tech_.triggerReady();
|
player.tech_.triggerReady();
|
||||||
|
clock.tick(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('fires cuechange when cues become active and inactive', function(assert) {
|
QUnit.test('fires cuechange when cues become active and inactive', function(assert) {
|
||||||
@ -321,7 +325,7 @@ QUnit.test('fires cuechange when cues become active and inactive', function(asse
|
|||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
|
||||||
assert.equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');
|
assert.equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');
|
||||||
|
|
||||||
@ -329,7 +333,7 @@ QUnit.test('fires cuechange when cues become active and inactive', function(asse
|
|||||||
return 7;
|
return 7;
|
||||||
};
|
};
|
||||||
|
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
|
||||||
assert.equal(changes, 4, 'a cuechange event trigger addEventListener and oncuechange');
|
assert.equal(changes, 4, 'a cuechange event trigger addEventListener and oncuechange');
|
||||||
|
|
||||||
@ -360,17 +364,18 @@ QUnit.test('enabled and disabled cuechange handler when changing mode to hidden'
|
|||||||
player.tech_.currentTime = function() {
|
player.tech_.currentTime = function() {
|
||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
|
||||||
assert.equal(changes, 1, 'a cuechange event trigger');
|
assert.equal(changes, 1, 'a cuechange event trigger');
|
||||||
|
|
||||||
changes = 0;
|
changes = 0;
|
||||||
|
// debugger;
|
||||||
tt.mode = 'disabled';
|
tt.mode = 'disabled';
|
||||||
|
|
||||||
player.tech_.currentTime = function() {
|
player.tech_.currentTime = function() {
|
||||||
return 7;
|
return 7;
|
||||||
};
|
};
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
|
||||||
assert.equal(changes, 0, 'NO cuechange event trigger');
|
assert.equal(changes, 0, 'NO cuechange event trigger');
|
||||||
|
|
||||||
@ -379,6 +384,7 @@ QUnit.test('enabled and disabled cuechange handler when changing mode to hidden'
|
|||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('enabled and disabled cuechange handler when changing mode to showing', function(assert) {
|
QUnit.test('enabled and disabled cuechange handler when changing mode to showing', function(assert) {
|
||||||
|
const clock = sinon.useFakeTimers();
|
||||||
const player = TestHelpers.makePlayer();
|
const player = TestHelpers.makePlayer();
|
||||||
let changes = 0;
|
let changes = 0;
|
||||||
const tt = new TextTrack({
|
const tt = new TextTrack({
|
||||||
@ -401,7 +407,8 @@ QUnit.test('enabled and disabled cuechange handler when changing mode to showing
|
|||||||
player.tech_.currentTime = function() {
|
player.tech_.currentTime = function() {
|
||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
clock.tick(10);
|
||||||
|
|
||||||
assert.equal(changes, 1, 'a cuechange event trigger');
|
assert.equal(changes, 1, 'a cuechange event trigger');
|
||||||
|
|
||||||
@ -411,12 +418,13 @@ QUnit.test('enabled and disabled cuechange handler when changing mode to showing
|
|||||||
player.tech_.currentTime = function() {
|
player.tech_.currentTime = function() {
|
||||||
return 7;
|
return 7;
|
||||||
};
|
};
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
|
||||||
assert.equal(changes, 0, 'NO cuechange event trigger');
|
assert.equal(changes, 0, 'NO cuechange event trigger');
|
||||||
|
|
||||||
tt.off();
|
tt.off();
|
||||||
player.dispose();
|
player.dispose();
|
||||||
|
clock.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('if preloadTextTracks is false, default tracks are not parsed until mode is showing', function(assert) {
|
QUnit.test('if preloadTextTracks is false, default tracks are not parsed until mode is showing', function(assert) {
|
||||||
|
@ -338,7 +338,7 @@ QUnit.test('no lang attribute on cue elements if one is provided', function(asse
|
|||||||
player.tech_.textTracks().addTrack(tt);
|
player.tech_.textTracks().addTrack(tt);
|
||||||
|
|
||||||
player.currentTime(2);
|
player.currentTime(2);
|
||||||
player.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
|
||||||
assert.notOk(tt.activeCues[0].displayState.hasAttribute('lang'), 'no lang attribute should be set');
|
assert.notOk(tt.activeCues[0].displayState.hasAttribute('lang'), 'no lang attribute should be set');
|
||||||
|
|
||||||
@ -361,7 +361,7 @@ QUnit.test('set lang attribute on cue elements if one is provided', function(ass
|
|||||||
player.tech_.textTracks().addTrack(tt);
|
player.tech_.textTracks().addTrack(tt);
|
||||||
|
|
||||||
player.currentTime(2);
|
player.currentTime(2);
|
||||||
player.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
|
|
||||||
assert.equal(tt.activeCues[0].displayState.getAttribute('lang'), 'en', 'the lang should be set to en');
|
assert.equal(tt.activeCues[0].displayState.getAttribute('lang'), 'en', 'the lang should be set to en');
|
||||||
|
|
||||||
@ -404,7 +404,7 @@ QUnit.test('removes cuechange event when text track is hidden for emulated track
|
|||||||
player.tech_.currentTime = function() {
|
player.tech_.currentTime = function() {
|
||||||
return 3;
|
return 3;
|
||||||
};
|
};
|
||||||
player.tech_.trigger('timeupdate');
|
player.tech_.trigger('playing');
|
||||||
assert.equal(
|
assert.equal(
|
||||||
numTextTrackChanges, 3,
|
numTextTrackChanges, 3,
|
||||||
'texttrackchange should be triggered once for the cuechange'
|
'texttrackchange should be triggered once for the cuechange'
|
||||||
|
Loading…
Reference in New Issue
Block a user