diff options
author | Jonathan Rose <jrose@digium.com> | 2013-07-19 19:35:21 +0000 |
---|---|---|
committer | Jonathan Rose <jrose@digium.com> | 2013-07-19 19:35:21 +0000 |
commit | 17c546173fe1f24749af4643f19b40be180803de (patch) | |
tree | 46d8cf430d12142587196703c732d5e9ce9bba8e /res/stasis_http | |
parent | 5a8f32703c445f7d09b5e029e85d76692626a67f (diff) |
ARI: Bridge Playback, Bridge Record
Adds a new channel driver for creating channels for specific purposes
in bridges, primarily to act as either recorders or announcers. Adds
ARI commands for playing announcements to ever participant in a bridge
as well as for recording a bridge. This patch also includes some
documentation/reponse fixes to related ARI models such as playback
controls.
(closes issue ASTERISK-21592)
Reported by: Matt Jordan
(closes issue ASTERISK-21593)
Reported by: Matt Jordan
Review: https://reviewboard.asterisk.org/r/2670/
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@394809 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res/stasis_http')
-rw-r--r-- | res/stasis_http/ari_model_validators.c | 44 | ||||
-rw-r--r-- | res/stasis_http/ari_model_validators.h | 4 | ||||
-rw-r--r-- | res/stasis_http/resource_bridges.c | 274 | ||||
-rw-r--r-- | res/stasis_http/resource_bridges.h | 29 | ||||
-rw-r--r-- | res/stasis_http/resource_channels.c | 2 |
5 files changed, 342 insertions, 11 deletions
diff --git a/res/stasis_http/ari_model_validators.c b/res/stasis_http/ari_model_validators.c index bf3c0e7d0..bc5f25aea 100644 --- a/res/stasis_http/ari_model_validators.c +++ b/res/stasis_http/ari_model_validators.c @@ -578,16 +578,38 @@ int ari_validate_live_recording(struct ast_json *json) { int res = 1; struct ast_json_iter *iter; - int has_id = 0; + int has_format = 0; + int has_name = 0; + int has_state = 0; for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) { - if (strcmp("id", ast_json_object_iter_key(iter)) == 0) { + if (strcmp("format", ast_json_object_iter_key(iter)) == 0) { int prop_is_valid; - has_id = 1; + has_format = 1; + prop_is_valid = ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI LiveRecording field format failed validation\n"); + res = 0; + } + } else + if (strcmp("name", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_name = 1; + prop_is_valid = ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI LiveRecording field name failed validation\n"); + res = 0; + } + } else + if (strcmp("state", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_state = 1; prop_is_valid = ari_validate_string( ast_json_object_iter_value(iter)); if (!prop_is_valid) { - ast_log(LOG_ERROR, "ARI LiveRecording field id failed validation\n"); + ast_log(LOG_ERROR, "ARI LiveRecording field state failed validation\n"); res = 0; } } else @@ -599,8 +621,18 @@ int ari_validate_live_recording(struct ast_json *json) } } - if (!has_id) { - ast_log(LOG_ERROR, "ARI LiveRecording missing required field id\n"); + if (!has_format) { + ast_log(LOG_ERROR, "ARI LiveRecording missing required field format\n"); + res = 0; + } + + if (!has_name) { + ast_log(LOG_ERROR, "ARI LiveRecording missing required field name\n"); + res = 0; + } + + if (!has_state) { + ast_log(LOG_ERROR, "ARI LiveRecording missing required field state\n"); res = 0; } diff --git a/res/stasis_http/ari_model_validators.h b/res/stasis_http/ari_model_validators.h index 2f6418657..3cf630020 100644 --- a/res/stasis_http/ari_model_validators.h +++ b/res/stasis_http/ari_model_validators.h @@ -816,7 +816,9 @@ ari_validator ari_validate_stasis_start_fn(void); * - id: string (required) * - technology: string (required) * LiveRecording - * - id: string (required) + * - format: string (required) + * - name: string (required) + * - state: string (required) * StoredRecording * - duration_seconds: int * - formats: List[string] (required) diff --git a/res/stasis_http/resource_bridges.c b/res/stasis_http/resource_bridges.c index 6dad91116..b378eb8a5 100644 --- a/res/stasis_http/resource_bridges.c +++ b/res/stasis_http/resource_bridges.c @@ -35,8 +35,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis.h" #include "asterisk/stasis_bridging.h" #include "asterisk/stasis_app.h" +#include "asterisk/stasis_app_playback.h" +#include "asterisk/stasis_app_recording.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/core_unreal.h" #include "asterisk/channel.h" #include "asterisk/bridging.h" +#include "asterisk/format_cap.h" +#include "asterisk/file.h" /*! * \brief Finds a bridge, filling the response with an error, if appropriate. @@ -144,9 +150,275 @@ void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct stasis_http_response_no_content(response); } +struct bridge_channel_control_thread_data { + struct ast_channel *bridge_channel; + struct stasis_app_control *control; +}; + +static void *bridge_channel_control_thread(void *data) +{ + struct bridge_channel_control_thread_data *thread_data = data; + struct ast_channel *bridge_channel = thread_data->bridge_channel; + struct stasis_app_control *control = thread_data->control; + + RAII_VAR(struct ast_callid *, callid, ast_channel_callid(bridge_channel), ast_callid_cleanup); + + if (callid) { + ast_callid_threadassoc_add(callid); + } + + ast_free(thread_data); + thread_data = NULL; + + stasis_app_control_execute_until_exhausted(bridge_channel, control); + + ast_hangup(bridge_channel); + ao2_cleanup(control); + return NULL; +} + +static struct ast_channel *prepare_bridge_media_channel(const char *type) +{ + RAII_VAR(struct ast_format_cap *, cap, NULL, ast_format_cap_destroy); + struct ast_format format; + + cap = ast_format_cap_alloc_nolock(); + if (!cap) { + return NULL; + } + + ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0)); + + if (!cap) { + return NULL; + } + + return ast_request(type, cap, NULL, "ARI", NULL); +} + +void stasis_http_play_on_bridge(struct ast_variable *headers, struct ast_play_on_bridge_args *args, struct stasis_http_response *response) +{ + 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); + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + + 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"))) { + stasis_http_response_error( + response, 500, "Internal Error", "Could not create playback channel"); + return; + } + ast_debug(1, "Created announcer channel '%s'\n", ast_channel_name(play_channel)); + + if (ast_unreal_channel_push_to_bridge(play_channel, bridge)) { + stasis_http_response_error( + response, 500, "Internal Error", "Failed to put playback channel into the bridge"); + return; + } + + control = stasis_app_control_create(play_channel); + if (control == NULL) { + stasis_http_response_alloc_failed(response); + return; + } + + snapshot = stasis_app_control_get_snapshot(control); + if (!snapshot) { + stasis_http_response_error( + response, 500, "Internal Error", "Failed to get control snapshot"); + return; + } + + 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); + + if (!playback) { + stasis_http_response_alloc_failed(response); + return; + } + + ast_asprintf(&playback_url, "/playback/%s", + stasis_app_playback_get_id(playback)); + + if (!playback_url) { + stasis_http_response_alloc_failed(response); + return; + } + + json = stasis_app_playback_to_json(playback); + if (!json) { + stasis_http_response_alloc_failed(response); + return; + } + + /* Give play_channel and control reference to the thread data */ + thread_data = ast_calloc(1, sizeof(*thread_data)); + if (!thread_data) { + stasis_http_response_alloc_failed(response); + return; + } + + thread_data->bridge_channel = play_channel; + thread_data->control = control; + + if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) { + stasis_http_response_alloc_failed(response); + ast_free(thread_data); + return; + } + + /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */ + play_channel = NULL; + control = NULL; + + stasis_http_response_created(response, playback_url, json); +} + void stasis_http_record_bridge(struct ast_variable *headers, struct ast_record_bridge_args *args, struct stasis_http_response *response) { - ast_log(LOG_ERROR, "TODO: stasis_http_record_bridge\n"); + RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup); + RAII_VAR(struct ast_channel *, record_channel, NULL, ast_hangup); + RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); + RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup); + RAII_VAR(char *, recording_url, NULL, ast_free); + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + RAII_VAR(struct stasis_app_recording_options *, options, NULL, ao2_cleanup); + RAII_VAR(char *, uri_encoded_name, NULL, ast_free); + + size_t uri_name_maxlen; + struct bridge_channel_control_thread_data *thread_data; + pthread_t threadid; + + ast_assert(response != NULL); + + if (bridge == NULL) { + return; + } + + if (!(record_channel = prepare_bridge_media_channel("Recorder"))) { + stasis_http_response_error( + response, 500, "Internal Server Error", "Failed to create recording channel"); + return; + } + + if (ast_unreal_channel_push_to_bridge(record_channel, bridge)) { + stasis_http_response_error( + response, 500, "Internal Error", "Failed to put recording channel into the bridge"); + return; + } + + control = stasis_app_control_create(record_channel); + if (control == NULL) { + stasis_http_response_alloc_failed(response); + return; + } + + options = stasis_app_recording_options_create(args->name, args->format); + if (options == NULL) { + stasis_http_response_alloc_failed(response); + return; + } + + options->max_silence_seconds = args->max_silence_seconds; + options->max_duration_seconds = args->max_duration_seconds; + options->terminate_on = + stasis_app_recording_termination_parse(args->terminate_on); + options->if_exists = + stasis_app_recording_if_exists_parse(args->if_exists); + options->beep = args->beep; + + recording = stasis_app_control_record(control, options); + if (recording == NULL) { + switch(errno) { + case EINVAL: + /* While the arguments are invalid, we should have + * caught them prior to calling record. + */ + stasis_http_response_error( + response, 500, "Internal Server Error", + "Error parsing request"); + break; + case EEXIST: + stasis_http_response_error(response, 409, "Conflict", + "Recording '%s' already in progress", + args->name); + break; + case ENOMEM: + stasis_http_response_alloc_failed(response); + break; + case EPERM: + stasis_http_response_error( + response, 400, "Bad Request", + "Recording name invalid"); + break; + default: + ast_log(LOG_WARNING, + "Unrecognized recording error: %s\n", + strerror(errno)); + stasis_http_response_error( + response, 500, "Internal Server Error", + "Internal Server Error"); + break; + } + return; + } + + uri_name_maxlen = strlen(args->name) * 3; + uri_encoded_name = ast_malloc(uri_name_maxlen); + if (!uri_encoded_name) { + stasis_http_response_alloc_failed(response); + return; + } + 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) { + stasis_http_response_alloc_failed(response); + return; + } + + json = stasis_app_recording_to_json(recording); + if (!json) { + stasis_http_response_alloc_failed(response); + return; + } + + thread_data = ast_calloc(1, sizeof(*thread_data)); + if (!thread_data) { + stasis_http_response_alloc_failed(response); + return; + } + + thread_data->bridge_channel = record_channel; + thread_data->control = control; + + if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) { + stasis_http_response_alloc_failed(response); + ast_free(thread_data); + return; + } + + /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */ + record_channel = NULL; + control = NULL; + + stasis_http_response_created(response, recording_url, json); } void stasis_http_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct stasis_http_response *response) diff --git a/res/stasis_http/resource_bridges.h b/res/stasis_http/resource_bridges.h index ec1992f26..3935a116c 100644 --- a/res/stasis_http/resource_bridges.h +++ b/res/stasis_http/resource_bridges.h @@ -123,18 +123,43 @@ struct ast_remove_channel_from_bridge_args { * \param[out] response HTTP response */ void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_play_on_bridge() */ +struct ast_play_on_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_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 Start playback of media on a bridge. + * + * The media URI may be any of a number of URI's. You may use http: and https: URI's, as well as sound: and recording: URI's. 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 stasis_http_play_on_bridge(struct ast_variable *headers, struct ast_play_on_bridge_args *args, struct stasis_http_response *response); /*! \brief Argument struct for stasis_http_record_bridge() */ struct ast_record_bridge_args { /*! \brief Bridge's id */ const char *bridge_id; /*! \brief Recording's filename */ const char *name; + /*! \brief Format to encode audio in */ + const char *format; /*! \brief Maximum duration of the recording, in seconds. 0 for no limit. */ int max_duration_seconds; /*! \brief Maximum duration of silence, in seconds. 0 for no limit. */ int max_silence_seconds; - /*! \brief If true, and recording already exists, append to recording. */ - int append; + /*! \brief Action to take if a recording with the same name already exists. */ + const char *if_exists; /*! \brief Play beep when recording begins */ int beep; /*! \brief DTMF input to terminate recording. */ diff --git a/res/stasis_http/resource_channels.c b/res/stasis_http/resource_channels.c index f0bbd4b1f..c25917bb6 100644 --- a/res/stasis_http/resource_channels.c +++ b/res/stasis_http/resource_channels.c @@ -273,7 +273,7 @@ void stasis_http_play_on_channel(struct ast_variable *headers, language = S_OR(args->lang, snapshot->language); playback = stasis_app_control_play_uri(control, args->media, language, - args->skipms, args->offsetms); + args->channel_id, STASIS_PLAYBACK_TARGET_CHANNEL, args->skipms, args->offsetms); if (!playback) { stasis_http_response_error( response, 500, "Internal Server Error", |