This commit is contained in:
2024-04-21 14:42:52 +02:00
parent 4b69674ede
commit 8a25f53c99
10700 changed files with 55767 additions and 14201 deletions

View File

@ -0,0 +1,183 @@
'use strict';
const brandDataKey = 'brandData';
const brandData = window.sessionStorage.getItem(brandDataKey);
const title = document.getElementById('title');
const icon = document.getElementById('icon');
const appleTouchIcon = document.getElementById('appleTouchIcon');
const description = document.getElementById('description');
const keywords = document.getElementById('keywords');
const ogType = document.getElementById('ogType');
const ogSiteName = document.getElementById('ogSiteName');
const ogTitle = document.getElementById('ogTitle');
const ogDescription = document.getElementById('ogDescription');
const ogImage = document.getElementById('ogImage');
const appTitle = document.getElementById('appTitle');
const appDescription = document.getElementById('appDescription');
const features = document.getElementById('features');
const teams = document.getElementById('teams');
const tryEasier = document.getElementById('tryEasier');
const poweredBy = document.getElementById('poweredBy');
const sponsors = document.getElementById('sponsors');
const advertisers = document.getElementById('advertisers');
const footer = document.getElementById('footer');
//...
// app/src/config.js - ui.brand
let BRAND = {
app: {
name: 'MiroTalk SFU',
title: 'MiroTalk SFU<br />Free browser based Real-time video calls.<br />Simple, Secure, Fast.',
description:
'Start your next video call with a single click. No download, plug-in, or login is required. Just get straight to talking, messaging, and sharing your screen.',
},
site: {
title: 'MiroTalk SFU, Free Video Calls, Messaging and Screen Sharing',
icon: '../images/logo.svg',
appleTouchIcon: '../images/logo.svg',
},
meta: {
description:
'MiroTalk SFU powered by WebRTC and mediasoup, Real-time Simple Secure Fast video calls, messaging and screen sharing capabilities in the browser.',
keywords:
'webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting',
},
og: {
type: 'app-webrtc',
siteName: 'MiroTalk SFU',
title: 'Click the link to make a call.',
description: 'MiroTalk SFU calling provides real-time video calls, messaging and screen sharing.',
image: 'https://sfu.mirotalk.com/images/mirotalksfu.png',
},
html: {
features: true,
teams: true,
tryEasier: true,
poweredBy: true,
sponsors: true,
advertisers: true,
footer: true,
},
//...
};
async function initialize() {
await getBrand();
customizeSite();
customizeMetaTags();
customizeOpenGraph();
customizeApp();
checkBrand();
}
async function getBrand() {
if (brandData) {
setBrand(JSON.parse(brandData));
} else {
try {
const response = await fetch('/brand', { timeout: 5000 });
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
const serverBrand = data.message;
if (serverBrand) {
setBrand(serverBrand);
console.log('FETCH BRAND SETTINGS', {
serverBrand: serverBrand,
clientBrand: BRAND,
});
window.sessionStorage.setItem(brandDataKey, JSON.stringify(serverBrand));
}
} catch (error) {
console.error('FETCH GET BRAND ERROR', error.message);
}
}
}
// BRAND configurations
function setBrand(data) {
BRAND = data;
console.log('Set Brand done');
}
// BRAND check
function checkBrand() {
!BRAND.html.features && elementDisplay(features, false);
!BRAND.html.teams && elementDisplay(teams, false);
!BRAND.html.tryEasier && elementDisplay(tryEasier, false);
!BRAND.html.poweredBy && elementDisplay(poweredBy, false);
!BRAND.html.sponsors && elementDisplay(sponsors, false);
!BRAND.html.advertisers && elementDisplay(advertisers, false);
!BRAND.html.footer && elementDisplay(footer, false);
}
// ELEMENT display mode
function elementDisplay(element, display, mode = 'block') {
if (!element) return;
element.style.display = display ? mode : 'none';
}
// APP customize
function customizeApp() {
if (appTitle) {
appTitle.innerHTML = BRAND.app.title;
}
if (appDescription) {
appDescription.textContent = BRAND.app.description;
}
}
// SITE metadata
function customizeSite() {
if (title) {
title.textContent = BRAND.site.title;
}
if (icon) {
icon.href = BRAND.site.icon;
}
if (appleTouchIcon) {
appleTouchIcon.href = BRAND.site.appleTouchIcon;
}
}
// SEO metadata
function customizeMetaTags() {
if (description) {
description.content = BRAND.meta.description;
}
if (keywords) {
keywords.content = BRAND.meta.keywords;
}
}
// SOCIAL MEDIA SHARING metadata
function customizeOpenGraph() {
if (ogType) {
ogType.content = BRAND.og.type;
}
if (ogSiteName) {
ogSiteName.content = BRAND.og.siteName;
}
if (ogTitle) {
ogTitle.content = BRAND.og.title;
}
if (ogDescription) {
ogDescription.content = BRAND.og.description;
}
if (ogImage) {
ogImage.content = BRAND.og.image;
}
}
initialize();

View File

@ -0,0 +1,83 @@
'use strict';
class MixedAudioRecorder {
constructor(useGainNode = true) {
this.useGainNode = useGainNode;
this.gainNode = null;
this.audioSources = [];
this.audioDestination = null;
this.audioContext = this.createAudioContext();
}
createAudioContext() {
if (window.AudioContext) {
return new AudioContext();
} else if (window.webkitAudioContext) {
return new webkitAudioContext();
} else if (window.mozAudioContext) {
return new mozAudioContext();
} else {
throw new Error('Web Audio API is not supported in this browser');
}
}
getMixedAudioStream(audioStreams) {
this.audioSources = [];
if (this.useGainNode) {
this.gainNode = this.audioContext.createGain();
this.gainNode.connect(this.audioContext.destination);
this.gainNode.gain.value = 0;
}
audioStreams.forEach((stream) => {
if (!stream || !stream.getTracks().filter((t) => t.kind === 'audio').length) {
return;
}
console.log('Mixed audio tracks to add on MediaStreamAudioDestinationNode --->', stream.getTracks());
let audioSource = this.audioContext.createMediaStreamSource(stream);
if (this.useGainNode) {
audioSource.connect(this.gainNode);
}
this.audioSources.push(audioSource);
});
this.audioDestination = this.audioContext.createMediaStreamDestination();
this.audioSources.forEach((audioSource) => {
audioSource.connect(this.audioDestination);
});
return this.audioDestination.stream;
}
stopMixedAudioStream() {
if (this.useGainNode) {
this.gainNode.disconnect();
this.gainNode = null;
}
if (this.audioSources.length) {
this.audioSources.forEach((source) => {
source.disconnect();
});
this.audioSources = [];
}
if (this.audioDestination) {
this.audioDestination.disconnect();
this.audioDestination = null;
}
if (this.audioContext) {
this.audioContext.close();
this.audioContext = null;
}
console.log('Stop Mixed Audio Stream');
}
}
// Usage
// const audioRecorder = new MixedAudioRecorder();
// To start recording, call audioRecorder.getMixedAudioStream(audioStreams);
// To stop recording, call audioRecorder.stopMixedAudioStream();
// Credits: https://github.com/muaz-khan/MultiStreamsMixer

View File

