diff --git a/app/src/Server.js b/app/src/Server.js index 01ad68d6..3ab8bac5 100644 --- a/app/src/Server.js +++ b/app/src/Server.js @@ -55,7 +55,7 @@ dev dependencies: { * @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 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.5.74 + * @version 1.5.75 * */ diff --git a/app/src/config.template.js b/app/src/config.template.js index 7d1be5cd..b3fdeb07 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -385,6 +385,7 @@ module.exports = { }, producerVideo: { videoPictureInPicture: true, + videoMirrorButton: true, fullScreenButton: true, snapShotButton: true, muteAudioButton: true, @@ -392,6 +393,7 @@ module.exports = { }, consumerVideo: { videoPictureInPicture: true, + videoMirrorButton: true, fullScreenButton: true, snapShotButton: true, focusVideoButton: true, diff --git a/package.json b/package.json index e04d5f96..4e8d52df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.5.74", + "version": "1.5.75", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { @@ -64,7 +64,7 @@ "compression": "1.7.4", "cors": "2.8.5", "crypto-js": "4.2.0", - "dompurify": "^3.1.6", + "dompurify": "^3.1.7", "express": "4.21.0", "express-openid-connect": "^2.17.1", "fluent-ffmpeg": "^2.1.3", @@ -77,7 +77,7 @@ "mediasoup-client": "3.7.16", "ngrok": "^5.0.0-beta.2", "nodemailer": "^6.9.15", - "openai": "^4.63.0", + "openai": "^4.65.0", "qs": "6.13.0", "socket.io": "4.8.0", "swagger-ui-express": "5.0.1", diff --git a/public/css/Room.css b/public/css/Room.css index d15615ec..21541c14 100644 --- a/public/css/Room.css +++ b/public/css/Room.css @@ -442,10 +442,6 @@ th { width: 180px; } -#VideoMirrorDiv { - margin-top: 10px; -} - /*-------------------------------------------------------------- # RTMP settings --------------------------------------------------------------*/ diff --git a/public/css/VideoGrid.css b/public/css/VideoGrid.css index eba6819d..77690dbc 100644 --- a/public/css/VideoGrid.css +++ b/public/css/VideoGrid.css @@ -192,6 +192,7 @@ video { object-fit: var(--videoObjFit); border-radius: 10px; cursor: pointer; + transition: transform 0.3s ease-in-out; } #canvasAIElement { diff --git a/public/js/Room.js b/public/js/Room.js index 1ac7f2b3..b3d37436 100644 --- a/public/js/Room.js +++ b/public/js/Room.js @@ -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 CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.5.74 + * @version 1.5.75 * */ @@ -213,6 +213,7 @@ let isEnumerateVideoDevices = false; let isAudioAllowed = false; let isVideoAllowed = false; let isVideoPrivacyActive = false; +let isInitVideoMirror = false; let isRecording = false; let isAudioVideoAllowed = false; let isParticipantsListOpen = false; @@ -658,6 +659,10 @@ function setupInitButtons() { initStopScreenButton.onclick = async () => { await toggleScreenSharing(); }; + initVideoMirrorButton.onclick = () => { + initVideo.classList.toggle('mirror'); + isInitVideoMirror = initVideo.classList.contains('mirror'); + }; } // #################################################### @@ -925,7 +930,11 @@ async function whoAreYou() { }); const serverButtons = response.data.message; if (serverButtons) { - BUTTONS = serverButtons; + // Merge serverButtons into BUTTONS, keeping the existing keys in BUTTONS if they are not present in serverButtons + BUTTONS = { + ...BUTTONS, // Spread current BUTTONS first to keep existing keys + ...serverButtons, // Overwrite or add new keys from serverButtons + }; console.log('04 ----> AXIOS ROOM BUTTONS SETTINGS', { serverButtons: serverButtons, clientButtons: BUTTONS, @@ -1980,6 +1989,7 @@ function setButtonsInit() { setTippy('initAudioVideoButton', 'Toggle the audio & video', 'top'); setTippy('initStartScreenButton', 'Toggle screen sharing', 'top'); setTippy('initStopScreenButton', 'Toggle screen sharing', 'top'); + setTippy('initVideoMirrorButton', 'Toggle video mirror', 'top'); } if (!isAudioAllowed) hide(initAudioButton); if (!isVideoAllowed) hide(initVideoButton); @@ -2246,11 +2256,13 @@ function handleCameraMirror(video) { // Desktop devices... if (!video.classList.contains('mirror')) { video.classList.toggle('mirror'); + isInitVideoMirror = true; } } else { // Mobile, Tablet, IPad devices... if (video.classList.contains('mirror')) { video.classList.remove('mirror'); + isInitVideoMirror = false; } } } @@ -2347,11 +2359,6 @@ function handleSelects() { lS.setSettings(localStorageSettings); e.target.blur(); }; - switchVideoMirror.onchange = (e) => { - rc.toggleVideoMirror(); - rc.roomMessage('toggleVideoMirror', e.currentTarget.checked); - e.target.blur(); - }; switchSounds.onchange = (e) => { isSoundEnabled = e.currentTarget.checked; rc.roomMessage('sounds', isSoundEnabled); @@ -2903,7 +2910,6 @@ function handleRoomClientEvents() { show(stopVideoButton); setColor(startVideoButton, 'red'); setVideoButtonsDisabled(false); - switchVideoMirror.disabled = false; // if (isParticipantsListOpen) getRoomParticipants(); }); rc.on(RoomClient.EVENTS.pauseVideo, () => { @@ -2926,7 +2932,6 @@ function handleRoomClientEvents() { show(startVideoButton); setVideoButtonsDisabled(false); isVideoPrivacyActive = false; - switchVideoMirror.disabled = true; // if (isParticipantsListOpen) getRoomParticipants(); }); rc.on(RoomClient.EVENTS.startScreen, () => { @@ -4458,7 +4463,7 @@ function showAbout() { imageUrl: image.about, customClass: { image: 'img-about' }, position: 'center', - title: 'WebRTC SFU v1.5.74', + title: 'WebRTC SFU v1.5.75', html: `
diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index 81aa035d..a0f7d295 100644 --- a/public/js/RoomClient.js +++ b/public/js/RoomClient.js @@ -9,7 +9,7 @@ * @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 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.5.74 + * @version 1.5.75 * */ @@ -45,6 +45,7 @@ const html = { videoPrivacy: 'far fa-circle', expand: 'fas fa-ellipsis-vertical', hideALL: 'fas fa-eye', + mirror: 'fas fa-arrow-right-arrow-left', }; const icons = { @@ -1406,6 +1407,10 @@ class RoomClient { if (type == mediaType.video) this.videoProducerId = producer.id; if (type == mediaType.screen) this.screenProducerId = producer.id; elem = await this.handleProducer(producer.id, type, stream); + // No mirror effect for producer + if (!isInitVideoMirror && elem.classList.contains('mirror')) { + elem.classList.toggle('mirror'); + } //if (!screen && !isEnumerateDevices) enumerateVideoDevices(stream); } else { this.localAudioStream = stream; @@ -1882,7 +1887,7 @@ class RoomClient { } async handleProducer(id, type, stream) { - let elem, vb, vp, ts, d, p, i, au, pip, fs, pm, pb, pn; + let elem, vb, vp, ts, d, p, i, au, pip, fs, pm, pb, pn, mv; switch (type) { case mediaType.video: case mediaType.screen: @@ -1914,6 +1919,9 @@ class RoomClient { ts = document.createElement('button'); ts.id = id + '__snapshot'; ts.className = html.snapshot; + mv = document.createElement('button'); + mv.id = id + '__mirror'; + mv.className = html.mirror; pn = document.createElement('button'); pn.id = id + '__pin'; pn.className = html.pin; @@ -1945,6 +1953,7 @@ class RoomClient { BUTTONS.producerVideo.videoPictureInPicture && this.isVideoPictureInPictureSupported && vb.appendChild(pip); + BUTTONS.producerVideo.videoMirrorButton && vb.appendChild(mv); BUTTONS.producerVideo.fullScreenButton && this.isVideoFullScreenSupported && vb.appendChild(fs); if (!this.isMobileDevice) vb.appendChild(pn); d.appendChild(elem); @@ -1959,6 +1968,7 @@ class RoomClient { this.isVideoFullScreenSupported && this.handleFS(elem.id, fs.id); this.handleDD(elem.id, this.peer_id, true); this.handleTS(elem.id, ts.id); + this.handleMV(elem.id, mv.id); this.handlePN(elem.id, pn.id, d.id, isScreen); this.handleZV(elem.id, d.id, this.peer_id); if (!isScreen) this.handleVP(elem.id, vp.id); @@ -1968,6 +1978,7 @@ class RoomClient { handleAspectRatio(); if (!this.isMobileDevice) { this.setTippy(pn.id, 'Toggle Pin', 'bottom'); + this.setTippy(mv.id, 'Toggle mirror', 'bottom'); this.setTippy(pip.id, 'Toggle picture in picture', 'bottom'); this.setTippy(ts.id, 'Snapshot', 'bottom'); this.setTippy(vp.id, 'Toggle video privacy', 'bottom'); @@ -2262,7 +2273,7 @@ class RoomClient { } async handleConsumer(id, type, stream, peer_name, peer_info) { - let elem, vb, d, p, i, cm, au, pip, fs, ts, sf, sm, sv, gl, ban, ko, pb, pm, pv, pn, ha; + let elem, vb, d, p, i, cm, au, pip, fs, ts, sf, sm, sv, gl, ban, ko, pb, pm, pv, pn, ha, mv; let eDiv, eBtn, eVc; // expand buttons @@ -2311,6 +2322,9 @@ class RoomClient { pip = document.createElement('button'); pip.id = id + '__pictureInPicture'; pip.className = html.pip; + mv = document.createElement('button'); + mv.id = id + '__videoMirror'; + mv.className = html.mirror; fs = document.createElement('button'); fs.id = id + '__fullScreen'; fs.className = html.fullScreen; @@ -2379,6 +2393,7 @@ class RoomClient { BUTTONS.consumerVideo.videoPictureInPicture && this.isVideoPictureInPictureSupported && vb.appendChild(pip); + BUTTONS.consumerVideo.videoMirrorButton && vb.appendChild(mv); BUTTONS.consumerVideo.fullScreenButton && this.isVideoFullScreenSupported && vb.appendChild(fs); BUTTONS.consumerVideo.focusVideoButton && vb.appendChild(ha); if (!this.isMobileDevice) vb.appendChild(pn); @@ -2393,6 +2408,7 @@ class RoomClient { this.isVideoFullScreenSupported && this.handleFS(elem.id, fs.id); this.handleDD(elem.id, remotePeerId); this.handleTS(elem.id, ts.id); + this.handleMV(elem.id, mv.id); this.handleSF(sf.id); this.handleHA(ha.id, d.id); this.handleSM(sm.id, peer_name); @@ -2425,6 +2441,7 @@ class RoomClient { this.setTippy(pn.id, 'Toggle Pin', 'bottom'); this.setTippy(ha.id, 'Toggle Focus mode', 'bottom'); this.setTippy(pip.id, 'Toggle picture in picture', 'bottom'); + this.setTippy(mv.id, 'Toggle mirror', 'bottom'); this.setTippy(ts.id, 'Snapshot', 'bottom'); this.setTippy(sf.id, 'Send file', 'bottom'); this.setTippy(sm.id, 'Send message', 'bottom'); @@ -3610,6 +3627,21 @@ class RoomClient { } } + // #################################################### + // HANDLE VIDEO MIRROR + // #################################################### + + handleMV(elemId, tsId) { + let videoPlayer = this.getId(elemId); + let btnMv = this.getId(tsId); + if (btnMv && videoPlayer) { + btnMv.addEventListener('click', () => { + videoPlayer.classList.toggle('mirror'); + //rc.roomMessage('toggleVideoMirror', videoPlayer.classList.contains('mirror')); + }); + } + } + // #################################################### // VIDEO CIRCLE - PRIVACY MODE // #################################################### diff --git a/public/js/Rules.js b/public/js/Rules.js index c33ea23e..3bb19a41 100644 --- a/public/js/Rules.js +++ b/public/js/Rules.js @@ -48,6 +48,7 @@ let BUTTONS = { }, producerVideo: { videoPictureInPicture: true, + videoMirrorButton: true, fullScreenButton: true, snapShotButton: true, muteAudioButton: true, @@ -55,6 +56,7 @@ let BUTTONS = { }, consumerVideo: { videoPictureInPicture: true, + videoMirrorButton: true, fullScreenButton: true, snapShotButton: true, focusVideoButton: true, diff --git a/public/views/Room.html b/public/views/Room.html index c344f0b6..83d1a941 100644 --- a/public/views/Room.html +++ b/public/views/Room.html @@ -170,6 +170,7 @@ access to use this app. + @@ -378,15 +379,6 @@ access to use this app.

Video Source:

-
-
- -

Toggle mirror

-
- -
-
-