[mirotalk] - refactoring live caption feature

This commit is contained in:
Miroslav Pejic
2022-01-13 19:07:49 +01:00
parent 966272074e
commit 1423b86623
8 changed files with 601 additions and 559 deletions
+2 -9
View File
@@ -221,7 +221,7 @@ function getMeetingURL(host) {
// end of MiroTalk API v1
// not match any of page before, so 404 not found
app.get('*', function(req, res) {
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, '../../', 'public/view/404.html'));
});
@@ -695,13 +695,6 @@ io.sockets.on('connect', (socket) => {
let room_id = config.room_id;
sendToRoom(room_id, socket.id, 'whiteboardAction', config);
});
// for live subtitle or transcripts
socket.on('speech_transcript', (config) => {
log.debug(config);
let room_id = config.room_id;
sendToRoom(room_id, socket.id, 'speech_transcript', config);
});
}); // end [sockets.on-connect]
/**
@@ -731,4 +724,4 @@ async function sendToPeer(peer_id, sockets, msg, config = {}) {
if (peer_id in sockets) {
await sockets[peer_id].emit(msg, config);
}
}
}
+8 -4
View File
@@ -982,7 +982,9 @@ video:fullscreen {
#myPeerNameSetBtn,
#muteEveryoneBtn,
#hideEveryoneBtn,
#lockUnlockRoomBtn {
#lockUnlockRoomBtn,
#speechRecognitionStart,
#speechRecognitionStop {
padding: 5px;
border-radius: 5px;
color: white;
@@ -990,7 +992,9 @@ video:fullscreen {
}
#myPeerNameSetBtn:hover,
#lockUnlockRoomBtn:hover {
#lockUnlockRoomBtn:hover,
#speechRecognitionStart:hover,
#speechRecognitionStop:hover {
color: var(--hover-color);
transform: var(--btns-hover-scale);
transition: all 0.3s ease-in-out;
@@ -1007,7 +1011,7 @@ video:fullscreen {
.tab {
overflow: hidden;
border: 1px solid rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.7);
background-color: rgba(0, 0, 0);
}
/* Style the buttons inside the tab */
@@ -1031,7 +1035,7 @@ video:fullscreen {
/* Create an active/current tablink class */
.tab button.active {
background-color: rgba(0, 0, 0, 0.7);
background-color: rgb(30 29 29);
}
/* Style the tab content */
+205 -131
View File
@@ -85,7 +85,7 @@ let myVideoStatus = true;
let myAudioStatus = true;
let isScreenStreaming = false;
let isChatRoomVisible = false;
let isCaptionBoxVisible = false; //added for caption button
let isCaptionBoxVisible = false;
let isChatEmojiVisible = false;
let isButtonsVisible = false;
let isMySettingsVisible = false;
@@ -134,7 +134,7 @@ let screenShareBtn;
let recordStreamBtn;
let fullScreenBtn;
let chatRoomBtn;
let captionBtn; //for text transcript
let captionBtn;
let myHandBtn;
let whiteboardBtn;
let fileShareBtn;
@@ -188,6 +188,8 @@ let screenFpsSelect;
let themeSelect;
let btnsBarSelect;
let selectors;
let speechRecognitionStart;
let speechRecognitionStop;
// my video element
let myVideo;
let myVideoWrap;
@@ -270,7 +272,7 @@ function getHtmlElementsById() {
screenShareBtn = getId('screenShareBtn');
recordStreamBtn = getId('recordStreamBtn');
fullScreenBtn = getId('fullScreenBtn');
captionBtn = getId('captionBtn'); //for getting caption buttons
captionBtn = getId('captionBtn');
chatRoomBtn = getId('chatRoomBtn');
whiteboardBtn = getId('whiteboardBtn');
fileShareBtn = getId('fileShareBtn');
@@ -324,6 +326,8 @@ function getHtmlElementsById() {
screenFpsSelect = getId('screenFps');
themeSelect = getId('mirotalkTheme');
btnsBarSelect = getId('mirotalkBtnsBar');
speechRecognitionStart = getId('speechRecognitionStart');
speechRecognitionStop = getId('speechRecognitionStop');
// my conference name, hand, video - audio status
myVideoParagraph = getId('myVideoParagraph');
myHandStatusIcon = getId('myHandStatusIcon');
@@ -401,9 +405,8 @@ function setButtonsTitle() {
content: 'OPEN the chat',
placement: 'right-start',
});
//for hover pop-over
tippy(captionBtn, {
content: 'See Caption',
content: 'OPEN the caption',
placement: 'right-start',
});
tippy(myHandBtn, {
@@ -445,7 +448,7 @@ function setButtonsTitle() {
content: 'Save messages',
});
tippy(msgerClose, {
content: 'Close the chat',
content: 'Close',
});
tippy(msgerEmojiBtn, {
content: 'Emoji',
@@ -454,6 +457,20 @@ function setButtonsTitle() {
content: 'Send',
});
// caption buttons
tippy(captionTheme, {
content: 'Ghost theme',
});
tippy(captionClean, {
content: 'Clean messages',
});
tippy(captionSaveBtn, {
content: 'Save messages',
});
tippy(msgerClose, {
content: 'Close',
});
// settings
tippy(mySettingsCloseBtn, {
content: 'Close settings',
@@ -462,6 +479,20 @@ function setButtonsTitle() {
content: 'Change name',
});
// tab btns
tippy(tabDevicesBtn, {
content: 'Devices',
});
tippy(tabBandwidthBtn, {
content: 'Bandwidth',
});
tippy(tabRoomBtn, {
content: 'Room',
});
tippy(tabStylingBtn, {
content: 'Styling',
});
// whiteboard btns
tippy(wbDrawingColorEl, {
content: 'DRAWING color',
@@ -687,7 +718,6 @@ function initClientPeer() {
signalingSocket.on('videoPlayer', handleVideoPlayer);
signalingSocket.on('disconnect', handleDisconnect);
signalingSocket.on('removePeer', handleRemovePeer);
signalingSocket.on('speech_transcript', handleSpeechTranscript);
} // end [initClientPeer]
/**
@@ -703,7 +733,7 @@ async function sendToServer(msg, config = {}) {
* Connected to Signaling Server. Once the user has given us access to their
* microphone/cam, join the channel and start peering up
*/
function handleConnect(socket) {
function handleConnect() {
console.log('Connected to signaling server');
if (localMediaStream) joinToChannel();
else
@@ -828,7 +858,8 @@ function welcomeUser() {
title: '<strong>Welcome ' + myPeerName + '</strong>',
imageAlt: 'mirotalk-welcome',
imageUrl: welcomeImg,
html: `
html:
`
<br/>
<p style="color:white;">Share this meeting invite others to join.</p>
<p style="color:rgb(8, 189, 89);">` +
@@ -959,9 +990,16 @@ function handleRTCDataChannels(peer_id) {
case 'mirotalk_chat_channel':
try {
let dataMessage = JSON.parse(msg.data);
handleDataChannelChat(dataMessage);
switch (dataMessage.type) {
case 'chat':
handleDataChannelChat(dataMessage);
break;
case 'speech':
handleDataChannelSpeechTranscript(dataMessage);
break;
}
} catch (err) {
console.error('handleDataChannelChat', err);
console.error('mirotalk_chat_channel', err);
}
break;
case 'mirotalk_file_sharing_channel':
@@ -969,7 +1007,7 @@ function handleRTCDataChannels(peer_id) {
let dataFile = msg.data;
handleDataChannelFileSharing(dataFile);
} catch (err) {
console.error('handleDataChannelFS', err);
console.error('mirotalk_file_sharing_channel', err);
}
break;
}
@@ -1245,7 +1283,7 @@ function setTheme(theme) {
document.documentElement.style.setProperty('--private-msg-bg', 'rgba(252, 110, 110, 0.7)');
document.documentElement.style.setProperty('--right-msg-bg', 'rgba(0, 0, 0, 0.7)');
break;
// ...
// ...
default:
console.log('No theme found');
}
@@ -1822,6 +1860,7 @@ function manageLeftButtons() {
setRecordStreamBtn();
setFullScreenBtn();
setChatRoomBtn();
setCaptionRoomBtn();
setChatEmojiBtn();
setMyHandBtn();
setMyWhiteboardBtn();
@@ -1836,7 +1875,7 @@ function manageLeftButtons() {
* Copy - share room url button click event
*/
function setShareRoomBtn() {
shareRoomBtn.addEventListener('click', async(e) => {
shareRoomBtn.addEventListener('click', async (e) => {
shareRoomUrl();
});
}
@@ -1934,7 +1973,7 @@ function setFullScreenBtn() {
*/
function setChatRoomBtn() {
// adapt chat room size for mobile
setChatRoomForMobile();
setChatRoomAndCaptionForMobile();
// open hide chat room
chatRoomBtn.addEventListener('click', (e) => {
@@ -1959,19 +1998,6 @@ function setChatRoomBtn() {
}
});
// ghost theme + undo
captionTheme.addEventListener('click', (e) => {
if (mirotalkTheme == 'ghost') return;
if (e.target.className == 'fas fa-ghost') {
e.target.className = 'fas fa-undo';
document.documentElement.style.setProperty('--msger-bg', 'rgba(0, 0, 0, 0.100)');
} else {
e.target.className = 'fas fa-ghost';
document.documentElement.style.setProperty('--msger-bg', 'linear-gradient(to left, #383838, #000000)');
}
});
// show msger participants section
msgerCPBtn.addEventListener('click', (e) => {
if (!thereIsPeerConnections()) {
@@ -1991,11 +2017,6 @@ function setChatRoomBtn() {
cleanMessages();
});
// clean caption transcripts
captionClean.addEventListener('click', (e) => {
cleanCaptions();
});
// save chat messages to file
msgerSaveBtn.addEventListener('click', (e) => {
if (chatMessages.length != 0) {
@@ -2005,37 +2026,12 @@ function setChatRoomBtn() {
userLog('info', 'No chat messages to save');
});
// save caption transcripts to file
captionSaveBtn.addEventListener('click', (e) => {
if (transcripts.length != 0) {
downloadCaptions();
return;
}
userLog('info', 'No captions to save');
});
// open hide chat room
captionBtn.addEventListener('click', (e) => {
if (!isCaptionBoxVisible) {
showCaptionDraggable();
} else {
//hideChatRoomAndEmojiPicker();
e.target.className = 'fas fa-closed-captioning';
}
});
// close chat room - show left button and status menu if hide
msgerClose.addEventListener('click', (e) => {
hideChatRoomAndEmojiPicker();
showButtonsBarAndMenu();
});
// close caption box - show left button and status menu if hide
captionClose.addEventListener('click', (e) => {
hideCaptionBox();
showButtonsBarAndMenu();
});
// open Video Url Player
msgerVideoUrlBtn.addEventListener('click', (e) => {
sendVideoUrl();
@@ -2051,7 +2047,7 @@ function setChatRoomBtn() {
});
// on input check 4emoji from map
msgerInput.oninput = function() {
msgerInput.oninput = function () {
for (let i in chatInputEmoji) {
let regex = new RegExp(escapeSpecialChars(i), 'gim');
this.value = this.value.replace(regex, chatInputEmoji[i]);
@@ -2066,6 +2062,70 @@ function setChatRoomBtn() {
});
}
/**
* Caption room buttons click event
*/
function setCaptionRoomBtn() {
if ('webkitSpeechRecognition' in window) {
// open hide caption
captionBtn.addEventListener('click', (e) => {
if (!isCaptionBoxVisible) {
showCaptionDraggable();
} else {
hideCaptionBox();
}
});
} else {
captionBtn.style.display = 'none';
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API#browser_compatibility
}
// ghost theme + undo
captionTheme.addEventListener('click', (e) => {
if (mirotalkTheme == 'ghost') return;
if (e.target.className == 'fas fa-ghost') {
e.target.className = 'fas fa-undo';
document.documentElement.style.setProperty('--msger-bg', 'rgba(0, 0, 0, 0.100)');
} else {
e.target.className = 'fas fa-ghost';
document.documentElement.style.setProperty('--msger-bg', 'linear-gradient(to left, #383838, #000000)');
}
});
// clean caption transcripts
captionClean.addEventListener('click', (e) => {
cleanCaptions();
});
// save caption transcripts to file
captionSaveBtn.addEventListener('click', (e) => {
if (transcripts.length != 0) {
downloadCaptions();
return;
}
userLog('info', 'No captions to save');
});
// close caption box - show left button and status menu if hide
captionClose.addEventListener('click', (e) => {
hideCaptionBox();
showButtonsBarAndMenu();
});
// hide it
speechRecognitionStop.style.display = 'none';
// start recognition speech
speechRecognitionStart.addEventListener('click', (e) => {
startSpeech(true);
});
// stop recognition speech
speechRecognitionStop.addEventListener('click', (e) => {
startSpeech(false);
});
}
/**
* Emoji picker chat room button click event
*/
@@ -2088,7 +2148,7 @@ function setChatEmojiBtn() {
* Set my hand button click event
*/
function setMyHandBtn() {
myHandBtn.addEventListener('click', async(e) => {
myHandBtn.addEventListener('click', async (e) => {
setMyHandStatus();
});
}
@@ -2376,10 +2436,10 @@ function getVideoConstraints(videoQuality) {
switch (videoQuality) {
case 'useVideo':
return useVideo;
// Firefox not support set frameRate (OverconstrainedError) O.o
// Firefox not support set frameRate (OverconstrainedError) O.o
case 'default':
return { frameRate: frameRate };
// video cam constraints default
// video cam constraints default
case 'qvgaVideo':
return {
width: { exact: 320 },
@@ -2581,7 +2641,13 @@ function attachMediaStream(element, stream) {
* if mobile and mySettings open do nothing return
*/
function showButtonsBarAndMenu() {
if (isButtonsVisible || (isMobileDevice && isChatRoomVisible) || (isMobileDevice && isMySettingsVisible)) return;
if (
isButtonsVisible ||
(isMobileDevice && isChatRoomVisible) ||
(isMobileDevice && isCaptionBoxVisible) ||
(isMobileDevice && isMySettingsVisible)
)
return;
toggleClassElements('statusMenu', 'inline');
buttonsBar.style.display = 'flex';
isButtonsVisible = true;
@@ -2628,7 +2694,8 @@ async function shareRoomUrl() {
title: 'Share the Room',
// imageAlt: 'mirotalk-share',
// imageUrl: shareUrlImg,
html: `
html:
`
<br/>
<div id="qrRoomContainer">
<canvas id="qrRoom"></canvas>
@@ -2716,9 +2783,9 @@ function handleAudio(e, init, force = null) {
localMediaStream.getAudioTracks()[0].enabled =
force != null ? force : !localMediaStream.getAudioTracks()[0].enabled;
myAudioStatus = localMediaStream.getAudioTracks()[0].enabled;
force != null ?
(e.className = 'fas fa-microphone' + (myAudioStatus ? '' : '-slash')) :
(e.target.className = 'fas fa-microphone' + (myAudioStatus ? '' : '-slash'));
force != null
? (e.className = 'fas fa-microphone' + (myAudioStatus ? '' : '-slash'))
: (e.target.className = 'fas fa-microphone' + (myAudioStatus ? '' : '-slash'));
if (init) {
audioBtn.className = 'fas fa-microphone' + (myAudioStatus ? '' : '-slash');
if (!isMobileDevice) {
@@ -2729,12 +2796,6 @@ function handleAudio(e, init, force = null) {
}
}
setMyAudioStatus(myAudioStatus);
if (myAudioStatus) {
start_stop_speech(true); //speech to text transcript function
} else {
start_stop_speech(false); //speech to text transcript function
}
}
/**
@@ -2747,9 +2808,9 @@ function handleVideo(e, init, force = null) {
localMediaStream.getVideoTracks()[0].enabled =
force != null ? force : !localMediaStream.getVideoTracks()[0].enabled;
myVideoStatus = localMediaStream.getVideoTracks()[0].enabled;
force != null ?
(e.className = 'fas fa-video' + (myVideoStatus ? '' : '-slash')) :
(e.target.className = 'fas fa-video' + (myVideoStatus ? '' : '-slash'));
force != null
? (e.className = 'fas fa-video' + (myVideoStatus ? '' : '-slash'))
: (e.target.className = 'fas fa-video' + (myVideoStatus ? '' : '-slash'));
if (init) {
videoBtn.className = 'fas fa-video' + (myVideoStatus ? '' : '-slash');
if (!isMobileDevice) {
@@ -3198,13 +3259,15 @@ function createChatDataChannel(peer_id) {
/**
* Set the chat room on full screen mode for mobile
*/
function setChatRoomForMobile() {
function setChatRoomAndCaptionForMobile() {
if (isMobileDevice) {
document.documentElement.style.setProperty('--msger-height', '99%');
document.documentElement.style.setProperty('--msger-width', '99%');
} else {
// make chat room draggable for desktop
dragElement(msgerDraggable, msgerHeader);
// make caption draggable for desktop
dragElement(captionDraggable, captionHeader);
}
}
@@ -3231,7 +3294,6 @@ function showChatRoomDraggable() {
}
}
/**
* Show caption box draggable on center screen position
*/
@@ -3318,8 +3380,6 @@ function cleanCaptions() {
});
}
/**
* Hide chat room and emoji picker
*/
@@ -3343,14 +3403,12 @@ function hideChatRoomAndEmojiPicker() {
*/
function hideCaptionBox() {
captionDraggable.style.display = 'none';
msgerEmojiPicker.style.display = 'none';
captionBtn.className = 'far fa-closed-captioning';
captionBtn.className = 'fas fa-closed-captioning';
isCaptionBoxVisible = false;
isChatEmojiVisible = false;
// only for desktop
if (!isMobileDevice) {
tippy(captionBtn, {
content: 'OPEN the caption box',
content: 'OPEN the caption',
placement: 'right-start',
});
}
@@ -3401,6 +3459,49 @@ function handleDataChannelChat(dataMessage) {
appendMessage(msgFrom, leftChatAvatar, 'left', msg, msgPrivate);
}
/**
* Handle text transcipt getting from peers
* @param {*} config
*/
function handleDataChannelSpeechTranscript(config) {
handleSpeechTranscript(config);
}
/**
* Handle text transcipt getting from peers
* @param {*} data
*/
function handleSpeechTranscript(config) {
if (!config) return;
let time_stamp = getFormatDate(new Date());
let name = config.peer_name;
let avatar_image = avatarApiUrl + '?name=' + name + '&size=32' + '&background=random&rounded=true';
let transcipt = config.text_data;
console.log('Handle speech transcript', config);
const msgHTML = `
<div class="msg left-msg">
<div class="msg-img" style="background-image: url('${avatar_image}')"></div>
<div>
<div class="msg-info">
<div class="msg-info-name" style="color:white;">${name} : ${time_stamp}</div>
</div>
<div class="msg-text" style="color:white;">${transcipt}</div>
</div>
</div>
`;
captionChat.insertAdjacentHTML('beforeend', msgHTML);
captionChat.scrollTop += 500;
transcripts.push({
time: time_stamp,
name: name,
caption: transcipt,
});
playSound('speech');
}
/**
* Escape Special Chars
* @param {*} regex
@@ -3578,6 +3679,7 @@ function emitMsg(from, to, msg, privateMsg) {
if (!msg) return;
let chatMessage = {
type: 'chat',
from: from,
to: to,
msg: msg,
@@ -4050,8 +4152,10 @@ function disableAllPeers(element) {
position: 'center',
imageUrl: element == 'audio' ? audioOffImg : camOffImg,
title: element == 'audio' ? 'Mute everyone except yourself?' : 'Hide everyone except yourself?',
text: element == 'audio' ?
"Once muted, you won't be able to unmute them, but they can unmute themselves at any time." : "Once hided, you won't be able to unhide them, but they can unhide themselves at any time.",
text:
element == 'audio'
? "Once muted, you won't be able to unmute them, but they can unmute themselves at any time."
: "Once hided, you won't be able to unhide them, but they can unhide themselves at any time.",
showDenyButton: true,
confirmButtonText: element == 'audio' ? `Mute` : `Hide`,
denyButtonText: `Cancel`,
@@ -4092,8 +4196,10 @@ function disablePeer(peer_id, element) {
position: 'center',
imageUrl: element == 'audio' ? audioOffImg : camOffImg,
title: element == 'audio' ? 'Mute this participant?' : 'Hide this participant?',
text: element == 'audio' ?
"Once muted, you won't be able to unmute them, but they can unmute themselves at any time." : "Once hided, you won't be able to unhide them, but they can unhide themselves at any time.",
text:
element == 'audio'
? "Once muted, you won't be able to unmute them, but they can unmute themselves at any time."
: "Once hided, you won't be able to unhide them, but they can unhide themselves at any time.",
showDenyButton: true,
confirmButtonText: element == 'audio' ? `Mute` : `Hide`,
denyButtonText: `Cancel`,
@@ -4314,7 +4420,7 @@ function whiteboardAddObj(type) {
if (result.isConfirmed) {
let wbCanvasImgURL = result.value;
if (isImageURL(wbCanvasImgURL)) {
fabric.Image.fromURL(wbCanvasImgURL, function(myImg) {
fabric.Image.fromURL(wbCanvasImgURL, function (myImg) {
addWbCanvasObj(myImg);
});
} else {
@@ -4342,10 +4448,10 @@ function whiteboardAddObj(type) {
let wbCanvasImg = result.value;
if (wbCanvasImg && wbCanvasImg.size > 0) {
let reader = new FileReader();
reader.onload = function(event) {
reader.onload = function (event) {
let imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
imgObj.onload = function () {
let image = new fabric.Image(imgObj);
image.set({ top: 0, left: 0 }).scale(0.3);
addWbCanvasObj(image);
@@ -4432,16 +4538,16 @@ function addWbCanvasObj(obj) {
* Whiteboard: Local listners
*/
function setupWhiteboardLocalListners() {
wbCanvas.on('mouse:down', function(e) {
wbCanvas.on('mouse:down', function (e) {
mouseDown(e);
});
wbCanvas.on('mouse:up', function() {
wbCanvas.on('mouse:up', function () {
mouseUp();
});
wbCanvas.on('mouse:move', function() {
wbCanvas.on('mouse:move', function () {
mouseMove();
});
wbCanvas.on('object:added', function() {
wbCanvas.on('object:added', function () {
objectAdded();
});
}
@@ -4662,7 +4768,7 @@ function handleWhiteboardAction(config, logme = true) {
case 'toggle':
toggleWhiteboard();
break;
//...
//...
}
}
@@ -5133,7 +5239,8 @@ function handleKickedOut(config) {
position: 'center',
imageUrl: kickedOutImg,
title: 'Kicked out!',
html: `<h2 style="color: red;">` +
html:
`<h2 style="color: red;">` +
`User ` +
peer_name +
`</h2> will kick out you after <b style="color: red;"></b> milliseconds.`,
@@ -5371,7 +5478,7 @@ function userLog(type, message) {
title: message,
});
break;
// ......
// ......
default:
alert(message);
}
@@ -5429,36 +5536,3 @@ function getSl(selector) {
function getEcN(className) {
return document.getElementsByClassName(className);
}
/**
* Handle text transcipt getting from peers
* @param {*} data
*/
function handleSpeechTranscript(config) {
let peer_id = config.peer_id;
let time_stamp = getFormatDate(new Date());;
let name = config.peer_name;
let avatar_image = avatarApiUrl + '?name=' + name + '&size=32' + '&background=random&rounded=true';
let transcipt = config.text_data;
console.log(config);
const msgHTML = `
<div class="msg left-msg">
<div class="msg-img" style="background-image: url('${avatar_image}')"></div>
<div>
<div class="msg-info">
<div class="msg-info-name" style="color:white;">${name}</div>
<div class="msg-info-time" style="color:white;">${time_stamp}</div>
</div>
<div class="msg-text" style="color:white;">${transcipt}</div>
</div>
</div>
`;
captionChat.insertAdjacentHTML('beforeend', msgHTML);
captionChat.scrollTop += 500;
transcripts.push({
time: time_stamp,
name: name,
caption: transcipt,
});
}
+79
View File
@@ -0,0 +1,79 @@
/**
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API
*/
let isWebkitSpeechRecognitionSupported = false;
let recognitionRunning = false;
let recognition;
if ('webkitSpeechRecognition' in window) {
recognition = new webkitSpeechRecognition();
recognition.maxAlternatives = 1;
recognition.continuous = true;
recognition.onstart = function () {
console.log('Start speech recognition');
speechRecognitionStart.style.display = 'none';
speechRecognitionStop.style.display = 'block';
};
// Detect the said words
recognition.onresult = (e) => {
let current = e.resultIndex;
// Get a transcript of what was said.
let transcript = e.results[current][0].transcript;
config = {
type: 'speech',
room_id: roomId,
peer_name: myPeerName,
text_data: transcript,
time_stamp: new Date(),
};
// save also my speech to text
handleSpeechTranscript(config);
if (thereIsPeerConnections()) {
// Send speech transcript through RTC Data Channels
for (let peer_id in chatDataChannels) {
if (chatDataChannels[peer_id].readyState === 'open')
chatDataChannels[peer_id].send(JSON.stringify(config));
}
}
};
recognition.onerror = function (event) {
console.warn('Speech recognition error', event.error);
};
recognition.onend = function () {
console.log('Stop speech recognition');
// if (recognitionRunning) recognition.start();
speechRecognitionStop.style.display = 'none';
speechRecognitionStart.style.display = 'block';
};
isWebkitSpeechRecognitionSupported = true;
console.info('Browser supports webkitSpeechRecognition');
} else {
console.warn(
'This browser not supports webkitSpeechRecognition, check out supported browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API#browser_compatibility',
);
}
//start or stop decider
function startSpeech(config) {
if (isWebkitSpeechRecognitionSupported) {
if (config) {
try {
recognitionRunning = true;
recognition.start();
} catch (error) {
console.log('Start speech', error);
}
} else {
recognitionRunning = false;
recognition.stop();
}
} else {
userLog('info', 'This browser not supports webkitSpeechRecognition');
}
}
-38
View File
@@ -1,38 +0,0 @@
let recordingStarted = false;
// initialize SpeechRecognition object
let recognition = new webkitSpeechRecognition();
recognition.maxAlternatives = 1;
recognition.continuous = true;
// Detect the said words
recognition.onresult = e => {
let current = event.resultIndex;
// Get a transcript of what was said.
let transcript = event.results[current][0].transcript;
// Add the current transcript with existing said values
config = {
room_id: roomId,
peer_name: myPeerName,
text_data: transcript,
time_stamp: new Date(),
};
sendToServer('speech_transcript', config); //sending data to signaling server for specific room
}
//start or stop decider
function start_stop_speech(config) {
if (config) {
try {
// Start recognition
recognition.start();
} catch (error) {
console.log(error);
}
} else {
//stop recognition
recognition.stop();
}
}
navigator.getUserMedia({ audio: true }, startUserMedia, function(e) {
__log('No live audio input: ' + e);
});
-96
View File
@@ -1,96 +0,0 @@
/*
The MIT License (MIT)
Copyright (c) 2014 Chris Wilson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
Usage:
audioNode = createAudioMeter(audioContext,clipLevel,averaging,clipLag);
audioContext: the AudioContext you're using.
clipLevel: the level (0 to 1) that you would consider "clipping".
Defaults to 0.98.
averaging: how "smoothed" you would like the meter to be over time.
Should be between 0 and less than 1. Defaults to 0.95.
clipLag: how long you would like the "clipping" indicator to show
after clipping has occured, in milliseconds. Defaults to 750ms.
Access the clipping through node.checkClipping(); use node.shutdown to get rid of it.
*/
function createAudioMeter(audioContext, clipLevel, averaging, clipLag) {
var processor = audioContext.createScriptProcessor(512);
processor.onaudioprocess = volumeAudioProcess;
processor.clipping = false;
processor.lastClip = 0;
processor.volume = 0;
processor.clipLevel = clipLevel || 0.98;
processor.averaging = averaging || 0.95;
processor.clipLag = clipLag || 750;
// this will have no effect, since we don't copy the input to the output,
// but works around a current Chrome bug.
processor.connect(audioContext.destination);
processor.checkClipping =
function() {
if (!this.clipping)
return false;
if ((this.lastClip + this.clipLag) < window.performance.now())
this.clipping = false;
return this.clipping;
};
processor.shutdown =
function() {
this.disconnect();
this.onaudioprocess = null;
};
return processor;
}
function volumeAudioProcess(event) {
var buf = event.inputBuffer.getChannelData(0);
var bufLength = buf.length;
var sum = 0;
var x;
// Do a root-mean-square on the samples: sum up the squares...
for (var i = 0; i < bufLength; i++) {
x = buf[i];
if (Math.abs(x) >= this.clipLevel) {
this.clipping = true;
this.lastClip = window.performance.now();
}
sum += x * x;
}
// ... then take the square root of the sum.
var rms = Math.sqrt(sum / bufLength);
// Now smooth this out with the averaging factor applied
// to the previous sample - take the max here because we
// want "fast attack, slow release."
this.volume = Math.max(rms, this.volume * this.averaging);
}
Binary file not shown.
+307 -281
View File
@@ -1,216 +1,237 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-3XM60XK9RQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
<script async src="https://www.googletagmanager.com/gtag/js?id=G-3XM60XK9RQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-3XM60XK9RQ');
</script>
gtag('config', 'G-3XM60XK9RQ');
</script>
<!-- Title and Icon -->
<!-- Title and Icon -->
<title>MiroTalk WebRTC Video call, Chat Room & Screen Sharing.</title>
<link rel="shortcut icon" href="../images/logo.svg" />
<link rel="apple-touch-icon" href="../images/logo.svg" />
<title>MiroTalk WebRTC Video call, Chat Room & Screen Sharing.</title>
<link rel="shortcut icon" href="../images/logo.svg" />
<link rel="apple-touch-icon" href="../images/logo.svg" />
<!-- Meta Information -->
<!-- Meta Information -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta
name="viewport"
content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<meta
name="description"
content="MiroTalk powered by WebRTC, Real-time Simple Secure Fast video calls, chat and screen sharing capabilities in the browser, from your mobile or desktop."
/>
<meta
name="keywords"
content="webrtc, webrtc stun, webrtc turn, video meeting, video chat, multi video chat, peer to peer, p2p, zoom"
/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="description" content="MiroTalk powered by WebRTC, Real-time Simple Secure Fast video calls, chat and screen sharing capabilities in the browser, from your mobile or desktop." />
<meta name="keywords" content="webrtc, webrtc stun, webrtc turn, video meeting, video chat, multi video chat, peer to peer, p2p, zoom" />
<!-- https://ogp.me -->
<!-- https://ogp.me -->
<meta property="og:type" content="app-webrtc" />
<meta property="og:site_name" content="MiroTalk" />
<meta property="og:title" content="Click the link to join this call." />
<meta property="og:description" content="Free WebRTC browser-based video call." />
<meta property="og:image" content="https://mirotalk.up.railway.app/images/preview.png" />
<meta property="og:url" content="https://mirotalk.herokuapp.com/" />
<link rel="icon" type="image/png" sizes="32x32" href="../images/favicon.png" />
<meta property="og:type" content="app-webrtc" />
<meta property="og:site_name" content="MiroTalk" />
<meta property="og:title" content="Click the link to join this call." />
<meta property="og:description" content="Free WebRTC browser-based video call." />
<meta property="og:image" content="https://mirotalk.up.railway.app/images/preview.png" />
<meta property="og:url" content="https://mirotalk.herokuapp.com/" />
<link rel="icon" type="image/png" sizes="32x32" href="../images/favicon.png" />
<!-- StyleSheet -->
<!-- StyleSheet -->
<link rel="stylesheet" href="../css/client.css" />
<link rel="stylesheet" href="../css/whiteboard.css" />
<!-- https://animate.style 4 using for swal fadeIn-Out -->
<link rel="stylesheet" href="../css/client.css" />
<link rel="stylesheet" href="../css/whiteboard.css" />
<!-- https://animate.style 4 using for swal fadeIn-Out -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
</head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
</head>
<body onload="initClientPeer()">
<noscript>You need to enable JavaScript to run this app.</noscript>
<body onload="initClientPeer()">
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- just 4SEO Optimization -->
<!-- just 4SEO Optimization -->
<div id="webRTCSeo">
<h1>WebRTC</h1>
</div>
<div id="webRTCSeo">
<h1>WebRTC</h1>
</div>
<!-- show this before to join -->
<!-- show this before to join -->
<div id="loadingDiv">
<h1 class="pulsate">Loading...</h1>
<pre>
<div id="loadingDiv">
<h1 class="pulsate">Loading...</h1>
<pre>
Please allow camera & microphone
access to use this app.
</pre>
</div>
</div>
<!-- Start buttons bar
<!-- Start buttons bar
https://fontawesome.com/icons?d=gallery
-->
<div id="buttonsBar">
<button id="shareRoomBtn" class="fas fa-users"></button>
<button id="audioBtn" class="fas fa-microphone"></button>
<button id="videoBtn" class="fas fa-video"></button>
<button id="swapCameraBtn" class="fas fa-sync-alt"></button>
<button id="screenShareBtn" class="fas fa-desktop"></button>
<button id="recordStreamBtn" class="fas fa-record-vinyl"></button>
<button id="fullScreenBtn" class="fas fa-expand-alt"></button>
<button id="chatRoomBtn" class="fas fa-comment"></button>
<button id="captionBtn" class="fas fa-closed-captioning"></button>
<button id="myHandBtn" class="fas fa-hand-paper"></button>
<button id="whiteboardBtn" class="fas fa-chalkboard-teacher"></button>
<button id="fileShareBtn" class="fas fa-folder-open"></button>
<button id="mySettingsBtn" class="fas fa-cogs"></button>
<button id="aboutBtn" class="fas fa-question"></button>
<button id="leaveRoomBtn" class="fas fa-phone-slash"></button>
</div>
<div id="buttonsBar">
<button id="shareRoomBtn" class="fas fa-users"></button>
<button id="audioBtn" class="fas fa-microphone"></button>
<button id="videoBtn" class="fas fa-video"></button>
<button id="swapCameraBtn" class="fas fa-sync-alt"></button>
<button id="screenShareBtn" class="fas fa-desktop"></button>
<button id="recordStreamBtn" class="fas fa-record-vinyl"></button>
<button id="fullScreenBtn" class="fas fa-expand-alt"></button>
<button id="chatRoomBtn" class="fas fa-comment"></button>
<button id="captionBtn" class="fas fa-closed-captioning"></button>
<button id="myHandBtn" class="fas fa-hand-paper"></button>
<button id="whiteboardBtn" class="fas fa-chalkboard-teacher"></button>
<button id="fileShareBtn" class="fas fa-folder-open"></button>
<button id="mySettingsBtn" class="fas fa-cogs"></button>
<button id="aboutBtn" class="fas fa-question"></button>
<button id="leaveRoomBtn" class="fas fa-phone-slash"></button>
</div>
<!-- End left buttons -->
<!-- End left buttons -->
<!-- Start chat room
<!-- Start chat room
https://codepen.io/sajadhsm/pen/odaBdd
https://getemoji.com
-->
<section id="msgerDraggable" class="msger-draggable">
<section id="msger" class="msger">
<header id="msgerHeader" class="msger-header">
<div class="msger-header-title"><i class="fas fa-comment-alt"></i> Chat</div>
<div class="msger-header-options">
<button id="msgerTheme" class="fas fa-ghost"></button>
<button id="msgerCPBtn" class="fas fa-users"></button>
<button id="msgerClean" class="fas fa-trash"></button>
<button id="msgerSaveBtn" class="fas fa-save"></button>
<button id="msgerClose" class="fas fa-times"></button>
</div>
</header>
<section id="msgerDraggable" class="msger-draggable">
<section id="msger" class="msger">
<header id="msgerHeader" class="msger-header">
<div class="msger-header-title"><i class="fas fa-comment-alt"></i> Chat</div>
<div class="msger-header-options">
<button id="msgerTheme" class="fas fa-ghost"></button>
<button id="msgerCPBtn" class="fas fa-users"></button>
<button id="msgerClean" class="fas fa-trash"></button>
<button id="msgerSaveBtn" class="fas fa-save"></button>
<button id="msgerClose" class="fas fa-times"></button>
</div>
</header>
<main id="msgerChat" class="msger-chat"></main>
<main id="msgerChat" class="msger-chat"></main>
<!-- Start emoji picker
<!-- Start emoji picker
https://github.com/nolanlawson/emoji-picker-element ]
-->
<section id="msgerEmojiPicker">
<emoji-picker class="dark"></emoji-picker>
<!-- <emoji-picker class="light"></emoji-picker> -->
<section id="msgerEmojiPicker">
<emoji-picker class="dark"></emoji-picker>
<!-- <emoji-picker class="light"></emoji-picker> -->
</section>
<!-- End emoji picker -->
<div class="msger-inputarea">
<input id="msgerInput" class="msger-input" type="text" placeholder="💬 Enter your message..." />
<button id="msgerVideoUrlBtn" class="fab fa-youtube"></button>
<button id="msgerEmojiBtn" class="fas fa-smile"></button>
<button id="msgerSendBtn" class="fas fa-paper-plane"></button>
</div>
</section>
<!-- End emoji picker -->
<!-- Start private msg -->
<div class="msger-inputarea">
<input id="msgerInput" class="msger-input" type="text" placeholder="💬 Enter your message..." />
<button id="msgerVideoUrlBtn" class="fab fa-youtube"></button>
<button id="msgerEmojiBtn" class="fas fa-smile"></button>
<button id="msgerSendBtn" class="fas fa-paper-plane"></button>
</div>
<section id="msgerCP">
<section id="msgerCPSec" class="msger">
<header id="msgerCPHeader" class="msger-private-header">
<div class="msger-header-title"><i class="fas fa-comment-alt"></i> Send Private messages</div>
<div class="msger-header-options">
<button id="msgerCPCloseBtn" class="fas fa-times"></button>
</div>
</header>
<main id="msgerCPChat" class="msger-chat">
<div class="search-container">
<input
id="searchPeerBarName"
type="text"
placeholder=" 🔍 Search peer by name..."
name="search"
onkeyup="searchPeer()"
/>
</div>
<br />
<div id="msgerCPList"></div>
</main>
</section>
</section>
<!-- End private msg -->
</section>
<!-- Start private msg -->
<!-- End chat room -->
<section id="msgerCP">
<section id="msgerCPSec" class="msger">
<header id="msgerCPHeader" class="msger-private-header">
<div class="msger-header-title"><i class="fas fa-comment-alt"></i> Send Private messages</div>
<!-- Start of caption section -->
<section id="captionDraggable" class="msger-draggable">
<section id="caption" class="msger">
<header id="captionHeader" class="msger-header">
<div class="msger-header-title"><i class="fas fa-comment-alt"></i> Captions</div>
<div class="msger-header-options">
<button id="msgerCPCloseBtn" class="fas fa-times"></button>
<button id="captionTheme" class="fas fa-ghost"></button>
<button id="captionClean" class="fas fa-trash"></button>
<button id="captionSaveBtn" class="fas fa-save"></button>
<button id="captionClose" class="fas fa-times"></button>
</div>
</header>
<main id="msgerCPChat" class="msger-chat">
<div class="search-container">
<input id="searchPeerBarName" type="text" placeholder=" 🔍 Search peer by name..." name="search" onkeyup="searchPeer()" />
</div>
<br />
<div id="msgerCPList"></div>
</main>
<main id="captionChat" class="msger-chat"></main>
<div class="msger-inputarea">
<label>Speech recognition</label>
<button type="button" id="speechRecognitionStart" class="fas fa-play">&nbsp;Start</button>
<button type="button" id="speechRecognitionStop" class="fas fa-stop">&nbsp;Stop</button>
</div>
</section>
</section>
<!-- End private msg -->
</section>
<!-- End of caption section -->
<!-- End chat room -->
<!-- Start my settings -->
<!-- start of caption section -->
<section id="captionDraggable" class="msger-draggable">
<section id="caption" class="msger">
<header id="captionHeader" class="msger-header">
<div class="caption-header-title"><i class="fas fa-comment-alt"></i>Captions</div>
<div class="caption-header-options">
<button id="captionTheme" class="fas fa-ghost"></button>
<button id="captionClean" class="fas fa-trash"></button>
<button id="captionSaveBtn" class="fas fa-save"></button>
<button id="captionClose" class="fas fa-times"></button>
</div>
<section id="mySettings">
<header id="mySettingsHeader">
<button id="mySettingsCloseBtn" class="fas fa-times"></button>
</header>
<main id="captionChat" class="msger-chat"></main>
</section>
</section>
<!-- End of caption section -->
<!-- Start my settings -->
<section id="mySettings">
<header id="mySettingsHeader">
<button id="mySettingsCloseBtn" class="fas fa-times"></button>
</header>
<main>
<br />
<div class="tab">
<button id="tabDevicesBtn" class="tablinks">Devices</button>
<button id="tabBandwidthBtn" class="tablinks">Bandwidth</button>
<button id="tabRoomBtn" class="tablinks">Room</button>
<button id="tabStylingBtn" class="tablinks">Styling</button>
</div>
<div id="tabDevices" class="tabcontent">
<main>
<br />
<div>
<label for="videoSource">Camera</label><br />
<select id="videoSource"></select>
<div class="tab">
<button id="tabDevicesBtn" class="fas fa-cog tablinks"></button>
<button id="tabBandwidthBtn" class="fas fa-wifi tablinks"></button>
<button id="tabRoomBtn" class="fas fa-home tablinks"></button>
<button id="tabStylingBtn" class="fas fa-palette tablinks"></button>
</div>
<br />
<div>
<label for="audioSource">Microphone</label><br />
<select id="audioSource"></select>
</div>
<br />
<div>
<label for="audioOutput">Speaker</label><br />
<select id="audioOutput"></select>
</div>
</div>
<div id="tabBandwidth" class="tabcontent">
<br />
<label for="videoQuality">Video quality</label>
<br />
<select id="videoQuality">
<div id="tabDevices" class="tabcontent">
<br />
<div>
<label for="videoSource">Camera</label><br />
<select id="videoSource"></select>
</div>
<br />
<div>
<label for="audioSource">Microphone</label><br />
<select id="audioSource"></select>
</div>
<br />
<div>
<label for="audioOutput">Speaker</label><br />
<select id="audioOutput"></select>
</div>
</div>
<div id="tabBandwidth" class="tabcontent">
<br />
<label for="videoQuality">Video quality</label>
<br />
<select id="videoQuality">
<option value="default">Default</option>
<option value="qvgaVideo">QVGA</option>
<option value="vgaVideo">VGA</option>
@@ -218,153 +239,158 @@ access to use this app.
<option value="fhdVideo">FULL HD</option>
<option value="4kVideo">4K</option>
</select>
<br /><br />
<label for="videoFps">Camera fps</label>
<br />
<select id="videoFps">
<option value="60">60 fps</option>
<option value="30">30 fps</option>
<option value="25">25 fps</option>
<option value="20">20 fps</option>
<option value="15">15 fps</option>
<option value="10">10 fps</option>
<option value="5">5 fps</option>
</select>
<br /><br />
<label for="screenFps">Screen fps</label>
<br />
<select id="screenFps">
<option value="60">60 fps</option>
<option value="30">30 fps</option>
<option value="25">25 fps</option>
<option value="20">20 fps</option>
<option value="15">15 fps</option>
<option value="10">10 fps</option>
<option value="5">5 fps</option>
</select>
</div>
<div id="tabRoom" class="tabcontent">
<br />
<div>
<label>My name</label><br />
<input id="myPeerNameSet" type="text" placeholder="Change name..." />&nbsp;&nbsp;
<button id="myPeerNameSetBtn" class="fas fa-user-edit">&nbsp;Change</button>
</div>
<br />
<div>
<label>Participants</label><br />
<button id="muteEveryoneBtn" class="fas fa-microphone">&nbsp;Mute everyone</button>&nbsp;&nbsp;
<button id="hideEveryoneBtn" class="fas fa-video">&nbsp;Hide everyone</button>
<br /><br />
<label>Security</label><br />
<button id="lockUnlockRoomBtn" class="fas fa-lock-open"></button>
<label for="videoFps">Camera fps</label>
<br />
<select id="videoFps">
<option value="60">60 fps</option>
<option value="30">30 fps</option>
<option value="25">25 fps</option>
<option value="20">20 fps</option>
<option value="15">15 fps</option>
<option value="10">10 fps</option>
<option value="5">5 fps</option>
</select>
<br /><br />
<label for="screenFps">Screen fps</label>
<br />
<select id="screenFps">
<option value="60">60 fps</option>
<option value="30">30 fps</option>
<option value="25">25 fps</option>
<option value="20">20 fps</option>
<option value="15">15 fps</option>
<option value="10">10 fps</option>
<option value="5">5 fps</option>
</select>
</div>
</div>
<div id="tabStyling" class="tabcontent">
<br />
<label for="mirotalkTheme">Theme color</label>
<br />
<select id="mirotalkTheme">
<div id="tabRoom" class="tabcontent">
<br />
<div>
<label>My name</label><br />
<input id="myPeerNameSet" type="text" placeholder="Change name..." />&nbsp;&nbsp;
<button id="myPeerNameSetBtn" class="fas fa-user-edit">&nbsp;Change</button>
</div>
<br />
<div>
<label>Participants</label><br />
<button id="muteEveryoneBtn" class="fas fa-microphone">&nbsp;Mute everyone</button>&nbsp;&nbsp;
<button id="hideEveryoneBtn" class="fas fa-video">&nbsp;Hide everyone</button>
<br /><br />
<label>Security</label><br />
<button id="lockUnlockRoomBtn" class="fas fa-lock-open"></button>
</div>
</div>
<div id="tabStyling" class="tabcontent">
<br />
<label for="mirotalkTheme">Theme color</label>
<br />
<select id="mirotalkTheme">
<option value="neon">🟣 MiroTalk - neon</option>
<option value="dark">⚫️ MiroTalk - dark</option>
<option value="forest">🟢 MiroTalk - forest</option>
<option value="sky">🔵 MiroTalk - sky</option>
<option value="ghost">⚪️ MiroTalk - ghost</option>
</select>
<br /><br />
<label for="mirotalkBtnsBar">Buttons bar</label>
<br />
<select id="mirotalkBtnsBar">
<br /><br />
<label for="mirotalkBtnsBar">Buttons bar</label>
<br />
<select id="mirotalkBtnsBar">
<option value="vertical">🚦 Vertical</option>
<option value="horizontal">🚥 Horizontal</option>
</select>
</div>
</main>
</section>
</div>
</main>
</section>
<!-- End my settings -->
<!-- End my settings -->
<!-- Start whiteboard -->
<!-- Start whiteboard -->
<section id="whiteboard" class="hidden">
<header id="whiteboardHeader" class="whiteboard-header">
<div id="whiteboardTitle" class="whiteboard-header-title"></div>
<div class="whiteboard-header-options">
pencil
<input id="wbDrawingColorEl" class="whiteboardColorPicker" type="color" value="#FFFFFF" /> background
<input id="wbBackgroundColorEl" class="whiteboardColorPicker" type="color" value="#000000" />
<button id="whiteboardPencilBtn" class="fas fa-pencil-alt"></button>
<button id="whiteboardObjectBtn" class="fas fa-mouse-pointer"></button>
<button id="whiteboardUndoBtn" class="fas fa-undo"></button>
<button id="whiteboardRedoBtn" class="fas fa-redo"></button>
<button id="whiteboardImgFileBtn" class="far fa-image"></button>
<button id="whiteboardImgUrlBtn" class="fas fa-link"></button>
<button id="whiteboardTextBtn" class="fas fa-spell-check"></button>
<button id="whiteboardLineBtn" class="fas fa-slash"></button>
<button id="whiteboardRectBtn" class="far fa-square"></button>
<button id="whiteboardCircleBtn" class="far fa-circle"></button>
<button id="whiteboardSaveBtn" class="fas fa-save"></button>
<button id="whiteboardEraserBtn" class="fas fa-eraser"></button>
<button id="whiteboardCleanBtn" class="fas fa-trash"></button>
<button id="whiteboardCloseBtn" class="fas fa-times"></button>
</div>
</header>
<main>
<canvas id="wbCanvas"></canvas>
</main>
</section>
<section id="whiteboard" class="hidden">
<header id="whiteboardHeader" class="whiteboard-header">
<div id="whiteboardTitle" class="whiteboard-header-title"></div>
<div class="whiteboard-header-options">
pencil
<input id="wbDrawingColorEl" class="whiteboardColorPicker" type="color" value="#FFFFFF" />
background
<input id="wbBackgroundColorEl" class="whiteboardColorPicker" type="color" value="#000000" />
<button id="whiteboardPencilBtn" class="fas fa-pencil-alt"></button>
<button id="whiteboardObjectBtn" class="fas fa-mouse-pointer"></button>
<button id="whiteboardUndoBtn" class="fas fa-undo"></button>
<button id="whiteboardRedoBtn" class="fas fa-redo"></button>
<button id="whiteboardImgFileBtn" class="far fa-image"></button>
<button id="whiteboardImgUrlBtn" class="fas fa-link"></button>
<button id="whiteboardTextBtn" class="fas fa-spell-check"></button>
<button id="whiteboardLineBtn" class="fas fa-slash"></button>
<button id="whiteboardRectBtn" class="far fa-square"></button>
<button id="whiteboardCircleBtn" class="far fa-circle"></button>
<button id="whiteboardSaveBtn" class="fas fa-save"></button>
<button id="whiteboardEraserBtn" class="fas fa-eraser"></button>
<button id="whiteboardCleanBtn" class="fas fa-trash"></button>
<button id="whiteboardCloseBtn" class="fas fa-times"></button>
</div>
</header>
<main>
<canvas id="wbCanvas"></canvas>
</main>
</section>
<!-- End whiteboard -->
<!-- End whiteboard -->
<!-- Start File Send -->
<!-- Start File Send -->
<div id="sendFileDiv">
<img id="imgShare" src="../images/share.png" alt="mirotalk-share" class="center" /><br />
<div id="sendFileInfo"></div>
<div id="sendFilePercentage"></div>
<progress id="sendProgress" max="0" value="0"></progress>
<button id="sendAbortBtn" class="fas fa-stop-circle">&nbsp;Abort</button>
</div>
<!-- End File Send -->
<!-- Start video URL iframe -->
<div id="videoUrlCont">
<div id="videoUrlHeader">
<button id="videoUrlCloseBtn" class="fas fa-times"></button>
<div id="sendFileDiv">
<img id="imgShare" src="../images/share.png" alt="mirotalk-share" class="center" /><br />
<div id="sendFileInfo"></div>
<div id="sendFilePercentage"></div>
<progress id="sendProgress" max="0" value="0"></progress>
<button id="sendAbortBtn" class="fas fa-stop-circle">&nbsp;Abort</button>
</div>
<br />
<iframe id="videoUrlIframe" title="Video Url Player" src="https://www.youtube.com/embed/RT6_Id5-7-s" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<!-- End Youtube Iframe -->
<!-- End File Send -->
<!-- Js scripts -->
<!-- Start video URL iframe -->
<script defer src="https://kit.fontawesome.com/d2f1016e6f.js" crossorigin="anonymous"></script>
<script defer src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script defer src="https://cdn.rawgit.com/muaz-khan/DetectRTC/master/DetectRTC.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.20"></script>
<script defer type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/460/fabric.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
<script defer src="https://unpkg.com/@popperjs/core@2"></script>
<script defer src="https://unpkg.com/tippy.js@6"></script>
<script defer src="/socket.io/socket.io.js"></script>
<script defer src="../js/client.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="../js/volume_meter.js"></script>
<script src="../js/speech_recognition.js"></script>
<div id="videoUrlCont">
<div id="videoUrlHeader">
<button id="videoUrlCloseBtn" class="fas fa-times"></button>
</div>
<br />
<iframe
id="videoUrlIframe"
title="Video Url Player"
src="https://www.youtube.com/embed/RT6_Id5-7-s"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
<!-- end of Js scripts -->
<!-- End Youtube Iframe -->
<!--
<!-- Js scripts -->
<script defer src="https://kit.fontawesome.com/d2f1016e6f.js" crossorigin="anonymous"></script>
<script defer src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script defer src="https://cdn.rawgit.com/muaz-khan/DetectRTC/master/DetectRTC.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.20"></script>
<script defer type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/460/fabric.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
<script defer src="https://unpkg.com/@popperjs/core@2"></script>
<script defer src="https://unpkg.com/tippy.js@6"></script>
<script defer src="/socket.io/socket.io.js"></script>
<script defer src="../js/client.js"></script>
<script defer src="../js/speechRecognition.js"></script>
<!-- end of Js scripts -->
<!--
the <video> and <audio> tags are all added and removed dynamically
in 'onAddStream', 'setupLocalMedia', and 'removePeer'/'disconnect'
-->
</body>
</html>
</body>
</html>