[asterisk-commits] jrose: trunk r412641 - in /trunk: ./ include/asterisk/ res/ res/ari/ res/stas...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Apr 18 15:09:34 CDT 2014


Author: jrose
Date: Fri Apr 18 15:09:24 2014
New Revision: 412641

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=412641
Log:
ARI: Make bridges/{bridgeID}/play queue sound files

Previously multiple play actions against a bridge at one time would cause
the sounds to play simultaneously on the bridge. Now if a sound is already
playing, the play action will queue playback to occur after the completion
of other sounds currently on the queue.

(closes issue ASTERISK-22677)
Reported by: John Bigelow
Review: https://reviewboard.asterisk.org/r/3379/
........

Merged revisions 412639 from http://svn.asterisk.org/svn/asterisk/branches/12

Modified:
    trunk/   (props changed)
    trunk/CHANGES
    trunk/include/asterisk/stasis_app.h
    trunk/res/ari/resource_bridges.c
    trunk/res/ari/resource_bridges.h
    trunk/res/ari/resource_channels.c
    trunk/res/res_ari_bridges.c
    trunk/res/res_stasis.c
    trunk/res/res_stasis_playback.c
    trunk/res/stasis/control.c
    trunk/res/stasis/control.h
    trunk/rest-api/api-docs/bridges.json

Propchange: trunk/
------------------------------------------------------------------------------
Binary property 'branch-12-merged' - no diff available.

Modified: trunk/CHANGES
URL: http://svnview.digium.com/svn/asterisk/trunk/CHANGES?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/CHANGES (original)
+++ trunk/CHANGES Fri Apr 18 15:09:24 2014
@@ -150,7 +150,8 @@
    (POST bridges/my-bridge-id) or as a query parameter.
 
  * A playbackId can be provided when starting a playback, either in the uri
-   (POST channels/my-channel-id/play/my-playback-id) or as a query parameter.
+   (POST channels/my-channel-id/play/my-playback-id /
+    POST bridges/my-bridge-id/play/my-playback-id)  or as a query parameter.
 
  * A snoop channel can be started with a snoopId, in the uri or query.
 

Modified: trunk/include/asterisk/stasis_app.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/stasis_app.h?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/include/asterisk/stasis_app.h (original)
+++ trunk/include/asterisk/stasis_app.h Fri Apr 18 15:09:24 2014
@@ -375,6 +375,15 @@
 	struct stasis_app_control *control);
 
 /*!
+ * \brief Check if a control is marked as done
+ * \since 12.2.0
+ *
+ * \param control Which control object is being evaluated
+ */
+int stasis_app_control_is_done(
+	struct stasis_app_control *control);
+
+/*!
  * \brief Returns the uniqueid of the channel associated with this control
  *
  * \param control Control object.
@@ -636,6 +645,30 @@
  */
 int stasis_app_bridge_moh_stop(
 	struct ast_bridge *bridge);
