ARI: Bridge Playback, Bridge Record
authorJonathan Rose <jrose@digium.com>
Fri, 19 Jul 2013 19:35:21 +0000 (19:35 +0000)
committerJonathan Rose <jrose@digium.com>
Fri, 19 Jul 2013 19:35:21 +0000 (19:35 +0000)
Adds a new channel driver for creating channels for specific purposes
in bridges, primarily to act as either recorders or announcers. Adds
ARI commands for playing announcements to ever participant in a bridge
as well as for recording a bridge. This patch also includes some
documentation/reponse fixes to related ARI models such as playback
controls.

(closes issue ASTERISK-21592)
Reported by: Matt Jordan

(closes issue ASTERISK-21593)
Reported by: Matt Jordan

Review: https://reviewboard.asterisk.org/r/2670/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@394809 65c4cc65-6c06-0410-ace0-fbb531ad65f3

21 files changed:
channels/chan_bridge_media.c [new file with mode: 0644]
include/asterisk/core_unreal.h
include/asterisk/logger.h
include/asterisk/stasis_app.h
include/asterisk/stasis_app_playback.h
main/core_unreal.c
res/res_stasis.c
res/res_stasis_http_bridges.c
res/res_stasis_http_channels.c
res/res_stasis_http_playback.c
res/res_stasis_playback.c
res/stasis/control.c
res/stasis_http/ari_model_validators.c
res/stasis_http/ari_model_validators.h
res/stasis_http/resource_bridges.c
res/stasis_http/resource_bridges.h
res/stasis_http/resource_channels.c
rest-api/api-docs/bridges.json
rest-api/api-docs/channels.json
rest-api/api-docs/playback.json
rest-api/api-docs/recordings.json

diff --git a/channels/chan_bridge_media.c b/channels/chan_bridge_media.c
new file mode 100644 (file)
index 0000000..d242012
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Bridge Media Channels driver
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * \brief Bridge Media Channels
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+#include "asterisk/module.h"
+
+static int media_call(struct ast_channel *chan, const char *addr, int timeout)
+{
+       /* ast_call() will fail unconditionally against channels provided by this driver */
+       return -1;
+}
+
+static int media_hangup(struct ast_channel *ast)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int res;
+
+       if (!p) {
+               return -1;
+       }
+
+       /* Give the pvt a ref to fulfill calling requirements. */
+       ao2_ref(p, +1);
+       res = ast_unreal_hangup(p, ast);
+       ao2_ref(p, -1);
+
+       return res;
+}
+
+static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap,
+       const struct ast_channel *requestor, const char *data, int *cause);
+
+static struct ast_channel *record_request(const char *type, struct ast_format_cap *cap,
+       const struct ast_channel *requestor, const char *data, int *cause);
+
+static struct ast_channel_tech announce_tech = {
+       .type = "Announcer",
+       .description = "Bridge Media Announcing Channel Driver",
+       .requester = announce_request,
+       .call = media_call,
+       .hangup = media_hangup,
+
+       .send_digit_begin = ast_unreal_digit_begin,
+       .send_digit_end = ast_unreal_digit_end,
+       .read = ast_unreal_read,
+       .write = ast_unreal_write,
+       .write_video = ast_unreal_write,
+       .exception = ast_unreal_read,
+       .indicate = ast_unreal_indicate,
+       .fixup = ast_unreal_fixup,
+       .send_html = ast_unreal_sendhtml,
+       .send_text = ast_unreal_sendtext,
+       .queryoption = ast_unreal_queryoption,
+       .setoption = ast_unreal_setoption,
+       .properties = AST_CHAN_TP_ANNOUNCER,
+};
+
+static struct ast_channel_tech record_tech = {
+       .type = "Recorder",
+       .description = "Bridge Media Recording Channel Driver",
+       .requester = record_request,
+       .call = media_call,
+       .hangup = media_hangup,
+
+       .send_digit_begin = ast_unreal_digit_begin,
+       .send_digit_end = ast_unreal_digit_end,
+       .read = ast_unreal_read,
+       .write = ast_unreal_write,
+       .write_video = ast_unreal_write,
+       .exception = ast_unreal_read,
+       .indicate = ast_unreal_indicate,
+       .fixup = ast_unreal_fixup,
+       .send_html = ast_unreal_sendhtml,
+       .send_text = ast_unreal_sendtext,
+       .queryoption = ast_unreal_queryoption,
+       .setoption = ast_unreal_setoption,
+       .properties = AST_CHAN_TP_RECORDER,
+};
+
+static struct ast_channel *media_request_helper(struct ast_format_cap *cap,
+       const struct ast_channel *requestor, const char *data, struct ast_channel_tech *tech, const char *role)
+{
+       struct ast_channel *chan;
+
+       RAII_VAR(struct ast_callid *, callid, NULL, ast_callid_cleanup);
+       RAII_VAR(struct ast_unreal_pvt *, pvt, NULL, ao2_cleanup);
+
+       if (!(pvt = ast_unreal_alloc(sizeof(*pvt), ast_unreal_destructor, cap))) {
+               return NULL;
+       }
+
+       ast_copy_string(pvt->name, data, sizeof(pvt->name));
+
+       ast_set_flag(pvt, AST_UNREAL_NO_OPTIMIZATION);
+
+       callid = ast_read_threadstorage_callid();
+
+       chan = ast_unreal_new_channels(pvt, tech,
+               AST_STATE_UP, AST_STATE_UP, NULL, NULL, requestor, callid);
+       if (!chan) {
+               return NULL;
+       }
+
+       ast_answer(pvt->owner);
+       ast_answer(pvt->chan);
+
+       if (ast_channel_add_bridge_role(pvt->chan, role)) {
+               ast_hangup(chan);
+               return NULL;
+       }
+
+       return chan;
+}
+
+static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap,
+       const struct ast_channel *requestor, const char *data, int *cause)
+{
+       return media_request_helper(cap, requestor, data, &announce_tech, "announcer");
+}
+
+static struct ast_channel *record_request(const char *type, struct ast_format_cap *cap,
+       const struct ast_channel *requestor, const char *data, int *cause)
+{
+       return media_request_helper(cap, requestor, data, &record_tech, "recorder");
+}
+
+static void cleanup_capabilities(void)
+{
+       if (announce_tech.capabilities) {
+               announce_tech.capabilities = ast_format_cap_destroy(announce_tech.capabilities);
+       }
+
+       if (record_tech.capabilities) {
+               record_tech.capabilities = ast_format_cap_destroy(record_tech.capabilities);
+       }
+}
+
+static int unload_module(void)
+{
+       ast_channel_unregister(&announce_tech);
+       ast_channel_unregister(&record_tech);
+       cleanup_capabilities();
+       return 0;
+}
+
+static int load_module(void)
+{
+       announce_tech.capabilities = ast_format_cap_alloc();
+       if (!announce_tech.capabilities) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       record_tech.capabilities = ast_format_cap_alloc();
+       if (!record_tech.capabilities) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       ast_format_cap_add_all(announce_tech.capabilities);
+       ast_format_cap_add_all(record_tech.capabilities);
+
+       if (ast_channel_register(&announce_tech)) {
+               ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n",
+                       announce_tech.type, announce_tech.description);
+               cleanup_capabilities();
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       if (ast_channel_register(&record_tech)) {
+               ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n",
+                       record_tech.type, record_tech.description);
+               cleanup_capabilities();
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bridge Media Channel Driver",
+    .load = load_module,
+    .unload = unload_module,
+);
index a6e9889..751c5d4 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "asterisk/astobj2.h"
 #include "asterisk/channel.h"
 
 #include "asterisk/astobj2.h"
 #include "asterisk/channel.h"
