mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	This patch implements the REST API's for POST /channels/{channelId}/play
and GET /playback/{playbackId}.
This allows an external application to initiate playback of a sound on a
channel while the channel is in the Stasis application.
/play commands are issued asynchronously, and return immediately with
the URL of the associated /playback resource. Playback commands queue up,
playing in succession. The /playback resource shows the state of a
playback operation as enqueued, playing or complete. (Although the
operation will only be in the 'complete' state for a very short time,
since it is almost immediately freed up).
(closes issue ASTERISK-21283)
(closes issue ASTERISK-21586)
Review: https://reviewboard.asterisk.org/r/2531/
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389587 65c4cc65-6c06-0410-ace0-fbb531ad65f3
			
			
This commit is contained in:
		| @@ -636,14 +636,19 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in | ||||
|  | ||||
| /*! | ||||
|  * \brief Stream a file with fast forward, pause, reverse, restart. | ||||
|  * \param chan | ||||
|  * \param file filename | ||||
|  * \param fwd, rev, stop, pause, restart, skipms, offsetms | ||||
|  * \param chan Channel | ||||
|  * \param file File to play. | ||||
|  * \param fwd, rev, stop, pause, restart DTMF keys for media control | ||||
|  * \param skipms Number of milliseconds to skip for fwd/rev. | ||||
|  * \param offsetms Number of milliseconds to skip when starting the media. | ||||
|  * | ||||
|  * Before calling this function, set this to be the number | ||||
|  * of ms to start from the beginning of the file.  When the function | ||||
|  * returns, it will be the number of ms from the beginning where the | ||||
|  * playback stopped.  Pass NULL if you don't care. | ||||
|  * | ||||
|  * \retval 0 on success | ||||
|  * \retval Non-zero on failure | ||||
|  */ | ||||
| int ast_control_streamfile(struct ast_channel *chan, const char *file, const char *fwd, const char *rev, const char *stop, const char *pause, const char *restart, int skipms, long *offsetms); | ||||
|  | ||||
|   | ||||
							
								
								
									
										117
									
								
								include/asterisk/stasis_app_playback.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								include/asterisk/stasis_app_playback.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| /* | ||||
|  * Asterisk -- An open source telephony toolkit. | ||||
|  * | ||||
|  * Copyright (C) 2013, Digium, Inc. | ||||
|  * | ||||
|  * David M. Lee, II <dlee@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. | ||||
|  */ | ||||
|  | ||||
| #ifndef _ASTERISK_STASIS_APP_PLAYBACK_H | ||||
| #define _ASTERISK_STASIS_APP_PLAYBACK_H | ||||
|  | ||||
| /*! \file | ||||
|  * | ||||
|  * \brief Stasis Application Playback API. See \ref res_stasis "Stasis | ||||
|  * Application API" for detailed documentation. | ||||
|  * | ||||
|  * \author David M. Lee, II <dlee@digium.com> | ||||
|  * \since 12 | ||||
|  */ | ||||
|  | ||||
| #include "asterisk/stasis_app.h" | ||||
|  | ||||
| /*! Opaque struct for handling the playback of a single file */ | ||||
| struct stasis_app_playback; | ||||
|  | ||||
| /*! State of a playback operation */ | ||||
| enum stasis_app_playback_state { | ||||
| 	/*! The playback has not started yet */ | ||||
| 	STASIS_PLAYBACK_STATE_QUEUED, | ||||
| 	/*! The media is currently playing */ | ||||
| 	STASIS_PLAYBACK_STATE_PLAYING, | ||||
| 	/*! The media has stopped playing */ | ||||
| 	STASIS_PLAYBACK_STATE_COMPLETE, | ||||
| }; | ||||
|  | ||||
| enum stasis_app_playback_media_control { | ||||
| 	STASIS_PLAYBACK_STOP, | ||||
| 	STASIS_PLAYBACK_PAUSE, | ||||
| 	STASIS_PLAYBACK_PLAY, | ||||
| 	STASIS_PLAYBACK_REWIND, | ||||
| 	STASIS_PLAYBACK_FAST_FORWARD, | ||||
| 	STASIS_PLAYBACK_SPEED_UP, | ||||
| 	STASIS_PLAYBACK_SLOW_DOWN, | ||||
| }; | ||||
|  | ||||
| /*! | ||||
|  * \brief Play a file to the control's channel. | ||||
|  * | ||||
|  * Note that the file isn't the full path to the file. Asterisk's internal | ||||
|  * playback mechanism will automagically select the best format based on the | ||||
|  * available codecs for the channel. | ||||
|  * | ||||
|  * \param control Control for \c res_stasis. | ||||
|  * \param file Base filename for the file to play. | ||||
|  * \return Playback control object. | ||||
|  * \return \c NULL on error. | ||||
|  */ | ||||
| struct stasis_app_playback *stasis_app_control_play_uri( | ||||
| 	struct stasis_app_control *control, const char *file, | ||||
| 	const char *language); | ||||
|  | ||||
| /*! | ||||
|  * \brief Gets the current state of a playback operation. | ||||
|  * | ||||
|  * \param playback Playback control object. | ||||
|  * \return The state of the \a playback object. | ||||
|  */ | ||||
| enum stasis_app_playback_state stasis_app_playback_get_state( | ||||
| 	struct stasis_app_playback *playback); | ||||
|  | ||||
| /*! | ||||
|  * \brief Gets the unique id of a playback object. | ||||
|  * | ||||
|  * \param playback Playback control object. | ||||
|  * \return \a playback's id. | ||||
|  * \return \c NULL if \a playback ic \c NULL | ||||
|  */ | ||||
| const char *stasis_app_playback_get_id( | ||||
| 	struct stasis_app_playback *playback); | ||||
|  | ||||
| /*! | ||||
|  * \brief Finds the playback object with the given id. | ||||
|  * | ||||
|  * \param id Id of the playback object to find. | ||||
|  * \return Associated \ref stasis_app_playback object. | ||||
|  * \return \c NULL if \a id not found. | ||||
|  */ | ||||
| struct ast_json *stasis_app_playback_find_by_id(const char *id); | ||||
|  | ||||
| /*! | ||||
|  * \brief Controls the media for a given playback operation. | ||||
|  * | ||||
|  * \param playback Playback control object. | ||||
|  * \param control Media control operation. | ||||
|  * \return 0 on success | ||||
|  * \return non-zero on error. | ||||
|  */ | ||||
| int stasis_app_playback_control(struct stasis_app_playback *playback, | ||||
| 	enum stasis_app_playback_media_control control); | ||||
|  | ||||
| /*! | ||||
|  * \brief Message type for playback updates. The data is an | ||||
|  * \ref ast_channel_blob. | ||||
|  */ | ||||
| struct stasis_message_type *stasis_app_playback_snapshot_type(void); | ||||
|  | ||||
| #endif /* _ASTERISK_STASIS_APP_PLAYBACK_H */ | ||||
| @@ -54,6 +54,7 @@ struct ast_channel_snapshot { | ||||
| 		AST_STRING_FIELD(caller_number);	/*!< Caller ID Number */ | ||||
| 		AST_STRING_FIELD(connected_name);	/*!< Connected Line Name */ | ||||
| 		AST_STRING_FIELD(connected_number);	/*!< Connected Line Number */ | ||||
| 		AST_STRING_FIELD(language);		/*!< The default spoken language for the channel */ | ||||
| 	); | ||||
|  | ||||
| 	struct timeval creationtime;	/*!< The time of channel creation */ | ||||
| @@ -122,6 +123,17 @@ struct stasis_message_type *ast_channel_snapshot_type(void); | ||||
| struct ast_channel_snapshot *ast_channel_snapshot_create( | ||||
| 	struct ast_channel *chan); | ||||
|  | ||||
| /*! | ||||
|  * \since 12 | ||||
|  * \brief Get the most recent snapshot for channel with the given \a uniqueid. | ||||
|  * | ||||
|  * \param uniqueid Uniqueid of the snapshot to fetch. | ||||
|  * \return Most recent channel snapshot | ||||
|  * \return \c NULL on error | ||||
|  */ | ||||
| struct ast_channel_snapshot *ast_channel_snapshot_get_latest( | ||||
| 	const char *uniqueid); | ||||
|  | ||||
| /*! | ||||
|  * \since 12 | ||||
|  * \brief Creates a \ref ast_channel_blob message. | ||||
| @@ -140,6 +152,23 @@ struct ast_channel_snapshot *ast_channel_snapshot_create( | ||||
| struct stasis_message *ast_channel_blob_create(struct ast_channel *chan, | ||||
| 	struct stasis_message_type *type, struct ast_json *blob); | ||||
|  | ||||
| /*! | ||||
|  * \since 12 | ||||
|  * \brief Create a \ref ast_channel_blob message, pulling channel state from | ||||
|  *        the cache. | ||||
|  * | ||||
|  * \param uniqueid Uniqueid of the channel. | ||||
|  * \param type Message type for this blob. | ||||
|  * \param blob JSON object representing the data, or \c NULL for no data. If | ||||
|  *             \c NULL, ast_json_null() is put into the object. | ||||
|  * | ||||
|  * \return \ref ast_channel_blob message. | ||||
|  * \return \c NULL on error | ||||
|  */ | ||||
| struct stasis_message *ast_channel_blob_create_from_cache( | ||||
| 	const char *uniqueid, struct stasis_message_type *type, | ||||
| 	struct ast_json *blob); | ||||
|  | ||||
| /*! | ||||
|  * \since 12 | ||||
|  * \brief Create a \ref ast_multi_channel_blob suitable for a \ref stasis_message. | ||||
| @@ -221,6 +250,14 @@ struct ast_json *ast_multi_channel_blob_get_json(struct ast_multi_channel_blob * | ||||
| void ast_multi_channel_blob_add_channel(struct ast_multi_channel_blob *obj, | ||||
| 	const char *role, struct ast_channel_snapshot *snapshot); | ||||
|  | ||||
| /*! | ||||
|  * \since 12 | ||||
|  * \brief Publish a \ref ast_channel_snapshot for a channel. | ||||
|  * | ||||
|  * \param chan Channel to publish. | ||||
|  */ | ||||
| void ast_channel_publish_snapshot(struct ast_channel *chan); | ||||
|  | ||||
| /*! | ||||
|  * \since 12 | ||||
|  * \brief Publish a \ref ast_channel_varset for a channel. | ||||
|   | ||||
| @@ -162,6 +162,12 @@ void stasis_http_response_ok(struct stasis_http_response *response, | ||||
|  */ | ||||
| void stasis_http_response_no_content(struct stasis_http_response *response); | ||||
|  | ||||
| /*! | ||||
|  * \brief Fill in a <tt>Created</tt> (201) \a stasis_http_response. | ||||
|  */ | ||||
| void stasis_http_response_created(struct stasis_http_response *response, | ||||
| 	const char *url); | ||||
|  | ||||
| /*! | ||||
|  * \brief Fill in \a response with a 500 message for allocation failures. | ||||
|  * \param response Response to fill in. | ||||
|   | ||||
| @@ -414,15 +414,17 @@ int ast_channel_data_cmp_structure(const struct ast_data_search *tree, | ||||
|  | ||||
| /* ACCESSORS */ | ||||
|  | ||||
| #define DEFINE_STRINGFIELD_SETTERS_FOR(field) \ | ||||
| #define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish)			\ | ||||
| void ast_channel_##field##_set(struct ast_channel *chan, const char *value) \ | ||||
| { \ | ||||
| 	ast_string_field_set(chan, field, value); \ | ||||
| 	if (publish) ast_channel_publish_snapshot(chan); \ | ||||
| } \ | ||||
|   \ | ||||
| void ast_channel_##field##_build_va(struct ast_channel *chan, const char *fmt, va_list ap) \ | ||||
| { \ | ||||
| 	ast_string_field_build_va(chan, field, fmt, ap); \ | ||||
| 	if (publish) ast_channel_publish_snapshot(chan); \ | ||||
| } \ | ||||
| void ast_channel_##field##_build(struct ast_channel *chan, const char *fmt, ...) \ | ||||
| { \ | ||||
| @@ -430,19 +432,20 @@ void ast_channel_##field##_build(struct ast_channel *chan, const char *fmt, ...) | ||||
| 	va_start(ap, fmt); \ | ||||
| 	ast_channel_##field##_build_va(chan, fmt, ap); \ | ||||
| 	va_end(ap); \ | ||||
| 	if (publish) ast_channel_publish_snapshot(chan); \ | ||||
| } | ||||
|  | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(name); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(language); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(musicclass); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(accountcode); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(userfield); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(call_forward); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(name, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(language, 1); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(call_forward, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource, 0); | ||||
| DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext, 0); | ||||
|  | ||||
| #define DEFINE_STRINGFIELD_GETTER_FOR(field) const char *ast_channel_##field(const struct ast_channel *chan) \ | ||||
| { \ | ||||
|   | ||||
| @@ -134,6 +134,7 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha | ||||
| 		S_COR(ast_channel_connected(chan)->id.name.valid, ast_channel_connected(chan)->id.name.str, "")); | ||||
| 	ast_string_field_set(snapshot, connected_number, | ||||
| 		S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, "")); | ||||
| 	ast_string_field_set(snapshot, language, ast_channel_language(chan)); | ||||
|  | ||||
| 	snapshot->creationtime = ast_channel_creationtime(chan); | ||||
| 	snapshot->state = ast_channel_state(chan); | ||||
| @@ -149,6 +150,28 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha | ||||
| 	return snapshot; | ||||
| } | ||||
|  | ||||
| struct ast_channel_snapshot *ast_channel_snapshot_get_latest( | ||||
| 	const char *uniqueid) | ||||
| { | ||||
|         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); | ||||
| 	struct ast_channel_snapshot *snapshot; | ||||
|  | ||||
| 	msg = stasis_cache_get(ast_channel_topic_all_cached(), | ||||
| 		ast_channel_snapshot_type(), uniqueid); | ||||
|  | ||||
| 	if (!msg) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	snapshot = stasis_message_data(msg); | ||||
| 	if (!snapshot) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	ao2_ref(snapshot, +1); | ||||
| 	return snapshot; | ||||
| } | ||||
|  | ||||
| static void publish_message_for_channel_topics(struct stasis_message *message, struct ast_channel *chan) | ||||
| { | ||||
| 	if (chan) { | ||||
| @@ -207,7 +230,8 @@ void ast_channel_publish_dial(struct ast_channel *caller, struct ast_channel *pe | ||||
| 	publish_message_for_channel_topics(msg, caller); | ||||
| } | ||||
|  | ||||
| struct stasis_message *ast_channel_blob_create(struct ast_channel *chan, | ||||
| static struct stasis_message *channel_blob_create( | ||||
| 	struct ast_channel_snapshot *snapshot, | ||||
| 	struct stasis_message_type *type, struct ast_json *blob) | ||||
| { | ||||
| 	RAII_VAR(struct ast_channel_blob *, obj, NULL, ao2_cleanup); | ||||
| @@ -222,11 +246,9 @@ struct stasis_message *ast_channel_blob_create(struct ast_channel *chan, | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	if (chan) { | ||||
| 		obj->snapshot = ast_channel_snapshot_create(chan); | ||||
| 		if (obj->snapshot == NULL) { | ||||
| 			return NULL; | ||||
| 		} | ||||
| 	if (snapshot) { | ||||
| 		ao2_ref(snapshot, +1); | ||||
| 		obj->snapshot = snapshot; | ||||
| 	} | ||||
|  | ||||
| 	obj->blob = ast_json_ref(blob); | ||||
| @@ -240,6 +262,35 @@ struct stasis_message *ast_channel_blob_create(struct ast_channel *chan, | ||||
| 	return msg; | ||||
| } | ||||
|  | ||||
| struct stasis_message *ast_channel_blob_create(struct ast_channel *chan, | ||||
| 	struct stasis_message_type *type, struct ast_json *blob) | ||||
| { | ||||
| 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); | ||||
|  | ||||
| 	if (chan != NULL) { | ||||
| 		snapshot = ast_channel_snapshot_create(chan); | ||||
| 		if (snapshot == NULL) { | ||||
| 			return NULL; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return channel_blob_create(snapshot, type, blob); | ||||
| } | ||||
|  | ||||
| struct stasis_message *ast_channel_blob_create_from_cache( | ||||
| 	const char *uniqueid, struct stasis_message_type *type, | ||||
| 	struct ast_json *blob) | ||||
| { | ||||
| 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); | ||||
|  | ||||
| 	snapshot = ast_channel_snapshot_get_latest(uniqueid); | ||||
| 	if (snapshot == NULL) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	return channel_blob_create(snapshot, type, blob); | ||||
| } | ||||
|  | ||||
| /*! \brief A channel snapshot wrapper object used in \ref ast_multi_channel_blob objects */ | ||||
| struct channel_role_snapshot { | ||||
| 	struct ast_channel_snapshot *snapshot;	/*!< A channel snapshot */ | ||||
| @@ -389,6 +440,26 @@ struct ast_json *ast_multi_channel_blob_get_json(struct ast_multi_channel_blob * | ||||
| 	return obj->blob; | ||||
| } | ||||
|  | ||||
| void ast_channel_publish_snapshot(struct ast_channel *chan) | ||||
| { | ||||
| 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); | ||||
| 	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); | ||||
|  | ||||
| 	snapshot = ast_channel_snapshot_create(chan); | ||||
| 	if (!snapshot) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	message = stasis_message_create(ast_channel_snapshot_type(), snapshot); | ||||
| 	if (!message) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	ast_assert(ast_channel_topic(chan) != NULL); | ||||
| 	stasis_publish(ast_channel_topic(chan), message); | ||||
| } | ||||
|  | ||||
|  | ||||
| void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value) | ||||
| { | ||||
| 	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); | ||||
|   | ||||
| @@ -330,6 +330,15 @@ void stasis_http_response_alloc_failed(struct stasis_http_response *response) | ||||
| 	response->response_text = "Internal Server Error"; | ||||
| } | ||||
|  | ||||
| void stasis_http_response_created(struct stasis_http_response *response, | ||||
| 	const char *url) | ||||
| { | ||||
| 	response->message = ast_json_null(); | ||||
| 	response->response_code = 201; | ||||
| 	response->response_text = "Created"; | ||||
| 	ast_str_append(&response->headers, 0, "Location: %s\r\n", url); | ||||
| } | ||||
|  | ||||
| static void add_allow_header(struct stasis_rest_handlers *handler, | ||||
| 			     struct stasis_http_response *response) | ||||
| { | ||||
|   | ||||
| @@ -327,6 +327,9 @@ static void stasis_http_play_on_channel_cb( | ||||
| 		if (strcmp(i->name, "media") == 0) { | ||||
| 			args.media = (i->value); | ||||
| 		} else | ||||
| 		if (strcmp(i->name, "lang") == 0) { | ||||
| 			args.lang = (i->value); | ||||
| 		} else | ||||
| 		{} | ||||
| 	} | ||||
| 	for (i = path_vars; i; i = i->next) { | ||||
|   | ||||
| @@ -116,30 +116,18 @@ struct ast_json *stasis_json_event_bridge_created_create( | ||||
| 	return ast_json_ref(message); | ||||
| } | ||||
|  | ||||
| struct ast_json *stasis_json_event_channel_destroyed_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| struct ast_json *stasis_json_event_playback_finished_create( | ||||
| 	struct ast_json *blob | ||||
| 	) | ||||
| { | ||||
| 	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); | ||||
| 	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); | ||||
| 	struct ast_json *validator; | ||||
| 	int ret; | ||||
|  | ||||
| 	ast_assert(channel_snapshot != NULL); | ||||
| 	ast_assert(blob != NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "channel") == NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "type") == NULL); | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "cause"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} else { | ||||
| 		/* fail message generation if the required parameter doesn't exist */ | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "cause_txt"); | ||||
| 	validator = ast_json_object_get(blob, "playback"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} else { | ||||
| @@ -152,13 +140,7 @@ struct ast_json *stasis_json_event_channel_destroyed_create( | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	ret = ast_json_object_set(event, | ||||
| 		"channel", ast_channel_snapshot_to_json(channel_snapshot)); | ||||
| 	if (ret) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	message = ast_json_pack("{s: o}", "channel_destroyed", ast_json_ref(event)); | ||||
| 	message = ast_json_pack("{s: o}", "playback_finished", ast_json_ref(event)); | ||||
| 	if (!message) { | ||||
| 		return NULL; | ||||
| 	} | ||||
| @@ -245,29 +227,23 @@ struct ast_json *stasis_json_event_channel_caller_id_create( | ||||
| 	return ast_json_ref(message); | ||||
| } | ||||
|  | ||||
| struct ast_json *stasis_json_event_channel_hangup_request_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| struct ast_json *stasis_json_event_playback_started_create( | ||||
| 	struct ast_json *blob | ||||
| 	) | ||||
| { | ||||
| 	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); | ||||
| 	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); | ||||
| 	struct ast_json *validator; | ||||
| 	int ret; | ||||
|  | ||||
| 	ast_assert(channel_snapshot != NULL); | ||||
| 	ast_assert(blob != NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "channel") == NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "type") == NULL); | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "soft"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "cause"); | ||||
| 	validator = ast_json_object_get(blob, "playback"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} else { | ||||
| 		/* fail message generation if the required parameter doesn't exist */ | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	event = ast_json_deep_copy(blob); | ||||
| @@ -275,13 +251,7 @@ struct ast_json *stasis_json_event_channel_hangup_request_create( | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	ret = ast_json_object_set(event, | ||||
| 		"channel", ast_channel_snapshot_to_json(channel_snapshot)); | ||||
| 	if (ret) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	message = ast_json_pack("{s: o}", "channel_hangup_request", ast_json_ref(event)); | ||||
| 	message = ast_json_pack("{s: o}", "playback_started", ast_json_ref(event)); | ||||
| 	if (!message) { | ||||
| 		return NULL; | ||||
| 	} | ||||
| @@ -350,6 +320,56 @@ struct ast_json *stasis_json_event_application_replaced_create( | ||||
| 	return ast_json_ref(message); | ||||
| } | ||||
|  | ||||
| struct ast_json *stasis_json_event_channel_destroyed_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| 	struct ast_json *blob | ||||
| 	) | ||||
| { | ||||
| 	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); | ||||
| 	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); | ||||
| 	struct ast_json *validator; | ||||
| 	int ret; | ||||
|  | ||||
| 	ast_assert(channel_snapshot != NULL); | ||||
| 	ast_assert(blob != NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "channel") == NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "type") == NULL); | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "cause"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} else { | ||||
| 		/* fail message generation if the required parameter doesn't exist */ | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "cause_txt"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} else { | ||||
| 		/* fail message generation if the required parameter doesn't exist */ | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	event = ast_json_deep_copy(blob); | ||||
| 	if (!event) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	ret = ast_json_object_set(event, | ||||
| 		"channel", ast_channel_snapshot_to_json(channel_snapshot)); | ||||
| 	if (ret) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	message = ast_json_pack("{s: o}", "channel_destroyed", ast_json_ref(event)); | ||||
| 	if (!message) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	return ast_json_ref(message); | ||||
| } | ||||
|  | ||||
| struct ast_json *stasis_json_event_channel_varset_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| 	struct ast_json *blob | ||||
| @@ -587,6 +607,50 @@ struct ast_json *stasis_json_event_channel_state_change_create( | ||||
| 	return ast_json_ref(message); | ||||
| } | ||||
|  | ||||
| struct ast_json *stasis_json_event_channel_hangup_request_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| 	struct ast_json *blob | ||||
| 	) | ||||
| { | ||||
| 	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); | ||||
| 	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); | ||||
| 	struct ast_json *validator; | ||||
| 	int ret; | ||||
|  | ||||
| 	ast_assert(channel_snapshot != NULL); | ||||
| 	ast_assert(blob != NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "channel") == NULL); | ||||
| 	ast_assert(ast_json_object_get(blob, "type") == NULL); | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "soft"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} | ||||
|  | ||||
| 	validator = ast_json_object_get(blob, "cause"); | ||||
| 	if (validator) { | ||||
| 		/* do validation? XXX */ | ||||
| 	} | ||||
|  | ||||
| 	event = ast_json_deep_copy(blob); | ||||
| 	if (!event) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	ret = ast_json_object_set(event, | ||||
| 		"channel", ast_channel_snapshot_to_json(channel_snapshot)); | ||||
| 	if (ret) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	message = ast_json_pack("{s: o}", "channel_hangup_request", ast_json_ref(event)); | ||||
| 	if (!message) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	return ast_json_ref(message); | ||||
| } | ||||
|  | ||||
| struct ast_json *stasis_json_event_channel_entered_bridge_create( | ||||
| 	struct ast_bridge_snapshot *bridge_snapshot, | ||||
| 	struct ast_channel_snapshot *channel_snapshot | ||||
|   | ||||
| @@ -2,18 +2,20 @@ | ||||
| 	global: | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_userevent_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_bridge_created_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_destroyed_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_playback_finished_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_snapshot_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_caller_id_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_hangup_request_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_playback_started_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_bridge_destroyed_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_application_replaced_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_destroyed_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_varset_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_left_bridge_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_created_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_stasis_start_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_dialplan_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_state_change_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_hangup_request_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_entered_bridge_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_channel_dtmf_received_create; | ||||
| 		LINKER_SYMBOL_PREFIXstasis_json_event_stasis_end_create; | ||||
|   | ||||
							
								
								
									
										320
									
								
								res/res_stasis_playback.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								res/res_stasis_playback.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,320 @@ | ||||
| /* | ||||
|  * Asterisk -- An open source telephony toolkit. | ||||
|  * | ||||
|  * Copyright (C) 2013, Digium, Inc. | ||||
|  * | ||||
|  * David M. Lee, II <dlee@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 res_stasis playback support. | ||||
|  * | ||||
|  * \author David M. Lee, II <dlee@digium.com> | ||||
|  */ | ||||
|  | ||||
| /*** MODULEINFO | ||||
| 	<depend type="module">res_stasis</depend> | ||||
| 	<support_level>core</support_level> | ||||
|  ***/ | ||||
|  | ||||
| #include "asterisk.h" | ||||
|  | ||||
| ASTERISK_FILE_VERSION(__FILE__, "$Revision$") | ||||
|  | ||||
| #include "asterisk/app.h" | ||||
| #include "asterisk/astobj2.h" | ||||
| #include "asterisk/file.h" | ||||
| #include "asterisk/logger.h" | ||||
| #include "asterisk/module.h" | ||||
| #include "asterisk/stasis_app_impl.h" | ||||
| #include "asterisk/stasis_app_playback.h" | ||||
| #include "asterisk/stasis_channels.h" | ||||
| #include "asterisk/stringfields.h" | ||||
| #include "asterisk/uuid.h" | ||||
|  | ||||
| /*! Number of hash buckets for playback container. Keep it prime! */ | ||||
| #define PLAYBACK_BUCKETS 127 | ||||
|  | ||||
| /*! Number of milliseconds of media to skip */ | ||||
| #define PLAYBACK_SKIPMS 250 | ||||
|  | ||||
| #define SOUND_URI_SCHEME "sound:" | ||||
| #define RECORDING_URI_SCHEME "recording:" | ||||
|  | ||||
| STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type); | ||||
|  | ||||
| /*! Container of all current playbacks */ | ||||
| static struct ao2_container *playbacks; | ||||
|  | ||||
| /*! Playback control object for res_stasis */ | ||||
| struct stasis_app_playback { | ||||
| 	AST_DECLARE_STRING_FIELDS( | ||||
| 		AST_STRING_FIELD(id);	/*!< Playback unique id */ | ||||
| 		AST_STRING_FIELD(media);	/*!< Playback media uri */ | ||||
| 		AST_STRING_FIELD(language);	/*!< Preferred language */ | ||||
| 		); | ||||
| 	/*! Current playback state */ | ||||
| 	enum stasis_app_playback_state state; | ||||
| 	/*! Control object for the channel we're playing back to */ | ||||
| 	struct stasis_app_control *control; | ||||
| }; | ||||
|  | ||||
| static int playback_hash(const void *obj, int flags) | ||||
| { | ||||
| 	const struct stasis_app_playback *playback = obj; | ||||
| 	const char *id = flags & OBJ_KEY ? obj : playback->id; | ||||
| 	return ast_str_hash(id); | ||||
| } | ||||
|  | ||||
| static int playback_cmp(void *obj, void *arg, int flags) | ||||
| { | ||||
| 	struct stasis_app_playback *lhs = obj; | ||||
| 	struct stasis_app_playback *rhs = arg; | ||||
| 	const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id; | ||||
|  | ||||
| 	if (strcmp(lhs->id, rhs_id) == 0) { | ||||
| 		return CMP_MATCH | CMP_STOP; | ||||
| 	} else { | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static const char *state_to_string(enum stasis_app_playback_state state) | ||||
| { | ||||
| 	switch (state) { | ||||
| 	case STASIS_PLAYBACK_STATE_QUEUED: | ||||
| 		return "queued"; | ||||
| 	case STASIS_PLAYBACK_STATE_PLAYING: | ||||
| 		return "playing"; | ||||
| 	case STASIS_PLAYBACK_STATE_COMPLETE: | ||||
| 		return "done"; | ||||
| 	} | ||||
|  | ||||
| 	return "?"; | ||||
| } | ||||
|  | ||||
| static struct ast_json *playback_to_json(struct stasis_app_playback *playback) | ||||
| { | ||||
| 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); | ||||
|  | ||||
| 	if (playback == NULL) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	json = ast_json_pack("{s: s, s: s, s: s, s: s}", | ||||
| 		"id", playback->id, | ||||
| 		"media_uri", playback->media, | ||||
| 		"language", playback->language, | ||||
| 		"state", state_to_string(playback->state)); | ||||
|  | ||||
| 	return ast_json_ref(json); | ||||
| } | ||||
|  | ||||
| static void playback_publish(struct stasis_app_playback *playback) | ||||
| { | ||||
| 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); | ||||
| 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); | ||||
| 	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); | ||||
|  | ||||
| 	ast_assert(playback != NULL); | ||||
|  | ||||
| 	json = playback_to_json(playback); | ||||
| 	if (json == NULL) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	message = ast_channel_blob_create_from_cache( | ||||
| 		stasis_app_control_get_channel_id(playback->control), | ||||
| 		stasis_app_playback_snapshot_type(), json); | ||||
| 	if (message == NULL) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	stasis_app_control_publish(playback->control, message); | ||||
| } | ||||
|  | ||||
| static void playback_set_state(struct stasis_app_playback *playback, | ||||
| 	enum stasis_app_playback_state state) | ||||
| { | ||||
| 	SCOPED_AO2LOCK(lock, playback); | ||||
|  | ||||
| 	playback->state = state; | ||||
| 	playback_publish(playback); | ||||
| } | ||||
|  | ||||
| static void playback_cleanup(struct stasis_app_playback *playback) | ||||
| { | ||||
| 	playback_set_state(playback, STASIS_PLAYBACK_STATE_COMPLETE); | ||||
|  | ||||
| 	ao2_unlink_flags(playbacks, playback, | ||||
| 		OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); | ||||
| } | ||||
|  | ||||
| static void *__app_control_play_uri(struct stasis_app_control *control, | ||||
| 	struct ast_channel *chan, void *data) | ||||
| { | ||||
| 	RAII_VAR(struct stasis_app_playback *, playback, NULL, | ||||
| 		playback_cleanup); | ||||
| 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); | ||||
| 	const char *file; | ||||
| 	int res; | ||||
| 	/* Even though these local variables look fairly pointless, the avoid | ||||
| 	 * having a bunch of NULL's passed directly into | ||||
| 	 * ast_control_streamfile() */ | ||||
| 	const char *fwd = NULL; | ||||
| 	const char *rev = NULL; | ||||
| 	const char *stop = NULL; | ||||
| 	const char *pause = NULL; | ||||
| 	const char *restart = NULL; | ||||
| 	int skipms = PLAYBACK_SKIPMS; | ||||
| 	long offsetms = 0; | ||||
|  | ||||
| 	playback = data; | ||||
| 	ast_assert(playback != NULL); | ||||
|  | ||||
| 	playback_set_state(playback, STASIS_PLAYBACK_STATE_PLAYING); | ||||
|  | ||||
| 	if (ast_channel_state(chan) != AST_STATE_UP) { | ||||
| 		ast_answer(chan); | ||||
| 	} | ||||
|  | ||||
| 	if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) { | ||||
| 		/* Play sound */ | ||||
| 		file = playback->media + strlen(SOUND_URI_SCHEME); | ||||
| 	} else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) { | ||||
| 		/* Play recording */ | ||||
| 		file = playback->media + strlen(RECORDING_URI_SCHEME); | ||||
| 	} else { | ||||
| 		/* Play URL */ | ||||
| 		ast_log(LOG_ERROR, "Unimplemented\n"); | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	res = ast_control_streamfile(chan, file, fwd, rev, stop, pause, | ||||
| 		restart, skipms, &offsetms); | ||||
|  | ||||
| 	if (res != 0) { | ||||
| 		ast_log(LOG_WARNING, "%s: Playback failed for %s", | ||||
| 			ast_channel_uniqueid(chan), playback->media); | ||||
| 	} | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| static void playback_dtor(void *obj) | ||||
| { | ||||
| 	struct stasis_app_playback *playback = obj; | ||||
|  | ||||
| 	ast_string_field_free_memory(playback); | ||||
| } | ||||
|  | ||||
| struct stasis_app_playback *stasis_app_control_play_uri( | ||||
| 	struct stasis_app_control *control, const char *uri, | ||||
| 	const char *language) | ||||
| { | ||||
| 	RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup); | ||||
| 	char id[AST_UUID_STR_LEN]; | ||||
|  | ||||
| 	ast_debug(3, "%s: Sending play(%s) command\n", | ||||
| 		stasis_app_control_get_channel_id(control), uri); | ||||
|  | ||||
| 	playback = ao2_alloc(sizeof(*playback), playback_dtor); | ||||
| 	if (!playback || ast_string_field_init(playback, 128) ){ | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	ast_uuid_generate_str(id, sizeof(id)); | ||||
| 	ast_string_field_set(playback, id, id); | ||||
| 	ast_string_field_set(playback, media, uri); | ||||
| 	ast_string_field_set(playback, language, language); | ||||
| 	playback->control = control; | ||||
| 	ao2_link(playbacks, playback); | ||||
|  | ||||
| 	playback_set_state(playback, STASIS_PLAYBACK_STATE_QUEUED); | ||||
|  | ||||
| 	ao2_ref(playback, +1); | ||||
| 	stasis_app_send_command_async( | ||||
| 		control, __app_control_play_uri, playback); | ||||
|  | ||||
|  | ||||
| 	ao2_ref(playback, +1); | ||||
| 	return playback; | ||||
| } | ||||
|  | ||||
| enum stasis_app_playback_state stasis_app_playback_get_state( | ||||
| 	struct stasis_app_playback *control) | ||||
| { | ||||
| 	SCOPED_AO2LOCK(lock, control); | ||||
| 	return control->state; | ||||
| } | ||||
|  | ||||
| const char *stasis_app_playback_get_id( | ||||
| 	struct stasis_app_playback *control) | ||||
| { | ||||
| 	/* id is immutable; no lock needed */ | ||||
| 	return control->id; | ||||
| } | ||||
|  | ||||
| struct ast_json *stasis_app_playback_find_by_id(const char *id) | ||||
| { | ||||
| 	RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup); | ||||
| 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); | ||||
|  | ||||
| 	playback = ao2_find(playbacks, id, OBJ_KEY); | ||||
| 	if (playback == NULL) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	json = playback_to_json(playback); | ||||
| 	return ast_json_ref(json); | ||||
| } | ||||
|  | ||||
| int stasis_app_playback_control(struct stasis_app_playback *playback, | ||||
| 	enum stasis_app_playback_media_control control) | ||||
| { | ||||
| 	SCOPED_AO2LOCK(lock, playback); | ||||
| 	ast_assert(0); /* TODO */ | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| static int load_module(void) | ||||
| { | ||||
| 	int r; | ||||
|  | ||||
| 	r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type); | ||||
| 	if (r != 0) { | ||||
| 		return AST_MODULE_LOAD_FAILURE; | ||||
| 	} | ||||
|  | ||||
| 	playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash, | ||||
| 		playback_cmp); | ||||
| 	if (!playbacks) { | ||||
| 		return AST_MODULE_LOAD_FAILURE; | ||||
| 	} | ||||
| 	return AST_MODULE_LOAD_SUCCESS; | ||||
| } | ||||
|  | ||||
| static int unload_module(void) | ||||
| { | ||||
| 	ao2_cleanup(playbacks); | ||||
| 	playbacks = NULL; | ||||
| 	STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, | ||||
| 	"Stasis application playback support", | ||||
| 	.load = load_module, | ||||
| 	.unload = unload_module, | ||||
| 	.nonoptreq = "res_stasis"); | ||||
							
								
								
									
										6
									
								
								res/res_stasis_playback.exports.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								res/res_stasis_playback.exports.in
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| { | ||||
| 	global: | ||||
| 		LINKER_SYMBOL_PREFIXstasis_app_*; | ||||
| 	local: | ||||
| 		*; | ||||
| }; | ||||
| @@ -56,7 +56,8 @@ struct stasis_app_control *control_create(struct ast_channel *channel) | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	control->command_queue = ao2_container_alloc_list(0, 0, NULL, NULL); | ||||
| 	control->command_queue = ao2_container_alloc_list( | ||||
| 		AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL); | ||||
|  | ||||
| 	control->channel = channel; | ||||
|  | ||||
| @@ -75,10 +76,8 @@ static struct stasis_app_command *exec_command( | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	ao2_lock(control); | ||||
| 	ao2_ref(command, +1); | ||||
| 	/* command_queue is a thread safe list; no lock needed */ | ||||
| 	ao2_link(control->command_queue, command); | ||||
| 	ao2_unlock(control); | ||||
|  | ||||
| 	ao2_ref(command, +1); | ||||
| 	return command; | ||||
| @@ -182,8 +181,6 @@ int control_dispatch_all(struct stasis_app_control *control, | ||||
| 	struct ao2_iterator i; | ||||
| 	void *obj; | ||||
|  | ||||
| 	SCOPED_AO2LOCK(lock, control); | ||||
|  | ||||
| 	ast_assert(control->channel == chan); | ||||
|  | ||||
| 	i = ao2_iterator_init(control->command_queue, AO2_ITERATOR_UNLINK); | ||||
|   | ||||
| @@ -24,6 +24,7 @@ | ||||
|  */ | ||||
|  | ||||
| /*** MODULEINFO | ||||
| 	<depend type="module">res_stasis_app_playback</depend> | ||||
| 	<support_level>core</support_level> | ||||
|  ***/ | ||||
|  | ||||
| @@ -31,10 +32,14 @@ | ||||
|  | ||||
| ASTERISK_FILE_VERSION(__FILE__, "$Revision$") | ||||
|  | ||||
| #include "asterisk/file.h" | ||||
| #include "asterisk/stasis_app.h" | ||||
| #include "asterisk/stasis_app_playback.h" | ||||
| #include "asterisk/stasis_channels.h" | ||||
| #include "resource_channels.h" | ||||
|  | ||||
| #include <limits.h> | ||||
|  | ||||
| /*! | ||||
|  * \brief Finds the control object for a channel, filling the response with an | ||||
|  * error, if appropriate. | ||||
| @@ -131,9 +136,53 @@ void stasis_http_unhold_channel(struct ast_variable *headers, struct ast_unhold_ | ||||
| { | ||||
| 	ast_log(LOG_ERROR, "TODO: stasis_http_unhold_channel\n"); | ||||
| } | ||||
| void stasis_http_play_on_channel(struct ast_variable *headers, struct ast_play_on_channel_args *args, struct stasis_http_response *response) | ||||
|  | ||||
| void stasis_http_play_on_channel(struct ast_variable *headers, | ||||
| 	struct ast_play_on_channel_args *args, | ||||
| 	struct stasis_http_response *response) | ||||
| { | ||||
| 	ast_log(LOG_ERROR, "TODO: stasis_http_play_on_channel\n"); | ||||
| 	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); | ||||
| 	const char *language; | ||||
|  | ||||
| 	ast_assert(response != NULL); | ||||
|  | ||||
| 	control = find_control(response, args->channel_id); | ||||
| 	if (control == NULL) { | ||||
| 		/* Response filled in by find_control */ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	snapshot = stasis_app_control_get_snapshot(control); | ||||
| 	if (!snapshot) { | ||||
| 		stasis_http_response_error( | ||||
| 			response, 404, "Not Found", | ||||
| 			"Channel not found"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	language = S_OR(args->lang, snapshot->language); | ||||
|  | ||||
| 	playback = stasis_app_control_play_uri(control, args->media, language); | ||||
| 	if (!playback) { | ||||
| 		stasis_http_response_error( | ||||
| 			response, 500, "Internal Server Error", | ||||
| 			"Failed to answer channel"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	ast_asprintf(&playback_url, "/playback/%s", | ||||
| 		stasis_app_playback_get_id(playback)); | ||||
| 	if (!playback_url) { | ||||
| 		stasis_http_response_error( | ||||
| 			response, 500, "Internal Server Error", | ||||
| 			"Out of memory"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	stasis_http_response_created(response, playback_url); | ||||
| } | ||||
| void stasis_http_record_channel(struct ast_variable *headers, struct ast_record_channel_args *args, struct stasis_http_response *response) | ||||
| { | ||||
| @@ -143,8 +192,8 @@ void stasis_http_get_channel(struct ast_variable *headers, | ||||
| 			     struct ast_get_channel_args *args, | ||||
| 			     struct stasis_http_response *response) | ||||
| { | ||||
| 	RAII_VAR(struct stasis_caching_topic *, caching_topic, NULL, ao2_cleanup); | ||||
| 	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); | ||||
| 	struct stasis_caching_topic *caching_topic; | ||||
| 	struct ast_channel_snapshot *snapshot; | ||||
|  | ||||
| 	caching_topic = ast_channel_topic_all_cached(); | ||||
| @@ -154,7 +203,6 @@ void stasis_http_get_channel(struct ast_variable *headers, | ||||
| 			"Message bus not initialized"); | ||||
| 		return; | ||||
| 	} | ||||
| 	ao2_ref(caching_topic, +1); | ||||
|  | ||||
| 	msg = stasis_cache_get(caching_topic, ast_channel_snapshot_type(), | ||||
| 			       args->channel_id); | ||||
|   | ||||
| @@ -200,6 +200,8 @@ struct ast_play_on_channel_args { | ||||
| 	const char *channel_id; | ||||
| 	/*! \brief Media's URI to play. */ | ||||
| 	const char *media; | ||||
| 	/*! \brief For sounds, selects language for sound */ | ||||
| 	const char *lang; | ||||
| }; | ||||
| /*! | ||||
|  * \brief Start playback of media. | ||||
|   | ||||
| @@ -27,11 +27,22 @@ | ||||
|  | ||||
| ASTERISK_FILE_VERSION(__FILE__, "$Revision$") | ||||
|  | ||||
| #include "asterisk/stasis_app_playback.h" | ||||
| #include "resource_playback.h" | ||||
|  | ||||
| void stasis_http_get_playback(struct ast_variable *headers, struct ast_get_playback_args *args, struct stasis_http_response *response) | ||||
| void stasis_http_get_playback(struct ast_variable *headers, | ||||
| 	struct ast_get_playback_args *args, | ||||
| 	struct stasis_http_response *response) | ||||
| { | ||||
| 	ast_log(LOG_ERROR, "TODO: stasis_http_get_playback\n"); | ||||
| 	RAII_VAR(struct ast_json *, playback, NULL, ast_json_unref); | ||||
| 	playback = stasis_app_playback_find_by_id(args->playback_id); | ||||
| 	if (playback == NULL) { | ||||
| 		stasis_http_response_error(response, 404, "Not Found", | ||||
| 			"Playback not found"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	stasis_http_response_ok(response, ast_json_ref(playback)); | ||||
| } | ||||
| void stasis_http_stop_playback(struct ast_variable *headers, struct ast_stop_playback_args *args, struct stasis_http_response *response) | ||||
| { | ||||
|   | ||||
| @@ -40,7 +40,17 @@ | ||||
| /* | ||||
|  * JSON models | ||||
|  * | ||||
|  * CallerID | ||||
|  * - name: string (required) | ||||
|  * - number: string (required) | ||||
|  * Dialed | ||||
|  * Originated | ||||
|  * Playback | ||||
|  * - language: string | ||||
|  * - media_uri: string (required) | ||||
|  * - id: string (required) | ||||
|  * - target_uri: string (required) | ||||
|  * - state: string (required) | ||||
|  * DialplanCEP | ||||
|  * - priority: long (required) | ||||
|  * - exten: string (required) | ||||
| @@ -61,10 +71,6 @@ | ||||
|  * - hangupsource: string (required) | ||||
|  * - dialplan: DialplanCEP (required) | ||||
|  * - data: string (required) | ||||
|  * CallerID | ||||
|  * - name: string (required) | ||||
|  * - number: string (required) | ||||
|  * Dialed | ||||
|  */ | ||||
|  | ||||
| #endif /* _ASTERISK_RESOURCE_CHANNELS_H */ | ||||
|   | ||||
| @@ -68,18 +68,15 @@ struct ast_json *stasis_json_event_bridge_created_create( | ||||
| 	); | ||||
|  | ||||
| /*! | ||||
|  * \brief Notification that a channel has been destroyed. | ||||
|  * \brief Event showing the completion of a media playback operation. | ||||
|  * | ||||
|  * \param channel The channel to be used to generate this event | ||||
|  * \param blob JSON blob containing the following parameters: | ||||
|  * - cause: integer - Integer representation of the cause of the hangup (required) | ||||
|  * - cause_txt: string - Text representation of the cause of the hangup (required) | ||||
|  * - playback: Playback - Playback control object (required) | ||||
|  * | ||||
|  * \retval NULL on error | ||||
|  * \retval JSON (ast_json) describing the event | ||||
|  */ | ||||
| struct ast_json *stasis_json_event_channel_destroyed_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| struct ast_json *stasis_json_event_playback_finished_create( | ||||
| 	struct ast_json *blob | ||||
| 	); | ||||
|  | ||||
| @@ -112,18 +109,15 @@ struct ast_json *stasis_json_event_channel_caller_id_create( | ||||
| 	); | ||||
|  | ||||
| /*! | ||||
|  * \brief A hangup was requested on the channel. | ||||
|  * \brief Event showing the start of a media playback operation. | ||||
|  * | ||||
|  * \param channel The channel on which the hangup was requested. | ||||
|  * \param blob JSON blob containing the following parameters: | ||||
|  * - soft: boolean - Whether the hangup request was a soft hangup request. | ||||
|  * - cause: integer - Integer representation of the cause of the hangup. | ||||
|  * - playback: Playback - Playback control object (required) | ||||
|  * | ||||
|  * \retval NULL on error | ||||
|  * \retval JSON (ast_json) describing the event | ||||
|  */ | ||||
| struct ast_json *stasis_json_event_channel_hangup_request_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| struct ast_json *stasis_json_event_playback_started_create( | ||||
| 	struct ast_json *blob | ||||
| 	); | ||||
|  | ||||
| @@ -152,6 +146,22 @@ struct ast_json *stasis_json_event_application_replaced_create( | ||||
| 	struct ast_json *blob | ||||
| 	); | ||||
|  | ||||
| /*! | ||||
|  * \brief Notification that a channel has been destroyed. | ||||
|  * | ||||
|  * \param channel The channel to be used to generate this event | ||||
|  * \param blob JSON blob containing the following parameters: | ||||
|  * - cause: integer - Integer representation of the cause of the hangup (required) | ||||
|  * - cause_txt: string - Text representation of the cause of the hangup (required) | ||||
|  * | ||||
|  * \retval NULL on error | ||||
|  * \retval JSON (ast_json) describing the event | ||||
|  */ | ||||
| struct ast_json *stasis_json_event_channel_destroyed_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| 	struct ast_json *blob | ||||
| 	); | ||||
|  | ||||
| /*! | ||||
|  * \brief Channel variable changed. | ||||
|  * | ||||
| @@ -237,6 +247,22 @@ struct ast_json *stasis_json_event_channel_state_change_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot | ||||
| 	); | ||||
|  | ||||
| /*! | ||||
|  * \brief A hangup was requested on the channel. | ||||
|  * | ||||
|  * \param channel The channel on which the hangup was requested. | ||||
|  * \param blob JSON blob containing the following parameters: | ||||
|  * - soft: boolean - Whether the hangup request was a soft hangup request. | ||||
|  * - cause: integer - Integer representation of the cause of the hangup. | ||||
|  * | ||||
|  * \retval NULL on error | ||||
|  * \retval JSON (ast_json) describing the event | ||||
|  */ | ||||
| struct ast_json *stasis_json_event_channel_hangup_request_create( | ||||
| 	struct ast_channel_snapshot *channel_snapshot, | ||||
| 	struct ast_json *blob | ||||
| 	); | ||||
|  | ||||
| /*! | ||||
|  * \brief Notification that a channel has entered a bridge. | ||||
|  * | ||||
| @@ -284,19 +310,20 @@ struct ast_json *stasis_json_event_stasis_end_create( | ||||
|  * ChannelUserevent | ||||
|  * - eventname: string (required) | ||||
|  * BridgeCreated | ||||
|  * ChannelDestroyed | ||||
|  * - cause: integer (required) | ||||
|  * - cause_txt: string (required) | ||||
|  * PlaybackFinished | ||||
|  * - playback: Playback (required) | ||||
|  * ChannelSnapshot | ||||
|  * ChannelCallerId | ||||
|  * - caller_presentation_txt: string (required) | ||||
|  * - caller_presentation: integer (required) | ||||
|  * ChannelHangupRequest | ||||
|  * - soft: boolean | ||||
|  * - cause: integer | ||||
|  * PlaybackStarted | ||||
|  * - playback: Playback (required) | ||||
|  * BridgeDestroyed | ||||
|  * ApplicationReplaced | ||||
|  * - application: string (required) | ||||
|  * ChannelDestroyed | ||||
|  * - cause: integer (required) | ||||
|  * - cause_txt: string (required) | ||||
|  * ChannelVarset | ||||
|  * - variable: string (required) | ||||
|  * - value: string (required) | ||||
| @@ -308,6 +335,9 @@ struct ast_json *stasis_json_event_stasis_end_create( | ||||
|  * - application: string (required) | ||||
|  * - application_data: string (required) | ||||
|  * ChannelStateChange | ||||
|  * ChannelHangupRequest | ||||
|  * - soft: boolean | ||||
|  * - cause: integer | ||||
|  * ChannelEnteredBridge | ||||
|  * ChannelDtmfReceived | ||||
|  * - digit: string (required) | ||||
| @@ -325,10 +355,12 @@ struct ast_json *stasis_json_event_stasis_end_create( | ||||
|  * - application: string (required) | ||||
|  * - channel_hangup_request: ChannelHangupRequest | ||||
|  * - channel_userevent: ChannelUserevent | ||||
|  * - playback_started: PlaybackStarted | ||||
|  * - channel_snapshot: ChannelSnapshot | ||||
|  * - channel_dtmf_received: ChannelDtmfReceived | ||||
|  * - channel_caller_id: ChannelCallerId | ||||
|  * - bridge_destroyed: BridgeDestroyed | ||||
|  * - playback_finished: PlaybackFinished | ||||
|  * - stasis_end: StasisEnd | ||||
|  * StasisEnd | ||||
|  */ | ||||
|   | ||||
| @@ -405,6 +405,14 @@ | ||||
| 							"required": true, | ||||
| 							"allowMultiple": false, | ||||
| 							"dataType": "string" | ||||
| 						}, | ||||
| 						{ | ||||
| 							"name": "lang", | ||||
| 							"description": "For sounds, selects language for sound", | ||||
| 							"paramType": "query", | ||||
| 							"required": false, | ||||
| 							"allowMultiple": false, | ||||
| 							"dataType": "string" | ||||
| 						} | ||||
| 					], | ||||
| 					"errorResponses": [ | ||||
| @@ -640,6 +648,44 @@ | ||||
| 					"description": "Timestamp when channel was created" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"Playback": { | ||||
| 			"id": "Playback", | ||||
| 			"description": "Object representing the playback of media to a channel", | ||||
| 			"properties": { | ||||
| 				"id": { | ||||
| 					"type": "string", | ||||
| 					"description": "ID for this playback operation", | ||||
| 					"required": true | ||||
| 				}, | ||||
| 				"media_uri": { | ||||
| 					"type": "string", | ||||
| 					"description": "URI for the media to play back.", | ||||
| 					"required": true | ||||
| 				}, | ||||
| 				"target_uri": { | ||||
| 					"type": "string", | ||||
| 					"description": "URI for the channel or bridge to play the media on", | ||||
| 					"required": true | ||||
| 				}, | ||||
| 				"language": { | ||||
| 					"type": "string", | ||||
| 					"description": "For media types that support multiple languages, the language requested for playback." | ||||
| 				}, | ||||
| 				"state": { | ||||
| 					"type": "string", | ||||
| 					"description": "Current state of the playback operation.", | ||||
| 					"required": true, | ||||
| 					"allowableValues": { | ||||
| 						"valueType": "LIST", | ||||
| 						"values": [ | ||||
| 							"queued", | ||||
| 							"playing", | ||||
| 							"complete" | ||||
| 						] | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -69,7 +69,31 @@ | ||||
| 				"channel_hangup_request": { "type": "ChannelHangupRequest" }, | ||||
| 				"channel_varset": { "type": "ChannelVarset" }, | ||||
| 				"stasis_end": { "type": "StasisEnd" }, | ||||
| 				"stasis_start": { "type": "StasisStart" } | ||||
| 				"stasis_start": { "type": "StasisStart" }, | ||||
| 				"playback_started": { "type": "PlaybackStarted" }, | ||||
| 				"playback_finished": { "type": "PlaybackFinished" } | ||||
| 			} | ||||
| 		}, | ||||
| 		"PlaybackStarted": { | ||||
| 			"id": "PlaybackStarted", | ||||
| 			"description": "Event showing the start of a media playback operation.", | ||||
| 			"properties": { | ||||
| 				"playback": { | ||||
| 					"type": "Playback", | ||||
| 					"description": "Playback control object", | ||||
| 					"required": true | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"PlaybackFinished": { | ||||
| 			"id": "PlaybackFinished", | ||||
| 			"description": "Event showing the completion of a media playback operation.", | ||||
| 			"properties": { | ||||
| 				"playback": { | ||||
| 					"type": "Playback", | ||||
| 					"description": "Playback control object", | ||||
| 					"required": true | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"ApplicationReplaced": { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user