From 6a4ba7a30048d12b944fcef297c872e96b20c826 Mon Sep 17 00:00:00 2001 From: Chad Pilkey <capilkey@gmail.com> Date: Tue, 18 Feb 2020 14:03:06 -0800 Subject: [PATCH] watch for voice call state updates instead of DTMFs in the client --- .../imports/api/audio/client/bridge/sip.js | 69 +++++++++++-------- .../server/modifiers/meetingHasEnded.js | 2 + .../imports/api/voice-call-states/index.js | 13 ++++ .../voice-call-states/server/eventHandlers.js | 4 ++ .../server/handlers/voiceCallStateEvent.js | 48 +++++++++++++ .../api/voice-call-states/server/index.js | 2 + .../server/modifiers/clearVoiceCallStates.js | 14 ++++ .../voice-call-states/server/publishers.js | 22 ++++++ .../api/voice-call-states/utils/callStates.js | 8 +++ .../ui/components/subscriptions/component.jsx | 1 + bigbluebutton-html5/server/main.js | 2 +- 11 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 bigbluebutton-html5/imports/api/voice-call-states/index.js create mode 100644 bigbluebutton-html5/imports/api/voice-call-states/server/eventHandlers.js create mode 100644 bigbluebutton-html5/imports/api/voice-call-states/server/handlers/voiceCallStateEvent.js create mode 100644 bigbluebutton-html5/imports/api/voice-call-states/server/index.js create mode 100644 bigbluebutton-html5/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates.js create mode 100644 bigbluebutton-html5/imports/api/voice-call-states/server/publishers.js create mode 100644 bigbluebutton-html5/imports/api/voice-call-states/utils/callStates.js diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js index ab9ee5b48c..16b4884ca5 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js @@ -10,6 +10,10 @@ import { analyzeSdp, logSelectedCandidate, } 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_TAG = MEDIA.mediaTag; @@ -47,14 +51,6 @@ class SIPSession { 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) { return new Promise((resolve, reject) => { const callExtension = extension ? `${extension}${this.userData.voiceBridge}` : this.userData.voiceBridge; @@ -119,8 +115,10 @@ class SIPSession { return new Promise((resolve, reject) => { this.inEchoTest = false; - const timeout = setInterval(() => { - clearInterval(timeout); + let trackerControl = null; + + const timeout = setTimeout(() => { + trackerControl.stop(); logger.error({ logCode: 'sip_js_transfer_timed_out' }, 'Timeout on transferring from echo test to conference'); this.callback({ status: this.baseCallStates.failed, @@ -136,15 +134,22 @@ class SIPSession { // This is is the call transfer code ask @chadpilkey this.currentSession.dtmf(1); - this.currentSession.on('dtmf', (event) => { - if (event.body && (typeof event.body === 'string')) { - const key = SIPSession.parseDTMF(event.body); - if (key === '7') { - clearInterval(timeout); - onTransferSuccess(); - resolve(); - } - } + Tracker.autorun((c) => { + trackerControl = c; + const selector = { meetingId: Auth.meetingID, userId: Auth.userID }; + const query = VoiceCallStates.find(selector); + + query.observeChanges({ + 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)); - const inEchoDTMF = (event) => { - if (event.body && typeof event.body === 'string') { - const dtmf = SIPSession.parseDTMF(event.body); - if (dtmf === '0') { - fsReady = true; - checkIfCallReady(); - } - } - currentSession.off('dtmf', inEchoDTMF); - }; - currentSession.on('dtmf', inEchoDTMF); + Tracker.autorun((c) => { + const selector = { meetingId: Auth.meetingID, userId: Auth.userID }; + const query = VoiceCallStates.find(selector); + + query.observeChanges({ + changed: (id, fields) => { + if (fields.callState === CallStateOptions.IN_ECHO_TEST) { + fsReady = true; + checkIfCallReady(); + + c.stop(); + } + }, + }); + }); }); } } diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js index f51f6aa75b..6b245da807 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js @@ -19,6 +19,7 @@ import clearNote from '/imports/api/note/server/modifiers/clearNote'; import clearNetworkInformation from '/imports/api/network-information/server/modifiers/clearNetworkInformation'; import clearLocalSettings from '/imports/api/local-settings/server/modifiers/clearLocalSettings'; import clearRecordMeeting from './clearRecordMeeting'; +import clearVoiceCallStates from '/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates'; export default function meetingHasEnded(meetingId) { removeAnnotationsStreamer(meetingId); @@ -40,6 +41,7 @@ export default function meetingHasEnded(meetingId) { clearNetworkInformation(meetingId); clearLocalSettings(meetingId); clearRecordMeeting(meetingId); + clearVoiceCallStates(meetingId); return Logger.info(`Cleared Meetings with id ${meetingId}`); }); diff --git a/bigbluebutton-html5/imports/api/voice-call-states/index.js b/bigbluebutton-html5/imports/api/voice-call-states/index.js new file mode 100644 index 0000000000..7d2dfc3e39 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-call-states/index.js @@ -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; diff --git a/bigbluebutton-html5/imports/api/voice-call-states/server/eventHandlers.js b/bigbluebutton-html5/imports/api/voice-call-states/server/eventHandlers.js new file mode 100644 index 0000000000..4d7a7397a3 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-call-states/server/eventHandlers.js @@ -0,0 +1,4 @@ +import RedisPubSub from '/imports/startup/server/redis'; +import handleVoiceCallStateEvent from './handlers/voiceCallStateEvent'; + +RedisPubSub.on('VoiceCallStateEvtMsg', handleVoiceCallStateEvent); diff --git a/bigbluebutton-html5/imports/api/voice-call-states/server/handlers/voiceCallStateEvent.js b/bigbluebutton-html5/imports/api/voice-call-states/server/handlers/voiceCallStateEvent.js new file mode 100644 index 0000000000..16ed32e39e --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-call-states/server/handlers/voiceCallStateEvent.js @@ -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); +} diff --git a/bigbluebutton-html5/imports/api/voice-call-states/server/index.js b/bigbluebutton-html5/imports/api/voice-call-states/server/index.js new file mode 100644 index 0000000000..f993f38e5b --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-call-states/server/index.js @@ -0,0 +1,2 @@ +import './eventHandlers'; +import './publishers'; diff --git a/bigbluebutton-html5/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates.js b/bigbluebutton-html5/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates.js new file mode 100644 index 0000000000..6eed55eec6 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates.js @@ -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'); + }); +} diff --git a/bigbluebutton-html5/imports/api/voice-call-states/server/publishers.js b/bigbluebutton-html5/imports/api/voice-call-states/server/publishers.js new file mode 100644 index 0000000000..aa43e17861 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-call-states/server/publishers.js @@ -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); diff --git a/bigbluebutton-html5/imports/api/voice-call-states/utils/callStates.js b/bigbluebutton-html5/imports/api/voice-call-states/utils/callStates.js new file mode 100644 index 0000000000..332ad30370 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-call-states/utils/callStates.js @@ -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; diff --git a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx index 72a071988a..62fd2f003a 100755 --- a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx @@ -20,6 +20,7 @@ const SUBSCRIPTIONS = [ 'voiceUsers', 'whiteboard-multi-user', 'screenshare', 'group-chat', 'presentation-pods', 'users-settings', 'guestUser', 'users-infos', 'note', 'meeting-time-remaining', 'network-information', 'ping-pong', 'local-settings', 'users-typing', 'record-meetings', 'video-streams', + 'voice-call-states', ]; class Subscriptions extends Component { diff --git a/bigbluebutton-html5/server/main.js b/bigbluebutton-html5/server/main.js index fbf7cf1edf..517604f3d2 100755 --- a/bigbluebutton-html5/server/main.js +++ b/bigbluebutton-html5/server/main.js @@ -26,7 +26,7 @@ import '/imports/api/external-videos/server'; import '/imports/api/guest-users/server'; import '/imports/api/ping-pong/server'; import '/imports/api/local-settings/server'; - +import '/imports/api/voice-call-states/server'; // Commons import '/imports/api/log-client/server'; -- GitLab