watch for voice call state updates instead of DTMFs in the client

This commit is contained in:
Chad Pilkey 2020-02-18 14:03:06 -08:00
parent 4b5c6c65e9
commit 6a4ba7a300
11 changed files with 154 additions and 31 deletions

View File

@ -10,6 +10,10 @@ import {
analyzeSdp, analyzeSdp,
logSelectedCandidate, logSelectedCandidate,
} from '/imports/utils/sdpUtils'; } from '/imports/utils/sdpUtils';
import { Tracker } from 'meteor/tracker';
import VoiceCallStates from '/imports/api/voice-call-states';
import CallStateOptions from '/imports/api/voice-call-states/utils/callStates';
import Auth from '/imports/ui/services/auth';
const MEDIA = Meteor.settings.public.media; const MEDIA = Meteor.settings.public.media;
const MEDIA_TAG = MEDIA.mediaTag; const MEDIA_TAG = MEDIA.mediaTag;
@ -47,14 +51,6 @@ class SIPSession {
this.reconnectAttempt = reconnectAttempt; this.reconnectAttempt = reconnectAttempt;
} }
static parseDTMF(message) {
const parse = message.match(/Signal=(.)/);
if (parse && parse.length === 2) {
return parse[1];
}
return '';
}
joinAudio({ isListenOnly, extension, inputStream }, managerCallback) { joinAudio({ isListenOnly, extension, inputStream }, managerCallback) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const callExtension = extension ? `${extension}${this.userData.voiceBridge}` : this.userData.voiceBridge; const callExtension = extension ? `${extension}${this.userData.voiceBridge}` : this.userData.voiceBridge;
@ -119,8 +115,10 @@ class SIPSession {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.inEchoTest = false; this.inEchoTest = false;
const timeout = setInterval(() => { let trackerControl = null;
clearInterval(timeout);
const timeout = setTimeout(() => {
trackerControl.stop();
logger.error({ logCode: 'sip_js_transfer_timed_out' }, 'Timeout on transferring from echo test to conference'); logger.error({ logCode: 'sip_js_transfer_timed_out' }, 'Timeout on transferring from echo test to conference');
this.callback({ this.callback({
status: this.baseCallStates.failed, status: this.baseCallStates.failed,
@ -136,15 +134,22 @@ class SIPSession {
// This is is the call transfer code ask @chadpilkey // This is is the call transfer code ask @chadpilkey
this.currentSession.dtmf(1); this.currentSession.dtmf(1);
this.currentSession.on('dtmf', (event) => { Tracker.autorun((c) => {
if (event.body && (typeof event.body === 'string')) { trackerControl = c;
const key = SIPSession.parseDTMF(event.body); const selector = { meetingId: Auth.meetingID, userId: Auth.userID };
if (key === '7') { const query = VoiceCallStates.find(selector);
clearInterval(timeout);
onTransferSuccess(); query.observeChanges({
resolve(); changed: (id, fields) => {
} if (fields.callState === CallStateOptions.IN_CONFERENCE) {
} clearTimeout(timeout);
onTransferSuccess();
c.stop();
resolve();
}
},
});
}); });
}); });
} }
@ -491,17 +496,21 @@ class SIPSession {
}; };
['iceConnectionClosed'].forEach(e => mediaHandler.on(e, handleIceConnectionTerminated)); ['iceConnectionClosed'].forEach(e => mediaHandler.on(e, handleIceConnectionTerminated));
const inEchoDTMF = (event) => { Tracker.autorun((c) => {
if (event.body && typeof event.body === 'string') { const selector = { meetingId: Auth.meetingID, userId: Auth.userID };
const dtmf = SIPSession.parseDTMF(event.body); const query = VoiceCallStates.find(selector);
if (dtmf === '0') {
fsReady = true; query.observeChanges({
checkIfCallReady(); changed: (id, fields) => {
} if (fields.callState === CallStateOptions.IN_ECHO_TEST) {
} fsReady = true;
currentSession.off('dtmf', inEchoDTMF); checkIfCallReady();
};
currentSession.on('dtmf', inEchoDTMF); c.stop();
}
},
});
});
}); });
} }
} }

View File

@ -19,6 +19,7 @@ import clearNote from '/imports/api/note/server/modifiers/clearNote';
import clearNetworkInformation from '/imports/api/network-information/server/modifiers/clearNetworkInformation'; import clearNetworkInformation from '/imports/api/network-information/server/modifiers/clearNetworkInformation';
import clearLocalSettings from '/imports/api/local-settings/server/modifiers/clearLocalSettings'; import clearLocalSettings from '/imports/api/local-settings/server/modifiers/clearLocalSettings';
import clearRecordMeeting from './clearRecordMeeting'; import clearRecordMeeting from './clearRecordMeeting';
import clearVoiceCallStates from '/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates';
export default function meetingHasEnded(meetingId) { export default function meetingHasEnded(meetingId) {
removeAnnotationsStreamer(meetingId); removeAnnotationsStreamer(meetingId);
@ -40,6 +41,7 @@ export default function meetingHasEnded(meetingId) {
clearNetworkInformation(meetingId); clearNetworkInformation(meetingId);
clearLocalSettings(meetingId); clearLocalSettings(meetingId);
clearRecordMeeting(meetingId); clearRecordMeeting(meetingId);
clearVoiceCallStates(meetingId);
return Logger.info(`Cleared Meetings with id ${meetingId}`); return Logger.info(`Cleared Meetings with id ${meetingId}`);
}); });