+#include "asterisk/bridging.h"
 #include "asterisk/abstract_jb.h"
 
 #if defined(__cplusplus) || defined(c_plusplus)
 #include "asterisk/abstract_jb.h"
 
 #if defined(__cplusplus) || defined(c_plusplus)
@@ -208,6 +209,20 @@ struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
  */
 void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2);
 
  */
 void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2);
 
+/*!
+ * \brief Push the semi2 unreal channel into a bridge from either member of the unreal pair
+ * \since 12.0.0
+ *
+ * \param ast A member of the unreal channel being pushed
+ * \param bridge Which bridge we want to push the channel to
+ *
+ * \retval 0 if the channel is successfully imparted onto the bridge
+ * \retval -1 on failure
+ *
+ * \note This is equivalent to ast_call() on unreal based channel drivers that are designed to use it instead.
+ */
+int ast_unreal_channel_push_to_bridge(struct ast_channel *ast, struct ast_bridge *bridge);
+
 /* ------------------------------------------------------------------- */
 
 #if defined(__cplusplus) || defined(c_plusplus)
 /* ------------------------------------------------------------------- */
 
 #if defined(__cplusplus) || defined(c_plusplus)
index 09d2b4c..bbad84b 100644 (file)
@@ -281,7 +281,16 @@ struct ast_callid *ast_read_threadstorage_callid(void);
  *
  * \retval NULL always
  */
  *
  * \retval NULL always
  */
