[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