1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-12-22 15:25:48 +01:00

Make filters run asynchronously.

Drop a frame if a filter takes too long to run.
This commit is contained in:
Juliusz Chroboczek 2024-11-16 23:59:14 +01:00
parent 94e7e9c147
commit 38807d5f6b

View file

@ -1025,9 +1025,9 @@ function cancelReconsiderParameters() {
/** /**
* @typedef {Object} filterDefinition * @typedef {Object} filterDefinition
* @property {string} [description] * @property {string} [description]
* @property {(this: Filter) => void} [init] * @property {(this: Filter) => Promise<void>} [init]
* @property {(this: Filter) => void} [cleanup] * @property {(this: Filter) => Promise<void>} [cleanup]
* @property {(this: Filter, src: HTMLVideoElement, ctx: CanvasRenderingContext2D) => boolean} draw * @property {(this: Filter, src: HTMLVideoElement, ctx: RenderingContext) => Promise<boolean>} draw
*/ */
/** /**
@ -1067,9 +1067,11 @@ function Filter(stream, definition) {
this.userdata = {} this.userdata = {}
/** @type {MediaStream} */ /** @type {MediaStream} */
this.captureStream = this.canvas.captureStream(0); this.captureStream = this.canvas.captureStream(0);
/** @type {boolean} */
this.busy = false;
} }
Filter.prototype.start = function() { Filter.prototype.start = async function() {
/** @ts-ignore */ /** @ts-ignore */
if(!this.captureStream.getTracks()[0].requestFrame) { if(!this.captureStream.getTracks()[0].requestFrame) {
console.warn('captureFrame not supported, using fixed framerate'); console.warn('captureFrame not supported, using fixed framerate');
@ -1089,11 +1091,11 @@ Filter.prototype.start = function() {
this.video.muted = true; this.video.muted = true;
this.video.play(); this.video.play();
if(this.definition.init) if(this.definition.init)
this.definition.init.call(this); await this.definition.init.call(this);
this.timer = setInterval(() => this.draw(), 1000 / this.frameRate); this.timer = setInterval(() => this.draw(), 1000 / this.frameRate);
} }
Filter.prototype.draw = function() { Filter.prototype.draw = async function() {
if(this.video.videoWidth === 0 && this.video.videoHeight === 0) { if(this.video.videoWidth === 0 && this.video.videoHeight === 0) {
// video not started yet // video not started yet
return; return;
@ -1115,28 +1117,39 @@ Filter.prototype.draw = function() {
} }
} }
let ok = false; if(this.busy) {
try { // drop frame
ok = this.definition.draw.call(this, this.video, this.context); return;
} catch(e) {
console.error(e);
}
if(ok && !this.fixedFramerate) {
/** @ts-ignore */
this.captureStream.getTracks()[0].requestFrame();
} }
this.count++; try {
this.busy = true;
let ok = false;
try {
ok = await this.definition.draw.call(
this, this.video, this.context,
);
} catch(e) {
console.error(e);
}
if(ok && !this.fixedFramerate) {
/** @ts-ignore */
this.captureStream.getTracks()[0].requestFrame();
}
this.count++;
} finally {
this.busy = false;
}
}; };
Filter.prototype.stop = function() { Filter.prototype.stop = async function() {
if(!this.timer) if(!this.timer)
return; return;
this.captureStream.getTracks()[0].stop(); this.captureStream.getTracks()[0].stop();
clearInterval(this.timer); clearInterval(this.timer);
this.timer = null; this.timer = null;
if(this.definition.cleanup) if(this.definition.cleanup)
this.definition.cleanup.call(this); await this.definition.cleanup.call(this);
}; };
/** /**
@ -1144,7 +1157,7 @@ Filter.prototype.stop = function() {
* *
* @param {Stream} c * @param {Stream} c
*/ */
function removeFilter(c) { async function removeFilter(c) {
let old = c.userdata.filter; let old = c.userdata.filter;
if(!old) if(!old)
return; return;
@ -1153,7 +1166,7 @@ function removeFilter(c) {
throw new Error('userdata.filter is not a filter'); throw new Error('userdata.filter is not a filter');
c.setStream(old.inputStream); c.setStream(old.inputStream);
old.stop(); await old.stop();
c.userdata.filter = null; c.userdata.filter = null;
} }
@ -1162,14 +1175,14 @@ function removeFilter(c) {
* *
* @param {Stream} c * @param {Stream} c
*/ */
function setFilter(c) { async function setFilter(c) {
removeFilter(c); await removeFilter(c);
if(!c.userdata.filterDefinition) if(!c.userdata.filterDefinition)
return; return;
let filter = new Filter(c.stream, c.userdata.filterDefinition); let filter = new Filter(c.stream, c.userdata.filterDefinition);
filter.start(); await filter.start();
c.setStream(filter.outputStream); c.setStream(filter.outputStream);
c.userdata.filter = filter; c.userdata.filter = filter;
} }
@ -1180,7 +1193,7 @@ function setFilter(c) {
let filters = { let filters = {
'mirror-h': { 'mirror-h': {
description: "Horizontal mirror", description: "Horizontal mirror",
draw: function(src, ctx) { draw: async function(src, ctx) {
if(!(ctx instanceof CanvasRenderingContext2D)) if(!(ctx instanceof CanvasRenderingContext2D))
throw new Error('bad context type'); throw new Error('bad context type');
if(ctx.canvas.width !== src.videoWidth || if(ctx.canvas.width !== src.videoWidth ||
@ -1196,7 +1209,7 @@ let filters = {
}, },
'mirror-v': { 'mirror-v': {
description: "Vertical mirror", description: "Vertical mirror",
draw: function(src, ctx) { draw: async function(src, ctx) {
if(!(ctx instanceof CanvasRenderingContext2D)) if(!(ctx instanceof CanvasRenderingContext2D))
throw new Error('bad context type'); throw new Error('bad context type');
if(ctx.canvas.width !== src.videoWidth || if(ctx.canvas.width !== src.videoWidth ||
@ -1259,20 +1272,20 @@ function doSimulcast() {
* @param {MediaStream} stream * @param {MediaStream} stream
*/ */
function setUpStream(c, stream) { async function setUpStream(c, stream) {
if(c.stream != null) if(c.stream != null)
throw new Error("Setting nonempty stream"); throw new Error("Setting nonempty stream");
c.setStream(stream); c.setStream(stream);
try { try {
setFilter(c); await setFilter(c);
} catch(e) { } catch(e) {
displayWarning("Couldn't set filter: " + e); displayWarning("Couldn't set filter: " + e);
} }
c.onclose = replace => { c.onclose = async replace => {
removeFilter(c); await removeFilter(c);
if(!replace) { if(!replace) {
stopStream(c.stream); stopStream(c.stream);
if(c.userdata.onclose) if(c.userdata.onclose)
@ -1394,7 +1407,7 @@ function setUpStream(c, stream) {
* @returns {Promise<Stream>} * @returns {Promise<Stream>}
*/ */
async function replaceUpStream(c) { async function replaceUpStream(c) {
removeFilter(c); await removeFilter(c);
let cn = newUpStream(c.localId); let cn = newUpStream(c.localId);
cn.label = c.label; cn.label = c.label;
if(c.userdata.filterDefinition) if(c.userdata.filterDefinition)
@ -1403,7 +1416,7 @@ async function replaceUpStream(c) {
cn.userdata.onclose = c.userdata.onclose; cn.userdata.onclose = c.userdata.onclose;
let media = /** @type{HTMLVideoElement} */ let media = /** @type{HTMLVideoElement} */
(document.getElementById('media-' + c.localId)); (document.getElementById('media-' + c.localId));
setUpStream(cn, c.stream); await setUpStream(cn, c.stream);
await setMedia(cn, await setMedia(cn,
cn.label == 'camera' && getSettings().mirrorView, cn.label == 'camera' && getSettings().mirrorView,
cn.label == 'video' && media); cn.label == 'video' && media);
@ -1471,7 +1484,7 @@ async function addLocalMedia(localId) {
let old = serverConnection.findByLocalId(localId); let old = serverConnection.findByLocalId(localId);
if(old) { if(old) {
// make sure that the camera is released before we try to reopen it // make sure that the camera is released before we try to reopen it
removeFilter(old); await removeFilter(old);
stopStream(old.stream); stopStream(old.stream);
} }
@ -1507,7 +1520,7 @@ async function addLocalMedia(localId) {
displayWarning(`Unknown filter ${settings.filter}`); displayWarning(`Unknown filter ${settings.filter}`);
} }
setUpStream(c, stream); await setUpStream(c, stream);
await setMedia(c, settings.mirrorView); await setMedia(c, settings.mirrorView);
setButtonsVisibility(); setButtonsVisibility();
} }
@ -1546,7 +1559,7 @@ async function addShareMedia() {
let c = newUpStream(); let c = newUpStream();
c.label = 'screenshare'; c.label = 'screenshare';
setUpStream(c, stream); await setUpStream(c, stream);
await setMedia(c); await setMedia(c);
setButtonsVisibility(); setButtonsVisibility();
} }