Page MenuHomePhabricator

No OneTemporary

diff --git a/lib/account.js b/lib/account.js
index f330742..8bfebb4 100644
--- a/lib/account.js
+++ b/lib/account.js
@@ -1,165 +1,165 @@
'use strict';
import debug from 'debug';
import md5 from 'blueimp-md5';
import transform from 'sdp-transform';
import utils from './utils';
import { EventEmitter } from 'events';
import { Call } from './call';
import { ConferenceCall } from './conference';
const DEBUG = debug('sylkrtc:Account');
class Account extends EventEmitter {
constructor(options, connection) {
if (options.account.indexOf('@') === -1) {
throw new Error('Invalid account id specified');
}
super();
const id = options.account;
const [username, domain] = id.split('@');
this._id = id;
this._displayName = options.displayName;
this._password = md5(username + ':' + (options.realm || domain)+ ':' + options.password);
this._connection = connection;
this._registrationState = null;
this._calls = new Map();
this._confCalls = new Map();
}
get id() {
return this._id;
}
get password() {
return this._password;
}
get displayName() {
return this._displayName;
}
get registrationState() {
return this._registrationState;
}
register() {
const req = {
sylkrtc: 'account-register',
account: this._id
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Register error: %s', error);
const oldState = this._registrationState;
const newState = 'failed';
const data = {reason: error.toString()};
this._registrationState = newState;
this.emit('registrationStateChanged', oldState, newState, data);
}
});
}
unregister() {
const req = {
sylkrtc: 'account-unregister',
account: this._id,
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Unregister error: %s', error);
}
const oldState = this._registrationState;
const newState = null;
this._registrationState = newState;
this.emit('registrationStateChanged', oldState, newState, {});
});
}
call(uri, options={}) {
const callObj = new Call(this);
callObj._initOutgoing(uri, options);
this._calls.set(callObj.id, callObj);
this.emit('outgoingCall', callObj);
return callObj;
}
joinConference(uri, options={}) {
const confCall = new ConferenceCall(this);
confCall._initialize(uri, options);
this._confCalls.set(confCall.id, confCall);
this.emit('conferenceCall', confCall);
return confCall;
}
setDeviceToken(oldToken, newToken) {
DEBUG('Setting device token: %s -> %s', oldToken, newToken);
const req = {
sylkrtc: 'account-devicetoken',
account: this._id,
old_token: oldToken,
new_token: newToken
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Error setting device token: %s', error);
}
});
}
// Private API
_handleEvent(message) {
DEBUG('Received account event: %s', message.event);
const data = {};
switch (message.event) {
- case 'registration_state':
+ case 'registration-state':
const oldState = this._registrationState;
- const newState = message.data.state;
+ const newState = message.state;
this._registrationState = newState;
if (newState === 'failed') {
- data.reason = message.data.reason;
+ data.reason = message.reason;
}
this.emit('registrationStateChanged', oldState, newState, data);
break;
- case 'incoming_session':
+ case 'incoming-session':
let call = new Call(this);
- call._initIncoming(message.session, message.data.originator, message.data.sdp);
+ call._initIncoming(message.session, message.originator, message.sdp);
this._calls.set(call.id, call);
// see what media types are offered
const mediaTypes = { audio: false, video: false };
- const parsedSdp = transform.parse(message.data.sdp);
+ const parsedSdp = transform.parse(message.sdp);
for (let media of parsedSdp.media) {
if (media.type === 'audio' && media.port !== 0 && media.direction === 'sendrecv') {
mediaTypes.audio = true;
} else if (media.type === 'video' && media.port !== 0 && media.direction === 'sendrecv') {
mediaTypes.video = true;
}
}
- DEBUG('Incoming call from %s with media types: %o', message.data.originator.uri, mediaTypes);
+ DEBUG('Incoming call from %s with media types: %o', message.originator.uri, mediaTypes);
this.emit('incomingCall', call, mediaTypes);
break;
- case 'missed_session':
- data.originator = new utils.Identity(message.data.originator.uri, message.data.originator.display_name);
+ case 'missed-session':
+ data.originator = new utils.Identity(message.originator.uri, message.originator.display_name);
this.emit('missedCall', data);
break;
- case 'conference_invite':
- data.originator = new utils.Identity(message.data.originator.uri, message.data.originator.display_name);
- data.room = message.data.room;
+ case 'conference-invite':
+ data.originator = new utils.Identity(message.originator.uri, message.originator.display_name);
+ data.room = message.room;
this.emit('conferenceInvite', data);
break;
default:
break;
}
}
_sendRequest(req, cb) {
this._connection._sendRequest(req, cb);
}
}
export { Account };
diff --git a/lib/call.js b/lib/call.js
index 1d704b2..977dda0 100644
--- a/lib/call.js
+++ b/lib/call.js
@@ -1,360 +1,360 @@
'use strict';
import debug from 'debug';
import uuidv4 from 'uuid/v4';
import utils from './utils';
import { EventEmitter } from 'events';
const DEBUG = debug('sylkrtc:Call');
class Call extends EventEmitter {
constructor(account) {
super();
this._account = account;
this._id = null;
this._direction = null;
this._pc = null;
this._state = null;
this._terminated = false;
this._incomingSdp = null;
this._localIdentity = new utils.Identity(account.id, account.displayName);
this._remoteIdentity = null;
this._dtmfSender = null;
// bind some handlers to this instance
this._onDtmf = this._onDtmf.bind(this);
}
get account() {
return this._account;
}
get id() {
return this._id;
}
get direction() {
return this._direction;
}
get state() {
return this._state;
}
get localIdentity() {
return this._localIdentity;
}
get remoteIdentity() {
return this._remoteIdentity;
}
getLocalStreams() {
if (this._pc !== null) {
return this._pc.getLocalStreams();
} else {
return [];
}
}
getRemoteStreams() {
if (this._pc !== null) {
return this._pc.getRemoteStreams();
} else {
return [];
}
}
answer(options = {}) {
if (this._state !== 'incoming') {
throw new Error('Call is not in the incoming state: ' + this._state);
}
if (!options.localStream) {
throw new Error('Missing localStream');
}
const pcConfig = options.pcConfig || {iceServers:[]};
const answerOptions = options.answerOptions;
// Create the RTCPeerConnection
this._initRTCPeerConnection(pcConfig);
this._pc.addStream(options.localStream);
this.emit('localStreamAdded', options.localStream);
this._pc.setRemoteDescription(
new RTCSessionDescription({type: 'offer', sdp: this._incomingSdp}),
// success
() => {
utils.createLocalSdp(this._pc, 'answer', answerOptions)
.then((sdp) => {
DEBUG('Local SDP: %s', sdp);
this._sendAnswer(sdp);
})
.catch((reason) => {
DEBUG(reason);
this.terminate();
});
},
// failure
(error) => {
DEBUG('Error setting remote description: %s', error);
this.terminate();
}
);
}
terminate() {
if (this._terminated) {
return;
}
DEBUG('Terminating call');
this._sendTerminate();
}
sendDtmf(tones, duration=100, interToneGap=70) {
DEBUG('sendDtmf()');
if (this._dtmfSender === null) {
if (this._pc !== null) {
let track = null;
try {
track = this._pc.getLocalStreams()[0].getAudioTracks()[0];
} catch (e) {
// ignore
}
if (track !== null) {
DEBUG('Creating DTMF sender');
this._dtmfSender = this._pc.createDTMFSender(track);
if (this._dtmfSender) {
this._dtmfSender.addEventListener('tonechange', this._onDtmf);
}
}
}
}
if (this._dtmfSender) {
DEBUG('Sending DTMF tones');
this._dtmfSender.insertDTMF(tones, duration, interToneGap);
}
}
// Private API
_initOutgoing(uri, options={}) {
if (uri.indexOf('@') === -1) {
throw new Error('Invalid URI');
}
if (!options.localStream) {
throw new Error('Missing localStream');
}
this._id = uuidv4();
this._direction = 'outgoing';
this._remoteIdentity = new utils.Identity(uri);
const pcConfig = options.pcConfig || {iceServers:[]};
const offerOptions = options.offerOptions;
// Create the RTCPeerConnection
this._initRTCPeerConnection(pcConfig);
this._pc.addStream(options.localStream);
this.emit('localStreamAdded', options.localStream);
utils.createLocalSdp(this._pc, 'offer', offerOptions)
.then((sdp) => {
DEBUG('Local SDP: %s', sdp);
this._sendCall(uri, sdp);
})
.catch((reason) => {
DEBUG(reason);
this._localTerminate(reason);
});
}
_initIncoming(id, caller, sdp) {
this._id = id;
this._remoteIdentity = new utils.Identity(caller.uri, caller.display_name);
this._incomingSdp = sdp;
this._direction = 'incoming';
this._state = 'incoming';
DEBUG('Remote SDP: %s', sdp);
}
_handleEvent(message) {
DEBUG('Call event: %o', message);
switch (message.event) {
case 'state':
const oldState = this._state;
- const newState = message.data.state;
+ const newState = message.state;
this._state = newState;
const data = {};
if (newState === 'accepted' && this._direction === 'outgoing') {
- const sdp = utils.mungeSdp(message.data.sdp);
+ const sdp = utils.mungeSdp(message.sdp);
DEBUG('Remote SDP: %s', sdp);
this._pc.setRemoteDescription(
new RTCSessionDescription({type: 'answer', sdp: sdp}),
// success
() => {
// fake 'established' state change
this._state = 'established';
this.emit('stateChanged', oldState, 'established', {});
},
// failure
(error) => {
DEBUG('Error accepting call: %s', error);
this.terminate();
}
);
DEBUG('Call accepted');
this.emit('stateChanged', oldState, newState, data);
} else if (newState === 'established' && this._direction === 'outgoing') {
// TODO: remove this
this._state = oldState;
} else {
if (newState === 'terminated') {
- data.reason = message.data.reason;
+ data.reason = message.reason;
this._terminated = true;
this._account._calls.delete(this.id);
this._closeRTCPeerConnection();
}
this.emit('stateChanged', oldState, newState, data);
}
break;
default:
break;
}
}
_initRTCPeerConnection(pcConfig) {
if (this._pc !== null) {
throw new Error('RTCPeerConnection already initialized');
}
this._pc = new RTCPeerConnection(pcConfig);
this._pc.addEventListener('addstream', (event) => {
DEBUG('Stream added');
this.emit('streamAdded', event.stream);
});
this._pc.addEventListener('icecandidate', (event) => {
if (event.candidate !== null) {
DEBUG('New ICE candidate %o', event.candidate);
} else {
DEBUG('ICE candidate gathering finished');
}
this._sendTrickle(event.candidate);
});
}
_sendRequest(req, cb) {
this._account._sendRequest(req, cb);
}
_sendCall(uri, sdp) {
const req = {
sylkrtc: 'session-create',
account: this.account.id,
session: this.id,
uri: uri,
sdp: sdp
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Call error: %s', error);
this._localTerminate(error);
}
});
}
_sendTerminate() {
const req = {
sylkrtc: 'session-terminate',
session: this.id
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Error terminating call: %s', error);
this._localTerminate(error);
}
});
setTimeout(() => {
if (!this._terminated) {
DEBUG('Timeout terminating call');
this._localTerminate('200 OK');
}
this._terminated = true;
}, 150);
}
_sendTrickle(candidate) {
const req = {
sylkrtc: 'session-trickle',
session: this.id,
candidates: candidate !== null ? [candidate] : [],
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Trickle error: %s', error);
this._localTerminate(error);
}
});
}
_sendAnswer(sdp) {
const req = {
sylkrtc: 'session-answer',
session: this.id,
sdp: sdp
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Answer error: %s', error);
this.terminate();
}
});
}
_closeRTCPeerConnection() {
DEBUG('Closing RTCPeerConnection');
if (this._pc !== null) {
for (let stream of this._pc.getLocalStreams()) {
utils.closeMediaStream(stream);
}
for (let stream of this._pc.getRemoteStreams()) {
utils.closeMediaStream(stream);
}
this._pc.close();
this._pc = null;
if (this._dtmfSender !== null) {
this._dtmfSender.removeEventListener('tonechange', this._onDtmf);
this._dtmfSender = null;
}
}
}
_localTerminate(error) {
if (this._terminated) {
return;
}
DEBUG('Local terminate');
this._account._calls.delete(this.id);
this._terminated = true;
const oldState = this._state;
const newState = 'terminated';
const data = {
reason: error.toString()
};
this._closeRTCPeerConnection();
this.emit('stateChanged', oldState, newState, data);
}
_onDtmf(event) {
DEBUG('Sent DTMF tone %s', event.tone);
this.emit('dtmfToneSent', event.tone);
}
}
export { Call };
diff --git a/lib/conference.js b/lib/conference.js
index 7424733..e079873 100644
--- a/lib/conference.js
+++ b/lib/conference.js
@@ -1,642 +1,618 @@
'use strict';
import debug from 'debug';
import uuidv4 from 'uuid/v4';
import utils from './utils';
import { EventEmitter } from 'events';
const DEBUG = debug('sylkrtc:Conference');
class Participant extends EventEmitter {
constructor(publisherId, identity, conference) {
super();
this._id = uuidv4();
this._publisherId = publisherId;
this._identity = identity;
this._conference = conference;
this._state = null;
this._pc = null;
this._videoSubscriptionPaused = false;
this._audioSubscriptionPaused = false;
this._videoPublishingPaused = false;
this._audioPublishingPaused = false;
}
get id() {
return this._id;
}
get publisherId() {
return this._publisherId;
}
get identity() {
return this._identity;
}
get conference() {
return this._conference;
}
get videoPaused() {
return this._videoSubscriptionPaused;
}
get state() {
return this._state;
}
get streams() {
if (this._pc !== null) {
return this._pc.getRemoteStreams();
} else {
return [];
}
}
attach() {
if (this._state !== null) {
return;
}
this._setState('progress');
this._sendAttach();
}
detach() {
if (this._state !== null) {
this._sendDetach();
}
}
pauseVideo() {
this._sendUpdate({video: false});
this._videoSubscriptionPaused = true;
}
resumeVideo() {
this._sendUpdate({video: true});
this._videoSubscriptionPaused = false;
}
_setState(newState) {
const oldState = this._state;
this._state = newState;
DEBUG(`Participant ${this.id} state change: ${oldState} -> ${newState}`);
this.emit('stateChanged', oldState, newState);
}
_handleOffer(offerSdp) {
DEBUG('Handling SDP for participant offer: %s', offerSdp);
// Create the RTCPeerConnection
const pcConfig = this.conference._pcConfig;
const pc = new RTCPeerConnection(pcConfig);
pc.addEventListener('addstream', (event) => {
DEBUG('Stream added');
this.emit('streamAdded', event.stream);
});
pc.addEventListener('icecandidate', (event) => {
if (event.candidate !== null) {
DEBUG('New ICE candidate %o', event.candidate);
} else {
DEBUG('ICE candidate gathering finished');
}
this._sendTrickle(event.candidate);
});
this._pc = pc;
// no need for a local stream since we are only going to receive media here
pc.setRemoteDescription(
new RTCSessionDescription({type: 'offer', sdp: offerSdp}),
// success
() => {
utils.createLocalSdp(pc, 'answer')
.then((sdp) => {
DEBUG('Local SDP: %s', sdp);
this._sendAnswer(sdp);
})
.catch((reason) => {
DEBUG(reason);
this._close();
});
},
// failure
(error) => {
DEBUG('Error setting remote description: %s', error);
this._close();
}
);
}
_sendAttach() {
const req = {
- sylkrtc: 'videoroom-ctl',
+ sylkrtc: 'videoroom-feed-attach',
session: this.conference.id,
- option: 'feed-attach',
- feed_attach: {
- session: this.id,
- publisher: this._publisherId
- }
+ publisher: this._publisherId,
+ feed: this.id
};
DEBUG('Sending request: %o', req);
this.conference._sendRequest(req, (error) => {
if (error) {
DEBUG('Error attaching to participant %s: %s', this._publisherId, error);
}
});
}
_sendDetach() {
const req = {
- sylkrtc: 'videoroom-ctl',
+ sylkrtc: 'videoroom-feed-detach',
session: this.conference.id,
- option: 'feed-detach',
- feed_detach: {
- session: this.id
- }
+ feed: this.id
};
DEBUG('Sending request: %o', req);
this.conference._sendRequest(req, (error) => {
if (error) {
DEBUG('Error detaching to participant %s: %s', this._publisherId, error);
}
this._close();
});
}
_sendTrickle(candidate) {
const req = {
- sylkrtc: 'videoroom-ctl',
- session: this.conference.id,
- option: 'trickle',
- trickle: {
- session: this.id,
- candidates: candidate !== null ? [candidate] : []
- }
+ sylkrtc: 'videoroom-session-trickle',
+ session: this.id,
+ candidates: candidate !== null ? [candidate] : []
};
this.conference._sendRequest(req, (error) => {
if (error) {
DEBUG('Trickle error: %s', error);
this._close();
}
});
}
_sendAnswer(sdp) {
const req = {
- sylkrtc: 'videoroom-ctl',
+ sylkrtc: 'videoroom-feed-answer',
session: this.conference.id,
- option: 'feed-answer',
- feed_answer: {
- session: this.id,
- sdp: sdp
- }
+ feed: this.id,
+ sdp: sdp
};
+ DEBUG('Sending request: %o', req);
this.conference._sendRequest(req, (error) => {
if (error) {
DEBUG('Answer error: %s', error);
this._close();
}
});
}
_sendUpdate(options = {}) {
const req = {
- sylkrtc: 'videoroom-ctl',
+ sylkrtc: 'videoroom-session-update',
session: this.id,
- option: 'update',
- update: {}
+ options: options
};
-
- req.update = Object.assign({}, options);
DEBUG('Sending update participant request %o', req);
this.conference._sendRequest(req, (error) => {
if (error) {
DEBUG('Answer error: %s', error);
}
});
}
_close() {
DEBUG('Closing Participant RTCPeerConnection');
if (this._pc !== null) {
for (let stream of this._pc.getLocalStreams()) {
utils.closeMediaStream(stream);
}
for (let stream of this._pc.getRemoteStreams()) {
utils.closeMediaStream(stream);
}
this._pc.close();
this._pc = null;
this._setState(null);
}
}
}
class ConferenceCall extends EventEmitter {
constructor(account) {
super();
this._account = account;
this._id = null;
this._pc = null;
this._participants = new Map();
this._terminated = false;
this._state = null;
this._localIdentity = new utils.Identity(account.id, account.displayName);
this._remoteIdentity = null;
this._activeParticpants = [];
this._pcConfig = null; // saved on initialize, used later for subscriptions
}
get account() {
return this._account;
}
get id() {
return this._id;
}
get direction() {
// make this object API compatible with `Call`
return 'outgoing';
}
get state() {
return this._state;
}
get localIdentity() {
return this._localIdentity;
}
get remoteIdentity() {
return this._remoteIdentity;
}
get participants() {
return Array.from(new Set(this._participants.values()));
}
get activeParticipants() {
return this._activeParticpants;
}
getLocalStreams() {
if (this._pc !== null) {
return this._pc.getLocalStreams();
} else {
return [];
}
}
getRemoteStreams() {
let streams = [];
for (let participant of new Set(this._participants.values())) {
streams = streams.concat(participant.streams);
}
return streams;
}
scaleLocalTrack(oldTrack, divider) {
DEBUG('Scaling track by %d', divider);
let sender;
for (sender of this._pc.getSenders()) {
if (sender.track === oldTrack) {
DEBUG('Found sender to modify track %o', sender);
break;
}
}
sender.setParameters({encodings: [{scaleResolutionDownBy: divider}]})
.then(() => {
DEBUG("Scale set to %o", divider);
DEBUG('Active encodings %o', sender.getParameters().encodings);
})
.catch((error) => {
DEBUG('Error %o', error)
});
}
configureRoom(ps, cb=null) {
if (!Array.isArray(ps)) {
return;
}
this._sendConfigureRoom(ps, cb);
}
terminate() {
if (this._terminated) {
return;
}
DEBUG('Terminating conference');
this._sendTerminate();
}
inviteParticipants(ps) {
if (this._terminated) {
return;
}
if (!Array.isArray(ps) || ps.length === 0) {
return;
}
DEBUG('Inviting participants: %o', ps);
const req = {
- sylkrtc: 'videoroom-ctl',
+ sylkrtc: 'videoroom-invite',
session: this.id,
- option: 'invite-participants',
- invite_participants: {
- participants: ps
- }
+ participants: ps
};
this._sendRequest(req, null);
}
// Private API
_initialize(uri, options={}) {
if (this._id !== null) {
throw new Error('Already initialized');
}
if (uri.indexOf('@') === -1) {
throw new Error('Invalid URI');
}
if (!options.localStream) {
throw new Error('Missing localStream');
}
this._id = uuidv4();
this._remoteIdentity = new utils.Identity(uri);
options = Object.assign({}, options);
const pcConfig = options.pcConfig || {iceServers:[]};
this._pcConfig = pcConfig;
this._initialParticipants = options.initialParticipants || [];
const offerOptions = options.offerOptions || {};
// only send audio / video through the publisher connection
offerOptions.offerToReceiveAudio = false;
offerOptions.offerToReceiveVideo = false;
delete offerOptions.mandatory;
// Create the RTCPeerConnection
this._pc = new RTCPeerConnection(pcConfig);
this._pc.addEventListener('icecandidate', (event) => {
if (event.candidate !== null) {
DEBUG('New ICE candidate %o', event.candidate);
} else {
DEBUG('ICE candidate gathering finished');
}
this._sendTrickle(event.candidate);
});
this._pc.addStream(options.localStream);
this.emit('localStreamAdded', options.localStream);
DEBUG('Offer options: %o', offerOptions);
utils.createLocalSdp(this._pc, 'offer', offerOptions)
.then((sdp) => {
DEBUG('Local SDP: %s', sdp);
this._sendJoin(sdp);
})
.catch((reason) => {
this._localTerminate(reason);
});
}
_handleEvent(message) {
DEBUG('Conference event: %o', message);
switch (message.event) {
- case 'state':
+ case 'session-state':
const oldState = this._state;
- const newState = message.data.state;
+ const newState = message.state;
this._state = newState;
let data = {};
let participant;
if (newState === 'accepted') {
- let sdp = utils.mungeSdp(message.data.sdp);
+ let sdp = utils.mungeSdp(message.sdp);
DEBUG('Remote SDP: %s', sdp);
this._pc.setRemoteDescription(
new RTCSessionDescription({type: 'answer', sdp: sdp}),
// success
() => {
DEBUG('Conference accepted');
this.emit('stateChanged', oldState, newState, data);
if (this._initialParticipants.length > 0 ) {
setTimeout(() => {
this.inviteParticipants(this._initialParticipants);
}, 50);
}
},
// failure
(error) => {
DEBUG('Error processing conference accept: %s', error);
this.terminate();
}
);
} else {
if (newState === 'terminated') {
- data.reason = message.data.reason;
+ data.reason = message.reason;
this._terminated = true;
this._close();
}
this.emit('stateChanged', oldState, newState, data);
}
break;
- case 'initial_publishers':
+ case 'initial-publishers':
// this comes between 'accepted' and 'established' states
- for (let p of message.data.publishers) {
+ for (let p of message.publishers) {
participant = new Participant(p.id, new utils.Identity(p.uri, p.display_name), this);
this._participants.set(participant.id, participant);
this._participants.set(p.id, participant);
}
break;
- case 'publishers_joined':
- for (let p of message.data.publishers) {
+ case 'publishers-joined':
+ for (let p of message.publishers) {
DEBUG('Participant joined: %o', p);
participant = new Participant(p.id, new utils.Identity(p.uri, p.display_name), this);
this._participants.set(participant.id, participant);
this._participants.set(p.id, participant);
this.emit('participantJoined', participant);
}
break;
- case 'publishers_left':
- for (let pId of message.data.publishers) {
+ case 'publishers-left':
+ for (let pId of message.publishers) {
participant = this._participants.get(pId);
if (participant) {
this._participants.delete(participant.id);
this._participants.delete(pId);
this.emit('participantLeft', participant);
}
}
break;
- case 'feed_attached':
- participant = this._participants.get(message.data.subscription);
+ case 'feed-attached':
+ participant = this._participants.get(message.feed);
if (participant) {
- participant._handleOffer(message.data.sdp);
+ participant._handleOffer(message.sdp);
}
break;
- case 'feed_established':
- participant = this._participants.get(message.data.subscription);
+ case 'feed-established':
+ participant = this._participants.get(message.feed);
if (participant) {
participant._setState('established');
}
break;
- case 'configure-room':
+ case 'configure':
let activeParticipants = [];
let originator;
const mappedOriginator = this._participants.get(message.originator);
if (mappedOriginator) {
originator = mappedOriginator.identity;
} else if (message.originator === this.id) {
originator = this.localIdentity;
} else if (message.originator === 'videoroom'){
originator = message.originator;
}
for (let pId of message.active_participants) {
participant = this._participants.get(pId);
if (participant) {
activeParticipants.push(participant);
} else if (pId === this.id) {
activeParticipants.push({
id: this.id,
publisherId: this.id,
identity: this.localIdentity,
streams: this.getLocalStreams()
})
}
}
this._activeParticpants = activeParticipants;
let roomConfig = {originator: originator, activeParticipants: this._activeParticpants}
this.emit('roomConfigured', roomConfig);
break;
default:
break;
}
}
_sendConfigureRoom(ps, cb = null) {
const req = {
- sylkrtc: 'videoroom-ctl',
+ sylkrtc: 'videoroom-configure',
session: this.id,
- option: 'configure-room',
- configure_room: {
- active_participants: ps
- }
+ active_participants: ps
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Error configuring room: %s', error);
if (cb) {
cb(error);
}
} else {
DEBUG('Configure room send: %o', ps);
}
});
}
_sendJoin(sdp) {
const req = {
sylkrtc: 'videoroom-join',
account: this.account.id,
session: this.id,
uri: this.remoteIdentity.uri,
sdp: sdp
};
DEBUG('Sending request: %o', req);
this._sendRequest(req, (error) => {
if (error) {
this._localTerminate(error);
}
});
}
_sendTerminate() {
const req = {
- sylkrtc: 'videoroom-terminate',
+ sylkrtc: 'videoroom-leave',
session: this.id
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Error terminating conference: %s', error);
this._localTerminate(error);
}
});
setTimeout(() => {
if (!this._terminated) {
DEBUG('Timeout terminating call');
this._localTerminate('');
}
this._terminated = true;
}, 150);
}
_sendTrickle(candidate) {
const req = {
- sylkrtc: 'videoroom-ctl',
+ sylkrtc: 'videoroom-session-trickle',
session: this.id,
- option: 'trickle',
- trickle: {
- candidates: candidate !== null ? [candidate] : []
- }
+ candidates: candidate !== null ? [candidate] : []
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('Trickle error: %s', error);
this._localTerminate(error);
}
});
}
_sendRequest(req, cb) {
this._account._sendRequest(req, cb);
}
_close() {
DEBUG('Closing RTCPeerConnection');
if (this._pc !== null) {
for (let stream of this._pc.getLocalStreams()) {
utils.closeMediaStream(stream);
}
for (let stream of this._pc.getRemoteStreams()) {
utils.closeMediaStream(stream);
}
this._pc.close();
this._pc = null;
}
const participants = this.participants;
this._participants = [];
for (let p of participants) {
p._close();
}
}
_localTerminate(reason) {
if (this._terminated) {
return;
}
DEBUG(`Local terminate, reason: ${reason}`);
this._account._confCalls.delete(this.id);
this._terminated = true;
const oldState = this._state;
const newState = 'terminated';
const data = {
reason: reason.toString()
};
this._close();
this.emit('stateChanged', oldState, newState, data);
}
}
export { ConferenceCall };
diff --git a/lib/connection.js b/lib/connection.js
index 03177b2..de4700c 100644
--- a/lib/connection.js
+++ b/lib/connection.js
@@ -1,323 +1,317 @@
'use strict';
import bowser from 'bowser';
import debug from 'debug';
import uuidv4 from 'uuid/v4';
import { EventEmitter } from 'events';
import { setImmediate } from 'timers';
import { w3cwebsocket as W3CWebSocket } from 'websocket';
import { Account } from './account';
-const SYLKRTC_PROTO = 'sylkRTC-1';
+const SYLKRTC_PROTO = 'sylkRTC-2';
const DEBUG = debug('sylkrtc:Connection');
const MSECS = 1000;
const INITIAL_DELAY = 0.5 * MSECS;
const MAX_DELAY = 64 * MSECS;
// compute a string for our well-known platforms
let platform;
if (bowser.iphone) {
platform = 'iPhone';
} else if (bowser.ipad) {
platform = 'iPad';
} else if (bowser.ipod) {
platform = 'iPod';
} else if (bowser.android) {
platform = 'Android';
} else if (bowser.mac) {
platform = 'macOS';
} else if (bowser.linux) {
platform = 'Linux';
} else if (bowser.windows) {
platform = 'Windows';
} else {
platform = 'Unknown';
}
if (bowser.osversion) {
platform = `${platform} ${bowser.osversion}`;
}
let browser = bowser.name;
if (bowser.version) {
browser = `${browser} ${bowser.version}`;
}
const USER_AGENT = `SylkRTC (${browser} on ${platform})`;
class Connection extends EventEmitter {
constructor(options = {}) {
if (!options.server) {
throw new Error('\"server\" must be specified');
}
super();
this._wsUri = options.server;
this._sock = null;
this._state = null;
this._closed = false;
this._timer = null;
this._delay = INITIAL_DELAY;
this._accounts = new Map();
this._requests = new Map();
}
get state() {
return this._state;
}
close() {
if (this._closed) {
return;
}
this._closed = true;
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
if (this._sock) {
this._sock.close();
this._sock = null;
} else {
setImmediate(() => {
this._setState('closed');
});
}
}
addAccount(options = {}, cb = null) {
if (typeof options.account !== 'string' || typeof options.password !== 'string') {
throw new Error('Invalid options, \"account\" and \"password\" must be supplied');
}
if (this._accounts.has(options.account)) {
throw new Error('Account already added');
}
const acc = new Account(options, this);
// add it early to the set so we don't add it more than once, ever
this._accounts.set(acc.id, acc);
const req = {
sylkrtc: 'account-add',
account: acc.id,
password: acc.password,
display_name: acc.displayName,
user_agent: USER_AGENT
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('add_account error: %s', error);
this._accounts.delete(acc.id);
}
if (cb) {
cb(error, error ? null : acc);
}
});
}
removeAccount(account, cb=null) {
const acc = this._accounts.get(account.id);
if (account !== acc) {
throw new Error('Unknown account');
}
// delete the account from the mapping, regardless of the result
this._accounts.delete(account.id);
const req = {
sylkrtc: 'account-remove',
account: acc.id
};
this._sendRequest(req, (error) => {
if (error) {
DEBUG('remove_account error: %s', error);
}
if (cb) {
cb(error);
}
});
}
reconnect() {
if (this._state === 'disconnected') {
clearTimeout(this._timer);
this._delay = INITIAL_DELAY;
this._timer = setTimeout(() => {
this._connect();
}, this._delay);
}
}
// Private API
_initialize() {
if (this._sock !== null) {
throw new Error('WebSocket already initialized');
}
if (this._timer !== null) {
throw new Error('Initialize is in progress');
}
DEBUG('Initializing');
if (process.browser) {
window.addEventListener('beforeunload', () => {
if (this._sock !== null) {
const noop = function() {};
this._sock.onerror = noop;
this._sock.onmessage = noop;
this._sock.onclose = noop;
this._sock.close();
}
});
}
this._timer = setTimeout(() => {
this._connect();
}, this._delay);
}
_connect() {
DEBUG('WebSocket connecting');
this._setState('connecting');
this._sock = new W3CWebSocket(this._wsUri, SYLKRTC_PROTO);
this._sock.onopen = () => {
DEBUG('WebSocket connection open');
this._onOpen();
};
this._sock.onerror = () => {
DEBUG('WebSocket connection got error');
};
this._sock.onclose = (event) => {
DEBUG('WebSocket connection closed: %d: (reason=\"%s\", clean=%s)', event.code, event.reason, event.wasClean);
this._onClose();
};
this._sock.onmessage = (event) => {
DEBUG('WebSocket received message: %o', event);
this._onMessage(event);
};
}
_sendRequest(req, cb) {
const transaction = uuidv4();
req.transaction = transaction;
if (this._state !== 'ready') {
setImmediate(() => {
cb(new Error('Connection is not ready'));
});
return;
}
this._requests.set(transaction, {req: req, cb: cb});
this._sock.send(JSON.stringify(req));
}
_setState(newState) {
DEBUG('Set state: %s -> %s', this._state, newState);
const oldState = this._state;
this._state = newState;
this.emit('stateChanged', oldState, newState);
}
// WebSocket callbacks
_onOpen() {
clearTimeout(this._timer);
this._timer = null;
this._delay = INITIAL_DELAY;
this._setState('connected');
}
_onClose() {
this._sock = null;
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
// remove all accounts, the server no longer has them anyway
this._accounts.clear();
this._setState('disconnected');
if (!this._closed) {
this._delay = this._delay * 2;
if (this._delay > MAX_DELAY) {
this._delay = INITIAL_DELAY;
}
DEBUG('Retrying connection in %s seconds', this._delay / MSECS);
this._timer = setTimeout(() => {
this._connect();
}, this._delay);
} else {
this._setState('closed');
}
}
_onMessage(event) {
const message = JSON.parse(event.data);
if (typeof message.sylkrtc === 'undefined') {
DEBUG('Unrecognized message received');
return;
}
DEBUG('Received \"%s\" message: %o', message.sylkrtc, message);
- if (message.sylkrtc === 'event') {
+ if (message.sylkrtc === 'ready-event') {
DEBUG('Received event: \"%s\"', message.event);
- switch (message.event) {
- case 'ready':
- this._setState('ready');
- break;
- default:
- break;
- }
- } else if (message.sylkrtc === 'account_event') {
+ this._setState('ready');
+ } else if (message.sylkrtc === 'account-event') {
let acc = this._accounts.get(message.account);
if (!acc) {
DEBUG('Account %s not found', message.account);
return;
}
acc._handleEvent(message);
- } else if (message.sylkrtc === 'session_event') {
+ } else if (message.sylkrtc === 'session-event') {
const sessionId = message.session;
for (let acc of this._accounts.values()) {
let call = acc._calls.get(sessionId);
if (call) {
call._handleEvent(message);
break;
}
}
- } else if (message.sylkrtc === 'videoroom_event') {
+ } else if (message.sylkrtc === 'videoroom-event') {
const confId = message.session;
for (let acc of this._accounts.values()) {
let confCall = acc._confCalls.get(confId);
if (confCall) {
confCall._handleEvent(message);
break;
}
}
} else if (message.sylkrtc === 'ack' || message.sylkrtc === 'error') {
const transaction = message.transaction;
const data = this._requests.get(transaction);
if (!data) {
DEBUG('Could not find transaction %s', transaction);
return;
}
this._requests.delete(transaction);
DEBUG('Received \"%s\" for request: %o', message.sylkrtc, data.req);
if (data.cb) {
if (message.sylkrtc === 'ack') {
data.cb(null);
} else {
data.cb(new Error(message.error));
}
}
}
}
}
export { Connection };

File Metadata

Mime Type
text/x-diff
Expires
Sat, Feb 1, 1:52 PM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3489374
Default Alt Text
(48 KB)

Event Timeline