+
+/*!
+ * \brief Finds an existing ARI playback channel in a bridge
+ *
+ * \param bridge Bridge we want to find the playback channel for
+ *
+ * \return NULL if the playback channel can not be found for any reason.
+ * \return Pointer to the ;1 end of the playback channel chain.
+ */
+struct ast_channel *stasis_app_bridge_playback_channel_find(
+	struct ast_bridge *bridge);
+
+/*!
+ * \brief Adds a channel to the list of ARI playback channels for bridges.
+ *
+ * \param bridge Bridge we are adding the playback channel for
+ * \param chan Channel being added as a playback channel (must be ;1)
+ *
+ * \retval -1 failed to add channel for any reason
+ * \retval 0 on success
+ */
+int stasis_app_bridge_playback_channel_add(struct ast_bridge *bridge,
+	struct ast_channel *chan,
+	struct stasis_app_control *control);
 
 /*!
  * \brief Result codes used when adding/removing channels to/from bridges.

Modified: trunk/res/ari/resource_bridges.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/ari/resource_bridges.c?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/ari/resource_bridges.c (original)
+++ trunk/res/ari/resource_bridges.c Fri Apr 18 15:09:24 2014
@@ -320,30 +320,92 @@
 	return ast_request(type, cap, NULL, NULL, "ARI", NULL);
 }
 
-void ast_ari_bridges_play(struct ast_variable *headers,
-	struct ast_ari_bridges_play_args *args,
-	struct ast_ari_response *response)
-{
-	RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
+/*!
+ * \brief Performs common setup for a bridge playback operation
+ * with both new controls and when existing controls are  found.
+ *
+ * \param args_media media string split from arguments
+ * \param args_lang language string split from arguments
+ * \param args_offset_ms milliseconds offset split from arguments
+ * \param args_playback_id string to use for playback split from
+ *        arguments (null valid)
+ * \param response ARI response being built
+ * \param bridge Bridge the playback is being peformed on
+ * \param control Control being used for the playback channel
+ * \param json contents of the response to ARI
+ * \param playback_url stores playback URL for use with response
+ *
+ * \retval -1 operation failed
+ * \retval operation was successful
+ */
+static int ari_bridges_play_helper(const char *args_media,
+	const char *args_lang,
+	int args_offset_ms,
+	int args_skipms,
+	const char *args_playback_id,
+	struct ast_ari_response *response,
+	struct ast_bridge *bridge,
+	struct stasis_app_control *control,
+	struct ast_json **json,
+	char **playback_url)
+{
+	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
+
+	const char *language;
+
+	snapshot = stasis_app_control_get_snapshot(control);
+	if (!snapshot) {
+		ast_ari_response_error(
+			response, 500, "Internal Error", "Failed to get control snapshot");
+		return -1;
+	}
+
+	language = S_OR(args_lang, snapshot->language);
+
+	playback = stasis_app_control_play_uri(control, args_media, language,
+		bridge->uniqueid, STASIS_PLAYBACK_TARGET_BRIDGE, args_skipms,
+		args_offset_ms, args_playback_id);
+
+	if (!playback) {
+		ast_ari_response_alloc_failed(response);
+		return -1;
+	}
+
+	if (ast_asprintf(playback_url, "/playback/%s",
+			stasis_app_playback_get_id(playback)) == -1) {
+		playback_url = NULL;
+		ast_ari_response_alloc_failed(response);
+		return -1;
+	}
+
+	*json = stasis_app_playback_to_json(playback);
+	if (!*json) {
+		ast_ari_response_alloc_failed(response);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void ari_bridges_play_new(const char *args_media,
+	const char *args_lang,
+	int args_offset_ms,
+	int args_skipms,
+	const char *args_playback_id,
+	struct ast_ari_response *response,
+	struct ast_bridge *bridge)
+{
 	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);
 	RAII_VAR(struct stasis_forward *, channel_forward, NULL, stasis_forward_cancel);
+	RAII_VAR(char *, playback_url, NULL, ast_free);
 
 	struct stasis_topic *channel_topic;
 	struct stasis_topic *bridge_topic;
 	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"))) {
 		ast_ari_response_error(
@@ -378,34 +440,16 @@
 		return;
 	}
 
-	snapshot = stasis_app_control_get_snapshot(control);
-	if (!snapshot) {
-		ast_ari_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, NULL);
-
-	if (!playback) {
-		ast_ari_response_alloc_failed(response);
-		return;
-	}
-
-	ast_asprintf(&playback_url, "/playback/%s",
-		stasis_app_playback_get_id(playback));
-
-	if (!playback_url) {
-		ast_ari_response_alloc_failed(response);
-		return;
-	}
-
-	json = stasis_app_playback_to_json(playback);
-	if (!json) {
+	ao2_lock(control);
+	if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
+			args_skipms, args_playback_id, response, bridge, control,
+			&json, &playback_url)) {
+		ao2_unlock(control);
+		return;
+	}
+	ao2_unlock(control);
+
+	if (stasis_app_bridge_playback_channel_add(bridge, play_channel, control)) {
 		ast_ari_response_alloc_failed(response);
 		return;
 	}
@@ -433,6 +477,134 @@
 	channel_forward = NULL;
 
 	ast_ari_response_created(response, playback_url, ast_json_ref(json));
+}
+
+enum play_found_result {
+	PLAY_FOUND_SUCCESS,
+	PLAY_FOUND_FAILURE,
+	PLAY_FOUND_CHANNEL_UNAVAILABLE,
+};
+
+/*!
+ * \brief Performs common setup for a bridge playback operation
+ * with both new controls and when existing controls are  found.
+ *
+ * \param args_media media string split from arguments
+ * \param args_lang language string split from arguments
+ * \param args_offset_ms milliseconds offset split from arguments
+ * \param args_playback_id string to use for playback split from
+ *        arguments (null valid)
+ * \param response ARI response being built
+ * \param bridge Bridge the playback is being peformed on
+ * \param found_channel The channel that was found controlling playback
+ *
+ * \retval PLAY_FOUND_SUCCESS The operation was successful
+ * \retval PLAY_FOUND_FAILURE The operation failed (terminal failure)
+ * \retval PLAY_FOUND_CHANNEL_UNAVAILABLE The operation failed because
+ * the channel requested to playback with is breaking down.
+ */
+static enum play_found_result ari_bridges_play_found(const char *args_media,
+	const char *args_lang,
+	int args_offset_ms,
+	int args_skipms,
+	const char *args_playback_id,
+	struct ast_ari_response *response,
+	struct ast_bridge *bridge,
+	struct ast_channel *found_channel)
+{
+	RAII_VAR(struct ast_channel *, play_channel, found_channel, ao2_cleanup);
+	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+	RAII_VAR(char *, playback_url, NULL, ast_free);
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+
+	control = stasis_app_control_find_by_channel(play_channel);
+	if (!control) {
+		ast_ari_response_error(
+			response, 500, "Internal Error", "Failed to get control snapshot");
+		return PLAY_FOUND_FAILURE;
+	}
+
+	ao2_lock(control);
+	if (stasis_app_control_is_done(control)) {
+		/* We failed to queue the action. Bailout and return that we aren't terminal. */
+		ao2_unlock(control);
+		return PLAY_FOUND_CHANNEL_UNAVAILABLE;
+	}
+
+	if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
+			args_skipms, args_playback_id, response, bridge, control,
+			&json, &playback_url)) {
+		ao2_unlock(control);
+		return PLAY_FOUND_FAILURE;
+	}
+	ao2_unlock(control);
+
+	ast_ari_response_created(response, playback_url, ast_json_ref(json));
+	return PLAY_FOUND_SUCCESS;
+}
+
+static void ari_bridges_handle_play(
+	const char *args_bridge_id,
+	const char *args_media,
+	const char *args_lang,
+	int args_offset_ms,
+	int args_skipms,
+	const char *args_playback_id,
+	struct ast_ari_response *response)
+{
+	RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args_bridge_id), ao2_cleanup);
+	struct ast_channel *play_channel;
+
+	ast_assert(response != NULL);
+
+	if (!bridge) {
+		return;
+	}
+
+	while ((play_channel = stasis_app_bridge_playback_channel_find(bridge))) {
+		/* If ari_bridges_play_found fails because the channel is unavailable for
+		 * playback, The channel will be removed from the playback list soon. We
+		 * can keep trying to get channels from the list until we either get one
+		 * that will work or else there isn't a channel for this bridge anymore,
+		 * in which case we'll revert to ari_bridges_play_new.
+		 */
+		if (ari_bridges_play_found(args_media, args_lang, args_offset_ms,
+				args_skipms, args_playback_id, response,bridge,
+				play_channel) == PLAY_FOUND_CHANNEL_UNAVAILABLE) {
+			continue;
+		}
+		return;
+	}
+
+	ari_bridges_play_new(args_media, args_lang, args_offset_ms,
+		args_skipms, args_playback_id, response, bridge);
+}
+
+
+void ast_ari_bridges_play(struct ast_variable *headers,
+	struct ast_ari_bridges_play_args *args,
+	struct ast_ari_response *response)
+{
+	ari_bridges_handle_play(args->bridge_id,
+	args->media,
+	args->lang,
+	args->offsetms,
+	args->skipms,
+	args->playback_id,
+	response);
+}
+
+void ast_ari_bridges_play_with_id(struct ast_variable *headers,
+	struct ast_ari_bridges_play_with_id_args *args,
+	struct ast_ari_response *response)
+{
+	ari_bridges_handle_play(args->bridge_id,
+	args->media,
+	args->lang,
+	args->offsetms,
+	args->skipms,
+	args->playback_id,
+	response);
 }
 
 void ast_ari_bridges_record(struct ast_variable *headers,
@@ -573,8 +745,9 @@
 	}
 	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) {
+	if (ast_asprintf(&recording_url, "/recordings/live/%s",
+			uri_encoded_name) == -1) {
+		recording_url = NULL;
 		ast_ari_response_alloc_failed(response);
 		return;
 	}

Modified: trunk/res/ari/resource_bridges.h
URL: http://svnview.digium.com/svn/asterisk/trunk/res/ari/resource_bridges.h?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/ari/resource_bridges.h (original)
+++ trunk/res/ari/resource_bridges.h Fri Apr 18 15:09:24 2014
@@ -253,6 +253,8 @@
 	int offsetms;
 	/*! \brief Number of milliseconds to skip for forward/reverse operations. */
 	int skipms;
+	/*! \brief Playback Id. */
+	const char *playback_id;
 };
 /*!
  * \brief Body parsing function for /bridges/{bridgeId}/play.
@@ -275,6 +277,42 @@
  * \param[out] response HTTP response
  */
 void ast_ari_bridges_play(struct ast_variable *headers, struct ast_ari_bridges_play_args *args, struct ast_ari_response *response);
