[mirotalksfu] - #194 Add support for GIF virtual backgrounds
هذا الالتزام موجود في:
@@ -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>
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم