Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F7313447
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
48 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rSYLKRTC SylkRTC.js
Attached
Detach File
Event Timeline
Log In to Comment