-#define ast_callid_unref(c) ({ ao2_ref(c, -1); (NULL); })
+#define ast_callid_unref(c) ({ ao2_ref(c, -1); (struct ast_callid *) (NULL); })
+
+/*!
+ * \brief Cleanup a callid reference (NULL safe ao2 unreference)
+ *
+ * \param c the ast_callid
+ *
+ * \retval NULL always
+ */
+#define ast_callid_cleanup(c) ({ ao2_cleanup(c); (struct ast_callid *) (NULL); })
 
 /*!
  * \brief Sets what is stored in the thread storage to the given
 
 /*!
  * \brief Sets what is stored in the thread storage to the given
index 7311336..244d9d1 100644 (file)
@@ -127,6 +127,29 @@ struct stasis_app_control *stasis_app_control_find_by_channel_id(
        const char *channel_id);
 
 /*!
        const char *channel_id);
 
 /*!
+ * \brief Creates a control handler for a channel that isn't in a stasis app.
+ * \since 12.0.0
+ *
+ * \param chan Channel to create controller handle for
+ *
+ * \return NULL on failure to create the handle
+ * \return Pointer to \c res_stasis handler.
+ */
+struct stasis_app_control *stasis_app_control_create(
+       struct ast_channel *chan);
+
+/*!
+ * \brief Act on a stasis app control queue until it is empty
+ * \since 12.0.0
+ *
+ * \param chan Channel to handle
+ * \param control Control object to execute
+ */
+void stasis_app_control_execute_until_exhausted(
+       struct ast_channel *chan,
+       struct stasis_app_control *control);
+
+/*!
  * \brief Returns the uniqueid of the channel associated with this control
  *
  * \param control Control object.
  * \brief Returns the uniqueid of the channel associated with this control
  *
  * \param control Control object.
index 59c2aab..3587871 100644 (file)
@@ -69,6 +69,13 @@ enum stasis_app_playback_media_operation {
        STASIS_PLAYBACK_MEDIA_OP_MAX,
 };
 
        STASIS_PLAYBACK_MEDIA_OP_MAX,
 };
 
+enum stasis_app_playback_target_type {
+       /*! The target is a channel */
+       STASIS_PLAYBACK_TARGET_CHANNEL = 0,
+       /*! The target is a bridge */
+       STASIS_PLAYBACK_TARGET_BRIDGE,
+};
+
 /*!
  * \brief Play a file to the control's channel.
  *
 /*!
  * \brief Play a file to the control's channel.
  *
@@ -79,6 +86,8 @@ enum stasis_app_playback_media_operation {
  * \param control Control for \c res_stasis.
  * \param file Base filename for the file to play.
  * \param language Selects the file based on language.
  * \param control Control for \c res_stasis.
  * \param file Base filename for the file to play.
  * \param language Selects the file based on language.
+ * \param target_id ID of the target bridge or channel.
+ * \param target_type What the target type is
  * \param skipms Number of milliseconds to skip for forward/reverse operations.
  * \param offsetms Number of milliseconds to skip before playing.
  * \return Playback control object.
  * \param skipms Number of milliseconds to skip for forward/reverse operations.
  * \param offsetms Number of milliseconds to skip before playing.
  * \return Playback control object.
@@ -86,7 +95,9 @@ enum stasis_app_playback_media_operation {
  */
 struct stasis_app_playback *stasis_app_control_play_uri(
        struct stasis_app_control *control, const char *file,
  */
 struct stasis_app_playback *stasis_app_control_play_uri(
        struct stasis_app_control *control, const char *file,
-       const char *language, int skipms, long offsetms);
+       const char *language, const char *target_id,
+       enum stasis_app_playback_target_type target_type,
+       int skipms, long offsetms);
 
 /*!
  * \brief Gets the current state of a playback operation.
 
 /*!
  * \brief Gets the current state of a playback operation.
index 71d0f6c..3d14a71 100644 (file)
@@ -668,6 +668,96 @@ void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2)
        ast_channel_datastore_inherit(semi1, semi2);
 }
 
        ast_channel_datastore_inherit(semi1, semi2);
 }
 
+int ast_unreal_channel_push_to_bridge(struct ast_channel *ast, struct ast_bridge *bridge)
+{
+       struct ast_bridge_features *features;
+       struct ast_channel *chan;
+       struct ast_channel *owner;
+       RAII_VAR(struct ast_unreal_pvt *, p, NULL, ao2_cleanup);
+
+       RAII_VAR(struct ast_callid *, bridge_callid, NULL, ast_callid_cleanup);
+
+       ast_bridge_lock(bridge);
+       bridge_callid = bridge->callid ? ast_callid_ref(bridge->callid) : NULL;
+       ast_bridge_unlock(bridge);
+
+       {
+               SCOPED_CHANNELLOCK(lock, ast);
+               p = ast_channel_tech_pvt(ast);
+               if (!p) {
+                       return -1;
+               }
+               ao2_ref(p, +1);
+       }
+
+       {
+               SCOPED_AO2LOCK(lock, p);
+               chan = p->chan;
+               if (!chan) {
+                       return -1;
+               }
+
+               owner = p->owner;
+               if (!owner) {
+                       return -1;
+               }
+
+               ast_channel_ref(chan);
+               ast_channel_ref(owner);
+       }
+
+       if (bridge_callid) {
+               struct ast_callid *chan_callid;
+               struct ast_callid *owner_callid;
+
+               /* chan side call ID setting */
+               ast_channel_lock(chan);
+
+               chan_callid = ast_channel_callid(chan);
+               if (!chan_callid) {
+                       ast_channel_callid_set(chan, bridge_callid);
+               }
+               ast_channel_unlock(chan);
+               ast_callid_cleanup(chan_callid);
+
+               /* owner side call ID setting */
+               ast_channel_lock(owner);
+
+               owner_callid = ast_channel_callid(owner);
+               if (!owner_callid) {
+                       ast_channel_callid_set(owner, bridge_callid);
+               }
+
+               ast_channel_unlock(owner);
+               ast_callid_cleanup(owner_callid);
+       }
+
+       /* We are done with the owner now that its call ID matches the bridge */
+       ast_channel_unref(owner);
+       owner = NULL;
+
+       features = ast_bridge_features_new();
+       if (!features) {
+               ast_channel_unref(chan);
+               return -1;
+       }
+       ast_set_flag(&features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE);
+
+       /* Impart the semi2 channel into the bridge */
+       if (ast_bridge_impart(bridge, chan, NULL, features, 1)) {
+               ast_bridge_features_destroy(features);
+               ast_channel_unref(chan);
+               return -1;
+       }
+
+       ao2_lock(p);
+       ast_set_flag(p, AST_UNREAL_CARETAKER_THREAD);
+       ao2_unlock(p);
+       ast_channel_unref(chan);
+
+       return 0;
+}
+
 int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast)
 {
        int hangup_chan = 0;
 int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast)
 {
        int hangup_chan = 0;
index ed38230..a294db9 100644 (file)
@@ -146,6 +146,11 @@ static int control_compare(void *lhs, void *rhs, int flags)
        }
 }
 
        }
 }
 
+struct stasis_app_control *stasis_app_control_create(struct ast_channel *chan)
+{
+       return control_create(chan);
+}
+
 struct stasis_app_control *stasis_app_control_find_by_channel(
        const struct ast_channel *chan)
 {
 struct stasis_app_control *stasis_app_control_find_by_channel(
        const struct ast_channel *chan)
 {
@@ -531,6 +536,16 @@ int app_send_end_msg(struct app *app, struct ast_channel *chan)
        return 0;
 }
 
        return 0;
 }
 
+void stasis_app_control_execute_until_exhausted(struct ast_channel *chan, struct stasis_app_control *control)
+{
+       while (!control_is_done(control)) {
+               int command_count = control_dispatch_all(control, chan);
+               if (command_count == 0 || ast_channel_fdno(chan) == -1) {
+                       break;
+               }
+       }
+}
+
 /*! /brief Stasis dialplan application callback */
 int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
                    char *argv[])
 /*! /brief Stasis dialplan application callback */
 int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
                    char *argv[])
