[asterisk-commits] jrose: trunk r394809 - in /trunk: channels/ include/asterisk/ main/ res/ res/...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Fri Jul 19 14:35:25 CDT 2013
Author: jrose
Date: Fri Jul 19 14:35:21 2013
New Revision: 394809
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=394809
Log:
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/
Added:
trunk/channels/chan_bridge_media.c (with props)
Modified:
trunk/include/asterisk/core_unreal.h
trunk/include/asterisk/logger.h
trunk/include/asterisk/stasis_app.h
trunk/include/asterisk/stasis_app_playback.h
trunk/main/core_unreal.c
trunk/res/res_stasis.c
trunk/res/res_stasis_http_bridges.c
trunk/res/res_stasis_http_channels.c
trunk/res/res_stasis_http_playback.c
trunk/res/res_stasis_playback.c
trunk/res/stasis/control.c
trunk/res/stasis_http/ari_model_validators.c
trunk/res/stasis_http/ari_model_validators.h
trunk/res/stasis_http/resource_bridges.c
trunk/res/stasis_http/resource_bridges.h
trunk/res/stasis_http/resource_channels.c
trunk/rest-api/api-docs/bridges.json
trunk/rest-api/api-docs/channels.json
trunk/rest-api/api-docs/playback.json
trunk/rest-api/api-docs/recordings.json
Added: trunk/channels/chan_bridge_media.c
URL: http://svnview.digium.com/svn/asterisk/trunk/channels/chan_bridge_media.c?view=auto&rev=394809
==============================================================================
--- trunk/channels/chan_bridge_media.c (added)
+++ trunk/channels/chan_bridge_media.c Fri Jul 19 14:35:21 2013
@@ -1,0 +1,218 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Jonathan Rose <jrose at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Bridge Media Channels driver
+ *
+ * \author Jonathan Rose <jrose at digium.com>
+ * \author Richard Mudgett <rmudgett at digium.com>
+ *
+ * \brief Bridge Media Channels
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+#include "asterisk/module.h"
+
+static int media_call(struct ast_channel *chan, const char *addr, int timeout)
+{
+ /* ast_call() will fail unconditionally against channels provided by this driver */
+ return -1;
+}
+
+static int media_hangup(struct ast_channel *ast)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int res;
+
+ if (!p) {
+ return -1;
+ }
+
+ /* Give the pvt a ref to fulfill calling requirements. */
+ ao2_ref(p, +1);
+ res = ast_unreal_hangup(p, ast);
+ ao2_ref(p, -1);
+
+ return res;
+}
+
+static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap,
+ const struct ast_channel *requestor, const char *data, int *cause);
+
+static struct ast_channel *record_request(const char *type, struct ast_format_cap *cap,
+ const struct ast_channel *requestor, const char *data, int *cause);
+
+static struct ast_channel_tech announce_tech = {
+ .type = "Announcer",
+ .description = "Bridge Media Announcing Channel Driver",
+ .requester = announce_request,
+ .call = media_call,
+ .hangup = media_hangup,
+
+ .send_digit_begin = ast_unreal_digit_begin,
+ .send_digit_end = ast_unreal_digit_end,
+ .read = ast_unreal_read,
+ .write = ast_unreal_write,
+ .write_video = ast_unreal_write,
+ .exception = ast_unreal_read,
+ .indicate = ast_unreal_indicate,
+ .fixup = ast_unreal_fixup,
+ .send_html = ast_unreal_sendhtml,
+ .send_text = ast_unreal_sendtext,
+ .queryoption = ast_unreal_queryoption,
+ .setoption = ast_unreal_setoption,
+ .properties = AST_CHAN_TP_ANNOUNCER,
+};
+
+static struct ast_channel_tech record_tech = {
+ .type = "Recorder",
+ .description = "Bridge Media Recording Channel Driver",
+ .requester = record_request,
+ .call = media_call,
+ .hangup = media_hangup,
+
+ .send_digit_begin = ast_unreal_digit_begin,
+ .send_digit_end = ast_unreal_digit_end,
+ .read = ast_unreal_read,
+ .write = ast_unreal_write,
+ .write_video = ast_unreal_write,
+ .exception = ast_unreal_read,
+ .indicate = ast_unreal_indicate,
+ .fixup = ast_unreal_fixup,
+ .send_html = ast_unreal_sendhtml,
+ .send_text = ast_unreal_sendtext,
+ .queryoption = ast_unreal_queryoption,
+ .setoption = ast_unreal_setoption,
+ .properties = AST_CHAN_TP_RECORDER,
+};
+
+static struct ast_channel *media_request_helper(struct ast_format_cap *cap,
+ const struct ast_channel *requestor, const char *data, struct ast_channel_tech *tech, const char *role)
+{
+ struct ast_channel *chan;
+
+ RAII_VAR(struct ast_callid *, callid, NULL, ast_callid_cleanup);
+ RAII_VAR(struct ast_unreal_pvt *, pvt, NULL, ao2_cleanup);
+
+ if (!(pvt = ast_unreal_alloc(sizeof(*pvt), ast_unreal_destructor, cap))) {
+ return NULL;
+ }
+
+ ast_copy_string(pvt->name, data, sizeof(pvt->name));
+
+ ast_set_flag(pvt, AST_UNREAL_NO_OPTIMIZATION);
+
+ callid = ast_read_threadstorage_callid();
+
+ chan = ast_unreal_new_channels(pvt, tech,
+ AST_STATE_UP, AST_STATE_UP, NULL, NULL, requestor, callid);
+ if (!chan) {
+ return NULL;
+ }
+
+ ast_answer(pvt->owner);
+ ast_answer(pvt->chan);
+
+ if (ast_channel_add_bridge_role(pvt->chan, role)) {
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ return chan;
+}
+
+static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap,
+ const struct ast_channel *requestor, const char *data, int *cause)
+{
+ return media_request_helper(cap, requestor, data, &announce_tech, "announcer");
+}
+
+static struct ast_channel *record_request(const char *type, struct ast_format_cap *cap,
+ const struct ast_channel *requestor, const char *data, int *cause)
+{
+ return media_request_helper(cap, requestor, data, &record_tech, "recorder");
+}
+
+static void cleanup_capabilities(void)
+{
+ if (announce_tech.capabilities) {
+ announce_tech.capabilities = ast_format_cap_destroy(announce_tech.capabilities);
+ }
+
+ if (record_tech.capabilities) {
+ record_tech.capabilities = ast_format_cap_destroy(record_tech.capabilities);
+ }
+}
+
+static int unload_module(void)
+{
+ ast_channel_unregister(&announce_tech);
+ ast_channel_unregister(&record_tech);
+ cleanup_capabilities();
+ return 0;
+}
+
+static int load_module(void)
+{
+ announce_tech.capabilities = ast_format_cap_alloc();
+ if (!announce_tech.capabilities) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ record_tech.capabilities = ast_format_cap_alloc();
+ if (!record_tech.capabilities) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ ast_format_cap_add_all(announce_tech.capabilities);
+ ast_format_cap_add_all(record_tech.capabilities);
+
+ if (ast_channel_register(&announce_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n",
+ announce_tech.type, announce_tech.description);
+ cleanup_capabilities();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (ast_channel_register(&record_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n",
+ record_tech.type, record_tech.description);
+ cleanup_capabilities();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bridge Media Channel Driver",
+ .load = load_module,
+ .unload = unload_module,
+);
Propchange: trunk/channels/chan_bridge_media.c
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: trunk/channels/chan_bridge_media.c
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: trunk/channels/chan_bridge_media.c
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: trunk/include/asterisk/core_unreal.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/core_unreal.h?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/include/asterisk/core_unreal.h (original)
+++ trunk/include/asterisk/core_unreal.h Fri Jul 19 14:35:21 2013
@@ -31,6 +31,7 @@
#include "asterisk/astobj2.h"
#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
#include "asterisk/abstract_jb.h"
#if defined(__cplusplus) || defined(c_plusplus)
@@ -208,6 +209,20 @@
*/
void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2);
+/*!
+ * \brief Push the semi2 unreal channel into a bridge from either member of the unreal pair
+ * \since 12.0.0
+ *
+ * \param ast A member of the unreal channel being pushed
+ * \param bridge Which bridge we want to push the channel to
+ *
+ * \retval 0 if the channel is successfully imparted onto the bridge
+ * \retval -1 on failure
+ *
+ * \note This is equivalent to ast_call() on unreal based channel drivers that are designed to use it instead.
+ */
+int ast_unreal_channel_push_to_bridge(struct ast_channel *ast, struct ast_bridge *bridge);
+
/* ------------------------------------------------------------------- */
#if defined(__cplusplus) || defined(c_plusplus)
Modified: trunk/include/asterisk/logger.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/logger.h?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/include/asterisk/logger.h (original)
+++ trunk/include/asterisk/logger.h Fri Jul 19 14:35:21 2013
@@ -281,7 +281,16 @@
*
* \retval NULL always
*/
-#define ast_callid_unref(c) ({ ao2_ref(c, -1); (NULL); })
+#define ast_callid_unref(c) ({ ao2_ref(c, -1); (struct ast_callid *) (NULL); })
+
+/*!
+ * \brief Cleanup a callid reference (NULL safe ao2 unreference)
+ *
+ * \param c the ast_callid
+ *
+ * \retval NULL always
+ */
+#define ast_callid_cleanup(c) ({ ao2_cleanup(c); (struct ast_callid *) (NULL); })
/*!
* \brief Sets what is stored in the thread storage to the given
Modified: trunk/include/asterisk/stasis_app.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/stasis_app.h?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/include/asterisk/stasis_app.h (original)
+++ trunk/include/asterisk/stasis_app.h Fri Jul 19 14:35:21 2013
@@ -127,6 +127,29 @@
const char *channel_id);
/*!
+ * \brief Creates a control handler for a channel that isn't in a stasis app.
+ * \since 12.0.0
+ *
+ * \param chan Channel to create controller handle for
+ *
+ * \return NULL on failure to create the handle
+ * \return Pointer to \c res_stasis handler.
+ */
+struct stasis_app_control *stasis_app_control_create(
+ struct ast_channel *chan);
+
+/*!
+ * \brief Act on a stasis app control queue until it is empty
+ * \since 12.0.0
+ *
+ * \param chan Channel to handle
+ * \param control Control object to execute
+ */
+void stasis_app_control_execute_until_exhausted(
+ struct ast_channel *chan,
+ struct stasis_app_control *control);
+
+/*!
* \brief Returns the uniqueid of the channel associated with this control
*
* \param control Control object.
Modified: trunk/include/asterisk/stasis_app_playback.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/stasis_app_playback.h?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/include/asterisk/stasis_app_playback.h (original)
+++ trunk/include/asterisk/stasis_app_playback.h Fri Jul 19 14:35:21 2013
@@ -69,6 +69,13 @@
STASIS_PLAYBACK_MEDIA_OP_MAX,
};
+enum stasis_app_playback_target_type {
+ /*! The target is a channel */
+ STASIS_PLAYBACK_TARGET_CHANNEL = 0,
+ /*! The target is a bridge */
+ STASIS_PLAYBACK_TARGET_BRIDGE,
+};
+
/*!
* \brief Play a file to the control's channel.
*
@@ -79,6 +86,8 @@
* \param control Control for \c res_stasis.
* \param file Base filename for the file to play.
* \param language Selects the file based on language.
+ * \param target_id ID of the target bridge or channel.
+ * \param target_type What the target type is
* \param skipms Number of milliseconds to skip for forward/reverse operations.
* \param offsetms Number of milliseconds to skip before playing.
* \return Playback control object.
@@ -86,7 +95,9 @@
*/
struct stasis_app_playback *stasis_app_control_play_uri(
struct stasis_app_control *control, const char *file,
- const char *language, int skipms, long offsetms);
+ const char *language, const char *target_id,
+ enum stasis_app_playback_target_type target_type,
+ int skipms, long offsetms);
/*!
* \brief Gets the current state of a playback operation.
Modified: trunk/main/core_unreal.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/core_unreal.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/main/core_unreal.c (original)
+++ trunk/main/core_unreal.c Fri Jul 19 14:35:21 2013
@@ -666,6 +666,96 @@
}
}
ast_channel_datastore_inherit(semi1, semi2);
+}
+
+int ast_unreal_channel_push_to_bridge(struct ast_channel *ast, struct ast_bridge *bridge)
+{
+ struct ast_bridge_features *features;
+ struct ast_channel *chan;
+ struct ast_channel *owner;
+ RAII_VAR(struct ast_unreal_pvt *, p, NULL, ao2_cleanup);
+
+ RAII_VAR(struct ast_callid *, bridge_callid, NULL, ast_callid_cleanup);
+
+ ast_bridge_lock(bridge);
+ bridge_callid = bridge->callid ? ast_callid_ref(bridge->callid) : NULL;
+ ast_bridge_unlock(bridge);
+
+ {
+ SCOPED_CHANNELLOCK(lock, ast);
+ p = ast_channel_tech_pvt(ast);
+ if (!p) {
+ return -1;
+ }
+ ao2_ref(p, +1);
+ }
+
+ {
+ SCOPED_AO2LOCK(lock, p);
+ chan = p->chan;
+ if (!chan) {
+ return -1;
+ }
+
+ owner = p->owner;
+ if (!owner) {
+ return -1;
+ }
+
+ ast_channel_ref(chan);
+ ast_channel_ref(owner);
+ }
+
+ if (bridge_callid) {
+ struct ast_callid *chan_callid;
+ struct ast_callid *owner_callid;
+
+ /* chan side call ID setting */
+ ast_channel_lock(chan);
+
+ chan_callid = ast_channel_callid(chan);
+ if (!chan_callid) {
+ ast_channel_callid_set(chan, bridge_callid);
+ }
+ ast_channel_unlock(chan);
+ ast_callid_cleanup(chan_callid);
+
+ /* owner side call ID setting */
+ ast_channel_lock(owner);
+
+ owner_callid = ast_channel_callid(owner);
+ if (!owner_callid) {
+ ast_channel_callid_set(owner, bridge_callid);
+ }
+
+ ast_channel_unlock(owner);
+ ast_callid_cleanup(owner_callid);
+ }
+
+ /* We are done with the owner now that its call ID matches the bridge */
+ ast_channel_unref(owner);
+ owner = NULL;
+
+ features = ast_bridge_features_new();
+ if (!features) {
+ ast_channel_unref(chan);
+ return -1;
+ }
+ ast_set_flag(&features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE);
+
+ /* Impart the semi2 channel into the bridge */
+ if (ast_bridge_impart(bridge, chan, NULL, features, 1)) {
+ ast_bridge_features_destroy(features);
+ ast_channel_unref(chan);
+ return -1;
+ }
+
+ ao2_lock(p);
+ ast_set_flag(p, AST_UNREAL_CARETAKER_THREAD);
+ ao2_unlock(p);
+ ast_channel_unref(chan);
+
+ return 0;
}
int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast)
Modified: trunk/res/res_stasis.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/res_stasis.c (original)
+++ trunk/res/res_stasis.c Fri Jul 19 14:35:21 2013
@@ -144,6 +144,11 @@
} else {
return 0;
}
+}
+
+struct stasis_app_control *stasis_app_control_create(struct ast_channel *chan)
+{
+ return control_create(chan);
}
struct stasis_app_control *stasis_app_control_find_by_channel(
@@ -529,6 +534,16 @@
app_send(app, msg);
return 0;
+}
+
+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);
+ if (command_count == 0 || ast_channel_fdno(chan) == -1) {
+ break;
+ }
+ }
}
/*! /brief Stasis dialplan application callback */
@@ -750,7 +765,7 @@
struct ast_channel_snapshot *channel_snapshot,
const struct timeval *tv)
{
- return ast_json_pack("{s: s, s: o, s: o}",
+ return ast_json_pack("{s: s, s: o, s: o, s: o}",
"type", type,
"timestamp", ast_json_timeval(*tv, NULL),
"bridge", ast_bridge_snapshot_to_json(bridge_snapshot),
Modified: trunk/res/res_stasis_http_bridges.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_http_bridges.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/res_stasis_http_bridges.c (original)
+++ trunk/res/res_stasis_http_bridges.c Fri Jul 19 14:35:21 2013
@@ -358,6 +358,73 @@
#endif /* AST_DEVMODE */
}
/*!
+ * \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.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_play_on_bridge_cb(
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct stasis_http_response *response)
+{
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ struct ast_play_on_bridge_args args = {};
+ struct ast_variable *i;
+
+ 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
+ {}
+ }
+ stasis_http_play_on_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+ code = response->response_code;
+
+ switch (code) {
+ case 500: /* Internal server error */
+ 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 = ari_validate_playback(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/play\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/play\n");
+ stasis_http_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+}
+/*!
* \brief Parameter parsing callback for /bridges/{bridgeId}/record.
* \param get_params GET parameters in the HTTP request.
* \param path_vars Path variables extracted from the request.
@@ -380,14 +447,17 @@
if (strcmp(i->name, "name") == 0) {
args.name = (i->value);
} else
+ if (strcmp(i->name, "format") == 0) {
+ args.format = (i->value);
+ } else
if (strcmp(i->name, "maxDurationSeconds") == 0) {
args.max_duration_seconds = atoi(i->value);
} else
if (strcmp(i->name, "maxSilenceSeconds") == 0) {
args.max_silence_seconds = atoi(i->value);
} else
- if (strcmp(i->name, "append") == 0) {
- args.append = ast_true(i->value);
+ if (strcmp(i->name, "ifExists") == 0) {
+ args.if_exists = (i->value);
} else
if (strcmp(i->name, "beep") == 0) {
args.beep = ast_true(i->value);
@@ -443,6 +513,15 @@
.path_segment = "removeChannel",
.callbacks = {
[AST_HTTP_POST] = stasis_http_remove_channel_from_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 = {
+ [AST_HTTP_POST] = stasis_http_play_on_bridge_cb,
},
.num_children = 0,
.children = { }
@@ -464,8 +543,8 @@
[AST_HTTP_GET] = stasis_http_get_bridge_cb,
[AST_HTTP_DELETE] = stasis_http_delete_bridge_cb,
},
- .num_children = 3,
- .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_record, }
+ .num_children = 4,
+ .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_play,&bridges_bridgeId_record, }
};
/*! \brief REST handler for /api-docs/bridges.{format} */
static struct stasis_rest_handlers bridges = {
Modified: trunk/res/res_stasis_http_channels.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_http_channels.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/res_stasis_http_channels.c (original)
+++ trunk/res/res_stasis_http_channels.c Fri Jul 19 14:35:21 2013
@@ -796,7 +796,7 @@
break;
default:
if (200 <= code && code <= 299) {
- is_valid = ari_validate_void(
+ is_valid = ari_validate_live_recording(
response->message);
} else {
ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/record\n", code);
Modified: trunk/res/res_stasis_http_playback.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_http_playback.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/res_stasis_http_playback.c (original)
+++ trunk/res/res_stasis_http_playback.c Fri Jul 19 14:35:21 2013
@@ -192,7 +192,7 @@
break;
default:
if (200 <= code && code <= 299) {
- is_valid = ari_validate_playback(
+ is_valid = ari_validate_void(
response->message);
} else {
ast_log(LOG_ERROR, "Invalid error response %d for /playback/{playbackId}/control\n", code);
Modified: trunk/res/res_stasis_playback.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_playback.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/res_stasis_playback.c (original)
+++ trunk/res/res_stasis_playback.c Fri Jul 19 14:35:21 2013
@@ -64,6 +64,7 @@
AST_STRING_FIELD(id); /*!< Playback unique id */
AST_STRING_FIELD(media); /*!< Playback media uri */
AST_STRING_FIELD(language); /*!< Preferred language */
+ AST_STRING_FIELD(target); /*!< Playback device uri */
);
/*! Control object for the channel we're playing back to */
struct stasis_app_control *control;
@@ -263,9 +264,31 @@
ast_string_field_free_memory(playback);
}
+static void set_target_uri(
+ struct stasis_app_playback *playback,
+ enum stasis_app_playback_target_type target_type,
+ const char *target_id)
+{
+ const char *type = NULL;
+ switch (target_type) {
+ case STASIS_PLAYBACK_TARGET_CHANNEL:
+ type = "channel";
+ break;
+ case STASIS_PLAYBACK_TARGET_BRIDGE:
+ type = "bridge";
+ break;
+ }
+
+ ast_assert(type != NULL);
+
+ ast_string_field_build(playback, target, "%s:%s", type, target_id);
+}
+
struct stasis_app_playback *stasis_app_control_play_uri(
struct stasis_app_control *control, const char *uri,
- const char *language, int skipms, long offsetms)
+ const char *language, const char *target_id,
+ enum stasis_app_playback_target_type target_type,
+ int skipms, long offsetms)
{
RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
char id[AST_UUID_STR_LEN];
@@ -290,6 +313,7 @@
ast_string_field_set(playback, id, id);
ast_string_field_set(playback, media, uri);
ast_string_field_set(playback, language, language);
+ set_target_uri(playback, target_type, target_id);
playback->control = control;
playback->skipms = skipms;
playback->offsetms = offsetms;
@@ -342,9 +366,10 @@
return NULL;
}
- json = ast_json_pack("{s: s, s: s, s: s, s: s}",
+ json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
"id", playback->id,
"media_uri", playback->media,
+ "target_uri", playback->target,
"language", playback->language,
"state", state_to_string(playback->state));
Modified: trunk/res/stasis/control.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis/control.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/stasis/control.c (original)
+++ trunk/res/stasis/control.c Fri Jul 19 14:35:21 2013
@@ -65,6 +65,11 @@
control->command_queue = ao2_container_alloc_list(
AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+ if (!control->command_queue) {
+ ao2_cleanup(control);
+ return NULL;
+ }
+
control->channel = channel;
return control;
Modified: trunk/res/stasis_http/ari_model_validators.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis_http/ari_model_validators.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/stasis_http/ari_model_validators.c (original)
+++ trunk/res/stasis_http/ari_model_validators.c Fri Jul 19 14:35:21 2013
@@ -578,16 +578,38 @@
{
int res = 1;
struct ast_json_iter *iter;
- int has_id = 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) {
- int prop_is_valid;
- has_id = 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");
+ 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("format", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ 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 state failed validation\n");
res = 0;
}
} else
@@ -599,8 +621,18 @@
}
}
- 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;
}
Modified: trunk/res/stasis_http/ari_model_validators.h
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis_http/ari_model_validators.h?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/stasis_http/ari_model_validators.h (original)
+++ trunk/res/stasis_http/ari_model_validators.h Fri Jul 19 14:35:21 2013
@@ -816,7 +816,9 @@
* - 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)
Modified: trunk/res/stasis_http/resource_bridges.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis_http/resource_bridges.c?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/stasis_http/resource_bridges.c (original)
+++ trunk/res/stasis_http/resource_bridges.c Fri Jul 19 14:35:21 2013
@@ -35,8 +35,14 @@
#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 @@
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)
Modified: trunk/res/stasis_http/resource_bridges.h
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis_http/resource_bridges.h?view=diff&rev=394809&r1=394808&r2=394809
==============================================================================
--- trunk/res/stasis_http/resource_bridges.h (original)
+++ trunk/res/stasis_http/resource_bridges.h Fri Jul 19 14:35:21 2013
@@ -123,18 +123,43 @@
* \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;
[... 234 lines stripped ...]
More information about the asterisk-commits
mailing list