diff --git a/README.md b/README.md index 82ddd607..f7627c9b 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ - File sharing with drag-and-drop support. - Choose your audio input, output, and video source. - Supports video quality up to 8K and 60 FPS. -- Supports advance Picture-in-Picture (PiP) offering a more streamlined and flexible viewing experience. +- Supports advance Video/Document Picture-in-Picture (PiP) offering a more streamlined and flexible viewing experience. - Record your screen, audio, and video. - Snapshot video frames and save them as PNG images. - Chat with an Emoji Picker for expressing feelings, private messages, Markdown support, and conversation saving. diff --git a/app/src/server.js b/app/src/server.js index c084fe92..e7d8d9a5 100755 --- a/app/src/server.js +++ b/app/src/server.js @@ -39,7 +39,7 @@ dependencies: { * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.4.45 + * @version 1.4.46 * */ diff --git a/package.json b/package.json index a39df398..10904e09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalk", - "version": "1.4.45", + "version": "1.4.46", "description": "A free WebRTC browser-based video call", "main": "server.js", "scripts": { diff --git a/public/js/client.js b/public/js/client.js index 951c90d3..7e6ab9b6 100644 --- a/public/js/client.js +++ b/public/js/client.js @@ -15,7 +15,7 @@ * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.4.45 + * @version 1.4.46 * */ @@ -5162,6 +5162,18 @@ function setDocumentPiPBtn() { }); } +/** + * Restart documentPictureInPicture + * @returns void + */ +async function documentPictureInPictureRestart() { + if (!showDocumentPipBtn || !documentPictureInPicture.window) return; + documentPictureInPictureClose(); + setTimeout(async () => { + await documentPictureInPictureOpen(); + }, 300); +} + /** * Close documentPictureInPicture */ @@ -5217,35 +5229,19 @@ async function documentPictureInPictureOpen() { // No video stream detected or is video share from URL... if (!video.srcObject || video.id === 'videoAudioUrlElement') return; - let videoPIPAllowed = false; - // get video element const videoPlayer = getId(video.id); - // Check if video can be add on pipVideo - if (video.id === 'myVideo') { - const localVideoStatus = getId('myVideoStatusIcon'); - - videoPIPAllowed = - localVideoStatus.className === className.videoOn && // video is ON - !videoPlayer.classList.contains('videoCircle'); // not in privacy mode - - console.log('DOCUMENT PIP LOCAL videoPIPAllowed -----> ' + videoPIPAllowed); - } else { - const parts = video.id.split('___'); // peerId___video - const peer_id = parts[0]; - const remoteVideoStatus = getId(peer_id + '_videoStatus'); - - videoPIPAllowed = - remoteVideoStatus.className === className.videoOn && // video is ON - !videoPlayer.classList.contains('videoCircle'); // not in privacy mode + const isPIPAllowed = !videoPlayer.classList.contains('videoCircle'); // not in privacy mode - console.log('DOCUMENT PIP REMOTE videoPIPAllowed -----> ' + videoPIPAllowed); - } + // Check if video can be add on pipVideo + video.id === 'myVideo' + ? console.log('DOCUMENT PIP LOCAL: PiP allowed? -----> ' + isPIPAllowed) + : console.log('DOCUMENT PIP REMOTE: PiP allowed? -----> ' + isPIPAllowed); - if (!videoPIPAllowed) return; + if (!isPIPAllowed) return; - // Video is ON not in privacy mode continue.... + // Video is ON and not in privacy mode continue.... foundVideo = true; @@ -5258,6 +5254,19 @@ async function documentPictureInPictureOpen() { pipVideo.muted = true; pipVideoContainer.append(pipVideo); + + const videoElementObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + // Handle class changes in video elements + console.log(`Video ${mutation.target.id} class changed:`, mutation.target.className); + cloneVideoElements(); + } + }); + }); + + // Start observing for new videos and class changes + videoElementObserver.observe(video, { attributes: true, attributeFilter: ['class'] }); }); return foundVideo; @@ -5551,7 +5560,7 @@ function setupMySettings() { videoSelect.addEventListener('change', async () => { await changeLocalCamera(videoSelect.value); await handleLocalCameraMirror(); - await documentPictureInPictureClose(); + await documentPictureInPictureRestart(); refreshLsDevices(); }); // select video quality @@ -6427,12 +6436,9 @@ async function handleVideo(e, init, force = null) { if (!videoStatus) { if (!isScreenStreaming) { // Stop the video track based on the condition - if (init) { - await stopVideoTracks(initStream); // Stop init video track (camera LED off) - } else { - await stopVideoTracks(localVideoMediaStream); // Stop local video track (camera LED off) - await documentPictureInPictureClose(); // Close doc PIP if open - } + init + ? await stopVideoTracks(initStream) // Stop init video track (camera LED off) + : await stopVideoTracks(localVideoMediaStream); // Stop local video track (camera LED off) } } else { if (init) { @@ -6441,6 +6447,7 @@ async function handleVideo(e, init, force = null) { } else if (!isScreenStreaming) { // Resume the video track for the local camera (camera LED on) await changeLocalCamera(videoSelect.value); + await documentPictureInPictureRestart(); // Restart doc PIP if open } } @@ -11017,7 +11024,7 @@ function showAbout() { Swal.fire({ background: swBg, position: 'center', - title: 'WebRTC P2P v1.4.45', + title: 'WebRTC P2P v1.4.46', imageAlt: 'mirotalk-about', imageUrl: images.about, customClass: { image: 'img-about' },