mirror of https://github.com/jitsi/jitsi-meet
ref(screenshot-capture): move screenshot processing on a web worker (#14015)
Improvement for the screenshot capture feature by using a web worker to process the differences between the screenshots, and some code adjustments.pull/14038/head jitsi-meet_9080
parent
0b6705610c
commit
11f0ab9226
File diff suppressed because it is too large
Load Diff
@ -1,40 +1,44 @@ |
||||
/** |
||||
* Percent of pixels that signal if two images should be considered different. |
||||
*/ |
||||
export const PERCENTAGE_LOWER_BOUND = 5; |
||||
export const PERCENTAGE_LOWER_BOUND = 4; |
||||
|
||||
/** |
||||
* Number of milliseconds that represent how often screenshots should be taken. |
||||
*/ |
||||
export const POLL_INTERVAL = 4000; |
||||
export const POLL_INTERVAL = 2000; |
||||
|
||||
/** |
||||
* SET_INTERVAL constant is used to set interval and it is set in |
||||
* SET_TIMEOUT constant is used to set interval and it is set in |
||||
* the id property of the request.data property. TimeMs property must |
||||
* also be set. Request.data example: |
||||
* |
||||
* { |
||||
* id: SET_INTERVAL, |
||||
* id: SET_TIMEOUT, |
||||
* timeMs: 33 |
||||
* }. |
||||
*/ |
||||
export const SET_INTERVAL = 1; |
||||
export const SET_TIMEOUT = 1; |
||||
|
||||
/** |
||||
* CLEAR_INTERVAL constant is used to clear the interval and it is set in |
||||
* CLEAR_TIMEOUT constant is used to clear the interval and it is set in |
||||
* the id property of the request.data property. |
||||
* |
||||
* { |
||||
* id: CLEAR_INTERVAL |
||||
* id: CLEAR_TIMEOUT |
||||
* }. |
||||
*/ |
||||
export const CLEAR_INTERVAL = 2; |
||||
export const CLEAR_TIMEOUT = 2; |
||||
|
||||
/** |
||||
* INTERVAL_TIMEOUT constant is used as response and it is set in the id property. |
||||
* TIMEOUT_TICK constant is used as response and it is set in the id property. |
||||
* |
||||
* { |
||||
* id: INTERVAL_TIMEOUT |
||||
* id: TIMEOUT_TICK |
||||
* }. |
||||
*/ |
||||
export const INTERVAL_TIMEOUT = 3; |
||||
export const TIMEOUT_TICK = 3; |
||||
|
||||
export const SCREENSHOT_QUEUE_LIMIT = 3; |
||||
|
||||
export const MAX_FILE_SIZE = 1000000; |
||||
|
@ -1,10 +1,10 @@ |
||||
/** |
||||
* Helper method used to process screenshots captured by the {@code ScreenshotCaptureEffect}. |
||||
* |
||||
* @param {HTMLCanvasElement} canvas - The canvas containing a screenshot to be processed. |
||||
* @param {Blob} imageBlob - The blob of the screenshot that has to be processed. |
||||
* @param {Object} options - Custom options required for processing. |
||||
* @returns {void} |
||||
* @returns {Promise<void>} |
||||
*/ |
||||
export function processScreenshot(canvas, options) { // eslint-disable-line no-unused-vars
|
||||
export async function processScreenshot(imageBlob, options) { // eslint-disable-line no-unused-vars
|
||||
return; |
||||
} |
||||
|
@ -1,29 +1,132 @@ |
||||
import pixelmatch from 'pixelmatch'; |
||||
|
||||
import { |
||||
CLEAR_INTERVAL, |
||||
INTERVAL_TIMEOUT, |
||||
SET_INTERVAL |
||||
CLEAR_TIMEOUT, |
||||
MAX_FILE_SIZE, |
||||
PERCENTAGE_LOWER_BOUND, |
||||
SET_TIMEOUT, |
||||
TIMEOUT_TICK |
||||
} from './constants'; |
||||
|
||||
const code = ` |
||||
var timer; |
||||
|
||||
onmessage = function(request) { |
||||
let timer: ReturnType<typeof setTimeout>; |
||||
const canvas = new OffscreenCanvas(0, 0); |
||||
const ctx = canvas.getContext('2d'); |
||||
let storedImageData: ImageData | undefined; |
||||
|
||||
/** |
||||
* Sends Blob with the screenshot to main thread. |
||||
* |
||||
* @param {ImageData} imageData - The image of the screenshot. |
||||
* @returns {void} |
||||
*/ |
||||
async function sendBlob(imageData: ImageData) { |
||||
let imageBlob = await canvas.convertToBlob({ type: 'image/jpeg' }); |
||||
|
||||
if (imageBlob.size > MAX_FILE_SIZE) { |
||||
const quality = Number((MAX_FILE_SIZE / imageBlob.size).toFixed(2)) * 0.92; |
||||
|
||||
imageBlob = await canvas.convertToBlob({ type: 'image/jpeg', |
||||
quality }); |
||||
} |
||||
|
||||
storedImageData = imageData; |
||||
|
||||
postMessage({ |
||||
id: TIMEOUT_TICK, |
||||
imageBlob |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Sends empty message to main thread. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
function sendEmpty() { |
||||
postMessage({ |
||||
id: TIMEOUT_TICK |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Draws the image bitmap on the canvas and checks the difference percent with the previous image |
||||
* if there is no previous image the percentage is not calculated. |
||||
* |
||||
* @param {ImageBitmap} imageBitmap - The image bitmap that is drawn on canvas. |
||||
* @returns {void} |
||||
*/ |
||||
function checkScreenshot(imageBitmap: ImageBitmap) { |
||||
const { height, width } = imageBitmap; |
||||
|
||||
if (canvas.width !== width) { |
||||
canvas.width = width; |
||||
} |
||||
|
||||
if (canvas.height !== height) { |
||||
canvas.height = height; |
||||
} |
||||
|
||||
ctx?.drawImage(imageBitmap, 0, 0, width, height); |
||||
const imageData = ctx?.getImageData(0, 0, width, height); |
||||
|
||||
imageBitmap.close(); |
||||
|
||||
if (!imageData) { |
||||
sendEmpty(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
if (!storedImageData || imageData.data.length !== storedImageData.data.length) { |
||||
sendBlob(imageData); |
||||
|
||||
return; |
||||
} |
||||
|
||||
let numOfPixels = 0; |
||||
|
||||
try { |
||||
numOfPixels = pixelmatch( |
||||
imageData.data, |
||||
storedImageData.data, |
||||
null, |
||||
width, |
||||
height); |
||||
} catch { |
||||
sendEmpty(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
const percent = numOfPixels / imageData.data.length * 100; |
||||
|
||||
if (percent >= PERCENTAGE_LOWER_BOUND) { |
||||
sendBlob(imageData); |
||||
} else { |
||||
sendEmpty(); |
||||
} |
||||
} |
||||
|
||||
onmessage = function(request) { |
||||
switch (request.data.id) { |
||||
case ${SET_INTERVAL}: { |
||||
timer = setInterval(() => { |
||||
postMessage({ id: ${INTERVAL_TIMEOUT} }); |
||||
case SET_TIMEOUT: { |
||||
timer = setTimeout(async () => { |
||||
const imageBitmap = request.data.imageBitmap; |
||||
|
||||
if (imageBitmap) { |
||||
checkScreenshot(imageBitmap); |
||||
} else { |
||||
sendEmpty(); |
||||
} |
||||
}, request.data.timeMs); |
||||
break; |
||||
} |
||||
case ${CLEAR_INTERVAL}: { |
||||
case CLEAR_TIMEOUT: { |
||||
if (timer) { |
||||
clearInterval(timer); |
||||
clearTimeout(timer); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
}; |
||||
`;
|
||||
|
||||
// @ts-ignore
|
||||
export const timerWorkerScript = URL.createObjectURL(new Blob([ code ], { type: 'application/javascript' })); |
||||
}; |
||||
|
Loading…
Reference in new issue