Skip to content
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