import io from 'socket.io-client'; import kurentoUtils from 'kurento-utils'; import { EventEmitter } from 'events'; (async () => { // RTCPeerConnection let pc; // Send permission dialog to client const openMediaDevices = async (constraints) => { return await navigator.mediaDevices.getUserMedia(constraints); } try { const stream = await openMediaDevices({ 'video': true, 'audio': true }); console.log('Got MediaStream:', stream); function updateList(elements, type) { const listElement = document.querySelector(type == 'video' ? 'select#availableCameras' : 'select#availableMicrophones'); listElement.innerHTML = ''; elements.forEach(camera => { const cameraOption = document.createElement('option'); cameraOption.label = camera.label; cameraOption.value = camera.deviceId; listElement.add(cameraOption); }); } // Fetch an array of devices of a certain type async function getConnectedDevices(type) { const devices = await navigator.mediaDevices.enumerateDevices(); return devices.filter(device => device.kind === type) } // Listen for changes to media devices and update the list accordingly navigator.mediaDevices.addEventListener('devicechange', async _ => { updateList(await getConnectedDevices('videoinput'), 'video'); updateList(await getConnectedDevices('audioinput'), 'audio'); }); updateList(await getConnectedDevices('videoinput'), 'video'); updateList(await getConnectedDevices('audioinput'), 'audio'); async function playVideoFromCamera() { try { const videoSelect = document.querySelector('select#availableCameras'); const stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoSelect.options[videoSelect.selectedIndex].value, // width: 1920, // height: 1080, frameRate: 30, } }); const videoElement = document.querySelector('video#localVideo'); videoElement.srcObject = stream; } catch (error) { console.error('Error opening video camera.', error); } } // document.querySelector('button#startVideo').addEventListener('click', () => playVideoFromCamera()); } catch (error) { console.error('Error accessing media devices.', error); } // console.log('Make Call'); const socket = io(); socket.on('connect', function () { socket.on('startResponse', startResponse); }); // socket.emit('events', { test: 'test' }); // socket.emit('start', {}); // }); // socket.on('exception', function (data) { // Not working? // console.log('event', data); // }); // socket.on('disconnect', function () { // console.log('Disconnected'); // socket.close(); // Good idea? // }); var webRtcPeer; document.querySelector('button#startCall').addEventListener('click', () => start()); document.querySelector('button#stopCall').addEventListener('click', () => stop()); async function start() { console.log('Starting video call ...') console.log('Creating WebRtcPeer and generating local sdp offer ...'); pc = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.cliffbreak.de', credential: '8dd2a7643a840ff4bb10e82069b6feceaf6d344a19a590ffc062fa10ea34687b', }, ] }); const dataChannel = pc.createDataChannel('test'); //Unneeded? let videoStream; try { const videoSelect = document.querySelector('select#availableCameras'); const stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoSelect.options[videoSelect.selectedIndex].value, // width: 1920, // height: 1080, frameRate: 30, } }); const videoElement = document.querySelector('video#localVideo'); console.log(stream); videoElement.srcObject = stream; videoStream = stream; } catch (error) { console.error('Error opening video camera.', error); } let candidategatheringdone = false; const candidatesQueueOut = []; pc.onicecandidate = (event) => { const candidate = event.candidate; if (EventEmitter.listenerCount('icecandidate') || EventEmitter .listenerCount('candidategatheringdone')) { candidategatheringdone = false; } else if (!candidategatheringdone) { candidatesQueueOut.push(candidate); if (!candidate) { candidategatheringdone = true; console.log(candidatesQueueOut); while (candidatesQueueOut.length) { const candidate = candidatesQueueOut.shift(); if (!!candidate) onIceCandidate(candidate); } for (const track of videoStream.getVideoTracks()) { pc.addTrack(track, videoStream); } // pc.addStream(videoStream); } } }; pc.createOffer().then((offer) => { console.log('Created SDP offer'); console.log(offer); const newOffer = new RTCSessionDescription({ type: offer.type, sdp: removeFIDFromOffer(offer.sdp) + getSimulcastInfo(videoStream), }); return pc.setLocalDescription(newOffer); }).then(() => { const localDescription = pc.localDescription; console.log('Local description set: ' + localDescription.sdp); onOffer(null, localDescription.sdp); }).catch(onOffer); function removeFIDFromOffer(sdp) { var n = sdp.indexOf("a=ssrc-group:FID"); if (n > 0) { return sdp.slice(0, n); } else { return sdp; } } function getSimulcastInfo(videoStream) { var videoTracks = videoStream.getVideoTracks(); if (!videoTracks.length) { logger.warn('No video tracks available in the video stream') return '' } var lines = [ 'a=x-google-flag:conference', 'a=ssrc-group:SIM 1 2 3', 'a=ssrc:1 cname:localVideo', 'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id, 'a=ssrc:1 mslabel:' + videoStream.id, 'a=ssrc:1 label:' + videoTracks[0].id, 'a=ssrc:2 cname:localVideo', 'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id, 'a=ssrc:2 mslabel:' + videoStream.id, 'a=ssrc:2 label:' + videoTracks[0].id, 'a=ssrc:3 cname:localVideo', 'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id, 'a=ssrc:3 mslabel:' + videoStream.id, 'a=ssrc:3 label:' + videoTracks[0].id ]; lines.push(''); return lines.join('\n'); } // var options = { // localVideo: document.querySelector('video#localVideo'), // remoteVideo: document.querySelector('video#remoteVideo'), // onicecandidate: onIceCandidate // } // webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, function (error) { // if (error) return onError(error); // this.generateOffer(onOffer); // }); } function onIceCandidate(candidate) { console.log('Local candidate' + JSON.stringify(candidate)); socket.emit('onIceCandidate', JSON.stringify({ candidate })); } function onOffer(error, offerSdp) { if (error) return onError(error); console.info('Invoking SDP offer callback function ' + location.host); socket.emit('start', JSON.stringify({ sdpOffer: offerSdp })); } function onError(error) { console.error(error); } function startResponse(message) { console.log('SDP answer received from server. Processing ...'); const sdpAnswer = JSON.parse(message).sdpAnswer; // webRtcPeer.processAnswer(sdpAnswer); let answer = new RTCSessionDescription({ type: 'answer', sdp: sdpAnswer, }); pc.setRemoteDescription(answer, () => { const remoteVideo = document.querySelector('video#remoteVideo'); // remoteVideo.pause(); // pc.getRemoteStreams = function () { // var stream = new MediaStream(); // pc.getReceivers().forEach(function (sender) { // stream.addTrack(sender.track); // }); // return [stream]; // }; // console.table(pc.getRemoteStreams()); // const stream = pc.getRemoteStreams()[0]; // console.log(stream); pc.ontrack = function (event) { console.log('ontrack'); console.log(event); remoteVideo.srcObject = event.streams[0]; } // remoteVideo.load(); }); } function stop() { console.log('Stopping video call ...'); socket.emit('stop'); // TODO::: if (webRtcPeer) { webRtcPeer.dispose(); webRtcPeer = null; } } })();