diff --git a/doc/api.md b/doc/api.md index b25379d67..06c36dce3 100644 --- a/doc/api.md +++ b/doc/api.md @@ -197,6 +197,15 @@ api.executeCommand('displayName', 'New Nickname'); api.executeCommand('password', 'The Password'); ``` +* **sendTones** - Play touch tones. +```javascript +api.executeCommand('sendTones', { + tones: string, // The dial pad touch tones to play. For example, '12345#'. + duration: number, // Optional. The number of milliseconds each tone should play. The default is 200. + pause: number // Optional. The number of milliseconds between each tone. The default is 200. +}); +``` + * **subject** - Sets the subject of the conference. This command requires one argument - the new subject to be set. ```javascript api.executeCommand('subject', 'New Conference Subject'); diff --git a/modules/API/API.js b/modules/API/API.js index 08fc98bd7..2144bdff5 100644 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -5,7 +5,11 @@ import { createApiEvent, sendAnalytics } from '../../react/features/analytics'; -import { setPassword, setSubject } from '../../react/features/base/conference'; +import { + sendTones, + setPassword, + setSubject +} from '../../react/features/base/conference'; import { parseJWTFromURLParams } from '../../react/features/base/jwt'; import { invite } from '../../react/features/invite'; import { toggleTileView } from '../../react/features/video-layout'; @@ -90,6 +94,11 @@ function initCommands() { 'proxy-connection-event': event => { APP.conference.onProxyConnectionEvent(event); }, + 'send-tones': (options = {}) => { + const { duration, tones, pause } = options; + + APP.store.dispatch(sendTones(tones, duration, pause)); + }, 'subject': subject => { sendAnalytics(createApiEvent('subject.changed')); APP.store.dispatch(setSubject(subject)); diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index a089d27ae..44902f0d8 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -34,6 +34,7 @@ const commands = { email: 'email', hangup: 'video-hangup', password: 'password', + sendTones: 'send-tones', subject: 'subject', submitFeedback: 'submit-feedback', toggleAudio: 'toggle-audio', diff --git a/react/features/base/conference/actionTypes.js b/react/features/base/conference/actionTypes.js index 37d09726c..c1486c8e5 100644 --- a/react/features/base/conference/actionTypes.js +++ b/react/features/base/conference/actionTypes.js @@ -118,6 +118,18 @@ export const LOCK_STATE_CHANGED = 'LOCK_STATE_CHANGED'; */ export const P2P_STATUS_CHANGED = 'P2P_STATUS_CHANGED'; +/** + * The type of (redux) action which signals to play specified touch tones. + * + * { + * type: SEND_TONES, + * tones: string, + * duration: number, + * pause: number + * } + */ +export const SEND_TONES = 'SEND_TONES'; + /** * The type of (redux) action which sets the desktop sharing enabled flag for * the current conference. diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js index 875a12464..fa6d79dfc 100644 --- a/react/features/base/conference/actions.js +++ b/react/features/base/conference/actions.js @@ -37,6 +37,7 @@ import { KICKED_OUT, LOCK_STATE_CHANGED, P2P_STATUS_CHANGED, + SEND_TONES, SET_DESKTOP_SHARING_ENABLED, SET_FOLLOW_ME, SET_MAX_RECEIVER_VIDEO_QUALITY, @@ -520,6 +521,28 @@ export function p2pStatusChanged(p2p: boolean) { }; } +/** + * Signals to play touch tones. + * + * @param {string} tones - The tones to play. + * @param {number} [duration] - How long to play each tone. + * @param {number} [pause] - How long to pause between each tone. + * @returns {{ + * type: SEND_TONES, + * tones: string, + * duration: number, + * pause: number + * }} + */ +export function sendTones(tones: string, duration: number, pause: number) { + return { + type: SEND_TONES, + tones, + duration, + pause + }; +} + /** * Sets the flag for indicating if desktop sharing is enabled. * diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js index 8024646d1..d91541bf3 100644 --- a/react/features/base/conference/middleware.js +++ b/react/features/base/conference/middleware.js @@ -35,6 +35,7 @@ import { CONFERENCE_SUBJECT_CHANGED, CONFERENCE_WILL_LEAVE, DATA_CHANNEL_OPENED, + SEND_TONES, SET_PENDING_SUBJECT_CHANGE, SET_ROOM } from './actionTypes'; @@ -89,6 +90,9 @@ MiddlewareRegistry.register(store => next => action => { case PIN_PARTICIPANT: return _pinParticipant(store, next, action); + case SEND_TONES: + return _sendTones(store, next, action); + case SET_ROOM: return _setRoom(store, next, action); @@ -446,6 +450,31 @@ function _pinParticipant({ getState }, next, action) { return next(action); } +/** + * Requests the specified tones to be played. + * + * @param {Store} store - The redux store in which the specified {@code action} + * is being dispatched. + * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the + * specified {@code action} to the specified {@code store}. + * @param {Action} action - The redux action {@code SEND_TONES} which is + * being dispatched in the specified {@code store}. + * @private + * @returns {Object} The value returned by {@code next(action)}. + */ +function _sendTones({ getState }, next, action) { + const state = getState(); + const { conference } = state['features/base/conference']; + + if (conference) { + const { duration, tones, pause } = action; + + conference.sendTones(tones, duration, pause); + } + + return next(action); +} + /** * Helper function for updating the preferred receiver video constraint, based * on the user preference and the internal maximum.