Commit b24bd5c0 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Rework filters.

Make it possible to attach filters dynamically.  Work around bugs
in Firefox.
parent 49bcc342
...@@ -778,22 +778,36 @@ async function setMaxVideoThroughput(c, bps) { ...@@ -778,22 +778,36 @@ async function setMaxVideoThroughput(c, bps) {
} }
} }
/**
* @typedef {Object} filterDefinition
* @property {string} [description]
* @property {string} [contextType]
* @property {(this: Filter, src: CanvasImageSource, width: number, height: number, ctx: RenderingContext) => boolean} f
*/
/** /**
* @param {MediaStream} stream * @param {MediaStream} stream
* @param {(this: Filter, src: CanvasImageSource, dest: HTMLCanvasElement) => boolean} f * @param {filterDefinition} definition
* @constructor * @constructor
*/ */
function Filter(stream, f) { function Filter(stream, definition) {
/** @ts-ignore */
if(!HTMLCanvasElement.prototype.captureStream) {
throw new Error('Filters are not supported on this platform');
}
/** @type {MediaStream} */ /** @type {MediaStream} */
this.inputStream = stream; this.inputStream = stream;
/** @type {(this: Filter, src: CanvasImageSource, dest: HTMLCanvasElement) => boolean} */ /** @type {filterDefinition} */
this.f = f; this.definition = definition;
/** @type {number} */ /** @type {number} */
this.frameRate = 30; this.frameRate = 30;
/** @type {HTMLVideoElement} */ /** @type {HTMLVideoElement} */
this.video = document.createElement('video'); this.video = document.createElement('video');
/** @type {HTMLCanvasElement} */ /** @type {HTMLCanvasElement} */
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
/** @type {any} */
this.context = this.canvas.getContext(definition.contextType || '2d');
/** @type {MediaStream} */ /** @type {MediaStream} */
this.captureStream = null; this.captureStream = null;
/** @type {MediaStream} */ /** @type {MediaStream} */
...@@ -802,9 +816,18 @@ function Filter(stream, f) { ...@@ -802,9 +816,18 @@ function Filter(stream, f) {
this.timer = null; this.timer = null;
/** @type {number} */ /** @type {number} */
this.count = 0; this.count = 0;
/** @type {boolean} */
this.fixedFramerate = false;
/** @ts-ignore */ /** @ts-ignore */
this.captureStream = this.canvas.captureStream(0); this.captureStream = this.canvas.captureStream(0);
/** @ts-ignore */
if(!this.captureStream.getTracks()[0].requestFrame) {
console.warn('captureFrame not supported, using fixed framerate');
/** @ts-ignore */
this.captureStream = this.canvas.captureStream(this.frameRate);
this.fixedFramerate = true;
}
this.outputStream = new MediaStream(); this.outputStream = new MediaStream();
this.outputStream.addTrack(this.captureStream.getTracks()[0]); this.outputStream.addTrack(this.captureStream.getTracks()[0]);
...@@ -837,10 +860,11 @@ Filter.prototype.draw = function() { ...@@ -837,10 +860,11 @@ Filter.prototype.draw = function() {
} }
this.count++; this.count++;
this.canvas.width = this.video.videoWidth; let ok = this.definition.f.call(this, this.video,
this.canvas.height = this.video.videoHeight; this.video.videoWidth,
let ok = this.f.call(this, this.video, this.canvas); this.video.videoHeight,
if(ok) { this.context);
if(ok && !this.fixedFramerate) {
/** @ts-ignore */ /** @ts-ignore */
this.captureStream.getTracks()[0].requestFrame(); this.captureStream.getTracks()[0].requestFrame();
} }
...@@ -855,20 +879,65 @@ Filter.prototype.stop = function() { ...@@ -855,20 +879,65 @@ Filter.prototype.stop = function() {
}; };
/** /**
* @type {Object.<string, ((this: Filter, src: CanvasImageSource, dest: HTMLCanvasElement) => boolean)>} * @param {Stream} c
* @param {Filter} f
*/
function setFilter(c, f) {
if(!f) {
let filter = c.userdata.filter;
if(!(filter instanceof Filter))
throw new Error('userdata.filter is not a filter');
if(c.userdata.filter) {
c.stream = c.userdata.filter.inputStream;
c.userdata.filter.stop();
c.userdata.filter = null;
}
if(!c.onclose) {
console.warn("onclose not set, this shouldn't happen");
c.onclose = null;
}
return
}
if(c.userdata.filter)
setFilter(c, null);
if(c.onclose)
throw new Error('onclose already taken');
if(f.inputStream != c.stream)
throw new Error('Setting filter for wrong stream');
c.stream = f.outputStream;
c.userdata.filter = f;
c.onclose = () => setFilter(c, null);
}
/**
* @type {Object.<string,filterDefinition>}
*/ */
let filters = { let filters = {
'mirror-h': function(video, canvas) { 'mirror-h': {
let ctx = canvas.getContext('2d'); description: "Horizontal mirror",
ctx.scale(-1, 1); f: function(src, width, height, ctx) {
ctx.drawImage(video, -canvas.width, 0); if(!(ctx instanceof CanvasRenderingContext2D))
return true; throw new Error('bad context type');
ctx.canvas.width = width;
ctx.canvas.height = height;
ctx.scale(-1, 1);
ctx.drawImage(src, -width, 0);
return true;
},
}, },
'mirror-v': function(video, canvas) { 'mirror-v': {
let ctx = canvas.getContext('2d'); description: "Vertical mirror",
ctx.scale(1, -1); f: function(src, width, height, ctx) {
ctx.drawImage(video, 0, -canvas.height); if(!(ctx instanceof CanvasRenderingContext2D))
return true; throw new Error('bad context type');
ctx.canvas.width = width;
ctx.canvas.height = height;
ctx.scale(1, -1);
ctx.drawImage(src, 0, -height);
return true;
},
}, },
}; };
...@@ -933,14 +1002,15 @@ async function addLocalMedia(id) { ...@@ -933,14 +1002,15 @@ async function addLocalMedia(id) {
let c = newUpStream(id); let c = newUpStream(id);
c.kind = 'local'; c.kind = 'local';
c.stream = stream;
if(filter) { if(filter) {
let f = new Filter(stream, filter); try {
c.stream = f.outputStream; let f = new Filter(stream, filter);
c.onclose = function() { setFilter(c, f);
f.stop(); } catch(e) {
displayWarning(e);
} }
} else {
c.stream = stream;
} }
let mute = getSettings().localMute; let mute = getSettings().localMute;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment