[mirotalksfu] - #194 Add support for GIF virtual backgrounds

هذا الالتزام موجود في:
Miroslav Pejic
2025-02-16 21:39:57 +01:00
الأصل c76d2a34a0
التزام 97cc93f5d5
7 ملفات معدلة مع 72 إضافات و25 حذوفات

عرض الملف

@@ -58,7 +58,7 @@ dev dependencies: {
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.45 * @version 1.7.46
* *
*/ */

عرض الملف

@@ -1,6 +1,6 @@
{ {
"name": "mirotalksfu", "name": "mirotalksfu",
"version": "1.7.45", "version": "1.7.46",
"description": "WebRTC SFU browser-based video calls", "description": "WebRTC SFU browser-based video calls",
"main": "Server.js", "main": "Server.js",
"scripts": { "scripts": {

عرض الملف

@@ -64,7 +64,7 @@ let BRAND = {
}, },
about: { about: {
imageUrl: '../images/mirotalk-logo.gif', imageUrl: '../images/mirotalk-logo.gif',
title: '<strong>WebRTC SFU v1.7.45</strong>', title: '<strong>WebRTC SFU v1.7.46</strong>',
html: ` html: `
<button <button
id="support-button" id="support-button"

عرض الملف

@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.45 * @version 1.7.46
* *
*/ */
@@ -5235,7 +5235,7 @@ function showAbout() {
position: 'center', position: 'center',
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about, imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
customClass: { image: 'img-about' }, customClass: { image: 'img-about' },
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.7.45', title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.7.46',
html: ` html: `
<br /> <br />
<div id="about"> <div id="about">

عرض الملف

@@ -9,7 +9,7 @@
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.45 * @version 1.7.46
* *
*/ */

عرض الملف

@@ -4,6 +4,10 @@ class VirtualBackground {
constructor() { constructor() {
this.segmentation = null; this.segmentation = null;
this.initialized = false; this.initialized = false;
this.gifCanvas = null;
this.gifCtx = null;
this.gifAnimation = null;
this.currentGifFrame = null;
} }
async initializeSegmentation() { async initializeSegmentation() {
@@ -123,36 +127,78 @@ class VirtualBackground {
} }
async applyVirtualBackgroundToWebRTCStream(videoTrack, image = 'https://i.postimg.cc/t9PJw5P7/forest.jpg') { async applyVirtualBackgroundToWebRTCStream(videoTrack, image = 'https://i.postimg.cc/t9PJw5P7/forest.jpg') {
const backgroundImage = await this.loadImage(image); const isGif = image.endsWith('.gif') || image.startsWith('data:image/gif');
const backgroundImage = await this.loadImage(image, isGif);
const maskHandler = async (ctx, canvas, mask, imageBitmap) => { const maskHandler = async (ctx, canvas, mask, imageBitmap) => {
// Apply the mask to keep the person in focus try {
ctx.save(); // Apply the mask to keep the person in focus
ctx.globalCompositeOperation = 'destination-in'; ctx.save();
ctx.drawImage(mask, 0, 0, canvas.width, canvas.height); ctx.globalCompositeOperation = 'destination-in';
ctx.restore(); ctx.drawImage(mask, 0, 0, canvas.width, canvas.height);
ctx.restore();
// Save the focused person area // Save the focused person area
const personImageBitmap = await createImageBitmap(canvas); const personImageBitmap = await createImageBitmap(canvas);
// Draw the background image // If GIF is detected, draw the current animated frame
ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height); if (isGif) {
if (this.currentGifFrame) {
ctx.drawImage(this.currentGifFrame, 0, 0, canvas.width, canvas.height);
}
} else {
// Draw the background image
ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);
}
// Draw the person back on top of the background image // Draw the person back on top of the background image
ctx.globalCompositeOperation = 'source-over'; ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(personImageBitmap, 0, 0, canvas.width, canvas.height); ctx.drawImage(personImageBitmap, 0, 0, canvas.width, canvas.height);
} catch (error) {
console.error('❌ Error in maskHandler:', error);
}
}; };
if (isGif) this.animateGifBackground();
return this.processStreamWithSegmentation(videoTrack, maskHandler); return this.processStreamWithSegmentation(videoTrack, maskHandler);
} }
async loadImage(src) { async loadImage(src, isGif) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const img = new Image(); if (isGif) {
img.crossOrigin = 'anonymous'; // Setup canvas for GIF animation rendering
img.src = src; this.gifCanvas = document.createElement('canvas');
img.onload = () => resolve(img); this.gifCtx = this.gifCanvas.getContext('2d');
img.onerror = reject; try {
gifler(src).get((a) => {
this.gifAnimation = a;
a.animateInCanvas(this.gifCanvas); // Start the animation
console.log('✅ GIF loaded and animation started.');
resolve(this.gifCanvas);
});
} catch (error) {
console.error('❌ Error loading GIF:', error);
reject(error);
}
} else {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = src;
img.onload = () => resolve(img);
img.onerror = reject;
}
}); });
} }
animateGifBackground() {
if (!this.gifAnimation) return;
// Updates the current GIF frame at each animation step
const updateFrame = () => {
this.currentGifFrame = this.gifCanvas;
requestAnimationFrame(updateFrame);
};
updateFrame();
}
} }

عرض الملف

@@ -134,6 +134,7 @@
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script> <script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation"></script> <script defer src="https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation"></script>
<script defer src="https://cdn.jsdelivr.net/npm/gifler@0.1.0/gifler.min.js"></script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>