mirror of http://gerrit.asterisk.org/asterisk
and GET /playback/{playbackId}. This allows an external application to initiate playback of a sound on a channel while the channel is in the Stasis application. /play commands are issued asynchronously, and return immediately with the URL of the associated /playback resource. Playback commands queue up, playing in succession. The /playback resource shows the state of a playback operation as enqueued, playing or complete. (Although the operation will only be in the 'complete' state for a very short time, since it is almost immediately freed up). (closes issue ASTERISK-21283) (closes issue ASTERISK-21586) Review: https://reviewboard.asterisk.org/r/2531/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389587 65c4cc65-6c06-0410-ace0-fbb531ad65f3changes/78/78/1
parent
3464e0919a
commit
10ba6bf8a8
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
*
|
||||
* David M. Lee, II <dlee@digium.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef _ASTERISK_STASIS_APP_PLAYBACK_H
|
||||
#define _ASTERISK_STASIS_APP_PLAYBACK_H
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Stasis Application Playback API. See \ref res_stasis "Stasis
|
||||
* Application API" for detailed documentation.
|
||||
*
|
||||
* \author David M. Lee, II <dlee@digium.com>
|
||||
* \since 12
|
||||
*/
|
||||
|
||||
#include "asterisk/stasis_app.h"
|
||||
|
||||
/*! Opaque struct for handling the playback of a single file */
|
||||
struct stasis_app_playback;
|
||||
|
||||
/*! State of a playback operation */
|
||||
enum stasis_app_playback_state {
|
||||
/*! The playback has not started yet */
|
||||
STASIS_PLAYBACK_STATE_QUEUED,
|
||||
/*! The media is currently playing */
|
||||
STASIS_PLAYBACK_STATE_PLAYING,
|
||||
/*! The media has stopped playing */
|
||||
STASIS_PLAYBACK_STATE_COMPLETE,
|
||||
};
|
||||
|
||||
enum stasis_app_playback_media_control {
|
||||
STASIS_PLAYBACK_STOP,
|
||||
STASIS_PLAYBACK_PAUSE,
|
||||
STASIS_PLAYBACK_PLAY,
|
||||
STASIS_PLAYBACK_REWIND,
|
||||
STASIS_PLAYBACK_FAST_FORWARD,
|
||||
STASIS_PLAYBACK_SPEED_UP,
|
||||
STASIS_PLAYBACK_SLOW_DOWN,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Play a file to the control's channel.
|
||||
*
|
||||
* Note that the file isn't the full path to the file. Asterisk's internal
|
||||
* playback mechanism will automagically select the best format based on the
|
||||
* available codecs for the channel.
|
||||
*
|
||||
* \param control Control for \c res_stasis.
|
||||
* \param file Base filename for the file to play.
|
||||
* \return Playback control object.
|
||||
* \return \c NULL on error.
|
||||
*/
|
||||
struct stasis_app_playback *stasis_app_control_play_uri(
|
||||
struct stasis_app_control *control, const char *file,
|
||||
const char *language);
|
||||
|
||||
/*!
|
||||
* \brief Gets the current state of a playback operation.
|
||||
*
|
||||
* \param playback Playback control object.
|
||||
* \return The state of the \a playback object.
|
||||
*/
|
||||
enum stasis_app_playback_state stasis_app_playback_get_state(
|
||||
struct stasis_app_playback *playback);
|
||||
|
||||
/*!
|
||||
* \brief Gets the unique id of a playback object.
|
||||
*
|
||||
* \param playback Playback control object.
|
||||
* \return \a playback's id.
|
||||
* \return \c NULL if \a playback ic \c NULL
|
||||
*/
|
||||
const char *stasis_app_playback_get_id(
|
||||
struct stasis_app_playback *playback);
|
||||
|
||||
/*!
|
||||
* \brief Finds the playback object with the given id.
|
||||
*
|
||||
* \param id Id of the playback object to find.
|
||||
* \return Associated \ref stasis_app_playback object.
|
||||
* \return \c NULL if \a id not found.
|
||||
*/
|
||||
struct ast_json *stasis_app_playback_find_by_id(const char *id);
|
||||
|
||||
/*!
|
||||
* \brief Controls the media for a given playback operation.
|
||||
*
|
||||
* \param playback Playback control object.
|
||||
* \param control Media control operation.
|
||||
* \return 0 on success
|
||||
* \return non-zero on error.
|
||||
*/
|
||||
int stasis_app_playback_control(struct stasis_app_playback *playback,
|
||||
enum stasis_app_playback_media_control control);
|
||||
|
||||
/*!
|
||||
* \brief Message type for playback updates. The data is an
|
||||
* \ref ast_channel_blob.
|
||||
*/
|
||||
struct stasis_message_type *stasis_app_playback_snapshot_type(void);
|
||||
|
||||
#endif /* _ASTERISK_STASIS_APP_PLAYBACK_H */
|
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
*
|
||||
* David M. Lee, II <dlee@digium.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief res_stasis playback support.
|
||||
*
|
||||
* \author David M. Lee, II <dlee@digium.com>
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend type="module">res_stasis</depend>
|
||||
<support_level>core</support_level>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/astobj2.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/logger.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/stasis_app_impl.h"
|
||||
#include "asterisk/stasis_app_playback.h"
|
||||
#include "asterisk/stasis_channels.h"
|
||||
#include "asterisk/stringfields.h"
|
||||
#include "asterisk/uuid.h"
|
||||
|
||||
/*! Number of hash buckets for playback container. Keep it prime! */
|
||||
#define PLAYBACK_BUCKETS 127
|
||||
|
||||
/*! Number of milliseconds of media to skip */
|
||||
#define PLAYBACK_SKIPMS 250
|
||||
|
||||
#define SOUND_URI_SCHEME "sound:"
|
||||
#define RECORDING_URI_SCHEME "recording:"
|
||||
|
||||
STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type);
|
||||
|
||||
/*! Container of all current playbacks */
|
||||
static struct ao2_container *playbacks;
|
||||
|
||||
/*! Playback control object for res_stasis */
|
||||
struct stasis_app_playback {
|
||||
AST_DECLARE_STRING_FIELDS(
|
||||
AST_STRING_FIELD(id); /*!< Playback unique id */
|
||||
AST_STRING_FIELD(media); /*!< Playback media uri */
|
||||
AST_STRING_FIELD(language); /*!< Preferred language */
|
||||
);
|
||||
/*! Current playback state */
|
||||
enum stasis_app_playback_state state;
|
||||
/*! Control object for the channel we're playing back to */
|
||||
struct stasis_app_control *control;
|
||||
};
|
||||
|
||||
static int playback_hash(const void *obj, int flags)
|
||||
{
|
||||
const struct stasis_app_playback *playback = obj;
|
||||
const char *id = flags & OBJ_KEY ? obj : playback->id;
|
||||
return ast_str_hash(id);
|
||||
}
|
||||
|
||||
static int playback_cmp(void *obj, void *arg, int flags)
|
||||
{
|
||||
struct stasis_app_playback *lhs = obj;
|
||||
struct stasis_app_playback *rhs = arg;
|
||||
const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
|
||||
|
||||
if (strcmp(lhs->id, rhs_id) == 0) {
|
||||
return CMP_MATCH | CMP_STOP;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *state_to_string(enum stasis_app_playback_state state)
|
||||
{
|
||||
switch (state) {
|
||||
case STASIS_PLAYBACK_STATE_QUEUED:
|
||||
return "queued";
|
||||
case STASIS_PLAYBACK_STATE_PLAYING:
|
||||
return "playing";
|
||||
case STASIS_PLAYBACK_STATE_COMPLETE:
|
||||
return "done";
|
||||
}
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
static struct ast_json *playback_to_json(struct stasis_app_playback *playback)
|
||||
{
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
|
||||
if (playback == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json = ast_json_pack("{s: s, s: s, s: s, s: s}",
|
||||
"id", playback->id,
|
||||
"media_uri", playback->media,
|
||||
"language", playback->language,
|
||||
"state", state_to_string(playback->state));
|
||||
|
||||
return ast_json_ref(json);
|
||||
}
|
||||
|
||||
static void playback_publish(struct stasis_app_playback *playback)
|
||||
{
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
|
||||
|
||||
ast_assert(playback != NULL);
|
||||
|
||||
json = playback_to_json(playback);
|
||||
if (json == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
message = ast_channel_blob_create_from_cache(
|
||||
stasis_app_control_get_channel_id(playback->control),
|
||||
stasis_app_playback_snapshot_type(), json);
|
||||
if (message == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
stasis_app_control_publish(playback->control, message);
|
||||
}
|
||||
|
||||
static void playback_set_state(struct stasis_app_playback *playback,
|
||||
enum stasis_app_playback_state state)
|
||||
{
|
||||
SCOPED_AO2LOCK(lock, playback);
|
||||
|
||||
playback->state = state;
|
||||
playback_publish(playback);
|
||||
}
|
||||
|
||||
static void playback_cleanup(struct stasis_app_playback *playback)
|
||||
{
|
||||
playback_set_state(playback, STASIS_PLAYBACK_STATE_COMPLETE);
|
||||
|
||||
ao2_unlink_flags(playbacks, playback,
|
||||
OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
|
||||
}
|
||||
|
||||
static void *__app_control_play_uri(struct stasis_app_control *control,
|
||||
struct ast_channel *chan, void *data)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_playback *, playback, NULL,
|
||||
playback_cleanup);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
const char *file;
|
||||
int res;
|
||||
/* Even though these local variables look fairly pointless, the avoid
|
||||
* having a bunch of NULL's passed directly into
|
||||
* ast_control_streamfile() */
|
||||
const char *fwd = NULL;
|
||||
const char *rev = NULL;
|
||||
const char *stop = NULL;
|
||||
const char *pause = NULL;
|
||||
const char *restart = NULL;
|
||||
int skipms = PLAYBACK_SKIPMS;
|
||||
long offsetms = 0;
|
||||
|
||||
playback = data;
|
||||
ast_assert(playback != NULL);
|
||||
|
||||
playback_set_state(playback, STASIS_PLAYBACK_STATE_PLAYING);
|
||||
|
||||
if (ast_channel_state(chan) != AST_STATE_UP) {
|
||||
ast_answer(chan);
|
||||
}
|
||||
|
||||
if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
|
||||
/* Play sound */
|
||||
file = playback->media + strlen(SOUND_URI_SCHEME);
|
||||
} else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
|
||||
/* Play recording */
|
||||
file = playback->media + strlen(RECORDING_URI_SCHEME);
|
||||
} else {
|
||||
/* Play URL */
|
||||
ast_log(LOG_ERROR, "Unimplemented\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = ast_control_streamfile(chan, file, fwd, rev, stop, pause,
|
||||
restart, skipms, &offsetms);
|
||||
|
||||
if (res != 0) {
|
||||
ast_log(LOG_WARNING, "%s: Playback failed for %s",
|
||||
ast_channel_uniqueid(chan), playback->media);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void playback_dtor(void *obj)
|
||||
{
|
||||
struct stasis_app_playback *playback = obj;
|
||||
|
||||
ast_string_field_free_memory(playback);
|
||||
}
|
||||
|
||||
struct stasis_app_playback *stasis_app_control_play_uri(
|
||||
struct stasis_app_control *control, const char *uri,
|
||||
const char *language)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
|
||||
char id[AST_UUID_STR_LEN];
|
||||
|
||||
ast_debug(3, "%s: Sending play(%s) command\n",
|
||||
stasis_app_control_get_channel_id(control), uri);
|
||||
|
||||
playback = ao2_alloc(sizeof(*playback), playback_dtor);
|
||||
if (!playback || ast_string_field_init(playback, 128) ){
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_uuid_generate_str(id, sizeof(id));
|
||||
ast_string_field_set(playback, id, id);
|
||||
ast_string_field_set(playback, media, uri);
|
||||
ast_string_field_set(playback, language, language);
|
||||
playback->control = control;
|
||||
ao2_link(playbacks, playback);
|
||||
|
||||
playback_set_state(playback, STASIS_PLAYBACK_STATE_QUEUED);
|
||||
|
||||
ao2_ref(playback, +1);
|
||||
stasis_app_send_command_async(
|
||||
control, __app_control_play_uri, playback);
|
||||
|
||||
|
||||
ao2_ref(playback, +1);
|
||||
return playback;
|
||||
}
|
||||
|
||||
enum stasis_app_playback_state stasis_app_playback_get_state(
|
||||
struct stasis_app_playback *control)
|
||||
{
|
||||
SCOPED_AO2LOCK(lock, control);
|
||||
return control->state;
|
||||
}
|
||||
|
||||
const char *stasis_app_playback_get_id(
|
||||
struct stasis_app_playback *control)
|
||||
{
|
||||
/* id is immutable; no lock needed */
|
||||
return control->id;
|
||||
}
|
||||
|
||||
struct ast_json *stasis_app_playback_find_by_id(const char *id)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
|
||||
playback = ao2_find(playbacks, id, OBJ_KEY);
|
||||
if (playback == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json = playback_to_json(playback);
|
||||
return ast_json_ref(json);
|
||||
}
|
||||
|
||||
int stasis_app_playback_control(struct stasis_app_playback *playback,
|
||||
enum stasis_app_playback_media_control control)
|
||||
{
|
||||
SCOPED_AO2LOCK(lock, playback);
|
||||
ast_assert(0); /* TODO */
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
|
||||
if (r != 0) {
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
|
||||
playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash,
|
||||
playback_cmp);
|
||||
if (!playbacks) {
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
ao2_cleanup(playbacks);
|
||||
playbacks = NULL;
|
||||
STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS,
|
||||
"Stasis application playback support",
|
||||
.load = load_module,
|
||||
.unload = unload_module,
|
||||
.nonoptreq = "res_stasis");
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
global:
|
||||
LINKER_SYMBOL_PREFIXstasis_app_*;
|
||||
local:
|
||||
*;
|
||||
};
|
Loading…
Reference in new issue