+/*! \brief Argument struct for ast_ari_bridges_play_with_id() */
+struct ast_ari_bridges_play_with_id_args {
+	/*! \brief Bridge's id */
+	const char *bridge_id;
+	/*! \brief Playback ID. */
+	const char *playback_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 Body parsing function for /bridges/{bridgeId}/play/{playbackId}.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_bridges_play_with_id_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_play_with_id_args *args);
+
+/*!
+ * \brief Start playback of media on a bridge.
+ *
+ * The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. 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 ast_ari_bridges_play_with_id(struct ast_variable *headers, struct ast_ari_bridges_play_with_id_args *args, struct ast_ari_response *response);
 /*! \brief Argument struct for ast_ari_bridges_record() */
 struct ast_ari_bridges_record_args {
 	/*! \brief Bridge's id */

Modified: trunk/res/ari/resource_channels.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/ari/resource_channels.c?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/ari/resource_channels.c (original)
+++ trunk/res/ari/resource_channels.c Fri Apr 18 15:09:24 2014
@@ -411,9 +411,9 @@
 		return;
 	}
 
-	ast_asprintf(&playback_url, "/playback/%s",
-		stasis_app_playback_get_id(playback));
-	if (!playback_url) {
+	if (ast_asprintf(&playback_url, "/playback/%s",
+			stasis_app_playback_get_id(playback)) == -1) {
+		playback_url = NULL;
 		ast_ari_response_error(
 			response, 500, "Internal Server Error",
 			"Out of memory");
@@ -579,8 +579,9 @@
 	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) {
+	if (ast_asprintf(&recording_url, "/recordings/live/%s",
+			uri_encoded_name) == -1) {
+		recording_url = NULL;
 		ast_ari_response_error(
 			response, 500, "Internal Server Error",
 			"Out of memory");

Modified: trunk/res/res_ari_bridges.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_ari_bridges.c?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/res_ari_bridges.c (original)
+++ trunk/res/res_ari_bridges.c Fri Apr 18 15:09:24 2014
@@ -948,6 +948,10 @@
 	if (field) {
 		args->skipms = ast_json_integer_get(field);
 	}
+	field = ast_json_object_get(body, "playbackId");
+	if (field) {
+		args->playback_id = ast_json_string_get(field);
+	}
 	return 0;
 }
 
@@ -984,6 +988,9 @@
 		if (strcmp(i->name, "skipms") == 0) {
 			args.skipms = atoi(i->value);
 		} else
+		if (strcmp(i->name, "playbackId") == 0) {
+			args.playback_id = (i->value);
+		} else
 		{}
 	}
 	for (i = path_vars; i; i = i->next) {
@@ -1037,6 +1044,128 @@
 
 	if (!is_valid) {
 		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/play\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_bridges_play_with_id_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_play_with_id_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "media");
+	if (field) {
+		args->media = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "lang");
+	if (field) {
+		args->lang = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "offsetms");
+	if (field) {
+		args->offsetms = ast_json_integer_get(field);
+	}
+	field = ast_json_object_get(body, "skipms");
+	if (field) {
+		args->skipms = ast_json_integer_get(field);
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/play/{playbackId}.
+ * \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_bridges_play_with_id_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_ari_response *response)
+{
+	struct ast_ari_bridges_play_with_id_args args = {};
+	struct ast_variable *i;
+	RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	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
+		if (strcmp(i->name, "playbackId") == 0) {
+			args.playback_id = (i->value);
+		} else
+		{}
+	}
+	/* Look for a JSON request entity */
+	body = ast_http_get_json(ser, headers);
+	if (!body) {
+		switch (errno) {
+		case EFBIG:
+			ast_ari_response_error(response, 413, "Request Entity Too Large", "Request body too large");
+			goto fin;
+		case ENOMEM:
+			ast_ari_response_error(response, 500, "Internal Server Error", "Error processing request");
+			goto fin;
+		case EIO:
+			ast_ari_response_error(response, 400, "Bad Request", "Error parsing request body");
+			goto fin;
+		}
+	}
+	if (ast_ari_bridges_play_with_id_parse_body(body, &args)) {
+		ast_ari_response_alloc_failed(response);
+		goto fin;
+	}
+	ast_ari_bridges_play_with_id(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 a Stasis application */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_playback(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/play/{playbackId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/play/{playbackId}\n");
 		ast_ari_response_error(response, 500,
 			"Internal Server Error", "Response validation failed");
 	}
@@ -1217,13 +1346,23 @@
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId_play_playbackId = {
+	.path_segment = "playbackId",
+	.is_wildcard = 1,
+	.callbacks = {
+		[AST_HTTP_POST] = ast_ari_bridges_play_with_id_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] = ast_ari_bridges_play_cb,
 	},
-	.num_children = 0,
-	.children = {  }
+	.num_children = 1,
+	.children = { &bridges_bridgeId_play_playbackId, }
 };
 /*! \brief REST handler for /api-docs/bridges.{format} */
 static struct stasis_rest_handlers bridges_bridgeId_record = {

Modified: trunk/res/res_stasis.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis.c?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/res_stasis.c (original)
+++ trunk/res/res_stasis.c Fri Apr 18 15:09:24 2014
@@ -103,6 +103,8 @@
 
 struct ao2_container *app_bridges_moh;
 
+struct ao2_container *app_bridges_playback;
+
 const char *stasis_app_name(const struct stasis_app *app)
 {
 	return app_name(app);
@@ -341,26 +343,26 @@
 }
 
 /*!
- *  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 {
+ *  Used with app_bridges_moh and app_bridge_control, they provide links
+ *  between bridges and channels used for ARI application purposes
+ */
+struct stasis_app_bridge_channel_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;
+static void stasis_app_bridge_channel_wrapper_destructor(void *obj)
+{
+	struct stasis_app_bridge_channel_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;
+static int bridges_channel_hash_fn(const void *obj, const int flags)
+{
+	const struct stasis_app_bridge_channel_wrapper *wrapper;
 	const char *key;
 
 	switch (flags & OBJ_SEARCH_MASK) {
@@ -379,10 +381,10 @@
 	return ast_str_hash(key);
 }
 
-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;
+static int bridges_channel_sort_fn(const void *obj_left, const void *obj_right, const int flags)
+{
+	const struct stasis_app_bridge_channel_wrapper *left = obj_left;
+	const struct stasis_app_bridge_channel_wrapper *right = obj_right;
 	const char *right_key = obj_right;
 	int cmp;
 
@@ -469,7 +471,7 @@
  */
 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(struct stasis_app_bridge_channel_wrapper *, new_wrapper, NULL, ao2_cleanup);
 	RAII_VAR(char *, bridge_id, ast_strdup(bridge->uniqueid), ast_free);
 	struct ast_channel *chan;
 	pthread_t threadid;
@@ -498,7 +500,7 @@
 	}
 
 	new_wrapper = ao2_alloc_options(sizeof(*new_wrapper),
-		stasis_app_bridge_moh_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+		stasis_app_bridge_channel_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
 	if (!new_wrapper) {
 		ast_hangup(chan);
 		return NULL;
@@ -528,7 +530,7 @@
 
 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);
+	RAII_VAR(struct stasis_app_bridge_channel_wrapper *, moh_wrapper, NULL, ao2_cleanup);
 
 	{
 		SCOPED_AO2LOCK(lock, app_bridges_moh);
@@ -544,7 +546,7 @@
 
 int stasis_app_bridge_moh_stop(struct ast_bridge *bridge)
 {
-	RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_app_bridge_channel_wrapper *, moh_wrapper, NULL, ao2_cleanup);
 	struct ast_channel *chan;
 
 	moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_SEARCH_KEY | OBJ_UNLINK);
@@ -562,6 +564,92 @@
 	ao2_cleanup(chan);
 
 	return 0;
+}
+
+/*! Removes the bridge to playback channel link */
+static void remove_bridge_playback(char *bridge_id)
+{
+	struct stasis_app_bridge_channel_wrapper *wrapper;
+	struct stasis_app_control *control;
+
+	wrapper = ao2_find(app_bridges_playback, bridge_id, OBJ_SEARCH_KEY | OBJ_UNLINK);
+
+	if (wrapper) {
+		control = stasis_app_control_find_by_channel_id(wrapper->channel_id);
+		if (control) {
+			ao2_unlink(app_controls, control);
+			ao2_ref(control, -1);
+		}
+		ao2_ref(wrapper, -1);
+	}
+	ast_free(bridge_id);
+}
+
+static void playback_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
+{
+	char *bridge_id = data;
+
+	remove_bridge_playback(bridge_id);
+}
+
+static void playback_after_bridge_cb(struct ast_channel *chan, void *data)
+{
+	char *bridge_id = data;
+
+	remove_bridge_playback(bridge_id);
+}
+
+int stasis_app_bridge_playback_channel_add(struct ast_bridge *bridge,
+	struct ast_channel *chan,
+	struct stasis_app_control *control)
+{
+	RAII_VAR(struct stasis_app_bridge_channel_wrapper *, new_wrapper, NULL, ao2_cleanup);
+	char *bridge_id = ast_strdup(bridge->uniqueid);
+
+	if (!bridge_id) {
+		return -1;
+	}
+
+	if (ast_bridge_set_after_callback(chan,
+		playback_after_bridge_cb, playback_after_bridge_cb_failed, bridge_id)) {
+		ast_free(bridge_id);
+		return -1;
+	}
+
+	new_wrapper = ao2_alloc_options(sizeof(*new_wrapper),
+		stasis_app_bridge_channel_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!new_wrapper) {
+		return -1;
+	}
+
+	if (ast_string_field_init(new_wrapper, 32)) {
+		return -1;
+	}
+
+	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_playback, new_wrapper)) {
+		return -1;
+	}
+
+	ao2_link(app_controls, control);
+	return 0;
+}
+
+struct ast_channel *stasis_app_bridge_playback_channel_find(struct ast_bridge *bridge)
+{
+	struct stasis_app_bridge_channel_wrapper *playback_wrapper;
+	struct ast_channel *chan;
+
+	playback_wrapper = ao2_find(app_bridges_playback, bridge->uniqueid, OBJ_SEARCH_KEY);
+	if (!playback_wrapper) {
+		return NULL;
+	}
+
+	chan = ast_channel_get_by_name(playback_wrapper->channel_id);
+	ao2_ref(playback_wrapper, -1);
+	return chan;
 }
 
 struct ast_bridge *stasis_app_bridge_find_by_id(
@@ -720,11 +808,29 @@
 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);
+		int command_count;
+		command_count = control_dispatch_all(control, chan);
+
+		ao2_lock(control);
+
+		if (control_command_count(control)) {
+			/* If the command queue isn't empty, something added to the queue before it was locked. */
+			ao2_unlock(control);
+			continue;
+		}
+
 		if (command_count == 0 || ast_channel_fdno(chan) == -1) {
+			control_mark_done(control);
+			ao2_unlock(control);
 			break;
 		}
-	}
+		ao2_unlock(control);
+	}
+}
+
+int stasis_app_control_is_done(struct stasis_app_control *control)
+{
+	return control_is_done(control);
 }
 
 /*! /brief Stasis dialplan application callback */
@@ -1230,6 +1336,9 @@
 	ao2_cleanup(app_bridges_moh);
 	app_bridges_moh = NULL;
 
+	ao2_cleanup(app_bridges_playback);
+	app_bridges_playback = NULL;
+
 	return 0;
 }
 
