2019-02-21 05:58:37 +08:00
|
|
|
import browser from 'browser-detect';
|
2017-10-12 22:49:50 +08:00
|
|
|
import BaseAudioBridge from './base';
|
2018-07-12 06:03:56 +08:00
|
|
|
import logger from '/imports/startup/client/logger';
|
2018-07-10 05:29:27 +08:00
|
|
|
import { fetchStunTurnServers } from '/imports/utils/fetchStunTurnServers';
|
2019-06-04 02:54:30 +08:00
|
|
|
import {
|
|
|
|
isUnifiedPlan, toUnifiedPlan, toPlanB, stripMDnsCandidates,
|
|
|
|
} from '/imports/utils/sdpUtils';
|
2017-08-01 04:54:18 +08:00
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
const MEDIA = Meteor.settings.public.media;
|
|
|
|
const MEDIA_TAG = MEDIA.mediaTag;
|
|
|
|
const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout;
|
2017-10-27 01:14:56 +08:00
|
|
|
const CALL_HANGUP_TIMEOUT = MEDIA.callHangupTimeout;
|
|
|
|
const CALL_HANGUP_MAX_RETRIES = MEDIA.callHangupMaximumRetries;
|
2019-02-21 05:58:37 +08:00
|
|
|
const ICE_NEGOTIATION_FAILED = ['iceConnectionFailed'];
|
2019-04-13 06:23:22 +08:00
|
|
|
const CALL_CONNECT_TIMEOUT = 15000;
|
|
|
|
const ICE_NEGOTIATION_TIMEOUT = 20000;
|
2017-10-12 05:04:10 +08:00
|
|
|
|
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-10-18 03:16:42 +08:00
|
|
|
const {
|
|
|
|
userId,
|
|
|
|
username,
|
|
|
|
sessionToken,
|
|
|
|
} = userData;
|
|
|
|
|
|
|
|
this.user = {
|
|
|
|
userId,
|
|
|
|
sessionToken,
|
|
|
|
name: username,
|
|
|
|
};
|
|
|
|
|
|
|
|
this.media = {
|
|
|
|
inputDevice: {},
|
|
|
|
};
|
|
|
|
|
|
|
|
this.protocol = window.document.location.protocol;
|
|
|
|
this.hostname = window.document.location.hostname;
|
2019-05-22 00:48:01 +08:00
|
|
|
|
|
|
|
// SDP conversion utilitary methods to be used inside SIP.js
|
|
|
|
window.isUnifiedPlan = isUnifiedPlan;
|
|
|
|
window.toUnifiedPlan = toUnifiedPlan;
|
|
|
|
window.toPlanB = toPlanB;
|
2019-06-04 02:54:30 +08:00
|
|
|
window.stripMDnsCandidates = stripMDnsCandidates;
|
2017-09-29 21:38:10 +08:00
|
|
|
}
|
2017-07-24 22:15:46 +08:00
|
|
|
|
2019-05-10 05:01:34 +08:00
|
|
|
static parseDTMF(message) {
|
|
|
|
const parse = message.match(/Signal=(.)/);
|
|
|
|
if (parse && parse.length === 2) {
|
|
|
|
return parse[1];
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
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-12 22:49:50 +08:00
|
|
|
const callExtension = extension ? `${extension}${this.userData.voiceBridge}` : 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-12 05:30:38 +08:00
|
|
|
this.callback = callback;
|
|
|
|
|
2017-10-27 01:14:56 +08:00
|
|
|
return this.doCall({ callExtension, isListenOnly, inputStream })
|
2017-11-18 03:01:52 +08:00
|
|
|
.catch((reason) => {
|
|
|
|
reject(reason);
|
|
|
|
});
|
2017-09-30 04:42:34 +08:00
|
|
|
});
|
2017-07-24 22:15:46 +08:00
|
|
|
}
|
|
|
|
|
2017-10-20 18:11:51 +08:00
|
|
|
doCall(options) {
|
|
|
|
const {
|
|
|
|
isListenOnly,
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
const {
|
|
|
|
userId,
|
|
|
|
name,
|
|
|
|
sessionToken,
|
|
|
|
} = this.user;
|
|
|
|
|
|
|
|
const callerIdName = [
|
|
|
|
userId,
|
|
|
|
'bbbID',
|
|
|
|
isListenOnly ? `LISTENONLY-${name}` : name,
|
2019-02-22 04:53:39 +08:00
|
|
|
].join('-').replace(/"/g, "'");
|
2017-10-20 18:11:51 +08:00
|
|
|
|
2019-02-22 04:53:39 +08:00
|
|
|
this.user.callerIdName = callerIdName;
|
2017-10-20 18:11:51 +08:00
|
|
|
this.callOptions = options;
|
|
|
|
|
|
|
|
return fetchStunTurnServers(sessionToken)
|
2017-11-18 03:01:52 +08:00
|
|
|
.then(this.createUserAgent.bind(this))
|
|
|
|
.then(this.inviteUserAgent.bind(this))
|
|
|
|
.then(this.setupEventHandlers.bind(this));
|
2017-10-20 18:11:51 +08:00
|
|
|
}
|
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
transferCall(onTransferSuccess) {
|
2017-10-12 22:49:50 +08:00
|
|
|
return new Promise((resolve, reject) => {
|
2019-03-15 05:02:51 +08:00
|
|
|
const timeout = setInterval(() => {
|
2019-05-10 05:01:34 +08:00
|
|
|
clearInterval(timeout);
|
|
|
|
logger.error({ logCode: 'sip_js_transfer_timed_out' }, 'Timeout on transfering from echo test to conference');
|
|
|
|
this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
|
|
|
error: 1008,
|
|
|
|
bridgeError: 'Timeout on call transfer',
|
|
|
|
});
|
|
|
|
reject(this.baseErrorCodes.REQUEST_TIMEOUT);
|
2017-10-18 03:16:42 +08:00
|
|
|
}, CALL_TRANSFER_TIMEOUT);
|
|
|
|
|
|
|
|
// This is is the call transfer code ask @chadpilkey
|
|
|
|
this.currentSession.dtmf(1);
|
|
|
|
|
2019-05-10 05:01:34 +08:00
|
|
|
this.currentSession.on('dtmf', (event) => {
|
|
|
|
if (event.body && (typeof event.body === 'string')) {
|
|
|
|
const key = SIPBridge.parseDTMF(event.body);
|
|
|
|
if (key === '7') {
|
|
|
|
clearInterval(timeout);
|
|
|
|
onTransferSuccess();
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
}
|
2017-10-12 20:50:23 +08:00
|
|
|
});
|
2017-10-12 22:49:50 +08:00
|
|
|
});
|
2017-10-12 20:50:23 +08:00
|
|
|
}
|
|
|
|
|
2017-09-29 21:38:10 +08:00
|
|
|
exitAudio() {
|
2017-10-27 01:14:56 +08:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let hangupRetries = 0;
|
|
|
|
let hangup = false;
|
2018-07-03 00:36:54 +08:00
|
|
|
const { mediaHandler } = this.currentSession;
|
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
this.userRequestedHangup = true;
|
2018-07-03 00:36:54 +08:00
|
|
|
// Removing termination events to avoid triggering an error
|
2019-02-21 05:58:37 +08:00
|
|
|
ICE_NEGOTIATION_FAILED.forEach(e => mediaHandler.off(e));
|
2017-10-27 01:14:56 +08:00
|
|
|
const tryHangup = () => {
|
2019-02-22 04:49:04 +08:00
|
|
|
if (this.currentSession.endTime) {
|
2019-02-21 05:58:37 +08:00
|
|
|
hangup = true;
|
|
|
|
return resolve();
|
|
|
|
}
|
|
|
|
|
2017-10-27 01:14:56 +08:00
|
|
|
this.currentSession.bye();
|
|
|
|
hangupRetries += 1;
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
if (hangupRetries > CALL_HANGUP_MAX_RETRIES) {
|
|
|
|
this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
2019-02-21 05:58:37 +08:00
|
|
|
error: 1006,
|
2017-10-27 01:14:56 +08:00
|
|
|
bridgeError: 'Timeout on call hangup',
|
|
|
|
});
|
|
|
|
return reject(this.baseErrorCodes.REQUEST_TIMEOUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hangup) return tryHangup();
|
|
|
|
return resolve();
|
|
|
|
}, CALL_HANGUP_TIMEOUT);
|
|
|
|
};
|
|
|
|
|
2017-10-05 04:49:11 +08:00
|
|
|
this.currentSession.on('bye', () => {
|
2017-10-27 01:14:56 +08:00
|
|
|
hangup = true;
|
2017-10-05 04:49:11 +08:00
|
|
|
resolve();
|
|
|
|
});
|
2017-10-27 01:14:56 +08:00
|
|
|
|
|
|
|
return tryHangup();
|
2017-09-30 04:42:34 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
createUserAgent({ stun, turn }) {
|
2017-09-30 04:42:34 +08:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-10-18 03:16:42 +08:00
|
|
|
const {
|
|
|
|
hostname,
|
|
|
|
protocol,
|
|
|
|
} = this;
|
|
|
|
|
|
|
|
const {
|
|
|
|
callerIdName,
|
|
|
|
} = this.user;
|
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
let userAgentConnected = false;
|
|
|
|
|
2019-05-22 00:48:01 +08:00
|
|
|
// WebView safari needs a transceiver to be added. Made it a SIP.js hack.
|
|
|
|
// Don't like the UA picking though, we should straighten everything to user
|
|
|
|
// transceivers - prlanzarin 2019/05/21
|
|
|
|
const browserUA = window.navigator.userAgent.toLocaleLowerCase();
|
|
|
|
const isSafariWebview = ((browserUA.indexOf('iphone') > -1
|
2019-06-04 02:54:30 +08:00
|
|
|
|| browserUA.indexOf('ipad') > -1) && browserUA.indexOf('safari') === -1);
|
2019-05-22 00:48:01 +08:00
|
|
|
|
|
|
|
// Second UA check to get all Safari browsers to enable Unified Plan <-> PlanB
|
|
|
|
// translation
|
2019-05-22 00:56:57 +08:00
|
|
|
const isSafari = browser().name === 'safari';
|
2019-05-22 00:48:01 +08:00
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
logger.debug('Creating the user agent');
|
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
let userAgent = new window.SIP.UA({
|
|
|
|
uri: `sip:${encodeURIComponent(callerIdName)}@${hostname}`,
|
|
|
|
wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${hostname}/ws`,
|
|
|
|
displayName: callerIdName,
|
2017-09-30 04:42:34 +08:00
|
|
|
register: false,
|
|
|
|
traceSip: true,
|
|
|
|
autostart: false,
|
|
|
|
userAgentString: 'BigBlueButton',
|
|
|
|
stunServers: stun,
|
|
|
|
turnServers: turn,
|
2019-05-22 00:48:01 +08:00
|
|
|
hackPlanBUnifiedPlanTranslation: isSafari,
|
|
|
|
hackAddAudioTransceiver: isSafariWebview,
|
2017-09-30 04:42:34 +08:00
|
|
|
});
|
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
userAgent.removeAllListeners('connected');
|
|
|
|
userAgent.removeAllListeners('disconnected');
|
|
|
|
|
|
|
|
const handleUserAgentConnection = () => {
|
2019-02-21 05:58:37 +08:00
|
|
|
userAgentConnected = true;
|
2017-10-18 03:16:42 +08:00
|
|
|
resolve(userAgent);
|
|
|
|
};
|
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
const handleUserAgentDisconnection = () => {
|
2017-10-18 03:16:42 +08:00
|
|
|
userAgent.stop();
|
|
|
|
userAgent = null;
|
2019-02-21 05:58:37 +08:00
|
|
|
|
|
|
|
let error;
|
|
|
|
let bridgeError;
|
2019-02-22 04:49:04 +08:00
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
if (userAgentConnected) {
|
|
|
|
error = 1001;
|
|
|
|
bridgeError = 'Websocket disconnected';
|
|
|
|
} else {
|
|
|
|
error = 1002;
|
|
|
|
bridgeError = 'Websocket failed to connect';
|
|
|
|
}
|
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
2018-06-27 21:56:03 +08:00
|
|
|
error,
|
2019-02-21 05:58:37 +08:00
|
|
|
bridgeError,
|
2017-11-18 03:01:52 +08:00
|
|
|
});
|
2017-10-20 18:11:51 +08:00
|
|
|
reject(this.baseErrorCodes.CONNECTION_ERROR);
|
2017-10-18 03:16:42 +08:00
|
|
|
};
|
2017-09-30 04:42:34 +08:00
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
userAgent.on('connected', handleUserAgentConnection);
|
2018-07-10 05:29:27 +08:00
|
|
|
userAgent.on('disconnected', handleUserAgentDisconnection);
|
2017-09-30 04:42:34 +08:00
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
userAgent.start();
|
2017-09-30 04:42:34 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
inviteUserAgent(userAgent) {
|
|
|
|
const {
|
|
|
|
hostname,
|
|
|
|
} = this;
|
|
|
|
|
|
|
|
const {
|
|
|
|
inputStream,
|
|
|
|
callExtension,
|
|
|
|
} = this.callOptions;
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
media: {
|
|
|
|
stream: inputStream,
|
|
|
|
constraints: {
|
|
|
|
audio: true,
|
|
|
|
video: false,
|
|
|
|
},
|
|
|
|
render: {
|
|
|
|
remote: document.querySelector(MEDIA_TAG),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RTCConstraints: {
|
2018-12-22 01:14:05 +08:00
|
|
|
offerToReceiveAudio: true,
|
|
|
|
offerToReceiveVideo: false,
|
2017-10-18 03:16:42 +08:00
|
|
|
},
|
|
|
|
};
|
2017-10-11 02:03:29 +08:00
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
return userAgent.invite(`sip:${callExtension}@${hostname}`, options);
|
2017-09-30 04:42:34 +08:00
|
|
|
}
|
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
setupEventHandlers(currentSession) {
|
2017-10-13 03:22:10 +08:00
|
|
|
return new Promise((resolve) => {
|
2018-04-04 00:49:45 +08:00
|
|
|
const { mediaHandler } = currentSession;
|
2017-10-27 01:14:56 +08:00
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
this.connectionCompleted = false;
|
2019-05-10 05:01:34 +08:00
|
|
|
this.inEcho = false;
|
2019-02-21 05:58:37 +08:00
|
|
|
|
2019-01-30 08:11:20 +08:00
|
|
|
let connectionCompletedEvents = ['iceConnectionCompleted', 'iceConnectionConnected'];
|
|
|
|
// Edge sends a connected first and then a completed, but the call isn't ready until
|
|
|
|
// the completed comes in. Due to the way that we have the listeners set up, the only
|
|
|
|
// way to ignore one status is to not listen for it.
|
|
|
|
if (browser().name === 'edge') {
|
2019-02-21 05:58:37 +08:00
|
|
|
connectionCompletedEvents = ['iceConnectionCompleted'];
|
2019-01-30 08:11:20 +08:00
|
|
|
}
|
|
|
|
|
2019-05-10 05:01:34 +08:00
|
|
|
const checkIfCallReady = () => {
|
|
|
|
if (this.connectionCompleted && this.inEcho) {
|
|
|
|
this.callback({ status: this.baseCallStates.started });
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-02-22 04:49:04 +08:00
|
|
|
// Sometimes FreeSWITCH just won't respond with anything and hangs. This timeout is to
|
2019-02-21 05:58:37 +08:00
|
|
|
// avoid that issue
|
|
|
|
const callTimeout = setTimeout(() => {
|
|
|
|
this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
|
|
|
error: 1006,
|
2019-02-22 04:49:04 +08:00
|
|
|
bridgeError: `Call timed out on start after ${CALL_CONNECT_TIMEOUT / 1000}s`,
|
2019-02-21 05:58:37 +08:00
|
|
|
});
|
|
|
|
}, CALL_CONNECT_TIMEOUT);
|
|
|
|
|
|
|
|
let iceNegotiationTimeout;
|
|
|
|
|
2019-02-01 07:15:29 +08:00
|
|
|
const handleSessionAccepted = () => {
|
2019-02-21 05:58:37 +08:00
|
|
|
logger.info({ logCode: 'sip_js_session_accepted' }, 'Audio call session accepted');
|
|
|
|
clearTimeout(callTimeout);
|
|
|
|
|
|
|
|
// If ICE isn't connected yet then start timeout waiting for ICE to finish
|
|
|
|
if (!this.connectionCompleted) {
|
|
|
|
iceNegotiationTimeout = setTimeout(() => {
|
|
|
|
this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
|
|
|
error: 1010,
|
2019-02-22 04:49:04 +08:00
|
|
|
bridgeError: `ICE negotiation timeout after ${ICE_NEGOTIATION_TIMEOUT / 1000}s`,
|
2019-02-21 05:58:37 +08:00
|
|
|
});
|
|
|
|
}, ICE_NEGOTIATION_TIMEOUT);
|
|
|
|
}
|
2019-02-01 07:15:29 +08:00
|
|
|
};
|
|
|
|
currentSession.on('accepted', handleSessionAccepted);
|
|
|
|
|
2019-03-15 05:02:51 +08:00
|
|
|
const handleSessionProgress = (update) => {
|
|
|
|
logger.info({ logCode: 'sip_js_session_progress' }, 'Audio call session progress update');
|
|
|
|
clearTimeout(callTimeout);
|
|
|
|
currentSession.off('progress', handleSessionProgress);
|
|
|
|
};
|
|
|
|
currentSession.on('progress', handleSessionProgress);
|
|
|
|
|
2019-02-01 07:15:29 +08:00
|
|
|
const handleConnectionCompleted = (peer) => {
|
2019-02-21 05:58:37 +08:00
|
|
|
logger.info({ logCode: 'sip_js_ice_connection_success' }, `ICE connection success. Current state - ${peer.iceConnectionState}`);
|
|
|
|
clearTimeout(callTimeout);
|
|
|
|
clearTimeout(iceNegotiationTimeout);
|
2018-04-04 00:49:45 +08:00
|
|
|
connectionCompletedEvents.forEach(e => mediaHandler.off(e, handleConnectionCompleted));
|
2019-02-21 05:58:37 +08:00
|
|
|
this.connectionCompleted = true;
|
2019-05-10 05:01:34 +08:00
|
|
|
|
|
|
|
checkIfCallReady();
|
2017-10-18 03:16:42 +08:00
|
|
|
};
|
2018-04-04 00:49:45 +08:00
|
|
|
connectionCompletedEvents.forEach(e => mediaHandler.on(e, handleConnectionCompleted));
|
2017-10-18 03:16:42 +08:00
|
|
|
|
|
|
|
const handleSessionTerminated = (message, cause) => {
|
2019-02-21 05:58:37 +08:00
|
|
|
clearTimeout(callTimeout);
|
|
|
|
clearTimeout(iceNegotiationTimeout);
|
|
|
|
if (!message && !cause && !!this.userRequestedHangup) {
|
2017-10-18 03:16:42 +08:00
|
|
|
return this.callback({
|
|
|
|
status: this.baseCallStates.ended,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
logger.error({ logCode: 'sip_js_call_terminated' }, `Audio call terminated. cause=${cause}`);
|
2019-02-01 07:15:29 +08:00
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
let mappedCause;
|
|
|
|
if (!this.connectionCompleted) {
|
|
|
|
mappedCause = '1004';
|
|
|
|
} else {
|
|
|
|
mappedCause = '1005';
|
|
|
|
}
|
2017-10-20 18:11:51 +08:00
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
return this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
|
|
|
error: mappedCause,
|
|
|
|
bridgeError: cause,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
currentSession.on('terminated', handleSessionTerminated);
|
2017-10-10 04:48:10 +08:00
|
|
|
|
2019-02-21 05:58:37 +08:00
|
|
|
const handleIceNegotiationFailed = (peer) => {
|
2019-05-10 05:01:34 +08:00
|
|
|
if (this.connectionCompleted) {
|
|
|
|
logger.error({ logCode: 'sipjs_ice_failed_after' }, 'ICE connection failed after success');
|
|
|
|
} else {
|
|
|
|
logger.error({ logCode: 'sipjs_ice_failed_before' }, 'ICE connection failed before success');
|
|
|
|
}
|
2019-02-21 05:58:37 +08:00
|
|
|
clearTimeout(callTimeout);
|
|
|
|
clearTimeout(iceNegotiationTimeout);
|
|
|
|
ICE_NEGOTIATION_FAILED.forEach(e => mediaHandler.off(e, handleIceNegotiationFailed));
|
|
|
|
this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
|
|
|
error: 1007,
|
|
|
|
bridgeError: `ICE negotiation failed. Current state - ${peer.iceConnectionState}`,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
ICE_NEGOTIATION_FAILED.forEach(e => mediaHandler.on(e, handleIceNegotiationFailed));
|
|
|
|
|
|
|
|
const handleIceConnectionTerminated = (peer) => {
|
|
|
|
['iceConnectionClosed'].forEach(e => mediaHandler.off(e, handleIceConnectionTerminated));
|
2019-02-22 04:53:39 +08:00
|
|
|
if (!this.userRequestedHangup) {
|
|
|
|
logger.error({ logCode: 'sipjs_ice_closed' }, 'ICE connection closed');
|
|
|
|
}
|
2019-02-21 05:58:37 +08:00
|
|
|
/*
|
2018-04-04 00:49:45 +08:00
|
|
|
this.callback({
|
|
|
|
status: this.baseCallStates.failed,
|
2019-02-21 05:58:37 +08:00
|
|
|
error: 1012,
|
|
|
|
bridgeError: "ICE connection closed. Current state - " + peer.iceConnectionState,
|
2018-04-04 00:49:45 +08:00
|
|
|
});
|
2019-02-21 05:58:37 +08:00
|
|
|
*/
|
2018-04-04 00:49:45 +08:00
|
|
|
};
|
2019-02-21 05:58:37 +08:00
|
|
|
['iceConnectionClosed'].forEach(e => mediaHandler.on(e, handleIceConnectionTerminated));
|
2018-04-03 21:50:18 +08:00
|
|
|
|
2019-05-10 05:01:34 +08:00
|
|
|
const inEchoDTMF = (event) => {
|
|
|
|
if (event.body && typeof event.body === 'string') {
|
|
|
|
const dtmf = SIPBridge.parseDTMF(event.body);
|
|
|
|
if (dtmf === '0') {
|
|
|
|
this.inEcho = true;
|
|
|
|
checkIfCallReady();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currentSession.off('dtmf', inEchoDTMF);
|
|
|
|
};
|
|
|
|
currentSession.on('dtmf', inEchoDTMF);
|
|
|
|
|
2017-10-05 04:49:11 +08:00
|
|
|
this.currentSession = currentSession;
|
2017-10-12 05:04:10 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-02 20:10:01 +08:00
|
|
|
setDefaultInputDevice() {
|
|
|
|
const handleMediaSuccess = (mediaStream) => {
|
|
|
|
const deviceLabel = mediaStream.getAudioTracks()[0].label;
|
2018-04-18 22:15:17 +08:00
|
|
|
window.defaultInputStream = mediaStream.getTracks();
|
2017-11-02 20:10:01 +08:00
|
|
|
return navigator.mediaDevices.enumerateDevices().then((mediaDevices) => {
|
|
|
|
const device = mediaDevices.find(d => d.label === deviceLabel);
|
|
|
|
return this.changeInputDevice(device.deviceId);
|
|
|
|
});
|
2017-11-18 03:01:52 +08:00
|
|
|
};
|
2017-10-27 01:14:56 +08:00
|
|
|
|
2017-11-02 20:10:01 +08:00
|
|
|
return navigator.mediaDevices.getUserMedia({ audio: true }).then(handleMediaSuccess);
|
2017-10-27 01:14:56 +08:00
|
|
|
}
|
|
|
|
|
2017-11-02 20:10:01 +08:00
|
|
|
changeInputDevice(value) {
|
2017-10-18 03:16:42 +08:00
|
|
|
const {
|
|
|
|
media,
|
|
|
|
} = this;
|
|
|
|
|
|
|
|
if (media.inputDevice.audioContext) {
|
2017-11-02 20:10:01 +08:00
|
|
|
const handleAudioContextCloseSuccess = () => {
|
2017-10-18 03:16:42 +08:00
|
|
|
media.inputDevice.audioContext = null;
|
|
|
|
media.inputDevice.scriptProcessor = null;
|
|
|
|
media.inputDevice.source = null;
|
|
|
|
return this.changeInputDevice(value);
|
2017-11-18 03:01:52 +08:00
|
|
|
};
|
2017-11-02 20:10:01 +08:00
|
|
|
|
|
|
|
return media.inputDevice.audioContext.close().then(handleAudioContextCloseSuccess);
|
2017-10-18 03:16:42 +08:00
|
|
|
}
|
2017-10-12 05:04:10 +08:00
|
|
|
|
2017-10-18 03:16:42 +08:00
|
|
|
if ('AudioContext' in window) {
|
|
|
|
media.inputDevice.audioContext = new window.AudioContext();
|
|
|
|
} else {
|
|
|
|
media.inputDevice.audioContext = new window.webkitAudioContext();
|
2017-10-12 05:04:10 +08:00
|
|
|
}
|
2017-11-02 20:10:01 +08:00
|
|
|
|
|
|
|
media.inputDevice.id = value;
|
2017-10-18 03:16:42 +08:00
|
|
|
media.inputDevice.scriptProcessor = media.inputDevice.audioContext
|
2017-11-18 03:01:52 +08:00
|
|
|
.createScriptProcessor(2048, 1, 1);
|
2017-10-18 03:16:42 +08:00
|
|
|
media.inputDevice.source = null;
|
|
|
|
|
|
|
|
const constraints = {
|
|
|
|
audio: {
|
|
|
|
deviceId: value,
|
|
|
|
},
|
|
|
|
};
|
2017-10-12 05:04:10 +08:00
|
|
|
|
2017-11-02 20:10:01 +08:00
|
|
|
const handleMediaSuccess = (mediaStream) => {
|
|
|
|
media.inputDevice.stream = mediaStream;
|
2017-11-18 03:01:52 +08:00
|
|
|
media.inputDevice.source = media.inputDevice.audioContext
|
|
|
|
.createMediaStreamSource(mediaStream);
|
2017-11-02 20:10:01 +08:00
|
|
|
media.inputDevice.source.connect(media.inputDevice.scriptProcessor);
|
|
|
|
media.inputDevice.scriptProcessor.connect(media.inputDevice.audioContext.destination);
|
|
|
|
|
|
|
|
return this.media.inputDevice;
|
2017-11-18 03:01:52 +08:00
|
|
|
};
|
2017-10-18 03:16:42 +08:00
|
|
|
|
2017-11-02 20:10:01 +08:00
|
|
|
return navigator.mediaDevices.getUserMedia(constraints).then(handleMediaSuccess);
|
2017-10-18 03:16:42 +08:00
|
|
|
}
|
|
|
|
|
2017-10-27 01:14:56 +08:00
|
|
|
async changeOutputDevice(value) {
|
2017-10-18 03:16:42 +08:00
|
|
|
const audioContext = document.querySelector(MEDIA_TAG);
|
|
|
|
|
|
|
|
if (audioContext.setSinkId) {
|
2017-10-27 01:14:56 +08:00
|
|
|
try {
|
2018-10-25 04:26:20 +08:00
|
|
|
audioContext.srcObject = null;
|
2017-10-27 01:14:56 +08:00
|
|
|
await audioContext.setSinkId(value);
|
|
|
|
this.media.outputDeviceId = value;
|
|
|
|
} catch (err) {
|
2019-02-02 03:12:06 +08:00
|
|
|
logger.error({ logCode: 'audio_sip_changeoutputdevice_error' }, err);
|
2017-10-27 01:14:56 +08:00
|
|
|
throw new Error(this.baseErrorCodes.MEDIA_ERROR);
|
|
|
|
}
|
2017-10-18 03:16:42 +08:00
|
|
|
}
|
2017-10-19 03:40:01 +08:00
|
|
|
|
2017-11-17 19:52:48 +08:00
|
|
|
return this.media.outputDeviceId || value;
|
2017-07-24 22:15:46 +08:00
|
|
|
}
|
|
|
|
}
|