@@ -750,7 +765,7 @@ static struct ast_json *simple_bridge_channel_event(
        struct ast_channel_snapshot *channel_snapshot,
        const struct timeval *tv)
 {
        struct ast_channel_snapshot *channel_snapshot,
        const struct timeval *tv)
 {
-       return ast_json_pack("{s: s, s: o, s: o}",
+       return ast_json_pack("{s: s, s: o, s: o, s: o}",
                "type", type,
                "timestamp", ast_json_timeval(*tv, NULL),
                "bridge", ast_bridge_snapshot_to_json(bridge_snapshot),
                "type", type,
                "timestamp", ast_json_timeval(*tv, NULL),
                "bridge", ast_bridge_snapshot_to_json(bridge_snapshot),
index f46b7ac..86093ab 100644 (file)
@@ -358,6 +358,73 @@ static void stasis_http_remove_channel_from_bridge_cb(
 #endif /* AST_DEVMODE */
 }
 /*!
 #endif /* AST_DEVMODE */
 }
 /*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/play.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_play_on_bridge_cb(
+       struct ast_variable *get_params, struct ast_variable *path_vars,
+       struct ast_variable *headers, struct stasis_http_response *response)
+{
+#if defined(AST_DEVMODE)
+       int is_valid;
+       int code;
+#endif /* AST_DEVMODE */
+
+       struct ast_play_on_bridge_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "media") == 0) {
+                       args.media = (i->value);
+               } else
+               if (strcmp(i->name, "lang") == 0) {
+                       args.lang = (i->value);
+               } else
+               if (strcmp(i->name, "offsetms") == 0) {
+                       args.offsetms = atoi(i->value);
+               } else
+               if (strcmp(i->name, "skipms") == 0) {
+                       args.skipms = atoi(i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_play_on_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+       code = response->response_code;
+
+       switch (code) {
+       case 500: /* Internal server error */
+       case 404: /* Bridge not found */
+       case 409: /* Bridge not in a Stasis application */
+               is_valid = 1;
+               break;
+       default:
+               if (200 <= code && code <= 299) {
+                       is_valid = ari_validate_playback(
+                               response->message);
+               } else {
+                       ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/play\n", code);
+                       is_valid = 0;
+               }
+       }
+
+       if (!is_valid) {
+               ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/play\n");
+               stasis_http_response_error(response, 500,
+                       "Internal Server Error", "Response validation failed");
+       }
+#endif /* AST_DEVMODE */
+}
+/*!
  * \brief Parameter parsing callback for /bridges/{bridgeId}/record.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \brief Parameter parsing callback for /bridges/{bridgeId}/record.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
@@ -380,14 +447,17 @@ static void stasis_http_record_bridge_cb(
                if (strcmp(i->name, "name") == 0) {
                        args.name = (i->value);
                } else
                if (strcmp(i->name, "name") == 0) {
                        args.name = (i->value);
                } else
+               if (strcmp(i->name, "format") == 0) {
+                       args.format = (i->value);
+               } else
                if (strcmp(i->name, "maxDurationSeconds") == 0) {
                        args.max_duration_seconds = atoi(i->value);
                } else
                if (strcmp(i->name, "maxSilenceSeconds") == 0) {
                        args.max_silence_seconds = atoi(i->value);
                } else
                if (strcmp(i->name, "maxDurationSeconds") == 0) {
                        args.max_duration_seconds = atoi(i->value);
                } else
                if (strcmp(i->name, "maxSilenceSeconds") == 0) {
                        args.max_silence_seconds = atoi(i->value);
                } else
-               if (strcmp(i->name, "append") == 0) {
-                       args.append = ast_true(i->value);
+               if (strcmp(i->name, "ifExists") == 0) {
+                       args.if_exists = (i->value);
                } else
                if (strcmp(i->name, "beep") == 0) {
                        args.beep = ast_true(i->value);
                } else
                if (strcmp(i->name, "beep") == 0) {
                        args.beep = ast_true(i->value);
@@ -448,6 +518,15 @@ static struct stasis_rest_handlers bridges_bridgeId_removeChannel = {
        .children = {  }
 };
 /*! \brief REST handler for /api-docs/bridges.{format} */
        .children = {  }
 };
 /*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId_play = {
+       .path_segment = "play",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_play_on_bridge_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/bridges.{format} */
 static struct stasis_rest_handlers bridges_bridgeId_record = {
        .path_segment = "record",
        .callbacks = {
 static struct stasis_rest_handlers bridges_bridgeId_record = {
        .path_segment = "record",
        .callbacks = {
@@ -464,8 +543,8 @@ static struct stasis_rest_handlers bridges_bridgeId = {
                [AST_HTTP_GET] = stasis_http_get_bridge_cb,
                [AST_HTTP_DELETE] = stasis_http_delete_bridge_cb,
        },
                [AST_HTTP_GET] = stasis_http_get_bridge_cb,
                [AST_HTTP_DELETE] = stasis_http_delete_bridge_cb,
        },
-       .num_children = 3,
-       .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_record, }
+       .num_children = 4,
+       .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_play,&bridges_bridgeId_record, }
 };
 /*! \brief REST handler for /api-docs/bridges.{format} */
 static struct stasis_rest_handlers bridges = {
 };
 /*! \brief REST handler for /api-docs/bridges.{format} */
 static struct stasis_rest_handlers bridges = {
index 050ef00..806c370 100644 (file)
@@ -796,7 +796,7 @@ static void stasis_http_record_channel_cb(
                break;
        default:
                if (200 <= code && code <= 299) {
                break;
        default:
                if (200 <= code && code <= 299) {
-                       is_valid = ari_validate_void(
+                       is_valid = ari_validate_live_recording(
                                response->message);
                } else {
                        ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/record\n", code);
                                response->message);
                } else {
                        ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/record\n", code);
index 0e56e62..9b6ab59 100644 (file)
@@ -192,7 +192,7 @@ static void stasis_http_control_playback_cb(
                break;
        default:
                if (200 <= code && code <= 299) {
                break;
        default:
                if (200 <= code && code <= 299) {
-                       is_valid = ari_validate_playback(
+                       is_valid = ari_validate_void(
                                response->message);
                } else {
                        ast_log(LOG_ERROR, "Invalid error response %d for /playback/{playbackId}/control\n", code);
                                response->message);
                } else {
                        ast_log(LOG_ERROR, "Invalid error response %d for /playback/{playbackId}/control\n", code);
index 5b55ebc..483aff8 100644 (file)
@@ -64,6 +64,7 @@ struct stasis_app_playback {
                AST_STRING_FIELD(id);   /*!< Playback unique id */
                AST_STRING_FIELD(media);        /*!< Playback media uri */
                AST_STRING_FIELD(language);     /*!< Preferred language */
                AST_STRING_FIELD(id);   /*!< Playback unique id */
                AST_STRING_FIELD(media);        /*!< Playback media uri */
                AST_STRING_FIELD(language);     /*!< Preferred language */
+               AST_STRING_FIELD(target);       /*!< Playback device uri */
                );
        /*! Control object for the channel we're playing back to */
        struct stasis_app_control *control;
                );
        /*! Control object for the channel we're playing back to */
        struct stasis_app_control *control;
@@ -263,9 +264,31 @@ static void playback_dtor(void *obj)
        ast_string_field_free_memory(playback);
 }
 
        ast_string_field_free_memory(playback);
 }
 
+static void set_target_uri(
+       struct stasis_app_playback *playback,
+       enum stasis_app_playback_target_type target_type,
+       const char *target_id)
+{
+       const char *type = NULL;
+       switch (target_type) {
+       case STASIS_PLAYBACK_TARGET_CHANNEL:
+               type = "channel";
+               break;
+       case STASIS_PLAYBACK_TARGET_BRIDGE:
+               type = "bridge";
+               break;
+       }
+
+       ast_assert(type != NULL);
+
+       ast_string_field_build(playback, target, "%s:%s", type, target_id);
+}
+
 struct stasis_app_playback *stasis_app_control_play_uri(
        struct stasis_app_control *control, const char *uri,
 struct stasis_app_playback *stasis_app_control_play_uri(
        struct stasis_app_control *control, const char *uri,
-       const char *language, int skipms, long offsetms)
+       const char *language, const char *target_id,
+       enum stasis_app_playback_target_type target_type,
+       int skipms, long offsetms)
 {
        RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
        char id[AST_UUID_STR_LEN];
 {
        RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
        char id[AST_UUID_STR_LEN];
@@ -290,6 +313,7 @@ struct stasis_app_playback *stasis_app_control_play_uri(
        ast_string_field_set(playback, id, id);
        ast_string_field_set(playback, media, uri);
        ast_string_field_set(playback, language, language);
        ast_string_field_set(playback, id, id);
        ast_string_field_set(playback, media, uri);
        ast_string_field_set(playback, language, language);
+       set_target_uri(playback, target_type, target_id);
        playback->control = control;
        playback->skipms = skipms;
        playback->offsetms = offsetms;
        playback->control = control;
        playback->skipms = skipms;
        playback->offsetms = offsetms;
@@ -342,9 +366,10 @@ struct ast_json *stasis_app_playback_to_json(
                return NULL;
        }
 
                return NULL;
        }
 
-       json = ast_json_pack("{s: s, s: s, s: s, s: s}",
+       json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
                "id", playback->id,
                "media_uri", playback->media,
                "id", playback->id,
                "media_uri", playback->media,
+               "target_uri", playback->target,
                "language", playback->language,
                "state", state_to_string(playback->state));
 
                "language", playback->language,
                "state", state_to_string(playback->state));
 
index df57a90..9d8abe0 100644 (file)
@@ -65,6 +65,11 @@ struct stasis_app_control *control_create(struct ast_channel *channel)
        control->command_queue = ao2_container_alloc_list(
                AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
 
        control->command_queue = ao2_container_alloc_list(
                AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
 
+       if (!control->command_queue) {
+               ao2_cleanup(control);
+               return NULL;
+       }
+
        control->channel = channel;
 
        return control;
        control->channel = channel;
 
        return control;
index bf3c0e7..bc5f25a 100644 (file)
@@ -578,16 +578,38 @@ int ari_validate_live_recording(struct ast_json *json)
 {
        int res = 1;
        struct ast_json_iter *iter;
 {
        int res = 1;
        struct ast_json_iter *iter;
-       int has_id = 0;
+       int has_format = 0;
+       int has_name = 0;
+       int has_state = 0;
 
        for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
 
        for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
-               if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
+               if (strcmp("format", ast_json_object_iter_key(iter)) == 0) {
                        int prop_is_valid;
                        int prop_is_valid;
-                       has_id = 1;
+                       has_format = 1;
+                       prop_is_valid = ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI LiveRecording field format failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("name", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_name = 1;
+                       prop_is_valid = ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI LiveRecording field name failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_state = 1;
                        prop_is_valid = ari_validate_string(
                                ast_json_object_iter_value(iter));
                        if (!prop_is_valid) {
                        prop_is_valid = ari_validate_string(
                                ast_json_object_iter_value(iter));
                        if (!prop_is_valid) {
-                               ast_log(LOG_ERROR, "ARI LiveRecording field id failed validation\n");
+                               ast_log(LOG_ERROR, "ARI LiveRecording field state failed validation\n");
                                res = 0;
                        }
                } else
                                res = 0;
                        }
                } else
@@ -599,8 +621,18 @@ int ari_validate_live_recording(struct ast_json *json)
                }
        }
 
                }
        }
 
-       if (!has_id) {
-               ast_log(LOG_ERROR, "ARI LiveRecording missing required field id\n");
+       if (!has_format) {
+               ast_log(LOG_ERROR, "ARI LiveRecording missing required field format\n");
+               res = 0;
+       }
+
+       if (!has_name) {
+               ast_log(LOG_ERROR, "ARI LiveRecording missing required field name\n");
+               res = 0;
+       }
+
+       if (!has_state) {
+               ast_log(LOG_ERROR, "ARI LiveRecording missing required field state\n");
                res = 0;
        }
 
                res = 0;
        }
 
index 2f64186..3cf6300 100644 (file)
@@ -816,7 +816,9 @@ ari_validator ari_validate_stasis_start_fn(void);
  * - id: string (required)
  * - technology: string (required)
  * LiveRecording
  * - id: string (required)
  * - technology: string (required)
  * LiveRecording
- * - id: string (required)
+ * - format: string (required)
+ * - name: string (required)
+ * - state: string (required)
  * StoredRecording
  * - duration_seconds: int
  * - formats: List[string] (required)
  * StoredRecording
  * - duration_seconds: int
  * - formats: List[string] (required)
index 6dad911..b378eb8 100644 (file)
@@ -35,8 +35,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_bridging.h"
 #include "asterisk/stasis_app.h"
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_bridging.h"
 #include "asterisk/stasis_app.h"
+#include "asterisk/stasis_app_playback.h"
+#include "asterisk/stasis_app_recording.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/core_unreal.h"
 #include "asterisk/channel.h"
 #include "asterisk/bridging.h"
 #include "asterisk/channel.h"
 #include "asterisk/bridging.h"
+#include "asterisk/format_cap.h"
+#include "asterisk/file.h"
 
 /*!
  * \brief Finds a bridge, filling the response with an error, if appropriate.
 
 /*!
  * \brief Finds a bridge, filling the response with an error, if appropriate.
@@ -144,9 +150,275 @@ void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct
        stasis_http_response_no_content(response);
 }
 
        stasis_http_response_no_content(response);
 }
 
+struct bridge_channel_control_thread_data {
+       struct ast_channel *bridge_channel;
+       struct stasis_app_control *control;
+};
+
+static void *bridge_channel_control_thread(void *data)
+{
+       struct bridge_channel_control_thread_data *thread_data = data;
+       struct ast_channel *bridge_channel = thread_data->bridge_channel;
+       struct stasis_app_control *control = thread_data->control;
+
+       RAII_VAR(struct ast_callid *, callid, ast_channel_callid(bridge_channel), ast_callid_cleanup);
+
+       if (callid) {
+               ast_callid_threadassoc_add(callid);
+       }
+
+       ast_free(thread_data);
+       thread_data = NULL;
+
+       stasis_app_control_execute_until_exhausted(bridge_channel, control);
+
+       ast_hangup(bridge_channel);
+       ao2_cleanup(control);
+       return NULL;
+}
+
+static struct ast_channel *prepare_bridge_media_channel(const char *type)
+{
+       RAII_VAR(struct ast_format_cap *, cap, NULL, ast_format_cap_destroy);
+       struct ast_format format;
+
+       cap = ast_format_cap_alloc_nolock();
+       if (!cap) {
+               return NULL;
+       }
+
+       ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0));
+
+       if (!cap) {
+               return NULL;
+       }
+
+       return ast_request(type, cap, NULL, "ARI", NULL);
+}
+
+void stasis_http_play_on_bridge(struct ast_variable *headers, struct ast_play_on_bridge_args *args, struct stasis_http_response *response)
+{
+       RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
+       RAII_VAR(struct ast_channel *, play_channel, NULL, ast_hangup);
+       RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
+       RAII_VAR(char *, playback_url, NULL, ast_free);
+       RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+
+       struct bridge_channel_control_thread_data *thread_data;
+       const char *language;
+       pthread_t threadid;
+
+       ast_assert(response != NULL);
+
+       if (!bridge) {
+               return;
+       }
+
+       if (!(play_channel = prepare_bridge_media_channel("Announcer"))) {
+               stasis_http_response_error(
+                       response, 500, "Internal Error", "Could not create playback channel");
+               return;
+       }
+       ast_debug(1, "Created announcer channel '%s'\n", ast_channel_name(play_channel));
+
+       if (ast_unreal_channel_push_to_bridge(play_channel, bridge)) {
+               stasis_http_response_error(
+                       response, 500, "Internal Error", "Failed to put playback channel into the bridge");
+               return;
+       }
+
+       control = stasis_app_control_create(play_channel);
+       if (control == NULL) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       snapshot = stasis_app_control_get_snapshot(control);
+       if (!snapshot) {
+               stasis_http_response_error(
+                       response, 500, "Internal Error", "Failed to get control snapshot");
+               return;
+       }
+
+       language = S_OR(args->lang, snapshot->language);
+
+       playback = stasis_app_control_play_uri(control, args->media, language,
+               args->bridge_id, STASIS_PLAYBACK_TARGET_BRIDGE, args->skipms,
+               args->offsetms);
+
+       if (!playback) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       ast_asprintf(&playback_url, "/playback/%s",
+               stasis_app_playback_get_id(playback));
+
+       if (!playback_url) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       json = stasis_app_playback_to_json(playback);
+       if (!json) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       /* Give play_channel and control reference to the thread data */
+       thread_data = ast_calloc(1, sizeof(*thread_data));
+       if (!thread_data) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       thread_data->bridge_channel = play_channel;
+       thread_data->control = control;
+
+       if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) {
+               stasis_http_response_alloc_failed(response);
+               ast_free(thread_data);
+               return;
+       }
+
+       /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */
+       play_channel = NULL;
+       control = NULL;
+
+       stasis_http_response_created(response, playback_url, json);
+}
+
 void stasis_http_record_bridge(struct ast_variable *headers, struct ast_record_bridge_args *args, struct stasis_http_response *response)
 {
 void stasis_http_record_bridge(struct ast_variable *headers, struct ast_record_bridge_args *args, struct stasis_http_response *response)
 {
-       ast_log(LOG_ERROR, "TODO: stasis_http_record_bridge\n");
+       RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
+       RAII_VAR(struct ast_channel *, record_channel, NULL, ast_hangup);
+       RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
+       RAII_VAR(char *, recording_url, NULL, ast_free);
+       RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+       RAII_VAR(struct stasis_app_recording_options *, options, NULL, ao2_cleanup);
+       RAII_VAR(char *, uri_encoded_name, NULL, ast_free);
+
+       size_t uri_name_maxlen;
+       struct bridge_channel_control_thread_data *thread_data;
+       pthread_t threadid;
+
+       ast_assert(response != NULL);
+
+       if (bridge == NULL) {
+               return;
+       }
+
+       if (!(record_channel = prepare_bridge_media_channel("Recorder"))) {
+               stasis_http_response_error(
+                       response, 500, "Internal Server Error", "Failed to create recording channel");
+               return;
+       }
+
+       if (ast_unreal_channel_push_to_bridge(record_channel, bridge)) {
+               stasis_http_response_error(
+                       response, 500, "Internal Error", "Failed to put recording channel into the bridge");
+               return;
+       }
+
+       control = stasis_app_control_create(record_channel);
+       if (control == NULL) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       options = stasis_app_recording_options_create(args->name, args->format);
+       if (options == NULL) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       options->max_silence_seconds = args->max_silence_seconds;
+       options->max_duration_seconds = args->max_duration_seconds;
+       options->terminate_on =
+               stasis_app_recording_termination_parse(args->terminate_on);
+       options->if_exists =
+               stasis_app_recording_if_exists_parse(args->if_exists);
+       options->beep = args->beep;
+
+       recording = stasis_app_control_record(control, options);
+       if (recording == NULL) {
+               switch(errno) {
+               case EINVAL:
+                       /* While the arguments are invalid, we should have
+                        * caught them prior to calling record.
+                        */
+                       stasis_http_response_error(
+                               response, 500, "Internal Server Error",
+                               "Error parsing request");
+                       break;
+               case EEXIST:
+                       stasis_http_response_error(response, 409, "Conflict",
+                               "Recording '%s' already in progress",
+                               args->name);
+                       break;
+               case ENOMEM:
+                       stasis_http_response_alloc_failed(response);
+                       break;
+               case EPERM:
+                       stasis_http_response_error(
+                               response, 400, "Bad Request",
+                               "Recording name invalid");
+                       break;
+               default:
+                       ast_log(LOG_WARNING,
+                               "Unrecognized recording error: %s\n",
+                               strerror(errno));
+                       stasis_http_response_error(
+                               response, 500, "Internal Server Error",
+                               "Internal Server Error");
+                       break;
+               }
+               return;
+       }
+
+       uri_name_maxlen = strlen(args->name) * 3;
+       uri_encoded_name = ast_malloc(uri_name_maxlen);
+       if (!uri_encoded_name) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+       ast_uri_encode(args->name, uri_encoded_name, uri_name_maxlen, ast_uri_http);
+
+       ast_asprintf(&recording_url, "/recordings/live/%s", uri_encoded_name);
+       if (!recording_url) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       json = stasis_app_recording_to_json(recording);
+       if (!json) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       thread_data = ast_calloc(1, sizeof(*thread_data));
+       if (!thread_data) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       thread_data->bridge_channel = record_channel;
+       thread_data->control = control;
+
+       if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) {
+               stasis_http_response_alloc_failed(response);
+               ast_free(thread_data);
+               return;
+       }
+
+       /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */
+       record_channel = NULL;
+       control = NULL;
+
+       stasis_http_response_created(response, recording_url, json);
 }
 
 void stasis_http_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct stasis_http_response *response)
 }
 
 void stasis_http_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct stasis_http_response *response)
