diff options
author | David M. Lee <dlee@digium.com> | 2013-05-23 20:21:16 +0000 |
---|---|---|
committer | David M. Lee <dlee@digium.com> | 2013-05-23 20:21:16 +0000 |
commit | 557125664da831cd332b6bab9d3da219fd484c63 (patch) | |
tree | c414cdca4ab023c7b00a7dcaf90f7205092c60bc /res/res_stasis_playback.c | |
parent | 10ba6bf8a8114278ca974861ecebcb3a827d8d5b (diff) |
This patch adds support for controlling a playback operation from the
Asterisk REST interface.
This adds the /playback/{playbackId}/control resource, which may be
POSTed to to pause, unpause, reverse, forward or restart the media
playback.
Attempts to control a playback that is not currently playing will
either return a 404 Not Found (because the playback object no longer
exists) or a 409 Conflict (because the playback object is still in the
queue to be played).
This patch also adds skipms and offsetms parameters to the
/channels/{channelId}/play resource.
(closes issue ASTERISK-21587)
Review: https://reviewboard.asterisk.org/r/2559
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389603 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res/res_stasis_playback.c')
-rw-r--r-- | res/res_stasis_playback.c | 272 |
1 files changed, 220 insertions, 52 deletions
diff --git a/res/res_stasis_playback.c b/res/res_stasis_playback.c index 5f54a14b4..842fc0e1d 100644 --- a/res/res_stasis_playback.c +++ b/res/res_stasis_playback.c @@ -46,8 +46,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /*! 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 +/*! Default number of milliseconds of media to skip */ +#define PLAYBACK_DEFAULT_SKIPMS 3000 #define SOUND_URI_SCHEME "sound:" #define RECORDING_URI_SCHEME "recording:" @@ -64,10 +64,17 @@ struct stasis_app_playback { 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; + /*! 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; }; static int playback_hash(const void *obj, int flags) @@ -97,30 +104,21 @@ static const char *state_to_string(enum stasis_app_playback_state state) return "queued"; case STASIS_PLAYBACK_STATE_PLAYING: return "playing"; + case STASIS_PLAYBACK_STATE_PAUSED: + return "paused"; + case STASIS_PLAYBACK_STATE_STOPPED: case STASIS_PLAYBACK_STATE_COMPLETE: + case STASIS_PLAYBACK_STATE_CANCELED: + /* It doesn't really matter how we got here, but all of these + * states really just mean 'done' */ return "done"; + case STASIS_PLAYBACK_STATE_MAX: + break; } 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); @@ -129,7 +127,7 @@ static void playback_publish(struct stasis_app_playback *playback) ast_assert(playback != NULL); - json = playback_to_json(playback); + json = stasis_app_playback_to_json(playback); if (json == NULL) { return; } @@ -144,24 +142,54 @@ static void playback_publish(struct stasis_app_playback *playback) stasis_app_control_publish(playback->control, message); } -static void playback_set_state(struct stasis_app_playback *playback, - enum stasis_app_playback_state state) +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) { + int res; SCOPED_AO2LOCK(lock, playback); - playback->state = state; + 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; + } + playback_publish(playback); + return res; } -static void playback_cleanup(struct stasis_app_playback *playback) +static void playback_final_update(struct stasis_app_playback *playback, + long playedms, int res, const char *uniqueid) { - playback_set_state(playback, STASIS_PLAYBACK_STATE_COMPLETE); + SCOPED_AO2LOCK(lock, playback); - ao2_unlink_flags(playbacks, playback, - OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); + 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); } -static void *__app_control_play_uri(struct stasis_app_control *control, +static void *play_uri(struct stasis_app_control *control, struct ast_channel *chan, void *data) { RAII_VAR(struct stasis_app_playback *, playback, NULL, @@ -169,6 +197,8 @@ static void *__app_control_play_uri(struct stasis_app_control *control, RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); const char *file; int res; + long offsetms; + /* Even though these local variables look fairly pointless, the avoid * having a bunch of NULL's passed directly into * ast_control_streamfile() */ @@ -177,13 +207,17 @@ static void *__app_control_play_uri(struct stasis_app_control *control, 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); + offsetms = playback->offsetms; + + res = playback_first_update(playback, ast_channel_uniqueid(chan)); + + if (res != 0) { + return NULL; + } if (ast_channel_state(chan) != AST_STATE_UP) { ast_answer(chan); @@ -201,13 +235,11 @@ static void *__app_control_play_uri(struct stasis_app_control *control, return NULL; } - res = ast_control_streamfile(chan, file, fwd, rev, stop, pause, - restart, skipms, &offsetms); + res = ast_control_streamfile_lang(chan, file, fwd, rev, stop, pause, + restart, playback->skipms, playback->language, &offsetms); - if (res != 0) { - ast_log(LOG_WARNING, "%s: Playback failed for %s", - ast_channel_uniqueid(chan), playback->media); - } + playback_final_update(playback, offsetms, res, + ast_channel_uniqueid(chan)); return NULL; } @@ -221,11 +253,15 @@ static void playback_dtor(void *obj) struct stasis_app_playback *stasis_app_control_play_uri( struct stasis_app_control *control, const char *uri, - const char *language) + const char *language, int skipms, long offsetms) { RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup); char id[AST_UUID_STR_LEN]; + if (skipms < 0 || offsetms < 0) { + return NULL; + } + ast_debug(3, "%s: Sending play(%s) command\n", stasis_app_control_get_channel_id(control), uri); @@ -234,20 +270,26 @@ struct stasis_app_playback *stasis_app_control_play_uri( return NULL; } + if (skipms == 0) { + skipms = PLAYBACK_DEFAULT_SKIPMS; + } + 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; + playback->skipms = skipms; + playback->offsetms = offsetms; 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); + playback->state = STASIS_PLAYBACK_STATE_QUEUED; + playback_publish(playback); + /* A ref is kept in the playbacks container; no need to bump */ + stasis_app_send_command_async(control, play_uri, playback); + /* Although this should be bumped for the caller */ ao2_ref(playback, +1); return playback; } @@ -266,26 +308,152 @@ const char *stasis_app_playback_get_id( return control->id; } -struct ast_json *stasis_app_playback_find_by_id(const char *id) +struct stasis_app_playback *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); + 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)); + return ast_json_ref(json); } -int stasis_app_playback_control(struct stasis_app_playback *playback, - enum stasis_app_playback_media_control control) +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) { SCOPED_AO2LOCK(lock, playback); - ast_assert(0); /* TODO */ - return -1; + 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; } static int load_module(void) |