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. |
* 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. |
* 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 |
* the id property of the request.data property. TimeMs property must |
||||||
* also be set. Request.data example: |
* also be set. Request.data example: |
||||||
* |
* |
||||||
* { |
* { |
||||||
* id: SET_INTERVAL, |
* id: SET_TIMEOUT, |
||||||
* timeMs: 33 |
* 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. |
* 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}. |
* 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. |
* @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; |
return; |
||||||
} |
} |
||||||
|
@ -1,29 +1,132 @@ |
|||||||
|
import pixelmatch from 'pixelmatch'; |
||||||
|
|
||||||
import { |
import { |
||||||
CLEAR_INTERVAL, |
CLEAR_TIMEOUT, |
||||||
INTERVAL_TIMEOUT, |
MAX_FILE_SIZE, |
||||||
SET_INTERVAL |
PERCENTAGE_LOWER_BOUND, |
||||||
|
SET_TIMEOUT, |
||||||
|
TIMEOUT_TICK |
||||||
} from './constants'; |
} 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) { |
switch (request.data.id) { |
||||||
case ${SET_INTERVAL}: { |
case SET_TIMEOUT: { |
||||||
timer = setInterval(() => { |
timer = setTimeout(async () => { |
||||||
postMessage({ id: ${INTERVAL_TIMEOUT} }); |
const imageBitmap = request.data.imageBitmap; |
||||||
|
|
||||||
|
if (imageBitmap) { |
||||||
|
checkScreenshot(imageBitmap); |
||||||
|
} else { |
||||||
|
sendEmpty(); |
||||||
|
} |
||||||
}, request.data.timeMs); |
}, request.data.timeMs); |
||||||
break; |
break; |
||||||
} |
} |
||||||
case ${CLEAR_INTERVAL}: { |
case CLEAR_TIMEOUT: { |
||||||
if (timer) { |
if (timer) { |
||||||
clearInterval(timer); |
clearTimeout(timer); |
||||||
} |
} |
||||||
break; |
break; |
||||||
} |
} |
||||||
} |
} |
||||||
}; |
}; |
||||||
`;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
export const timerWorkerScript = URL.createObjectURL(new Blob([ code ], { type: 'application/javascript' })); |
|
||||||
|
Loading…
Reference in new issue