[mirotalksfu] #212 - add maximize/minimize button
هذا الالتزام موجود في:
@@ -64,7 +64,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.8.48
|
* @version 1.8.49
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.8.48",
|
"version": "1.8.49",
|
||||||
"description": "WebRTC SFU browser-based video calls",
|
"description": "WebRTC SFU browser-based video calls",
|
||||||
"main": "Server.js",
|
"main": "Server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -162,7 +162,10 @@
|
|||||||
.videoMenuBarShare {
|
.videoMenuBarShare {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: inline;
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1px;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ let BRAND = {
|
|||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
imageUrl: '../images/mirotalk-logo.gif',
|
imageUrl: '../images/mirotalk-logo.gif',
|
||||||
title: '<strong>WebRTC SFU v1.8.48</strong>',
|
title: '<strong>WebRTC SFU v1.8.49</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.8.48
|
* @version 1.8.49
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -5445,7 +5445,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.8.48',
|
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.8.49',
|
||||||
html: `
|
html: `
|
||||||
<br />
|
<br />
|
||||||
<div id="about">
|
<div id="about">
|
||||||
@@ -5487,4 +5487,4 @@ function showAbout() {
|
|||||||
showClass: { popup: 'animate__animated animate__fadeInDown' },
|
showClass: { popup: 'animate__animated animate__fadeInDown' },
|
||||||
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
|
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.8.48
|
* @version 1.8.49
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -383,7 +383,7 @@ class RoomClient {
|
|||||||
this.recSyncChunkSize = 1000000; // 1MB
|
this.recSyncChunkSize = 1000000; // 1MB
|
||||||
|
|
||||||
// Encodings
|
// Encodings
|
||||||
this.preferLocalCodecsOrder = false; // Prefer local codecs order
|
this.preferLocalCodecsOrder = false; // Prefer local codecs order
|
||||||
this.forceVP8 = false; // Force VP8 codec for webcam and screen sharing
|
this.forceVP8 = false; // Force VP8 codec for webcam and screen sharing
|
||||||
this.forceVP9 = false; // Force VP9 codec for webcam and screen sharing
|
this.forceVP9 = false; // Force VP9 codec for webcam and screen sharing
|
||||||
this.forceH264 = false; // Force H264 codec for webcam and screen sharing
|
this.forceH264 = false; // Force H264 codec for webcam and screen sharing
|
||||||
@@ -749,11 +749,11 @@ class RoomClient {
|
|||||||
try {
|
try {
|
||||||
await device.load({
|
await device.load({
|
||||||
routerRtpCapabilities,
|
routerRtpCapabilities,
|
||||||
preferLocalCodecsOrder: !!this.preferLocalCodecsOrder
|
preferLocalCodecsOrder: !!this.preferLocalCodecsOrder,
|
||||||
});
|
});
|
||||||
console.log(
|
console.log(
|
||||||
`Device loaded successfully with router RTP capabilities (preferLocalCodecsOrder: ${!!this.preferLocalCodecsOrder})`,
|
`Device loaded successfully with router RTP capabilities (preferLocalCodecsOrder: ${!!this.preferLocalCodecsOrder})`,
|
||||||
device.rtpCapabilities
|
device.rtpCapabilities,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading device with router RTP capabilities:', error);
|
console.error('Error loading device with router RTP capabilities:', error);
|
||||||
@@ -5324,8 +5324,8 @@ class RoomClient {
|
|||||||
if (!input || typeof input !== 'string') return false;
|
if (!input || typeof input !== 'string') return false;
|
||||||
try {
|
try {
|
||||||
const url = new URL(input);
|
const url = new URL(input);
|
||||||
return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff', '.svg'].some(ext =>
|
return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff', '.svg'].some((ext) =>
|
||||||
url.pathname.toLowerCase().endsWith(ext)
|
url.pathname.toLowerCase().endsWith(ext),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
@@ -7147,7 +7147,7 @@ class RoomClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openVideo(data) {
|
openVideo(data) {
|
||||||
let d, vb, e, video, pn;
|
let d, vb, e, video, pn, fsBtn;
|
||||||
let peer_name = data.peer_name;
|
let peer_name = data.peer_name;
|
||||||
let video_url = data.video_url + (this.isMobileSafari ? '&enablejsapi=1&mute=1' : ''); // Safari need user interaction
|
let video_url = data.video_url + (this.isMobileSafari ? '&enablejsapi=1&mute=1' : ''); // Safari need user interaction
|
||||||
let is_youtube = data.is_youtube;
|
let is_youtube = data.is_youtube;
|
||||||
@@ -7162,6 +7162,8 @@ class RoomClient {
|
|||||||
vb.className = 'videoMenuBarShare fadein';
|
vb.className = 'videoMenuBarShare fadein';
|
||||||
e = this.createButton('__videoExit', 'fas fa-times');
|
e = this.createButton('__videoExit', 'fas fa-times');
|
||||||
pn = this.createButton('__pinUnpin', html.pin);
|
pn = this.createButton('__pinUnpin', html.pin);
|
||||||
|
fsBtn = this.createButton('__videoFS', html.fullScreen);
|
||||||
|
|
||||||
if (is_youtube) {
|
if (is_youtube) {
|
||||||
video = document.createElement('iframe');
|
video = document.createElement('iframe');
|
||||||
video.setAttribute('title', peer_name);
|
video.setAttribute('title', peer_name);
|
||||||
@@ -7179,20 +7181,17 @@ class RoomClient {
|
|||||||
allowEscapeKey: false,
|
allowEscapeKey: false,
|
||||||
background: swalBackground,
|
background: swalBackground,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
// icon: 'info',
|
|
||||||
imageUrl: image.videoShare,
|
imageUrl: image.videoShare,
|
||||||
title: 'Unmute Video',
|
title: 'Unmute Video',
|
||||||
text: 'Tap the button below to unmute and play the video with sound.',
|
text: 'Tap the button below to unmute and play the video with sound.',
|
||||||
confirmButtonText: 'Unmute',
|
confirmButtonText: 'Unmute',
|
||||||
didOpen: () => {
|
didOpen: () => {
|
||||||
// Focus on the button when the popup opens
|
|
||||||
const unmuteButton = Swal.getConfirmButton();
|
const unmuteButton = Swal.getConfirmButton();
|
||||||
if (unmuteButton) unmuteButton.focus();
|
if (unmuteButton) unmuteButton.focus();
|
||||||
},
|
},
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
if (video && video.contentWindow) {
|
if (video && video.contentWindow) {
|
||||||
// Unmute the video and play
|
|
||||||
video.contentWindow.postMessage('{"event":"command","func":"unMute","args":""}', '*');
|
video.contentWindow.postMessage('{"event":"command","func":"unMute","args":""}', '*');
|
||||||
video.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
|
video.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
|
||||||
}
|
}
|
||||||
@@ -7213,11 +7212,77 @@ class RoomClient {
|
|||||||
video.setAttribute('width', '100%');
|
video.setAttribute('width', '100%');
|
||||||
video.setAttribute('height', '100%');
|
video.setAttribute('height', '100%');
|
||||||
vb.appendChild(e);
|
vb.appendChild(e);
|
||||||
|
vb.appendChild(fsBtn);
|
||||||
if (!this.isMobileDevice) vb.appendChild(pn);
|
if (!this.isMobileDevice) vb.appendChild(pn);
|
||||||
d.appendChild(video);
|
d.appendChild(video);
|
||||||
d.appendChild(vb);
|
d.appendChild(vb);
|
||||||
this.videoMediaContainer.appendChild(d);
|
this.videoMediaContainer.appendChild(d);
|
||||||
|
|
||||||
|
fsBtn.addEventListener('click', () => {
|
||||||
|
// Try to use the Fullscreen API
|
||||||
|
if (
|
||||||
|
video.requestFullscreen ||
|
||||||
|
video.webkitRequestFullscreen ||
|
||||||
|
video.mozRequestFullScreen ||
|
||||||
|
video.msRequestFullscreen
|
||||||
|
) {
|
||||||
|
this.isFullScreen() ? this.goOutFullscreen(video) : this.goInFullscreen(video);
|
||||||
|
} else {
|
||||||
|
elemDisplay('__videoFS', false);
|
||||||
|
|
||||||
|
// Maximize video with CSS
|
||||||
|
video.style.position = 'fixed';
|
||||||
|
video.style.top = 0;
|
||||||
|
video.style.left = 0;
|
||||||
|
video.style.width = '100vw';
|
||||||
|
video.style.height = '100vh';
|
||||||
|
video.style.zIndex = 9999;
|
||||||
|
|
||||||
|
// Add a close/maximize button for fallback
|
||||||
|
let isMaximized = true;
|
||||||
|
const closeBtn = document.createElement('button');
|
||||||
|
closeBtn.innerText = isMaximized ? 'Minimize' : 'Maximize';
|
||||||
|
closeBtn.style.position = 'absolute';
|
||||||
|
closeBtn.style.top = '1px';
|
||||||
|
closeBtn.style.left = '1px';
|
||||||
|
closeBtn.style.zIndex = 10000;
|
||||||
|
closeBtn.style.background = 'rgba(0,0,0,0.5)';
|
||||||
|
closeBtn.style.color = '#fff';
|
||||||
|
closeBtn.style.border = 'none';
|
||||||
|
closeBtn.style.padding = '8px 12px';
|
||||||
|
closeBtn.style.borderRadius = '4px';
|
||||||
|
closeBtn.style.cursor = 'pointer';
|
||||||
|
|
||||||
|
closeBtn.onclick = () => {
|
||||||
|
if (isMaximized) {
|
||||||
|
video.style.position = '';
|
||||||
|
video.style.top = '';
|
||||||
|
video.style.left = '';
|
||||||
|
video.style.width = '';
|
||||||
|
video.style.height = '';
|
||||||
|
video.style.zIndex = '';
|
||||||
|
closeBtn.innerText = 'Maximize';
|
||||||
|
isMaximized = false;
|
||||||
|
} else {
|
||||||
|
video.style.position = 'fixed';
|
||||||
|
video.style.top = 0;
|
||||||
|
video.style.left = 0;
|
||||||
|
video.style.width = '100vw';
|
||||||
|
video.style.height = '100vh';
|
||||||
|
video.style.zIndex = 9999;
|
||||||
|
closeBtn.innerText = 'Minimize';
|
||||||
|
isMaximized = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure only one button is added
|
||||||
|
if (!video.parentNode.querySelector('.mobile-video-close-btn')) {
|
||||||
|
closeBtn.classList.add('mobile-video-close-btn');
|
||||||
|
video.parentNode.appendChild(closeBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const exitVideoBtn = this.getId(e.id);
|
const exitVideoBtn = this.getId(e.id);
|
||||||
exitVideoBtn.addEventListener('click', (e) => {
|
exitVideoBtn.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -7231,6 +7296,7 @@ class RoomClient {
|
|||||||
if (!this.isMobileDevice) {
|
if (!this.isMobileDevice) {
|
||||||
this.setTippy(pn.id, 'Toggle Pin video player', 'bottom');
|
this.setTippy(pn.id, 'Toggle Pin video player', 'bottom');
|
||||||
this.setTippy(e.id, 'Close video player', 'bottom');
|
this.setTippy(e.id, 'Close video player', 'bottom');
|
||||||
|
this.setTippy(fsBtn.id, 'Full screen', 'bottom');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAspectRatio();
|
handleAspectRatio();
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم