Skip to content

Commit cc899e0

Browse files
authored
🚀 v0.5.4
v0.5.4
2 parents 443e618 + db92dbb commit cc899e0

File tree

13 files changed

+537
-26
lines changed

13 files changed

+537
-26
lines changed

Diff for: composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "rtckit/eqivo",
33
"description": "Telephony API Platform",
4-
"version": "0.5.3",
4+
"version": "0.5.4",
55
"keywords": [
66
"telecommunications",
77
"voip",

Diff for: etc/Dockerfile.freeswitch

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
FROM rtckit/slimswitch-builder:v1.10.7
2+
3+
# Build with:
4+
#
5+
# docker build -f ./etc/Dockerfile.freeswitch -t rtckit/eqivo-freeswitch-builder:v1.10.7 .
6+
#
7+
# Minify with rtckit/slimswitch:
8+
#
9+
# ./bin/mkslim.sh \
10+
# -r rtckit/eqivo-freeswitch-builder \
11+
# -m mod_event_socket \
12+
# -m mod_commands \
13+
# -m mod_dialplan_xml \
14+
# -m mod_dptools \
15+
# -m mod_sofia \
16+
# -m mod_tone_stream \
17+
# -m mod_sndfile \
18+
# -m mod_conference \
19+
# -m mod_flite \
20+
# -m mod_say_en \
21+
# -m mod_soundtouch \
22+
# -m mod_amd \
23+
# -m mod_avmd \
24+
# -m mod_http_cache \
25+
# -m mod_shout \
26+
# -s rtckit/eqivo-freeswitch
27+
#
28+
# mod_http_cache and mod_shout are not required by Eqivo but are fairly common in practice
29+
30+
RUN cd /usr/src/freeswitch* && \
31+
wget https://codeload.github.com/rtckit/mod_amd/tar.gz/d49f81f -O amd.tar.gz && \
32+
tar zfvx amd.tar.gz -C ./src/mod/applications && \
33+
mv ./src/mod/applications/mod_amd-d49f81f ./src/mod/applications/mod_amd && \
34+
sed -i 's#src/mod/applications/mod_mariadb/Makefile#src/mod/applications/mod_mariadb/Makefile\n\t\tsrc/mod/applications/mod_amd/Makefile#' configure.ac && \
35+
./bootstrap.sh -j && ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-debug && \
36+
echo applications/mod_amd >> ./modules.conf && \
37+
make -j mod_amd && make mod_amd-install

Diff for: src/App.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
class App
1818
{
19-
public const VERSION = '0.5.3';
19+
public const VERSION = '0.5.4';
2020

2121
public Config\Set $config;
2222

Diff for: src/Config/CliArguments.php

+3-5
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ class CliArguments implements ResolverInterface
1111
{
1212
public function resolve(Set $config): void
1313
{
14-
if (!isset($_SERVER['argv'][1])) {
15-
return;
16-
}
17-
1814
$args = getopt(
1915
'hc:fdp:',
2016
[
@@ -308,7 +304,9 @@ function (string $value): bool {
308304

309305
protected function help(): never
310306
{
311-
echo 'Usage: ' . $_SERVER['argv'][0] . ' [options]' . PHP_EOL . PHP_EOL . 'Options:' . PHP_EOL;
307+
$cmd = !is_array($_SERVER['argv']) || empty($_SERVER['argv'][0]) ? './bin/eqivo' : $_SERVER['argv'][0];
308+
309+
echo 'Usage: ' . $cmd . ' [options]' . PHP_EOL . PHP_EOL . 'Options:' . PHP_EOL;
312310
echo <<<EOD
313311
--help | -h show this help message and exit
314312
--config | -c <FILE> set config file to FILE

Diff for: src/Inbound/Controller.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function subscribe(Core $core): PromiseInterface
5151
EventEnum::SESSION_HEARTBEAT->value,
5252
EventEnum::CALL_UPDATE->value,
5353
EventEnum::RECORD_STOP->value,
54-
EventEnum::CUSTOM->value . ' conference::maintenance',
54+
EventEnum::CUSTOM->value . ' conference::maintenance amd::info avmd::beep avmd::timeout',
5555
]));
5656
}
5757

Diff for: src/Inbound/Handler/Custom.php

+141-18
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
Core,
99
EventEnum
1010
};
11+
use RTCKit\Eqivo\Rest\Controller\V0_1\Call;
1112

13+
use React\EventLoop\Loop;
14+
use RTCKit\ESL;
1215
use stdClass as Event;
1316

1417
class Custom implements HandlerInterface
@@ -20,36 +23,156 @@ class Custom implements HandlerInterface
2023

2124
public function execute(Core $core, Event $event): void
2225
{
23-
if (!isset($event->{'Event-Subclass'}, $event->Action, $event->{'Conference-Unique-ID'})) {
26+
if (!isset($event->{'Event-Subclass'})) {
2427
return;
2528
}
2629

27-
if ($event->{'Event-Subclass'} === 'conference::maintenance') {
28-
$conference = $core->getConference($event->{'Conference-Unique-ID'});
30+
switch ($event->{'Event-Subclass'}) {
31+
case 'conference::maintenance':
32+
if (!isset($event->Action, $event->{'Conference-Unique-ID'})) {
33+
return;
34+
}
2935

30-
if (!isset($conference)) {
31-
return;
32-
}
36+
$conference = $core->getConference($event->{'Conference-Unique-ID'});
37+
38+
if (!isset($conference)) {
39+
return;
40+
}
41+
42+
switch ($event->Action) {
43+
case 'stop-recording':
44+
if (!isset($this->app->config->recordUrl)) {
45+
return;
46+
}
3347

34-
switch ($event->Action) {
35-
case 'stop-recording':
36-
if (!isset($this->app->config->recordUrl)) {
48+
$params = [
49+
'RecordFile' => $event->Path,
50+
'RecordDuration' => isset($event->{'Milliseconds-Elapsed'}) ? (int)$event->{'Milliseconds-Elapsed'} : -1,
51+
];
52+
53+
$this->app->inboundServer->logger->info('Conference Record Stop event ' . json_encode($params));
54+
$this->app->inboundServer->controller->fireConferenceCallback($conference, $this->app->config->recordUrl, $this->app->config->defaultHttpMethod, $params);
55+
return;
56+
57+
case 'conference-destroy':
58+
$core->removeConference($event->{'Conference-Unique-ID'});
3759
return;
60+
}
61+
62+
return;
63+
64+
case 'amd::info':
65+
$session = $core->getSession($event->{'Unique-ID'});
66+
67+
if (!isset($session)) {
68+
return;
69+
}
70+
71+
$this->app->inboundServer->logger->info('AMD event ' . json_encode($event));
72+
$isAmdEvent = true;
73+
74+
$session->amdDuration = (int)(((int)$event->variable_amd_result_microtime - (int)$event->{'Caller-Channel-Answered-Time'}) / 1000);
75+
$session->amdAnsweredBy = 'unknown';
76+
77+
$result = $event->variable_amd_result ?? 'NOTSURE';
78+
$isMachine = false;
79+
80+
switch ($result) {
81+
case 'HUMAN':
82+
$session->amdAnsweredBy = 'human';
83+
break;
84+
85+
case 'MACHINE':
86+
$isMachine = true;
87+
$session->amdAnsweredBy = 'machine_start';
88+
break;
89+
}
90+
91+
$session->amdMethod = $event->{"variable_{$this->app->config->appPrefix}_amd_method"} ?? Call::DEFAULT_AMD_METHOD;
92+
$session->amdAsync = $event->{"variable_{$this->app->config->appPrefix}_amd_async"} === 'on';
93+
94+
if ($session->amdAsync) {
95+
$urlVar = "variable_{$this->app->config->appPrefix}_amd_url";
96+
97+
if (isset($event->{$urlVar})) {
98+
$session->amdUrl = $event->{$urlVar};
3899
}
100+
} else {
101+
$session->amdUrl = $event->{"variable_{$this->app->config->appPrefix}_amd_target_url"};
102+
}
103+
104+
if ($isMachine && isset($event->{"variable_{$this->app->config->appPrefix}_amd_msg_end"})) {
105+
/* Kick off AVMD */
106+
$this->app->inboundServer->logger->info('Activating AVMD (DetectMessageEnd enabled)');
107+
108+
$session->client->sendMsg(
109+
(new ESL\Request\SendMsg)
110+
->setHeader('call-command', 'execute')
111+
->setHeader('execute-app-name', 'avmd_start')
112+
);
113+
114+
$timeout = (float)$event->{"variable_{$this->app->config->appPrefix}_amd_timeout"} - ($session->amdDuration / 1000);
115+
116+
$session->avmdTimer = Loop::addTimer($timeout, function() use ($session): void {
117+
unset($session->avmdTimer);
39118

40-
$params = [
41-
'RecordFile' => $event->Path,
42-
'RecordDuration' => isset($event->{'Milliseconds-Elapsed'}) ? (int)$event->{'Milliseconds-Elapsed'} : -1,
43-
];
119+
$session->client->sendMsg(
120+
(new ESL\Request\SendMsg)
121+
->setHeader('call-command', 'execute')
122+
->setHeader('execute-app-name', 'event')
123+
->setHeader('execute-app-arg', 'Event-Subclass=avmd::timeout,Event-Name=CUSTOM')
124+
);
125+
});
44126

45-
$this->app->inboundServer->logger->info('Conference Record Stop event ' . json_encode($params));
46-
$this->app->inboundServer->controller->fireConferenceCallback($conference, $this->app->config->recordUrl, $this->app->config->defaultHttpMethod, $params);
47127
return;
128+
}
129+
130+
case 'avmd::timeout':
131+
case 'avmd::beep':
132+
if (!isset($isAmdEvent)) {
133+
$session = $core->getSession($event->{'Unique-ID'});
134+
135+
if (!isset($session)) {
136+
return;
137+
}
138+
139+
$this->app->inboundServer->logger->info('AVMD event ' . json_encode($event));
48140

49-
case 'conference-destroy':
50-
$core->removeConference($event->{'Conference-Unique-ID'});
141+
if (isset($session->avmdTimer)) {
142+
Loop::cancelTimer($session->avmdTimer);
143+
unset($session->avmdTimer);
144+
}
145+
146+
$session->client->sendMsg(
147+
(new ESL\Request\SendMsg)
148+
->setHeader('call-command', 'execute')
149+
->setHeader('execute-app-name', 'avmd_stop')
150+
);
151+
152+
$session->amdAnsweredBy = ($event->{'Event-Subclass'} === 'avmd::beep') ? 'machine_end_beep' : 'machine_end_other';
153+
}
154+
155+
if (!isset($session)) {
51156
return;
52-
}
157+
}
158+
159+
$method = $event->{"variable_{$this->app->config->appPrefix}_amd_method"} ?? Call::DEFAULT_AMD_METHOD;
160+
$params = [
161+
'AnsweredBy' => $session->amdAnsweredBy,
162+
'MachineDetectionDuration' => $session->amdDuration,
163+
];
164+
165+
if ($session->amdAsync) {
166+
/* Asynchronous, just fire the callback, if configured */
167+
if (isset($session->amdUrl)) {
168+
$this->app->inboundServer->controller->fireSessionCallback($session, $session->amdUrl, $session->amdMethod, $params);
169+
}
170+
} else {
171+
/* Synchronous? Kick off call flow execution */
172+
$this->app->outboundServer->controller->fetchAndExecuteRestXml($session, $session->amdUrl, $session->amdMethod, $params);
173+
}
174+
175+
return;
53176
}
54177
}
55178
}

Diff for: src/Outbound/Controller.php

+38
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
RestXmlSyntaxException,
2020
UnrecognizedRestXmlException
2121
};
22+
use RTCKit\Eqivo\Rest\Controller\V0_1\Call;
2223

2324
use Psr\Http\Message\ResponseInterface;
2425
use React\EventLoop\Loop;
@@ -33,6 +34,7 @@
3334
use SimpleXMLIterator;
3435
use stdClass as Event;
3536
use function React\Promise\{
37+
all,
3638
reject,
3739
resolve
3840
};
@@ -210,6 +212,42 @@ public function onConnect(RemoteOutboundClient $client, ESL\Response\CommandRepl
210212

211213
$logger->info('Processing Call');
212214

215+
/* Check if AMD is enabled */
216+
if (
217+
!empty($context["variable_{$this->app->config->appPrefix}_amd"]) &&
218+
($context["variable_{$this->app->config->appPrefix}_amd"] === 'on')
219+
) {
220+
/* If synchronous, call flow execution must be blocked until AMD resolution is ready */
221+
if (
222+
!empty($context["variable_{$this->app->config->appPrefix}_amd_async"]) &&
223+
($context["variable_{$this->app->config->appPrefix}_amd_async"] === 'off')
224+
) {
225+
$amdTimeoutAdj = (int)($context["variable_{$this->app->config->appPrefix}_amd_timeout"] ?? Call::DEFAULT_AMD_TIMEOUT) + 1;
226+
227+
$logger->info('Activating synchronous AMD');
228+
229+
all([
230+
$session->client->sendMsg(
231+
(new ESL\Request\SendMsg)
232+
->setHeader('call-command', 'execute')
233+
->setHeader('execute-app-name', 'set')
234+
->setHeader('execute-app-arg', "{$this->app->config->appPrefix}_amd_target_url={$session->targetUrl}")
235+
->setHeader('event-lock', 'true')
236+
),
237+
$session->client->sendMsg(
238+
(new ESL\Request\SendMsg)
239+
->setHeader('call-command', 'execute')
240+
->setHeader('execute-app-name', 'playback')
241+
->setHeader('execute-app-arg', 'file_string://silence_stream://' . ($amdTimeoutAdj * 1000))
242+
),
243+
]);
244+
245+
return;
246+
} else {
247+
$logger->info('Activating asynchronous AMD');
248+
}
249+
}
250+
213251
$this->fetchAndExecuteRestXml($session, $session->targetUrl)
214252
->then(function () use ($session): PromiseInterface {
215253
if (isset($session->hangupCause)) {

0 commit comments

Comments
 (0)