Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to offscreen canvas in setDesktopwithCameraSource #520

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions karma.conf.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ module.exports = function(config) {

],

proxies: {
'/volume-meter-processor.js': '/base/src/main/js/volume-meter-processor.js'
proxies: {'/volume-meter-processor.js': '/base/src/main/js/volume-meter-processor.js',
'/draw-desktop-with-camera-source-worker.js': '/base/src/main/js/draw-desktop-with-camera-source-worker.js'
},

reporters: ['progress', 'coverage'],
Expand Down
1 change: 1 addition & 0 deletions rollup.config.module.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const builds = {
'src/main/js/utility.js',
'src/main/js/media_manager.js',
'src/main/js/stream_merger.js',
'src/main/js/draw-desktop-with-camera-source-worker.js',
],
output: [{
dir: 'dist',
Expand Down
67 changes: 67 additions & 0 deletions src/main/js/draw-desktop-with-camera-source-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
let canvas, ctx;
let canvasWidth = 1920;
let canvasHeight = 1080;
let overlayWidth, overlayHeight;

// Handle messages from the main thread
self.onmessage = function (event) {
const { type, data } = event.data;

switch (type) {
case 'init':
initializeCanvas(data.canvas, data.width, data.height);
break;
case 'frame':
drawFrame(data.screenFrame, data.cameraFrame);
break;
case 'resize':
resizeCanvas(data.width, data.height);
break;
default:
console.warn('Unknown message type:', type);
}
};

function initializeCanvas(offscreenCanvas, width, height) {
canvas = offscreenCanvas;
ctx = canvas.getContext('2d');
canvasWidth = width;
canvasHeight = height;

canvas.width = canvasWidth;
canvas.height = canvasHeight;

overlayWidth = canvasWidth / 4;
console.log('Canvas initialized in worker.');
}

function resizeCanvas(width, height) {
canvasWidth = width;
canvasHeight = height;
canvas.width = canvasWidth;
canvas.height = canvasHeight;

overlayWidth = canvasWidth / 4;
console.log('Canvas resized in worker:', canvasWidth, canvasHeight);
}

function drawFrame(screenFrame, cameraFrame) {
if (!ctx) return;

// Clear the canvas
ctx.clearRect(0, 0, canvasWidth, canvasHeight);

// Draw the screen video frame
ctx.drawImage(screenFrame, 0, 0, canvasWidth, canvasHeight);

// Draw the camera overlay
const overlayHeight = overlayWidth * (cameraFrame.height / cameraFrame.width);
const positionX = canvasWidth - overlayWidth - 20;
const positionY = canvasHeight - overlayHeight - 20;

ctx.drawImage(cameraFrame, positionX, positionY, overlayWidth, overlayHeight);

// Release ImageBitmap resources
screenFrame.close();
cameraFrame.close();
}
110 changes: 68 additions & 42 deletions src/main/js/media_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,63 +354,89 @@
*/
setDesktopwithCameraSource(stream, streamId, onEndedCallback) {
this.desktopStream = stream;
return this.navigatorUserMedia({video: true, audio: false}, cameraStream => {
this.smallVideoTrack = cameraStream.getVideoTracks()[0];

//create a canvas element
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
return this.navigatorUserMedia({ video: true, audio: false }, cameraStream => {
this.smallVideoTrack = cameraStream.getVideoTracks()[0];

//create video element for screen
//var screenVideo = document.getElementById('sourceVideo');
var screenVideo = document.createElement('video');
// Create OffscreenCanvas
const canvas = document.createElement("canvas");
const offscreenCanvas = canvas.transferControlToOffscreen();

// Create video elements for streams
const screenVideo = document.createElement("video");
screenVideo.srcObject = stream;
screenVideo.play();
//create video element for camera
var cameraVideo = document.createElement('video');

const cameraVideo = document.createElement("video");
cameraVideo.srcObject = cameraStream;
cameraVideo.play();
var canvasStream = canvas.captureStream(15);

if (onEndedCallback != null) {
stream.getVideoTracks()[0].onended = function (event) {
// Start playing videos only after metadata is loaded
const setupVideo = video =>
new Promise(resolve => {
video.onloadedmetadata = () => {
video.play();
resolve(video);

Check warning on line 377 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L376-L377

Added lines #L376 - L377 were not covered by tests
};
});

// Handle stream end callback
if (onEndedCallback) {
stream.getVideoTracks()[0].onended = event => {
onEndedCallback(event);
}
}
var promise;
if (this.localStream == null) {
promise = this.gotStream(canvasStream);
} else {
promise = this.updateVideoTrack(canvasStream, streamId, onended, null);
};
}

promise.then(() => {
// Create a MediaStream from the canvas
const canvasStream = canvas.captureStream(15); // Capture at 15 fps

//update the canvas
this.desktopCameraCanvasDrawerTimer = setInterval(() => {
//draw screen to canvas
canvas.width = screenVideo.videoWidth;
canvas.height = screenVideo.videoHeight;
canvasContext.drawImage(screenVideo, 0, 0, canvas.width, canvas.height);
// Initialize or update the local stream
const promise = this.localStream == null
? this.gotStream(canvasStream)
: this.updateVideoTrack(canvasStream, streamId, onEndedCallback, null);

var cameraWidth = screenVideo.videoWidth * (this.camera_percent / 100);
var cameraHeight = (cameraVideo.videoHeight / cameraVideo.videoWidth) * cameraWidth
promise.then(() => {
// Initialize the worker
const worker = new Worker(new URL("./draw-desktop-with-camera-source-worker.js", import.meta.url));

// Send the OffscreenCanvas to the worker
worker.postMessage({
type: 'init',
data: { canvas: offscreenCanvas, width: screenVideo.videoWidth, height: screenVideo.videoHeight },
}, [offscreenCanvas]);

// Wait for both videos to load
Promise.all([setupVideo(screenVideo), setupVideo(cameraVideo)]).then(() => {
const frameInterval = 1000 / 15; // 15 fps

Check warning on line 408 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L408

Added line #L408 was not covered by tests

// Periodically send frames to the worker
const sendFrames = () => {
if (screenVideo.videoWidth > 0 && cameraVideo.videoWidth > 0) {
createImageBitmap(screenVideo).then(screenBitmap => {
createImageBitmap(cameraVideo).then(cameraBitmap => {
worker.postMessage({

Check warning on line 415 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L411-L415

Added lines #L411 - L415 were not covered by tests
type: 'frame',
data: { screenFrame: screenBitmap, cameraFrame: cameraBitmap },
}, [screenBitmap, cameraBitmap]);
}).catch(err => {
console.error("Error creating camera ImageBitmap:", err);

Check warning on line 420 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L420

Added line #L420 was not covered by tests
});
}).catch(err => {
console.error("Error creating screen ImageBitmap:", err);

Check warning on line 423 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L423

Added line #L423 was not covered by tests
});
} else {
console.warn("Video dimensions are invalid.");

Check warning on line 426 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L426

Added line #L426 was not covered by tests
}
};

var positionX = (canvas.width - cameraWidth) - this.camera_margin;
var positionY;
const frameTimer = setInterval(sendFrames, frameInterval);

Check warning on line 430 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L430

Added line #L430 was not covered by tests

if (this.camera_location == "top") {
positionY = this.camera_margin;
} else { //if not top, make it bottom
//draw camera on right bottom corner
positionY = (canvas.height - cameraHeight) - this.camera_margin;
}
canvasContext.drawImage(cameraVideo, positionX, positionY, cameraWidth, cameraHeight);
}, 66);
// Cleanup
this.desktopCameraCanvasDrawerTimer = () => {
clearInterval(frameTimer);
worker.terminate();

Check warning on line 435 in src/main/js/media_manager.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/media_manager.js#L433-L435

Added lines #L433 - L435 were not covered by tests
};
});
});
}, true)
}, true);
}

/**
Expand Down
Loading
Loading