ARI: External Media
The Channel resource has a new sub-resource "externalMedia". This allows an application to create a channel for the sole purpose of exchanging media with an external server. Once created, this channel could be placed into a bridge with existing channels to allow the external server to inject audio into the bridge or receive audio from the bridge. See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI for more information. Change-Id: I9618899198880b4c650354581b50c0401b58bc46
This commit is contained in:
parent
c00a010fe8
commit
d566314e38
|
@ -0,0 +1,10 @@
|
|||
Subject: ARI Channels
|
||||
|
||||
The Channel resource has a new sub-resource "externalMedia".
|
||||
This allows an application to create a channel for the sole purpose
|
||||
of exchanging media with an external server. Once created, this
|
||||
channel could be placed into a bridge with existing channels to
|
||||
allow the external server to inject audio into the bridge or
|
||||
receive audio from the bridge.
|
||||
See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI
|
||||
for more information.
|
|
@ -1385,6 +1385,62 @@ ari_validator ast_ari_validate_dialplan_cep_fn(void)
|
|||
return ast_ari_validate_dialplan_cep;
|
||||
}
|
||||
|
||||
int ast_ari_validate_external_media(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
struct ast_json_iter *iter;
|
||||
int has_channel = 0;
|
||||
|
||||
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
|
||||
if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_channel = 1;
|
||||
prop_is_valid = ast_ari_validate_channel(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ExternalMedia field channel failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("local_address", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ExternalMedia field local_address failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("local_port", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
prop_is_valid = ast_ari_validate_int(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ExternalMedia field local_port failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
{
|
||||
ast_log(LOG_ERROR,
|
||||
"ARI ExternalMedia has undocumented field %s\n",
|
||||
ast_json_object_iter_key(iter));
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_channel) {
|
||||
ast_log(LOG_ERROR, "ARI ExternalMedia missing required field channel\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ari_validator ast_ari_validate_external_media_fn(void)
|
||||
{
|
||||
return ast_ari_validate_external_media;
|
||||
}
|
||||
|
||||
int ast_ari_validate_rtpstat(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
|
|
|
@ -477,6 +477,24 @@ int ast_ari_validate_dialplan_cep(struct ast_json *json);
|
|||
*/
|
||||
ari_validator ast_ari_validate_dialplan_cep_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for ExternalMedia.
|
||||
*
|
||||
* ExternalMedia session.
|
||||
*
|
||||
* \param json JSON object to validate.
|
||||
* \returns True (non-zero) if valid.
|
||||
* \returns False (zero) if invalid.
|
||||
*/
|
||||
int ast_ari_validate_external_media(struct ast_json *json);
|
||||
|
||||
/*!
|
||||
* \brief Function pointer to ast_ari_validate_external_media().
|
||||
*
|
||||
* See \ref ast_ari_model_validators.h for more details.
|
||||
*/
|
||||
ari_validator ast_ari_validate_external_media_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for RTPstat.
|
||||
*
|
||||
|
@ -1522,6 +1540,10 @@ ari_validator ast_ari_validate_application_fn(void);
|
|||
* - context: string (required)
|
||||
* - exten: string (required)
|
||||
* - priority: long (required)
|
||||
* ExternalMedia
|
||||
* - channel: Channel (required)
|
||||
* - local_address: string
|
||||
* - local_port: int
|
||||
* RTPstat
|
||||
* - channel_uniqueid: string (required)
|
||||
* - local_maxjitter: double
|
||||
|
|
|
@ -1056,7 +1056,7 @@ end:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
||||
static struct ast_channel *ari_channels_handle_originate_with_id(const char *args_endpoint,
|
||||
const char *args_extension,
|
||||
const char *args_context,
|
||||
long args_priority,
|
||||
|
@ -1094,19 +1094,19 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
|| (assignedids.uniqueid2 && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid2))) {
|
||||
ast_ari_response_error(response, 400, "Bad Request",
|
||||
"Uniqueid length exceeds maximum of %d", AST_MAX_PUBLIC_UNIQUEID);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(args_endpoint)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request",
|
||||
"Endpoint must be specified");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(args_originator) && !ast_strlen_zero(args_formats)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request",
|
||||
"Originator and formats can't both be specified");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dialtech = ast_strdupa(args_endpoint);
|
||||
|
@ -1118,7 +1118,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
if (ast_strlen_zero(dialtech) || ast_strlen_zero(dialdevice)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request",
|
||||
"Invalid endpoint specified");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(args_app)) {
|
||||
|
@ -1126,7 +1126,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
|
||||
if (!appdata) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_str_set(&appdata, 0, "%s", args_app);
|
||||
|
@ -1137,7 +1137,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
origination = ast_calloc(1, sizeof(*origination) + ast_str_size(appdata) + 1);
|
||||
if (!origination) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strcpy(origination->appdata, ast_str_buffer(appdata));
|
||||
|
@ -1145,7 +1145,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
origination = ast_calloc(1, sizeof(*origination) + 1);
|
||||
if (!origination) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_copy_string(origination->context, S_OR(args_context, "default"), sizeof(origination->context));
|
||||
|
@ -1160,7 +1160,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
if (ipri == -1) {
|
||||
ast_log(AST_LOG_ERROR, "Requested label: %s can not be found in context: %s\n", args_label, args_context);
|
||||
ast_ari_response_error(response, 404, "Not Found", "Requested label can not be found");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
ast_debug(3, "Numeric value provided for label, jumping to that priority\n");
|
||||
|
@ -1170,7 +1170,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
ast_log(AST_LOG_ERROR, "Invalid priority label '%s' specified for extension %s in context: %s\n",
|
||||
args_label, args_extension, args_context);
|
||||
ast_ari_response_error(response, 400, "Bad Request", "Requested priority is illegal");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Our priority was provided by a label */
|
||||
|
@ -1184,14 +1184,14 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
} else {
|
||||
ast_ari_response_error(response, 400, "Bad Request",
|
||||
"Application or extension must be specified");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dial = ast_dial_create();
|
||||
if (!dial) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
ast_free(origination);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
ast_dial_set_user_data(dial, origination);
|
||||
|
||||
|
@ -1199,7 +1199,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
ast_ari_response_alloc_failed(response);
|
||||
ast_dial_destroy(dial);
|
||||
ast_free(origination);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (args_timeout > 0) {
|
||||
|
@ -1227,7 +1227,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
"Provided originator channel was not found");
|
||||
ast_dial_destroy(dial);
|
||||
ast_free(origination);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1240,7 +1240,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
ast_dial_destroy(dial);
|
||||
ast_free(origination);
|
||||
ast_channel_cleanup(other);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while ((format_name = ast_strip(strsep(&formats_copy, ",")))) {
|
||||
|
@ -1259,7 +1259,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
ast_channel_cleanup(other);
|
||||
ao2_ref(format_cap, -1);
|
||||
ao2_cleanup(fmt);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
ao2_ref(fmt, -1);
|
||||
}
|
||||
|
@ -1275,7 +1275,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
ast_dial_destroy(dial);
|
||||
ast_free(origination);
|
||||
ast_channel_cleanup(other);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_channel_cleanup(other);
|
||||
|
@ -1286,7 +1286,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
ast_ari_response_alloc_failed(response);
|
||||
ast_dial_destroy(dial);
|
||||
ast_free(origination);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
|
||||
|
@ -1351,8 +1351,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
|
|||
ast_ari_response_ok(response, ast_channel_snapshot_to_json(snapshot, NULL));
|
||||
}
|
||||
|
||||
ast_channel_unref(chan);
|
||||
return;
|
||||
return chan;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -1393,6 +1392,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
|
|||
struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_variable *variables = NULL;
|
||||
struct ast_channel *chan;
|
||||
|
||||
/* Parse any query parameters out of the body parameter */
|
||||
if (args->variables) {
|
||||
|
@ -1406,7 +1406,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
|
|||
}
|
||||
}
|
||||
|
||||
ari_channels_handle_originate_with_id(
|
||||
chan = ari_channels_handle_originate_with_id(
|
||||
args->endpoint,
|
||||
args->extension,
|
||||
args->context,
|
||||
|
@ -1422,6 +1422,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
|
|||
args->originator,
|
||||
args->formats,
|
||||
response);
|
||||
ast_channel_cleanup(chan);
|
||||
ast_variables_destroy(variables);
|
||||
}
|
||||
|
||||
|
@ -1430,6 +1431,7 @@ void ast_ari_channels_originate(struct ast_variable *headers,
|
|||
struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_variable *variables = NULL;
|
||||
struct ast_channel *chan;
|
||||
|
||||
/* Parse any query parameters out of the body parameter */
|
||||
if (args->variables) {
|
||||
|
@ -1443,7 +1445,7 @@ void ast_ari_channels_originate(struct ast_variable *headers,
|
|||
}
|
||||
}
|
||||
|
||||
ari_channels_handle_originate_with_id(
|
||||
chan = ari_channels_handle_originate_with_id(
|
||||
args->endpoint,
|
||||
args->extension,
|
||||
args->context,
|
||||
|
@ -1459,6 +1461,7 @@ void ast_ari_channels_originate(struct ast_variable *headers,
|
|||
args->originator,
|
||||
args->formats,
|
||||
response);
|
||||
ast_channel_cleanup(chan);
|
||||
ast_variables_destroy(variables);
|
||||
}
|
||||
|
||||
|
@ -2049,3 +2052,148 @@ void ast_ari_channels_rtpstatistics(struct ast_variable *headers,
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
static void external_media_rtp_udp(struct ast_ari_channels_external_media_args *args,
|
||||
struct ast_variable *variables,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
size_t endpoint_len;
|
||||
char *endpoint;
|
||||
struct ast_channel *chan;
|
||||
struct ast_json *json_chan;
|
||||
struct varshead *vars;
|
||||
|
||||
endpoint_len = strlen("UnicastRTP/") + strlen(args->external_host) + 1;
|
||||
endpoint = ast_alloca(endpoint_len);
|
||||
snprintf(endpoint, endpoint_len, "UnicastRTP/%s", args->external_host);
|
||||
|
||||
chan = ari_channels_handle_originate_with_id(
|
||||
endpoint,
|
||||
NULL,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
args->app,
|
||||
NULL,
|
||||
NULL,
|
||||
0,
|
||||
variables,
|
||||
args->channel_id,
|
||||
NULL,
|
||||
NULL,
|
||||
args->format,
|
||||
response);
|
||||
ast_variables_destroy(variables);
|
||||
|
||||
if (!chan) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point, response->message contains a channel object so we
|
||||
* need to save it then create a new ExternalMedia object and put the
|
||||
* channel in it.
|
||||
*/
|
||||
json_chan = response->message;
|
||||
response->message = ast_json_object_create();
|
||||
if (!response->message) {
|
||||
ast_channel_unref(chan);
|
||||
ast_json_unref(json_chan);
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
ast_json_object_set(response->message, "channel", json_chan);
|
||||
/*
|
||||
* At the time the channel snapshot was taken the channel variables might
|
||||
* not have been set so we try to grab them directly from the channel.
|
||||
*/
|
||||
ast_channel_lock(chan);
|
||||
vars = ast_channel_varshead(chan);
|
||||
if (vars && !AST_LIST_EMPTY(vars)) {
|
||||
struct ast_var_t *variables;
|
||||
|
||||
/* Put them all on the channel object */
|
||||
ast_json_object_set(json_chan, "channelvars", ast_json_channel_vars(vars));
|
||||
/* Grab out the local address and port */
|
||||
AST_LIST_TRAVERSE(vars, variables, entries) {
|
||||
if (!strcmp("UNICASTRTP_LOCAL_ADDRESS", ast_var_name(variables))) {
|
||||
ast_json_object_set(response->message, "local_address",
|
||||
ast_json_string_create(ast_var_value(variables)));
|
||||
}
|
||||
else if (!strcmp("UNICASTRTP_LOCAL_PORT", ast_var_name(variables))) {
|
||||
ast_json_object_set(response->message, "local_port",
|
||||
ast_json_integer_create(strtol(ast_var_value(variables), NULL, 10)));
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_channel_unlock(chan);
|
||||
ast_channel_unref(chan);
|
||||
}
|
||||
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/netsock2.h"
|
||||
|
||||
void ast_ari_channels_external_media(struct ast_variable *headers,
|
||||
struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_variable *variables = NULL;
|
||||
char *external_host;
|
||||
char *host = NULL;
|
||||
char *port = NULL;
|
||||
|
||||
ast_assert(response != NULL);
|
||||
|
||||
if (ast_strlen_zero(args->app)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request", "app cannot be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(args->external_host)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request", "external_host cannot be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
external_host = ast_strdupa(args->external_host);
|
||||
if (!ast_sockaddr_split_hostport(external_host, &host, &port, PARSE_PORT_REQUIRE)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request", "external_host must be <host>:<port>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(args->format)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request", "format cannot be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(args->encapsulation)) {
|
||||
args->encapsulation = "rtp";
|
||||
}
|
||||
if (ast_strlen_zero(args->transport)) {
|
||||
args->transport = "udp";
|
||||
}
|
||||
if (ast_strlen_zero(args->connection_type)) {
|
||||
args->connection_type = "client";
|
||||
}
|
||||
if (ast_strlen_zero(args->direction)) {
|
||||
args->direction = "both";
|
||||
}
|
||||
|
||||
if (args->variables) {
|
||||
struct ast_json *json_variables;
|
||||
|
||||
ast_ari_channels_external_media_parse_body(args->variables, args);
|
||||
json_variables = ast_json_object_get(args->variables, "variables");
|
||||
if (json_variables
|
||||
&& json_to_ast_variables(response, json_variables, &variables)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcasecmp(args->encapsulation, "rtp") == 0 && strcasecmp(args->transport, "udp") == 0) {
|
||||
external_media_rtp_udp(args, variables, response);
|
||||
} else {
|
||||
ast_ari_response_error(
|
||||
response, 501, "Not Implemented",
|
||||
"The encapsulation and/or transport is not supported");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -822,5 +822,47 @@ struct ast_ari_channels_rtpstatistics_args {
|
|||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_channels_rtpstatistics(struct ast_variable *headers, struct ast_ari_channels_rtpstatistics_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_channels_external_media() */
|
||||
struct ast_ari_channels_external_media_args {
|
||||
/*! The unique id to assign the channel on creation. */
|
||||
const char *channel_id;
|
||||
/*! Stasis Application to place channel into */
|
||||
const char *app;
|
||||
/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */
|
||||
struct ast_json *variables;
|
||||
/*! Hostname/ip:port of external host */
|
||||
const char *external_host;
|
||||
/*! Payload encapsulation protocol */
|
||||
const char *encapsulation;
|
||||
/*! Transport protocol */
|
||||
const char *transport;
|
||||
/*! Connection type (client/server) */
|
||||
const char *connection_type;
|
||||
/*! Format to encode audio in */
|
||||
const char *format;
|
||||
/*! External media direction */
|
||||
const char *direction;
|
||||
};
|
||||
/*!
|
||||
* \brief Body parsing function for /channels/externalMedia.
|
||||
* \param body The JSON body from which to parse parameters.
|
||||
* \param[out] args The args structure to parse into.
|
||||
* \retval zero on success
|
||||
* \retval non-zero on failure
|
||||
*/
|
||||
int ast_ari_channels_external_media_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_channels_external_media_args *args);
|
||||
|
||||
/*!
|
||||
* \brief Start an External Media session.
|
||||
*
|
||||
* Create a channel to an External Media source/sink.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_channels_external_media(struct ast_variable *headers, struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response);
|
||||
|
||||
#endif /* _ASTERISK_RESOURCE_CHANNELS_H */
|
||||
|
|
|
@ -2804,6 +2804,128 @@ static void ast_ari_channels_rtpstatistics_cb(
|
|||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
int ast_ari_channels_external_media_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_channels_external_media_args *args)
|
||||
{
|
||||
struct ast_json *field;
|
||||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "channelId");
|
||||
if (field) {
|
||||
args->channel_id = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "app");
|
||||
if (field) {
|
||||
args->app = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "external_host");
|
||||
if (field) {
|
||||
args->external_host = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "encapsulation");
|
||||
if (field) {
|
||||
args->encapsulation = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "transport");
|
||||
if (field) {
|
||||
args->transport = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "connection_type");
|
||||
if (field) {
|
||||
args->connection_type = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "format");
|
||||
if (field) {
|
||||
args->format = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "direction");
|
||||
if (field) {
|
||||
args->direction = ast_json_string_get(field);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /channels/externalMedia.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void ast_ari_channels_external_media_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_channels_external_media_args args = {};
|
||||
struct ast_variable *i;
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "channelId") == 0) {
|
||||
args.channel_id = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "app") == 0) {
|
||||
args.app = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "external_host") == 0) {
|
||||
args.external_host = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "encapsulation") == 0) {
|
||||
args.encapsulation = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "transport") == 0) {
|
||||
args.transport = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "connection_type") == 0) {
|
||||
args.connection_type = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "format") == 0) {
|
||||
args.format = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "direction") == 0) {
|
||||
args.direction = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
args.variables = body;
|
||||
ast_ari_channels_external_media(headers, &args, response);
|
||||
#if defined(AST_DEVMODE)
|
||||
code = response->response_code;
|
||||
|
||||
switch (code) {
|
||||
case 0: /* Implementation is still a stub, or the code wasn't set */
|
||||
is_valid = response->message == NULL;
|
||||
break;
|
||||
case 500: /* Internal Server Error */
|
||||
case 501: /* Not Implemented */
|
||||
case 400: /* Invalid parameters */
|
||||
case 409: /* Channel is not in a Stasis application; Channel is already bridged */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_external_media(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /channels/externalMedia\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /channels/externalMedia\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
|
@ -3000,14 +3122,23 @@ static struct stasis_rest_handlers channels_channelId = {
|
|||
.children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/channels.json */
|
||||
static struct stasis_rest_handlers channels_externalMedia = {
|
||||
.path_segment = "externalMedia",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = ast_ari_channels_external_media_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/channels.json */
|
||||
static struct stasis_rest_handlers channels = {
|
||||
.path_segment = "channels",
|
||||
.callbacks = {
|
||||
[AST_HTTP_GET] = ast_ari_channels_list_cb,
|
||||
[AST_HTTP_POST] = ast_ari_channels_originate_cb,
|
||||
},
|
||||
.num_children = 2,
|
||||
.children = { &channels_create,&channels_channelId, }
|
||||
.num_children = 3,
|
||||
.children = { &channels_create,&channels_channelId,&channels_externalMedia, }
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
|
|
|
@ -1748,6 +1748,131 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/channels/externalMedia",
|
||||
"description": "Create a channel to an External Media source/sink.",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
"summary": "Start an External Media session.",
|
||||
"notes": "Create a channel to an External Media source/sink.",
|
||||
"nickname": "externalMedia",
|
||||
"responseClass": "ExternalMedia",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "channelId",
|
||||
"description": "The unique id to assign the channel on creation.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "app",
|
||||
"description": "Stasis Application to place channel into",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "variables",
|
||||
"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
|
||||
"paramType": "body",
|
||||
"required": false,
|
||||
"dataType": "containers",
|
||||
"allowMultiple": false
|
||||
},
|
||||
{
|
||||
"name": "external_host",
|
||||
"description": "Hostname/ip:port of external host",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "encapsulation",
|
||||
"description": "Payload encapsulation protocol",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string",
|
||||
"defaultValue": "rtp",
|
||||
"allowableValues": {
|
||||
"valueType": "LIST",
|
||||
"values": [
|
||||
"rtp"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "transport",
|
||||
"description": "Transport protocol",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string",
|
||||
"defaultValue": "udp",
|
||||
"allowableValues": {
|
||||
"valueType": "LIST",
|
||||
"values": [
|
||||
"udp"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "connection_type",
|
||||
"description": "Connection type (client/server)",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string",
|
||||
"defaultValue": "client",
|
||||
"allowableValues": {
|
||||
"valueType": "LIST",
|
||||
"values": [
|
||||
"client"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"description": "Format to encode audio in",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "direction",
|
||||
"description": "External media direction",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string",
|
||||
"defaultValue": "both",
|
||||
"allowableValues": {
|
||||
"valueType": "LIST",
|
||||
"values": [
|
||||
"both"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
{
|
||||
"code": 400,
|
||||
"reason": "Invalid parameters"
|
||||
},
|
||||
{
|
||||
"code": 409,
|
||||
"reason": "Channel is not in a Stasis application; Channel is already bridged"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"models": {
|
||||
|
@ -2034,6 +2159,27 @@
|
|||
"description": "Channel variables"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExternalMedia": {
|
||||
"id": "ExternalMedia",
|
||||
"description": "ExternalMedia session.",
|
||||
"properties": {
|
||||
"channel": {
|
||||
"required": true,
|
||||
"type": "Channel",
|
||||
"description": "The Asterisk channel representing the external media"
|
||||
},
|
||||
"local_address": {
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"description": "The local ip address used"
|
||||
},
|
||||
"local_port": {
|
||||
"required": false,
|
||||
"type": "int",
|
||||
"description": "The local ip port used"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue