summaryrefslogtreecommitdiff
path: root/res/stasis_http
diff options
context:
space:
mode:
authorJonathan Rose <jrose@digium.com>2013-07-19 19:35:21 +0000
committerJonathan Rose <jrose@digium.com>2013-07-19 19:35:21 +0000
commit17c546173fe1f24749af4643f19b40be180803de (patch)
tree46d8cf430d12142587196703c732d5e9ce9bba8e /res/stasis_http
parent5a8f32703c445f7d09b5e029e85d76692626a67f (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.c44
-rw-r--r--res/stasis_http/ari_model_validators.h4
-rw-r--r--res/stasis_http/resource_bridges.c274
-rw-r--r--res/stasis_http/resource_bridges.h29
-rw-r--r--res/stasis_http/resource_channels.c2
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",