282 lines
		
	
	
		
			No EOL
		
	
	
		
			9.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
		
			No EOL
		
	
	
		
			9.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
})(); |