From b9d7dfcc62c80d2b2827dd7b70701dfb21512c13 Mon Sep 17 00:00:00 2001 From: Jonathan Rose Date: Fri, 18 Apr 2014 20:09:24 +0000 Subject: 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 --- res/ari/resource_bridges.c | 255 +++++++++++++++++++++++++++++++++++++------- res/ari/resource_bridges.h | 38 +++++++ res/ari/resource_channels.c | 11 +- 3 files changed, 258 insertions(+), 46 deletions(-) (limited to 'res/ari') diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c index d78601fae..eeb4237d0 100644 --- a/res/ari/resource_bridges.c +++ b/res/ari/resource_bridges.c @@ -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; } diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h index 404760c91..f8cd6139a 100644 --- a/res/ari/resource_bridges.h +++ b/res/ari/resource_bridges.h @@ -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 */ diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c index 9d127b2ea..30ced1b2f 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -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"); -- cgit v1.2.3