const WS_VIDEO_URL = $('meta[name=ws_video_url]').attr("content"); const socket = io(WS_VIDEO_URL, {rejectUnauthorized: false}); const myvideo = document.querySelector("#vd1"); myvideo.muted = true; const roomid = __roomid; let username; const chatRoom = document.querySelector('.chat-cont'); const usersRoom = document.querySelector('.user-cont'); const sendButton = document.querySelector('.chat-send'); const messageField = document.querySelector('.chat-input'); const videoContainer = document.querySelector('#vcont'); const overlayContainer = document.querySelector('#overlay') const continueButt = document.querySelector('.continue-name'); const nameField = document.querySelector('#name-field'); const passworField = document.querySelector('#password-field'); const videoButt = document.querySelector('.novideo'); const audioButt = document.querySelector('.audio'); const cutCall = document.querySelector('.cutcall'); const screenShareButt = document.querySelector('.screenshare'); const fullscreenButt = document.querySelector('.fullscreen'); const usersButt = document.querySelector('#users'); const chatsButt = document.querySelector('#chats'); const whiteboardButt = document.querySelector('.board-icon') const senders = []; //whiteboard js start const whiteboardCont = document.querySelector('.whiteboard-cont'); const canvas = document.querySelector("#whiteboard"); const ctx = canvas.getContext('2d'); let boardVisisble = false; // variaveis para gravacao da chamada let isStreamRecording = false; const notifyBySound = true; // turn on - off sound notifications let isRecScreenSream = false; let recScreenStream; // recorded screen stream let recordedBlobs; let isMobileDevice = false let localMediaStream; // my microphone / webcam let mediaRecorder; whiteboardCont.style.visibility = 'hidden'; let isDrawing = 0; let x = 0; let y = 0; let color = "black"; let drawsize = 3; let colorRemote = "black"; let drawsizeRemote = 3; function fitToContainer(canvas) { canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; } fitToContainer(canvas); //getCanvas call is under join room call socket.on('getCanvas', url => { let img = new Image(); img.onload = start; img.src = url; function start() { ctx.drawImage(img, 0, 0); } }) function setColor(newcolor) { color = newcolor; drawsize = 3; } function setEraser() { color = "white"; drawsize = 10; } //might remove this function reportWindowSize() { fitToContainer(canvas); } window.onresize = reportWindowSize; // function clearBoard() { if (window.confirm('Você tem certeza que deseja limpar o board? Operação irreversível.')) { ctx.clearRect(0, 0, canvas.width, canvas.height); socket.emit('store canvas', canvas.toDataURL()); socket.emit('clearBoard'); } else return; } socket.on('clearBoard', () => { ctx.clearRect(0, 0, canvas.width, canvas.height); }) function draw(newx, newy, oldx, oldy) { ctx.strokeStyle = color; ctx.lineWidth = drawsize; ctx.beginPath(); ctx.moveTo(oldx, oldy); ctx.lineTo(newx, newy); ctx.stroke(); ctx.closePath(); socket.emit('store canvas', canvas.toDataURL()); } function drawRemote(newx, newy, oldx, oldy) { ctx.strokeStyle = colorRemote; ctx.lineWidth = drawsizeRemote; ctx.beginPath(); ctx.moveTo(oldx, oldy); ctx.lineTo(newx, newy); ctx.stroke(); ctx.closePath(); } canvas.addEventListener('mousedown', e => { x = e.offsetX; y = e.offsetY; isDrawing = 1; }) canvas.addEventListener('mousemove', e => { if (isDrawing) { draw(e.offsetX, e.offsetY, x, y); socket.emit('draw', e.offsetX, e.offsetY, x, y, color, drawsize); x = e.offsetX; y = e.offsetY; } }) window.addEventListener('mouseup', e => { if (isDrawing) { isDrawing = 0; } }) socket.on('draw', (newX, newY, prevX, prevY, color, size) => { colorRemote = color; drawsizeRemote = size; drawRemote(newX, newY, prevX, prevY); }) //whiteboard js end let videoAllowed = 1; let audioAllowed = 1; //Objeto que contém as informações atualizadas de todos os usuário da sala (socket_id, status do mic e do video, etc) let dataModelVideo = {}; let mymuteicon = document.querySelector("#mymuteicon"); let myvideooff = document.querySelector("#myvideooff"); myvideooff.style.visibility = 'hidden'; // const configuration = { iceServers: [{ urls: "stun:stun.stunprotocol.org" }] } const configuration = { iceServers: [{ urls: "stun:stun1.l.google.com:19302" }], sdpSemantics: 'unified-plan' } let mediaConstraints = { video: true, audio: {'echoCancellation': true} }; let connections = {}; let mystreamvideo, myscreenshare; document.querySelector('.roomcode').innerHTML = `${roomid}` function CopyClassText() { var textToCopy = document.querySelector('.roomcode'); var currentRange; if (document.getSelection().rangeCount > 0) { currentRange = document.getSelection().getRangeAt(0); window.getSelection().removeRange(currentRange); } else { currentRange = false; } var CopyRange = document.createRange(); CopyRange.selectNode(textToCopy); window.getSelection().addRange(CopyRange); document.execCommand("copy"); window.getSelection().removeRange(CopyRange); if (currentRange) { window.getSelection().addRange(currentRange); } document.querySelector(".copycode-button").textContent = "Copiado!" setTimeout(()=>{ document.querySelector(".copycode-button").textContent = "Copiar"; }, 5000); } continueButt.addEventListener('click', () => { if (nameField.value == '') return; username = nameField.value; // passwd = passworField.value; overlayContainer.style.visibility = 'hidden'; document.querySelector("#myname").innerHTML = `${username} (Você)`; socket.emit("join room", roomid, username, __room_id, __user_id, __user_photo); }) nameField.addEventListener("keyup", function (event) { if (event.keyCode === 13) { event.preventDefault(); continueButt.click(); } }); function updateUserCount(count) { let _count = count - 0 if (_count == 0) { $('#countUsers').css('display', 'none') } else { $('#countUsers').css('display', 'block') } $('#countUsers').html(`${_count}`) if (count > 1) { videoContainer.className = 'video-cont'; } else { videoContainer.className = 'video-cont-single'; } } let peerConnection; function handleGetUserMediaError(e) { switch (e.name) { case "NotFoundError": console.log("Não é possível abrir sua chamada porque não há câmera e/ou microfone não foi encontrado."); break; case "SecurityError": case "PermissionDeniedError": break; default: console.log("Erro ao abrir sua camêra e/ou microfone: " + e.message); break; } } function reportError(e) { console.log(e); return; } audio_track_connections = [] /* types - init -> envia as tracks para todas as conexões -> utilizado quando eu entro na sala - -> envia as tracks para uma conexão específica (utilizado quando outro usuário entra na sala, envia as tracks para mim e eu preciso responder com as minhas) - screen_on -> liga a captura de tela e envia a track para todos as conexões (se tiver webcam, só da replace no sender) - screen_off -> desliga a captura de tela e remove a track de todas as conexões (se tiver webcam, só da replace no sender pra track da webcam) */ async function startStream(type = 'init') { if(!mystreamvideo) { console.log('startStrem: mediaConstraints', mediaConstraints); try { mystreamvideo = await navigator.mediaDevices.getUserMedia(mediaConstraints); myvideo.srcObject = mystreamvideo; } catch (e) { handleGetUserMediaError(e); if(mediaConstraints.video) { mediaConstraints.video = false; startStream(type); } return false; } } if(type == 'screen_on') { try { myscreenshare = await navigator.mediaDevices.getDisplayMedia({cursor: true}); myscreenshare.getVideoTracks()[0].onended = () => { startStream('screen_off'); }; } catch (e) { handleGetUserMediaError(e); return false; } myvideo.srcObject = myscreenshare; } if(type == 'screen_off') { myvideo.srcObject = mystreamvideo; myscreenshare = null; } let is_sid = connections[type] ? true : false; let audio_track = mystreamvideo.getAudioTracks(); audio_track = (audio_track && audio_track.length) ? audio_track[0] : null let video_track = myscreenshare ? myscreenshare.getVideoTracks() : mystreamvideo.getVideoTracks(); video_track = (video_track && video_track.length) ? video_track[0] : null; console.log(`startStrem: type=${type}, is_sid=${is_sid}`, audio_track, video_track); //Se estiver iniciando o compartilhamento de tela (screen_on) e o video estiver bloqueado, desbloqueia; //Se estiver iniciando as conexões (init) ou finalizando um compartilhamento de vídeo, e não tiver webcam, bloqueia o vídeo; if((['screen_off', 'init'].includes(type) && (!video_track && videoAllowed)) || ((type == 'screen_on') && (video_track && !videoAllowed))) { $(videoButt).click(); } for(key in connections) { let sid = is_sid ? type : key; let senders = connections[sid].getSenders(); let audio_sender = (senders && senders.length) ? senders.filter(sender => {return sender && sender.track && sender.track.kind == 'audio'}) : null; console.log('audio sender', audio_sender); if((!audio_sender || !audio_sender.length) && audio_track) { audio_track_stream = await connections[sid].addTrack(audio_track, mystreamvideo); audio_track_connections.push({ audio_track: audio_track_stream }) console.log('startStream: add audio'); senders = connections[sid].getSenders(); } // let video_sender = (senders && senders.length) ? senders.filter(sender => {return !sender.track || sender.track.kind == 'video'}) : null; // console.log(video_sender); // if(video_sender && video_sender.length) { // await video_sender[0].replaceTrack(video_track); // console.log('startStream: replace video'); // } else if(video_track) { // await connections[sid].addTrack(video_track, myscreenshare ? myscreenshare : mystreamvideo); // console.log('startStream: add video'); // } let video_sender = (senders && senders.length) ? senders.filter(sender => {return sender && sender.track && sender.track.kind == 'video'}) : null; if(video_sender && video_sender.length) { if(!video_track) { await connections[sid].removeTrack(video_sender[0]); console.log('startStream: remove video'); } else if(video_track.id != video_sender[0].track.id) { await video_sender[0].replaceTrack(video_track); console.log('startStream: switch video'); } } else if(video_track) { // await connections[sid].addTrack(video_track, myscreenshare ? myscreenshare : mystreamvideo); if(myscreenshare) { await connections[sid].addTrack(video_track); //streamless addtrack -> adiciona a track na stream do peer remoto manualmente, para adicionar junto com a do audio console.log('startStream: add screen video'); } else { await connections[sid].addTrack(video_track, mystreamvideo); console.log('startStream: add webcam video'); } } if(is_sid) break; } return true; } function detectWebcam(callback) { let md = navigator.mediaDevices; if (!md || !md.enumerateDevices) return callback(false); md.enumerateDevices().then(devices => { callback(devices.some(device => 'videoinput' === device.kind)); }) } async function handleVideoOffer(offer, sid, data) { console.log(`[SDP] offer received from ${sid}:`, offer); const no_p2p_conn = !connections[sid]; if(no_p2p_conn) { dataModelVideo[sid] = data; addRTC(sid); startStream(sid); } let desc = new RTCSessionDescription(offer); await connections[sid].setRemoteDescription(desc); const ans = await connections[sid].createAnswer(); await connections[sid].setLocalDescription(ans); socket.emit('video-answer', connections[sid].localDescription, sid); console.log(`[SDP] answer sent to ${sid}:`, connections[sid].localDescription); } function handleNewIceCandidate(candidate, sid) { // console.log(`[ICE] candidate recieved from ${sid}`, candidate); let newcandidate = new RTCIceCandidate(candidate); connections[sid].addIceCandidate(newcandidate).catch(reportError); } function handleVideoAnswer(answer, sid) { console.log(`[SDP] answer received from ${sid}:`, answer); const ans = new RTCSessionDescription(answer); connections[sid].setRemoteDescription(ans); } socket.on('video-offer', handleVideoOffer); socket.on('new icecandidate', handleNewIceCandidate); socket.on('video-answer', handleVideoAnswer); function createVideoBox(sid) { let vidCont = document.createElement('div'); let newvideo = document.createElement('video'); let vidFooter = document.createElement('div'); let name = document.createElement('div'); let muteIcon = document.createElement('i'); let fullScreenBt = document.createElement('div'); let videoOff = document.createElement('div'); let videoOffImg = document.createElement('img'); videoOff.classList.add('video-off'); vidFooter.classList.add('video-footer'); muteIcon.classList.add('mute-icon'); name.classList.add('nametag'); fullScreenBt.classList.add('video-bt-fullscreen'); name.innerHTML = `${dataModelVideo[sid].user_name}`; vidCont.id = sid; muteIcon.id = `mute${sid}`; videoOff.id = `vidoff${sid}`; muteIcon.setAttribute('data-feather', 'mic-off'); fullScreenBt.innerHTML = '' videoOffImg.src = dataModelVideo[sid].photo; videoOff.appendChild(videoOffImg); vidCont.classList.add('video-box'); newvideo.classList.add('video-frame'); newvideo.autoplay = true; newvideo.playsinline = true; newvideo.id = `video${sid}`; if (dataModelVideo[sid].mic == 'on') muteIcon.style.display = 'none'; else muteIcon.style.display = 'block'; if (dataModelVideo[sid].vid == 'on') videoOff.style.visibility = 'hidden'; else videoOff.style.visibility = 'visible'; vidFooter.appendChild(muteIcon); vidFooter.appendChild(name); vidCont.appendChild(newvideo); vidCont.appendChild(vidFooter); vidCont.appendChild(videoOff); vidCont.appendChild(fullScreenBt); videoContainer.appendChild(vidCont); fullScreenBt.addEventListener('click', () => { toogleVideoFullscreen(sid); }); vidCont.addEventListener('fullscreenchange', () => { videoFullscreenChange(sid); }); videoSetArea(); } tracks_audio = [] tracks_audio_aux_id = [] function addRTC(sid) { console.log('RTCPeerConnection', sid) connections[sid] = new RTCPeerConnection(configuration); if (!document.getElementById(sid)) { createVideoBox(sid); feather.replace(); } connections[sid].onicecandidate = function (event) { if (event.candidate) { // console.log(`[ICE] canditate sent to ${sid}:`, event.candidate); socket.emit('new icecandidate', event.candidate, sid); } }; connections[sid].ontrack = function (event) { // console.log("recebendo tracks", event.track, event.streams); console.log("recebendo tracks do " + sid, event.track, event.streams); if (tracks_audio_aux_id.indexOf(sid) == -1 && event.track.kind == 'audio') { tracks_audio_aux_id.push(sid) tracks_audio.push({ id: sid, track: event.track, stream: event.streams }) } let video_element = document.getElementById(`video${sid}`); if(event.streams && event.streams[0]) { video_element.srcObject = event.streams[0]; } else { event.track.onmute = () => { console.log(`${sid} stopped screen sharing and has no webcam`); video_element.srcObject.removeTrack(event.track); }; video_element.srcObject.addTrack(event.track); } }; // connections[sid].addTransceiver("video"); connections[sid].onremovetrack = function (event) { console.log('track removed', sid, event); if (document.getElementById(sid)) { document.getElementById(sid).remove(); } } connections[sid].onnegotiationneeded = async function () { try { const offer = await connections[sid].createOffer() await connections[sid].setLocalDescription(offer); socket.emit('video-offer', connections[sid].localDescription, sid); console.log(`[SDP] offer sent to ${sid}:`, connections[sid].localDescription); } catch (e) { reportError(e); } }; } whoami = [] socket.on('join room', async (data) => { console.log('join room', data) socket.emit('getCanvas'); let conc = []; data.forEach((user_data) => { dataModelVideo[user_data.socket_id] = user_data; conc.push(user_data.socket_id); }) // dataModelVideo = data; // let conc = Object.keys(dataModelVideo); if (conc) { conc.forEach(sid => { if(sid != socket.id) addRTC(sid); else whoami.push(sid) }); } await startStream(); feather.replace(); }) socket.on('remove peer', sid => { if (document.getElementById(sid)) { document.getElementById(sid).remove(); videoSetArea(); } console.log("o peer aqui ta vazando ") console.log(sid) console.log(connections[sid]) users_socketid.splice(users_socketid.indexOf(sid), 1); delete dataModelVideo[sid]; delete connections[sid]; }) sendButton.addEventListener('click', () => { const msg = messageField.value; if ((msg.length > 0) && (msg.trim() != "")) { messageField.value = ''; let sendmessage = { msg: msg, username: username, roomid: roomid, room_id: __room_id, user_id: __user_id } chatTextAreaResize(messageField, true); socket.emit('message', sendmessage); } else { alert("Digite sua mensagem."); } }) messageField.addEventListener("keydown", function (event) { if (event.keyCode === 13 && !event.shiftKey) { event.preventDefault(); sendButton.click(); } }); messageField.addEventListener("input", function(event) { chatTextAreaResize(messageField); }); // recept message and put in div socket.on('message', (msg, sendername, time) => { chatRoom.innerHTML += `
${sendername}
${time}
${msg}
`; setTimeout(function () { chatRoom.scrollTop = chatRoom.scrollHeight; }, 50); }); fullscreenButt.addEventListener('click', () => { if ($('#fullscreen_video').height() == screen.height) { $('.fullscreen.tooltip').css('background-color', '#d8d8d8') $('.fullscreen.tooltip').css('color', '#273037') document.exitFullscreen() } else { $('.fullscreen.tooltip').css('background-color', '#cc4e4e') $('.fullscreen.tooltip').css('color', 'white') document.getElementById('fullscreen_video').requestFullscreen() } }) videoButt.addEventListener('click', () => { let screen_video = myscreenshare ? myscreenshare.getVideoTracks()[0] : null; let webcam_video = mystreamvideo ? mystreamvideo.getVideoTracks()[0] : null; let video_track = myscreenshare ? screen_video : webcam_video; if (videoAllowed) { socket.emit('action', 'videooff'); } else { if(!video_track) { return; } socket.emit('action', 'videoon'); } }); function toggleVideo(allow = true) { let screen_video = myscreenshare ? myscreenshare.getVideoTracks()[0] : null; let webcam_video = mystreamvideo ? mystreamvideo.getVideoTracks()[0] : null; if (!allow) { videoButt.innerHTML = ``; videoAllowed = 0; videoButt.style.backgroundColor = "#b12c2c"; screen_video ? screen_video.enabled = false : ''; webcam_video ? webcam_video.enabled = false : ''; myvideooff.style.visibility = 'visible'; } else { videoButt.innerHTML = ``; videoAllowed = 1; videoButt.style.backgroundColor = "#F58535"; screen_video ? screen_video.enabled = true : ''; webcam_video ? webcam_video.enabled = true : ''; myvideooff.style.visibility = 'hidden'; } feather.replace(); } audioButt.addEventListener('click', () => { if (audioAllowed) { socket.emit('action', 'mute'); } else { socket.emit('action', 'unmute'); } }); function toggleAudio(allow = true) { let audio_track = mystreamvideo ? mystreamvideo.getAudioTracks()[0] : null; if (!allow) { audioButt.innerHTML = ``; audioAllowed = 0; audioButt.style.backgroundColor = "#b12c2c"; if (audio_track) { audio_track.enabled = false; } $('#mymuteicon').show(); } else { audioButt.innerHTML = ``; audioAllowed = 1; audioButt.style.backgroundColor = "#F58535"; if (audio_track) { audio_track.enabled = true; } $('#mymuteicon').hide(); } feather.replace(); } socket.on('action', (msg, sid, src_sid) => { const admin_action = (sid != src_sid); if (msg == 'mute') { console.log(sid + ' muted by ' + src_sid); if(sid == socket.id) { toggleAudio(false); } else { $(`#mute${sid}`).show(); } dataModelVideo[sid].mic = 'off'; if(admin_action) { dataModelVideo[sid].mic_admin = false; $(`#mic-${sid}`).html(''); } } else if (msg == 'unmute') { if(admin_action) { dataModelVideo[sid].mic_admin = true; $(`#mic-${sid}`).html(''); } else { console.log(sid + ' unmuted themself'); if(sid == socket.id) { toggleAudio(true); } else { $(`#mute${sid}`).hide(); } dataModelVideo[sid].mic = 'on'; } } else if (msg == 'videooff') { console.log(sid + 'turned video off'); if(sid == socket.id) { toggleVideo(false); } else { document.querySelector(`#vidoff${sid}`).style.visibility = 'visible'; } dataModelVideo[sid].vid = 'off'; if(admin_action) { dataModelVideo[sid].vid_admin = false; } } else if (msg == 'videoon') { if(admin_action) { dataModelVideo[sid].vid_admin = true; } else { console.log(sid + 'turned video on'); if(sid == socket.id) { toggleVideo(true); } else { document.querySelector(`#vidoff${sid}`).style.visibility = 'hidden'; } dataModelVideo[sid].vid = 'on'; } } feather.replace(); }) whiteboardButt.addEventListener('click', () => { if (boardVisisble) { whiteboardCont.style.visibility = 'hidden'; videoContainer.style.visibility = 'visible'; boardVisisble = false; } else { whiteboardCont.style.visibility = 'visible'; videoContainer.style.visibility = 'hidden'; boardVisisble = true; } }) screenShareButt.addEventListener('click', () => { if(!myscreenshare){ console.log("vai chamar o share screen") startStream('screen_on'); }else{ console.log("vai mudar pro mystreamvideo de nv") startStream('screen_off'); } }); socket.on('open-screenshare', display => { document.getElementById('videos-container').style.display = display }) cutCall.addEventListener('click', () => { location.href = '/videoConference'; }) function toogleVideoFullscreen(sid) { let video = $(`#${sid}`); if(video.height() >= (screen.height - 10)) { document.exitFullscreen(); } else { video[0].requestFullscreen(); } } function videoFullscreenChange(sid) { let video = $(`#${sid}`); let button = $(`#${sid} .video-bt-fullscreen`); button[0].innerHTML = ``; feather.replace(); } ///////////////////////////////////////////////////////////////////////// //Funções para calcular o tamanho dos vídeos (de forma a maximizar a área dos mesmos) //Baseado em: https://github.com/Alicunde/Videoconference-Dish-CSS-JS //Recalcula (videoSetArea) em: window.onload, window.onresize, Chat toggle, CreateVideoBox, videobox remove const VIDEO_ASPECT_RATIO = 0.75; const VIDEO_MARGIN = 2; const VIDEO_WIDTH_CALC_ERROR_CORRECTION = 2*VIDEO_MARGIN; //empirico function videoAreaCalc(Increment, Count, Width, Height, Margin = 10) { let i = w = 0; let h = Increment * VIDEO_ASPECT_RATIO + (Margin * 2); while (i < (Count)) { if ((w + Increment) > Width) { w = 0; h = h + (Increment * VIDEO_ASPECT_RATIO) + (Margin * 2); } w = w + Increment + (Margin * 2); i++; } if (h > Height) return false; else return Increment; } function videoSetArea() { // variables: let Margin = VIDEO_MARGIN; let Scenary = document.getElementById('vcont'); let Width = Scenary.offsetWidth - (Margin * 2); let Height = Scenary.offsetHeight - (Margin * 2); let Cameras = document.getElementsByClassName('video-box'); let max = 0; if(Cameras.length <= 1) return; // loop (i recommend you optimize this) let i = 1; while (i < 5000) { let w = videoAreaCalc(i, Cameras.length, Width, Height, Margin); if (w === false) { max = i - (1 + VIDEO_WIDTH_CALC_ERROR_CORRECTION); break; } i++; } // set styles max = max - (Margin * 2); videoSetAreaCss(max, Margin); // console.log('Recalculando área de vídeos:',max); if(Scenary.scrollHeight > Scenary.clientHeight) { Scenary.classList.add('overflow'); } else { Scenary.classList.remove('overflow'); } } function videoSetAreaCss(width, margin) { let Cameras = document.getElementsByClassName('video-box'); for (var s = 0; s < Cameras.length; s++) { Cameras[s].style.width = width + "px"; Cameras[s].style.margin = margin + "px"; Cameras[s].style.height = (width * VIDEO_ASPECT_RATIO) + "px"; } } window.addEventListener("load", function (event) { $('.options').css('display', 'none') videoSetArea(); window.onresize = videoSetArea; }, false); $(function() { $('.options').css('display', 'none') }) /** ********************************************** ** INICIO - CONFIGURAÇÃO PARA GRAVAR A TELA ** ********************************************** **/ /** * Get MediaRecorder MimeTypes * @returns mimeType */ function getSupportedMimeTypes() { const possibleTypes = [ 'video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/webm;codecs=h264,opus', 'video/mp4;codecs=h264,aac', 'video/mp4', ]; return possibleTypes.filter((mimeType) => { return MediaRecorder.isTypeSupported(mimeType); }); } /** * Start Recording * https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/record * https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder * https://developer.mozilla.org/en-US/docs/Web/API/MediaStream */ var startAudioStream = [] socket.on('audio_track', audio_track => { console.log('GET AUDIO TRACKS HERE', audio_track) }) AUDIO_CONTEXT = new AudioContext(); async function startStreamRecording() { recordedBlobs = []; let options = getSupportedMimeTypes(); console.log('MediaRecorder options supported', options); options = { mimeType: options[0] }; // select the first available as mimeType try { // on desktop devices recording screen + audio screenMaxFrameRate = 30; console.log('whoami: ', whoami[0]) console.log('tracks_audio: ', tracks_audio) new_tracks_audio = [] console.log('==================================================') $.each(users_socketid, function(i, user_socketid){ if (whoami[0] != user_socketid) { // connections[user_socketid].currentRemoteDescription let video_element = document.getElementById(`video${user_socketid}`) } }) // const DISPLAY_STREAM = await captureScreen(); // const VOICE_STREAM = await captureAudio(); const DISPLAY_STREAM = await navigator.mediaDevices.getDisplayMedia({video: {cursor: "motion"}, audio: {'echoCancellation': true}}); // retrieving screen-media const VOICE_STREAM = await navigator.mediaDevices.getUserMedia({ audio: {'echoCancellation': true}, video: false }, (stream) => { var source = AUDIO_CONTEXT.createMediaStreamSource(stream); source.connect(inputPoint); }); // retrieving microphone-media inputPoint = AUDIO_CONTEXT.createGain(); var analyserNode = AUDIO_CONTEXT.createAnalyser(); inputPoint.connect(analyserNode); MEDIA_AUDIO = AUDIO_CONTEXT.createMediaStreamSource(DISPLAY_STREAM); // passing source of on-screen audio MIC_AUDIO = AUDIO_CONTEXT.createMediaStreamSource(VOICE_STREAM); // passing source of microphone audio AUDIO_MERGER = AUDIO_CONTEXT.createMediaStreamDestination(); // audio merger MEDIA_AUDIO.connect(AUDIO_MERGER); // passing media-audio to merger MIC_AUDIO.connect(AUDIO_MERGER); // passing microphone-audio to merger const TRACKS = [...DISPLAY_STREAM.getVideoTracks(), ...AUDIO_MERGER.stream.getTracks()] // connecting on-screen video with merged-audio stream = new MediaStream(TRACKS); mediaRecorder = new MediaRecorder(stream); // osc.connect(dest); handleMediaRecorder(mediaRecorder); $('#streamRecording').addClass('fa-blink') $('#streamRecording').attr('onclick', 'stopStreamRecording()') $('#streamRecording>span').html('Parar Gravação') console.log('Created MediaRecorder', mediaRecorder, 'with options', options); } catch (err) { console.error('Exception while creating MediaRecorder: ', err); return; } } async function captureScreen(mediaContraints = {video: true}) { const screenStream = await navigator.mediaDevices.getDisplayMedia( mediaContraints ); return screenStream; } async function captureAudio(mediaContraints = {video: false,audio: true,}) { const audioStream = await navigator.mediaDevices.getUserMedia( mediaContraints ); return audioStream; } /** * Handle Media Recorder obj * @param {*} mediaRecorder */ function handleMediaRecorder(mediaRecorder) { mediaRecorder.start(); mediaRecorder.addEventListener('start', handleMediaRecorderStart); mediaRecorder.addEventListener('dataavailable', handleMediaRecorderData); mediaRecorder.addEventListener('stop', handleMediaRecorderStop); } function handleMediaRecorderStart(event) { playSound('recStart'); if (isRecScreenSream) { emitPeersAction('recStart'); emitPeerStatus('rec', isRecScreenSream); } console.log('MediaRecorder started: ', event); isStreamRecording = true; } /** * Handle Media Recorder ondata event * @param {*} event */ function handleMediaRecorderData(event) { console.log('MediaRecorder data: ', event); if (event.data && event.data.size > 0) recordedBlobs.push(event.data); } /** * Handle Media Recorder onstop event * @param {*} event */ function handleMediaRecorderStop(event) { playSound('recStop'); console.log('MediaRecorder stopped: ', event); console.log('MediaRecorder Blobs: ', recordedBlobs); isStreamRecording = false; if (isRecScreenSream) { recScreenStream.getTracks().forEach((track) => { // if (track.kind === 'video') track.stop(); }); isRecScreenSream = false; emitPeersAction('recStop'); emitPeerStatus('rec', isRecScreenSream); } downloadRecordedStream(); } /** * https://notificationsounds.com/notification-sounds * @param {*} name */ async function playSound(name) { if (!notifyBySound) return; let sound = '../../sounds/' + name + '.mp3'; let audioToPlay = new Audio(sound); try { await audioToPlay.play(); } catch (err) { // console.error("Cannot play sound", err); // Automatic playback failed. (safari) return; } } function emitPeersAction(peerAction) { if (!thereIsPeerConnections()) return; myPeerName = username sendToServer('peerAction', { room_id: roomid, peer_name: myPeerName, peer_id: null, peer_action: peerAction, }); } function thereIsPeerConnections() { if (Object.keys(connections).length === 0) return false; return true; } /** * Send my Video-Audio-Hand... status * @param {*} element * @param {*} status */ function emitPeerStatus(element, status) { myPeerName = username sendToServer('peerStatus', { room_id: roomid, peer_name: myPeerName, element: element, status: status, }); } /** * Send async data to signaling server (server.js) * @param {*} msg msg to send to signaling server * @param {*} config JSON data to send to signaling server */ async function sendToServer(msg, config = {}) { await socket.emit(msg, config); } /** * Save to PC / Mobile devices * https://developer.mozilla.org/en-US/docs/Web/API/Blob * @param {*} blob * @param {*} file */ function saveBlobToFile(blob, file) { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = file; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); window.URL.revokeObjectURL(url); }, 100); } /** * Download recorded stream */ function downloadRecordedStream() { const type = 'mp4'; const blob = new Blob(recordedBlobs, { type: 'video/mp4', type: 'audio/ogg; codecs=opus' }); const recFileName = getDataTimeString() + '-REC.' + type; const currentDevice = isMobileDevice ? 'MOBILE' : 'PC'; const blobFileSize = bytesToSize(blob.size); saveBlobToFile(blob, recFileName); } function stopStreamRecording() { $('#streamRecording').removeClass('fa-blink') $('#streamRecording').attr('onclick', 'startStreamRecording()') $('#streamRecording>span').html('Iniciar Gravação') mediaRecorder.stop(); } /** * Data Formated DD-MM-YYYY-H_M_S * https://convertio.co/it/ * @returns data string */ function getDataTimeString() { const d = new Date(); const date = d.toISOString().split('T')[0]; const time = d.toTimeString().split(' ')[0]; return `${date}-${time}`; } /** * Convert bytes to KB-MB-GB-TB * @param {*} bytes * @returns size */ function bytesToSize(bytes) { let sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes == 0) return '0 Byte'; let i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; }