export default class CallService { private callId: string; private inCall: boolean; private peerId: number; private subscription; private localStream: MediaStream; private remoteStream: MediaStream; private needToAddStream: boolean = true; private pc: RTCPeerConnection; constructor(private ws) { this.callId = null; this.inCall = false; this.peerId = -1; this.subscription = null; this.pc = null; this.localStream = null; this.remoteStream = null; this.remoteStream = new MediaStream(); } async connectToCall(callId: string): Promise { if (this.inCall) throw new Error('Already connected to call'); this.subscription = this.ws.subscribe(`call:${callId}`); const subscription = this.subscription; const self = this; return new Promise((resolve, reject) => { subscription.on('error', (e) => { console.error(e); resolve(false) }); subscription.on('ready', () => { this.callId = callId; this.inCall = true; resolve(true) }); subscription.on('close', self.close.bind(this)); subscription.on('call:start', self.onCallStart.bind(this)); subscription.on('call:standby', self.onCallStandby.bind(this)); subscription.on('wrtc:sdp:offer', self.onRemoteOffer.bind(this)); subscription.on('wrtc:sdp:answer', self.onRemoteAnswer.bind(this)); subscription.on('wrtc:ice', self.onRemoteIce.bind(this)); }); } private send(event: string, payload: { [key: string]: any }) { this.subscription.emit(event, { id: this.peerId, ...payload }) } async onCallStart(payload: { iceServers: RTCIceServer[], id: number }) { console.log(payload); this.peerId = payload.id; this.pc = new RTCPeerConnection({ iceServers: payload.iceServers }); console.log('Created PeerConnection'); this.setupPeerConnectionListeners(); const sdp = await this.pc.createOffer(); await this.pc.setLocalDescription(sdp); console.log('Local description Set'); this.send('wrtc:sdp:offer', { sdp }); return true; } async onCallStandby(payload: { iceServers: RTCIceServer[], id: number }) { console.log(payload); this.peerId = payload.id; this.pc = new RTCPeerConnection({ iceServers: payload.iceServers }); console.log('Created PeerConnection'); this.setupPeerConnectionListeners(); return true; } setupPeerConnectionListeners() { this.pc.addEventListener("icecandidate", this.onLocalIce.bind(this)); this.pc.addEventListener('connectionstatechange', event => { console.log(`PC Connection state: ${this.pc.connectionState}`); // if (this.pc.connectionState === 'connected') { // // Peers connected! // } }); this.pc.addEventListener('iceconnectionstatechange', event => { console.log('iceconnectionstatechange'); console.log(this.pc.iceConnectionState); }) this.pc.addEventListener('track', async (event) => { console.log('On remote track!'); this.remoteStream.addTrack(event.track); }); this.pc.addEventListener('icegatheringstatechange', event => { console.log('icegatheringstatechange', this.pc.iceGatheringState); }); if (this.needToAddStream && this.localStream) { this.localStream.getTracks().forEach(t => { console.log('adding track to pc - in the event list'); console.log(t); this.pc.addTrack(t, this.localStream); }); this.needToAddStream = false; } } onLocalIce(event) { if (event.candidate) { console.log('Sending candidate'); this.send('wrtc:ice', { ice: event.candidate }); } } async onRemoteOffer(payload) { const offer = new RTCSessionDescription(payload.sdp); await this.pc.setRemoteDescription(offer); console.log('Remote offer Set'); const sdp = await this.pc.createAnswer(); this.send('wrtc:sdp:answer', { sdp }); await this.pc.setLocalDescription(sdp); console.log('Local answer Set'); return true; } async onRemoteAnswer(payload) { const answer = new RTCSessionDescription(payload.sdp); await this.pc.setRemoteDescription(answer); console.log('Remote answer Set'); return true; } async onRemoteIce(payload) { const ice = payload.ice; await this.pc.addIceCandidate(ice); return true; } async getUserMedia(constraints: MediaStreamConstraints = { video: false, audio: true }) { if (this.localStream) return this.localStream; this.localStream = await navigator.mediaDevices.getUserMedia(constraints); if (this.pc) { if (this.needToAddStream && this.localStream) { this.localStream.getTracks().forEach(t => { console.log('adding track to pc - in get user media'); this.pc.addTrack(t, this.localStream); }); this.needToAddStream = false; } } return this.localStream; } getRemoteStream() { return this.remoteStream; } close() { if (this.subscription) this.subscription.close(); this.subscription = null; this.inCall = false; } }