2013-05-23 20:11:35 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
/*! Default number of milliseconds of media to skip */
|
|
|
|
#define PLAYBACK_DEFAULT_SKIPMS 3000
|
2013-05-23 20:11:35 +00:00
|
|
|
|
|
|
|
#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 */
|
|
|
|
);
|
|
|
|
/*! Control object for the channel we're playing back to */
|
|
|
|
struct stasis_app_control *control;
|
2013-05-23 20:21:16 +00:00
|
|
|
/*! Number of milliseconds to skip before playing */
|
|
|
|
long offsetms;
|
|
|
|
/*! Number of milliseconds to skip for forward/reverse operations */
|
|
|
|
int skipms;
|
|
|
|
|
|
|
|
/*! Number of milliseconds of media that has been played */
|
|
|
|
long playedms;
|
|
|
|
/*! Current playback state */
|
|
|
|
enum stasis_app_playback_state state;
|
2013-05-23 20:11:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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";
|
2013-05-23 20:21:16 +00:00
|
|
|
case STASIS_PLAYBACK_STATE_PAUSED:
|
|
|
|
return "paused";
|
|
|
|
case STASIS_PLAYBACK_STATE_STOPPED:
|
2013-05-23 20:11:35 +00:00
|
|
|
case STASIS_PLAYBACK_STATE_COMPLETE:
|
2013-05-23 20:21:16 +00:00
|
|
|
case STASIS_PLAYBACK_STATE_CANCELED:
|
|
|
|
/* It doesn't really matter how we got here, but all of these
|
|
|
|
* states really just mean 'done' */
|
2013-05-23 20:11:35 +00:00
|
|
|
return "done";
|
2013-05-23 20:21:16 +00:00
|
|
|
case STASIS_PLAYBACK_STATE_MAX:
|
|
|
|
break;
|
2013-05-23 20:11:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return "?";
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
json = stasis_app_playback_to_json(playback);
|
2013-05-23 20:11:35 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
static void playback_cleanup(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
ao2_unlink_flags(playbacks, playback,
|
|
|
|
OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_first_update(struct stasis_app_playback *playback,
|
|
|
|
const char *uniqueid)
|
2013-05-23 20:11:35 +00:00
|
|
|
{
|
2013-05-23 20:21:16 +00:00
|
|
|
int res;
|
2013-05-23 20:11:35 +00:00
|
|
|
SCOPED_AO2LOCK(lock, playback);
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
if (playback->state == STASIS_PLAYBACK_STATE_CANCELED) {
|
|
|
|
ast_log(LOG_NOTICE, "%s: Playback canceled for %s\n",
|
|
|
|
uniqueid, playback->media);
|
|
|
|
res = -1;
|
|
|
|
} else {
|
|
|
|
res = 0;
|
|
|
|
playback->state = STASIS_PLAYBACK_STATE_PLAYING;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:11:35 +00:00
|
|
|
playback_publish(playback);
|
2013-05-23 20:21:16 +00:00
|
|
|
return res;
|
2013-05-23 20:11:35 +00:00
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
static void playback_final_update(struct stasis_app_playback *playback,
|
|
|
|
long playedms, int res, const char *uniqueid)
|
2013-05-23 20:11:35 +00:00
|
|
|
{
|
2013-05-23 20:21:16 +00:00
|
|
|
SCOPED_AO2LOCK(lock, playback);
|
2013-05-23 20:11:35 +00:00
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
playback->playedms = playedms;
|
|
|
|
if (res == 0) {
|
|
|
|
playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
|
|
|
|
} else {
|
|
|
|
if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
|
|
|
|
ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
|
|
|
|
uniqueid, playback->media);
|
|
|
|
} else {
|
|
|
|
ast_log(LOG_WARNING, "%s: Playback failed for %s\n",
|
|
|
|
uniqueid, playback->media);
|
|
|
|
playback->state = STASIS_PLAYBACK_STATE_STOPPED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
playback_publish(playback);
|
2013-05-23 20:11:35 +00:00
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
static void *play_uri(struct stasis_app_control *control,
|
2013-05-23 20:11:35 +00:00
|
|
|
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;
|
2013-05-23 20:21:16 +00:00
|
|
|
long offsetms;
|
|
|
|
|
2013-05-23 20:11:35 +00:00
|
|
|
/* 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;
|
|
|
|
|
|
|
|
playback = data;
|
|
|
|
ast_assert(playback != NULL);
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
offsetms = playback->offsetms;
|
|
|
|
|
|
|
|
res = playback_first_update(playback, ast_channel_uniqueid(chan));
|
|
|
|
|
|
|
|
if (res != 0) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2013-05-23 20:11:35 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
res = ast_control_streamfile_lang(chan, file, fwd, rev, stop, pause,
|
|
|
|
restart, playback->skipms, playback->language, &offsetms);
|
2013-05-23 20:11:35 +00:00
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
playback_final_update(playback, offsetms, res,
|
|
|
|
ast_channel_uniqueid(chan));
|
2013-05-23 20:11:35 +00:00
|
|
|
|
|
|
|
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,
|
2013-05-23 20:21:16 +00:00
|
|
|
const char *language, int skipms, long offsetms)
|
2013-05-23 20:11:35 +00:00
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
|
|
|
|
char id[AST_UUID_STR_LEN];
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
if (skipms < 0 || offsetms < 0) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:11:35 +00:00
|
|
|
ast_debug(3, "%s: Sending play(%s) command\n",
|
|
|
|
stasis_app_control_get_channel_id(control), uri);
|
|
|
|
|
|
|
|
playback = ao2_alloc(sizeof(*playback), playback_dtor);
|
2013-05-23 21:46:38 +00:00
|
|
|
if (!playback || ast_string_field_init(playback, 128)) {
|
2013-05-23 20:11:35 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
if (skipms == 0) {
|
|
|
|
skipms = PLAYBACK_DEFAULT_SKIPMS;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:11:35 +00:00
|
|
|
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;
|
2013-05-23 20:21:16 +00:00
|
|
|
playback->skipms = skipms;
|
|
|
|
playback->offsetms = offsetms;
|
2013-05-23 20:11:35 +00:00
|
|
|
ao2_link(playbacks, playback);
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
playback->state = STASIS_PLAYBACK_STATE_QUEUED;
|
|
|
|
playback_publish(playback);
|
2013-05-23 20:11:35 +00:00
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
/* A ref is kept in the playbacks container; no need to bump */
|
|
|
|
stasis_app_send_command_async(control, play_uri, playback);
|
2013-05-23 20:11:35 +00:00
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
/* Although this should be bumped for the caller */
|
2013-05-23 20:11:35 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
|
2013-05-23 20:11:35 +00:00
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
|
|
|
|
|
|
|
|
playback = ao2_find(playbacks, id, OBJ_KEY);
|
|
|
|
if (playback == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
ao2_ref(playback, +1);
|
|
|
|
return playback;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ast_json *stasis_app_playback_to_json(
|
|
|
|
const 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));
|
|
|
|
|
2013-05-23 20:11:35 +00:00
|
|
|
return ast_json_ref(json);
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
|
|
|
|
|
|
|
|
static int playback_noop(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_cancel(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
SCOPED_AO2LOCK(lock, playback);
|
|
|
|
playback->state = STASIS_PLAYBACK_STATE_CANCELED;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_stop(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
SCOPED_AO2LOCK(lock, playback);
|
|
|
|
playback->state = STASIS_PLAYBACK_STATE_STOPPED;
|
|
|
|
return stasis_app_control_queue_control(playback->control,
|
|
|
|
AST_CONTROL_STREAM_STOP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_restart(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
return stasis_app_control_queue_control(playback->control,
|
|
|
|
AST_CONTROL_STREAM_RESTART);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_pause(struct stasis_app_playback *playback)
|
2013-05-23 20:11:35 +00:00
|
|
|
{
|
|
|
|
SCOPED_AO2LOCK(lock, playback);
|
2013-05-23 20:21:16 +00:00
|
|
|
playback->state = STASIS_PLAYBACK_STATE_PAUSED;
|
|
|
|
playback_publish(playback);
|
|
|
|
return stasis_app_control_queue_control(playback->control,
|
|
|
|
AST_CONTROL_STREAM_SUSPEND);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_unpause(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
SCOPED_AO2LOCK(lock, playback);
|
|
|
|
playback->state = STASIS_PLAYBACK_STATE_PLAYING;
|
|
|
|
playback_publish(playback);
|
|
|
|
return stasis_app_control_queue_control(playback->control,
|
|
|
|
AST_CONTROL_STREAM_SUSPEND);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_reverse(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
return stasis_app_control_queue_control(playback->control,
|
|
|
|
AST_CONTROL_STREAM_REVERSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int playback_forward(struct stasis_app_playback *playback)
|
|
|
|
{
|
|
|
|
return stasis_app_control_queue_control(playback->control,
|
|
|
|
AST_CONTROL_STREAM_FORWARD);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief A sparse array detailing how commands should be handled in the
|
|
|
|
* various playback states. Unset entries imply invalid operations.
|
|
|
|
*/
|
|
|
|
playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
|
|
|
|
[STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
|
|
|
|
[STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
|
|
|
|
|
|
|
|
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
|
|
|
|
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
|
|
|
|
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
|
|
|
|
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
|
|
|
|
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
|
|
|
|
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
|
|
|
|
|
|
|
|
[STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
|
|
|
|
[STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
|
|
|
|
[STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
|
|
|
|
|
|
|
|
[STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
|
|
|
|
[STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
|
|
|
|
[STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum stasis_playback_oper_results stasis_app_playback_operation(
|
|
|
|
struct stasis_app_playback *playback,
|
|
|
|
enum stasis_app_playback_media_operation operation)
|
|
|
|
{
|
|
|
|
playback_opreation_cb cb;
|
|
|
|
SCOPED_AO2LOCK(lock, playback);
|
|
|
|
|
|
|
|
ast_assert(playback->state >= 0 && playback->state < STASIS_PLAYBACK_STATE_MAX);
|
|
|
|
|
|
|
|
if (operation < 0 || operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
|
|
|
|
ast_log(LOG_ERROR, "Invalid playback operation %d\n", operation);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
cb = operations[playback->state][operation];
|
|
|
|
|
|
|
|
if (!cb) {
|
|
|
|
if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
|
|
|
|
/* So we can be specific in our error message. */
|
|
|
|
return STASIS_PLAYBACK_OPER_NOT_PLAYING;
|
|
|
|
} else {
|
|
|
|
/* And, really, all operations should be valid during
|
|
|
|
* playback */
|
|
|
|
ast_log(LOG_ERROR,
|
|
|
|
"Unhandled operation during playback: %d\n",
|
|
|
|
operation);
|
|
|
|
return STASIS_PLAYBACK_OPER_FAILED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cb(playback) ?
|
|
|
|
STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
|
2013-05-23 20:11:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|