index ec1992f..3935a11 100644 (file)
@@ -123,18 +123,43 @@ struct ast_remove_channel_from_bridge_args {
  * \param[out] response HTTP response
  */
 void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct stasis_http_response *response);
  * \param[out] response HTTP response
  */
 void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_play_on_bridge() */
+struct ast_play_on_bridge_args {
+       /*! \brief Bridge's id */
+       const char *bridge_id;
+       /*! \brief Media's URI to play. */
+       const char *media;
+       /*! \brief For sounds, selects language for sound. */
+       const char *lang;
+       /*! \brief Number of media to skip before playing. */
+       int offsetms;
+       /*! \brief Number of milliseconds to skip for forward/reverse operations. */
+       int skipms;
+};
+/*!
+ * \brief Start playback of media on a bridge.
+ *
+ * The media URI may be any of a number of URI's. You may use http: and https: URI's, as well as sound: and recording: URI's. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_play_on_bridge(struct ast_variable *headers, struct ast_play_on_bridge_args *args, struct stasis_http_response *response);
 /*! \brief Argument struct for stasis_http_record_bridge() */
 struct ast_record_bridge_args {
        /*! \brief Bridge's id */
        const char *bridge_id;
        /*! \brief Recording's filename */
        const char *name;
 /*! \brief Argument struct for stasis_http_record_bridge() */
 struct ast_record_bridge_args {
        /*! \brief Bridge's id */
        const char *bridge_id;
        /*! \brief Recording's filename */
        const char *name;
+       /*! \brief Format to encode audio in */
+       const char *format;
        /*! \brief Maximum duration of the recording, in seconds. 0 for no limit. */
        int max_duration_seconds;
        /*! \brief Maximum duration of silence, in seconds. 0 for no limit. */
        int max_silence_seconds;
        /*! \brief Maximum duration of the recording, in seconds. 0 for no limit. */
        int max_duration_seconds;
        /*! \brief Maximum duration of silence, in seconds. 0 for no limit. */
        int max_silence_seconds;
-       /*! \brief If true, and recording already exists, append to recording. */
-       int append;
+       /*! \brief Action to take if a recording with the same name already exists. */
+       const char *if_exists;
        /*! \brief Play beep when recording begins */
        int beep;
        /*! \brief DTMF input to terminate recording. */
        /*! \brief Play beep when recording begins */
        int beep;
        /*! \brief DTMF input to terminate recording. */
index f0bbd4b..c25917b 100644 (file)
@@ -273,7 +273,7 @@ void stasis_http_play_on_channel(struct ast_variable *headers,
        language = S_OR(args->lang, snapshot->language);
 
        playback = stasis_app_control_play_uri(control, args->media, language,
        language = S_OR(args->lang, snapshot->language);
 
        playback = stasis_app_control_play_uri(control, args->media, language,
-               args->skipms, args->offsetms);
+               args->channel_id, STASIS_PLAYBACK_TARGET_CHANNEL, args->skipms, args->offsetms);
        if (!playback) {
                stasis_http_response_error(
                        response, 500, "Internal Server Error",
        if (!playback) {
                stasis_http_response_error(
                        response, 500, "Internal Server Error",
index 87d5b3d..7b3c4a3 100644 (file)
                        ]
                },
                {
                        ]
                },
                {
+                       "path": "/bridges/{bridgeId}/play",
+                       "description": "Play media to the participants of a bridge",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Start playback of media on a bridge.",
+                                       "notes": "The media URI may be any of a number of URI's. You may use http: and https: URI's, as well as sound: and recording: URI's. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
+                                       "nickname": "playOnBridge",
+                                       "responseClass": "Playback",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "media",
+                                                       "description": "Media's URI to play.",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "lang",
+                                                       "description": "For sounds, selects language for sound.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "offsetms",
+                                                       "description": "Number of media to skip before playing.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "int",
+                                                       "defaultValue": 0,
+                                                       "allowableValues": {
+                                                               "valueType": "RANGE",
+                                                               "min": 0
+                                                       }
+
+                                               },
+                                               {
+                                                       "name": "skipms",
+                                                       "description": "Number of milliseconds to skip for forward/reverse operations.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "int",
+                                                       "defaultValue": 3000,
+                                                       "allowableValues": {
+                                                               "valueType": "RANGE",
+                                                               "min": 0
+                                                       }
+
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Bridge not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Bridge not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
                        "path": "/bridges/{bridgeId}/record",
                        "description": "Record audio on a bridge",
                        "operations": [
                        "path": "/bridges/{bridgeId}/record",
                        "description": "Record audio on a bridge",
                        "operations": [
                                                        "dataType": "string"
                                                },
                                                {
                                                        "dataType": "string"
                                                },
                                                {
+                                                       "name": "format",
+                                                       "description": "Format to encode audio in",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": true,
+                                                       "dataType": "string"
+                                               },
+                                               {
                                                        "name": "maxDurationSeconds",
                                                        "description": "Maximum duration of the recording, in seconds. 0 for no limit.",
                                                        "paramType": "query",
                                                        "required": false,
                                                        "allowMultiple": false,
                                                        "dataType": "int",
                                                        "name": "maxDurationSeconds",
                                                        "description": "Maximum duration of the recording, in seconds. 0 for no limit.",
                                                        "paramType": "query",
                                                        "required": false,
                                                        "allowMultiple": false,
                                                        "dataType": "int",
-                                                       "defaultValue": 0
+                                                       "defaultValue": 0,
+                                                       "allowableValues": {
+                                                               "valueType": "RANGE",
+                                                               "min": 0
+                                                       }
                                                },
                                                {
                                                        "name": "maxSilenceSeconds",
                                                },
                                                {
                                                        "name": "maxSilenceSeconds",
                                                        "required": false,
                                                        "allowMultiple": false,
                                                        "dataType": "int",
                                                        "required": false,
                                                        "allowMultiple": false,
                                                        "dataType": "int",
-                                                       "defaultValue": 0
+                                                       "defaultValue": 0,
+                                                       "allowableValues": {
+                                                               "valueType": "RANGE",
+                                                               "min": 0
+                                                       }
                                                },
                                                {
                                                },
                                                {
-                                                       "name": "append",
-                                                       "description": "If true, and recording already exists, append to recording.",
+                                                       "name": "ifExists",
+                                                       "description": "Action to take if a recording with the same name already exists.",
                                                        "paramType": "query",
                                                        "required": false,
                                                        "allowMultiple": false,
                                                        "paramType": "query",
                                                        "required": false,
                                                        "allowMultiple": false,
-                                                       "dataType": "boolean",
-                                                       "defaultValue": false
+                                                       "dataType": "string",
+                                                       "defaultValue": "fail",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "fail",
+                                                                       "overwrite",
+                                                                       "append"
+                                                               ]
+                                                       }
                                                },
                                                {
                                                        "name": "beep",
                                                },
                                                {
                                                        "name": "beep",
index 07c9750..69e9058 100644 (file)
                                        "summary": "Start a recording.",
                                        "notes": "Record audio from a channel. Note that this will not capture audio sent to the channel. The bridge itself has a record feature if that's what you want.",
                                        "nickname": "recordChannel",
                                        "summary": "Start a recording.",
                                        "notes": "Record audio from a channel. Note that this will not capture audio sent to the channel. The bridge itself has a record feature if that's what you want.",
                                        "nickname": "recordChannel",
-                                       "responseClass": "void",
+                                       "responseClass": "LiveRecording",
                                        "parameters": [
                                                {
                                                        "name": "channelId",
                                        "parameters": [
                                                {
                                                        "name": "channelId",
index 884c0db..734abdb 100644 (file)
@@ -53,7 +53,7 @@
                                        "httpMethod": "POST",
                                        "summary": "Get a playback's details.",
                                        "nickname": "controlPlayback",
                                        "httpMethod": "POST",
                                        "summary": "Get a playback's details.",
                                        "nickname": "controlPlayback",
-                                       "responseClass": "Playback",
+                                       "responseClass": "void",
                                        "parameters": [
                                                {
                                                        "name": "playbackId",
                                        "parameters": [
                                                {
                                                        "name": "playbackId",
index 9efdc7b..b564ede 100644 (file)
                        "id": "LiveRecording",
                        "description": "A recording that is in progress",
                        "properties": {
                        "id": "LiveRecording",
                        "description": "A recording that is in progress",
                        "properties": {
-                               "id": {
+                               "name": {
+                                       "required": true,
+                                       "type": "string"
+                               },
+                               "state": {
+                                       "required": true,
+                                       "type": "string"
+                               },
+                               "format": {
                                        "required": true,
                                        "type": "string"
                                }
                                        "required": true,
                                        "type": "string"
                                }