import React, { Component } from 'react' import './App.css'; const io = require("socket.io-client"); let localStream = null; let remoteStream = null; // const iceServer = { // 'iceServer': [ // // { 'urls': 'stun:stun.services.mozilla.com' }, // public // // { 'urls': 'stun:stun.l.google.com:19302' } // public // // { 'urls': 'dev.linx.safemobile.com:19302' }, // // { // // urls: 'turn:dev.linx.safemobile.com:19302', // // username: "safemobile", // // credential: "Safemobile123" // // } // // , // { // urls: 'turn:numb.viagenie.ca', // username: "webrtc@live.com", // credential: "muazkh" // } // ] // } let localVideo = document.getElementById("localVideo"); let remoteVideo = document.getElementById("remoteVideo"); const streamConstraints = { audio: false, video: true }; class App extends Component { constructor() { super(); this.state = { login: '', password: '', user: null, hubStatus: 0, // 0 uninitialized | 1 connecting | 2 connected | 3 connection error socket: null, arsSent: false, dest_asset_id: '', isCaller: false, callId: null, rtcPeerConnection: null, stunUrl: 'stun:dev.linx.safemobile.com:19302', turnUrl: 'turn:dev.linx.safemobile.com:19302', turnUsername: 'safemobile', turnCredential: 'Safemobile123' }; } componentDidMount() { } handleLoginClick = async () => { const loginRequest = await fetch('https://dev.linx.safemobile.com/api/login', { // const loginRequest = await fetch('http://localhost:41418/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "login": this.state.login, "password": this.state.password }) }); const loginResponseJson = await loginRequest.json(); this.setState({ user: loginResponseJson.data }, () => { // Connect to HUB // this.setState({ socket: io("localhost:41414", {reconnect:true, transports: ['websocket']}) }, () => { this.setState({ socket: io("https://hub.dev.linx.safemobile.com/", {reconnect:true, transports: ['websocket']}) }, () => { const socket = this.state.socket; socket.on('connect', () => { this.setState({ hubStatus: 2 }) // Send ARS after connected to HUB socket.emit('ars', JSON.stringify({ ars: true, asset_id: this.state.user.asset.id, account_id: this.state.user.account.id })); this.setState({ arsSent: true }); }); // HUB 'video' event handler socket.on('video', (data) => { const parseDate = JSON.parse(data); console.log('video', parseDate); // The response to the 'notify-request' (hardcoded 'YES') if(parseDate.type === 'notify-request') { this.setState({ dest_asset_id: parseInt(parseDate.origin_asset_id), callId: parseInt(parseDate.video_call_id) }); socket.emit('video', JSON.stringify({ origin_asset_id: this.state.user.asset.id, dest_asset_id: parseInt(parseDate.origin_asset_id), type: 'notify-answer', origin_asset_priority: this.state.user.asset.priority, origin_asset_type_name: this.state.user.user_role.name, origin_asset_name: this.state.user.name, video_call_id: parseDate.video_call_id, answer: 'accepted' // answer: 'rejected' })); } // Handler for 'notify-end'(when the video closed) if(parseDate.type === 'notify-end') { console.log('notify-end'); this.cleanVideoStreams(); } // Handler for 'notify-answer' if(parseDate.type === 'notify-answer') { if(parseDate.answer === 'rejected') { this.closeVideo() return } // Create media stream navigator.mediaDevices.getUserMedia(streamConstraints).then(stream => { console.log('Created getUserMedia', stream); localStream = stream; localVideo.srcObject = stream; console.log('iceServers', this.calculateIceServers()) // Create SDP this.setState({ rtcPeerConnection: new RTCPeerConnection(this.calculateIceServers()) }, () => { this.state.rtcPeerConnection.onicecandidate = this.onIceCandidate; this.state.rtcPeerConnection.ontrack = this.onAddStream; this.state.rtcPeerConnection.addTrack(localStream.getTracks()[0], localStream); // video // rtcPeerConnection.addTrack(localStream.getTracks()[1], localStream) // audio // Create a offer this.state.rtcPeerConnection.createOffer().then(sessionDescription => { // Save SDP in state and send it to destination asset_id as offer this.setState({ isCaller: true, callId: parseInt(parseDate.video_call_id) }, () => { this.state.rtcPeerConnection.setLocalDescription(sessionDescription); console.log('Created SDP', sessionDescription); socket.emit('video', JSON.stringify({ origin_asset_id: this.state.user.asset.id, type: 'offer', sdp: sessionDescription, dest_asset_id: parseInt(this.state.dest_asset_id), video_call_id: parseDate.video_call_id })); }) }) .catch(e => { console.log(e); }) }) }) .catch(err => { console.log('An error occured', err); }) } // Handler for 'offer' if(parseDate.type === 'offer') { // When we are NOT the caller if(!this.state.isCaller) { // Create media stream navigator.mediaDevices.getUserMedia(streamConstraints).then(stream => { console.log('Created getUserMedia', stream); localStream = stream; localVideo.srcObject = stream; console.log('iceServers', this.calculateIceServers()) // Create SDP this.setState({ rtcPeerConnection: new RTCPeerConnection(this.calculateIceServers()) }, () => { this.state.rtcPeerConnection.onicecandidate = this.onIceCandidate; this.state.rtcPeerConnection.ontrack = this.onAddStream; this.state.rtcPeerConnection.addTrack(localStream.getTracks()[0], localStream); // video // rtcPeerConnection.addTrack(localStream.getTracks()[1], localStream) // audio // Set setRemoteDescription to be sender SDP this.state.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(parseDate.sdp)); // Create an answer this.state.rtcPeerConnection.createAnswer().then(sessionDescription => { // Save SDP in state and send it to destination asset_id as offer this.setState({ sdp: sessionDescription }, () => { this.state.rtcPeerConnection.setLocalDescription(sessionDescription); console.log('Created SDP', sessionDescription); socket.emit('video', JSON.stringify({ origin_asset_id: this.state.user.asset.id, type: 'offer', sdp: sessionDescription, dest_asset_id: parseInt(this.state.dest_asset_id), video_call_id: parseDate.video_call_id })); }) }) .catch(e => { console.log(e); }) }) }) } else { // When we ARE the caller console.log('parseDate.sdp', parseDate.sdp); this.state.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(parseDate.sdp)); } } if(parseDate.type === 'candidate') { if(this.state.rtcPeerConnection) { const candidate = new RTCIceCandidate({ sdpMLineIndex: parseDate.label, candidate: parseDate.candidate }); this.state.rtcPeerConnection.addIceCandidate(candidate); } } }); }); }) } calculateIceServers = () => { return { 'iceServer': [ { 'urls': this.state.stunUrl }, { urls: this.state.turnUrl, username: this.state.turnUsername, credential: this.state.turnCredential } ] } } onAddStream = (event) => { console.log('onAddStream', event); remoteVideo.srcObject = event.streams[0]; remoteStream = event.streams[0]; } onIceCandidate = (event) => { if(event.candidate) { console.log('sending ice candidate', event.candidate); this.state.socket.emit('video', JSON.stringify({ type: 'candidate', label: event.candidate.sdpMLineIndex, id: event.candidate.sdpMid, candidate: event.candidate.candidate, dest_asset_id: this.state.dest_asset_id })); } } handleChangeLogin = (e) => { this.setState({ login: e.target.value}); } handleChangePassword = (e) => { this.setState({ password: e.target.value}); } handleChangeDestAssetId = (e) => { this.setState({ dest_asset_id: e.target.value}); } handleChangeStunUrl = (e) => { this.setState({ stunUrl: e.target.value}); } handleChangeTurnUrl = (e) => { this.setState({ turnUrl: e.target.value}); } handleChangeTurnUsername = (e) => { this.setState({ turnUsername: e.target.value}); } handleChangeTurnCredential = (e) => { this.setState({ turnCredential: e.target.value}); } handleClickEvent = () => { this.state.socket.emit('video', JSON.stringify({ origin_asset_id: this.state.user.asset.id, dest_asset_id: parseInt(this.state.dest_asset_id), type: 'notify-request', origin_asset_priority: this.state.user.asset.priority, origin_asset_type_name: this.state.user.user_role.name, origin_asset_name: this.state.user.name, video_call_id: null })); } closeVideo = () => { this.state.socket.emit('video', JSON.stringify({ origin_asset_id: this.state.user.asset.id, dest_asset_id: parseInt(this.state.dest_asset_id), type: 'notify-end', video_call_id: this.state.callId })); this.cleanVideoStreams(); } cleanVideoStreams = () => { this.state.rtcPeerConnection != null && this.state.rtcPeerConnection.close(); this.setState({ callId: null, rtcPeerConnection: null }); localVideo.srcObject = null; remoteVideo.srcObject = null; } render() { const { login, password, user, hubStatus, arsSent, dest_asset_id, isCaller, callId, stunUrl, turnUrl, turnUsername, turnCredential } = { ...this.state }; return (

HUB TESTER



STUN and TURN servers

STUN Server URL :

TURN Server URL :

TURN Server Username :

TURN Server Credential :



Login

Username :

Password :



isCaller: {isCaller ? 'TRUE' : 'FALSE'}

User details:

{ user && (

account_id: {user.account.id}

user_id: {user.id}

asset_id: {user.asset.id}

user_role: {user.user_role.name}

) }

HUB

status: {(hubStatus === 0 ? 'uninitialized' : hubStatus === 1 ? 'connecting' : hubStatus === 2 ? 'connected' : 'connection error' )}

ARS Sent: {arsSent === true ? 'TRUE' : 'FALSE'}

Call id: {callId}





); }; }; export default App;