UNPKG

stalk-js-webrtc

Version:

S-Talk web-rtc javascript client implementation.

222 lines (186 loc) 8.16 kB
/** * S-TAlK React-Native webrtc peer implementation... * * Copyright 2017 Ahoo Studio.co.th. */ import { Platform } from 'react-native'; import { EventEmitter } from 'events'; import { RTCPeerConnection, RTCIceCandidate, RTCSessionDescription, } from 'react-native-webrtc'; import { AbstractPeerConnection, IPC_Handler, PeerConstructor } from "../core/AbstractPeerConnection"; import { AbstractPeer } from "../core/AbstractPeer"; import { IMessageExchange } from "../core/WebrtcSignaling"; export class Peer extends AbstractPeer.BasePeer { /** * reture PeerConnection * @param socket * @param stream * @param options */ constructor(config: PeerConstructor) { super(config); this.initPeerConnection(config.stream, config.iceConfig); } initPeerConnection(stream: MediaStream, iceConfig: RTCConfiguration) { let self = this; self.channels = {}; self.pcEvent = new EventEmitter(); let iceServers; if (!!iceConfig) iceServers = iceConfig; else iceServers = this.configuration; this.pc = new RTCPeerConnection(iceServers); if (self.debug) { console.log(JSON.stringify(iceServers)); console.log(`connection: ${this.pc.iceConnectionState}, Gathering: ${this.pc.iceGatheringState}, signaling: ${this.pc.signalingState}`); } this.pc.onnegotiationneeded = function () { if (self.debug) console.log("onnegotiationneeded"); self.pcEvent.emit(AbstractPeerConnection.PeerEvent, "onnegotiationneeded"); if (self.offer) { self.createOffer(); } } this.pc.onicecandidate = function (event) { self.send_event(AbstractPeerConnection.CANDIDATE, event.candidate, { to: self.id }); }; this.pc.oniceconnectionstatechange = function (event) { let target = event.target as RTCPeerConnection; if (self.debug) console.log('oniceconnectionstatechange', target.iceConnectionState); self.pcEvent.emit("oniceconnectionstatechange", target.iceConnectionState); if (target.iceConnectionState === 'completed') { // setTimeout(() => { // self.getStats(); // }, 1000); self.parentsEmitter.emit(AbstractPeerConnection.ON_ICE_COMPLETED, self.pcPeers); } if (target.iceConnectionState === 'connected') { self.createDataChannel(); self.parentsEmitter.emit(AbstractPeerConnection.ON_ICE_CONNECTED, self.pcPeers); } else if (target.iceConnectionState == "failed") { self.parentsEmitter.emit(AbstractPeerConnection.ON_ICE_CONNECTION_FAILED, self.pcPeers); self.send_event(AbstractPeerConnection.CONNECTIVITY_ERROR, null, { to: self.id }); } else if (target.iceConnectionState == "closed") { self.parentsEmitter.emit(AbstractPeerConnection.ON_ICE_CONNECTION_CLOSED, self.pcPeers); } }; this.pc.onicegatheringstatechange = (event) => { let target = event.target as RTCPeerConnection; if (self.debug) console.log("onicegatheringstatechange", target.iceGatheringState); // When iceGatheringState == complete it fire onicecandidate with null. if (target.iceGatheringState == "complete") { self.sendOffer(); } self.pcEvent.emit("onicegatheringstatechange", target.iceGatheringState); }; this.pc.onsignalingstatechange = function (event) { let target = event.target as RTCPeerConnection; if (self.debug) console.log('onsignalingstatechange', target.signalingState); self.pcEvent.emit("onsignalingstatechange", target.signalingState); }; this.pc.onaddstream = function (peer) { if (self.debug) console.log('onaddstream'); self.parentsEmitter.emit(AbstractPeerConnection.PEER_STREAM_ADDED, peer); }; this.pc.onremovestream = function (peer) { if (self.debug) console.log('onremovestream'); self.parentsEmitter.emit(AbstractPeerConnection.PEER_STREAM_REMOVED, peer.stream); }; this.pc.addStream(stream); self.parentsEmitter.emit(AbstractPeerConnection.CREATED_PEER, self); } getStats() { let self = this; const peer = this.pcPeers[Object.keys(this.pcPeers)[0]]; const pc = peer.pc; if (pc.getRemoteStreams()[0] && pc.getRemoteStreams()[0].getAudioTracks()[0]) { const track = pc.getRemoteStreams()[0].getAudioTracks()[0]; console.log('track', track); pc.getStats(track, function (report) { console.log('getStats report', report); }, self.logError); } } handleMessage(message: IMessageExchange) { let self = this; if (self.debug) console.log('handleMessage', message.type); if (message.prefix) this.browserPrefix = message.prefix; if (message.type === AbstractPeerConnection.OFFER) { if (!this.nick) this.nick = message.payload.nick; delete message.payload.nick; // Not support promise return type. self.pc.setRemoteDescription(new RTCSessionDescription(message.payload), function success() { if (self.debug) console.log("setRemoteDescription complete"); if (self.pc.remoteDescription.type == AbstractPeerConnection.OFFER) { self.createAnswer(message); } }, self.onSetSessionDescriptionError); } else if (message.type === AbstractPeerConnection.ANSWER) { // Not support promise return type. self.pc.setRemoteDescription(new RTCSessionDescription(message.payload), () => { if (self.debug) console.log("setRemoteDescription complete"); }, self.onSetSessionDescriptionError); } else if (message.type === AbstractPeerConnection.CANDIDATE) { if (!message.payload) return; self.pc.addIceCandidate(new RTCIceCandidate(message.payload)); } else if (message.type === AbstractPeerConnection.CONNECTIVITY_ERROR) { this.parentsEmitter.emit(AbstractPeerConnection.CONNECTIVITY_ERROR, self.pc); } }; createDataChannel() { if (this.pc.textDataChannel) { return; } const dataChannel = this.pc.createDataChannel("text"); dataChannel.onerror = function (error) { console.log("dataChannel.onerror", error); }; dataChannel.onmessage = function (event) { console.log("dataChannel.onmessage:", event.data); let message = event.data; if (message.type === 'connectivityError') { this.parentsEmitter.emit(AbstractPeerConnection.CONNECTIVITY_ERROR, self); } else if (message.type === 'endOfCandidates') { // Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the // shim this will only be called in Edge. var mLines = this.pc.pc.transceivers || []; mLines.forEach(function (mLine) { if (mLine.iceTransport) { mLine.iceTransport.addRemoteCandidate({}); } }); } }; dataChannel.onopen = function () { console.log('dataChannel.onopen'); }; dataChannel.onclose = function () { console.log("dataChannel.onclose"); }; this.pc.textDataChannel = dataChannel; } }