mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-05 20:20:07 +00:00
ARI: Make bridges/{bridgeID}/play queue sound files
Previously multiple play actions against a bridge at one time would cause the sounds to play simultaneously on the bridge. Now if a sound is already playing, the play action will queue playback to occur after the completion of other sounds currently on the queue. (closes issue ASTERISK-22677) Reported by: John Bigelow Review: https://reviewboard.asterisk.org/r/3379/ ........ Merged revisions 412639 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@412641 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
3
CHANGES
3
CHANGES
@@ -150,7 +150,8 @@ ARI
|
||||
(POST bridges/my-bridge-id) or as a query parameter.
|
||||
|
||||
* A playbackId can be provided when starting a playback, either in the uri
|
||||
(POST channels/my-channel-id/play/my-playback-id) or as a query parameter.
|
||||
(POST channels/my-channel-id/play/my-playback-id /
|
||||
POST bridges/my-bridge-id/play/my-playback-id) or as a query parameter.
|
||||
|
||||
* A snoop channel can be started with a snoopId, in the uri or query.
|
||||
|
||||
|
@@ -374,6 +374,15 @@ void stasis_app_control_execute_until_exhausted(
|
||||
struct ast_channel *chan,
|
||||
struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Check if a control is marked as done
|
||||
* \since 12.2.0
|
||||
*
|
||||
* \param control Which control object is being evaluated
|
||||
*/
|
||||
int stasis_app_control_is_done(
|
||||
struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Returns the uniqueid of the channel associated with this control
|
||||
*
|
||||
@@ -637,6 +646,30 @@ struct ast_channel *stasis_app_bridge_moh_channel(
|
||||
int stasis_app_bridge_moh_stop(
|
||||
struct ast_bridge *bridge);
|
||||
|
||||
/*!
|
||||
* \brief Finds an existing ARI playback channel in a bridge
|
||||
*
|
||||
* \param bridge Bridge we want to find the playback channel for
|
||||
*
|
||||
* \return NULL if the playback channel can not be found for any reason.
|
||||
* \return Pointer to the ;1 end of the playback channel chain.
|
||||
*/
|
||||
struct ast_channel *stasis_app_bridge_playback_channel_find(
|
||||
struct ast_bridge *bridge);
|
||||
|
||||
/*!
|
||||
* \brief Adds a channel to the list of ARI playback channels for bridges.
|
||||
*
|
||||
* \param bridge Bridge we are adding the playback channel for
|
||||
* \param chan Channel being added as a playback channel (must be ;1)
|
||||
*
|
||||
* \retval -1 failed to add channel for any reason
|
||||
* \retval 0 on success
|
||||
*/
|
||||
int stasis_app_bridge_playback_channel_add(struct ast_bridge *bridge,
|
||||
struct ast_channel *chan,
|
||||
struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Result codes used when adding/removing channels to/from bridges.
|
||||
*/
|
||||
|
@@ -320,31 +320,93 @@ static struct ast_channel *prepare_bridge_media_channel(const char *type)
|
||||
return ast_request(type, cap, NULL, NULL, "ARI", NULL);
|
||||
}
|
||||
|
||||
void ast_ari_bridges_play(struct ast_variable *headers,
|
||||
struct ast_ari_bridges_play_args *args,
|
||||
struct ast_ari_response *response)
|
||||
/*!
|
||||
* \brief Performs common setup for a bridge playback operation
|
||||
* with both new controls and when existing controls are found.
|
||||
*
|
||||
* \param args_media media string split from arguments
|
||||
* \param args_lang language string split from arguments
|
||||
* \param args_offset_ms milliseconds offset split from arguments
|
||||
* \param args_playback_id string to use for playback split from
|
||||
* arguments (null valid)
|
||||
* \param response ARI response being built
|
||||
* \param bridge Bridge the playback is being peformed on
|
||||
* \param control Control being used for the playback channel
|
||||
* \param json contents of the response to ARI
|
||||
* \param playback_url stores playback URL for use with response
|
||||
*
|
||||
* \retval -1 operation failed
|
||||
* \retval operation was successful
|
||||
*/
|
||||
static int ari_bridges_play_helper(const char *args_media,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
const char *args_playback_id,
|
||||
struct ast_ari_response *response,
|
||||
struct ast_bridge *bridge,
|
||||
struct stasis_app_control *control,
|
||||
struct ast_json **json,
|
||||
char **playback_url)
|
||||
{
|
||||
RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
|
||||
RAII_VAR(struct ast_channel *, play_channel, NULL, ast_hangup);
|
||||
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
|
||||
RAII_VAR(char *, playback_url, NULL, ast_free);
|
||||
|
||||
const char *language;
|
||||
|
||||
snapshot = stasis_app_control_get_snapshot(control);
|
||||
if (!snapshot) {
|
||||
ast_ari_response_error(
|
||||
response, 500, "Internal Error", "Failed to get control snapshot");
|
||||
return -1;
|
||||
}
|
||||
|
||||
language = S_OR(args_lang, snapshot->language);
|
||||
|
||||
playback = stasis_app_control_play_uri(control, args_media, language,
|
||||
bridge->uniqueid, STASIS_PLAYBACK_TARGET_BRIDGE, args_skipms,
|
||||
args_offset_ms, args_playback_id);
|
||||
|
||||
if (!playback) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_asprintf(playback_url, "/playback/%s",
|
||||
stasis_app_playback_get_id(playback)) == -1) {
|
||||
playback_url = NULL;
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*json = stasis_app_playback_to_json(playback);
|
||||
if (!*json) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ari_bridges_play_new(const char *args_media,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
const char *args_playback_id,
|
||||
struct ast_ari_response *response,
|
||||
struct ast_bridge *bridge)
|
||||
{
|
||||
RAII_VAR(struct ast_channel *, play_channel, NULL, ast_hangup);
|
||||
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
RAII_VAR(struct stasis_forward *, channel_forward, NULL, stasis_forward_cancel);
|
||||
RAII_VAR(char *, playback_url, NULL, ast_free);
|
||||
|
||||
struct stasis_topic *channel_topic;
|
||||
struct stasis_topic *bridge_topic;
|
||||
struct bridge_channel_control_thread_data *thread_data;
|
||||
const char *language;
|
||||
pthread_t threadid;
|
||||
|
||||
ast_assert(response != NULL);
|
||||
|
||||
if (!bridge) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(play_channel = prepare_bridge_media_channel("Announcer"))) {
|
||||
ast_ari_response_error(
|
||||
response, 500, "Internal Error", "Could not create playback channel");
|
||||
@@ -378,34 +440,16 @@ void ast_ari_bridges_play(struct ast_variable *headers,
|
||||
return;
|
||||
}
|
||||
|
||||
snapshot = stasis_app_control_get_snapshot(control);
|
||||
if (!snapshot) {
|
||||
ast_ari_response_error(
|
||||
response, 500, "Internal Error", "Failed to get control snapshot");
|
||||
ao2_lock(control);
|
||||
if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response, bridge, control,
|
||||
&json, &playback_url)) {
|
||||
ao2_unlock(control);
|
||||
return;
|
||||
}
|
||||
ao2_unlock(control);
|
||||
|
||||
language = S_OR(args->lang, snapshot->language);
|
||||
|
||||
playback = stasis_app_control_play_uri(control, args->media, language,
|
||||
args->bridge_id, STASIS_PLAYBACK_TARGET_BRIDGE, args->skipms,
|
||||
args->offsetms, NULL);
|
||||
|
||||
if (!playback) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
ast_asprintf(&playback_url, "/playback/%s",
|
||||
stasis_app_playback_get_id(playback));
|
||||
|
||||
if (!playback_url) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
json = stasis_app_playback_to_json(playback);
|
||||
if (!json) {
|
||||
if (stasis_app_bridge_playback_channel_add(bridge, play_channel, control)) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
@@ -435,6 +479,134 @@ void ast_ari_bridges_play(struct ast_variable *headers,
|
||||
ast_ari_response_created(response, playback_url, ast_json_ref(json));
|
||||
}
|
||||
|
||||
enum play_found_result {
|
||||
PLAY_FOUND_SUCCESS,
|
||||
PLAY_FOUND_FAILURE,
|
||||
PLAY_FOUND_CHANNEL_UNAVAILABLE,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Performs common setup for a bridge playback operation
|
||||
* with both new controls and when existing controls are found.
|
||||
*
|
||||
* \param args_media media string split from arguments
|
||||
* \param args_lang language string split from arguments
|
||||
* \param args_offset_ms milliseconds offset split from arguments
|
||||
* \param args_playback_id string to use for playback split from
|
||||
* arguments (null valid)
|
||||
* \param response ARI response being built
|
||||
* \param bridge Bridge the playback is being peformed on
|
||||
* \param found_channel The channel that was found controlling playback
|
||||
*
|
||||
* \retval PLAY_FOUND_SUCCESS The operation was successful
|
||||
* \retval PLAY_FOUND_FAILURE The operation failed (terminal failure)
|
||||
* \retval PLAY_FOUND_CHANNEL_UNAVAILABLE The operation failed because
|
||||
* the channel requested to playback with is breaking down.
|
||||
*/
|
||||
static enum play_found_result ari_bridges_play_found(const char *args_media,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
const char *args_playback_id,
|
||||
struct ast_ari_response *response,
|
||||
struct ast_bridge *bridge,
|
||||
struct ast_channel *found_channel)
|
||||
{
|
||||
RAII_VAR(struct ast_channel *, play_channel, found_channel, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
|
||||
RAII_VAR(char *, playback_url, NULL, ast_free);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
|
||||
control = stasis_app_control_find_by_channel(play_channel);
|
||||
if (!control) {
|
||||
ast_ari_response_error(
|
||||
response, 500, "Internal Error", "Failed to get control snapshot");
|
||||
return PLAY_FOUND_FAILURE;
|
||||
}
|
||||
|
||||
ao2_lock(control);
|
||||
if (stasis_app_control_is_done(control)) {
|
||||
/* We failed to queue the action. Bailout and return that we aren't terminal. */
|
||||
ao2_unlock(control);
|
||||
return PLAY_FOUND_CHANNEL_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response, bridge, control,
|
||||
&json, &playback_url)) {
|
||||
ao2_unlock(control);
|
||||
return PLAY_FOUND_FAILURE;
|
||||
}
|
||||
ao2_unlock(control);
|
||||
|
||||
ast_ari_response_created(response, playback_url, ast_json_ref(json));
|
||||
return PLAY_FOUND_SUCCESS;
|
||||
}
|
||||
|
||||
static void ari_bridges_handle_play(
|
||||
const char *args_bridge_id,
|
||||
const char *args_media,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
const char *args_playback_id,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args_bridge_id), ao2_cleanup);
|
||||
struct ast_channel *play_channel;
|
||||
|
||||
ast_assert(response != NULL);
|
||||
|
||||
if (!bridge) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ((play_channel = stasis_app_bridge_playback_channel_find(bridge))) {
|
||||
/* If ari_bridges_play_found fails because the channel is unavailable for
|
||||
* playback, The channel will be removed from the playback list soon. We
|
||||
* can keep trying to get channels from the list until we either get one
|
||||
* that will work or else there isn't a channel for this bridge anymore,
|
||||
* in which case we'll revert to ari_bridges_play_new.
|
||||
*/
|
||||
if (ari_bridges_play_found(args_media, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response,bridge,
|
||||
play_channel) == PLAY_FOUND_CHANNEL_UNAVAILABLE) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ari_bridges_play_new(args_media, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response, bridge);
|
||||
}
|
||||
|
||||
|
||||
void ast_ari_bridges_play(struct ast_variable *headers,
|
||||
struct ast_ari_bridges_play_args *args,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
ari_bridges_handle_play(args->bridge_id,
|
||||
args->media,
|
||||
args->lang,
|
||||
args->offsetms,
|
||||
args->skipms,
|
||||
args->playback_id,
|
||||
response);
|
||||
}
|
||||
|
||||
void ast_ari_bridges_play_with_id(struct ast_variable *headers,
|
||||
struct ast_ari_bridges_play_with_id_args *args,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
ari_bridges_handle_play(args->bridge_id,
|
||||
args->media,
|
||||
args->lang,
|
||||
args->offsetms,
|
||||
args->skipms,
|
||||
args->playback_id,
|
||||
response);
|
||||
}
|
||||
|
||||
void ast_ari_bridges_record(struct ast_variable *headers,
|
||||
struct ast_ari_bridges_record_args *args,
|
||||
struct ast_ari_response *response)
|
||||
@@ -573,8 +745,9 @@ void ast_ari_bridges_record(struct ast_variable *headers,
|
||||
}
|
||||
ast_uri_encode(args->name, uri_encoded_name, uri_name_maxlen, ast_uri_http);
|
||||
|
||||
ast_asprintf(&recording_url, "/recordings/live/%s", uri_encoded_name);
|
||||
if (!recording_url) {
|
||||
if (ast_asprintf(&recording_url, "/recordings/live/%s",
|
||||
uri_encoded_name) == -1) {
|
||||
recording_url = NULL;
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
@@ -253,6 +253,8 @@ struct ast_ari_bridges_play_args {
|
||||
int offsetms;
|
||||
/*! \brief Number of milliseconds to skip for forward/reverse operations. */
|
||||
int skipms;
|
||||
/*! \brief Playback Id. */
|
||||
const char *playback_id;
|
||||
};
|
||||
/*!
|
||||
* \brief Body parsing function for /bridges/{bridgeId}/play.
|
||||
@@ -275,6 +277,42 @@ int ast_ari_bridges_play_parse_body(
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_bridges_play(struct ast_variable *headers, struct ast_ari_bridges_play_args *args, struct ast_ari_response *response);
|
||||
/*! \brief Argument struct for ast_ari_bridges_play_with_id() */
|
||||
struct ast_ari_bridges_play_with_id_args {
|
||||
/*! \brief Bridge's id */
|
||||
const char *bridge_id;
|
||||
/*! \brief Playback ID. */
|
||||
const char *playback_id;
|
||||
/*! \brief Media's URI to play. */
|
||||
const char *media;
|
||||
/*! \brief For sounds, selects language for sound. */
|
||||
const char *lang;
|
||||
/*! \brief Number of media to skip before playing. */
|
||||
int offsetms;
|
||||
/*! \brief Number of milliseconds to skip for forward/reverse operations. */
|
||||
int skipms;
|
||||
};
|
||||
/*!
|
||||
* \brief Body parsing function for /bridges/{bridgeId}/play/{playbackId}.
|
||||
* \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_bridges_play_with_id_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_bridges_play_with_id_args *args);
|
||||
|
||||
/*!
|
||||
* \brief Start playback of media on a bridge.
|
||||
*
|
||||
* The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_bridges_play_with_id(struct ast_variable *headers, struct ast_ari_bridges_play_with_id_args *args, struct ast_ari_response *response);
|
||||
/*! \brief Argument struct for ast_ari_bridges_record() */
|
||||
struct ast_ari_bridges_record_args {
|
||||
/*! \brief Bridge's id */
|
||||
|
@@ -411,9 +411,9 @@ static void ari_channels_handle_play(
|
||||
return;
|
||||
}
|
||||
|
||||
ast_asprintf(&playback_url, "/playback/%s",
|
||||
stasis_app_playback_get_id(playback));
|
||||
if (!playback_url) {
|
||||
if (ast_asprintf(&playback_url, "/playback/%s",
|
||||
stasis_app_playback_get_id(playback)) == -1) {
|
||||
playback_url = NULL;
|
||||
ast_ari_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Out of memory");
|
||||
@@ -579,8 +579,9 @@ void ast_ari_channels_record(struct ast_variable *headers,
|
||||
ast_uri_encode(args->name, uri_encoded_name, uri_name_maxlen,
|
||||
ast_uri_http);
|
||||
|
||||
ast_asprintf(&recording_url, "/recordings/live/%s", uri_encoded_name);
|
||||
if (!recording_url) {
|
||||
if (ast_asprintf(&recording_url, "/recordings/live/%s",
|
||||
uri_encoded_name) == -1) {
|
||||
recording_url = NULL;
|
||||
ast_ari_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Out of memory");
|
||||
|
@@ -948,6 +948,10 @@ int ast_ari_bridges_play_parse_body(
|
||||
if (field) {
|
||||
args->skipms = ast_json_integer_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "playbackId");
|
||||
if (field) {
|
||||
args->playback_id = ast_json_string_get(field);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -984,6 +988,9 @@ static void ast_ari_bridges_play_cb(
|
||||
if (strcmp(i->name, "skipms") == 0) {
|
||||
args.skipms = atoi(i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "playbackId") == 0) {
|
||||
args.playback_id = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
@@ -1042,6 +1049,128 @@ static void ast_ari_bridges_play_cb(
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
int ast_ari_bridges_play_with_id_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_bridges_play_with_id_args *args)
|
||||
{
|
||||
struct ast_json *field;
|
||||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "media");
|
||||
if (field) {
|
||||
args->media = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "lang");
|
||||
if (field) {
|
||||
args->lang = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "offsetms");
|
||||
if (field) {
|
||||
args->offsetms = ast_json_integer_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "skipms");
|
||||
if (field) {
|
||||
args->skipms = ast_json_integer_get(field);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /bridges/{bridgeId}/play/{playbackId}.
|
||||
* \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_bridges_play_with_id_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_bridges_play_with_id_args args = {};
|
||||
struct ast_variable *i;
|
||||
RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "media") == 0) {
|
||||
args.media = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "lang") == 0) {
|
||||
args.lang = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "offsetms") == 0) {
|
||||
args.offsetms = atoi(i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "skipms") == 0) {
|
||||
args.skipms = atoi(i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "bridgeId") == 0) {
|
||||
args.bridge_id = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "playbackId") == 0) {
|
||||
args.playback_id = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
/* Look for a JSON request entity */
|
||||
body = ast_http_get_json(ser, headers);
|
||||
if (!body) {
|
||||
switch (errno) {
|
||||
case EFBIG:
|
||||
ast_ari_response_error(response, 413, "Request Entity Too Large", "Request body too large");
|
||||
goto fin;
|
||||
case ENOMEM:
|
||||
ast_ari_response_error(response, 500, "Internal Server Error", "Error processing request");
|
||||
goto fin;
|
||||
case EIO:
|
||||
ast_ari_response_error(response, 400, "Bad Request", "Error parsing request body");
|
||||
goto fin;
|
||||
}
|
||||
}
|
||||
if (ast_ari_bridges_play_with_id_parse_body(body, &args)) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
ast_ari_bridges_play_with_id(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 404: /* Bridge not found */
|
||||
case 409: /* Bridge not in a Stasis application */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_playback(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/play/{playbackId}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/play/{playbackId}\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
@@ -1217,13 +1346,23 @@ static struct stasis_rest_handlers bridges_bridgeId_moh = {
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/bridges.{format} */
|
||||
static struct stasis_rest_handlers bridges_bridgeId_play_playbackId = {
|
||||
.path_segment = "playbackId",
|
||||
.is_wildcard = 1,
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = ast_ari_bridges_play_with_id_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/bridges.{format} */
|
||||
static struct stasis_rest_handlers bridges_bridgeId_play = {
|
||||
.path_segment = "play",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = ast_ari_bridges_play_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
.num_children = 1,
|
||||
.children = { &bridges_bridgeId_play_playbackId, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/bridges.{format} */
|
||||
static struct stasis_rest_handlers bridges_bridgeId_record = {
|
||||
|
146
res/res_stasis.c
146
res/res_stasis.c
@@ -103,6 +103,8 @@ struct ao2_container *app_bridges;
|
||||
|
||||
struct ao2_container *app_bridges_moh;
|
||||
|
||||
struct ao2_container *app_bridges_playback;
|
||||
|
||||
const char *stasis_app_name(const struct stasis_app *app)
|
||||
{
|
||||
return app_name(app);
|
||||
@@ -341,26 +343,26 @@ static int bridges_compare(void *obj, void *arg, int flags)
|
||||
}
|
||||
|
||||
/*!
|
||||
* Used with app_bridges_moh, provides links between bridges and existing music
|
||||
* on hold channels that are being used with them.
|
||||
* Used with app_bridges_moh and app_bridge_control, they provide links
|
||||
* between bridges and channels used for ARI application purposes
|
||||
*/
|
||||
struct stasis_app_bridge_moh_wrapper {
|
||||
struct stasis_app_bridge_channel_wrapper {
|
||||
AST_DECLARE_STRING_FIELDS(
|
||||
AST_STRING_FIELD(channel_id);
|
||||
AST_STRING_FIELD(bridge_id);
|
||||
);
|
||||
};
|
||||
|
||||
static void stasis_app_bridge_moh_wrapper_destructor(void *obj)
|
||||
static void stasis_app_bridge_channel_wrapper_destructor(void *obj)
|
||||
{
|
||||
struct stasis_app_bridge_moh_wrapper *wrapper = obj;
|
||||
struct stasis_app_bridge_channel_wrapper *wrapper = obj;
|
||||
ast_string_field_free_memory(wrapper);
|
||||
}
|
||||
|
||||
/*! AO2 hash function for the bridges moh container */
|
||||
static int bridges_moh_hash_fn(const void *obj, const int flags)
|
||||
static int bridges_channel_hash_fn(const void *obj, const int flags)
|
||||
{
|
||||
const struct stasis_app_bridge_moh_wrapper *wrapper;
|
||||
const struct stasis_app_bridge_channel_wrapper *wrapper;
|
||||
const char *key;
|
||||
|
||||
switch (flags & OBJ_SEARCH_MASK) {
|
||||
@@ -379,10 +381,10 @@ static int bridges_moh_hash_fn(const void *obj, const int flags)
|
||||
return ast_str_hash(key);
|
||||
}
|
||||
|
||||
static int bridges_moh_sort_fn(const void *obj_left, const void *obj_right, const int flags)
|
||||
static int bridges_channel_sort_fn(const void *obj_left, const void *obj_right, const int flags)
|
||||
{
|
||||
const struct stasis_app_bridge_moh_wrapper *left = obj_left;
|
||||
const struct stasis_app_bridge_moh_wrapper *right = obj_right;
|
||||
const struct stasis_app_bridge_channel_wrapper *left = obj_left;
|
||||
const struct stasis_app_bridge_channel_wrapper *right = obj_right;
|
||||
const char *right_key = obj_right;
|
||||
int cmp;
|
||||
|
||||
@@ -469,7 +471,7 @@ static void *moh_channel_thread(void *data)
|
||||
*/
|
||||
static struct ast_channel *bridge_moh_create(struct ast_bridge *bridge)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_bridge_moh_wrapper *, new_wrapper, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_app_bridge_channel_wrapper *, new_wrapper, NULL, ao2_cleanup);
|
||||
RAII_VAR(char *, bridge_id, ast_strdup(bridge->uniqueid), ast_free);
|
||||
struct ast_channel *chan;
|
||||
pthread_t threadid;
|
||||
@@ -498,7 +500,7 @@ static struct ast_channel *bridge_moh_create(struct ast_bridge *bridge)
|
||||
}
|
||||
|
||||
new_wrapper = ao2_alloc_options(sizeof(*new_wrapper),
|
||||
stasis_app_bridge_moh_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
|
||||
stasis_app_bridge_channel_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
|
||||
if (!new_wrapper) {
|
||||
ast_hangup(chan);
|
||||
return NULL;
|
||||
@@ -528,7 +530,7 @@ static struct ast_channel *bridge_moh_create(struct ast_bridge *bridge)
|
||||
|
||||
struct ast_channel *stasis_app_bridge_moh_channel(struct ast_bridge *bridge)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_app_bridge_channel_wrapper *, moh_wrapper, NULL, ao2_cleanup);
|
||||
|
||||
{
|
||||
SCOPED_AO2LOCK(lock, app_bridges_moh);
|
||||
@@ -544,7 +546,7 @@ struct ast_channel *stasis_app_bridge_moh_channel(struct ast_bridge *bridge)
|
||||
|
||||
int stasis_app_bridge_moh_stop(struct ast_bridge *bridge)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_app_bridge_channel_wrapper *, moh_wrapper, NULL, ao2_cleanup);
|
||||
struct ast_channel *chan;
|
||||
|
||||
moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_SEARCH_KEY | OBJ_UNLINK);
|
||||
@@ -564,6 +566,92 @@ int stasis_app_bridge_moh_stop(struct ast_bridge *bridge)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Removes the bridge to playback channel link */
|
||||
static void remove_bridge_playback(char *bridge_id)
|
||||
{
|
||||
struct stasis_app_bridge_channel_wrapper *wrapper;
|
||||
struct stasis_app_control *control;
|
||||
|
||||
wrapper = ao2_find(app_bridges_playback, bridge_id, OBJ_SEARCH_KEY | OBJ_UNLINK);
|
||||
|
||||
if (wrapper) {
|
||||
control = stasis_app_control_find_by_channel_id(wrapper->channel_id);
|
||||
if (control) {
|
||||
ao2_unlink(app_controls, control);
|
||||
ao2_ref(control, -1);
|
||||
}
|
||||
ao2_ref(wrapper, -1);
|
||||
}
|
||||
ast_free(bridge_id);
|
||||
}
|
||||
|
||||
static void playback_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
|
||||
{
|
||||
char *bridge_id = data;
|
||||
|
||||
remove_bridge_playback(bridge_id);
|
||||
}
|
||||
|
||||
static void playback_after_bridge_cb(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *bridge_id = data;
|
||||
|
||||
remove_bridge_playback(bridge_id);
|
||||
}
|
||||
|
||||
int stasis_app_bridge_playback_channel_add(struct ast_bridge *bridge,
|
||||
struct ast_channel *chan,
|
||||
struct stasis_app_control *control)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_bridge_channel_wrapper *, new_wrapper, NULL, ao2_cleanup);
|
||||
char *bridge_id = ast_strdup(bridge->uniqueid);
|
||||
|
||||
if (!bridge_id) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_bridge_set_after_callback(chan,
|
||||
playback_after_bridge_cb, playback_after_bridge_cb_failed, bridge_id)) {
|
||||
ast_free(bridge_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
new_wrapper = ao2_alloc_options(sizeof(*new_wrapper),
|
||||
stasis_app_bridge_channel_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
|
||||
if (!new_wrapper) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_string_field_init(new_wrapper, 32)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_string_field_set(new_wrapper, bridge_id, bridge->uniqueid);
|
||||
ast_string_field_set(new_wrapper, channel_id, ast_channel_uniqueid(chan));
|
||||
|
||||
if (!ao2_link(app_bridges_playback, new_wrapper)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ao2_link(app_controls, control);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ast_channel *stasis_app_bridge_playback_channel_find(struct ast_bridge *bridge)
|
||||
{
|
||||
struct stasis_app_bridge_channel_wrapper *playback_wrapper;
|
||||
struct ast_channel *chan;
|
||||
|
||||
playback_wrapper = ao2_find(app_bridges_playback, bridge->uniqueid, OBJ_SEARCH_KEY);
|
||||
if (!playback_wrapper) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
chan = ast_channel_get_by_name(playback_wrapper->channel_id);
|
||||
ao2_ref(playback_wrapper, -1);
|
||||
return chan;
|
||||
}
|
||||
|
||||
struct ast_bridge *stasis_app_bridge_find_by_id(
|
||||
const char *bridge_id)
|
||||
{
|
||||
@@ -720,13 +808,31 @@ static int send_end_msg(struct stasis_app *app, struct ast_channel *chan)
|
||||
void stasis_app_control_execute_until_exhausted(struct ast_channel *chan, struct stasis_app_control *control)
|
||||
{
|
||||
while (!control_is_done(control)) {
|
||||
int command_count = control_dispatch_all(control, chan);
|
||||
int command_count;
|
||||
command_count = control_dispatch_all(control, chan);
|
||||
|
||||
ao2_lock(control);
|
||||
|
||||
if (control_command_count(control)) {
|
||||
/* If the command queue isn't empty, something added to the queue before it was locked. */
|
||||
ao2_unlock(control);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (command_count == 0 || ast_channel_fdno(chan) == -1) {
|
||||
control_mark_done(control);
|
||||
ao2_unlock(control);
|
||||
break;
|
||||
}
|
||||
ao2_unlock(control);
|
||||
}
|
||||
}
|
||||
|
||||
int stasis_app_control_is_done(struct stasis_app_control *control)
|
||||
{
|
||||
return control_is_done(control);
|
||||
}
|
||||
|
||||
/*! /brief Stasis dialplan application callback */
|
||||
int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
||||
char *argv[])
|
||||
@@ -1230,6 +1336,9 @@ static int unload_module(void)
|
||||
ao2_cleanup(app_bridges_moh);
|
||||
app_bridges_moh = NULL;
|
||||
|
||||
ao2_cleanup(app_bridges_playback);
|
||||
app_bridges_playback = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1268,8 +1377,11 @@ static int load_module(void)
|
||||
app_bridges = ao2_container_alloc(BRIDGES_NUM_BUCKETS, bridges_hash, bridges_compare);
|
||||
app_bridges_moh = ao2_container_alloc_hash(
|
||||
AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
|
||||
37, bridges_moh_hash_fn, bridges_moh_sort_fn, NULL);
|
||||
if (!apps_registry || !app_controls || !app_bridges || !app_bridges_moh) {
|
||||
37, bridges_channel_hash_fn, bridges_channel_sort_fn, NULL);
|
||||
app_bridges_playback = ao2_container_alloc_hash(
|
||||
AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
|
||||
37, bridges_channel_hash_fn, bridges_channel_sort_fn, NULL);
|
||||
if (!apps_registry || !app_controls || !app_bridges || !app_bridges_moh || !app_bridges_playback) {
|
||||
unload_module();
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
|
@@ -330,7 +330,7 @@ static void play_on_channel(struct stasis_app_playback *playback,
|
||||
res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
|
||||
} else {
|
||||
/* Play URL */
|
||||
ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported",
|
||||
ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
|
||||
playback->media, ast_channel_name(chan));
|
||||
return;
|
||||
}
|
||||
|
@@ -368,12 +368,22 @@ void stasis_app_control_clear_roles(struct stasis_app_control *control)
|
||||
ast_channel_clear_bridge_roles(control->channel);
|
||||
}
|
||||
|
||||
int control_command_count(struct stasis_app_control *control)
|
||||
{
|
||||
return ao2_container_count(control->command_queue);
|
||||
}
|
||||
|
||||
int control_is_done(struct stasis_app_control *control)
|
||||
{
|
||||
/* Called from stasis_app_exec thread; no lock needed */
|
||||
return control->is_done;
|
||||
}
|
||||
|
||||
void control_mark_done(struct stasis_app_control *control)
|
||||
{
|
||||
control->is_done = 1;
|
||||
}
|
||||
|
||||
struct stasis_app_control_continue_data {
|
||||
char context[AST_MAX_CONTEXT];
|
||||
char extension[AST_MAX_EXTENSION];
|
||||
|
@@ -57,6 +57,15 @@ int control_dispatch_all(struct stasis_app_control *control,
|
||||
*/
|
||||
void control_wait(struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Returns the count of items in a control's command queue.
|
||||
*
|
||||
* \param control Control to count commands on
|
||||
*
|
||||
* \retval number of commands in the command que
|
||||
*/
|
||||
int control_command_count(struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Returns true if control_continue() has been called on this \a control.
|
||||
*
|
||||
@@ -66,5 +75,7 @@ void control_wait(struct stasis_app_control *control);
|
||||
*/
|
||||
int control_is_done(struct stasis_app_control *control);
|
||||
|
||||
void control_mark_done(struct stasis_app_control *control);
|
||||
|
||||
|
||||
#endif /* _ASTERISK_RES_STASIS_CONTROL_H */
|
||||
|
@@ -368,7 +368,14 @@
|
||||
"valueType": "RANGE",
|
||||
"min": 0
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"name": "playbackId",
|
||||
"description": "Playback Id.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
@@ -384,6 +391,90 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/bridges/{bridgeId}/play/{playbackId}",
|
||||
"description": "Play media to a bridge",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
"summary": "Start playback of media on a bridge.",
|
||||
"notes": "The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
|
||||
"nickname": "playWithId",
|
||||
"responseClass": "Playback",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "bridgeId",
|
||||
"description": "Bridge's id",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "playbackId",
|
||||
"description": "Playback ID.",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "media",
|
||||
"description": "Media's URI to play.",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "lang",
|
||||
"description": "For sounds, selects language for sound.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "offsetms",
|
||||
"description": "Number of media to skip before playing.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "int",
|
||||
"defaultValue": 0,
|
||||
"allowableValues": {
|
||||
"valueType": "RANGE",
|
||||
"min": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skipms",
|
||||
"description": "Number of milliseconds to skip for forward/reverse operations.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "int",
|
||||
"defaultValue": 3000,
|
||||
"allowableValues": {
|
||||
"valueType": "RANGE",
|
||||
"min": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
{
|
||||
"code": 404,
|
||||
"reason": "Bridge not found"
|
||||
},
|
||||
{
|
||||
"code": 409,
|
||||
"reason": "Bridge not in a Stasis application"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/bridges/{bridgeId}/record",
|
||||
"description": "Record audio on a bridge",
|
||||
|
Reference in New Issue
Block a user