Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Realtime-API Voice Detect Methods #492

Merged
merged 17 commits into from
Apr 25, 2022
Merged
7 changes: 7 additions & 0 deletions .changeset/rich-bikes-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sw-internal/playground-realtime-api': minor
'@signalwire/core': minor
'@signalwire/realtime-api': minor
---

Add ability to start detectors for machine/digit/fax in `Voice` Call.
67 changes: 51 additions & 16 deletions internal/playground-realtime-api/src/voice/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,62 @@
import { Voice } from '@signalwire/realtime-api'

const sleep = (ms = 3000) => {
return new Promise((r) => {
setTimeout(r, ms)
})
}

// In this example you need to perform and outbound/inbound call
const RUN_DETECTOR = false

async function run() {
try {
const client = new Voice.Client({
host: process.env.HOST || 'relay.swire.io',
project: process.env.PROJECT as string,
token: process.env.TOKEN as string,
contexts: [process.env.RELAY_CONTEXT as string],
// // logLevel: 'trace',
// debug: {
// logWsTraffic: true,
// },
// logLevel: 'trace',
debug: {
logWsTraffic: true,
},
})

client.on('call.received', async (call) => {
console.log('Got call', call.id, call.from, call.to, call.direction)

try {
await call.answer()
console.log('Inbound call answered', call)
setTimeout(async () => {
console.log('Terminating the call')
await call.hangup()
console.log('Call terminated!')
}, 3000)
console.log('Inbound call answered')
await sleep(1000)

// Send digits to trigger the detector
await call.sendDigits('1w2w3')

// Play media to mock an answering machine
// await call.play({
// media: [
// {
// type: 'tts',
// text: 'Hello, please leave a message',
// },
// {
// type: 'silence',
// duration: 2,
// },
// {
// type: 'audio',
// url: 'https://www.soundjay.com/buttons/beep-01a.mp3',
// },
// ],
// volume: 2.0,
// })

// setTimeout(async () => {
// console.log('Terminating the call')
// await call.hangup()
// console.log('Call terminated!')
// }, 3000)
} catch (error) {
console.error('Error answering inbound call', error)
}
Expand All @@ -46,13 +79,15 @@ async function run() {
})

console.log('Dial resolved!', call.id)
const sleep = (ms = 3000) => {
return new Promise((r) => {
setTimeout(r, ms)
})
}

await sleep()
if (RUN_DETECTOR) {
// See the `call.received` handler
const detect = await call.detectDigit()
const result = await detect.waitForResult()
console.log('Detect Result', result.type)

await sleep()
}

try {
const peer = await call.connect({
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/redux/features/pubSub/pubSubSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export function* pubSubSaga({
emitter.emit(type, payload)
}

getLogger().trace(
'Emit:',
toInternalEventName<string>({ namespace, event: type })
)
emitter.emit(
toInternalEventName<string>({ namespace, event: type }),
payload
Expand Down
165 changes: 165 additions & 0 deletions packages/core/src/types/voiceCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type CallCollect = 'call.collect'
export type CallTap = 'call.tap'
export type CallConnect = 'call.connect'
export type CallSendDigits = 'call.send_digits'
export type CallDetect = 'call.detect'

/**
* Public event types
Expand All @@ -86,6 +87,9 @@ export type CallConnectConnecting = 'connect.connecting'
export type CallConnectConnected = 'connect.connected'
export type CallConnectDisconnected = 'connect.disconnected'
export type CallConnectFailed = 'connect.failed'
export type CallDetectStarted = 'detect.started'
export type CallDetectUpdated = 'detect.updated'
export type CallDetectEnded = 'detect.ended'

/**
* List of public event names
Expand All @@ -110,6 +114,9 @@ export type VoiceCallEventNames =
| CallConnectConnected
| CallConnectDisconnected
| CallConnectFailed
| CallDetectStarted
| CallDetectUpdated
| CallDetectEnded

/**
* List of internal events
Expand Down Expand Up @@ -293,6 +300,35 @@ export interface VoiceCallConnectMethodParams {
devices: NestedArray<VoiceCallDeviceParams>
}

interface VoiceCallDetectBaseParams {
timeout?: number
waitForBeep?: boolean // SDK-side only
}

export interface VoiceCallDetectMachineParams
extends VoiceCallDetectBaseParams {
type: 'machine'
initialTimeout?: number
endSilenceTimeout?: number
machineVoiceThreshold?: number
machineWordsThreshold?: number
}

export interface VoiceCallDetectFaxParams extends VoiceCallDetectBaseParams {
type: 'fax'
tone?: 'CED' | 'CNG'
}

export interface VoiceCallDetectDigitParams extends VoiceCallDetectBaseParams {
type: 'digit'
digits?: string
}

export type VoiceCallDetectMethodParams =
| VoiceCallDetectMachineParams
| VoiceCallDetectFaxParams
| VoiceCallDetectDigitParams

export type VoiceCallDisconnectReason =
| 'hangup'
| 'cancel'
Expand Down Expand Up @@ -393,6 +429,34 @@ export type VoiceCallRecordingEntity =
export type VoiceCallRecordingMethods =
OnlyFunctionProperties<VoiceCallRecordingContract>

/**
* Public Contract for a VoiceCallDetect
*/
export interface VoiceCallDetectContract {
/** Unique id for this recording */
readonly id: string
/** @ignore */
readonly callId: string
/** @ignore */
readonly controlId: string
/** @ignore */
readonly type?: CallingCallDetectType

stop(): Promise<this>
waitForResult(): Promise<this>
}

/**
* VoiceCallDetect properties
*/
export type VoiceCallDetectEntity = OnlyStateProperties<VoiceCallDetectContract>

/**
* VoiceCallDetect methods
*/
export type VoiceCallDetectMethods =
OnlyFunctionProperties<VoiceCallDetectContract>

/**
* Public Contract for a VoiceCallPrompt
*/
Expand Down Expand Up @@ -517,6 +581,16 @@ export interface VoiceCallContract<T = any> {
connect(params: VoiceCallConnectMethodParams): Promise<VoiceCallContract>
waitUntilConnected(): Promise<this>
disconnect(): Promise<void>
detect(params: VoiceCallDetectMethodParams): Promise<VoiceCallDetectContract>
amd(
params?: Omit<VoiceCallDetectMachineParams, 'type'>
): Promise<VoiceCallDetectContract>
detectFax(
params?: Omit<VoiceCallDetectFaxParams, 'type'>
): Promise<VoiceCallDetectContract>
detectDigit(
params?: Omit<VoiceCallDetectDigitParams, 'type'>
): Promise<VoiceCallDetectContract>
}

/**
Expand Down Expand Up @@ -798,6 +872,64 @@ export interface CallingCallSendDigitsEvent extends SwEvent {
params: CallingCallSendDigitsEventParams
}

/**
* 'calling.call.detect'
*/
type CallingCallDetectState = 'finished' | 'error'
interface CallingCallDetectFax {
type: 'fax'
params: {
event: 'CED' | 'CNG' | CallingCallDetectState
}
}
interface CallingCallDetectMachine {
type: 'machine'
params: {
event:
| 'MACHINE'
| 'HUMAN'
| 'UNKNOWN'
| 'READY'
| 'NOT_READY'
| CallingCallDetectState
}
}
interface CallingCallDetectDigit {
type: 'digit'
params: {
event:
| '0'
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9'
| '#'
| '*'
| CallingCallDetectState
}
}
export type Detector =
| CallingCallDetectFax
| CallingCallDetectMachine
| CallingCallDetectDigit
type CallingCallDetectType = Detector['type']
export interface CallingCallDetectEventParams {
node_id: string
call_id: string
control_id: string
detect?: Detector
}

export interface CallingCallDetectEvent extends SwEvent {
event_type: ToInternalVoiceEvent<CallDetect>
params: CallingCallDetectEventParams
}

/**
* ==========
* ==========
Expand Down Expand Up @@ -937,6 +1069,28 @@ export interface CallConnectFailedEvent extends SwEvent {
params: CallingCallConnectEventParams
}

/**
* 'calling.detect.started'
*/
export interface CallDetectStartedEvent extends SwEvent {
event_type: ToInternalVoiceEvent<CallDetectStarted>
params: CallingCallDetectEventParams & { tag: string }
}
/**
* 'calling.detect.updated'
*/
export interface CallDetectUpdatedEvent extends SwEvent {
event_type: ToInternalVoiceEvent<CallDetectUpdated>
params: CallingCallDetectEventParams & { tag: string }
}
/**
* 'calling.detect.ended'
*/
export interface CallDetectEndedEvent extends SwEvent {
event_type: ToInternalVoiceEvent<CallDetectEnded>
params: CallingCallDetectEventParams & { tag: string }
}

// interface VoiceCallStateEvent {
// call_id: string
// node_id: string
Expand Down Expand Up @@ -974,6 +1128,7 @@ export type VoiceCallEvent =
| CallingCallTapEvent
| CallingCallConnectEvent
| CallingCallSendDigitsEvent
| CallingCallDetectEvent
// SDK Events
| CallReceivedEvent
| CallPlaybackStartedEvent
Expand All @@ -993,6 +1148,9 @@ export type VoiceCallEvent =
| CallConnectConnectedEvent
| CallConnectDisconnectedEvent
| CallConnectFailedEvent
| CallDetectStartedEvent
| CallDetectUpdatedEvent
| CallDetectEndedEvent

export type VoiceCallEventParams =
// Server Event Params
Expand All @@ -1005,6 +1163,7 @@ export type VoiceCallEventParams =
| CallingCallTapEventParams
| CallingCallConnectEventParams
| CallingCallSendDigitsEventParams
| CallingCallDetectEventParams
// SDK Event Params
| CallReceivedEvent['params']
| CallPlaybackStartedEvent['params']
Expand All @@ -1024,6 +1183,9 @@ export type VoiceCallEventParams =
| CallConnectConnectedEvent['params']
| CallConnectDisconnectedEvent['params']
| CallConnectFailedEvent['params']
| CallDetectStartedEvent['params']
| CallDetectUpdatedEvent['params']
| CallDetectEndedEvent['params']

export type VoiceCallAction = MapToPubSubShape<VoiceCallEvent>

Expand All @@ -1046,6 +1208,8 @@ export type VoiceCallJSONRPCMethod =
| 'calling.connect'
| 'calling.disconnect'
| 'calling.send_digits'
| 'calling.detect'
| 'calling.detect.stop'

export type CallingTransformType =
| 'voiceCallReceived'
Expand All @@ -1055,3 +1219,4 @@ export type CallingTransformType =
| 'voiceCallTap'
| 'voiceCallConnect'
| 'voiceCallState'
| 'voiceCallDetect'
Loading