- Docs »
- Source: webrtc-client.js
import EventEmitter from 'eventemitter3';
import OutgoingCall from './call/outgoing-call';
import IncomingCall from './call/incoming-call';
import Offer from './signalings/offer';
const DEFAULT_RTCCONF = {
iceServers: [
{
urls: [
'stun:stun.l.google.com:19302',
'stun:stun1.l.google.com:19302',
'stun:stun2.l.google.com:19302',
'stun:stun3.l.google.com:19302',
'stun:stun4.l.google.com:19302',
'stun:stun.ekiga.net',
'stun:stun.ideasip.com',
'stun:stun.rixtelecom.se',
'stun:stun.schlund.de',
'stun:stun.stunprotocol.org:3478',
'stun:stun.voiparound.com',
'stun:stun.voipbuster.com',
'stun:stun.voipstunt.com',
'stun:stun.voxgratia.org',
],
},
],
};
export default class WebRTCClient extends EventEmitter {
/**
* 无法直接实例化,请使用 {@link createWebRTCClient Realtime#createWebRTCClient} 创建新的 WebRTCClient
*/
constructor(id, options) {
if (typeof id !== 'string') {
throw new TypeError('id is not a string');
}
super();
/** @type {string} */
this.id = id;
this.options = {
RTCConfiguration: DEFAULT_RTCCONF,
...options,
};
}
_open(realtime, clientOptions) {
return realtime
.createIMClient(this.id, clientOptions, 'webrtc')
.then(imClient => {
this._imClient = imClient;
this.id = imClient.id;
imClient.on('message', (message, conversation) => {
if (message instanceof Offer) {
return this._handleOffer(message, conversation);
}
return false;
});
/**
* 用户在其他客户端登录,当前客户端被服务端强行下线。详见文档「单点登录」章节。
* @event WebRTCClient#conflict
*/
imClient.on('conflict', (...payload) =>
this.emit('conflict', ...payload)
);
return this;
});
}
/**
* 关闭客户端
* @return {Promise}
*/
close() {
return this._imClient.close();
}
/**
* 呼叫另一个用户
* @param {string} targetId 用户 ID
* @param {MediaStream} stream 本地流媒体,参见 {@link https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API MediaStream}
* @return {Promise.<OutgoingCall>} 呼出通话
*/
call(targetId, stream) {
if (typeof targetId !== 'string') {
throw new TypeError('target id is not a string');
}
if (!stream) {
throw new TypeError('a MediaStream instance is required to make a call');
}
return this._imClient.ping([targetId]).then(onlineClients => {
if (!onlineClients.length) {
throw new Error(`Call failed as ${targetId} is not online`);
}
const outgoingCall = new OutgoingCall(
targetId,
null,
this.options.RTCConfiguration
);
const promise = new Promise(resolve => {
outgoingCall._peerConnection.onnegotiationneeded = resolve;
});
outgoingCall._peerConnection.addStream(stream);
return promise
.then(() =>
Promise.all([
this._imClient.createConversation({
members: [targetId],
unique: true,
}),
outgoingCall._peerConnection
.createOffer()
.then(localDescription => {
outgoingCall._peerConnection.setLocalDescription(
localDescription
);
}),
])
)
.then(([conversation]) => {
outgoingCall._setConversation(conversation);
return conversation.send(
new Offer(outgoingCall._peerConnection.localDescription)
);
})
.then(() => outgoingCall);
});
}
_handleOffer(offer, conversation) {
const incomingCall = new IncomingCall(
offer,
conversation,
this.options.RTCConfiguration
);
/**
* 收到其他用户的呼叫
* @event WebRTCClient#call
* @param {incomingCall} incomingCall 呼入通话
*/
this.emit('call', incomingCall);
}
}