@ -0,0 +1,163 @@
'use-strict';
class LocalStorage {
constructor() {
this.MEDIA_TYPE = {
audio: 'audio',
video: 'video',
audioVideo: 'audioVideo',
speaker: 'speaker',
};
this.INIT_CONFIG = {
audio: true,
video: true,
audioVideo: true,
};
this.SFU_SETTINGS = {
share_on_join: true, // popup message on join
show_chat_on_msg: true, // show chat on new message
transcript_persistent_mode: false, // Prevent stop transcript in case of no-speech
transcript_show_on_msg: true, // show transcript on new message
speech_in_msg: false, // speech incoming message
moderator_audio_start_muted: false, // Everyone starts muted in the room
moderator_video_start_hidden: false, // Everyone starts hidden in the room
moderator_audio_cant_unmute: false, // Everyone can't unmute themselves
moderator_video_cant_unhide: false, // Everyone can't unhide themselves
moderator_screen_cant_share: false, // Everyone can't share screen
moderator_chat_cant_privately: false, // Everyone can't chat privately, only Public chat allowed
moderator_chat_cant_chatgpt: false, // Everyone can't chat with ChatGPT
moderator_disconnect_all_on_leave: false, // Disconnect all participants on leave room
mic_auto_gain_control: false,
mic_echo_cancellations: true,
mic_noise_suppression: true,
mic_sample_rate: 0, // 0: 48000 Hz 1: 44100 Hz
mic_sample_size: 0, // 0: 16 bits 1: 32 bits
mic_channel_count: 0, // 0: 1(mono) 1: 2 (stereo)
mic_latency: 50, // ms
mic_volume: 100, // %
video_fps: 0, // default 1280x768 30fps
screen_fps: 3, // default 5fps
broadcasting: false, // default false (one to many a/v streaming)
lobby: false, // default false
pitch_bar: true, // volume indicator
sounds: true, // room notify sounds
host_ony_recording: false, // presenter
rec_prioritize_h264: false, // Prioritize h.264 with AAC or h.264 with Opus codecs over VP8 with Opus or VP9 with Opus codecs
rec_server: false, // The recording will be stored on the server rather than locally
video_obj_fit: 2, // cover
video_controls: 0, // off
theme: 0, // dark
theme_color: '#000000', // custom theme color
theme_custom: false, // keep custom theme
buttons_bar: 0, // vertical
pin_grid: 0, // vertical
};
this.DEVICES_COUNT = {
audio: 0,
speaker: 0,
video: 0,
};
this.LOCAL_STORAGE_DEVICES = {
audio: {
count: 0,
index: 0,
select: null,
},
speaker: {
count: 0,
index: 0,
select: null,
},
video: {
count: 0,
index: 0,
select: null,
},
};
}
// ####################################################
// SET LOCAL STORAGE
// ####################################################
setItemLocalStorage(key, value) {
localStorage.setItem(key, value);
}
setObjectLocalStorage(name, object) {
localStorage.setItem(name, JSON.stringify(object));
}
setSettings(settings) {
this.SFU_SETTINGS = settings;
this.setObjectLocalStorage('SFU_SETTINGS', this.SFU_SETTINGS);
}
setInitConfig(type, status) {
switch (type) {
case this.MEDIA_TYPE.audio:
this.INIT_CONFIG.audio = status;
break;
case this.MEDIA_TYPE.video:
this.INIT_CONFIG.video = status;
break;
case this.MEDIA_TYPE.audioVideo:
this.INIT_CONFIG.audioVideo = status;
break;
default:
break;
}
this.setObjectLocalStorage('INIT_CONFIG', this.INIT_CONFIG);
}
setLocalStorageDevices(type, index, select) {
switch (type) {
case this.MEDIA_TYPE.audio:
this.LOCAL_STORAGE_DEVICES.audio.count = this.DEVICES_COUNT.audio;
this.LOCAL_STORAGE_DEVICES.audio.index = index;
this.LOCAL_STORAGE_DEVICES.audio.select = select;
break;
case this.MEDIA_TYPE.video:
this.LOCAL_STORAGE_DEVICES.video.count = this.DEVICES_COUNT.video;
this.LOCAL_STORAGE_DEVICES.video.index = index;
this.LOCAL_STORAGE_DEVICES.video.select = select;
break;
case this.MEDIA_TYPE.speaker:
this.LOCAL_STORAGE_DEVICES.speaker.count = this.DEVICES_COUNT.speaker;
this.LOCAL_STORAGE_DEVICES.speaker.index = index;
this.LOCAL_STORAGE_DEVICES.speaker.select = select;
break;
default:
break;
}
this.setObjectLocalStorage('LOCAL_STORAGE_DEVICES', this.LOCAL_STORAGE_DEVICES);
}
// ####################################################
// GET LOCAL STORAGE
// ####################################################
getLocalStorageSettings() {
return this.getObjectLocalStorage('SFU_SETTINGS');
}
getLocalStorageInitConfig() {
return this.getObjectLocalStorage('INIT_CONFIG');
}
getLocalStorageDevices() {
return this.getObjectLocalStorage('LOCAL_STORAGE_DEVICES');
}
getItemLocalStorage(key) {
localStorage.getItem(key);
}
getObjectLocalStorage(name) {
return JSON.parse(localStorage.getItem(name));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,242 @@
'use-strict';
let isPresenter = false;
// ####################################################
// SHOW HIDE DESIRED BUTTONS BY RULES
// ####################################################
const isRulesActive = true;
/**
* WARNING!
* This will be replaced by the ui.buttons specified in the server configuration file located at app/src/config.js.
* Ensure that any changes made here are also reflected in the configuration file to maintain synchronization.
*/
let BUTTONS = {
main: {
shareButton: true, // for quest, presenter default true
hideMeButton: true,
startAudioButton: true,
startVideoButton: true,
startScreenButton: true,
swapCameraButton: true,
chatButton: true,
raiseHandButton: true,
transcriptionButton: true,
whiteboardButton: true,
emojiRoomButton: true,
settingsButton: true,
aboutButton: true, // Please keep me always visible, thank you!
exitButton: true,
},
settings: {
fileSharing: true,
lockRoomButton: true, // presenter
unlockRoomButton: true, // presenter
broadcastingButton: true, // presenter
lobbyButton: true, // presenter
sendEmailInvitation: true, // presenter
micOptionsButton: true, // presenter
tabModerator: true, // presenter
tabRecording: true,
host_only_recording: true, // presenter
pushToTalk: true,
},
producerVideo: {
videoPictureInPicture: true,
fullScreenButton: true,
snapShotButton: true,
muteAudioButton: true,
videoPrivacyButton: true,
},
consumerVideo: {
videoPictureInPicture: true,
fullScreenButton: true,
snapShotButton: true,
sendMessageButton: true,
sendFileButton: true,
sendVideoButton: true,
muteVideoButton: true,
muteAudioButton: true,
audioVolumeInput: true, // Disabled for mobile
geolocationButton: true, // Presenter
banButton: true, // presenter
ejectButton: true, // presenter
},
videoOff: {
sendMessageButton: true,
sendFileButton: true,
sendVideoButton: true,
muteAudioButton: true,
audioVolumeInput: true, // Disabled for mobile
geolocationButton: true, // Presenter
banButton: true, // presenter
ejectButton: true, // presenter
},
chat: {
chatPinButton: true,
chatMaxButton: true,
chatSaveButton: true,
chatEmojiButton: true,
chatMarkdownButton: true,
chatSpeechStartButton: true,
chatGPT: true,
},
participantsList: {
saveInfoButton: true, // presenter
sendFileAllButton: true, // presenter
ejectAllButton: true, // presenter
sendFileButton: false, // presenter & guests
geoLocationButton: true, // presenter
banButton: true, // presenter
ejectButton: true, // presenter
},
whiteboard: {
whiteboardLockButton: true, // presenter
},
//...
};
function handleRules(isPresenter) {
console.log('07.1 ----> IsPresenter: ' + isPresenter);
if (!isRulesActive) return;
if (!isPresenter) {
// ##################################
// GUEST
// ##################################
//BUTTONS.main.shareButton = false;
BUTTONS.participantsList.saveInfoButton = false;
BUTTONS.settings.lockRoomButton = false;
BUTTONS.settings.unlockRoomButton = false;
BUTTONS.settings.broadcastingButton = false;
BUTTONS.settings.lobbyButton = false;
BUTTONS.settings.sendEmailInvitation = false;
BUTTONS.settings.micOptionsButton = false;
BUTTONS.settings.tabModerator = false;
BUTTONS.videoOff.muteAudioButton = false;
BUTTONS.videoOff.geolocationButton = false;
BUTTONS.videoOff.banButton = false;
BUTTONS.videoOff.ejectButton = false;
BUTTONS.consumerVideo.geolocationButton = false;
BUTTONS.consumerVideo.banButton = false;
BUTTONS.consumerVideo.ejectButton = false;
//BUTTONS.consumerVideo.muteAudioButton = false;
//BUTTONS.consumerVideo.muteVideoButton = false;
BUTTONS.whiteboard.whiteboardLockButton = false;
//...
} else {
// ##################################
// PRESENTER
// ##################################
BUTTONS.main.shareButton = true;
BUTTONS.settings.lockRoomButton = BUTTONS.settings.lockRoomButton && !isRoomLocked;
BUTTONS.settings.unlockRoomButton = BUTTONS.settings.lockRoomButton && isRoomLocked;
BUTTONS.settings.sendEmailInvitation = true;
//...
// ##################################
// Auto detected rules for presenter
// ##################################
// Room broadcasting
isBroadcastingEnabled = localStorageSettings.broadcasting;
switchBroadcasting.checked = isBroadcastingEnabled;
rc.roomAction('broadcasting', true, false);
if (isBroadcastingEnabled) rc.toggleRoomBroadcasting();
// Room lobby
isLobbyEnabled = localStorageSettings.lobby;
switchLobby.checked = isLobbyEnabled;
rc.roomAction(isLobbyEnabled ? 'lobbyOn' : 'lobbyOff', true, false);
// Room host-only-recording
hostOnlyRecording = localStorageSettings.host_only_recording;
switchHostOnlyRecording.checked = hostOnlyRecording;
rc.roomAction(hostOnlyRecording ? 'hostOnlyRecordingOn' : 'hostOnlyRecordingOff', true, false);
// Room moderator
switchEveryoneMute.checked = localStorageSettings.moderator_audio_start_muted;
switchEveryoneHidden.checked = localStorageSettings.moderator_video_start_hidden;
switchEveryoneCantUnmute.checked = localStorageSettings.moderator_audio_cant_unmute;
switchEveryoneCantUnhide.checked = localStorageSettings.moderator_video_cant_unhide;
switchEveryoneCantShareScreen.checked = localStorageSettings.moderator_screen_cant_share;
switchEveryoneCantChatPrivately.checked = localStorageSettings.moderator_chat_cant_privately;
switchEveryoneCantChatChatGPT.checked = localStorageSettings.moderator_chat_cant_chatgpt;
switchDisconnectAllOnLeave.checked = localStorageSettings.moderator_disconnect_all_on_leave;
// Update moderator settings...
const moderatorData = {
audio_start_muted: switchEveryoneMute.checked,
video_start_hidden: switchEveryoneHidden.checked,
audio_cant_unmute: switchEveryoneCantUnmute.checked,
video_cant_unhide: switchEveryoneCantUnhide.checked,
screen_cant_share: switchEveryoneCantShareScreen.checked,
chat_cant_privately: switchEveryoneCantChatPrivately.checked,
chat_cant_chatgpt: switchEveryoneCantChatChatGPT.checked,
};
rc.updateRoomModeratorALL(moderatorData);
}
// main. settings...
BUTTONS.main.shareButton ? show(shareButton) : hide(shareButton);
BUTTONS.settings.lockRoomButton ? show(lockRoomButton) : hide(lockRoomButton);
BUTTONS.settings.unlockRoomButton ? show(unlockRoomButton) : hide(unlockRoomButton);
BUTTONS.settings.broadcastingButton ? show(broadcastingButton) : hide(broadcastingButton);
BUTTONS.settings.lobbyButton ? show(lobbyButton) : hide(lobbyButton);
BUTTONS.settings.sendEmailInvitation ? show(sendEmailInvitation) : hide(sendEmailInvitation);
!BUTTONS.settings.micOptionsButton && hide(micOptionsButton);
!BUTTONS.settings.tabModerator && hide(tabModeratorBtn);
BUTTONS.participantsList.saveInfoButton ? show(participantsSaveBtn) : hide(participantsSaveBtn);
BUTTONS.whiteboard.whiteboardLockButton
? elemDisplay('whiteboardLockButton', true)
: elemDisplay('whiteboardLockButton', false, 'flex');
//...
}
function handleRulesBroadcasting() {
console.log('07.2 ----> handleRulesBroadcasting');
BUTTONS.main.shareButton = false;
BUTTONS.main.hideMeButton = false;
BUTTONS.main.startAudioButton = false;
BUTTONS.main.startVideoButton = false;
BUTTONS.main.startScreenButton = false;
BUTTONS.main.swapCameraButton = false;
//BUTTONS.main.raiseHandButton = false;
BUTTONS.main.whiteboardButton = false;
//BUTTONS.main.emojiRoomButton = false,
BUTTONS.main.transcriptionButton = false;
BUTTONS.main.settingsButton = false;
BUTTONS.participantsList.saveInfoButton = false;
BUTTONS.settings.lockRoomButton = false;
BUTTONS.settings.unlockRoomButton = false;
BUTTONS.settings.lobbyButton = false;
BUTTONS.videoOff.muteAudioButton = false;
BUTTONS.videoOff.geolocationButton = false;
BUTTONS.videoOff.banButton = false;
BUTTONS.videoOff.ejectButton = false;
BUTTONS.consumerVideo.sendMessageButton = false;
BUTTONS.consumerVideo.sendFileButton = false;
BUTTONS.consumerVideo.sendVideoButton = false;
BUTTONS.consumerVideo.geolocationButton = false;
BUTTONS.consumerVideo.banButton = false;
BUTTONS.consumerVideo.ejectButton = false;
BUTTONS.consumerVideo.muteAudioButton = false;
BUTTONS.consumerVideo.muteVideoButton = false;
BUTTONS.whiteboard.whiteboardLockButton = false;
//...
elemDisplay('shareButton', false);
elemDisplay('hideMeButton', false);
elemDisplay('startAudioButton', false);
elemDisplay('stopAudioButton', false);
elemDisplay('startVideoButton', false);
elemDisplay('stopVideoButton', false);
elemDisplay('startScreenButton', false);
elemDisplay('stopScreenButton', false);
elemDisplay('swapCameraButton', false);
//elemDisplay('raiseHandButton', false);
elemDisplay('whiteboardButton', false);
//elemDisplay('emojiRoomButton', false);
elemDisplay('transcriptionButton', false);
elemDisplay('lockRoomButton', false);
elemDisplay('unlockRoomButton', false);
elemDisplay('lobbyButton', false);
elemDisplay('settingsButton', false);
//...
}

View File

@ -0,0 +1,50 @@
'use strict';
// https://codepen.io/tutsplus/pen/BaVqjvg
const snowContainer = document.getElementById('snow-container');
const snowContent = ['&#10052', '&#10053', '&#10054'];
const random = (num) => {
return Math.floor(Math.random() * num);
};
const getRandomStyles = () => {
const top = random(100);
const left = random(100);
const dur = random(10) + 10;
const size = random(25) + 25;
return `
top: -${top}%;
left: ${left}%;
font-size: ${size}px;
animation-duration: ${dur}s;
`;
};
const createSnow = (num) => {
for (let i = num; i > 0; i--) {
let snow = document.createElement('div');
snow.className = 'snow';
snow.style.cssText = getRandomStyles();
snow.innerHTML = snowContent[random(3)];
snowContainer.append(snow);
}
};
const removeSnow = () => {
snowContainer.style.opacity = '0';
setTimeout(() => {
snowContainer.remove();
}, 500);
};
window.addEventListener('load', () => {
createSnow(30);
setTimeout(removeSnow, 1000 * 60);
});
window.addEventListener('click', () => {
removeSnow();
});

View File

@ -0,0 +1,354 @@
'use strict';
let isWebkitSpeechRecognitionSupported = false;
let recognition;
let isVoiceCommandsEnabled = true;
let browserLanguage = navigator.language || navigator.userLanguage;
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',
showMe: 'show me',
newRoom: 'new room',
leaveRoom: 'exit the room',
audioOn: 'start the audio',
audioOff: 'stop the audio',
videoOn: 'start the video',
videoOff: 'stop the video',
screenOn: 'start the screen',
screenOff: 'stop the screen',
chatOn: 'open the chat',
chatSend: 'send',
chatOff: 'close the chat',
toggleTr: 'toggle transcription',
whiteboardOn: 'open the whiteboard',
whiteboardOff: 'close the whiteboard',
recordingOn: 'start the recording',
recordingPause: 'pause the recording',
recordingResume: 'resume the recording',
recordingOff: 'stop the recording',
settingsOn: 'open the settings',
settingsOff: 'close the settings',
participantsOn: 'show the participants',
participantsOff: 'hide the participants',
participantsVideoOff: 'stop the participants video',
participantsAudioOff: 'stop the participants audio',
participantsKickOut: 'kick out the participants',
fileShareOn: 'open a file',
fileShareOff: 'close a file',
videoShareOn: 'share the video',
videoShareOff: 'close the video',
swapCamera: 'swap the camera',
raiseHand: 'raise the hand',
lowerHand: 'lower the hand',
roomLock: 'lock the room',
roomUnlock: 'unlock the room',
about: 'show the about',
email: 'open email',
google: 'open google',
googleTr: 'open google translate',
youtube: 'open youtube',
facebook: 'open facebook',
linkedin: 'open linkedin',
twitter: 'open twitter',
tiktok: 'open tiktok',
github: 'open github',
survey: 'open survey',
stopRecognition: 'stop the voice recognition',
};
const browser = {
newroom: '/newroom',
email: 'mailto:?subject=&body=',
google: 'https://www.google.com',
googleTr: 'https://translate.google.com/',
youtube: 'https://www.youtube.com',
facebook: 'https://www.facebook.com',
linkedin: 'https://www.linkedin.com',
twitter: 'https://www.twitter.com',
tiktok: 'https://www.tiktok.com',
github: 'https://github.com/miroslavpejic85',
};
if (speechRecognition) {
recognition = new speechRecognition();
recognition.maxAlternatives = 1;
recognition.continuous = true;
recognition.lang = browserLanguage;
console.log('Speech recognition', recognition);
recognition.onstart = function () {
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) {
if (transcript.trim().toLowerCase() != commands.chatSend) {
chatMessage.value = transcript;
}
if (isVoiceCommandsEnabled && isVoiceCommandSupported) {
execVoiceCommands(transcript);
}
}
};
recognition.onerror = function (event) {
console.error('Speech recognition error', event.error);
userLog('error', `Speech recognition error ${event.error}`, 'top-end', 6000);
};
recognition.onend = function () {
console.log('Speech recognition stopped');
show(chatSpeechStartButton);
hide(chatSpeechStopButton);
setColor(chatSpeechStopButton, 'white');
userLog('info', 'Speech recognition stopped', 'top-end');
};
isWebkitSpeechRecognitionSupported = true;
console.info('Browser supports webkitSpeechRecognition');
} else {
console.warn('This browser not supports webkitSpeechRecognition');
}
function startSpeech() {
recognition.lang = browserLanguage;
recognition.start();
}
function stopSpeech() {
recognition.stop();
}
function execVoiceCommands(transcript) {
switch (transcript.trim().toLowerCase()) {
case commands.shareRoom:
printCommand(commands.shareRoom);
shareButton.click();
break;
case commands.hideMe:
printCommand(commands.hideMe);
hideMeButton.click();
break;
case commands.showMe:
printCommand(commands.showMe);
hideMeButton.click();
break;
case commands.newRoom:
printCommand(commands.newRoom);
openURL(browser.newroom);
break;
case commands.leaveRoom:
printCommand(commands.leaveRoom);
exitButton.click();
break;
case commands.audioOn:
printCommand(commands.audioOn);
startAudioButton.click();
break;
case commands.audioOff:
printCommand(commands.audioOff);
stopAudioButton.click();
break;
case commands.videoOn:
printCommand(commands.videoOn);
startVideoButton.click();
break;
case commands.videoOff:
printCommand(commands.videoOff);
stopVideoButton.click();
break;
case commands.screenOn:
printCommand(commands.screenOn);
startScreenButton.click();
break;
case commands.screenOff:
printCommand(commands.screenOff);
stopScreenButton.click();
break;
case commands.chatOn:
printCommand(commands.chatOn);
chatButton.click();
break;
case commands.chatSend:
printCommand(commands.chatSend);
chatSendButton.click();
break;
case commands.chatOff:
printCommand(commands.chatOff);
chatCloseButton.click();
break;
case commands.toggleTr:
transcriptionButton.click();
break;
case commands.whiteboardOn:
printCommand(commands.whiteboardOn);
whiteboardButton.click();
break;
case commands.whiteboardOff:
printCommand(commands.whiteboardOff);
whiteboardCloseBtn.click();
break;
case commands.recordingOn:
printCommand(commands.recordingOn);
startRecButton.click();
break;
case commands.recordingPause:
printCommand(commands.recordingPause);
pauseRecButton.click();
break;
case commands.recordingResume:
printCommand(commands.recordingResume);
recordingResume.click();
break;
case commands.recordingOff:
printCommand(commands.recordingOff);
stopRecButton.click();
break;
case commands.settingsOn:
printCommand(commands.settingsOn);
settingsButton.click();
break;
case commands.settingsOff:
printCommand(commands.settingsOff);
mySettingsCloseBtn.click();
break;
case commands.participantsOn:
printCommand(commands.participantsOn);
chatButton.click();
break;
case commands.participantsOff:
printCommand(commands.participantsOff);
chatCloseButton.click();
break;
case commands.participantsVideoOff:
printCommand(commands.participantsVideoOff);
rc.peerAction('me', socket.id, 'hide', true, true);
break;
case commands.participantsAudioOff:
printCommand(commands.participantsAudioOff);
rc.peerAction('me', socket.id, 'mute', true, true);
break;
case commands.participantsKickOut:
printCommand(commands.participantsKickOut);
rc.peerAction('me', socket.id, 'eject', true, true);
break;
case commands.fileShareOn:
printCommand(commands.fileShareOn);
fileShareButton.click();
break;
case commands.fileShareOff:
printCommand(commands.fileShareOff);
sendAbortBtn.click();
break;
case commands.videoShareOn:
printCommand(commands.videoShareOn);
videoShareButton.click();
break;
case commands.videoShareOff:
printCommand(commands.videoShareOff);
videoCloseBtn.click();
break;
case commands.swapCamera:
printCommand(commands.swapCamera);
swapCameraButton.click();
break;
case commands.raiseHand:
printCommand(commands.raiseHand);
raiseHandButton.click();
break;
case commands.lowerHand:
printCommand(commands.lowerHand);
lowerHandButton.click();
break;
case commands.roomLock:
printCommand(commands.roomLock);
lockRoomButton.click();
break;
case commands.roomUnlock:
printCommand(commands.roomUnlock);
unlockRoomButton.click();
break;
case commands.about:
printCommand(commands.about);
aboutButton.click();
break;
case commands.email:
printCommand(commands.email);
openURL(browser.email, true);
sound('open');
break;
case commands.google:
printCommand(commands.google);
openURL(browser.google, true);
sound('open');
break;
case commands.googleTr:
printCommand(commands.googleTr);
openURL(browser.googleTr, true);
sound('open');
break;
case commands.youtube:
printCommand(commands.youtube);
openURL(browser.youtube, true);
sound('open');
break;
case commands.facebook:
printCommand(commands.facebook);
openURL(browser.facebook, true);
sound('open');
break;
case commands.linkedin:
printCommand(commands.linkedin);
openURL(browser.linkedin, true);
sound('open');
break;
case commands.twitter:
printCommand(commands.twitter);
openURL(browser.twitter, true);
sound('open');
break;
case commands.tiktok:
printCommand(commands.tiktok);
openURL(browser.tiktok, true);
sound('open');
break;
case commands.github:
printCommand(commands.github);
openURL(browser.github, true);
sound('open');
break;
case commands.survey:
printCommand(commands.survey);
survey.enabled && openURL(survey.url, true);
sound('open');
break;
case commands.stopRecognition:
printCommand(commands.stopRecognition);
chatSpeechStopButton.click();
break;
// ...
default:
break;
}
}
function printCommand(command) {
console.log('Detected', { command: command });
}

View File

@ -0,0 +1,429 @@
'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;
this.isHidden = true;
this.isPersistentMode = false;
this.isPersistent = false;
this.showOnMessage = true;
}
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');
hide(transcriptionSpeechStart);
show(transcriptionSpeechStop);
setColor(transcriptionSpeechStatus, 'lime');
!transcription.isPersistentMode
? userLog('info', 'Transcription started', 'top-end')
: (transcription.isPersistent = true);
};
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(),
broadcast: true,
};
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) {
console.error('Transcription error', event.error);
if (!transcription.isPersistent || !transcription.isPersistentMode)
userLog('error', `Transcription error ${event.error}`, 'top-end', 6000);
};
this.transcription.onend = function () {
console.log('Transcription stopped');
hide(transcriptionSpeechStop);
show(transcriptionSpeechStart);
setColor(transcriptionSpeechStatus, 'white');
// Prevent stopping in the absence of speech...
if (
transcription.isPersistentMode &&
transcription.isPersistent &&
transcription.transcriptionRunning
) {
setTimeout(() => {
transcription.start();
}, 2000);
} else {
transcription.isPersistent = false;
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.isValidEmail(peer_name) ? rc.genGravatar(peer_name) : rc.genAvatarSvg(peer_name, 32);
if (this.isHidden) {
if (this.showOnMessage) {
this.toggle();
} else {
this.handleTranscriptionPopup(transcriptionData);
}
}
const msgHTML = `
<div class="msg-transcription left-msg-transcription">
<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');
}
handleTranscriptionPopup(transcriptionData, duration = 5000) {
const transcriptionDisplay = document.createElement('div');
transcriptionDisplay.className = 'animate__animated animate__fadeInUp';
transcriptionDisplay.style.padding = '10px';
transcriptionDisplay.style.fontSize = '1rem';
transcriptionDisplay.style.color = '#FFF';
transcriptionDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
transcriptionDisplay.style.borderRadius = '10px';
transcriptionDisplay.innerText = `${transcriptionData.peer_name}: ${transcriptionData.text_data}`;
transcriptionPopup.appendChild(transcriptionDisplay);
setTimeout(() => {
transcriptionDisplay.remove();
}, duration);
}
toggle() {
if (this.isHidden) {
this.center();
transcriptionRoom.style.display = 'block';
rc.sound('open');
} else {
transcriptionRoom.style.display = 'none';
}
this.isHidden = !this.isHidden;
if (this.isPinned) this.unpinned();
resizeTranscriptionRoom();
}
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();
resizeTranscriptionRoom();
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.selectDisabled(true);
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.isPersistent = false;
this.selectDisabled(false);
this.transcription.stop();
}
selectDisabled(disabled = false) {
transcriptionLanguage.disabled = disabled;
transcriptionDialect.disabled = disabled;
transcriptPersistentMode.disabled = disabled;
}
}

View File

@ -0,0 +1,39 @@
'use strict';
console.log('STATS', window.location);
const statsDataKey = 'statsData';
const statsData = window.sessionStorage.getItem(statsDataKey);
const apiUrl = window.location.origin + '/stats';
if (statsData) {
setStats(JSON.parse(statsData));
} else {
fetch(apiUrl, { timeout: 5000 })
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => {
setStats(data);
window.sessionStorage.setItem(statsDataKey, JSON.stringify(data));
})
.catch((error) => {
console.error('Stats fetch error', error);
});
}
function setStats(data) {
console.log('STATS', data);
const { enabled, src, id } = data;
if (enabled) {
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('src', src);
script.setAttribute('data-website-id', id);
document.head.appendChild(script);
}
}

View File

@ -0,0 +1,218 @@
'use strict';
// ####################################################
// RESPONSIVE PARTICIPANTS VIEW
// ####################################################
let customRatio = true;
// aspect 0 1 2 3 4
let ratios = ['0:0', '4:3', '16:9', '1:1', '1:2'];
let aspect = 2;
let ratio = getAspectRatio();
function getAspectRatio() {
customRatio = aspect == 0 ? true : false;
var ratio = ratios[aspect].split(':');
return ratio[1] / ratio[0];
}
function setAspectRatio(i) {
aspect = i;
ratio = getAspectRatio();
resizeVideoMedia();
}
function Area(Increment, Count, Width, Height, Margin = 10) {
ratio = customRatio ? 0.75 : ratio;
let i = 0;
let w = 0;
let h = Increment * ratio + Margin * 2;
while (i < Count) {
if (w + Increment > Width) {
w = 0;
h = h + Increment * ratio + Margin * 2;
}
w = w + Increment + Margin * 2;
i++;
}
if (h > Height) return false;
else return Increment;
}
function resizeVideoMedia() {
let Margin = 5;
let videoMediaContainer = document.getElementById('videoMediaContainer');
let Cameras = document.getElementsByClassName('Camera');
let Width = videoMediaContainer.offsetWidth - Margin * 2;
let Height = videoMediaContainer.offsetHeight - Margin * 2;
let max = 0;
let optional = isHideMeActive && videoMediaContainer.childElementCount <= 2 ? 1 : 0;
let isOneVideoElement = videoMediaContainer.childElementCount - optional == 1 ? true : false;
// console.log('videoMediaContainer.childElementCount', {
// isOneVideoElement: isOneVideoElement,
// children: videoMediaContainer.childElementCount,
// optional: optional,
// });
// full screen mode
let bigWidth = Width * 4;
if (isOneVideoElement) {
Width = Width - bigWidth;
}
resetZoom();
// loop (i recommend you optimize this)
let i = 1;
while (i < 5000) {
let w = Area(i, Cameras.length, Width, Height, Margin);
if (w === false) {
max = i - 1;
break;
}
i++;
}
max = max - Margin * 2;
setWidth(Cameras, max, bigWidth, Margin, Height, isOneVideoElement);
document.documentElement.style.setProperty('--vmi-wh', max / 3 + 'px');
}
function resetZoom() {
const videoElements = document.querySelectorAll('video');
videoElements.forEach((video) => {
video.style.transform = '';
video.style.transformOrigin = 'center';
});
}
function setWidth(Cameras, width, bigWidth, margin, maxHeight, isOneVideoElement) {
ratio = customRatio ? 0.68 : ratio;
for (let s = 0; s < Cameras.length; s++) {
Cameras[s].style.width = width + 'px';
Cameras[s].style.margin = margin + 'px';
Cameras[s].style.height = width * ratio + 'px';
if (isOneVideoElement) {
Cameras[s].style.width = bigWidth + 'px';
Cameras[s].style.height = bigWidth * ratio + 'px';
let camHeigh = Cameras[s].style.height.substring(0, Cameras[s].style.height.length - 2);
if (camHeigh >= maxHeight) Cameras[s].style.height = maxHeight - 2 + 'px';
}
}
}
// ####################################################
// BREAKPOINTS
// ####################################################
const MOBILE_BREAKPOINT = 500;
const TABLET_BREAKPOINT = 580;
const DESKTOP_BREAKPOINT = 730;
const CUSTOM_BREAKPOINT = 680;
// ####################################################
// RESPONSIVE MAIN BUTTONS
// ####################################################
const mainButtonsBar = document.querySelectorAll('#control button');
const mainButtonsIcon = document.querySelectorAll('#control button i');
function resizeMainButtons() {
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const isButtonsBarVertical = BtnsBarPosition.selectedIndex === 0;
//console.log('Window size', { width: windowWidth, height: windowWidth});
if (isButtonsBarVertical) {
// Main buttons vertical align
if (windowHeight <= MOBILE_BREAKPOINT) {
setStyles(mainButtonsBar, '0.7rem', '4px', mainButtonsIcon, '0.8rem', '40px');
} else if (windowHeight <= TABLET_BREAKPOINT) {
setStyles(mainButtonsBar, '0.9rem', '4px', mainButtonsIcon, '1rem', '45px');
} else if (windowHeight <= DESKTOP_BREAKPOINT) {
setStyles(mainButtonsBar, '1rem', '5px', mainButtonsIcon, '1.1rem', '50px');
} else {
// > DESKTOP_BREAKPOINT
setStyles(mainButtonsBar, '1rem', '10px', mainButtonsIcon, '1.2rem', '60px');
}
} else {
// Main buttons horizontal align
if (windowWidth <= MOBILE_BREAKPOINT) {
setStyles(mainButtonsBar, '0.7rem', '4px', mainButtonsIcon, '0.8rem');
} else if (windowWidth <= TABLET_BREAKPOINT) {
setStyles(mainButtonsBar, '0.9rem', '4px', mainButtonsIcon, '1rem');
} else if (windowWidth <= DESKTOP_BREAKPOINT) {
setStyles(mainButtonsBar, '1rem', '5px', mainButtonsIcon, '1.1rem');
} else {
// > DESKTOP_BREAKPOINT
setStyles(mainButtonsBar, '1rem', '10px', mainButtonsIcon, '1.2rem');
}
}
//
function setStyles(elements, fontSize, padding, icons, fontSizeIcon, bWidth = null) {
if (bWidth) document.documentElement.style.setProperty('--btns-width', bWidth);
elements.forEach(function (element) {
element.style.fontSize = fontSize;
element.style.padding = padding;
});
icons.forEach(function (icon) {
icon.style.fontSize = fontSizeIcon;
});
}
}
// ####################################################
// RESPONSIVE CHAT
// ####################################################
function resizeChatRoom() {
if (!rc || rc.isMobileDevice || !rc.isChatOpen || rc.isChatPinned) return;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
windowWidth <= DESKTOP_BREAKPOINT || windowHeight <= DESKTOP_BREAKPOINT ? rc.chatMaximize() : rc.chatMinimize();
}
// ####################################################
// RESPONSIVE TRANSCRIPTION
// ####################################################
function resizeTranscriptionRoom() {
if (
DetectRTC.isMobileDevice ||
!Boolean(transcription.speechTranscription) ||
transcription.isHidden ||
transcription.isPinned
)
return;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
windowWidth <= CUSTOM_BREAKPOINT || windowHeight <= CUSTOM_BREAKPOINT
? transcription.maximize()
: transcription.minimize();
}
// ####################################################
// WINDOW LOAD/RESIZE EVENT
// ####################################################
window.addEventListener(
'load',
function (event) {
resizeVideoMedia();
resizeMainButtons();
window.onresize = function () {
resizeVideoMedia();
resizeMainButtons();
resizeChatRoom();
resizeTranscriptionRoom();
};
},
false,
);

View File

@ -0,0 +1,419 @@
!(function () {
window;
const e = document.documentElement;
if ((e.classList.remove('no-js'), e.classList.add('js'), document.body.classList.contains('has-animations'))) {
(window.sr = ScrollReveal()).reveal('.feature, .pricing-table-inner', {
duration: 600,
distance: '20px',
easing: 'cubic-bezier(0.5, -0.01, 0, 1.005)',
origin: 'bottom',
interval: 100,
}),
e.classList.add('anime-ready'),
anime
.timeline({ targets: '.hero-figure-box-05' })
.add({
duration: 400,
easing: 'easeInOutExpo',
scaleX: [0.05, 0.05],
scaleY: [0, 1],
perspective: '500px',
delay: anime.random(0, 400),
})
.add({ duration: 400, easing: 'easeInOutExpo', scaleX: 1 })
.add({
duration: 800,
rotateY: '-15deg',
rotateX: '8deg',
rotateZ: '-1deg',
}),
anime
.timeline({ targets: '.hero-figure-box-06, .hero-figure-box-07' })
.add({
duration: 400,
easing: 'easeInOutExpo',
scaleX: [0.05, 0.05],
scaleY: [0, 1],
perspective: '500px',
delay: anime.random(0, 400),
})
.add({ duration: 400, easing: 'easeInOutExpo', scaleX: 1 })
.add({ duration: 800, rotateZ: '20deg' }),
anime({
targets:
'.hero-figure-box-01, .hero-figure-box-02, .hero-figure-box-03, .hero-figure-box-04, .hero-figure-box-08, .hero-figure-box-09, .hero-figure-box-10',
duration: anime.random(600, 800),
delay: anime.random(600, 800),
rotate: [
anime.random(-360, 360),
function (e) {
return e.getAttribute('data-rotation');
},
],
scale: [0.7, 1],
opacity: [0, 1],
easing: 'easeInOutExpo',
});
}
})();
(function () {
'use strict';
const e = document.querySelectorAll('[class*=reveal-]');
let t = window.innerHeight;
function n(e, t) {
let n = 0;
return function () {
let i = new Date().getTime();
if (!(i - n < e)) return (n = i), t.apply(void 0, arguments);
};
}
function i() {
let n;
for (let i = 0; i < e.length; i++) {
let s = e[i],
a = s.getAttribute('data-reveal-delay'),
l = s.getAttribute('data-reveal-offset') ? s.getAttribute('data-reveal-offset') : '200',
c = s.getAttribute('data-reveal-container') ? s.closest(s.getAttribute('data-reveal-container')) : s;
(n = l),
c.getBoundingClientRect().top <= t - n &&
!s.classList.contains('is-revealed') &&
(a && 0 !== a
? setTimeout(function () {
s.classList.add('is-revealed');
}, a)
: s.classList.add('is-revealed'));
}
!(function () {
if (e.length > document.querySelectorAll('[class*=reveal-].is-revealed').length) return;
window.removeEventListener('load', i),
window.removeEventListener('scroll', s),
window.removeEventListener('resize', a);
})();
}
function s() {
n(30, i());
}
function a() {
(t = window.innerHeight), n(30, i());
}
e.length > 0 &&
document.body.classList.contains('has-animations') &&
(window.addEventListener('load', i),
window.addEventListener('scroll', s),
window.addEventListener('resize', a));
})();
!(function () {
'use strict';
const e = document.getElementsByClassName('accordion-header');
function t(e, t) {
e.classList.add('is-active'), (t.style.maxHeight = t.scrollHeight + 'px');
}
function n(e, t) {
e.classList.remove('is-active'), (t.style.maxHeight = null);
}
if (e.length > 0)
for (let i = 0; i < e.length; i++) {
const s = e[i],
a = s.parentNode,
l = s.nextElementSibling;
a.classList.contains('is-active') && t(a, l),
s.addEventListener('click', function () {
a.classList.contains('is-active') ? n(a, l) : t(a, l);
});
}
})(),
(function () {
'use strict';
let e = {
touchStartX: 0,
touchEndX: 0,
minSwipePixels: 30,
detectionZone: void 0,
swipeCallback: function () {},
init: function (t, n) {
(e.swipeCallback = n),
t.addEventListener(
'touchstart',
function (t) {
e.touchStartX = t.changedTouches[0].screenX;
},
!1,
),
t.addEventListener(
'touchend',
function (t) {
(e.touchEndX = t.changedTouches[0].screenX), e.handleSwipeGesture();
},
!1,
);
},
handleSwipeGesture: function () {
let t, n;
e.touchEndX <= e.touchStartX && ((n = e.touchStartX - e.touchEndX), (t = 'left')),
e.touchEndX >= e.touchStartX && ((n = e.touchEndX - e.touchStartX), (t = 'right')),
n > e.minSwipePixels && 'undefined' !== t && e.swipe(t, n);
},
swipe: function (t, n) {
let i = {};
(i.direction = t), (i.movedPixels = n), e.swipeCallback(i);
},
};
const t = document.getElementsByClassName('carousel-items');
function n(e, t) {
void 0 === t && (t = 'next');
let n = e.getElementsByClassName('carousel-item is-active')[0],
i = 'next' === t ? n.nextElementSibling : n.previousElementSibling,
s = n.getAttribute('data-carousel'),
a = e.parentNode.getElementsByClassName('carousel-bullet')[s],
l = 'next' === t ? a.nextElementSibling : a.previousElementSibling;
n.classList.remove('is-active'),
a.classList.remove('is-active'),
i
? (i.classList.add('is-active'), l.classList.add('is-active'))
: 'next' === t
? (e.firstElementChild.classList.add('is-active'),
e.parentNode
.getElementsByClassName('carousel-bullets')[0]
.firstElementChild.classList.add('is-active'))
: (e.lastElementChild.classList.add('is-active'),
e.parentNode
.getElementsByClassName('carousel-bullets')[0]
.lastElementChild.classList.add('is-active'));
}
function i(e, t) {
let n,
i = 0;
for (let e = 0; e < t.length; e++)
(t[0].parentNode.style.minHeight = i + 'px'),
t[e].classList.add('is-loading'),
(n = t[e].offsetHeight),
t[e].classList.remove('is-loading'),
n > i && (i = n);
t[0].parentNode.style.minHeight = i + 'px';
}
function s(e) {
e && clearInterval(e);
}
if (t.length > 0)
for (let a = 0; a < t.length; a++) {
let l = t[a],
c = l.getElementsByClassName('carousel-item'),
o = 0,
r = l.getAttribute('data-autorotate');
const d = document.createElement('div');
(d.className = 'carousel-bullets'), l.parentNode.insertBefore(d, l.nextSibling);
for (let e = 0; e < c.length; e++) {
c[e].setAttribute('data-carousel', e), c[e].classList.contains('is-active') && (o = e);
let t = document.createElement('button');
(t.className = 'carousel-bullet'),
t.setAttribute('data-bullet', e),
l.parentNode.getElementsByClassName('carousel-bullets')[0].appendChild(t);
}
c[o].classList.add('is-active');
let u = l.parentNode.getElementsByClassName('carousel-bullet');
u[o].classList.add('is-active'),
i(0, c),
window.addEventListener('resize', function () {
i(0, c);
});
let m = !1;
r &&
(m = setInterval(function () {
n(l, 'next');
}, r));
for (let e = 0; e < u.length; e++) {
let t = u[e];
t.addEventListener('click', function (e) {
if ((e.preventDefault(), t.classList.contains('is-active'))) return;
for (let e = 0; e < u.length; e++) u[e].classList.remove('is-active');
for (let e = 0; e < c.length; e++) c[e].classList.remove('is-active');
let n = this.getAttribute('data-bullet');
c[n].classList.add('is-active'), this.classList.add('is-active'), s(m);
});
}
e.init(l, function (e) {
'left' === e.direction ? n(l, 'next') : 'right' === e.direction && n(l, 'prev'), s(m);
});
}
})(),
(function () {
'use strict';
document.documentElement.classList.remove('no-js'),
document.documentElement.classList.add('js'),
window.addEventListener('load', function () {
document.body.classList.add('is-loaded');
});
})(),
(function () {
'use strict';
const e = document.getElementById('header-nav-toggle'),
t = document.getElementById('header-nav');
e &&
(e.addEventListener('click', function () {
document.body.classList.toggle('off-nav-is-active'),
t.classList.toggle('is-active'),
t.style.maxHeight ? (t.style.maxHeight = null) : (t.style.maxHeight = t.scrollHeight + 'px'),
'true' === this.getAttribute('aria-expanded')
? this.setAttribute('aria-expanded', 'false')
: this.setAttribute('aria-expanded', 'true');
}),
document.addEventListener('click', function (n) {
n.target === t ||
n.target === e ||
t.contains(n.target) ||
(document.body.classList.remove('off-nav-is-active'),
t.classList.remove('is-active'),
(t.style.maxHeight = null),
e.setAttribute('aria-expanded', 'false'));
}));
})(),
(function () {
'use strict';
const e = document.getElementsByClassName('modal'),
t = document.getElementsByClassName('modal-trigger');
function n() {
document.body.classList.remove('modal-is-active');
for (let t = 0; t < e.length; t++) e[t].classList.remove('is-active');
}
if (e.length > 0 && t.length > 0)
for (let e = 0; e < t.length; e++) {
let n = t[e],
i = document.getElementById(n.getAttribute('aria-controls'));
i &&
(n.hasAttribute('data-video') &&
(null !== i.querySelector('iframe')
? i.querySelector('iframe').setAttribute('src', n.getAttribute('data-video'))
: null !== i.querySelector('video') &&
i.querySelector('video').setAttribute('src', n.getAttribute('data-video'))),
n.addEventListener('click', function (e) {
let t;
e.preventDefault(),
n.hasAttribute('aria-controls') &&
(t = i) &&
(document.body.classList.add('modal-is-active'), t.classList.add('is-active'));
}));
}
document.addEventListener('click', function (e) {
(e.target.classList.contains('modal') || e.target.classList.contains('modal-close-trigger')) &&
(e.preventDefault(), n());
}),
document.addEventListener('keydown', function (e) {
27 === (e || window.event).keyCode && n();
});
})(),
(function () {
'use strict';
const e = document.getElementById('pricing-toggle');
function t() {
const t = document.getElementsByClassName('pricing-switchable');
if (e.checked) for (let e = 0; e < t.length; e++) t[e].innerHTML = t[e].getAttribute('data-pricing-yearly');
else for (let e = 0; e < t.length; e++) t[e].innerHTML = t[e].getAttribute('data-pricing-monthly');
}
e && (window.addEventListener('load', t), e.addEventListener('change', t));
})(),
(function () {
'use strict';
const e = document.querySelectorAll('[class*=reveal-]');
let t = window.innerHeight;
function n(e, t) {
let n = 0;
return function () {
let i = new Date().getTime();
if (!(i - n < e)) return (n = i), t.apply(void 0, arguments);
};
}
function i() {
let n;
for (let i = 0; i < e.length; i++) {
let s = e[i],
a = s.getAttribute('data-reveal-delay'),
l = s.getAttribute('data-reveal-offset') ? s.getAttribute('data-reveal-offset') : '200',
c = s.getAttribute('data-reveal-container')
? s.closest(s.getAttribute('data-reveal-container'))
: s;
(n = l),
c.getBoundingClientRect().top <= t - n &&
!s.classList.contains('is-revealed') &&
(a && 0 !== a
? setTimeout(function () {
s.classList.add('is-revealed');
}, a)
: s.classList.add('is-revealed'));
}
!(function () {
if (e.length > document.querySelectorAll('[class*=reveal-].is-revealed').length) return;
window.removeEventListener('load', i),
window.removeEventListener('scroll', s),
window.removeEventListener('resize', a);
})();
}
function s() {
n(30, i());
}
function a() {
(t = window.innerHeight), n(30, i());
}
e.length > 0 &&
document.body.classList.contains('has-animations') &&
(window.addEventListener('load', i),
window.addEventListener('scroll', s),
window.addEventListener('resize', a));
})(),
(function () {
'use strict';
const e = document.getElementsByClassName('smooth-scroll'),
t = (e, n, i, s, a) => {
const l = n - e;
let c = l / i;
const o = (function (e) {
return e < 0.5 ? 2 * e * e : (4 - 2 * e) * e - 1;
})((c = Math.min(c, 1)));
window.scroll(0, a + s * o),
l < i &&
window.requestAnimationFrame((n) => {
const l = n || new Date().getTime();
t(e, l, i, s, a);
});
};
if (e.length > 0)
for (let n = 0; n < e.length; n++) {
e[n].addEventListener('click', function (e) {
e.preventDefault();
const n = e.target.closest('.smooth-scroll'),
i = n.href.split('#')[1],
s = document.getElementById(i),
a = n.getAttribute('data-duration') || 1e3;
s &&
window.requestAnimationFrame((e) => {
const n = e || new Date().getTime(),
i = n,
l = window.pageYOffset,
c = s.getBoundingClientRect().top;
t(i, n, a, c, l);
});
});
}
})();

View File

@ -0,0 +1,155 @@
'use strict';
const adjectives = [
'small',
'big',
'large',
'smelly',
'new',
'happy',
'shiny',
'old',
'clean',
'nice',
'bad',
'cool',
'hot',
'cold',
'warm',
'hungry',
'slow',
'fast',
'red',
'white',
'black',
'blue',
'green',
'basic',
'strong',
'cute',
'poor',
'nice',
'huge',
'rare',
'lucky',
'weak',
'tall',
'short',
'tiny',
'great',
'long',
'single',
'rich',
'young',
'dirty',
'fresh',
'brown',
'dark',
'crazy',
'sad',
'loud',
'brave',
'calm',
'silly',
'smart',
];
const nouns = [
'dog',
'bat',
'wrench',
'apple',
'pear',
'ghost',
'cat',
'wolf',
'squid',
'goat',
'snail',
'hat',
'sock',
'plum',
'bear',
'snake',
'turtle',
'horse',
'spoon',
'fork',
'spider',
'tree',
'chair',
'table',
'couch',
'towel',
'panda',
'bread',
'grape',
'cake',
'brick',
'rat',
'mouse',
'bird',
'oven',
'phone',
'photo',
'frog',
'bear',
'camel',
'sheep',
'shark',
'tiger',
'zebra',
'duck',
'eagle',
'fish',
'kitten',
'lobster',
'monkey',
'owl',
'puppy',
'pig',
'rabbit',
'fox',
'whale',
'beaver',
'gorilla',
'lizard',
'parrot',
'sloth',
'swan',
];
function getRandomNumber(length) {
let result = '';
let characters = '0123456789';
let charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
let adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
let noun = nouns[Math.floor(Math.random() * nouns.length)];
let num = getRandomNumber(5);
noun = noun.charAt(0).toUpperCase() + noun.substring(1);
adjective = adjective.charAt(0).toUpperCase() + adjective.substring(1);
document.getElementById('roomName').value = '';
// ####################################################
// TYPING EFFECT
// ####################################################
let i = 0;
let txt = num + adjective + noun;
let speed = 100;
function typeWriter() {
if (i < txt.length) {
document.getElementById('roomName').value += txt.charAt(i);
i++;
setTimeout(typeWriter, speed);
}
}
typeWriter();