[mirotalksfu] - #194 Keep uploaded virtual background images in IndexedDB
هذا الالتزام موجود في:
@@ -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.36
|
* @version 1.7.37
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.7.36",
|
"version": "1.7.37",
|
||||||
"description": "WebRTC SFU browser-based video calls",
|
"description": "WebRTC SFU browser-based video calls",
|
||||||
"main": "Server.js",
|
"main": "Server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
"mediasoup-client": "3.8.1",
|
"mediasoup-client": "3.8.1",
|
||||||
"ngrok": "^5.0.0-beta.2",
|
"ngrok": "^5.0.0-beta.2",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"openai": "^4.85.0",
|
"openai": "^4.85.1",
|
||||||
"qs": "6.14.0",
|
"qs": "6.14.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"socket.io": "4.8.1",
|
"socket.io": "4.8.1",
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.image-grid img:hover {
|
.image-grid img:hover {
|
||||||
transform: scale(1.1);
|
opacity: 0.5;
|
||||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +242,33 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------
|
||||||
|
# Custom image delete icon
|
||||||
|
--------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #f5f5f5b0;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.delete-icon:hover {
|
||||||
|
background: var(--body-bg);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------
|
/*--------------------------------------------------------------
|
||||||
# Buttons bar
|
# Buttons bar
|
||||||
--------------------------------------------------------------*/
|
--------------------------------------------------------------*/
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ let BRAND = {
|
|||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
imageUrl: '../images/mirotalk-logo.gif',
|
imageUrl: '../images/mirotalk-logo.gif',
|
||||||
title: '<strong>WebRTC SFU v1.7.36</strong>',
|
title: '<strong>WebRTC SFU v1.7.37</strong>',
|
||||||
html: `
|
html: `
|
||||||
<button
|
<button
|
||||||
id="support-button"
|
id="support-button"
|
||||||
|
|||||||
@@ -5011,20 +5011,107 @@ function showImageSelector() {
|
|||||||
if (file) {
|
if (file) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
const customImg = document.createElement('img');
|
const imgData = e.target.result;
|
||||||
customImg.id = `customImage${imageCounter}`; // Assign a unique id
|
saveImageToIndexedDB(imgData);
|
||||||
customImg.src = e.target.result;
|
addImageToUI(imgData);
|
||||||
customImg.alt = 'Custom Background';
|
|
||||||
customImg.addEventListener('click', async function () {
|
|
||||||
await applyVirtualBackground(initVideo, initStream, false, e.target.result);
|
|
||||||
});
|
|
||||||
imageGrid.appendChild(customImg);
|
|
||||||
setTippy(customImg.id, 'Custom background', 'top');
|
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load images from IndexedDB
|
||||||
|
async function loadStoredImages() {
|
||||||
|
const storedImages = await getImagesFromIndexedDB();
|
||||||
|
storedImages.forEach((imgData) => addImageToUI(imgData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save image to IndexedDB
|
||||||
|
async function saveImageToIndexedDB(imgData) {
|
||||||
|
const db = await openDB();
|
||||||
|
const transaction = db.transaction('images', 'readwrite');
|
||||||
|
const store = transaction.objectStore('images');
|
||||||
|
store.add({ imgData });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open IndexedDB
|
||||||
|
function openDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open('customImageDB', 1);
|
||||||
|
request.onupgradeneeded = function (event) {
|
||||||
|
const db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains('images')) {
|
||||||
|
db.createObjectStore('images', { keyPath: 'id', autoIncrement: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get images from IndexedDB
|
||||||
|
function getImagesFromIndexedDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const dbRequest = openDB();
|
||||||
|
dbRequest.then((db) => {
|
||||||
|
const transaction = db.transaction('images', 'readonly');
|
||||||
|
const store = transaction.objectStore('images');
|
||||||
|
const request = store.getAll();
|
||||||
|
|
||||||
|
request.onsuccess = () => resolve(request.result.map((item) => item.imgData));
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add image to UI
|
||||||
|
function addImageToUI(imgData) {
|
||||||
|
const imageContainer = document.createElement('div');
|
||||||
|
imageContainer.className = 'image-wrapper';
|
||||||
|
const customImg = document.createElement('img');
|
||||||
|
|
||||||
|
customImg.id = `customImageGrid${imageCounter}`;
|
||||||
|
customImg.src = imgData;
|
||||||
|
customImg.alt = 'Custom Background';
|
||||||
|
customImg.addEventListener('click', async function () {
|
||||||
|
await applyVirtualBackground(initVideo, initStream, false, imgData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create delete button
|
||||||
|
const deleteBtn = createDeleteIcon(imgData, imageContainer);
|
||||||
|
imageContainer.appendChild(customImg);
|
||||||
|
imageContainer.appendChild(deleteBtn);
|
||||||
|
imageGrid.appendChild(imageContainer);
|
||||||
|
setTippy(customImg.id, 'Custom background', 'top');
|
||||||
|
|
||||||
|
imageCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create delete icon
|
||||||
|
function createDeleteIcon(imgData, container) {
|
||||||
|
const deleteIcon = document.createElement('span');
|
||||||
|
deleteIcon.className = 'delete-icon fas fa-times';
|
||||||
|
deleteIcon.addEventListener('click', function (event) {
|
||||||
|
event.stopPropagation(); // Prevent triggering background change
|
||||||
|
removeImageFromIndexedDB(imgData);
|
||||||
|
container.remove();
|
||||||
|
});
|
||||||
|
return deleteIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove image from IndexedDB
|
||||||
|
async function removeImageFromIndexedDB(imgData) {
|
||||||
|
const db = await openDB();
|
||||||
|
const transaction = db.transaction('images', 'readwrite');
|
||||||
|
const store = transaction.objectStore('images');
|
||||||
|
|
||||||
|
const request = store.getAll();
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const items = request.result;
|
||||||
|
const itemToDelete = items.find((item) => item.imgData === imgData);
|
||||||
|
if (itemToDelete) store.delete(itemToDelete.id);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Append the button to the imageGrid
|
// Append the button to the imageGrid
|
||||||
imageGrid.appendChild(uploadImg);
|
imageGrid.appendChild(uploadImg);
|
||||||
setTippy(uploadImg.id, 'Upload your custom background', 'top');
|
setTippy(uploadImg.id, 'Upload your custom background', 'top');
|
||||||
@@ -5040,6 +5127,9 @@ function showImageSelector() {
|
|||||||
});
|
});
|
||||||
imageGrid.appendChild(img);
|
imageGrid.appendChild(img);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load images from IndexedDB on page load
|
||||||
|
loadStoredImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyVirtualBackground(videoElement, stream, blurLevel, backgroundImage) {
|
async function applyVirtualBackground(videoElement, stream, blurLevel, backgroundImage) {
|
||||||
|
|||||||
@@ -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.36
|
* @version 1.7.37
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -1685,7 +1685,7 @@ class RoomClient {
|
|||||||
const cleanVbImg = document.createElement('img');
|
const cleanVbImg = document.createElement('img');
|
||||||
cleanVbImg.id = 'cleanVbImg';
|
cleanVbImg.id = 'cleanVbImg';
|
||||||
cleanVbImg.src = image.user;
|
cleanVbImg.src = image.user;
|
||||||
cleanVbImg.alt = 'Clean virtual bacgroundk';
|
cleanVbImg.alt = 'Clean virtual background';
|
||||||
cleanVbImg.dataset.index = 'cleanVb';
|
cleanVbImg.dataset.index = 'cleanVb';
|
||||||
cleanVbImg.addEventListener('click', async function () {
|
cleanVbImg.addEventListener('click', async function () {
|
||||||
if (!virtualBackgroundBlurLevel && !virtualBackgroundSelectedImage) {
|
if (!virtualBackgroundBlurLevel && !virtualBackgroundSelectedImage) {
|
||||||
@@ -1726,26 +1726,113 @@ class RoomClient {
|
|||||||
fileInput.click();
|
fileInput.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load images from IndexedDB
|
||||||
|
async function loadStoredImages() {
|
||||||
|
const storedImages = await getImagesFromIndexedDB();
|
||||||
|
storedImages.forEach((imgData) => addImageToUI(imgData));
|
||||||
|
}
|
||||||
|
|
||||||
// Handle image selection
|
// Handle image selection
|
||||||
fileInput.addEventListener('change', function () {
|
fileInput.addEventListener('change', function () {
|
||||||
const file = this.files[0];
|
const file = this.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
const customImg = document.createElement('img');
|
const imgData = e.target.result;
|
||||||
customImg.id = `customImage${imageCounter}`; // Assign a unique id
|
saveImageToIndexedDB(imgData);
|
||||||
customImg.src = e.target.result;
|
addImageToUI(imgData);
|
||||||
customImg.alt = 'Custom Background';
|
|
||||||
customImg.addEventListener('click', async function () {
|
|
||||||
await rc.applyVirtualBackground(false, e.target.result);
|
|
||||||
});
|
|
||||||
imageGridVideo.appendChild(customImg);
|
|
||||||
setTippy(customImg.id, 'Custom background', 'top');
|
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add image with delete button to UI
|
||||||
|
function addImageToUI(imgData) {
|
||||||
|
const imageContainer = document.createElement('div');
|
||||||
|
imageContainer.className = 'image-wrapper';
|
||||||
|
const customImg = document.createElement('img');
|
||||||
|
|
||||||
|
customImg.id = `customImageGrid${imageCounter}`;
|
||||||
|
customImg.src = imgData;
|
||||||
|
customImg.alt = 'Custom Background';
|
||||||
|
customImg.addEventListener('click', async function () {
|
||||||
|
await rc.applyVirtualBackground(false, imgData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create delete button
|
||||||
|
const deleteBtn = createDeleteIcon(imgData, imageContainer);
|
||||||
|
imageContainer.appendChild(customImg);
|
||||||
|
imageContainer.appendChild(deleteBtn);
|
||||||
|
imageGridVideo.appendChild(imageContainer);
|
||||||
|
setTippy(customImg.id, 'Custom background', 'top');
|
||||||
|
|
||||||
|
imageCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create delete icon
|
||||||
|
function createDeleteIcon(imgData, container) {
|
||||||
|
const deleteIcon = document.createElement('span');
|
||||||
|
deleteIcon.className = 'delete-icon fas fa-times';
|
||||||
|
deleteIcon.addEventListener('click', function (event) {
|
||||||
|
event.stopPropagation(); // Prevent triggering background change
|
||||||
|
removeImageFromIndexedDB(imgData);
|
||||||
|
container.remove();
|
||||||
|
});
|
||||||
|
return deleteIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save image to IndexedDB
|
||||||
|
async function saveImageToIndexedDB(imgData) {
|
||||||
|
const db = await openDB();
|
||||||
|
const transaction = db.transaction('images', 'readwrite');
|
||||||
|
const store = transaction.objectStore('images');
|
||||||
|
store.add({ imgData });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open IndexedDB
|
||||||
|
function openDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open('customImageDB', 1);
|
||||||
|
request.onupgradeneeded = function (event) {
|
||||||
|
const db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains('images')) {
|
||||||
|
db.createObjectStore('images', { keyPath: 'id', autoIncrement: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get images from IndexedDB
|
||||||
|
function getImagesFromIndexedDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const dbRequest = openDB();
|
||||||
|
dbRequest.then((db) => {
|
||||||
|
const transaction = db.transaction('images', 'readonly');
|
||||||
|
const store = transaction.objectStore('images');
|
||||||
|
const request = store.getAll();
|
||||||
|
|
||||||
|
request.onsuccess = () => resolve(request.result.map((item) => item.imgData));
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove image from IndexedDB
|
||||||
|
async function removeImageFromIndexedDB(imgData) {
|
||||||
|
const db = await openDB();
|
||||||
|
const transaction = db.transaction('images', 'readwrite');
|
||||||
|
const store = transaction.objectStore('images');
|
||||||
|
|
||||||
|
const request = store.getAll();
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const items = request.result;
|
||||||
|
const itemToDelete = items.find((item) => item.imgData === imgData);
|
||||||
|
if (itemToDelete) store.delete(itemToDelete.id);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Append the button to the imageGrid
|
// Append the button to the imageGrid
|
||||||
imageGridVideo.appendChild(uploadImg);
|
imageGridVideo.appendChild(uploadImg);
|
||||||
setTippy(uploadImg.id, 'Upload your custom background', 'top');
|
setTippy(uploadImg.id, 'Upload your custom background', 'top');
|
||||||
@@ -1761,6 +1848,9 @@ class RoomClient {
|
|||||||
});
|
});
|
||||||
imageGridVideo.appendChild(img);
|
imageGridVideo.appendChild(img);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load images from IndexedDB on page load
|
||||||
|
loadStoredImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyVirtualBackground(blurLevel, backgroundImage) {
|
async applyVirtualBackground(blurLevel, backgroundImage) {
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم