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:
parent
3464e0919a
commit
10ba6bf8a8
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
|
@ -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": {
|
||||
|
|
Loading…
Reference in New Issue