mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-06 04:30:28 +00:00
ARI: Bridge Playback, Bridge Record
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
This commit is contained in:
218
channels/chan_bridge_media.c
Normal file
218
channels/chan_bridge_media.c
Normal file
@@ -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,
|
||||
);
|
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "asterisk/astobj2.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/bridging.h"
|
||||
#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);
|
||||
|
||||
/*!
|
||||
* \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)
|
||||
|
@@ -281,7 +281,16 @@ struct ast_callid *ast_read_threadstorage_callid(void);
|
||||
*
|
||||
* \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
|
||||
|
@@ -126,6 +126,29 @@ struct stasis_app_control *stasis_app_control_find_by_channel(
|
||||
struct stasis_app_control *stasis_app_control_find_by_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
|
||||
*
|
||||
|
@@ -69,6 +69,13 @@ enum stasis_app_playback_media_operation {
|
||||
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.
|
||||
*
|
||||
@@ -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 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.
|
||||
@@ -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,
|
||||
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.
|
||||
|
@@ -668,6 +668,96 @@ void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *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;
|
||||
|
@@ -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)
|
||||
{
|
||||
@@ -531,6 +536,16 @@ int app_send_end_msg(struct app *app, struct ast_channel *chan)
|
||||
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[])
|
||||
@@ -750,7 +765,7 @@ static struct ast_json *simple_bridge_channel_event(
|
||||
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),
|
||||
|
@@ -357,6 +357,73 @@ static void stasis_http_remove_channel_from_bridge_cb(
|
||||
}
|
||||
#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.
|
||||
@@ -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, "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, "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);
|
||||
@@ -448,6 +518,15 @@ static struct stasis_rest_handlers bridges_bridgeId_removeChannel = {
|
||||
.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 = {
|
||||
@@ -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,
|
||||
},
|
||||
.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 = {
|
||||
|
@@ -796,7 +796,7 @@ static void stasis_http_record_channel_cb(
|
||||
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);
|
||||
|
@@ -192,7 +192,7 @@ static void stasis_http_control_playback_cb(
|
||||
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);
|
||||
|
@@ -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(target); /*!< Playback device uri */
|
||||
);
|
||||
/*! 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);
|
||||
}
|
||||
|
||||
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,
|
||||
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];
|
||||
@@ -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);
|
||||
set_target_uri(playback, target_type, target_id);
|
||||
playback->control = control;
|
||||
playback->skipms = skipms;
|
||||
playback->offsetms = offsetms;
|
||||
@@ -342,9 +366,10 @@ struct ast_json *stasis_app_playback_to_json(
|
||||
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,
|
||||
"target_uri", playback->target,
|
||||
"language", playback->language,
|
||||
"state", state_to_string(playback->state));
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
if (!control->command_queue) {
|
||||
ao2_cleanup(control);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
control->channel = channel;
|
||||
|
||||
return control;
|
||||
|
@@ -578,16 +578,38 @@ int ari_validate_live_recording(struct ast_json *json)
|
||||
{
|
||||
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)) {
|
||||
if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
|
||||
if (strcmp("format", ast_json_object_iter_key(iter)) == 0) {
|
||||
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 id failed validation\n");
|
||||
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) {
|
||||
ast_log(LOG_ERROR, "ARI LiveRecording field state failed validation\n");
|
||||
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;
|
||||
}
|
||||
|
||||
|
@@ -816,7 +816,9 @@ ari_validator ari_validate_stasis_start_fn(void);
|
||||
* - 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)
|
||||
|
@@ -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_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/format_cap.h"
|
||||
#include "asterisk/file.h"
|
||||
|
||||
/*!
|
||||
* \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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@@ -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);
|
||||
/*! \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 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 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. */
|
||||
|
@@ -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,
|
||||
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",
|
||||
|
@@ -168,6 +168,83 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@@ -195,6 +272,14 @@
|
||||
"allowMultiple": false,
|
||||
"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.",
|
||||
@@ -202,7 +287,11 @@
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "int",
|
||||
"defaultValue": 0
|
||||
"defaultValue": 0,
|
||||
"allowableValues": {
|
||||
"valueType": "RANGE",
|
||||
"min": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "maxSilenceSeconds",
|
||||
@@ -211,16 +300,28 @@
|
||||
"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,
|
||||
"dataType": "boolean",
|
||||
"defaultValue": false
|
||||
"dataType": "string",
|
||||
"defaultValue": "fail",
|
||||
"allowableValues": {
|
||||
"valueType": "LIST",
|
||||
"values": [
|
||||
"fail",
|
||||
"overwrite",
|
||||
"append"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beep",
|
||||
|
@@ -538,7 +538,7 @@
|
||||
"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",
|
||||
|
@@ -53,7 +53,7 @@
|
||||
"httpMethod": "POST",
|
||||
"summary": "Get a playback's details.",
|
||||
"nickname": "controlPlayback",
|
||||
"responseClass": "Playback",
|
||||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "playbackId",
|
||||
|
@@ -243,7 +243,15 @@
|
||||
"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"
|
||||
}
|
||||
|
Reference in New Issue
Block a user