[mirotalksfu] - add YouTube share video
هذا الالتزام موجود في:
@@ -20,6 +20,7 @@ Powered by `WebRTC` with [SFU](https://mediasoup.org) integrated server.
|
||||
- Collaborative whiteboard for the teachers
|
||||
- Select Microphone - Speaker and Video source
|
||||
- Recording your Screen, Audio or Video
|
||||
- Share any YouTube video in real time to your participants
|
||||
- Full Screen Mode on mouse click on the Video element
|
||||
- Supports [REST API](api/README.md) (Application Programming Interface)
|
||||
|
||||
|
||||
@@ -139,6 +139,14 @@ access to use this app.
|
||||
<button id="startScreenButton" class="hidden"><i class="fas fa-desktop"></i> Start screen</button>
|
||||
<button id="stopScreenButton" class="hidden"><i class="fas fa-stop-circle"></i> Stop screen</button>
|
||||
<button id="fileShareButton" class="hidden"><i class="fas fa-folder-open"></i> File Sharing</button>
|
||||
<div class="dropdown">
|
||||
<button id="youTubeShareButton" class="hidden"><i class="fab fa-youtube"></i> YouTube</button>
|
||||
<div id="youTubeSettings" class="dropdown-content fadein">
|
||||
<div id="youTubeOptions">
|
||||
<button id="youTubeCloseBtn"><i class="fas fa-times"></i> Close Video</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button id="whiteboardButton" class="hidden">
|
||||
<i class="fas fa-chalkboard-teacher"></i> Whiteboard
|
||||
|
||||
@@ -611,6 +611,20 @@ progress {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# YouTube Video
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
#youTubeSettings {
|
||||
display: 'none';
|
||||
position: relative;
|
||||
background: var(--body-bg);
|
||||
}
|
||||
|
||||
#youTubeCloseBtn:hover {
|
||||
color: red;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Whiteboard
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
ثنائية
public/images/youtube.png
Normal file
ثنائية
public/images/youtube.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 2.4 KiB |
@@ -434,6 +434,7 @@ function roomIsReady() {
|
||||
show(raiseHandButton);
|
||||
if (isAudioAllowed) show(startAudioButton);
|
||||
if (isVideoAllowed) show(startVideoButton);
|
||||
show(youTubeShareButton);
|
||||
show(fileShareButton);
|
||||
show(whiteboardButton);
|
||||
show(participantsButton);
|
||||
@@ -612,6 +613,12 @@ function handleButtons() {
|
||||
fileShareButton.onclick = () => {
|
||||
rc.selectFileToShare();
|
||||
};
|
||||
youTubeShareButton.onclick = () => {
|
||||
rc.youTubeShareVideo();
|
||||
};
|
||||
youTubeCloseBtn.onclick = () => {
|
||||
rc.closeYouTube(true);
|
||||
};
|
||||
sendAbortBtn.onclick = () => {
|
||||
rc.abortFileTransfer();
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ const image = {
|
||||
locked: '../images/locked.png',
|
||||
mute: '../images/mute.png',
|
||||
hide: '../images/hide.png',
|
||||
youtube: '../images/youtube.png',
|
||||
};
|
||||
|
||||
const mediaType = {
|
||||
@@ -458,6 +459,13 @@ class RoomClient {
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
this.socket.on(
|
||||
'youTubeAction',
|
||||
function (data) {
|
||||
this.youTubeAction(data);
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
this.socket.on(
|
||||
'fileAbort',
|
||||
function (data) {
|
||||
@@ -1857,6 +1865,117 @@ class RoomClient {
|
||||
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// YOUTUBE SHARE VIDEO
|
||||
// ####################################################
|
||||
|
||||
youTubeShareVideo() {
|
||||
rc.sound('open');
|
||||
|
||||
Swal.fire({
|
||||
background: swalBackground,
|
||||
position: 'center',
|
||||
imageUrl: image.youtube,
|
||||
title: 'Share YouTube Video',
|
||||
text: 'Past YouTube video URL',
|
||||
input: 'text',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: `Share`,
|
||||
showClass: {
|
||||
popup: 'animate__animated animate__fadeInDown',
|
||||
},
|
||||
hideClass: {
|
||||
popup: 'animate__animated animate__fadeOutUp',
|
||||
},
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
if (!this.thereIsParticipants()) {
|
||||
userLog('info', 'No participants detected', 'top-end');
|
||||
return;
|
||||
}
|
||||
// https://www.youtube.com/watch?v=RT6_Id5-7-s
|
||||
|
||||
let you_tube_url = this.getYoutubeEmbed(result.value);
|
||||
if (you_tube_url) {
|
||||
let data = {
|
||||
peer_name: this.peer_name,
|
||||
you_tube_url: you_tube_url,
|
||||
action: 'open',
|
||||
};
|
||||
console.log('YouTube video URL: ', you_tube_url);
|
||||
this.socket.emit('youTubeAction', data);
|
||||
this.openYouTube(data);
|
||||
} else {
|
||||
this.userLog('error', 'Not valid YouTube URL', 'top-end');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getYoutubeEmbed(url) {
|
||||
let regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
||||
let match = url.match(regExp);
|
||||
return match && match[7].length == 11 ? 'https://www.youtube.com/embed/' + match[7] + '?autoplay=1' : false;
|
||||
}
|
||||
|
||||
youTubeAction(data) {
|
||||
let action = data.action;
|
||||
switch (action) {
|
||||
case 'open':
|
||||
this.userLog('info', `${peer_name} <i class="fab fa-youtube"></i> opened the YouTube video`, 'top-end');
|
||||
this.openYouTube(data);
|
||||
break;
|
||||
case 'close':
|
||||
this.userLog('info', `${peer_name} <i class="fab fa-youtube"></i> closed the YouTube video`, 'top-end');
|
||||
this.closeYouTube();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
openYouTube(data) {
|
||||
let d, iframe;
|
||||
let peer_name = data.peer_name;
|
||||
let you_tube_url = data.you_tube_url;
|
||||
this.closeYouTube();
|
||||
youTubeSettings.style.display = 'block';
|
||||
d = document.createElement('div');
|
||||
d.className = 'Camera';
|
||||
d.id = '__youTube';
|
||||
iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('id', '__youTubeIframe');
|
||||
iframe.setAttribute('title', peer_name);
|
||||
iframe.setAttribute('width', '100%');
|
||||
iframe.setAttribute('height', '100%');
|
||||
iframe.setAttribute('src', you_tube_url);
|
||||
iframe.setAttribute(
|
||||
'allow',
|
||||
'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
|
||||
);
|
||||
iframe.setAttribute('frameborder', '0');
|
||||
iframe.setAttribute('allowfullscreen', true);
|
||||
d.appendChild(iframe);
|
||||
this.videoMediaContainer.appendChild(d);
|
||||
resizeVideoMedia();
|
||||
rc.sound('joined');
|
||||
}
|
||||
|
||||
closeYouTube(emit = false) {
|
||||
if (emit) {
|
||||
let data = {
|
||||
peer_name: this.peer_name,
|
||||
action: 'close',
|
||||
};
|
||||
this.socket.emit('youTubeAction', data);
|
||||
}
|
||||
let youTubeDiv = this.getId('__youTube');
|
||||
if (youTubeDiv) {
|
||||
youTubeSettings.style.display = 'none';
|
||||
youTubeDiv.parentNode.removeChild(youTubeDiv);
|
||||
resizeVideoMedia();
|
||||
rc.sound('left');
|
||||
}
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// ROOM ACTION
|
||||
// ####################################################
|
||||
|
||||
@@ -117,8 +117,8 @@
|
||||
</h2>
|
||||
<p class="section-paragraph mb-0">
|
||||
MiroTalk with SFU integrated Server. We engineered a platform with maximum video
|
||||
quality lowest latency that makes your calls crystal clear. Desktop and Mobile
|
||||
compatible!
|
||||
quality lowest latency that makes your calls crystal clear. Compatible with Desktop
|
||||
and Mobile devices!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +140,7 @@ async function ngrokStart() {
|
||||
tunnel: tunnel,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Ngrok Start error: ', err);
|
||||
log.error('Ngrok Start error: ', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,7 @@ async function createWorkers() {
|
||||
rtcMaxPort: config.mediasoup.worker.rtcMaxPort,
|
||||
});
|
||||
worker.on('died', () => {
|
||||
console.error('Mediasoup worker died, exiting in 2 seconds... [pid:%d]', worker.pid);
|
||||
log.error('Mediasoup worker died, exiting in 2 seconds... [pid:%d]', worker.pid);
|
||||
setTimeout(() => process.exit(1), 2000);
|
||||
});
|
||||
workers.push(worker);
|
||||
@@ -265,6 +265,11 @@ io.on('connection', (socket) => {
|
||||
roomList.get(socket.room_id).broadCast(socket.id, 'fileAbort', data);
|
||||
});
|
||||
|
||||
socket.on('youTubeAction', (data) => {
|
||||
log.debug('YouTube: ', data);
|
||||
roomList.get(socket.room_id).broadCast(socket.id, 'youTubeAction', data);
|
||||
});
|
||||
|
||||
socket.on('wbCanvasToJson', (data) => {
|
||||
// let objLength = bytesToSize(Object.keys(data).length);
|
||||
// log.debug('Send Whiteboard canvas JSON', { length: objLength });
|
||||
@@ -328,7 +333,7 @@ io.on('connection', (socket) => {
|
||||
const { params } = await roomList.get(socket.room_id).createWebRtcTransport(socket.id);
|
||||
callback(params);
|
||||
} catch (err) {
|
||||
console.error('Create WebRtc Transport error: ', err);
|
||||
log.error('Create WebRtc Transport error: ', err);
|
||||
callback({
|
||||
error: err.message,
|
||||
});
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم