[mirotalksfu] - add Document PIP

هذا الالتزام موجود في:
Miroslav Pejic
2025-01-11 18:53:02 +01:00
الأصل ad21adc338
التزام da0cc530f6
7 ملفات معدلة مع 181 إضافات و2 حذوفات

عرض الملف

@@ -62,7 +62,7 @@
- File sharing with drag-and-drop support.
- Choose your audio input, output, and video source.
- Supports video quality up to 4K.
- 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 locally or on your Server.
- Snapshot video frames and save them as PNG images.
- Chat with an Emoji Picker for expressing feelings, private messages, Markdown support, and conversation saving.

عرض الملف

@@ -457,6 +457,7 @@ module.exports = {
raiseHandButton: true,
transcriptionButton: true,
whiteboardButton: true,
documentPiPButton: true,
snapshotRoomButton: true,
emojiRoomButton: true,
settingsButton: true,

عرض الملف

@@ -0,0 +1,27 @@
*,
*:before,
*:after {
box-sizing: border-box;
}
body {
background: var(--body-bg);
}
.pipVideoContainer {
display: grid;
gap: 10px;
}
.pipVideo {
width: 100%;
object-fit: cover;
border-radius: 5px;
aspect-ratio: 16 / 9;
}
.mirror {
-webkit-transform: rotateY(180deg);
-moz-transform: rotateY(180deg);
transform: rotateY(180deg);
}

عرض الملف

@@ -31,6 +31,9 @@ const isIPadDevice = parserResult.device.model?.toLowerCase() === 'ipad';
const isDesktopDevice = deviceType === 'desktop';
const thisInfo = getInfo();
const isEmbedded = window.self !== window.top;
const showDocumentPipBtn = !isEmbedded && 'documentPictureInPicture' in window;
const socket = io({
transports: ['websocket'],
reconnection: isDesktopDevice,
@@ -412,6 +415,7 @@ function refreshMainButtonsToolTipPlacement() {
setTippy('editorButton', 'Toggle the editor', placement);
setTippy('transcriptionButton', 'Toggle transcription', placement);
setTippy('whiteboardButton', 'Toggle the whiteboard', placement);
setTippy('documentPiPButton', 'Toggle Document picture in picture', placement);
setTippy('snapshotRoomButton', 'Snapshot screen, window, or tab', placement);
setTippy('settingsButton', 'Toggle the settings', placement);
setTippy('restartICEButton', 'Restart ICE', placement);
@@ -1506,6 +1510,7 @@ function roomIsReady() {
show(fullScreenButton);
}
BUTTONS.main.whiteboardButton && show(whiteboardButton);
if (BUTTONS.main.documentPiPButton && showDocumentPipBtn) show(documentPiPButton);
BUTTONS.main.settingsButton && show(settingsButton);
isAudioAllowed ? show(stopAudioButton) : BUTTONS.main.startAudioButton && show(startAudioButton);
isVideoAllowed ? show(stopVideoButton) : BUTTONS.main.startVideoButton && show(startVideoButton);
@@ -2044,6 +2049,9 @@ function handleButtons() {
whiteboardButton.onclick = () => {
toggleWhiteboard();
};
documentPiPButton.onclick = () => {
rc.toggleDocumentPIP();
};
snapshotRoomButton.onclick = () => {
rc.snapshotRoom();
};
@@ -2872,6 +2880,17 @@ function handleSelects() {
}
whiteboardButton.click();
break;
case 'd':
if (notPresenter && !BUTTONS.main.documentPiPButton) {
userLog(
'warning',
'The presenter has disabled your ability to open the document PIP',
'top-end',
);
break;
}
documentPiPButton.click();
break;
case 'j':
if (notPresenter && !BUTTONS.main.emojiRoomButton) {
userLog('warning', 'The presenter has disabled your ability to open the room emoji', 'top-end');

عرض الملف

@@ -3512,6 +3512,134 @@ class RoomClient {
}
}
// ####################################################
// HANDLE DOCUMENT PIP
// ####################################################
async toggleDocumentPIP() {
if (documentPictureInPicture.window) {
documentPictureInPicture.window.close();
console.log('DOCUMENT PIP close');
return;
}
await this.documentPictureInPictureOpen();
}
documentPictureInPictureClose() {
if (!showDocumentPipBtn) return;
if (documentPictureInPicture.window) {
documentPictureInPicture.window.close();
console.log('DOCUMENT PIP close');
}
}
async documentPictureInPictureOpen() {
if (!showDocumentPipBtn) return;
try {
const pipWindow = await documentPictureInPicture.requestWindow({
width: 300,
height: 720,
});
function updateCustomProperties() {
const documentStyle = getComputedStyle(document.documentElement);
pipWindow.document.documentElement.style = `
--body-bg: ${documentStyle.getPropertyValue('--body-bg')};
`;
}
updateCustomProperties();
const pipStylesheet = document.createElement('link');
const pipVideoContainer = document.createElement('div');
pipStylesheet.type = 'text/css';
pipStylesheet.rel = 'stylesheet';
pipStylesheet.href = '../css/DocumentPiP.css';
pipVideoContainer.className = 'pipVideoContainer';
pipWindow.document.head.append(pipStylesheet);
pipWindow.document.body.append(pipVideoContainer);
function cloneVideoElements() {
let foundVideo = false;
pipVideoContainer.innerHTML = '';
[...document.querySelectorAll('video')].forEach((video) => {
console.log('DOCUMENT PIP found video id -----> ' + video.id);
// No video stream detected or is video share from URL...
if (!video.srcObject || video.id === '__videoShare') return;
let videoPIPAllowed = false;
// get video element
const videoPlayer = rc.getId(video.id);
// Check if video can be add on pipVideo
if ([rc.videoProducerId, rc.screenProducerId].includes(video.id)) {
// PRODUCER
videoPIPAllowed = !videoPlayer.classList.contains('videoCircle'); // not in privacy mode
console.log('DOCUMENT PIP PRODUCER videoPIPAllowed -----> ' + videoPIPAllowed);
} else {
// CONSUMER
videoPIPAllowed = !videoPlayer.classList.contains('videoCircle'); // not in privacy mode
console.log('DOCUMENT PIP CONAUMER videoPIPAllowed -----> ' + videoPIPAllowed);
}
if (!videoPIPAllowed) return;
// Video is ON not in privacy mode continue....
foundVideo = true;
const pipVideo = document.createElement('video');
pipVideo.classList.add('pipVideo');
pipVideo.classList.toggle('mirror', video.classList.contains('mirror'));
pipVideo.srcObject = video.srcObject;
pipVideo.autoplay = true;
pipVideo.muted = true;
pipVideoContainer.append(pipVideo);
});
return foundVideo;
}
if (!cloneVideoElements()) {
rc.documentPictureInPictureClose();
return userLog('warning', 'No video allowed for Document PIP', 'top-end', 6000);
}
const videoObserver = new MutationObserver(() => {
cloneVideoElements();
});
videoObserver.observe(rc.videoMediaContainer, {
childList: true,
});
const documentObserver = new MutationObserver(() => {
updateCustomProperties();
});
documentObserver.observe(document.documentElement, {
attributeFilter: ['style'],
});
pipWindow.addEventListener('unload', () => {
videoObserver.disconnect();
documentObserver.disconnect();
});
} catch (err) {
userLog('warning', err.message, 'top-end', 6000);
}
}
// ####################################################
// FULL SCREEN
// ####################################################

عرض الملف

@@ -23,10 +23,11 @@ let BUTTONS = {
swapCameraButton: true,
chatButton: true,
pollButton: true,
editorButton: true,
raiseHandButton: true,
transcriptionButton: true,
whiteboardButton: true,
editorButton: true,
documentPiPButton: true,
snapshotRoomButton: true,
emojiRoomButton: true,
settingsButton: true,
@@ -240,6 +241,7 @@ function handleRulesBroadcasting() {
BUTTONS.main.swapCameraButton = false;
//BUTTONS.main.raiseHandButton = false;
BUTTONS.main.whiteboardButton = false;
BUTTONS.main.documentPiPButton = false;
//BUTTONS.main.snapshotRoomButton = false;
//BUTTONS.main.emojiRoomButton = false,
//BUTTONS.main.pollButton = false;
@@ -275,6 +277,7 @@ function handleRulesBroadcasting() {
elemDisplay('swapCameraButton', false);
//elemDisplay('raiseHandButton', false);
elemDisplay('whiteboardButton', false);
elemDisplay('documentPiPButton', false);
//elemDisplay('snapshotRoomButton', false);
//elemDisplay('emojiRoomButton', false);
//elemDisplay('pollButton', false);

عرض الملف

@@ -192,6 +192,7 @@ access to use this app.
<button id="pollButton" class="hidden"><i class="fas fa-square-poll-horizontal"></i></button>
<button id="editorButton" class="hidden"><i class="fas fa-pen-to-square"></i></button>
<button id="whiteboardButton" class="hidden"><i class="fas fa-chalkboard-teacher"></i></button>
<button id="documentPiPButton" class="hidden"><i class="fas fa-images"></i></button>
<button id="snapshotRoomButton" class="hidden"><i class="fas fas fa-camera-retro"></i></button>
<button id="settingsButton" class="hidden"><i class="fas fa-cogs"></i></button>
<button id="restartICEButton" class="hidden"><i class="fas fa-satellite-dish"></i></button>