Skip to content

Commit

Permalink
🚀 v0.5.4
Browse files Browse the repository at this point in the history
v0.5.4
  • Loading branch information
cdosoftei committed Jun 14, 2022
2 parents 443e618 + db92dbb commit cc899e0
Show file tree
Hide file tree
Showing 13 changed files with 537 additions and 26 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "rtckit/eqivo",
"description": "Telephony API Platform",
"version": "0.5.3",
"version": "0.5.4",
"keywords": [
"telecommunications",
"voip",
Expand Down
37 changes: 37 additions & 0 deletions etc/Dockerfile.freeswitch
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
FROM rtckit/slimswitch-builder:v1.10.7

# Build with:
#
# docker build -f ./etc/Dockerfile.freeswitch -t rtckit/eqivo-freeswitch-builder:v1.10.7 .
#
# Minify with rtckit/slimswitch:
#
# ./bin/mkslim.sh \
# -r rtckit/eqivo-freeswitch-builder \
# -m mod_event_socket \
# -m mod_commands \
# -m mod_dialplan_xml \
# -m mod_dptools \
# -m mod_sofia \
# -m mod_tone_stream \
# -m mod_sndfile \
# -m mod_conference \
# -m mod_flite \
# -m mod_say_en \
# -m mod_soundtouch \
# -m mod_amd \
# -m mod_avmd \
# -m mod_http_cache \
# -m mod_shout \
# -s rtckit/eqivo-freeswitch
#
# mod_http_cache and mod_shout are not required by Eqivo but are fairly common in practice

RUN cd /usr/src/freeswitch* && \
wget https://codeload.github.com/rtckit/mod_amd/tar.gz/d49f81f -O amd.tar.gz && \
tar zfvx amd.tar.gz -C ./src/mod/applications && \
mv ./src/mod/applications/mod_amd-d49f81f ./src/mod/applications/mod_amd && \
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 && \
./bootstrap.sh -j && ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-debug && \
echo applications/mod_amd >> ./modules.conf && \
make -j mod_amd && make mod_amd-install
2 changes: 1 addition & 1 deletion src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class App
{
public const VERSION = '0.5.3';
public const VERSION = '0.5.4';

public Config\Set $config;

Expand Down
8 changes: 3 additions & 5 deletions src/Config/CliArguments.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ class CliArguments implements ResolverInterface
{
public function resolve(Set $config): void
{
if (!isset($_SERVER['argv'][1])) {
return;
}

$args = getopt(
'hc:fdp:',
[
Expand Down Expand Up @@ -308,7 +304,9 @@ function (string $value): bool {

protected function help(): never
{
echo 'Usage: ' . $_SERVER['argv'][0] . ' [options]' . PHP_EOL . PHP_EOL . 'Options:' . PHP_EOL;
$cmd = !is_array($_SERVER['argv']) || empty($_SERVER['argv'][0]) ? './bin/eqivo' : $_SERVER['argv'][0];

echo 'Usage: ' . $cmd . ' [options]' . PHP_EOL . PHP_EOL . 'Options:' . PHP_EOL;
echo <<<EOD
--help | -h show this help message and exit
--config | -c <FILE> set config file to FILE
Expand Down
2 changes: 1 addition & 1 deletion src/Inbound/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function subscribe(Core $core): PromiseInterface
EventEnum::SESSION_HEARTBEAT->value,
EventEnum::CALL_UPDATE->value,
EventEnum::RECORD_STOP->value,
EventEnum::CUSTOM->value . ' conference::maintenance',
EventEnum::CUSTOM->value . ' conference::maintenance amd::info avmd::beep avmd::timeout',
]));
}

Expand Down
159 changes: 141 additions & 18 deletions src/Inbound/Handler/Custom.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
Core,
EventEnum
};
use RTCKit\Eqivo\Rest\Controller\V0_1\Call;

use React\EventLoop\Loop;
use RTCKit\ESL;
use stdClass as Event;

class Custom implements HandlerInterface
Expand All @@ -20,36 +23,156 @@ class Custom implements HandlerInterface

public function execute(Core $core, Event $event): void
{
if (!isset($event->{'Event-Subclass'}, $event->Action, $event->{'Conference-Unique-ID'})) {
if (!isset($event->{'Event-Subclass'})) {
return;
}

if ($event->{'Event-Subclass'} === 'conference::maintenance') {
$conference = $core->getConference($event->{'Conference-Unique-ID'});
switch ($event->{'Event-Subclass'}) {
case 'conference::maintenance':
if (!isset($event->Action, $event->{'Conference-Unique-ID'})) {
return;
}

if (!isset($conference)) {
return;
}
$conference = $core->getConference($event->{'Conference-Unique-ID'});

if (!isset($conference)) {
return;
}

switch ($event->Action) {
case 'stop-recording':
if (!isset($this->app->config->recordUrl)) {
return;
}

switch ($event->Action) {
case 'stop-recording':
if (!isset($this->app->config->recordUrl)) {
$params = [
'RecordFile' => $event->Path,
'RecordDuration' => isset($event->{'Milliseconds-Elapsed'}) ? (int)$event->{'Milliseconds-Elapsed'} : -1,
];

$this->app->inboundServer->logger->info('Conference Record Stop event ' . json_encode($params));
$this->app->inboundServer->controller->fireConferenceCallback($conference, $this->app->config->recordUrl, $this->app->config->defaultHttpMethod, $params);
return;

case 'conference-destroy':
$core->removeConference($event->{'Conference-Unique-ID'});
return;
}

return;

case 'amd::info':
$session = $core->getSession($event->{'Unique-ID'});

if (!isset($session)) {
return;
}

$this->app->inboundServer->logger->info('AMD event ' . json_encode($event));
$isAmdEvent = true;

$session->amdDuration = (int)(((int)$event->variable_amd_result_microtime - (int)$event->{'Caller-Channel-Answered-Time'}) / 1000);
$session->amdAnsweredBy = 'unknown';

$result = $event->variable_amd_result ?? 'NOTSURE';
$isMachine = false;

switch ($result) {
case 'HUMAN':
$session->amdAnsweredBy = 'human';
break;

case 'MACHINE':
$isMachine = true;
$session->amdAnsweredBy = 'machine_start';
break;
}

$session->amdMethod = $event->{"variable_{$this->app->config->appPrefix}_amd_method"} ?? Call::DEFAULT_AMD_METHOD;
$session->amdAsync = $event->{"variable_{$this->app->config->appPrefix}_amd_async"} === 'on';

if ($session->amdAsync) {
$urlVar = "variable_{$this->app->config->appPrefix}_amd_url";

if (isset($event->{$urlVar})) {
$session->amdUrl = $event->{$urlVar};
}
} else {
$session->amdUrl = $event->{"variable_{$this->app->config->appPrefix}_amd_target_url"};
}

if ($isMachine && isset($event->{"variable_{$this->app->config->appPrefix}_amd_msg_end"})) {
/* Kick off AVMD */
$this->app->inboundServer->logger->info('Activating AVMD (DetectMessageEnd enabled)');

$session->client->sendMsg(
(new ESL\Request\SendMsg)
->setHeader('call-command', 'execute')
->setHeader('execute-app-name', 'avmd_start')
);

$timeout = (float)$event->{"variable_{$this->app->config->appPrefix}_amd_timeout"} - ($session->amdDuration / 1000);

$session->avmdTimer = Loop::addTimer($timeout, function() use ($session): void {
unset($session->avmdTimer);

$params = [
'RecordFile' => $event->Path,
'RecordDuration' => isset($event->{'Milliseconds-Elapsed'}) ? (int)$event->{'Milliseconds-Elapsed'} : -1,
];
$session->client->sendMsg(
(new ESL\Request\SendMsg)
->setHeader('call-command', 'execute')
->setHeader('execute-app-name', 'event')
->setHeader('execute-app-arg', 'Event-Subclass=avmd::timeout,Event-Name=CUSTOM')
);
});

$this->app->inboundServer->logger->info('Conference Record Stop event ' . json_encode($params));
$this->app->inboundServer->controller->fireConferenceCallback($conference, $this->app->config->recordUrl, $this->app->config->defaultHttpMethod, $params);
return;
}

case 'avmd::timeout':
case 'avmd::beep':
if (!isset($isAmdEvent)) {
$session = $core->getSession($event->{'Unique-ID'});

if (!isset($session)) {
return;
}

$this->app->inboundServer->logger->info('AVMD event ' . json_encode($event));

case 'conference-destroy':
$core->removeConference($event->{'Conference-Unique-ID'});
if (isset($session->avmdTimer)) {
Loop::cancelTimer($session->avmdTimer);
unset($session->avmdTimer);
}

$session->client->sendMsg(
(new ESL\Request\SendMsg)
->setHeader('call-command', 'execute')
->setHeader('execute-app-name', 'avmd_stop')
);

$session->amdAnsweredBy = ($event->{'Event-Subclass'} === 'avmd::beep') ? 'machine_end_beep' : 'machine_end_other';
}

if (!isset($session)) {
return;
}
}

$method = $event->{"variable_{$this->app->config->appPrefix}_amd_method"} ?? Call::DEFAULT_AMD_METHOD;
$params = [
'AnsweredBy' => $session->amdAnsweredBy,
'MachineDetectionDuration' => $session->amdDuration,
];

if ($session->amdAsync) {
/* Asynchronous, just fire the callback, if configured */
if (isset($session->amdUrl)) {
$this->app->inboundServer->controller->fireSessionCallback($session, $session->amdUrl, $session->amdMethod, $params);
}
} else {
/* Synchronous? Kick off call flow execution */
$this->app->outboundServer->controller->fetchAndExecuteRestXml($session, $session->amdUrl, $session->amdMethod, $params);
}

return;
}
}
}
38 changes: 38 additions & 0 deletions src/Outbound/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
RestXmlSyntaxException,
UnrecognizedRestXmlException
};
use RTCKit\Eqivo\Rest\Controller\V0_1\Call;

use Psr\Http\Message\ResponseInterface;
use React\EventLoop\Loop;
Expand All @@ -33,6 +34,7 @@
use SimpleXMLIterator;
use stdClass as Event;
use function React\Promise\{
all,
reject,
resolve
};
Expand Down Expand Up @@ -210,6 +212,42 @@ public function onConnect(RemoteOutboundClient $client, ESL\Response\CommandRepl

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

/* Check if AMD is enabled */
if (
!empty($context["variable_{$this->app->config->appPrefix}_amd"]) &&
($context["variable_{$this->app->config->appPrefix}_amd"] === 'on')
) {
/* If synchronous, call flow execution must be blocked until AMD resolution is ready */
if (
!empty($context["variable_{$this->app->config->appPrefix}_amd_async"]) &&
($context["variable_{$this->app->config->appPrefix}_amd_async"] === 'off')
) {
$amdTimeoutAdj = (int)($context["variable_{$this->app->config->appPrefix}_amd_timeout"] ?? Call::DEFAULT_AMD_TIMEOUT) + 1;

$logger->info('Activating synchronous AMD');

all([
$session->client->sendMsg(
(new ESL\Request\SendMsg)
->setHeader('call-command', 'execute')
->setHeader('execute-app-name', 'set')
->setHeader('execute-app-arg', "{$this->app->config->appPrefix}_amd_target_url={$session->targetUrl}")
->setHeader('event-lock', 'true')
),
$session->client->sendMsg(
(new ESL\Request\SendMsg)
->setHeader('call-command', 'execute')
->setHeader('execute-app-name', 'playback')
->setHeader('execute-app-arg', 'file_string://silence_stream://' . ($amdTimeoutAdj * 1000))
),
]);

return;
} else {
$logger->info('Activating asynchronous AMD');
}
}

$this->fetchAndExecuteRestXml($session, $session->targetUrl)
->then(function () use ($session): PromiseInterface {
if (isset($session->hangupCause)) {
Expand Down
Loading

0 comments on commit cc899e0

Please sign in to comment.