/* ██████ ██  ██ ███████ ███  ██ ████████  ██      ██  ██ ██      ████  ██    ██     ██  ██  ██ █████  ██ ██  ██  ██  ██  ██  ██ ██     ██  ██ ██  ██  ██████ ███████ ██ ███████ ██   ████  ██  */ /** * MiroTalk P2P - Client component * * @link GitHub: https://github.com/miroslavpejic85/mirotalk * @link Official Live demo: https://p2p.mirotalk.com * @license For open source use: AGPLv3 * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @version 1.5.05 * */ 'use strict'; // https://www.w3schools.com/js/js_strict.asp // Signaling server URL const signalingServer = getSignalingServer(); // This room const myRoomId = getId('myRoomId'); const roomId = getRoomId(); const myRoomUrl = window.location.origin + '/join/' + roomId; // share room url // Images const images = { caption: '../images/caption.png', chatgpt: '../images/chatgpt.png', confirmation: '../images/image-placeholder.png', share: '../images/share.png', locked: '../images/locked.png', videoOff: '../images/cam-off.png', audioOff: '../images/audio-off.png', audioGif: '../images/audio.gif', delete: '../images/delete.png', message: '../images/message.png', leave: '../images/leave-room.png', vaShare: '../images/va-share.png', about: '../images/mirotalk-logo.gif', feedback: '../images/feedback.png', forbidden: '../images/forbidden.png', avatar: '../images/mirotalk-logo.png', recording: '../images/recording.png', poster: '../images/loader.gif', }; // nice free icon: https://www.iconfinder.com const className = { user: 'fas fa-user', clock: 'fas fa-clock', hideMeOn: 'fas fa-user-slash', hideMeOff: 'fas fa-user', audioOn: 'fas fa-microphone', audioOff: 'fas fa-microphone-slash', videoOn: 'fas fa-video', videoOff: 'fas fa-video-slash', screenOn: 'fas fa-desktop', screenOff: 'fas fa-stop-circle', handPulsate: 'fas fa-hand-paper pulsate', privacy: 'far fa-circle', snapShot: 'fas fa-camera-retro', pinUnpin: 'fas fa-map-pin', mirror: 'fas fa-arrow-right-arrow-left', zoomIn: 'fas fa-magnifying-glass-plus', zoomOut: 'fas fa-magnifying-glass-minus', fullScreen: 'fas fa-expand', fsOn: 'fas fa-compress-alt', fsOff: 'fas fa-expand-alt', msgPrivate: 'fas fa-paper-plane', shareFile: 'fas fa-upload', shareVideoAudio: 'fab fa-youtube', kickOut: 'fas fa-sign-out-alt', chatOn: 'fas fa-comment', chatOff: 'fas fa-comment-slash', ghost: 'fas fa-ghost', undo: 'fas fa-undo', captionOn: 'fas fa-closed-captioning', trash: 'fas fa-trash', copy: 'fas fa-copy', speech: 'fas fa-volume-high', heart: 'fas fa-heart', pip: 'fas fa-images', hideAll: 'fas fa-eye', up: 'fas fa-chevron-up', down: 'fas fa-chevron-down', }; // https://fontawesome.com/search?o=r&m=free const icons = { lock: '', unlock: '', pitchBar: '', sounds: '', share: '', user: '', fileSend: '', fileReceive: '', codecs: '', theme: '', }; // Whiteboard and fileSharing const fileSharingInput = '*'; // allow all file extensions const Base64Prefix = 'data:application/pdf;base64,'; const wbPdfInput = 'application/pdf'; const wbImageInput = 'image/*'; const wbWidth = 1280; const wbHeight = 768; // Peer infos const extraInfo = getId('extraInfo'); const isWebRTCSupported = checkWebRTCSupported(); const userAgent = navigator.userAgent; const parser = new UAParser(userAgent); const parserResult = parser.getResult(); const deviceType = parserResult.device.type || 'desktop'; const isMobileDevice = deviceType === 'mobile'; const isTabletDevice = deviceType === 'tablet'; const isIPadDevice = parserResult.device.model?.toLowerCase() === 'ipad'; const isDesktopDevice = deviceType === 'desktop'; const osName = parserResult.os.name; const osVersion = parserResult.os.version; const browserName = parserResult.browser.name; const browserVersion = parserResult.browser.version; const isFirefox = browserName.toLowerCase().includes('firefox'); const peerInfo = getPeerInfo(); const thisInfo = getInfo(); // Local Storage class const lS = new LocalStorage(); const localStorageSettings = lS.getObjectLocalStorage('P2P_SETTINGS'); const lsSettings = localStorageSettings ? localStorageSettings : lS.P2P_SETTINGS; console.log('LOCAL_STORAGE_SETTINGS', lsSettings); // Check if embedded inside an iFrame const isEmbedded = window.self !== window.top; // Check if PIP is supported by this browser const showVideoPipBtn = document.pictureInPictureEnabled; // Check if Document PIP is supported by this browser const showDocumentPipBtn = !isEmbedded && 'documentPictureInPicture' in window; // Loading div const loadingDiv = getId('loadingDiv'); // Video/Audio media container const videoMediaContainer = getId('videoMediaContainer'); const videoPinMediaContainer = getId('videoPinMediaContainer'); const audioMediaContainer = getId('audioMediaContainer'); // Share Room QR popup const qrRoomPopupContainer = getId('qrRoomPopupContainer'); // Init audio-video const initUser = getId('initUser'); const initVideoContainer = getQs('.init-video-container'); const initVideo = getId('initVideo'); const initVideoBtn = getId('initVideoBtn'); const initAudioBtn = getId('initAudioBtn'); const initScreenShareBtn = getId('initScreenShareBtn'); const initVideoMirrorBtn = getId('initVideoMirrorBtn'); const initUsernameEmojiButton = getId('initUsernameEmojiButton'); const initVideoSelect = getId('initVideoSelect'); const initMicrophoneSelect = getId('initMicrophoneSelect'); const initSpeakerSelect = getId('initSpeakerSelect'); const usernameEmoji = getId('usernameEmoji'); // Buttons bar const buttonsBar = getId('buttonsBar'); const shareRoomBtn = getId('shareRoomBtn'); const recordStreamBtn = getId('recordStreamBtn'); const fullScreenBtn = getId('fullScreenBtn'); const chatRoomBtn = getId('chatRoomBtn'); const captionBtn = getId('captionBtn'); const roomEmojiPickerBtn = getId('roomEmojiPickerBtn'); const whiteboardBtn = getId('whiteboardBtn'); const snapshotRoomBtn = getId('snapshotRoomBtn'); const fileShareBtn = getId('fileShareBtn'); const documentPiPBtn = getId('documentPiPBtn'); const mySettingsBtn = getId('mySettingsBtn'); const aboutBtn = getId('aboutBtn'); // Buttons bottom const bottomButtons = getId('bottomButtons'); const toggleExtraBtn = getId('toggleExtraBtn'); const audioBtn = getId('audioBtn'); const videoBtn = getId('videoBtn'); const swapCameraBtn = getId('swapCameraBtn'); const hideMeBtn = getId('hideMeBtn'); const screenShareBtn = getId('screenShareBtn'); const myHandBtn = getId('myHandBtn'); const leaveRoomBtn = getId('leaveRoomBtn'); // Room Emoji Picker const closeEmojiPickerContainer = getId('closeEmojiPickerContainer'); const emojiPickerContainer = getId('emojiPickerContainer'); const emojiPickerHeader = getId('emojiPickerHeader'); const userEmoji = getId(`userEmoji`); // Chat room const msgerDraggable = getId('msgerDraggable'); const msgerHeader = getId('msgerHeader'); const msgerTogglePin = getId('msgerTogglePin'); const msgerTheme = getId('msgerTheme'); const msgerCPBtn = getId('msgerCPBtn'); const msgerDropDownMenuBtn = getId('msgerDropDownMenuBtn'); const msgerDropDownContent = getId('msgerDropDownContent'); const msgerClean = getId('msgerClean'); const msgerSaveBtn = getId('msgerSaveBtn'); const msgerClose = getId('msgerClose'); const msgerMaxBtn = getId('msgerMaxBtn'); const msgerMinBtn = getId('msgerMinBtn'); const msgerChat = getId('msgerChat'); const msgerEmojiBtn = getId('msgerEmojiBtn'); const msgerMarkdownBtn = getId('msgerMarkdownBtn'); const msgerGPTBtn = getId('msgerGPTBtn'); const msgerShareFileBtn = getId('msgerShareFileBtn'); const msgerVideoUrlBtn = getId('msgerVideoUrlBtn'); const msgerInput = getId('msgerInput'); const msgerCleanTextBtn = getId('msgerCleanTextBtn'); const msgerPasteBtn = getId('msgerPasteBtn'); const msgerShowChatOnMsgDiv = getId('msgerShowChatOnMsgDiv'); const msgerShowChatOnMsg = getId('msgerShowChatOnMsg'); const msgerSpeechMsgDiv = getId('msgerSpeechMsgDiv'); const msgerSpeechMsg = getId('msgerSpeechMsg'); const msgerSendBtn = getId('msgerSendBtn'); const chatInputEmoji = { '<3': '❤️', ':(': '😡', ':S': '😟', ':X': '🤐', ';(': '😥', ':T': '😖', ':@': '😠', ':$': '🤑', ':&': '🤗', ':#': '🤔', ':!': '😵', ':W': '😷', ':%': '🤒', ':*!': '🤩', ':G': '😬', ':R': '😋', ':M': '🤮', ':L': '🥴', ':C': '🥺', ':F': '🥳', ':Z': '🤢', ':^': '🤓', ':K': '🤫', ':D!': '🤯', ':H': '🧐', ':U': '🤥', ':V': '🤪', ':N': '🥶', ':J': '🥴', }; // https://github.com/wooorm/gemoji/blob/main/support.md // Chat room emoji picker const msgerEmojiPicker = getId('msgerEmojiPicker'); // Chat room connected peers const msgerCP = getId('msgerCP'); const msgerCPHeader = getId('msgerCPHeader'); const msgerCPCloseBtn = getId('msgerCPCloseBtn'); const msgerCPList = getId('msgerCPList'); const searchPeerBarName = getId('searchPeerBarName'); // Caption section const captionDraggable = getId('captionDraggable'); const captionHeader = getId('captionHeader'); const captionTogglePin = getId('captionTogglePin'); const captionTheme = getId('captionTheme'); const captionMaxBtn = getId('captionMaxBtn'); const captionMinBtn = getId('captionMinBtn'); const captionClean = getId('captionClean'); const captionSaveBtn = getId('captionSaveBtn'); const captionClose = getId('captionClose'); const captionChat = getId('captionChat'); const captionFooter = getId('captionFooter'); // My settings const mySettings = getId('mySettings'); const mySettingsHeader = getId('mySettingsHeader'); const tabVideoBtn = getId('tabVideoBtn'); const tabAudioBtn = getId('tabAudioBtn'); const tabVideoShareBtn = getId('tabVideoShareBtn'); const tabRecordingBtn = getId('tabRecordingBtn'); const tabParticipantsBtn = getId('tabParticipantsBtn'); const tabProfileBtn = getId('tabProfileBtn'); const tabShortcutsBtn = getId('tabShortcutsBtn'); const tabNetworkBtn = getId('tabNetworkBtn'); const networkIP = getId('networkIP'); const networkHost = getId('networkHost'); const networkStun = getId('networkStun'); const networkTurn = getId('networkTurn'); const tabRoomBtn = getId('tabRoomBtn'); const roomSendEmailBtn = getId('roomSendEmailBtn'); const tabStylingBtn = getId('tabStylingBtn'); const tabLanguagesBtn = getId('tabLanguagesBtn'); const mySettingsCloseBtn = getId('mySettingsCloseBtn'); const myPeerNameSet = getId('myPeerNameSet'); const myPeerNameSetBtn = getId('myPeerNameSetBtn'); const switchSounds = getId('switchSounds'); const switchShare = getId('switchShare'); const switchKeepButtonsVisible = getId('switchKeepButtonsVisible'); const switchPushToTalk = getId('switchPushToTalk'); const switchAudioPitchBar = getId('switchAudioPitchBar'); const audioInputSelect = getId('audioSource'); const audioOutputSelect = getId('audioOutput'); const audioOutputDiv = getId('audioOutputDiv'); const speakerTestBtn = getId('speakerTestBtn'); const videoSelect = getId('videoSource'); const videoQualitySelect = getId('videoQuality'); const videoFpsSelect = getId('videoFps'); const videoFpsDiv = getId('videoFpsDiv'); const screenFpsSelect = getId('screenFps'); const pushToTalkDiv = getId('pushToTalkDiv'); const recImage = getId('recImage'); const switchH264Recording = getId('switchH264Recording'); const pauseRecBtn = getId('pauseRecBtn'); const resumeRecBtn = getId('resumeRecBtn'); const recordingTime = getId('recordingTime'); const lastRecordingInfo = getId('lastRecordingInfo'); const themeSelect = getId('mirotalkTheme'); const videoObjFitSelect = getId('videoObjFitSelect'); const mainButtonsBar = getQsA('#buttonsBar button'); const mainButtonsIcon = getQsA('#buttonsBar button i'); const btnsBarSelect = getId('mainButtonsBarPosition'); const pinUnpinGridDiv = getId('pinUnpinGridDiv'); const pinVideoPositionSelect = getId('pinVideoPositionSelect'); const tabRoomPeerName = getId('tabRoomPeerName'); const tabRoomParticipants = getId('tabRoomParticipants'); const tabRoomSecurity = getId('tabRoomSecurity'); const tabEmailInvitation = getId('tabEmailInvitation'); const isPeerPresenter = getId('isPeerPresenter'); const peersCount = getId('peersCount'); const screenFpsDiv = getId('screenFpsDiv'); const switchShortcuts = getId('switchShortcuts'); // Audio options const dropDownMicOptions = getId('dropDownMicOptions'); const switchAutoGainControl = getId('switchAutoGainControl'); const switchNoiseSuppression = getId('switchNoiseSuppression'); const switchEchoCancellation = getId('switchEchoCancellation'); const sampleRateSelect = getId('sampleRateSelect'); const sampleSizeSelect = getId('sampleSizeSelect'); const channelCountSelect = getId('channelCountSelect'); const micLatencyRange = getId('micLatencyRange'); const micVolumeRange = getId('micVolumeRange'); const applyAudioOptionsBtn = getId('applyAudioOptionsBtn'); const micOptionsBtn = getId('micOptionsBtn'); const micDropDownMenu = getId('micDropDownMenu'); const micLatencyValue = getId('micLatencyValue'); const micVolumeValue = getId('micVolumeValue'); // Tab Media const shareMediaAudioVideoBtn = getId('shareMediaAudioVideoBtn'); // My whiteboard const whiteboard = getId('whiteboard'); const whiteboardHeader = getId('whiteboardHeader'); const whiteboardTitle = getId('whiteboardTitle'); const whiteboardOptions = getId('whiteboardOptions'); const wbDrawingColorEl = getId('wbDrawingColorEl'); const whiteboardGhostButton = getId('whiteboardGhostButton'); const wbBackgroundColorEl = getId('wbBackgroundColorEl'); const whiteboardPencilBtn = getId('whiteboardPencilBtn'); const whiteboardObjectBtn = getId('whiteboardObjectBtn'); const whiteboardUndoBtn = getId('whiteboardUndoBtn'); const whiteboardRedoBtn = getId('whiteboardRedoBtn'); const whiteboardDropDownMenuBtn = getId('whiteboardDropDownMenuBtn'); const whiteboardDropdownMenu = getId('whiteboardDropdownMenu'); const whiteboardImgFileBtn = getId('whiteboardImgFileBtn'); const whiteboardPdfFileBtn = getId('whiteboardPdfFileBtn'); const whiteboardImgUrlBtn = getId('whiteboardImgUrlBtn'); const whiteboardTextBtn = getId('whiteboardTextBtn'); const whiteboardLineBtn = getId('whiteboardLineBtn'); const whiteboardRectBtn = getId('whiteboardRectBtn'); const whiteboardTriangleBtn = getId('whiteboardTriangleBtn'); const whiteboardCircleBtn = getId('whiteboardCircleBtn'); const whiteboardSaveBtn = getId('whiteboardSaveBtn'); const whiteboardEraserBtn = getId('whiteboardEraserBtn'); const whiteboardCleanBtn = getId('whiteboardCleanBtn'); const whiteboardLockBtn = getId('whiteboardLockBtn'); const whiteboardUnlockBtn = getId('whiteboardUnlockBtn'); const whiteboardCloseBtn = getId('whiteboardCloseBtn'); // Room actions buttons const captionEveryoneBtn = getId('captionEveryoneBtn'); const muteEveryoneBtn = getId('muteEveryoneBtn'); const hideEveryoneBtn = getId('hideEveryoneBtn'); const ejectEveryoneBtn = getId('ejectEveryoneBtn'); const lockRoomBtn = getId('lockRoomBtn'); const unlockRoomBtn = getId('unlockRoomBtn'); // File send progress const sendFileDiv = getId('sendFileDiv'); const imgShareSend = getId('imgShareSend'); const sendFilePercentage = getId('sendFilePercentage'); const sendFileInfo = getId('sendFileInfo'); const sendProgress = getId('sendProgress'); const sendAbortBtn = getId('sendAbortBtn'); // File receive progress const receiveFileDiv = getId('receiveFileDiv'); const imgShareReceive = getId('imgShareReceive'); const receiveFilePercentage = getId('receiveFilePercentage'); const receiveFileInfo = getId('receiveFileInfo'); const receiveProgress = getId('receiveProgress'); const receiveHideBtn = getId('receiveHideBtn'); const receiveAbortBtn = getId('receiveAbortBtn'); // Video/audio url player const videoUrlCont = getId('videoUrlCont'); const videoAudioUrlCont = getId('videoAudioUrlCont'); const videoUrlHeader = getId('videoUrlHeader'); const videoAudioUrlHeader = getId('videoAudioUrlHeader'); const videoUrlCloseBtn = getId('videoUrlCloseBtn'); const videoAudioCloseBtn = getId('videoAudioCloseBtn'); const videoUrlIframe = getId('videoUrlIframe'); const videoAudioUrlElement = getId('videoAudioUrlElement'); // Speech recognition const speechRecognitionIcon = getId('speechRecognitionIcon'); const speechRecognitionStart = getId('speechRecognitionStart'); const speechRecognitionStop = getId('speechRecognitionStop'); // Media const sinkId = 'sinkId' in HTMLMediaElement.prototype; //.... const userLimits = { active: false, // Limit users per room count: 2, // Limit 2 users per room if userLimits.active true }; const isRulesActive = true; // Presenter can do anything, guest is slightly moderate, if false no Rules for the room. const forceCamMaxResolutionAndFps = false; // This force the webCam to max resolution as default, up to 8k and 60fps (very high bandwidth are required) if false, you can set it from settings const useAvatarSvg = true; // if false the cam-Off avatar = images.avatar /** * Determines the video zoom mode. * If set to true, the video zooms at the center of the frame. * If set to false, the video zooms at the cursor position. */ const ZOOM_CENTER_MODE = false; const ZOOM_IN_OUT_ENABLED = true; // Video Zoom in/out default (true) // Color Picker: const themeCustom = { input: getId('themeColorPicker'), check: getId('keepCustomTheme'), color: lsSettings.theme_color ? lsSettings.theme_color : '#000000', keep: lsSettings.theme_custom ? lsSettings.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: { preview: true, opacity: true, hue: true, }, }) .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', () => { lsSettings.theme_color = themeCustom.color; lS.setSettings(lsSettings); }); // misc let swBg = 'rgba(0, 0, 0, 0.7)'; // swAlert background color let callElapsedTime; // count time let mySessionTime; // conference session time let isDocumentOnFullScreen = false; let isToggleExtraBtnClicked = false; // peer let myPeerId; // This socket.id let myPeerUUID = getUUID(); // Unique peer id let myPeerName = getPeerName(); let myPeerAvatar = getPeerAvatar(); let myToken = getPeerToken(); // peer JWT let isPresenter = false; // True Who init the room (aka first peer joined) let myHandStatus = false; let myVideoStatus = false; let myAudioStatus = false; let myVideoStatusBefore = false; let myScreenStatus = false; let isScreenEnabled = getScreenEnabled(); let notify = getNotify(); // popup room sharing on join let notifyBySound = true; // turn on - off sound notifications let isPeerReconnected = false; // media let useAudio = true; // User allow for microphone usage let useVideo = true; // User allow for camera usage let isEnumerateVideoDevices = false; let isEnumerateAudioDevices = false; // video/audio player let isVideoUrlPlayerOpen = false; let pinnedVideoPlayerId = null; // connection let signalingSocket; // socket.io connection to our webserver let needToCreateOffer = false; // after session description answer let peerConnections = {}; // keep track of our peer connections, indexed by peer_id == socket.io id let chatDataChannels = {}; // keep track of our peer chat data channels let fileDataChannels = {}; // keep track of our peer file sharing data channels let allPeers = {}; // keep track of all peers in the room, indexed by peer_id == socket.io id // stream let initStream; // initial webcam stream let localVideoMediaStream; // my webcam let localAudioMediaStream; // my microphone let peerVideoMediaElements = {}; // keep track of our peer