[mirotalksfu] - add transcription
هذا الالتزام موجود في:
@@ -68,6 +68,11 @@
|
||||
--btns-width: 320px;
|
||||
--btns-flex-direction: row;
|
||||
*/
|
||||
|
||||
--transcription-height: 680px;
|
||||
--transcription-width: 420px;
|
||||
--transcription-bg: radial-gradient(#393939, #000000);
|
||||
|
||||
--vmi-wh: 15vw;
|
||||
/* https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit */
|
||||
--videoObjFit: cover;
|
||||
@@ -515,6 +520,142 @@ th {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Transcription Room
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.transcription-room {
|
||||
z-index: 5;
|
||||
display: none;
|
||||
position: fixed;
|
||||
height: var(--transcription-height);
|
||||
width: var(--transcription-width);
|
||||
min-height: var(--transcription-height);
|
||||
min-width: var(--transcription-width);
|
||||
background: var(--transcription-bg);
|
||||
border: var(--border);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--box-shadow);
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.transcription {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
justify-content: space-between;
|
||||
background: var(--transcription-bg);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Transcription room header
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.transcription-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
color: #666;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.transcription-header-title button,
|
||||
.transcription-header-options button {
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
transition: background 0.23s;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Transcription room output area
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.transcription-chat {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
background: var(--transcription-bg);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.transcription-chat::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
.transcription-chat::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.transcription-chat::-webkit-scrollbar-thumb {
|
||||
background: black;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Transcription room left side
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.left-msg .msg-transcription-bubble {
|
||||
background: var(--left-msg-bg);
|
||||
border-bottom-left-radius: 0;
|
||||
color: #fff;
|
||||
width: var(--msger-bubble-width);
|
||||
}
|
||||
|
||||
.left-msg .msg-transcription-img {
|
||||
margin: 0px 5px 5px 0px;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Transcription bubble text
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.msg-transcription-bubble {
|
||||
padding: 15px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.msg-transcription-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.msg-transcription-info-name {
|
||||
margin-right: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.msg-transcription-info-time {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.msg-transcription-text {
|
||||
display: inline;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Transcription room input area
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.transcription-inputarea {
|
||||
display: inline-flex;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border: var(--border);
|
||||
}
|
||||
|
||||
.transcription-inputarea select {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Chat Room
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
@@ -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 CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.0.9
|
||||
* @version 1.1.0
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -137,6 +137,8 @@ let scriptProcessor = null;
|
||||
|
||||
const RoomURL = window.location.origin + '/join/' + room_id;
|
||||
|
||||
let transcription;
|
||||
|
||||
// ####################################################
|
||||
// INIT ROOM
|
||||
// ####################################################
|
||||
@@ -208,6 +210,10 @@ function initClient() {
|
||||
}
|
||||
setupWhiteboard();
|
||||
initEnumerateDevices();
|
||||
|
||||
// Transcription
|
||||
transcription = new Transcription();
|
||||
transcription.init();
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
@@ -232,6 +238,7 @@ function refreshMainButtonsToolTipPlacement() {
|
||||
setTippy('roomEmojiPicker', 'Toggle emoji reaction', placement);
|
||||
setTippy('swapCameraButton', 'Swap the camera', placement);
|
||||
setTippy('chatButton', 'Toggle the chat', placement);
|
||||
setTippy('transcriptionButton', 'Toggle transcription', placement);
|
||||
setTippy('participantsButton', 'Toggle participants', placement);
|
||||
setTippy('whiteboardButton', 'Toggle the whiteboard', placement);
|
||||
setTippy('settingsButton', 'Toggle the settings', placement);
|
||||
@@ -917,6 +924,7 @@ function joinRoom(peer_name, room_id) {
|
||||
isScreenAllowed,
|
||||
joinRoomWithScreen,
|
||||
isSpeechSynthesisSupported,
|
||||
transcription,
|
||||
roomIsReady,
|
||||
);
|
||||
handleRoomClientEvents();
|
||||
@@ -943,11 +951,15 @@ function roomIsReady() {
|
||||
BUTTONS.chat.chatMarkdownButton && show(chatMarkdownButton);
|
||||
BUTTONS.chat.chatGPTButton && show(chatGPTButton);
|
||||
BUTTONS.chat.chatShareFileButton && show(chatShareFileButton);
|
||||
if (isWebkitSpeechRecognitionSupported && BUTTONS.chat.chatSpeechStartButton) {
|
||||
show(chatSpeechStartButton);
|
||||
} else {
|
||||
BUTTONS.chat.chatSpeechStartButton = false;
|
||||
}
|
||||
|
||||
isWebkitSpeechRecognitionSupported && BUTTONS.chat.chatSpeechStartButton
|
||||
? show(chatSpeechStartButton)
|
||||
: (BUTTONS.chat.chatSpeechStartButton = false);
|
||||
|
||||
transcription.isSupported() && BUTTONS.main.transcriptionButton
|
||||
? show(transcriptionButton)
|
||||
: (BUTTONS.main.transcriptionButton = false);
|
||||
|
||||
show(chatCleanTextButton);
|
||||
show(chatPasteButton);
|
||||
show(chatSendButton);
|
||||
@@ -957,6 +969,10 @@ function roomIsReady() {
|
||||
hide(chatTogglePin);
|
||||
hide(chatMaxButton);
|
||||
hide(chatMinButton);
|
||||
transcription.maximize();
|
||||
hide(transcriptionTogglePinBtn);
|
||||
hide(transcriptionMaxBtn);
|
||||
hide(transcriptionMinBtn);
|
||||
} else {
|
||||
rc.makeDraggable(emojiPickerContainer, emojiPickerHeader);
|
||||
rc.makeDraggable(chatRoom, chatHeader);
|
||||
@@ -966,6 +982,7 @@ function roomIsReady() {
|
||||
rc.makeDraggable(sendFileDiv, imgShareSend);
|
||||
rc.makeDraggable(receiveFileDiv, imgShareReceive);
|
||||
rc.makeDraggable(lobby, lobbyHeader);
|
||||
rc.makeDraggable(transcriptionRoom, transcriptionHeader);
|
||||
if (navigator.getDisplayMedia || navigator.mediaDevices.getDisplayMedia) {
|
||||
if (BUTTONS.main.startScreenButton) {
|
||||
show(startScreenButton);
|
||||
@@ -1150,6 +1167,30 @@ function handleButtons() {
|
||||
chatButton.onclick = () => {
|
||||
rc.toggleChat();
|
||||
};
|
||||
transcriptionButton.onclick = () => {
|
||||
transcription.toggle();
|
||||
};
|
||||
transcriptionCloseBtn.onclick = () => {
|
||||
transcription.toggle();
|
||||
};
|
||||
transcriptionTogglePinBtn.onclick = () => {
|
||||
transcription.togglePinUnpin();
|
||||
};
|
||||
transcriptionMaxBtn.onclick = () => {
|
||||
transcription.maximize();
|
||||
};
|
||||
transcriptionMinBtn.onclick = () => {
|
||||
transcription.minimize();
|
||||
};
|
||||
transcriptionGhostBtn.onclick = () => {
|
||||
transcription.toggleBg();
|
||||
};
|
||||
transcriptionSaveBtn.onclick = () => {
|
||||
transcription.save();
|
||||
};
|
||||
transcriptionCleanBtn.onclick = () => {
|
||||
transcription.delete();
|
||||
};
|
||||
chatGhostButton.onclick = (e) => {
|
||||
rc.chatToggleBg();
|
||||
};
|
||||
@@ -1195,10 +1236,16 @@ function handleButtons() {
|
||||
fileShareButton.click();
|
||||
};
|
||||
chatSpeechStartButton.onclick = () => {
|
||||
startSpeech(true);
|
||||
startSpeech();
|
||||
};
|
||||
chatSpeechStopButton.onclick = () => {
|
||||
startSpeech(false);
|
||||
stopSpeech();
|
||||
};
|
||||
transcriptionSpeechStart.onclick = () => {
|
||||
transcription.start();
|
||||
};
|
||||
transcriptionSpeechStop.onclick = () => {
|
||||
transcription.stop();
|
||||
};
|
||||
fullScreenButton.onclick = () => {
|
||||
rc.toggleFullScreen();
|
||||
@@ -1787,7 +1834,11 @@ function handleRoomEmojiPicker() {
|
||||
|
||||
function sendEmojiToRoom(data) {
|
||||
console.log('Selected Emoji:', data.native);
|
||||
const cmd = `roomEmoji|${peer_name}|${data.native}`;
|
||||
const cmd = {
|
||||
type: 'roomEmoji',
|
||||
peer_name: peer_name,
|
||||
emoji: data.native,
|
||||
};
|
||||
if (rc.thereAreParticipants()) {
|
||||
rc.emitCmd(cmd);
|
||||
}
|
||||
@@ -2907,6 +2958,7 @@ function setTheme() {
|
||||
case 'dark':
|
||||
swalBackground = 'radial-gradient(#393939, #000000)';
|
||||
document.documentElement.style.setProperty('--body-bg', 'radial-gradient(#393939, #000000)');
|
||||
document.documentElement.style.setProperty('--transcription-bg', 'radial-gradient(#393939, #000000)');
|
||||
document.documentElement.style.setProperty('--msger-bg', 'radial-gradient(#393939, #000000)');
|
||||
document.documentElement.style.setProperty('--left-msg-bg', '#252d31');
|
||||
document.documentElement.style.setProperty('--right-msg-bg', '#056162');
|
||||
@@ -2921,6 +2973,7 @@ function setTheme() {
|
||||
case 'grey':
|
||||
swalBackground = 'radial-gradient(#666, #333)';
|
||||
document.documentElement.style.setProperty('--body-bg', 'radial-gradient(#666, #333)');
|
||||
document.documentElement.style.setProperty('--transcription-bg', 'radial-gradient(#666, #333)');
|
||||
document.documentElement.style.setProperty('--msger-bg', 'radial-gradient(#666, #333)');
|
||||
document.documentElement.style.setProperty('--left-msg-bg', '#252d31');
|
||||
document.documentElement.style.setProperty('--right-msg-bg', '#056162');
|
||||
@@ -2935,6 +2988,7 @@ function setTheme() {
|
||||
case 'green':
|
||||
swalBackground = 'radial-gradient(#003934, #001E1A)';
|
||||
document.documentElement.style.setProperty('--body-bg', 'radial-gradient(#003934, #001E1A)');
|
||||
document.documentElement.style.setProperty('--transcription-bg', 'radial-gradient(#003934, #001E1A)');
|
||||
document.documentElement.style.setProperty('--msger-bg', 'radial-gradient(#003934, #001E1A)');
|
||||
document.documentElement.style.setProperty('--left-msg-bg', '#003934');
|
||||
document.documentElement.style.setProperty('--right-msg-bg', '#001E1A');
|
||||
@@ -2949,6 +3003,7 @@ function setTheme() {
|
||||
case 'blue':
|
||||
swalBackground = 'radial-gradient(#306bac, #141B41)';
|
||||
document.documentElement.style.setProperty('--body-bg', 'radial-gradient(#306bac, #141B41)');
|
||||
document.documentElement.style.setProperty('--transcription-bg', 'radial-gradient(#306bac, #141B41)');
|
||||
document.documentElement.style.setProperty('--msger-bg', 'radial-gradient(#306bac, #141B41)');
|
||||
document.documentElement.style.setProperty('--left-msg-bg', '#306bac');
|
||||
document.documentElement.style.setProperty('--right-msg-bg', '#141B41');
|
||||
@@ -2963,6 +3018,7 @@ function setTheme() {
|
||||
case 'red':
|
||||
swalBackground = 'radial-gradient(#69140E, #3C1518)';
|
||||
document.documentElement.style.setProperty('--body-bg', 'radial-gradient(#69140E, #3C1518)');
|
||||
document.documentElement.style.setProperty('--transcription-bg', 'radial-gradient(#69140E, #3C1518)');
|
||||
document.documentElement.style.setProperty('--msger-bg', 'radial-gradient(#69140E, #3C1518)');
|
||||
document.documentElement.style.setProperty('--left-msg-bg', '#69140E');
|
||||
document.documentElement.style.setProperty('--right-msg-bg', '#3C1518');
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @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
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.0.9
|
||||
* @version 1.1.0
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -136,6 +136,7 @@ class RoomClient {
|
||||
isScreenAllowed,
|
||||
joinRoomWithScreen,
|
||||
isSpeechSynthesisSupported,
|
||||
transcription,
|
||||
successCallback,
|
||||
) {
|
||||
this.localAudioEl = localAudioEl;
|
||||
@@ -196,6 +197,8 @@ class RoomClient {
|
||||
|
||||
this.RoomPassword = false;
|
||||
|
||||
this.transcription = transcription;
|
||||
|
||||
// File transfer settings
|
||||
this.fileToSend = null;
|
||||
this.fileReader = null;
|
||||
@@ -2734,6 +2737,9 @@ class RoomClient {
|
||||
if (this.isChatPinned) {
|
||||
this.chatPin();
|
||||
}
|
||||
if (this.transcription.isPin()) {
|
||||
this.transcription.pinned();
|
||||
}
|
||||
}
|
||||
|
||||
adaptVideoObjectFit(index) {
|
||||
@@ -2782,7 +2788,11 @@ class RoomClient {
|
||||
this.sound('click');
|
||||
isVideoPrivacyActive = !isVideoPrivacyActive;
|
||||
this.setVideoPrivacyStatus(this.peer_id, isVideoPrivacyActive);
|
||||
this.emitCmd(`privacy|${this.peer_id}|${isVideoPrivacyActive}`);
|
||||
this.emitCmd({
|
||||
type: 'privacy',
|
||||
peer_id: this.peer_id,
|
||||
active: isVideoPrivacyActive,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2881,6 +2891,9 @@ class RoomClient {
|
||||
}
|
||||
|
||||
toggleChatPin() {
|
||||
if (transcription.isPin()) {
|
||||
return userLog('info', 'Please unpin the transcription that appears to be currently pinned', 'top-end');
|
||||
}
|
||||
this.isChatPinned ? this.chatUnpin() : this.chatPin();
|
||||
this.sound('click');
|
||||
}
|
||||
@@ -4733,20 +4746,20 @@ class RoomClient {
|
||||
// HANDLE COMMANDS
|
||||
// ####################################################
|
||||
|
||||
emitCmd(data) {
|
||||
this.socket.emit('cmd', data);
|
||||
emitCmd(cmd) {
|
||||
this.socket.emit('cmd', cmd);
|
||||
}
|
||||
|
||||
handleCmd(data) {
|
||||
// cmd|foo|bar|....
|
||||
const words = data.split('|');
|
||||
let cmd = words[0];
|
||||
switch (cmd) {
|
||||
handleCmd(cmd) {
|
||||
switch (cmd.type) {
|
||||
case 'privacy':
|
||||
this.setVideoPrivacyStatus(words[1], words[2] == 'true');
|
||||
this.setVideoPrivacyStatus(cmd.peer_id, cmd.active);
|
||||
break;
|
||||
case 'roomEmoji':
|
||||
this.handleRoomEmoji(words);
|
||||
this.handleRoomEmoji(cmd);
|
||||
break;
|
||||
case 'transcript':
|
||||
this.transcription.handleTranscript(cmd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -4754,7 +4767,7 @@ class RoomClient {
|
||||
}
|
||||
}
|
||||
|
||||
handleRoomEmoji(words, duration = 5000) {
|
||||
handleRoomEmoji(cmd, duration = 5000) {
|
||||
const userEmoji = document.getElementById(`userEmoji`);
|
||||
if (userEmoji) {
|
||||
const emojiDisplay = document.createElement('div');
|
||||
@@ -4764,7 +4777,7 @@ class RoomClient {
|
||||
emojiDisplay.style.color = '#FFF';
|
||||
emojiDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
|
||||
emojiDisplay.style.borderRadius = '10px';
|
||||
emojiDisplay.innerText = `${words[2]} ${words[1]}`;
|
||||
emojiDisplay.innerText = `${cmd.emoji} ${cmd.peer_name}`;
|
||||
userEmoji.appendChild(emojiDisplay);
|
||||
setTimeout(() => {
|
||||
emojiDisplay.remove();
|
||||
|
||||
@@ -17,6 +17,7 @@ const BUTTONS = {
|
||||
startScreenButton: true,
|
||||
swapCameraButton: true,
|
||||
chatButton: true,
|
||||
transcriptionButton: true,
|
||||
participantsButton: true,
|
||||
whiteboardButton: true,
|
||||
emojiRoomButton: true,
|
||||
|
||||
@@ -8,6 +8,11 @@ let isVoiceCommandSupported = browserLanguage.includes('en-');
|
||||
|
||||
const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
|
||||
/**
|
||||
* Enable real-time voice recognition in the chat, allowing you to execute commands using your voice.
|
||||
* Note: Currently, it supports only the English language.
|
||||
* TODO make it multi languages...
|
||||
*/
|
||||
const commands = {
|
||||
shareRoom: 'room',
|
||||
hideMe: 'hide me',
|
||||
@@ -23,6 +28,7 @@ const commands = {
|
||||
chatOn: 'open the chat',
|
||||
chatSend: 'send',
|
||||
chatOff: 'close the chat',
|
||||
toggleTr: 'toggle transcription',
|
||||
chatGPTOn: 'open chatGPT',
|
||||
chatGPTOff: 'close chatGPT',
|
||||
whiteboardOn: 'open the whiteboard',
|
||||
@@ -84,34 +90,37 @@ if (speechRecognition) {
|
||||
console.log('Speech recognition', recognition);
|
||||
|
||||
recognition.onstart = function () {
|
||||
console.log('Start speech recognition');
|
||||
console.log('Speech recognition started');
|
||||
hide(chatSpeechStartButton);
|
||||
show(chatSpeechStopButton);
|
||||
setColor(chatSpeechStopButton, 'lime');
|
||||
userLog('info', 'Speech recognition started', 'top-end');
|
||||
};
|
||||
|
||||
recognition.onresult = (e) => {
|
||||
let current = e.resultIndex;
|
||||
let transcript = e.results[current][0].transcript;
|
||||
|
||||
if (transcript.trim().toLowerCase() != commands.chatSend) {
|
||||
chatMessage.value = transcript;
|
||||
}
|
||||
|
||||
if (isVoiceCommandsEnabled && isVoiceCommandSupported) {
|
||||
execVoiceCommands(transcript);
|
||||
if (transcript) {
|
||||
if (transcript.trim().toLowerCase() != commands.chatSend) {
|
||||
chatMessage.value = transcript;
|
||||
}
|
||||
if (isVoiceCommandsEnabled && isVoiceCommandSupported) {
|
||||
execVoiceCommands(transcript);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
recognition.onerror = function (event) {
|
||||
console.warn('Speech recognition error', event.error);
|
||||
console.error('Speech recognition error', event.error);
|
||||
userLog('error', `Speech recognition error ${event.error}`, 'top-end', 6000);
|
||||
};
|
||||
|
||||
recognition.onend = function () {
|
||||
console.log('Stop speech recognition');
|
||||
console.log('Speech recognition stopped');
|
||||
show(chatSpeechStartButton);
|
||||
hide(chatSpeechStopButton);
|
||||
setColor(chatSpeechStopButton, 'white');
|
||||
userLog('info', 'Speech recognition stopped', 'top-end');
|
||||
};
|
||||
|
||||
isWebkitSpeechRecognitionSupported = true;
|
||||
@@ -120,13 +129,13 @@ if (speechRecognition) {
|
||||
console.warn('This browser not supports webkitSpeechRecognition');
|
||||
}
|
||||
|
||||
function startSpeech(action) {
|
||||
if (action) {
|
||||
recognition.lang = browserLanguage;
|
||||
recognition.start();
|
||||
} else {
|
||||
recognition.stop();
|
||||
}
|
||||
function startSpeech() {
|
||||
recognition.lang = browserLanguage;
|
||||
recognition.start();
|
||||
}
|
||||
|
||||
function stopSpeech() {
|
||||
recognition.stop();
|
||||
}
|
||||
|
||||
function execVoiceCommands(transcript) {
|
||||
@@ -187,6 +196,9 @@ function execVoiceCommands(transcript) {
|
||||
printCommand(commands.chatOff);
|
||||
chatCloseButton.click();
|
||||
break;
|
||||
case commands.toggleTr:
|
||||
transcriptionButton.click();
|
||||
break;
|
||||
case commands.whiteboardOn:
|
||||
printCommand(commands.whiteboardOn);
|
||||
whiteboardButton.click();
|
||||
@@ -342,6 +354,8 @@ function execVoiceCommands(transcript) {
|
||||
chatSpeechStopButton.click();
|
||||
break;
|
||||
// ...
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
387
public/js/Transcription.js
Normal file
387
public/js/Transcription.js
Normal file
@@ -0,0 +1,387 @@
|
||||
'use strict';
|
||||
|
||||
class Transcription {
|
||||
constructor() {
|
||||
this.languages = [
|
||||
['Afrikaans', ['af-ZA']],
|
||||
['Bahasa Indonesia', ['id-ID']],
|
||||
['Bahasa Melayu', ['ms-MY']],
|
||||
['Català', ['ca-ES']],
|
||||
['Čeština', ['cs-CZ']],
|
||||
['Deutsch', ['de-DE']],
|
||||
[
|
||||
'English',
|
||||
['en-AU', 'Australia'],
|
||||
['en-CA', 'Canada'],
|
||||
['en-IN', 'India'],
|
||||
['en-NZ', 'New Zealand'],
|
||||
['en-ZA', 'South Africa'],
|
||||
['en-GB', 'United Kingdom'],
|
||||
['en-US', 'United States'],
|
||||
],
|
||||
[
|
||||
'Español',
|
||||
['es-AR', 'Argentina'],
|
||||
['es-BO', 'Bolivia'],
|
||||
['es-CL', 'Chile'],
|
||||
['es-CO', 'Colombia'],
|
||||
['es-CR', 'Costa Rica'],
|
||||
['es-EC', 'Ecuador'],
|
||||
['es-SV', 'El Salvador'],
|
||||
['es-ES', 'España'],
|
||||
['es-US', 'Estados Unidos'],
|
||||
['es-GT', 'Guatemala'],
|
||||
['es-HN', 'Honduras'],
|
||||
['es-MX', 'México'],
|
||||
['es-NI', 'Nicaragua'],
|
||||
['es-PA', 'Panamá'],
|
||||
['es-PY', 'Paraguay'],
|
||||
['es-PE', 'Perú'],
|
||||
['es-PR', 'Puerto Rico'],
|
||||
['es-DO', 'República Dominicana'],
|
||||
['es-UY', 'Uruguay'],
|
||||
['es-VE', 'Venezuela'],
|
||||
],
|
||||
['Euskara', ['eu-ES']],
|
||||
['Français', ['fr-FR']],
|
||||
['Galego', ['gl-ES']],
|
||||
['Hrvatski', ['hr_HR']],
|
||||
['IsiZulu', ['zu-ZA']],
|
||||
['Íslenska', ['is-IS']],
|
||||
['Italiano', ['it-IT', 'Italia'], ['it-CH', 'Svizzera']],
|
||||
['Magyar', ['hu-HU']],
|
||||
['Nederlands', ['nl-NL']],
|
||||
['Norsk bokmål', ['nb-NO']],
|
||||
['Polski', ['pl-PL']],
|
||||
['Português', ['pt-BR', 'Brasil'], ['pt-PT', 'Portugal']],
|
||||
['Română', ['ro-RO']],
|
||||
['Slovenčina', ['sk-SK']],
|
||||
['Suomi', ['fi-FI']],
|
||||
['Svenska', ['sv-SE']],
|
||||
['Türkçe', ['tr-TR']],
|
||||
['български', ['bg-BG']],
|
||||
['Pусский', ['ru-RU']],
|
||||
['Српски', ['sr-RS']],
|
||||
['한국어', ['ko-KR']],
|
||||
[
|
||||
'中文',
|
||||
['cmn-Hans-CN', '普通话 (中国大陆)'],
|
||||
['cmn-Hans-HK', '普通话 (香港)'],
|
||||
['cmn-Hant-TW', '中文 (台灣)'],
|
||||
['yue-Hant-HK', '粵語 (香港)'],
|
||||
],
|
||||
['日本語', ['ja-JP']],
|
||||
['Lingua latīna', ['la']],
|
||||
];
|
||||
this.speechTranscription = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
this.isTranscriptionSupported = false;
|
||||
this.transcriptionRunning = false;
|
||||
this.transcription;
|
||||
this.transcripts = [];
|
||||
this.isBgTransparent = false;
|
||||
this.isPinned = false;
|
||||
}
|
||||
|
||||
isSupported() {
|
||||
return Boolean(this.speechTranscription);
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.isSupported()) {
|
||||
this.handleLanguages();
|
||||
|
||||
this.transcription = new this.speechTranscription();
|
||||
this.transcription.maxAlternatives = 1;
|
||||
this.transcription.continuous = true;
|
||||
this.transcription.lang = transcriptionDialect.value;
|
||||
|
||||
this.transcription.onstart = function () {
|
||||
console.log('Transcription started');
|
||||
transcriptionSpeechStart.style.display = 'none';
|
||||
transcriptionSpeechStop.style.display = 'block';
|
||||
setColor(transcriptionSpeechStatus, 'lime');
|
||||
userLog('info', 'Transcription started', 'top-end');
|
||||
};
|
||||
|
||||
this.transcription.onresult = (e) => {
|
||||
const current = e.resultIndex;
|
||||
const transcript = e.results[current][0].transcript;
|
||||
const transcriptionData = {
|
||||
type: 'transcript',
|
||||
room_id: room_id,
|
||||
peer_name: peer_name,
|
||||
text_data: transcript,
|
||||
time_stamp: new Date(),
|
||||
};
|
||||
if (transcript) {
|
||||
this.sendTranscript(transcriptionData);
|
||||
this.handleTranscript(transcriptionData);
|
||||
}
|
||||
};
|
||||
|
||||
this.transcription.onaudiostart = () => {
|
||||
console.log('Transcription start to capture your voice');
|
||||
};
|
||||
|
||||
this.transcription.onaudioend = () => {
|
||||
console.log('Transcription stop to capture your voice');
|
||||
};
|
||||
|
||||
this.transcription.onerror = function (event) {
|
||||
userLog('error', `Transcription error ${event.error}`, 'top-end', 6000);
|
||||
console.error('Transcription error', event.error);
|
||||
};
|
||||
|
||||
this.transcription.onend = function () {
|
||||
console.log('Transcription stopped');
|
||||
transcriptionSpeechStop.style.display = 'none';
|
||||
transcriptionSpeechStart.style.display = 'block';
|
||||
setColor(transcriptionSpeechStatus, 'white');
|
||||
userLog('info', 'Transcription stopped', 'top-end');
|
||||
};
|
||||
|
||||
this.isTranscriptionSupported = true;
|
||||
console.info('This Browser support Transcription');
|
||||
} else {
|
||||
console.warn(
|
||||
'This browser not support Transcription, check out supported browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API#browser_compatibility',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sendTranscript(transcriptionData) {
|
||||
if (rc.thereAreParticipants()) {
|
||||
//console.log('TRANSCRIPTION SEND', transcriptionData);
|
||||
rc.emitCmd(transcriptionData);
|
||||
}
|
||||
}
|
||||
|
||||
handleTranscript(transcriptionData) {
|
||||
console.log('TRANSCRIPTION TEXT', transcriptionData.text_data);
|
||||
|
||||
transcriptionData.text_data = filterXSS(transcriptionData.text_data);
|
||||
transcriptionData.peer_name = filterXSS(transcriptionData.peer_name);
|
||||
|
||||
const { peer_name, text_data } = transcriptionData;
|
||||
const time_stamp = rc.getTimeNow();
|
||||
const avatar_image = rc.genAvatarSvg(peer_name, 32);
|
||||
|
||||
if (this.isHidden()) this.toggle();
|
||||
|
||||
const msgHTML = `
|
||||
<div class="msg left-msg">
|
||||
<img class="msg-transcription-img" src="${avatar_image}" />
|
||||
<div class="msg-transcription-bubble">
|
||||
<div class="msg-transcription-info">
|
||||
<div class="msg-transcription-info-name">${peer_name} : ${time_stamp}</div>
|
||||
</div>
|
||||
<div class="msg-transcription-text">${text_data}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
transcriptionChat.insertAdjacentHTML('beforeend', msgHTML);
|
||||
transcriptionChat.scrollTop += 500;
|
||||
|
||||
this.transcripts.push({
|
||||
time: time_stamp,
|
||||
name: peer_name,
|
||||
caption: text_data,
|
||||
});
|
||||
rc.sound('transcript');
|
||||
}
|
||||
|
||||
isHidden() {
|
||||
return Boolean(transcriptionRoom.style.display === 'none' || transcriptionRoom.style.display === '');
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.isHidden()) {
|
||||
this.center();
|
||||
transcriptionRoom.style.display = 'block';
|
||||
rc.sound('open');
|
||||
} else {
|
||||
transcriptionRoom.style.display = 'none';
|
||||
}
|
||||
if (this.isPinned) this.unpinned();
|
||||
}
|
||||
|
||||
toggleBg() {
|
||||
this.isBgTransparent = !this.isBgTransparent;
|
||||
this.isBgTransparent
|
||||
? document.documentElement.style.setProperty('--transcription-bg', 'rgba(0, 0, 0, 0.100)')
|
||||
: setTheme();
|
||||
}
|
||||
|
||||
maximize() {
|
||||
hide(transcriptionMaxBtn);
|
||||
show(transcriptionMinBtn);
|
||||
this.center();
|
||||
document.documentElement.style.setProperty('--transcription-width', '100%');
|
||||
document.documentElement.style.setProperty('--transcription-height', '100%');
|
||||
}
|
||||
|
||||
minimize() {
|
||||
hide(transcriptionMinBtn);
|
||||
show(transcriptionMaxBtn);
|
||||
if (this.isPinned) {
|
||||
this.pinned();
|
||||
} else {
|
||||
this.center();
|
||||
document.documentElement.style.setProperty('--transcription-width', '420px');
|
||||
document.documentElement.style.setProperty('--transcription-height', '680px');
|
||||
}
|
||||
}
|
||||
|
||||
center() {
|
||||
transcriptionRoom.style.position = 'fixed';
|
||||
transcriptionRoom.style.transform = 'translate(-50%, -50%)';
|
||||
transcriptionRoom.style.top = '50%';
|
||||
transcriptionRoom.style.left = '50%';
|
||||
}
|
||||
|
||||
togglePinUnpin() {
|
||||
if (rc.isChatPinned) {
|
||||
return userLog('info', 'Please unpin the chat that appears to be currently pinned', 'top-end');
|
||||
}
|
||||
this.isPinned ? this.unpinned() : this.pinned();
|
||||
rc.sound('click');
|
||||
}
|
||||
|
||||
isPin() {
|
||||
return this.isPinned;
|
||||
}
|
||||
|
||||
pinned() {
|
||||
if (!rc.isVideoPinned) {
|
||||
rc.videoMediaContainer.style.top = 0;
|
||||
rc.videoMediaContainer.style.width = '75%';
|
||||
rc.videoMediaContainer.style.height = '100%';
|
||||
}
|
||||
this.pin();
|
||||
this.isPinned = true;
|
||||
setColor(transcriptionTogglePinBtn, 'lime');
|
||||
resizeVideoMedia();
|
||||
transcriptionRoom.style.resize = 'none';
|
||||
if (!rc.isMobileDevice) rc.makeUnDraggable(transcriptionRoom, transcriptionHeader);
|
||||
}
|
||||
|
||||
pin() {
|
||||
transcriptionRoom.style.position = 'absolute';
|
||||
transcriptionRoom.style.top = 0;
|
||||
transcriptionRoom.style.right = 0;
|
||||
transcriptionRoom.style.left = null;
|
||||
transcriptionRoom.style.transform = null;
|
||||
document.documentElement.style.setProperty('--transcription-width', '25%');
|
||||
document.documentElement.style.setProperty('--transcription-height', '100%');
|
||||
}
|
||||
|
||||
unpinned() {
|
||||
if (!rc.isVideoPinned) {
|
||||
rc.videoMediaContainer.style.top = 0;
|
||||
rc.videoMediaContainer.style.right = null;
|
||||
rc.videoMediaContainer.style.width = '100%';
|
||||
rc.videoMediaContainer.style.height = '100%';
|
||||
}
|
||||
document.documentElement.style.setProperty('--transcription-width', '420px');
|
||||
document.documentElement.style.setProperty('--transcription-height', '680px');
|
||||
hide(transcriptionMinBtn);
|
||||
show(transcriptionMaxBtn);
|
||||
this.center();
|
||||
this.isPinned = false;
|
||||
setColor(transcriptionTogglePinBtn, 'white');
|
||||
resizeVideoMedia();
|
||||
transcriptionRoom.style.resize = 'both';
|
||||
if (!rc.isMobileDevice) rc.makeDraggable(transcriptionRoom, transcriptionHeader);
|
||||
}
|
||||
|
||||
save() {
|
||||
if (this.transcripts.length != 0) {
|
||||
const a = document.createElement('a');
|
||||
a.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(this.transcripts, null, 1));
|
||||
a.download = getDataTimeString() + room_id + '-TRANSCRIPTIONS.txt';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
rc.sound('download');
|
||||
} else {
|
||||
userLog('info', "There isn't transcriptions to save", 'top-end');
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
if (this.transcripts.length != 0) {
|
||||
Swal.fire({
|
||||
background: swalBackground,
|
||||
position: 'center',
|
||||
title: 'Clean up all transcripts?',
|
||||
imageUrl: image.delete,
|
||||
showDenyButton: true,
|
||||
confirmButtonText: `Yes`,
|
||||
denyButtonText: `No`,
|
||||
showClass: { popup: 'animate__animated animate__fadeInDown' },
|
||||
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
let captions = transcriptionChat.firstChild;
|
||||
while (captions) {
|
||||
transcriptionChat.removeChild(captions);
|
||||
captions = transcriptionChat.firstChild;
|
||||
}
|
||||
this.transcripts = [];
|
||||
rc.sound('delete');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
userLog('info', "There isn't transcriptions to delete", 'top-end');
|
||||
}
|
||||
}
|
||||
|
||||
updateCountry() {
|
||||
for (let i = transcriptionDialect.options.length - 1; i >= 0; i--) {
|
||||
transcriptionDialect.remove(i);
|
||||
}
|
||||
let list = this.languages[transcriptionLanguage.selectedIndex];
|
||||
for (let i = 1; i < list.length; i++) {
|
||||
transcriptionDialect.options.add(new Option(list[i][1], list[i][0]));
|
||||
}
|
||||
transcriptionDialect.style.visibility = list[1].length == 1 ? 'hidden' : 'visible';
|
||||
}
|
||||
|
||||
handleLanguages() {
|
||||
for (let i = 0; i < this.languages.length; i++) {
|
||||
transcriptionLanguage.options[i] = new Option(this.languages[i][0], i);
|
||||
}
|
||||
|
||||
transcriptionLanguage.selectedIndex = 6;
|
||||
this.updateCountry();
|
||||
|
||||
transcriptionDialect.selectedIndex = 6;
|
||||
transcriptionLanguage.onchange = () => {
|
||||
this.updateCountry();
|
||||
};
|
||||
}
|
||||
|
||||
start() {
|
||||
try {
|
||||
this.transcriptionRunning = true;
|
||||
this.transcription.lang = transcriptionDialect.value;
|
||||
this.selectEnabled(false);
|
||||
this.transcription.start();
|
||||
} catch (error) {
|
||||
this.transcriptionRunning = false;
|
||||
userLog('error', `Transcription start error ${error.message}`, 'top-end', 6000);
|
||||
console.error('Transcription start error', error);
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.transcriptionRunning = false;
|
||||
this.selectEnabled();
|
||||
this.transcription.stop();
|
||||
}
|
||||
|
||||
selectEnabled(enabled = true) {
|
||||
transcriptionLanguage.disabled = enabled;
|
||||
transcriptionDialect.disabled = enabled;
|
||||
}
|
||||
}
|
||||
ثنائية
public/sounds/transcript.wav
Normal file
ثنائية
public/sounds/transcript.wav
Normal file
ملف ثنائي غير معروض.
@@ -80,6 +80,7 @@
|
||||
<script defer src="../js/Room.js"></script>
|
||||
<script defer src="../js/RoomClient.js"></script>
|
||||
<script defer src="../js/SpeechRec.js"></script>
|
||||
<script defer src="../js/Transcription.js"></script>
|
||||
<script defer src="../js/VideoGrid.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/detectrtc@1.4.1/DetectRTC.min.js"></script>
|
||||
@@ -148,6 +149,7 @@ access to use this app.
|
||||
<button id="lowerHandButton" class="hidden"><i id="lowerHandIcon" class="fas fa-hand-paper"></i></button>
|
||||
<button id="roomEmojiPicker" class="hidden"><i class="fas fa-face-smile"></i></button>
|
||||
<button id="chatButton" class="hidden"><i class="fas fa-comments"></i></button>
|
||||
<button id="transcriptionButton" class="hidden"><i class="fas fa-closed-captioning"></i></button>
|
||||
<button id="participantsButton" class="hidden"><i class="fas fa-users"></i></button>
|
||||
<button id="whiteboardButton" class="hidden"><i class="fas fa-chalkboard-teacher"></i></button>
|
||||
<button id="settingsButton" class="hidden"><i class="fas fa-cogs"></i></button>
|
||||
@@ -749,6 +751,32 @@ access to use this app.
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="transcriptionRoom" class="transcription-room fadein">
|
||||
<section id="transcriptionSection" class="transcription">
|
||||
<header id="transcriptionHeader" class="transcription-header">
|
||||
<div class="transcription-header-title">
|
||||
<button id="transcriptionCloseBtn" class="fas fa-times"></button>
|
||||
<button id="transcriptionTogglePinBtn" class="fas fa-map-pin"></button>
|
||||
<button id="transcriptionMaxBtn" class=""><i class="fas fa-expand"></i></button>
|
||||
<button id="transcriptionMinBtn" class="hidden"><i class="fas fa-compress"></i></button>
|
||||
<button id="transcriptionSpeechStatus" class="fas fa-microphone-alt"></button>
|
||||
</div>
|
||||
<div class="transcription-header-options">
|
||||
<button id="transcriptionGhostBtn" class="fas fa-circle-half-stroke"></button>
|
||||
<button id="transcriptionSaveBtn" class="fas fa-save"></button>
|
||||
<button id="transcriptionCleanBtn" class="fas fa-trash"></button>
|
||||
</div>
|
||||
</header>
|
||||
<main id="transcriptionChat" class="transcription-chat"></main>
|
||||
<div class="transcription-inputarea">
|
||||
<select id="transcriptionLanguage" class="form-select text-light bg-dark"></select>
|
||||
<select id="transcriptionDialect" class="form-select text-light bg-dark"></select>
|
||||
<button id="transcriptionSpeechStart" class=""><i class="fas fa-play"></i></button>
|
||||
<button id="transcriptionSpeechStop" class="hidden"><i class="fas fa-stop"></i></button>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="lobby" class="fadein center hidden">
|
||||
<header id="lobbyHeader" class="lobby-header">
|
||||
<div id="lobbyHeaderTitle" class="lobby-header-title">Lobby users</div>
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم