'use strict'; if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.href.substr(4, location.href.length - 4); /** * MiroTalk SFU - Room component * * @link GitHub: https://github.com/miroslavpejic85/mirotalksfu * @link Official Live demo: https://sfu.mirotalk.com * @license For open source use: AGPLv3 * @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.4.20 * */ // #################################################### // STATIC SETTINGS // #################################################### console.log('Window Location', window.location); const socket = io({ transports: ['websocket'] }); let survey = { enabled: true, url: 'https://www.questionpro.com/t/AUs7VZq02P', }; let redirect = { enabled: true, url: '/newroom', }; let recCodecs = null; let recPrioritizeH264 = false; const _PEER = { presenter: '', guest: '', audioOn: '', audioOff: '', videoOn: '', videoOff: '', screenOn: '', screenOff: '', raiseHand: '', lowerHand: '', acceptPeer: '', banPeer: '', ejectPeer: '', geoLocation: '', sendFile: '', sendMsg: '', sendVideo: '', }; const initUser = document.getElementById('initUser'); const initVideoContainerClass = document.querySelector('.init-video-container'); const bars = document.querySelectorAll('.volume-bar'); const userAgent = navigator.userAgent.toLowerCase(); const isTabletDevice = isTablet(userAgent); const isIPadDevice = isIpad(userAgent); const Base64Prefix = 'data:application/pdf;base64,'; const wbImageInput = 'image/*'; const wbPdfInput = 'application/pdf'; const wbWidth = 1200; const wbHeight = 600; const swalImageUrl = '../images/pricing-illustration.svg'; // Media const sinkId = 'sinkId' in HTMLMediaElement.prototype; // #################################################### // LOCAL STORAGE // #################################################### const lS = new LocalStorage(); const localStorageSettings = lS.getLocalStorageSettings() || lS.SFU_SETTINGS; const localStorageDevices = lS.getLocalStorageDevices() || lS.LOCAL_STORAGE_DEVICES; const localStorageInitConfig = lS.getLocalStorageInitConfig() || lS.INIT_CONFIG; console.log('LOCAL_STORAGE', { localStorageSettings: localStorageSettings, localStorageDevices: localStorageDevices, localStorageInitConfig: localStorageInitConfig, }); // #################################################### // THEME CUSTOM COLOR - PICKER // #################################################### const themeCustom = { input: document.getElementById('themeColorPicker'), color: localStorageSettings.theme_color ? localStorageSettings.theme_color : '#000000', keep: localStorageSettings.theme_custom ? localStorageSettings.theme_custom : false, }; const pickr = Pickr.create({ el: themeCustom.input, theme: 'classic', // or 'monolith', or 'nano' default: themeCustom.color, useAsButton: true, swatches: [ 'rgba(244, 67, 54, 1)', 'rgba(233, 30, 99, 0.95)', 'rgba(156, 39, 176, 0.9)', 'rgba(103, 58, 183, 0.85)', 'rgba(63, 81, 181, 0.8)', 'rgba(33, 150, 243, 0.75)', 'rgba(3, 169, 244, 0.7)', 'rgba(0, 188, 212, 0.7)', 'rgba(0, 150, 136, 0.75)', 'rgba(76, 175, 80, 0.8)', 'rgba(139, 195, 74, 0.85)', 'rgba(205, 220, 57, 0.9)', 'rgba(255, 235, 59, 0.95)', 'rgba(255, 193, 7, 1)', ], components: { // Main components preview: true, opacity: true, hue: true, // Input / output Options interaction: { hex: false, rgba: false, hsla: false, hsva: false, cmyk: false, input: false, clear: false, save: false, }, }, }) .on('init', (pickr) => { themeCustom.input.value = pickr.getSelectedColor().toHEXA().toString(0); }) .on('change', (color) => { themeCustom.color = color.toHEXA().toString(); themeCustom.input.value = themeCustom.color; setCustomTheme(); }) .on('changestop', (color) => { localStorageSettings.theme_color = themeCustom.color; lS.setSettings(localStorageSettings); }); // #################################################### // ENUMERATE DEVICES SELECTS // #################################################### const videoSelect = getId('videoSelect'); const initVideoSelect = getId('initVideoSelect'); const microphoneSelect = getId('microphoneSelect'); const initMicrophoneSelect = getId('initMicrophoneSelect'); const speakerSelect = getId('speakerSelect'); const initSpeakerSelect = getId('initSpeakerSelect'); // #################################################### // DYNAMIC SETTINGS // #################################################### let swalBackground = 'radial-gradient(#393939, #000000)'; //'rgba(0, 0, 0, 0.7)'; let rc = null; let producer = null; let participantsCount = 0; let lobbyParticipantsCount = 0; let chatMessagesId = 0; let room_id = getRoomId(); let room_password = getRoomPassword(); let peer_name = getPeerName(); let peer_uuid = getPeerUUID(); let peer_token = getPeerToken(); let isScreenAllowed = getScreen(); let isHideMeActive = getHideMeActive(); let notify = getNotify(); isPresenter = isPeerPresenter(); let peer_info = null; let isPushToTalkActive = false; let isSpaceDown = false; let isPitchBarEnabled = true; let isSoundEnabled = true; let isBroadcastingEnabled = false; let isLobbyEnabled = false; let isLobbyOpen = false; let hostOnlyRecording = false; let isEnumerateAudioDevices = false; let isEnumerateVideoDevices = false; let isAudioAllowed = false; let isVideoAllowed = false; let isVideoPrivacyActive = false; let isRecording = false; let isAudioVideoAllowed = false; let isParticipantsListOpen = false; let isVideoControlsOn = false; let isChatPasteTxt = false; let isChatMarkdownOn = false; let isChatGPTOn = false; let isSpeechSynthesisSupported = 'speechSynthesis' in window; let joinRoomWithoutAudioVideo = true; let joinRoomWithScreen = false; let recTimer = null; let recElapsedTime = null; let wbCanvas = null; let wbIsLock = false; let wbIsDrawing = false; let wbIsOpen = false; let wbIsRedoing = false; let wbIsEraser = false; let wbIsBgTransparent = false; let wbPop = []; let coords = {}; let isButtonsVisible = false; let isButtonsBarOver = false; let isRoomLocked = false; let initStream = null; let scriptProcessor = null; const RoomURL = window.location.origin + '/join/' + room_id; // window.location.origin + '/join/?room=' + roomId + '&token=' + myToken let transcription; // #################################################### // INIT ROOM // #################################################### function initClient() { setTheme(); // Transcription transcription = new Transcription(); transcription.init(); if (!DetectRTC.isMobileDevice) { refreshMainButtonsToolTipPlacement(); setTippy('closeEmojiPickerContainer', 'Close', 'bottom'); setTippy('mySettingsCloseBtn', 'Close', 'bottom'); setTippy( 'switchPushToTalk', 'If Active, When SpaceBar keydown the microphone will be resumed, on keyup will be paused, like a walkie-talkie.', 'right', ); setTippy('lobbyAcceptAllBtn', 'Accept', 'top'); setTippy('lobbyRejectAllBtn', 'Reject', 'top'); setTippy( 'switchBroadcasting', 'Broadcasting is the dissemination of audio or video content to a large audience (one to many)', 'right', ); setTippy( 'switchLobby', 'Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator', 'right', ); setTippy('initVideoAudioRefreshButton', 'Refresh audio/video devices', 'top'); setTippy('switchPitchBar', 'Toggle audio pitch bar', 'right'); setTippy('switchSounds', 'Toggle the sounds notifications', 'right'); setTippy('switchShare', "Show 'Share Room' popup on join", 'right'); setTippy('roomId', 'Room name (click to copy)', 'right'); setTippy('sessionTime', 'Session time', 'right'); setTippy('recordingImage', 'Toggle recording', 'right'); setTippy( 'switchHostOnlyRecording', 'Only the host (presenter) has the capability to record the meeting', 'right', ); setTippy( 'switchH264Recording', 'Prioritize h.264 with AAC or h.264 with Opus codecs over VP8 with Opus or VP9 with Opus codecs', 'right', ); setTippy('switchServerRecording', 'The recording will be stored on the server rather than locally', 'right'); setTippy('whiteboardGhostButton', 'Toggle transparent background', 'bottom'); setTippy('wbBackgroundColorEl', 'Background color', 'bottom'); setTippy('wbDrawingColorEl', 'Drawing color', 'bottom'); setTippy('whiteboardPencilBtn', 'Drawing mode', 'bottom'); setTippy('whiteboardObjectBtn', 'Object mode', 'bottom'); setTippy('whiteboardUndoBtn', 'Undo', 'bottom'); setTippy('whiteboardRedoBtn', 'Redo', 'bottom'); setTippy('whiteboardImgFileBtn', 'Add image file', 'bottom'); setTippy('whiteboardPdfFileBtn', 'Add pdf file', 'bottom'); setTippy('whiteboardImgUrlBtn', 'Add image url', 'bottom'); setTippy('whiteboardTextBtn', 'Add text', 'bottom'); setTippy('whiteboardLineBtn', 'Add line', 'bottom'); setTippy('whiteboardRectBtn', 'Add rectangle', 'bottom'); setTippy('whiteboardTriangleBtn', 'Add triangle', 'bottom'); setTippy('whiteboardCircleBtn', 'Add circle', 'bottom'); setTippy('whiteboardSaveBtn', 'Save', 'bottom'); setTippy('whiteboardEraserBtn', 'Eraser', 'bottom'); setTippy('whiteboardCleanBtn', 'Clean', 'bottom'); setTippy('whiteboardLockButton', 'If enabled, participants cannot interact', 'right'); setTippy('whiteboardCloseBtn', 'Close', 'right'); setTippy('chatCleanTextButton', 'Clean', 'top'); setTippy('chatPasteButton', 'Paste', 'top'); setTippy('chatSendButton', 'Send', 'top'); setTippy('showChatOnMsg', 'Show chat on new message comes', 'bottom'); setTippy('speechIncomingMsg', 'Speech the incoming messages', 'bottom'); setTippy('chatSpeechStartButton', 'Start speech recognition', 'top'); setTippy('chatSpeechStopButton', 'Stop speech recognition', 'top'); setTippy('chatEmojiButton', 'Emoji', 'top'); setTippy('chatMarkdownButton', 'Markdown', 'top'); setTippy('chatCloseButton', 'Close', 'bottom'); setTippy('chatTogglePin', 'Toggle pin', 'bottom'); setTippy('chatHideParticipantsList', 'Hide', 'bottom'); setTippy('chatShowParticipantsList', 'Toggle participants list', 'bottom'); setTippy('chatMaxButton', 'Maximize', 'bottom'); setTippy('chatMinButton', 'Minimize', 'bottom'); setTippy('participantsSaveBtn', 'Save participants info', 'bottom'); setTippy('participantsRaiseHandBtn', 'Toggle raise hands', 'bottom'); setTippy('participantsUnreadMessagesBtn', 'Toggle unread messages', 'bottom'); setTippy('transcriptionCloseBtn', 'Close', 'bottom'); setTippy('transcriptionTogglePinBtn', 'Toggle pin', 'bottom'); setTippy('transcriptionMaxBtn', 'Maximize', 'bottom'); setTippy('transcriptionMinBtn', 'Minimize', 'bottom'); setTippy('transcriptionSpeechStatus', 'Status', 'bottom'); setTippy('transcriptShowOnMsg', 'Show transcript on new message comes', 'bottom'); setTippy('transcriptPersistentMode', 'Prevent stopping in the absence of speech', 'bottom'); setTippy('transcriptionSpeechStart', 'Start transcription', 'top'); setTippy('transcriptionSpeechStop', 'Stop transcription', 'top'); } setupWhiteboard(); initEnumerateDevices(); } // #################################################### // HANDLE MAIN BUTTONS TOOLTIP // #################################################### function refreshMainButtonsToolTipPlacement() { if (!DetectRTC.isMobileDevice) { const placement = BtnsBarPosition.options[BtnsBarPosition.selectedIndex].value == 'vertical' ? 'right' : 'top'; setTippy('shareButton', 'Share room', placement); setTippy('hideMeButton', 'Toggle hide self view', placement); setTippy('startAudioButton', 'Start the audio', placement); setTippy('stopAudioButton', 'Stop the audio', placement); setTippy('startVideoButton', 'Start the video', placement); setTippy('stopVideoButton', 'Stop the video', placement); setTippy('startScreenButton', 'Start screen share', placement); setTippy('stopScreenButton', 'Stop screen share', placement); setTippy('startRecButton', 'Start recording', placement); setTippy('stopRecButton', 'Stop recording', placement); setTippy('raiseHandButton', 'Raise your hand', placement); setTippy('lowerHandButton', 'Lower your hand', placement); setTippy('emojiRoomButton', 'Toggle emoji reaction', placement); setTippy('swapCameraButton', 'Swap the camera', placement); setTippy('chatButton', 'Toggle the chat', placement); setTippy('transcriptionButton', 'Toggle transcription', placement); setTippy('whiteboardButton', 'Toggle the whiteboard', placement); setTippy('settingsButton', 'Toggle the settings', placement); setTippy('aboutButton', 'About this project', placement); setTippy('exitButton', 'Leave room', placement); } } // #################################################### // HANDLE TOOLTIP // #################################################### function setTippy(elem, content, placement, allowHTML = false) { const element = document.getElementById(elem); if (element) { if (element._tippy) { element._tippy.destroy(); } try { tippy(element, { content: content, placement: placement, allowHTML: allowHTML, }); } catch (err) { console.error('setTippy error', err.message); } } else { console.warn('setTippy element not found with content', content); } } // #################################################### // GET ROOM ID // #################################################### function getRoomId() { let qs = new URLSearchParams(window.location.search); let queryRoomId = filterXSS(qs.get('room')); let roomId = queryRoomId ? queryRoomId : location.pathname.substring(6); if (roomId == '') { roomId = makeId(12); } console.log('Direct join', { room: roomId }); window.localStorage.lastRoom = roomId; return roomId; } function makeId(length) { let result = ''; let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } // #################################################### // INIT ROOM // #################################################### async function initRoom() { if (!isAudioAllowed && !isVideoAllowed && !joinRoomWithoutAudioVideo) { openURL(`/permission?room_id=${room_id}&message=Not allowed both Audio and Video`); } else { setButtonsInit(); handleSelectsInit(); await whoAreYou(); await setSelectsInit(); } } // #################################################### // ENUMERATE DEVICES // #################################################### async function initEnumerateDevices() { console.log('01 ----> init Enumerate Devices'); await initEnumerateVideoDevices(); await initEnumerateAudioDevices(); await initRoom(); } async function refreshMyAudioVideoDevices() { await refreshMyVideoDevices(); await refreshMyAudioDevices(); } async function refreshMyVideoDevices() { if (!isVideoAllowed) return; const initVideoSelectIndex = initVideoSelect ? initVideoSelect.selectedIndex : 0; const videoSelectIndex = videoSelect ? videoSelect.selectedIndex : 0; await initEnumerateVideoDevices(); if (initVideoSelect) initVideoSelect.selectedIndex = initVideoSelectIndex; if (videoSelect) videoSelect.selectedIndex = videoSelectIndex; } async function refreshMyAudioDevices() { if (!isAudioAllowed) return; const initMicrophoneSelectIndex = initMicrophoneSelect ? initMicrophoneSelect.selectedIndex : 0; const initSpeakerSelectIndex = initSpeakerSelect ? initSpeakerSelect.selectedIndex : 0; const microphoneSelectIndex = microphoneSelect ? microphoneSelect.selectedIndex : 0; const speakerSelectIndex = speakerSelect ? speakerSelect.selectedIndex : 0; await initEnumerateAudioDevices(); if (initMicrophoneSelect) initMicrophoneSelect.selectedIndex = initMicrophoneSelectIndex; if (initSpeakerSelect) initSpeakerSelect.selectedIndex = initSpeakerSelectIndex; if (microphoneSelect) microphoneSelect.selectedIndex = microphoneSelectIndex; if (speakerSelect) speakerSelect.selectedIndex = speakerSelectIndex; } async function initEnumerateVideoDevices() { // allow the video await navigator.mediaDevices .getUserMedia({ video: true }) .then(async (stream) => { await enumerateVideoDevices(stream); isVideoAllowed = true; }) .catch(() => { isVideoAllowed = false; }); } async function enumerateVideoDevices(stream) { console.log('02 ----> Get Video Devices'); if (videoSelect) videoSelect.innerHTML = ''; if (initVideoSelect) initVideoSelect.innerHTML = ''; await navigator.mediaDevices .enumerateDevices() .then((devices) => devices.forEach(async (device) => { let el, eli = null; if ('videoinput' === device.kind) { if (videoSelect) el = videoSelect; if (initVideoSelect) eli = initVideoSelect; lS.DEVICES_COUNT.video++; } if (!el) return; await addChild(device, [el, eli]); }), ) .then(async () => { await stopTracks(stream); isEnumerateVideoDevices = true; }); } async function initEnumerateAudioDevices() { // allow the audio await navigator.mediaDevices .getUserMedia({ audio: true }) .then(async (stream) => { await enumerateAudioDevices(stream); await getMicrophoneVolumeIndicator(stream); isAudioAllowed = true; }) .catch(() => { isAudioAllowed = false; }); } async function enumerateAudioDevices(stream) { console.log('03 ----> Get Audio Devices'); if (microphoneSelect) microphoneSelect.innerHTML = ''; if (initMicrophoneSelect) initMicrophoneSelect.innerHTML = ''; if (speakerSelect) speakerSelect.innerHTML = ''; if (initSpeakerSelect) initSpeakerSelect.innerHTML = ''; await navigator.mediaDevices .enumerateDevices() .then((devices) => devices.forEach(async (device) => { let el, eli = null; if ('audioinput' === device.kind) { if (microphoneSelect) el = microphoneSelect; if (initMicrophoneSelect) eli = initMicrophoneSelect; lS.DEVICES_COUNT.audio++; } else if ('audiooutput' === device.kind) { if (speakerSelect) el = speakerSelect; if (initSpeakerSelect) eli = initSpeakerSelect; lS.DEVICES_COUNT.speaker++; } if (!el) return; await addChild(device, [el, eli]); }), ) .then(async () => { await stopTracks(stream); isEnumerateAudioDevices = true; speakerSelect.disabled = !sinkId; // Check if there is speakers if (!sinkId || initSpeakerSelect.options.length === 0) { hide(initSpeakerSelect); hide(speakerSelectDiv); } }); } async function stopTracks(stream) { stream.getTracks().forEach((track) => { track.stop(); }); } async function addChild(device, els) { let kind = device.kind; els.forEach((el) => { let option = document.createElement('option'); option.value = device.deviceId; switch (kind) { case 'videoinput': option.innerText = `ðđ ` + device.label || `ðđ camera ${el.length + 1}`; break; case 'audioinput': option.innerText = `ðĪ ` + device.label || `ðĪ microphone ${el.length + 1}`; break; case 'audiooutput': option.innerText = `ð ` + device.label || `ð speaker ${el.length + 1}`; break; default: break; } el.appendChild(option); }); } // #################################################### // MICROPHONE VOLUME INDICATOR // #################################################### async function getMicrophoneVolumeIndicator(stream) { if (isAudioContextSupported() && hasAudioTrack(stream)) { stopMicrophoneProcessing(); const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const microphone = audioContext.createMediaStreamSource(stream); scriptProcessor = audioContext.createScriptProcessor(1024, 1, 1); scriptProcessor.onaudioprocess = function (event) { const inputBuffer = event.inputBuffer.getChannelData(0); let sum = 0; for (let i = 0; i < inputBuffer.length; i++) { sum += inputBuffer[i] * inputBuffer[i]; } const rms = Math.sqrt(sum / inputBuffer.length); const volume = Math.max(0, Math.min(1, rms * 10)); updateVolumeIndicator(volume); }; microphone.connect(scriptProcessor); scriptProcessor.connect(audioContext.destination); } } function stopMicrophoneProcessing() { if (scriptProcessor) { scriptProcessor.disconnect(); scriptProcessor = null; } bars.forEach((bar) => { bar.classList.toggle('inactive'); }); } function updateVolumeIndicator(volume) { const activeBars = Math.ceil(volume * bars.length); bars.forEach((bar, index) => { bar.classList.toggle('active', index < activeBars); }); } function isAudioContextSupported() { return !!(window.AudioContext || window.webkitAudioContext); } function hasAudioTrack(mediaStream) { const audioTracks = mediaStream.getAudioTracks(); return audioTracks.length > 0; } function hasVideoTrack(mediaStream) { const videoTracks = mediaStream.getVideoTracks(); return videoTracks.length > 0; } // #################################################### // API CHECK // #################################################### function getScreen() { let qs = new URLSearchParams(window.location.search); let screen = filterXSS(qs.get('screen')); if (screen) { screen = screen.toLowerCase(); let queryScreen = screen === '1' || screen === 'true'; if (queryScreen != null && (navigator.getDisplayMedia || navigator.mediaDevices.getDisplayMedia)) { console.log('Direct join', { screen: queryScreen }); return queryScreen; } } console.log('Direct join', { screen: false }); return false; } function getNotify() { let qs = new URLSearchParams(window.location.search); let notify = filterXSS(qs.get('notify')); if (notify) { notify = notify.toLowerCase(); let queryNotify = notify === '1' || notify === 'true'; if (queryNotify != null) { console.log('Direct join', { notify: queryNotify }); return queryNotify; } } notify = localStorageSettings.share_on_join; console.log('Direct join', { notify: notify }); return notify; } function getHideMeActive() { let qs = new URLSearchParams(window.location.search); let hide = filterXSS(qs.get('hide')); let queryHideMe = false; if (hide) { hide = hide.toLowerCase(); queryHideMe = hide === '1' || hide === 'true'; } console.log('Direct join', { hide: queryHideMe }); return queryHideMe; } function isPeerPresenter() { let qs = new URLSearchParams(window.location.search); let presenter = filterXSS(qs.get('isPresenter')); if (presenter) { presenter = presenter.toLowerCase(); let queryPresenter = presenter === '1' || presenter === 'true'; if (queryPresenter != null) { console.log('Direct join Reconnect', { isPresenter: queryPresenter }); return queryPresenter; } } console.log('Direct join Reconnect', { presenter: false }); return false; } function getPeerName() { const qs = new URLSearchParams(window.location.search); const name = filterXSS(qs.get('name')); if (isHtml(name)) { console.log('Direct join', { name: 'Invalid name' }); return 'Invalid name'; } console.log('Direct join', { name: name }); return name; } function getPeerUUID() { if (lS.getItemLocalStorage('peer_uuid')) { return lS.getItemLocalStorage('peer_uuid'); } const peer_uuid = getUUID(); lS.setItemLocalStorage('peer_uuid', peer_uuid); return peer_uuid; } function getPeerToken() { if (window.sessionStorage.peer_token) return window.sessionStorage.peer_token; let qs = new URLSearchParams(window.location.search); let token = filterXSS(qs.get('token')); let queryToken = false; if (token) { queryToken = token; } console.log('Direct join', { token: queryToken }); return queryToken; } function getRoomPassword() { let qs = new URLSearchParams(window.location.search); let roomPassword = filterXSS(qs.get('roomPassword')); if (roomPassword) { let queryNoRoomPassword = roomPassword === '0' || roomPassword === 'false'; if (queryNoRoomPassword) { roomPassword = false; } console.log('Direct join', { password: roomPassword }); return roomPassword; } return false; } // #################################################### // INIT CONFIG // #################################################### async function checkInitConfig() { const localStorageInitConfig = lS.getLocalStorageInitConfig(); console.log('04.5 ----> Get init config', localStorageInitConfig); if (localStorageInitConfig) { if (isAudioVideoAllowed && !localStorageInitConfig.audioVideo) { await handleAudioVideo(); } else { if (isAudioAllowed && !localStorageInitConfig.audio) handleAudio(); if (isVideoAllowed && !localStorageInitConfig.video) handleVideo(); } } } // #################################################### // SOME PEER INFO // #################################################### function getPeerInfo() { peer_info = { join_data_time: getDataTimeString(), peer_uuid: peer_uuid, peer_id: socket.id, peer_name: peer_name, peer_token: peer_token, peer_presenter: isPresenter, peer_audio: isAudioAllowed, peer_video: isVideoAllowed, peer_screen: isScreenAllowed, peer_recording: isRecording, peer_video_privacy: isVideoPrivacyActive, peer_hand: false, is_desktop_device: !DetectRTC.isMobileDevice && !isTabletDevice && !isIPadDevice, is_mobile_device: DetectRTC.isMobileDevice, is_tablet_device: isTabletDevice, is_ipad_pro_device: isIPadDevice, os_name: DetectRTC.osName, os_version: DetectRTC.osVersion, browser_name: DetectRTC.browser.name, browser_version: DetectRTC.browser.version, user_agent: userAgent, }; } // #################################################### // ENTER YOUR NAME | Enable/Disable AUDIO/VIDEO // #################################################### async function whoAreYou() { console.log('04 ----> Who are you?'); hide(loadingDiv); document.body.style.background = 'var(--body-bg)'; try { const response = await axios.get('/config', { timeout: 5000, }); const serverButtons = response.data.message; if (serverButtons) { BUTTONS = serverButtons; console.log('04 ----> AXIOS ROOM BUTTONS SETTINGS', { serverButtons: serverButtons, clientButtons: BUTTONS, }); } } catch (error) { console.error('04 ----> AXIOS GET CONFIG ERROR', error.message); } if (navigator.getDisplayMedia || navigator.mediaDevices.getDisplayMedia) { BUTTONS.main.startScreenButton && show(initStartScreenButton); } if (peer_name) { checkMedia(); getPeerInfo(); joinRoom(peer_name, room_id); return; } let default_name = window.localStorage.peer_name ? window.localStorage.peer_name : ''; if (getCookie(room_id + '_name')) { default_name = getCookie(room_id + '_name'); } if (!BUTTONS.main.startVideoButton) { isVideoAllowed = false; elemDisplay('initVideo', false); elemDisplay('initVideoButton', false); elemDisplay('initAudioVideoButton', false); elemDisplay('initVideoAudioRefreshButton', false); elemDisplay('initVideoSelect', false); elemDisplay('tabVideoDevicesBtn', false); initVideoContainerShow(false); } if (!BUTTONS.main.startAudioButton) { isAudioAllowed = false; elemDisplay('initAudioButton', false); elemDisplay('initAudioVideoButton', false); elemDisplay('initVideoAudioRefreshButton', false); elemDisplay('initMicrophoneSelect', false); elemDisplay('initSpeakerSelect', false); elemDisplay('tabAudioDevicesBtn', false); } if (!BUTTONS.main.startScreenButton) { hide(initStartScreenButton); } initUser.classList.toggle('hidden'); Swal.fire({ allowOutsideClick: false, allowEscapeKey: false, background: swalBackground, title: BRAND.app.name, input: 'text', inputPlaceholder: 'Enter your name', inputAttributes: { maxlength: 32 }, inputValue: default_name, html: initUser, // Inject HTML confirmButtonText: `Join meeting`, customClass: { popup: 'init-modal-size' }, showClass: { popup: 'animate__animated animate__fadeInDown' }, hideClass: { popup: 'animate__animated animate__fadeOutUp' }, inputValidator: (name) => { if (!name) return 'Please enter your name'; if (name.length > 30) return 'Name must be max 30 char'; name = filterXSS(name); if (isHtml(name)) return 'Invalid name!'; if (!getCookie(room_id + '_name')) { window.localStorage.peer_name = name; } setCookie(room_id + '_name', name, 30); peer_name = name; }, }).then(async () => { if (initStream && !joinRoomWithScreen) { await stopTracks(initStream); elemDisplay('initVideo', false); initVideoContainerShow(false); } getPeerInfo(); joinRoom(peer_name, room_id); }); if (!isVideoAllowed) { elemDisplay('initVideo', false); initVideoContainerShow(false); hide(initVideoSelect); } if (!isAudioAllowed) { hide(initMicrophoneSelect); hide(initSpeakerSelect); } } function handleAudio() { isAudioAllowed = isAudioAllowed ? false : true; initAudioButton.className = 'fas fa-microphone' + (isAudioAllowed ? '' : '-slash'); setColor(initAudioButton, isAudioAllowed ? 'white' : 'red'); setColor(startAudioButton, isAudioAllowed ? 'white' : 'red'); checkInitAudio(isAudioAllowed); lS.setInitConfig(lS.MEDIA_TYPE.audio, isAudioAllowed); } function handleVideo() { isVideoAllowed = isVideoAllowed ? false : true; initVideoButton.className = 'fas fa-video' + (isVideoAllowed ? '' : '-slash'); setColor(initVideoButton, isVideoAllowed ? 'white' : 'red'); setColor(startVideoButton, isVideoAllowed ? 'white' : 'red'); checkInitVideo(isVideoAllowed); lS.setInitConfig(lS.MEDIA_TYPE.video, isVideoAllowed); } async function handleAudioVideo() { isAudioVideoAllowed = isAudioVideoAllowed ? false : true; isAudioAllowed = isAudioVideoAllowed; isVideoAllowed = isAudioVideoAllowed; lS.setInitConfig(lS.MEDIA_TYPE.audio, isAudioVideoAllowed); lS.setInitConfig(lS.MEDIA_TYPE.video, isAudioVideoAllowed); lS.setInitConfig(lS.MEDIA_TYPE.audioVideo, isAudioVideoAllowed); initAudioButton.className = 'fas fa-microphone' + (isAudioVideoAllowed ? '' : '-slash'); initVideoButton.className = 'fas fa-video' + (isAudioVideoAllowed ? '' : '-slash'); initAudioVideoButton.className = 'fas fa-eye' + (isAudioVideoAllowed ? '' : '-slash'); if (!isAudioVideoAllowed) { hide(initAudioButton); hide(initVideoButton); hide(initVideoAudioRefreshButton); } if (isAudioAllowed && isVideoAllowed && !DetectRTC.isMobileDevice) show(initVideoAudioRefreshButton); setColor(initAudioVideoButton, isAudioVideoAllowed ? 'white' : 'red'); setColor(initAudioButton, isAudioAllowed ? 'white' : 'red'); setColor(initVideoButton, isVideoAllowed ? 'white' : 'red'); setColor(startAudioButton, isAudioAllowed ? 'white' : 'red'); setColor(startVideoButton, isVideoAllowed ? 'white' : 'red'); await checkInitVideo(isVideoAllowed); checkInitAudio(isAudioAllowed); } async function checkInitVideo(isVideoAllowed) { if (isVideoAllowed && BUTTONS.main.startVideoButton) { if (initVideoSelect.value) { initVideoContainerShow(); await changeCamera(initVideoSelect.value); } sound('joined'); } else { if (initStream) { stopTracks(initStream); elemDisplay('initVideo', false); initVideoContainerShow(false); sound('left'); } } initVideoSelect.disabled = !isVideoAllowed; } function checkInitAudio(isAudioAllowed) { initMicrophoneSelect.disabled = !isAudioAllowed; initSpeakerSelect.disabled = !isAudioAllowed; isAudioAllowed ? sound('joined') : sound('left'); } function initVideoContainerShow(show = true) { initVideoContainerClass.style.width = show ? '100%' : 'auto'; } function checkMedia() { let qs = new URLSearchParams(window.location.search); let audio = filterXSS(qs.get('audio')); let video = filterXSS(qs.get('video')); if (audio) { audio = audio.toLowerCase(); let queryPeerAudio = audio === '1' || audio === 'true'; if (queryPeerAudio != null) isAudioAllowed = queryPeerAudio; } if (video) { video = video.toLowerCase(); let queryPeerVideo = video === '1' || video === 'true'; if (queryPeerVideo != null) isVideoAllowed = queryPeerVideo; } // elemDisplay('tabVideoDevicesBtn', isVideoAllowed); // elemDisplay('tabAudioDevicesBtn', isAudioAllowed); console.log('Direct join', { audio: isAudioAllowed, video: isVideoAllowed, }); } // #################################################### // SHARE ROOM // #################################################### async function shareRoom(useNavigator = false) { if (navigator.share && useNavigator) { try { await navigator.share({ url: RoomURL }); userLog('info', 'Room Shared successfully', 'top-end'); } catch (err) { share(); } } else { share(); } function share() { sound('open'); Swal.fire({ background: swalBackground, position: 'center', title: 'Share the room', html: `
Join from your mobile device
No need for apps, simply capture the QR code with your mobile camera Or Invite someone else to join by sending them the following URL
${RoomURL}
`, showDenyButton: true, showCancelButton: true, cancelButtonColor: 'red', denyButtonColor: 'green', confirmButtonText: `Copy URL`, denyButtonText: `Email invite`, cancelButtonText: `Close`, showClass: { popup: 'animate__animated animate__fadeInDown' }, hideClass: { popup: 'animate__animated animate__fadeOutUp' }, }).then((result) => { if (result.isConfirmed) { copyRoomURL(); } else if (result.isDenied) { shareRoomByEmail(); } // share screen on join if (isScreenAllowed) { rc.shareScreen(); } }); makeRoomQR(); } } // #################################################### // ROOM UTILITY // #################################################### function makeRoomQR() { let qr = new QRious({ element: document.getElementById('qrRoom'), value: RoomURL, }); qr.set({ size: 256, }); } function copyRoomURL() { let tmpInput = document.createElement('input'); document.body.appendChild(tmpInput); tmpInput.value = RoomURL; tmpInput.select(); tmpInput.setSelectionRange(0, 99999); // For mobile devices navigator.clipboard.writeText(tmpInput.value); document.body.removeChild(tmpInput); userLog('info', 'Meeting URL copied to clipboard ð', 'top-end'); } function shareRoomByEmail() { Swal.fire({ allowOutsideClick: false, allowEscapeKey: false, background: swalBackground, imageUrl: image.email, position: 'center', title: 'Select a Date and Time', html: '', showCancelButton: true, confirmButtonText: 'OK', cancelButtonColor: 'red', showClass: { popup: 'animate__animated animate__fadeInDown' }, hideClass: { popup: 'animate__animated animate__fadeOutUp' }, preConfirm: () => { const newLine = '%0D%0A%0D%0A'; const selectedDateTime = document.getElementById('datetimePicker').value; const roomPassword = isRoomLocked && (room_password || rc.RoomPassword) ? 'Password: ' + (room_password || rc.RoomPassword) + newLine : ''; const email = ''; const emailSubject = `Please join our ${BRAND.app.name} Video Chat Meeting`; const emailBody = `The meeting is scheduled at: ${newLine} DateTime: ${selectedDateTime} ${newLine}${roomPassword}Click to join: ${RoomURL} ${newLine}`; document.location = 'mailto:' + email + '?subject=' + emailSubject + '&body=' + emailBody; }, }); flatpickr('#datetimePicker', { enableTime: true, dateFormat: 'Y-m-d H:i', time_24hr: true, }); } // #################################################### // JOIN ROOM // #################################################### function joinRoom(peer_name, room_id) { if (rc && rc.isConnected()) { console.log('Already connected to a room'); } else { console.log('05 ----> join Room ' + room_id); roomId.innerText = room_id; userName.innerText = peer_name; isUserPresenter.innerText = isPresenter; rc = new RoomClient( localAudio, remoteAudios, videoMediaContainer, videoPinMediaContainer, window.mediasoupClient, socket, room_id, peer_name, peer_uuid, peer_info, isAudioAllowed, isVideoAllowed, isScreenAllowed, joinRoomWithScreen, isSpeechSynthesisSupported, transcription, roomIsReady, ); handleRoomClientEvents(); //notify ? shareRoom() : sound('joined'); } } function roomIsReady() { if (rc.isValidEmail(peer_name)) { myProfileAvatar.style.borderRadius = `50px`; myProfileAvatar.setAttribute('src', rc.genGravatar(peer_name)); } else { myProfileAvatar.setAttribute('src', rc.genAvatarSvg(peer_name, 64)); } BUTTONS.main.exitButton && show(exitButton); BUTTONS.main.shareButton && show(shareButton); BUTTONS.main.hideMeButton && show(hideMeButton); if (BUTTONS.settings.tabRecording) { show(startRecButton); } else { hide(startRecButton); hide(tabRecordingBtn); } BUTTONS.main.chatButton && show(chatButton); BUTTONS.main.raiseHandButton && show(raiseHandButton); BUTTONS.main.emojiRoomButton && show(emojiRoomButton); !BUTTONS.chat.chatSaveButton && hide(chatSaveButton); BUTTONS.chat.chatEmojiButton && show(chatEmojiButton); BUTTONS.chat.chatMarkdownButton && show(chatMarkdownButton); 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); if (DetectRTC.isMobileDevice) { hide(initVideoAudioRefreshButton); hide(refreshVideoDevices); hide(refreshAudioDevices); BUTTONS.main.swapCameraButton && show(swapCameraButton); rc.chatMaximize(); hide(chatTogglePin); hide(chatMaxButton); hide(chatMinButton); transcription.maximize(); hide(transcriptionTogglePinBtn); hide(transcriptionMaxBtn); hide(transcriptionMinBtn); } else { rc.makeDraggable(emojiPickerContainer, emojiPickerHeader); rc.makeDraggable(chatRoom, chatHeader); rc.makeDraggable(mySettings, mySettingsHeader); rc.makeDraggable(whiteboard, whiteboardHeader); 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); show(ScreenFpsDiv); } } BUTTONS.chat.chatPinButton && show(chatTogglePin); BUTTONS.chat.chatMaxButton && show(chatMaxButton); BUTTONS.settings.pushToTalk && show(pushToTalkDiv); } if (DetectRTC.browser.name != 'Safari') { document.onfullscreenchange = () => { if (!document.fullscreenElement) rc.isDocumentOnFullScreen = false; }; show(fullScreenButton); } BUTTONS.main.whiteboardButton && show(whiteboardButton); BUTTONS.main.settingsButton && show(settingsButton); isAudioAllowed ? show(stopAudioButton) : BUTTONS.main.startAudioButton && show(startAudioButton); isVideoAllowed ? show(stopVideoButton) : BUTTONS.main.startVideoButton && show(startVideoButton); BUTTONS.settings.fileSharing && show(fileShareButton); BUTTONS.settings.lockRoomButton && show(lockRoomButton); BUTTONS.settings.broadcastingButton && show(broadcastingButton); BUTTONS.settings.lobbyButton && show(lobbyButton); BUTTONS.settings.sendEmailInvitation && show(sendEmailInvitation); if (BUTTONS.settings.host_only_recording) { show(recordingImage); show(roomHostOnlyRecording); show(roomRecordingOptions); } if (rc.recSyncServerRecording) show(roomRecordingServer); BUTTONS.main.aboutButton && show(aboutButton); if (!DetectRTC.isMobileDevice) show(pinUnpinGridDiv); if (!isSpeechSynthesisSupported) hide(speechMsgDiv); handleButtons(); handleSelects(); handleInputs(); handleChatEmojiPicker(); handleRoomEmojiPicker(); loadSettingsFromLocalStorage(); startSessionTimer(); document.body.addEventListener('mousemove', (e) => { showButtons(); }); checkButtonsBar(); if (room_password) { lockRoomButton.click(); } } function elemDisplay(element, display, mode = 'block') { const elem = document.getElementById(element); elem ? (elem.style.display = display ? mode : 'none') : console.error('elemDisplay not found', element); } function hide(elem) { if (!elem.classList.contains('hidden')) elem.classList.toggle('hidden'); } function show(elem) { if (elem.classList.contains('hidden')) elem.classList.toggle('hidden'); } function disable(elem, disabled) { elem.disabled = disabled; } function setColor(elem, color) { elem.style.color = color; } // #################################################### // SESSION TIMER // #################################################### function startSessionTimer() { sessionTime.style.display = 'inline'; let callStartTime = Date.now(); setInterval(function printTime() { let callElapsedTime = Date.now() - callStartTime; sessionTime.innerText = getTimeToString(callElapsedTime); }, 1000); } function getTimeToString(time) { let diffInHrs = time / 3600000; let hh = Math.floor(diffInHrs); let diffInMin = (diffInHrs - hh) * 60; let mm = Math.floor(diffInMin); let diffInSec = (diffInMin - mm) * 60; let ss = Math.floor(diffInSec); let formattedHH = hh.toString().padStart(2, '0'); let formattedMM = mm.toString().padStart(2, '0'); let formattedSS = ss.toString().padStart(2, '0'); return `${formattedHH}:${formattedMM}:${formattedSS}`; } // #################################################### // RECORDING TIMER // #################################################### function secondsToHms(d) { d = Number(d); let h = Math.floor(d / 3600); let m = Math.floor((d % 3600) / 60); let s = Math.floor((d % 3600) % 60); let hDisplay = h > 0 ? h + 'h' : ''; let mDisplay = m > 0 ? m + 'm' : ''; let sDisplay = s > 0 ? s + 's' : ''; return hDisplay + ' ' + mDisplay + ' ' + sDisplay; } function startRecordingTimer() { recElapsedTime = 0; recTimer = setInterval(function printTime() { if (rc.isRecording()) { recElapsedTime++; recordingStatus.innerText = secondsToHms(recElapsedTime); } }, 1000); } function stopRecordingTimer() { clearInterval(recTimer); } // #################################################### // HTML BUTTONS // #################################################### function handleButtons() { control.onmouseover = () => { isButtonsBarOver = true; }; control.onmouseout = () => { isButtonsBarOver = false; }; exitButton.onclick = () => { rc.exitRoom(); }; shareButton.onclick = () => { shareRoom(true); }; hideMeButton.onclick = (e) => { isHideMeActive = !isHideMeActive; rc.handleHideMe(); }; settingsButton.onclick = () => { rc.toggleMySettings(); }; mySettingsCloseBtn.onclick = () => { rc.toggleMySettings(); }; tabVideoDevicesBtn.onclick = (e) => { rc.openTab(e, 'tabVideoDevices'); }; tabAudioDevicesBtn.onclick = (e) => { rc.openTab(e, 'tabAudioDevices'); }; tabRecordingBtn.onclick = (e) => { rc.openTab(e, 'tabRecording'); }; tabRoomBtn.onclick = (e) => { rc.openTab(e, 'tabRoom'); }; tabVideoShareBtn.onclick = (e) => { rc.openTab(e, 'tabVideoShare'); }; tabAspectBtn.onclick = (e) => { rc.openTab(e, 'tabAspect'); }; tabModeratorBtn.onclick = (e) => { rc.openTab(e, 'tabModerator'); }; tabProfileBtn.onclick = (e) => { rc.openTab(e, 'tabProfile'); }; tabStylingBtn.onclick = (e) => { rc.openTab(e, 'tabStyling'); }; tabLanguagesBtn.onclick = (e) => { rc.openTab(e, 'tabLanguages'); }; refreshVideoDevices.onclick = async () => { await refreshMyVideoDevices(); userLog('info', 'Refreshed video devices', 'top-end'); }; refreshAudioDevices.onclick = async () => { await refreshMyAudioDevices(); userLog('info', 'Refreshed audio devices', 'top-end'); }; applyAudioOptionsButton.onclick = () => { rc.closeThenProduce(RoomClient.mediaType.audio, microphoneSelect.value); }; speakerTestBtn.onclick = () => { sound('ring', true); }; roomId.onclick = () => { DetectRTC.isMobileDevice ? shareRoom(true) : copyRoomURL(); }; roomSendEmail.onclick = () => { shareRoomByEmail(); }; chatButton.onclick = () => { rc.toggleChat(); if (DetectRTC.isMobileDevice) { rc.toggleShowParticipants(); } }; 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(); }; chatHideParticipantsList.onclick = (e) => { rc.toggleShowParticipants(); }; chatShowParticipantsList.onclick = (e) => { rc.toggleShowParticipants(); }; chatShareRoomBtn.onclick = (e) => { shareRoom(true); }; chatGhostButton.onclick = (e) => { rc.chatToggleBg(); }; chatCleanButton.onclick = () => { rc.chatClean(); }; chatSaveButton.onclick = () => { rc.chatSave(); }; chatCloseButton.onclick = () => { rc.toggleChat(); }; chatTogglePin.onclick = () => { rc.toggleChatPin(); }; chatMaxButton.onclick = () => { rc.chatMaximize(); }; chatMinButton.onclick = () => { rc.chatMinimize(); }; chatCleanTextButton.onclick = () => { rc.cleanMessage(); }; chatPasteButton.onclick = () => { rc.pasteMessage(); }; chatSendButton.onclick = () => { rc.sendMessage(); }; chatEmojiButton.onclick = () => { rc.toggleChatEmoji(); }; chatMarkdownButton.onclick = () => { isChatMarkdownOn = !isChatMarkdownOn; setColor(chatMarkdownButton, isChatMarkdownOn ? 'lime' : 'white'); }; chatSpeechStartButton.onclick = () => { startSpeech(); }; chatSpeechStopButton.onclick = () => { stopSpeech(); }; transcriptionSpeechStart.onclick = () => { transcription.start(); }; transcriptionSpeechStop.onclick = () => { transcription.stop(); }; fullScreenButton.onclick = () => { rc.toggleFullScreen(); }; recordingImage.onclick = () => { isRecording ? stopRecButton.click() : startRecButton.click(); }; startRecButton.onclick = () => { if (participantsCount == 1 && !rc.peer_info.peer_audio) { return userLog('warning', 'ðī Recording requires your audio to be enabled', 'top-end', 6000); } rc.startRecording(); }; stopRecButton.onclick = () => { rc.stopRecording(); }; pauseRecButton.onclick = () => { rc.pauseRecording(); }; resumeRecButton.onclick = () => { rc.resumeRecording(); }; swapCameraButton.onclick = () => { if (isHideMeActive) rc.handleHideMe(); rc.closeThenProduce(RoomClient.mediaType.video, null, true); }; raiseHandButton.onclick = () => { rc.updatePeerInfo(peer_name, socket.id, 'hand', true); }; lowerHandButton.onclick = () => { rc.updatePeerInfo(peer_name, socket.id, 'hand', false); }; startAudioButton.onclick = async () => { const moderator = rc.getModerator(); if (moderator.audio_cant_unmute) { return userLog('warning', 'The moderator does not allow you to unmute', 'top-end', 6000); } if (isPushToTalkActive) return; setAudioButtonsDisabled(true); if (!isEnumerateAudioDevices) await initEnumerateAudioDevices(); rc.produce(RoomClient.mediaType.audio, microphoneSelect.value); rc.updatePeerInfo(peer_name, socket.id, 'audio', true); // rc.resumeProducer(RoomClient.mediaType.audio); }; stopAudioButton.onclick = () => { if (isPushToTalkActive) return; setAudioButtonsDisabled(true); rc.closeProducer(RoomClient.mediaType.audio); rc.updatePeerInfo(peer_name, socket.id, 'audio', false); // rc.pauseProducer(RoomClient.mediaType.audio); }; startVideoButton.onclick = async () => { const moderator = rc.getModerator(); if (moderator.video_cant_unhide) { return userLog('warning', 'The moderator does not allow you to unhide', 'top-end', 6000); } setVideoButtonsDisabled(true); if (!isEnumerateVideoDevices) await initEnumerateVideoDevices(); rc.produce(RoomClient.mediaType.video, videoSelect.value); // rc.resumeProducer(RoomClient.mediaType.video); }; stopVideoButton.onclick = () => { setVideoButtonsDisabled(true); rc.closeProducer(RoomClient.mediaType.video); // rc.pauseProducer(RoomClient.mediaType.video); }; startScreenButton.onclick = () => { const moderator = rc.getModerator(); if (moderator.screen_cant_share) { return userLog('warning', 'The moderator does not allow you to share the screen', 'top-end', 6000); } rc.produce(RoomClient.mediaType.screen); }; stopScreenButton.onclick = () => { rc.closeProducer(RoomClient.mediaType.screen); }; fileShareButton.onclick = () => { rc.selectFileToShare(socket.id, true); }; videoShareButton.onclick = () => { rc.shareVideo('all'); }; videoCloseBtn.onclick = () => { rc.closeVideo(true); }; sendAbortBtn.onclick = () => { rc.abortFileTransfer(); }; receiveHideBtn.onclick = () => { rc.hideFileTransfer(); }; whiteboardButton.onclick = () => { toggleWhiteboard(); }; whiteboardPencilBtn.onclick = () => { whiteboardIsDrawingMode(true); }; whiteboardObjectBtn.onclick = () => { whiteboardIsDrawingMode(false); }; whiteboardUndoBtn.onclick = () => { whiteboardAction(getWhiteboardAction('undo')); }; whiteboardRedoBtn.onclick = () => { whiteboardAction(getWhiteboardAction('redo')); }; whiteboardSaveBtn.onclick = () => { wbCanvasSaveImg(); }; whiteboardImgFileBtn.onclick = () => { whiteboardAddObj('imgFile'); }; whiteboardPdfFileBtn.onclick = () => { whiteboardAddObj('pdfFile'); }; whiteboardImgUrlBtn.onclick = () => { whiteboardAddObj('imgUrl'); }; whiteboardTextBtn.onclick = () => { whiteboardAddObj('text'); }; whiteboardLineBtn.onclick = () => { whiteboardAddObj('line'); }; whiteboardRectBtn.onclick = () => { whiteboardAddObj('rect'); }; whiteboardTriangleBtn.onclick = () => { whiteboardAddObj('triangle'); }; whiteboardCircleBtn.onclick = () => { whiteboardAddObj('circle'); }; whiteboardEraserBtn.onclick = () => { whiteboardIsEraser(true); }; whiteboardCleanBtn.onclick = () => { confirmClearBoard(); }; whiteboardLockButton.onchange = () => { wbIsLock = !wbIsLock; whiteboardAction(getWhiteboardAction(wbIsLock ? 'lock' : 'unlock')); }; whiteboardCloseBtn.onclick = () => { whiteboardAction(getWhiteboardAction('close')); }; participantsSaveBtn.onclick = () => { saveRoomPeers(); }; participantsUnreadMessagesBtn.onclick = () => { rc.toggleUnreadMsg(); }; participantsRaiseHandBtn.onclick = () => { rc.toggleRaiseHands(); }; searchParticipantsFromList.onkeyup = () => { rc.searchPeer(); }; lockRoomButton.onclick = () => { rc.roomAction('lock'); }; unlockRoomButton.onclick = () => { rc.roomAction('unlock'); }; aboutButton.onclick = () => { showAbout(); }; // restartICE.onclick = async () => { // await rc.restartIce(); // }; } // #################################################### // HANDLE INIT USER // #################################################### function setButtonsInit() { if (!DetectRTC.isMobileDevice) { setTippy('initAudioButton', 'Toggle the audio', 'top'); setTippy('initVideoButton', 'Toggle the video', 'top'); setTippy('initAudioVideoButton', 'Toggle the audio & video', 'top'); setTippy('initStartScreenButton', 'Toggle screen sharing', 'top'); setTippy('initStopScreenButton', 'Toggle screen sharing', 'top'); } if (!isAudioAllowed) hide(initAudioButton); if (!isVideoAllowed) hide(initVideoButton); if (!isAudioAllowed || !isVideoAllowed) hide(initAudioVideoButton); if ((!isAudioAllowed && !isVideoAllowed) || DetectRTC.isMobileDevice) hide(initVideoAudioRefreshButton); isAudioVideoAllowed = isAudioAllowed && isVideoAllowed; } function handleSelectsInit() { // devices init options initVideoSelect.onchange = async () => { await changeCamera(initVideoSelect.value); videoSelect.selectedIndex = initVideoSelect.selectedIndex; refreshLsDevices(); }; initMicrophoneSelect.onchange = () => { microphoneSelect.selectedIndex = initMicrophoneSelect.selectedIndex; refreshLsDevices(); }; initSpeakerSelect.onchange = () => { speakerSelect.selectedIndex = initSpeakerSelect.selectedIndex; refreshLsDevices(); }; } async function setSelectsInit() { if (localStorageDevices) { console.log('04.0 ----> Get Local Storage Devices before', localStorageDevices); // const initMicrophoneExist = selectOptionByValueExist(initMicrophoneSelect, localStorageDevices.audio.select); const initSpeakerExist = selectOptionByValueExist(initSpeakerSelect, localStorageDevices.speaker.select); const initVideoExist = selectOptionByValueExist(initVideoSelect, localStorageDevices.video.select); // const microphoneExist = selectOptionByValueExist(microphoneSelect, localStorageDevices.audio.select); const speakerExist = selectOptionByValueExist(speakerSelect, localStorageDevices.speaker.select); const videoExist = selectOptionByValueExist(videoSelect, localStorageDevices.video.select); console.log('Check for audio changes', { previous: localStorageDevices.audio.select, current: microphoneSelect.value, }); if (!initMicrophoneExist || !microphoneExist) { console.log('04.1 ----> Audio devices seems changed, use default index 0'); initMicrophoneSelect.selectedIndex = 0; microphoneSelect.selectedIndex = 0; refreshLsDevices(); } console.log('Check for speaker changes', { previous: localStorageDevices.speaker.select, current: speakerSelect.value, }); if (!initSpeakerExist || !speakerExist) { console.log('04.2 ----> Speaker devices seems changed, use default index 0'); initSpeakerSelect.selectedIndex = 0; speakerSelect.selectedIndex = 0; refreshLsDevices(); } console.log('Check for video changes', { previous: localStorageDevices.video.select, current: videoSelect.value, }); if (!initVideoExist || !videoExist) { console.log('04.3 ----> Video devices seems changed, use default index 0'); initVideoSelect.selectedIndex = 0; videoSelect.selectedIndex = 0; refreshLsDevices(); } // console.log('04.4 ----> Get Local Storage Devices after', lS.getLocalStorageDevices()); } if (initVideoSelect.value) await changeCamera(initVideoSelect.value); } function selectOptionByValueExist(selectElement, value) { let foundValue = false; for (let i = 0; i < selectElement.options.length; i++) { if (selectElement.options[i].value === value) { selectElement.selectedIndex = i; foundValue = true; break; } } return foundValue; } function refreshLsDevices() { lS.setLocalStorageDevices(lS.MEDIA_TYPE.video, videoSelect.selectedIndex, videoSelect.value); lS.setLocalStorageDevices(lS.MEDIA_TYPE.audio, microphoneSelect.selectedIndex, microphoneSelect.value); lS.setLocalStorageDevices(lS.MEDIA_TYPE.speaker, speakerSelect.selectedIndex, speakerSelect.value); } async function changeCamera(deviceId) { if (initStream) { await stopTracks(initStream); elemDisplay('initVideo', true); initVideoContainerShow(); if (!initVideo.classList.contains('mirror')) { initVideo.classList.toggle('mirror'); } } const videoConstraints = { audio: false, video: { width: { ideal: 1280 }, height: { ideal: 720 }, deviceId: deviceId, aspectRatio: 1.777, }, }; await navigator.mediaDevices .getUserMedia(videoConstraints) .then((camStream) => { initVideo.className = 'mirror'; initVideo.srcObject = camStream; initStream = camStream; console.log( '04.5 ----> Success attached init cam video stream', initStream.getVideoTracks()[0].getSettings(), ); checkInitConfig(); handleCameraMirror(initVideo); }) .catch((error) => { console.error('[Error] changeCamera', error); handleMediaError('video/audio', error); }); } // #################################################### // HANDLE MEDIA ERROR // #################################################### function handleMediaError(mediaType, err) { sound('alert'); let errMessage = err; switch (err.name) { case 'NotFoundError': case 'DevicesNotFoundError': errMessage = 'Required track is missing'; break; case 'NotReadableError': case 'TrackStartError': errMessage = 'Already in use'; break; case 'OverconstrainedError': case 'ConstraintNotSatisfiedError': errMessage = 'Constraints cannot be satisfied by available devices'; break; case 'NotAllowedError': case 'PermissionDeniedError': errMessage = 'Permission denied in browser'; break; case 'TypeError': errMessage = 'Empty constraints object'; break; default: break; } const $html = `Error message:
${errMessage}