2017-08-01 04:54:18 +08:00
|
|
|
import BaseAudioBridge from './base';
|
|
|
|
|
2017-10-12 05:04:10 +08:00
|
|
|
const STUN_TURN_FETCH_URL = Meteor.settings.public.media.stunTurnServersFetchAddress;
|
|
|
|
const MEDIA_TAG = Meteor.settings.public.media.mediaTag;
|
|
|
|
|
|
|
|
const handleStunTurnResponse = ({ result, stunServers, turnServers }) =>
|
|
|
|
new Promise((resolve) => {
|
|
|
|
if (result) {
|
|
|
|
resolve({ error: 404, stun: [], turn: [] });
|
|
|
|
}
|
|
|
|
resolve({
|
|
|
|
stun: stunServers.map(server => server.url),
|
|
|
|
turn: turnServers.map(server => server.url),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const fetchStunTurnServers = sessionToken =>
|
|
|
|
new Promise(async (resolve, reject) => {
|
|
|
|
console.log('FETCHSTUNTURN');
|
|
|
|
const url = `${STUN_TURN_FETCH_URL}?sessionToken=${sessionToken}`;
|
|
|
|
|
|
|
|
const response = await fetch(url)
|
|
|
|
.then(res => res.json())
|
|
|
|
.then(json => handleStunTurnResponse(json));
|
|
|
|
|
|
|
|
if (response.error) return reject('Could not fetch the stuns/turns servers!');
|
|
|
|
return resolve(response);
|
|
|
|
});
|
|
|
|
|
|
|
|
const inviteUserAgent = (voiceBridge, server, userAgent, inputStream) => {
|
|
|
|
console.log('INVITEUSERAGENT');
|
|
|
|
const options = {
|
|
|
|
media: {
|
|
|
|
stream: inputStream,
|
|
|
|
constraints: {
|
|
|
|
audio: true,
|
|
|
|
video: false,
|
|
|
|
},
|
|
|
|
render: {
|
|
|
|
remote: document.querySelector(MEDIA_TAG),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RTCConstraints: {
|
|
|
|
mandatory: {
|
|
|
|
OfferToReceiveAudio: true,
|
|
|
|
OfferToReceiveVideo: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
console.log(voiceBridge, server, userAgent);
|
|
|
|
return userAgent.invite(`sip:${voiceBridge}@${server}`, options);
|
|
|
|
};
|
|
|
|
|
2017-07-24 22:15:46 +08:00
|
|
|
export default class SIPBridge extends BaseAudioBridge {
|
|
|
|
constructor(userData) {
|
2017-10-12 05:04:10 +08:00
|
|
|
super(userData);
|
2017-09-30 04:42:34 +08:00
|
|
|
this.isConnected = false;
|
2017-10-12 05:04:10 +08:00
|
|
|
|
|
|
|
this.errorCodes = {
|
|
|
|
'Request Timeout': this.baseErrorCodes.REQUEST_TIMEOUT,
|
|
|
|
'Invalid Target': this.baseErrorCodes.INVALID_TARGET,
|
|
|
|
'Connection Error': this.baseErrorCodes.CONNECTION_ERROR,
|
|
|
|
};
|
2017-09-29 21:38:10 +08:00
|
|
|
}
|
2017-07-24 22:15:46 +08:00
|
|
|
|
2017-09-30 04:42:34 +08:00
|
|
|
joinAudio({ isListenOnly, extension, inputStream }, managerCallback) {
|
2017-10-05 04:49:11 +08:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-10-11 02:03:29 +08:00
|
|
|
const callExtension = extension || this.userData.voiceBridge;
|
2017-07-24 22:15:46 +08:00
|
|
|
|
2017-09-29 21:38:10 +08:00
|
|
|
const callback = (message) => {
|
2017-10-12 05:04:10 +08:00
|
|
|
managerCallback(message).then(resolve);
|
2017-09-30 04:42:34 +08:00
|
|
|
};
|
2017-07-24 22:15:46 +08:00
|
|
|
|
2017-10-11 02:03:29 +08:00
|
|
|
return this.doCall({ callExtension, isListenOnly, inputStream }, callback)
|
|
|
|
.catch((reason) => {
|
2017-10-12 05:04:10 +08:00
|
|
|
callback({ status: this.baseCallStates.failed, error: reason });
|
2017-10-05 04:49:11 +08:00
|
|
|
reject(reason);
|
|
|
|
});
|
2017-09-30 04:42:34 +08:00
|
|
|
});
|
2017-07-24 22:15:46 +08:00
|
|
|
}
|
|
|
|
|
2017-09-29 21:38:10 +08:00
|
|
|
exitAudio() {
|
|
|
|
return new Promise((resolve) => {
|
2017-10-05 04:49:11 +08:00
|
|
|
this.currentSession.on('bye', () => {
|
|
|
|
this.hangup = true;
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
this.currentSession.bye();
|
2017-09-30 04:42:34 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-11 02:03:29 +08:00
|
|
|
doCall({ isListenOnly, callExtension, inputStream }, callback) {
|
2017-09-30 04:42:34 +08:00
|
|
|
const {
|
|
|
|
userId,
|
|
|
|
username,
|
|
|
|
sessionToken,
|
|
|
|
} = this.userData;
|
|
|
|
|
|
|
|
const server = window.document.location.hostname;
|
|
|
|
const callerIdName = `${userId}-bbbID-${username}`;
|
|
|
|
|
2017-10-12 05:04:10 +08:00
|
|
|
return fetchStunTurnServers(sessionToken)
|
2017-10-11 02:03:29 +08:00
|
|
|
.then(stunTurnServers =>
|
|
|
|
this.createUserAgent(server, callerIdName, stunTurnServers))
|
|
|
|
.then(userAgent =>
|
2017-10-12 05:04:10 +08:00
|
|
|
inviteUserAgent(callExtension, server, userAgent, inputStream))
|
2017-10-11 02:03:29 +08:00
|
|
|
.then(currentSession =>
|
|
|
|
this.setupEventHandlers(currentSession, callback));
|
2017-09-30 04:42:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
createUserAgent(server, username, { stun, turn }) {
|
|
|
|
console.log('CREATEUSERAGENT');
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const protocol = document.location.protocol;
|
2017-10-11 02:03:29 +08:00
|
|
|
console.log('username', username);
|
2017-09-30 04:42:34 +08:00
|
|
|
this.userAgent = new window.SIP.UA({
|
|
|
|
uri: `sip:${encodeURIComponent(username)}@${server}`,
|
2017-10-12 05:04:10 +08:00
|
|
|
wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${server}/ws`,
|
2017-09-30 04:42:34 +08:00
|
|
|
displayName: username,
|
|
|
|
register: false,
|
|
|
|
traceSip: true,
|
|
|
|
autostart: false,
|
|
|
|
userAgentString: 'BigBlueButton',
|
|
|
|
stunServers: stun,
|
|
|
|
turnServers: turn,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.userAgent.removeAllListeners('connected');
|
|
|
|
this.userAgent.removeAllListeners('disconnected');
|
|
|
|
|
2017-10-12 05:04:10 +08:00
|
|
|
this.userAgent.on('connected', () => this.handleUserAgentConnection(resolve));
|
|
|
|
this.userAgent.on('disconnected', () => this.handleUserAgentDisconnection(reject));
|
2017-09-30 04:42:34 +08:00
|
|
|
|
|
|
|
this.userAgent.start();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-12 05:04:10 +08:00
|
|
|
handleUserAgentConnection(resolve) {
|
|
|
|
console.log('CONNECTED');
|
|
|
|
this.isConnected = true;
|
|
|
|
resolve(this.userAgent);
|
|
|
|
}
|
2017-10-11 02:03:29 +08:00
|
|
|
|
2017-10-12 05:04:10 +08:00
|
|
|
handleUserAgentDisconnection(reject) {
|
|
|
|
this.userAgent.stop();
|
|
|
|
this.userAgent = null;
|
2017-10-12 05:22:36 +08:00
|
|
|
this.callback({ status: this.baseCallStates.failed,
|
|
|
|
error: this.baseErrorCodes.GENERIC_ERROR,
|
|
|
|
bridgeError: 'User Agent' });
|
2017-10-12 05:04:10 +08:00
|
|
|
console.log('DISCONNECTED');
|
|
|
|
reject('CONNECTION_ERROR');
|
2017-09-30 04:42:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
setupEventHandlers(currentSession, callback) {
|
2017-10-12 05:04:10 +08:00
|
|
|
return new Promise(() => {
|
2017-09-30 04:42:34 +08:00
|
|
|
console.log('SETUPEVENTHANDLERS');
|
|
|
|
|
2017-10-12 05:04:10 +08:00
|
|
|
currentSession.on('terminated', (message, cause) => this.handleSessionTerminated(message, cause, callback));
|
2017-10-05 04:49:11 +08:00
|
|
|
|
2017-10-12 05:04:10 +08:00
|
|
|
currentSession.mediaHandler.on('iceConnectionCompleted', () => this.handleConnectionCompleted(callback));
|
|
|
|
currentSession.mediaHandler.on('iceConnectionConnected', () => this.handleConnectionCompleted(callback));
|
2017-10-10 04:48:10 +08:00
|
|
|
|
2017-10-05 04:49:11 +08:00
|
|
|
this.currentSession = currentSession;
|
2017-10-12 05:04:10 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
handleConnectionCompleted(callback) {
|
|
|
|
console.log('handleConnectionCompleted');
|
|
|
|
this.hangup = false;
|
|
|
|
callback({ status: this.baseCallStates.started });
|
|
|
|
Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleSessionTerminated(message, cause, callback) {
|
|
|
|
console.log('TERMINATED', message, cause);
|
|
|
|
if (!message && !cause) {
|
|
|
|
return callback({ status: this.baseCallStates.ended });
|
|
|
|
}
|
|
|
|
|
|
|
|
const mappedCause = cause in this.errorCodes ?
|
|
|
|
this.errorCodes[cause] :
|
|
|
|
this.baseErrorCodes.GENERIC_ERROR;
|
|
|
|
|
|
|
|
return callback({ status: this.baseCallStates.failed, error: mappedCause, bridgeError: cause });
|
2017-07-24 22:15:46 +08:00
|
|
|
}
|
|
|
|
}
|