summaryrefslogtreecommitdiff
path: root/res
diff options
context:
space:
mode:
authorJonathan Rose <jrose@digium.com>2013-08-23 00:26:19 +0000
committerJonathan Rose <jrose@digium.com>2013-08-23 00:26:19 +0000
commit21e22310c72f0c863f73dde8c31208cfdfa5aa3c (patch)
tree5945e2a28d0b42b3abe354e53d79a08c3d8b0450 /res
parentc25c093c676b27f9eaf9847cfeeda8e8cdc46d46 (diff)
ARI: Music on Hold/Background Music for bridges
Adds ARI functions to be able to turn on/off music on hold in a bridge. It actually functions more as a background music without further actions on the bridge since if the rest of the channels in the bridge aren't explicitly muted, they will still be able to communicate. (closes issue ASTERISK-21974) Reported by: Matt Jordan Review: https://reviewboard.asterisk.org/r/2688/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@397505 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res')
-rw-r--r--res/ari/resource_bridges.c43
-rw-r--r--res/ari/resource_bridges.h30
-rw-r--r--res/res_ari_bridges.c144
-rw-r--r--res/res_stasis.c246
4 files changed, 461 insertions, 2 deletions
diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c
index 348cf972b..670792fc1 100644
--- a/res/ari/resource_bridges.c
+++ b/res/ari/resource_bridges.c
@@ -43,6 +43,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/bridge.h"
#include "asterisk/format_cap.h"
#include "asterisk/file.h"
+#include "asterisk/musiconhold.h"
/*!
* \brief Finds a bridge, filling the response with an error, if appropriate.
@@ -498,6 +499,48 @@ void ast_ari_record_bridge(struct ast_variable *headers, struct ast_record_bridg
ast_ari_response_created(response, recording_url, json);
}
+void ast_ari_moh_start_bridge(struct ast_variable *headers, struct ast_moh_start_bridge_args *args, struct ast_ari_response *response)
+{
+ RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
+ struct ast_channel *moh_channel;
+ const char *moh_class = args->moh_class;
+
+ if (!bridge) {
+ /* The response is provided by find_bridge() */
+ return;
+ }
+
+ moh_channel = stasis_app_bridge_moh_channel(bridge);
+ if (!moh_channel) {
+ ast_ari_response_alloc_failed(response);
+ return;
+ }
+
+ ast_moh_start(moh_channel, moh_class, NULL);
+
+ ast_ari_response_no_content(response);
+
+}
+
+void ast_ari_moh_stop_bridge(struct ast_variable *headers, struct ast_moh_stop_bridge_args *args, struct ast_ari_response *response)
+{
+ RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
+
+ if (!bridge) {
+ /* the response is provided by find_bridge() */
+ return;
+ }
+
+ if (stasis_app_bridge_moh_stop(bridge)) {
+ ast_ari_response_error(
+ response, 409, "Conflict",
+ "Bridge isn't playing music");
+ return;
+ }
+
+ ast_ari_response_no_content(response);
+}
+
void ast_ari_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct ast_ari_response *response)
{
RAII_VAR(struct ast_bridge_snapshot *, snapshot, ast_bridge_snapshot_get_latest(args->bridge_id), ao2_cleanup);
diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h
index d82cb6f8b..dd4575a73 100644
--- a/res/ari/resource_bridges.h
+++ b/res/ari/resource_bridges.h
@@ -133,6 +133,36 @@ struct ast_remove_channel_from_bridge_args {
* \param[out] response HTTP response
*/
void ast_ari_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct ast_ari_response *response);
+/*! \brief Argument struct for ast_ari_moh_start_bridge() */
+struct ast_moh_start_bridge_args {
+ /*! \brief Bridge's id */
+ const char *bridge_id;
+ /*! \brief Channel's id */
+ const char *moh_class;
+};
+/*!
+ * \brief Play music on hold to a bridge or change the MOH class that is playing.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_moh_start_bridge(struct ast_variable *headers, struct ast_moh_start_bridge_args *args, struct ast_ari_response *response);
+/*! \brief Argument struct for ast_ari_moh_stop_bridge() */
+struct ast_moh_stop_bridge_args {
+ /*! \brief Bridge's id */
+ const char *bridge_id;
+};
+/*!
+ * \brief Stop playing music on hold to a bridge.
+ *
+ * This will only stop music on hold being played via bridges/{bridgeId}/mohStart.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_moh_stop_bridge(struct ast_variable *headers, struct ast_moh_stop_bridge_args *args, struct ast_ari_response *response);
/*! \brief Argument struct for ast_ari_play_on_bridge() */
struct ast_play_on_bridge_args {
/*! \brief Bridge's id */
diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c
index 05ea12e6a..fe72df714 100644
--- a/res/res_ari_bridges.c
+++ b/res/res_ari_bridges.c
@@ -469,6 +469,128 @@ fin: __attribute__((unused))
return;
}
/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/mohStart.
+ * \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_moh_start_bridge_cb(
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_moh_start_bridge_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, "mohClass") == 0) {
+ args.moh_class = (i->value);
+ } else
+ {}
+ }
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "bridgeId") == 0) {
+ args.bridge_id = (i->value);
+ } else
+ {}
+ }
+ ast_ari_moh_start_bridge(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 Stasis application */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_void(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/mohStart\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/mohStart\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+ return;
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/mohStop.
+ * \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_moh_stop_bridge_cb(
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_moh_stop_bridge_args args = {};
+ struct ast_variable *i;
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "bridgeId") == 0) {
+ args.bridge_id = (i->value);
+ } else
+ {}
+ }
+ ast_ari_moh_stop_bridge(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 Stasis application */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_void(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/mohStop\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/mohStop\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+ return;
+}
+/*!
* \brief Parameter parsing callback for /bridges/{bridgeId}/play.
* \param get_params GET parameters in the HTTP request.
* \param path_vars Path variables extracted from the request.
@@ -641,6 +763,24 @@ static struct stasis_rest_handlers bridges_bridgeId_removeChannel = {
.children = { }
};
/*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId_mohStart = {
+ .path_segment = "mohStart",
+ .callbacks = {
+ [AST_HTTP_POST] = ast_ari_moh_start_bridge_cb,
+ },
+ .num_children = 0,
+ .children = { }
+};
+/*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId_mohStop = {
+ .path_segment = "mohStop",
+ .callbacks = {
+ [AST_HTTP_POST] = ast_ari_moh_stop_bridge_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 = {
@@ -666,8 +806,8 @@ static struct stasis_rest_handlers bridges_bridgeId = {
[AST_HTTP_GET] = ast_ari_get_bridge_cb,
[AST_HTTP_DELETE] = ast_ari_delete_bridge_cb,
},
- .num_children = 4,
- .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_play,&bridges_bridgeId_record, }
+ .num_children = 6,
+ .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_mohStart,&bridges_bridgeId_mohStop,&bridges_bridgeId_play,&bridges_bridgeId_record, }
};
/*! \brief REST handler for /api-docs/bridges.{format} */
static struct stasis_rest_handlers bridges = {
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 963e70d9e..35c1847bd 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -65,6 +65,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/strings.h"
#include "stasis/app.h"
#include "stasis/control.h"
+#include "asterisk/core_unreal.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/causes.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/bridge_after.h"
/*! Time to wait for a frame in the application */
#define MAX_WAIT_MS 200
@@ -90,6 +95,8 @@ struct ao2_container *app_controls;
struct ao2_container *app_bridges;
+struct ao2_container *app_bridges_moh;
+
/*! \brief Message router for the channel caching topic */
struct stasis_message_router *channel_router;
@@ -194,6 +201,234 @@ static int bridges_compare(void *lhs, void *rhs, int flags)
}
}
+/*!
+ * Used with app_bridges_moh, provides links between bridges and existing music
+ * on hold channels that are being used with them.
+ */
+struct stasis_app_bridge_moh_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)
+{
+ struct stasis_app_bridge_moh_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)
+{
+ const struct stasis_app_bridge_moh_wrapper *wrapper;
+ const char *key;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_KEY:
+ key = obj;
+ return ast_str_hash(key);
+ case OBJ_POINTER:
+ wrapper = obj;
+ return ast_str_hash(wrapper->bridge_id);
+ default:
+ /* Hash can only work on something with a full key. */
+ ast_assert(0);
+ return 0;
+ }
+}
+
+static int bridges_moh_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 char *right_key = obj_right;
+ int cmp;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_POINTER:
+ right_key = right->bridge_id;
+ /* Fall through */
+ case OBJ_KEY:
+ cmp = strcmp(left->bridge_id, right_key);
+ break;
+ case OBJ_PARTIAL_KEY:
+ cmp = strncmp(left->bridge_id, right_key, strlen(right_key));
+ break;
+ default:
+ /* Sort can only work on something with a full or partial key. */
+ ast_assert(0);
+ cmp = 0;
+ break;
+ }
+ return cmp;
+}
+
+/*! Removes the bridge to music on hold channel link */
+static void remove_bridge_moh(char *bridge_id)
+{
+ RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, ao2_find(app_bridges_moh, bridge_id, OBJ_KEY), ao2_cleanup);
+
+ if (moh_wrapper) {
+ ao2_unlink_flags(app_bridges_moh, moh_wrapper, OBJ_NOLOCK);
+ }
+ ast_free(bridge_id);
+}
+
+/*! After bridge failure callback for moh channels */
+static void moh_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
+{
+ char *bridge_id = data;
+
+ remove_bridge_moh(bridge_id);
+}
+
+/*! After bridge callback for moh channels */
+static void moh_after_bridge_cb(struct ast_channel *chan, void *data)
+{
+ char *bridge_id = data;
+
+ remove_bridge_moh(bridge_id);
+}
+
+/*! Request a bridge MOH channel */
+static struct ast_channel *prepare_bridge_moh_channel(void)
+{
+ 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));
+
+ return ast_request("Announcer", cap, NULL, "ARI_MOH", NULL);
+}
+
+/*! Provides the moh channel with a thread so it can actually play its music */
+static void *moh_channel_thread(void *data)
+{
+ struct ast_channel *moh_channel = data;
+
+ while (!ast_safe_sleep(moh_channel, 1000));
+
+ ast_moh_stop(moh_channel);
+ ast_hangup(moh_channel);
+
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Creates, pushes, and links a channel for playing music on hold to bridge
+ *
+ * \param bridge Which bridge this moh channel exists for
+ *
+ * \retval NULL if the channel could not be created, pushed, or linked
+ * \retval Reference to the channel on success
+ */
+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(char *, bridge_id, ast_strdup(bridge->uniqueid), ast_free);
+ struct ast_channel *chan;
+ pthread_t threadid;
+
+ if (!bridge_id) {
+ return NULL;
+ }
+
+ chan = prepare_bridge_moh_channel();
+
+ if (!chan) {
+ return NULL;
+ }
+
+ /* The after bridge callback assumes responsibility of the bridge_id. */
+ ast_bridge_set_after_callback(chan, moh_after_bridge_cb, moh_after_bridge_cb_failed, bridge_id);
+
+ bridge_id = NULL;
+
+ if (ast_unreal_channel_push_to_bridge(chan, bridge,
+ AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ new_wrapper = ao2_alloc_options(sizeof(*new_wrapper), stasis_app_bridge_moh_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!new_wrapper) {
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ if (ast_string_field_init(new_wrapper, 32)) {
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ 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_moh, new_wrapper)) {
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ if (ast_pthread_create_detached(&threadid, NULL, moh_channel_thread, chan)) {
+ ast_log(LOG_ERROR, "Failed to create channel thread. Abandoning MOH channel creation.\n");
+ ao2_unlink_flags(app_bridges_moh, new_wrapper, OBJ_NOLOCK);
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ return chan;
+}
+
+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);
+
+ SCOPED_AO2LOCK(lock, app_bridges_moh);
+
+ moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_KEY | OBJ_NOLOCK);
+
+ if (!moh_wrapper) {
+ struct ast_channel *bridge_moh_channel = bridge_moh_create(bridge);
+ return bridge_moh_channel;
+ }
+
+ return ast_channel_get_by_name(moh_wrapper->channel_id);
+}
+
+int stasis_app_bridge_moh_stop(struct ast_bridge *bridge)
+{
+ RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup);
+ struct ast_channel *chan;
+
+ SCOPED_AO2LOCK(lock, app_bridges_moh);
+
+ moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_KEY | OBJ_NOLOCK);
+
+ if (!moh_wrapper) {
+ return -1;
+ }
+
+ chan = ast_channel_get_by_name(moh_wrapper->channel_id);
+ if (!chan) {
+ return -1;
+ }
+
+ ast_moh_stop(chan);
+ ast_softhangup(chan, AST_CAUSE_NORMAL_CLEARING);
+ ao2_cleanup(chan);
+
+ ao2_unlink_flags(app_bridges_moh, moh_wrapper, OBJ_NOLOCK);
+
+ return 0;
+}
+
struct ast_bridge *stasis_app_bridge_find_by_id(
const char *bridge_id)
{
@@ -1003,6 +1238,14 @@ static int load_module(void)
return AST_MODULE_LOAD_FAILURE;
}
+ 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 (!app_bridges_moh) {
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
channel_router = stasis_message_router_create(ast_channel_topic_all_cached());
if (!channel_router) {
return AST_MODULE_LOAD_FAILURE;
@@ -1058,6 +1301,9 @@ static int unload_module(void)
ao2_cleanup(app_bridges);
app_bridges = NULL;
+ ao2_cleanup(app_bridges_moh);
+ app_bridges_moh = NULL;
+
return r;
}