Skip to content

Commit 48e2ec2

Browse files
committed
refacto(camera): refacto size of video upload as custom stream, in order to increase connexion stability
1 parent 12b2eca commit 48e2ec2

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

src/plugins/Camera.js

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,27 @@ export default class Camera extends OverlayPlugin {
497497
const sourceWidth = isImage ? mediaElement.width : mediaElement.videoWidth;
498498
const sourceHeight = isImage ? mediaElement.height : mediaElement.videoHeight;
499499

500+
// Fixed target resolution (16:9) to ensure stability and standard aspect ratio
501+
const targetWidth = 1280;
502+
const targetHeight = 720;
503+
500504
const canvas = document.createElement('canvas');
501-
canvas.width = sourceWidth || 1280;
502-
canvas.height = sourceHeight || 720;
505+
canvas.width = targetWidth;
506+
canvas.height = targetHeight;
503507
const ctx = canvas.getContext('2d');
504508

509+
// Fill black background for letterboxing
510+
ctx.fillRect(0, 0, targetWidth, targetHeight);
511+
512+
// Calculate scaling to fit (contain) while preserving aspect ratio
513+
const ratio = Math.min(targetWidth / sourceWidth, targetHeight / sourceHeight);
514+
const drawWidth = Math.floor(sourceWidth * ratio);
515+
const drawHeight = Math.floor(sourceHeight * ratio);
516+
517+
// Center the video in the canvas
518+
const startX = (targetWidth - drawWidth) / 2;
519+
const startY = (targetHeight - drawHeight) / 2;
520+
505521
let audioTrack = null;
506522
/*
507523
* If the instance supports microphone and we are playing a video,
@@ -582,13 +598,14 @@ export default class Camera extends OverlayPlugin {
582598

583599
if (isImage) {
584600
// Optimization: Draw image once, no loop needed
585-
if (sourceWidth && sourceHeight) {
586-
ctx.drawImage(mediaElement, 0, 0, sourceWidth, sourceHeight);
601+
if (drawWidth && drawHeight) {
602+
ctx.drawImage(mediaElement, startX, startY, drawWidth, drawHeight);
587603
}
588604
} else {
589605
// Optimization: Video draw loop, use requestVideoFrameCallback if available for efficient sync, otherwise fallback to throttled rAF
590606
this[type + 'UseVideoFrameCallback'] = 'requestVideoFrameCallback' in mediaElement;
591607
let lastTime = 0;
608+
592609
// Target 30fps for the fallback throttle
593610
const throttleInterval = 1000 / 30;
594611

@@ -599,16 +616,16 @@ export default class Camera extends OverlayPlugin {
599616
}
600617

601618
if (this[type + 'UseVideoFrameCallback']) {
602-
if (sourceWidth && sourceHeight) {
603-
ctx.drawImage(mediaElement, 0, 0, sourceWidth, sourceHeight);
619+
if (drawWidth && drawHeight) {
620+
ctx.drawImage(mediaElement, startX, startY, drawWidth, drawHeight);
604621
}
605622
this[type + 'AnimationId'] = mediaElement.requestVideoFrameCallback(drawLoop);
606623
} else {
607624
// Throttling logic for rAF
608625
if (!lastTime || now - lastTime >= throttleInterval) {
609626
lastTime = now;
610-
if (sourceWidth && sourceHeight) {
611-
ctx.drawImage(mediaElement, 0, 0, sourceWidth, sourceHeight);
627+
if (drawWidth && drawHeight) {
628+
ctx.drawImage(mediaElement, startX, startY, drawWidth, drawHeight);
612629
}
613630
}
614631
this[type + 'AnimationId'] = requestAnimationFrame(drawLoop);
@@ -702,11 +719,11 @@ export default class Camera extends OverlayPlugin {
702719
if (typeof mediaElement.pause === 'function') {
703720
mediaElement.pause();
704721
}
722+
723+
mediaElement.removeAttribute('src');
724+
705725
if (typeof mediaElement.load === 'function') {
706-
mediaElement.src = '';
707726
mediaElement.load();
708-
} else {
709-
mediaElement.src = '';
710727
}
711728

712729
if (mediaElement.parentNode) {

src/plugins/MediaManager.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,16 @@ export default class MediaManager {
281281
if (sender) {
282282
log.debug(`Replacing ${type} video track on sender`);
283283
await sender.replaceTrack(stream.getVideoTracks()[0]);
284+
sender.setStreams(stream);
284285
this.setTransceiverDirection(sender, 'sendrecv');
286+
287+
// Limit bitrate to avoid congestion (1.5 Mbps)
288+
const parameters = sender.getParameters();
289+
if (!parameters.encodings) {
290+
parameters.encodings = [{}];
291+
}
292+
parameters.encodings[0].maxBitrate = 1500000;
293+
await sender.setParameters(parameters).catch(e => log.warn('Failed to set sender parameters', e));
285294
} else {
286295
// Try to find an existing video transceiver that is recvonly (reusable)
287296
const transceivers = this.instance.peerConnection.getTransceivers();
@@ -312,6 +321,15 @@ export default class MediaManager {
312321
} else {
313322
this.backCameraSender = transceiver.sender;
314323
}
324+
325+
const newSender = transceiver.sender;
326+
const parameters = newSender.getParameters();
327+
if (!parameters.encodings) {
328+
parameters.encodings = [{}];
329+
}
330+
parameters.encodings[0].maxBitrate = 1500000;
331+
await newSender.setParameters(parameters)
332+
.catch((e) => log.warn('Failed to set new sender parameters', e));
315333
}
316334
}
317335
} else {

0 commit comments

Comments
 (0)