@@ -1268,8 +1377,11 @@
 	app_bridges = ao2_container_alloc(BRIDGES_NUM_BUCKETS, bridges_hash, bridges_compare);
 	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 (!apps_registry || !app_controls || !app_bridges || !app_bridges_moh) {
+		37, bridges_channel_hash_fn, bridges_channel_sort_fn, NULL);
+	app_bridges_playback = ao2_container_alloc_hash(
+		AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
+		37, bridges_channel_hash_fn, bridges_channel_sort_fn, NULL);
+	if (!apps_registry || !app_controls || !app_bridges || !app_bridges_moh || !app_bridges_playback) {
 		unload_module();
 		return AST_MODULE_LOAD_FAILURE;
 	}

Modified: trunk/res/res_stasis_playback.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_playback.c?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/res_stasis_playback.c (original)
+++ trunk/res/res_stasis_playback.c Fri Apr 18 15:09:24 2014
@@ -330,7 +330,7 @@
 		res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
 	} else {
 		/* Play URL */
-		ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported",
+		ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
 			playback->media, ast_channel_name(chan));
 		return;
 	}

Modified: trunk/res/stasis/control.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis/control.c?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/stasis/control.c (original)
+++ trunk/res/stasis/control.c Fri Apr 18 15:09:24 2014
@@ -368,10 +368,20 @@
 	ast_channel_clear_bridge_roles(control->channel);
 }
 
