stalk-js-webrtc
Version:
S-Talk web-rtc javascript client implementation.
222 lines (186 loc) • 8.16 kB
text/typescript
/**
* 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;
}
}