This patch implements the REST API's for POST /channels/{channelId}/play
authorDavid M. Lee <dlee@digium.com>
Thu, 23 May 2013 20:11:35 +0000 (20:11 +0000)
committerDavid M. Lee <dlee@digium.com>
Thu, 23 May 2013 20:11:35 +0000 (20:11 +0000)
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

20 files changed:
include/asterisk/app.h
include/asterisk/stasis_app_playback.h [new file with mode: 0644]
include/asterisk/stasis_channels.h
include/asterisk/stasis_http.h
main/channel_internal_api.c
main/stasis_channels.c
res/res_stasis_http.c
res/res_stasis_http_channels.c
res/res_stasis_json_events.c
res/res_stasis_json_events.exports.in
res/res_stasis_playback.c [new file with mode: 0644]
res/res_stasis_playback.exports.in [new file with mode: 0644]
res/stasis/control.c
res/stasis_http/resource_channels.c
res/stasis_http/resource_channels.h
res/stasis_http/resource_playback.c
res/stasis_json/resource_channels.h
res/stasis_json/resource_events.h
rest-api/api-docs/channels.json
rest-api/api-docs/events.json

index 75d1fbb..2089bce 100644 (file)
@@ -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);
 
diff --git a/include/asterisk/stasis_app_playback.h b/include/asterisk/stasis_app_playback.h
new file mode 100644 (file)
index 0000000..598c6be
--- /dev/null
@@ -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 */
index 5ead93d..dace99a 100644 (file)
@@ -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 */
@@ -124,6 +125,17 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(
 
 /*!
  * \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.
  *
  * The given \a blob should be treated as immutable and not modified after it is
@@ -142,6 +154,23 @@ struct stasis_message *ast_channel_blob_create(struct ast_channel *chan,
 
 /*!
  * \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.
  *
  * The given \a blob should be treated as immutable and not modified after it is
@@ -223,6 +252,14 @@ void ast_multi_channel_blob_add_channel(struct ast_multi_channel_blob *obj,
 
 /*!
  * \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.
  *
  * \param chan Channel to pulish the event for, or \c NULL for 'none'.
index cc0ceee..f81a206 100644 (file)
@@ -163,6 +163,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.
  */
index 42aaada..87551f7 100644 (file)
@@ -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); \
-}
-
-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);
+       if (publish) ast_channel_publish_snapshot(chan); \
+}
+
+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) \
 { \
index 0b2ccbc..f8c9be3 100644 (file)
@@ -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);
index 75a1328..63e2601 100644 (file)
@@ -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)
 {
index aa44819..89c0cf0 100644 (file)
@@ -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) {
index 10d36be..e96d84e 100644 (file)
@@ -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
index e3f59ca..8be4c84 100644 (file)
@@ -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;
diff --git a/res/res_stasis_playback.c b/res/res_stasis_playback.c
new file mode 100644 (file)
index 0000000..5f54a14
--- /dev/null
@@ -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");
diff --git a/res/res_stasis_playback.exports.in b/res/res_stasis_playback.exports.in
new file mode 100644 (file)
index 0000000..0ad493c
--- /dev/null
@@ -0,0 +1,6 @@
+{
+       global:
+               LINKER_SYMBOL_PREFIXstasis_app_*;
+       local:
+               *;
+};
index e32781b..1cc8186 100644 (file)
@@ -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);
index 3cc97c5..4e404dc 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 /*** MODULEINFO
+       <depend type="module">res_stasis_app_playback</depend>
        <support_level>core</support_level>
  ***/
 
 
 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);
index 2c78589..8a35072 100644 (file)
@@ -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.
index 99f2e09..f016a00 100644 (file)
 
 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)
 {
index 45e1031..c561d30 100644 (file)
 /*
  * 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)
  * - hangupsource: string (required)
  * - dialplan: DialplanCEP (required)
  * - data: string (required)
- * CallerID
- * - name: string (required)
- * - number: string (required)
- * Dialed
  */
 
 #endif /* _ASTERISK_RESOURCE_CHANNELS_H */
index 63abe0f..d631817 100644 (file)
@@ -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
        );
 
@@ -153,6 +147,22 @@ struct ast_json *stasis_json_event_application_replaced_create(
        );
 
 /*!
+ * \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.
  *
  * \param channel The channel on which the variable was set.
@@ -238,6 +248,22 @@ struct ast_json *stasis_json_event_channel_state_change_create(
        );
 
 /*!
+ * \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.
  *
  * \param channel The channel to be used to generate this event
@@ -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
  */
index c2d77b2..3b4d4d4 100644 (file)
                                                        "required": true,
                                                        "allowMultiple": false,
                                                        "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "lang",
+                                                       "description": "For sounds, selects language for sound",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
                                                }
                                        ],
                                        "errorResponses": [
                                        "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"
+                                               ]
+                                       }
+                               }
+                       }
                }
        }
 }
index 4a36da3..0e0a822 100644 (file)
                                "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": {