+int control_command_count(struct stasis_app_control *control)
+{
+	return ao2_container_count(control->command_queue);
+}
+
 int control_is_done(struct stasis_app_control *control)
 {
 	/* Called from stasis_app_exec thread; no lock needed */
 	return control->is_done;
+}
+
+void control_mark_done(struct stasis_app_control *control)
+{
+	control->is_done = 1;
 }
 
 struct stasis_app_control_continue_data {

Modified: trunk/res/stasis/control.h
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis/control.h?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/res/stasis/control.h (original)
+++ trunk/res/stasis/control.h Fri Apr 18 15:09:24 2014
@@ -58,6 +58,15 @@
 void control_wait(struct stasis_app_control *control);
 
 /*!
+ * \brief Returns the count of items in a control's command queue.
+ *
+ * \param control Control to count commands on
+ *
+ * \retval number of commands in the command que
+ */
+int control_command_count(struct stasis_app_control *control);
+
+/*!
  * \brief Returns true if control_continue() has been called on this \a control.
  *
  * \param control Control to query.
@@ -66,5 +75,7 @@
  */
 int control_is_done(struct stasis_app_control *control);
 
+void control_mark_done(struct stasis_app_control *control);
+
 
 #endif /* _ASTERISK_RES_STASIS_CONTROL_H */

Modified: trunk/rest-api/api-docs/bridges.json
URL: http://svnview.digium.com/svn/asterisk/trunk/rest-api/api-docs/bridges.json?view=diff&rev=412641&r1=412640&r2=412641
==============================================================================
--- trunk/rest-api/api-docs/bridges.json (original)
+++ trunk/rest-api/api-docs/bridges.json Fri Apr 18 15:09:24 2014
@@ -368,7 +368,14 @@
 								"valueType": "RANGE",
 								"min": 0
 							}