View File

@ -0,0 +1,13 @@
import { Meteor } from 'meteor/meteor';
const VoiceCallStates = new Mongo.Collection('voiceCallStates');
if (Meteor.isServer) {
// types of queries for the voice users:
// 1. intId
// 2. meetingId, intId
VoiceCallStates._ensureIndex({ meetingId: 1, userId: 1 });
}
export default VoiceCallStates;

View File

@ -0,0 +1,4 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleVoiceCallStateEvent from './handlers/voiceCallStateEvent';
RedisPubSub.on('VoiceCallStateEvtMsg', handleVoiceCallStateEvent);

View File

@ -0,0 +1,48 @@
import { check } from 'meteor/check';
import VoiceCallState from '/imports/api/voice-call-states';
import Logger from '/imports/startup/server/logger';
// "CALL_STARTED", "IN_ECHO_TEST", "IN_CONFERENCE", "CALL_ENDED"
export default function handleVoiceCallStateEvent({ body }, meetingId) {
const {
voiceConf,
clientSession,
userId,
callerName,
callState,
} = body;
check(meetingId, String);
check(voiceConf, String);
check(clientSession, String);
check(userId, String);
check(callerName, String);
check(callState, String);
const selector = {
meetingId,
userId,
clientSession,
};
const modifier = {
$set: {
meetingId,
userId,
voiceConf,
clientSession,
callState,
},
};
const cb = (err) => {
if (err) {
return Logger.error(`Update voice call state=${userId}: ${err}`);
}
return Logger.debug(`Update voice call state=${userId} meeting=${meetingId} clientSession=${clientSession}`);
};
return VoiceCallState.upsert(selector, modifier, cb);
}

View File

@ -0,0 +1,2 @@
import './eventHandlers';
import './publishers';

View File

@ -0,0 +1,14 @@
import Logger from '/imports/startup/server/logger';
import VoiceCallStates from '/imports/api/voice-users';
export default function clearVoiceCallStates(meetingId) {
if (meetingId) {
return VoiceCallStates.remove({ meetingId }, () => {
Logger.info(`Cleared VoiceCallStates in (${meetingId})`);
});
}
return VoiceCallStates.remove({}, () => {
Logger.info('Cleared VoiceCallStates in all meetings');
});
}

View File

@ -0,0 +1,22 @@
import VoiceCallStates from '/imports/api/voice-call-states';
import { Meteor } from 'meteor/meteor';
import Logger from '/imports/startup/server/logger';
import { extractCredentials } from '/imports/api/common/server/helpers';
function voiceCallStates() {
if (!this.userId) {
return VoiceCallStates.find({ meetingId: '' });
}
const { meetingId, requesterUserId } = extractCredentials(this.userId);
Logger.debug(`Publishing Voice Call States for ${meetingId} ${requesterUserId}`);
return VoiceCallStates.find({ meetingId, userId: requesterUserId });
}
function publish(...args) {
const boundVoiceCallStates = voiceCallStates.bind(this);
return boundVoiceCallStates(...args);
}
Meteor.publish('voice-call-states', publish);

View File

@ -0,0 +1,8 @@
const CallStateOptions = {
CALL_STARTED: 'CALL_STARTED',
IN_ECHO_TEST: 'IN_ECHO_TEST',
IN_CONFERENCE: 'IN_CONFERENCE',
CALL_ENDED: 'CALL_ENDED',
};
export default CallStateOptions;

View File

@ -20,6 +20,7 @@ const SUBSCRIPTIONS = [
'voiceUsers', 'whiteboard-multi-user', 'screenshare', 'group-chat', 'voiceUsers', 'whiteboard-multi-user', 'screenshare', 'group-chat',
'presentation-pods', 'users-settings', 'guestUser', 'users-infos', 'note', 'meeting-time-remaining', 'presentation-pods', 'users-settings', 'guestUser', 'users-infos', 'note', 'meeting-time-remaining',
'network-information', 'ping-pong', 'local-settings', 'users-typing', 'record-meetings', 'video-streams', 'network-information', 'ping-pong', 'local-settings', 'users-typing', 'record-meetings', 'video-streams',
'voice-call-states',
]; ];
class Subscriptions extends Component { class Subscriptions extends Component {

View File

@ -26,7 +26,7 @@ import '/imports/api/external-videos/server';
import '/imports/api/guest-users/server'; import '/imports/api/guest-users/server';
import '/imports/api/ping-pong/server'; import '/imports/api/ping-pong/server';
import '/imports/api/local-settings/server'; import '/imports/api/local-settings/server';
import '/imports/api/voice-call-states/server';
// Commons // Commons
import '/imports/api/log-client/server'; import '/imports/api/log-client/server';