123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
-
- import { drawBokehEffect } from '@tensorflow-models/body-pix';
-
- import {
- CLEAR_INTERVAL,
- INTERVAL_TIMEOUT,
- SET_INTERVAL,
- timerWorkerScript
- } from './TimerWorker';
-
- /**
- * Represents a modified MediaStream that adds blur to video background.
- * <tt>JitsiStreamBlurEffect</tt> does the processing of the original
- * video stream.
- */
- export default class JitsiStreamBlurEffect {
- /**
- * Represents a modified video MediaStream track.
- *
- * @class
- * @param {BodyPix} bpModel - BodyPix model.
- */
- constructor(bpModel) {
- this._bpModel = bpModel;
-
- // Bind event handler so it is only bound once for every instance.
- this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
- this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
-
- this._outputCanvasElement = document.createElement('canvas');
-
- // Workaround for FF issue https://bugzilla.mozilla.org/show_bug.cgi?id=1388974
- this._outputCanvasElement.getContext('2d');
-
- this._maskCanvasElement = document.createElement('canvas');
- this._inputVideoElement = document.createElement('video');
-
- this._videoFrameTimerWorker = new Worker(timerWorkerScript);
- this._maskFrameTimerWorker = new Worker(timerWorkerScript);
- this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
- this._maskFrameTimerWorker.onmessage = this._onMaskFrameTimer;
- }
-
- /**
- * EventHandler onmessage for the videoFrameTimerWorker WebWorker.
- *
- * @private
- * @param {EventHandler} response - The onmessage EventHandler parameter.
- * @returns {void}
- */
- _onVideoFrameTimer(response) {
- if (response.data.id === INTERVAL_TIMEOUT) {
- this._renderVideo();
- }
- }
-
- /**
- * EventHandler onmessage for the maskFrameTimerWorker WebWorker.
- *
- * @private
- * @param {EventHandler} response - The onmessage EventHandler parameter.
- * @returns {void}
- */
- _onMaskFrameTimer(response) {
- if (response.data.id === INTERVAL_TIMEOUT) {
- this._renderMask();
- }
- }
-
- /**
- * Starts loop to capture video frame and render the segmentation mask.
- *
- * @param {MediaStream} stream - Stream to be used for processing.
- * @returns {MediaStream} - The stream with the applied effect.
- */
- startEffect(stream) {
- const firstVideoTrack = stream.getVideoTracks()[0];
- const { height, frameRate, width }
- = firstVideoTrack.getSettings ? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
-
- this._frameRate = frameRate;
- this._height = height;
- this._width = width;
-
- this._outputCanvasElement.width = width;
- this._outputCanvasElement.height = height;
-
- this._maskCanvasElement.width = width;
- this._maskCanvasElement.height = height;
-
- this._maskCanvasContext = this._maskCanvasElement.getContext('2d');
- this._inputVideoElement.width = width;
- this._inputVideoElement.height = height;
- this._inputVideoElement.autoplay = true;
- this._inputVideoElement.srcObject = stream;
-
- this._videoFrameTimerWorker.postMessage({
- id: SET_INTERVAL,
- timeMs: 1000 / this._frameRate
- });
- this._maskFrameTimerWorker.postMessage({
- id: SET_INTERVAL,
- timeMs: 50
- });
-
- return this._outputCanvasElement.captureStream(this._frameRate);
- }
-
- /**
- * Stops the capture and render loop.
- *
- * @returns {void}
- */
- stopEffect() {
- this._videoFrameTimerWorker.postMessage({
- id: CLEAR_INTERVAL
- });
- this._maskFrameTimerWorker.postMessage({
- id: CLEAR_INTERVAL
- });
- }
-
- /**
- * Loop function to render the video frame input and draw blur effect.
- *
- * @private
- * @returns {void}
- */
- _renderVideo() {
- this._maskCanvasContext.drawImage(this._inputVideoElement, 0, 0, this._width, this._height);
- if (this._segmentationData) {
- drawBokehEffect(
- this._outputCanvasElement,
- this._inputVideoElement,
- this._segmentationData,
- 7, // Constant for background blur, integer values between 0-20
- 7 // Constant for edge blur, integer values between 0-20
- );
-
- // Make sure we clear this buffer before feeding the segmentation data
- // to drawBokehEffect for creating the blur. This fixes the memory leak
- // that started happening in WebGL in Chrome 77 and up.
- this._segmentationData = null;
- }
- }
-
- /**
- * Loop function to render the background mask.
- *
- * @private
- * @returns {void}
- */
- _renderMask() {
- this._bpModel.estimatePersonSegmentation(
- this._maskCanvasElement,
- 32, // Chose 32 for better performance
- 0.75 // Represents probability that a pixel belongs to a person
- )
- .then(value => {
- this._segmentationData = value;
- });
- }
-
- /**
- * Checks if the local track supports this effect.
- *
- * @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect.
- * @returns {boolean} - Returns true if this effect can run on the specified track
- * false otherwise.
- */
- isEnabled(jitsiLocalTrack) {
- return jitsiLocalTrack.isVideoTrack() && jitsiLocalTrack.videoType === 'camera';
- }
- }
-
|