-
+						},
+						{
+							"name": "playbackId",
+							"description": "Playback Id.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string"
 						}
 					],
 					"errorResponses": [
@@ -381,6 +388,90 @@
 							"reason": "Bridge not in a Stasis application"
 						}
 					]
+				}
+			]
+		},
+		{
+			"path": "/bridges/{bridgeId}/play/{playbackId}",
+			"description": "Play media to a bridge",
+			"operations": [
+				{
+					"httpMethod": "POST",
+					"summary": "Start playback of media on a bridge.",
+					"notes": "The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
+					"nickname": "playWithId",
+					"responseClass": "Playback",
+					"parameters": [
+						{
+							"name": "bridgeId",
+							"description": "Bridge's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "playbackId",
+							"description": "Playback ID.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "media",
+							"description": "Media's URI to play.",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "lang",
+							"description": "For sounds, selects language for sound.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "offsetms",
+							"description": "Number of media to skip before playing.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "int",
+							"defaultValue": 0,
+							"allowableValues": {
+								"valueType": "RANGE",
+								"min": 0
+							}
+						},
+						{
+							"name": "skipms",
+							"description": "Number of milliseconds to skip for forward/reverse operations.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "int",
+							"defaultValue": 3000,
+							"allowableValues": {
+								"valueType": "RANGE",
+								"min": 0
+							}
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 404,
+							"reason": "Bridge not found"
+						},
+						{
+							"code": 409,
+							"reason": "Bridge not in a Stasis application"
+						}
+					]
+
 				}
 			]
 		},




More information about the asterisk-commits mailing list