diff --git a/include/asterisk/app.h b/include/asterisk/app.h index 2089bced9dfa24cb2225db3c2d06d27d121db208..6cfb3800497d9f8276014b3e733fd1db23f4a446 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -652,6 +652,17 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in */ int ast_control_streamfile(struct ast_channel *chan, const char *file, const char *fwd, const char *rev, const char *stop, const char *pause, const char *restart, int skipms, long *offsetms); +/*! + * \brief Version of ast_control_streamfile() which allows the language of the + * media file to be specified. + * + * \retval 0 on success + * \retval Non-zero on failure + */ +int ast_control_streamfile_lang(struct ast_channel *chan, const char *file, + const char *fwd, const char *rev, const char *stop, const char *suspend, + const char *restart, int skipms, const char *lang, long *offsetms); + /*! * \brief Stream a file with fast forward, pause, reverse, restart. * \param chan diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 91373cfbda3250196fb4de41a164749d674797e2..b27183e7cb5e185fab28f5fff876ed98069977fe 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -1203,7 +1203,7 @@ int ast_queue_hangup(struct ast_channel *chan); int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause); /*! - * \brief Queue a control frame with payload + * \brief Queue a control frame without payload * * \param chan channel to queue frame onto * \param control type of control frame diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index 8d3fb097c3ec25ba317f8437aec4142cef132bb6..27b3502f33d2e62d1472fc4ab166dad1fcd259c9 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -174,6 +174,18 @@ struct ast_channel_snapshot *stasis_app_control_get_snapshot( void stasis_app_control_publish( struct stasis_app_control *control, struct stasis_message *message); +/*! + * \brief Queue a control frame without payload. + * + * \param control Control to publish to. + * \param frame_type type of control frame. + * + * \return zero on success + * \return non-zero on failure + */ +int stasis_app_control_queue_control(struct stasis_app_control *control, + enum ast_control_frame_type frame_type); + /*! * \brief Increment the res_stasis reference count. * diff --git a/include/asterisk/stasis_app_playback.h b/include/asterisk/stasis_app_playback.h index 598c6be5980a04cae0ce058338ee893d42818052..59c2aab497e2010ffd3d7cd2c79353926a05d78f 100644 --- a/include/asterisk/stasis_app_playback.h +++ b/include/asterisk/stasis_app_playback.h @@ -39,18 +39,34 @@ enum stasis_app_playback_state { STASIS_PLAYBACK_STATE_QUEUED, /*! The media is currently playing */ STASIS_PLAYBACK_STATE_PLAYING, + /*! The media is currently playing */ + STASIS_PLAYBACK_STATE_PAUSED, /*! The media has stopped playing */ STASIS_PLAYBACK_STATE_COMPLETE, + /*! The playback was canceled. */ + STASIS_PLAYBACK_STATE_CANCELED, + /*! The playback was stopped. */ + STASIS_PLAYBACK_STATE_STOPPED, + /*! Enum end sentinel. */ + STASIS_PLAYBACK_STATE_MAX, }; -enum stasis_app_playback_media_control { +/*! Valid operation for controlling a playback. */ +enum stasis_app_playback_media_operation { + /*! Stop the playback operation. */ STASIS_PLAYBACK_STOP, + /*! Restart the media from the beginning. */ + STASIS_PLAYBACK_RESTART, + /*! Pause playback. */ STASIS_PLAYBACK_PAUSE, - STASIS_PLAYBACK_PLAY, - STASIS_PLAYBACK_REWIND, - STASIS_PLAYBACK_FAST_FORWARD, - STASIS_PLAYBACK_SPEED_UP, - STASIS_PLAYBACK_SLOW_DOWN, + /*! Resume paused playback. */ + STASIS_PLAYBACK_UNPAUSE, + /*! Rewind playback. */ + STASIS_PLAYBACK_REVERSE, + /*! Fast forward playback. */ + STASIS_PLAYBACK_FORWARD, + /*! Enum end sentinel. */ + STASIS_PLAYBACK_MEDIA_OP_MAX, }; /*! @@ -62,12 +78,15 @@ enum stasis_app_playback_media_control { * * \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 skipms Number of milliseconds to skip for forward/reverse operations. + * \param offsetms Number of milliseconds to skip before playing. * \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); + const char *language, int skipms, long offsetms); /*! * \brief Gets the current state of a playback operation. @@ -95,18 +114,27 @@ const char *stasis_app_playback_get_id( * \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); +struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id); + +struct ast_json *stasis_app_playback_to_json( + const struct stasis_app_playback *playback); +enum stasis_playback_oper_results { + STASIS_PLAYBACK_OPER_OK, + STASIS_PLAYBACK_OPER_FAILED, + STASIS_PLAYBACK_OPER_NOT_PLAYING, +}; /*! * \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. + * \return \c STASIS_PLAYBACK_OPER_OK on success. + * \return \ref stasis_playback_oper_results indicating failure. */ -int stasis_app_playback_control(struct stasis_app_playback *playback, - enum stasis_app_playback_media_control control); +enum stasis_playback_oper_results stasis_app_playback_operation( + struct stasis_app_playback *playback, + enum stasis_app_playback_media_operation operation); /*! * \brief Message type for playback updates. The data is an diff --git a/main/app.c b/main/app.c index 1473c06cbe1eb914a4f9d9e17b60d31000e69a41..3001450e8215f5f780ff728bb836252fd1c47a8c 100644 --- a/main/app.c +++ b/main/app.c @@ -930,6 +930,7 @@ static int control_streamfile(struct ast_channel *chan, const char *restart, int skipms, long *offsetms, + const char *lang, ast_waitstream_fr_cb cb) { char *breaks = NULL; @@ -945,6 +946,9 @@ static int control_streamfile(struct ast_channel *chan, if (offsetms) { offset = *offsetms * 8; /* XXX Assumes 8kHz */ } + if (lang == NULL) { + lang = ast_channel_language(chan); + } if (stop) { blen += strlen(stop); @@ -982,7 +986,7 @@ static int control_streamfile(struct ast_channel *chan, for (;;) { ast_stopstream(chan); - res = ast_streamfile(chan, file, ast_channel_language(chan)); + res = ast_streamfile(chan, file, lang); if (!res) { if (pause_restart_point) { ast_seekstream(ast_channel_stream(chan), pause_restart_point, SEEK_SET); @@ -1093,7 +1097,7 @@ int ast_control_streamfile_w_cb(struct ast_channel *chan, long *offsetms, ast_waitstream_fr_cb cb) { - return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, cb); + return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL, cb); } int ast_control_streamfile(struct ast_channel *chan, const char *file, @@ -1101,7 +1105,14 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file, const char *stop, const char *suspend, const char *restart, int skipms, long *offsetms) { - return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL); + return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL, NULL); +} + +int ast_control_streamfile_lang(struct ast_channel *chan, const char *file, + const char *fwd, const char *rev, const char *stop, const char *suspend, + const char *restart, int skipms, const char *lang, long *offsetms) +{ + return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, lang, NULL); } int ast_play_and_wait(struct ast_channel *chan, const char *fn) diff --git a/res/res_stasis_http_channels.c b/res/res_stasis_http_channels.c index 89c0cf09c58f9ff3c1b24c76930372928396829c..f1fcb8e64c9359a7621e9119bc57b37c4495f16d 100644 --- a/res/res_stasis_http_channels.c +++ b/res/res_stasis_http_channels.c @@ -330,6 +330,12 @@ static void stasis_http_play_on_channel_cb( 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) { diff --git a/res/res_stasis_playback.c b/res/res_stasis_playback.c index 5f54a14b455e2ace5e41e258fafc767d0a5af3ae..842fc0e1d0e81fb393574d258632e4bf573d13da 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) diff --git a/res/stasis/control.c b/res/stasis/control.c index 1cc818616f005a2b32963532243345f32572bb98..1663dd8a6dbb7d158c43fe9ee1fb90b7b8b58234 100644 --- a/res/stasis/control.c +++ b/res/stasis/control.c @@ -174,6 +174,12 @@ void stasis_app_control_publish( stasis_publish(ast_channel_topic(control->channel), message); } +int stasis_app_control_queue_control(struct stasis_app_control *control, + enum ast_control_frame_type frame_type) +{ + return ast_queue_control(control->channel, frame_type); +} + int control_dispatch_all(struct stasis_app_control *control, struct ast_channel *chan) { diff --git a/res/stasis_http/resource_channels.c b/res/stasis_http/resource_channels.c index 4e404dc1d404d6325ce40418c51cde4d731d3b95..0b9c2a61a161776714ecfe6163a7284a1448d5b1 100644 --- a/res/stasis_http/resource_channels.c +++ b/res/stasis_http/resource_channels.c @@ -163,13 +163,28 @@ void stasis_http_play_on_channel(struct ast_variable *headers, return; } + if (args->skipms < 0) { + stasis_http_response_error( + response, 500, "Internal Server Error", + "skipms cannot be negative"); + return; + } + + if (args->offsetms < 0) { + stasis_http_response_error( + response, 500, "Internal Server Error", + "offsetms cannot be negative"); + return; + } + language = S_OR(args->lang, snapshot->language); - playback = stasis_app_control_play_uri(control, args->media, language); + playback = stasis_app_control_play_uri(control, args->media, language, + args->skipms, args->offsetms); if (!playback) { stasis_http_response_error( response, 500, "Internal Server Error", - "Failed to answer channel"); + "Failed to queue media for playback"); return; } diff --git a/res/stasis_http/resource_channels.h b/res/stasis_http/resource_channels.h index 8a35072f02f406175c1f86678cd674fbd194d9bf..983642d9ace34577d0bbcc3fc5cdadd05517db9f 100644 --- a/res/stasis_http/resource_channels.h +++ b/res/stasis_http/resource_channels.h @@ -200,8 +200,12 @@ struct ast_play_on_channel_args { const char *channel_id; /*! \brief Media's URI to play. */ const char *media; - /*! \brief For sounds, selects language for sound */ + /*! \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. diff --git a/res/stasis_http/resource_playback.c b/res/stasis_http/resource_playback.c index f016a0095cb9ae5273ca5041b6e2497e65eb28da..a9edc4e15c3605e492291f52f373840e4520b39a 100644 --- a/res/stasis_http/resource_playback.c +++ b/res/stasis_http/resource_playback.c @@ -34,7 +34,9 @@ void stasis_http_get_playback(struct ast_variable *headers, struct ast_get_playback_args *args, struct stasis_http_response *response) { - RAII_VAR(struct ast_json *, playback, NULL, ast_json_unref); + RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + playback = stasis_app_playback_find_by_id(args->playback_id); if (playback == NULL) { stasis_http_response_error(response, 404, "Not Found", @@ -42,13 +44,94 @@ void stasis_http_get_playback(struct ast_variable *headers, return; } - stasis_http_response_ok(response, ast_json_ref(playback)); + json = stasis_app_playback_to_json(playback); + if (json == NULL) { + stasis_http_response_error(response, 500, + "Internal Server Error", "Error building response"); + return; + } + + stasis_http_response_ok(response, ast_json_ref(json)); } -void stasis_http_stop_playback(struct ast_variable *headers, struct ast_stop_playback_args *args, struct stasis_http_response *response) +void stasis_http_stop_playback(struct ast_variable *headers, + struct ast_stop_playback_args *args, + struct stasis_http_response *response) { - ast_log(LOG_ERROR, "TODO: stasis_http_stop_playback\n"); + RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup); + enum stasis_playback_oper_results res; + + playback = stasis_app_playback_find_by_id(args->playback_id); + if (playback == NULL) { + stasis_http_response_error(response, 404, "Not Found", + "Playback not found"); + return; + } + + res = stasis_app_playback_operation(playback, STASIS_PLAYBACK_STOP); + + switch (res) { + case STASIS_PLAYBACK_OPER_OK: + stasis_http_response_no_content(response); + return; + case STASIS_PLAYBACK_OPER_FAILED: + stasis_http_response_error(response, 500, + "Internal Server Error", "Could not stop playback"); + return; + case STASIS_PLAYBACK_OPER_NOT_PLAYING: + /* Stop operation should be valid even when not playing */ + ast_assert(0); + stasis_http_response_error(response, 500, + "Internal Server Error", "Could not stop playback"); + return; + } } -void stasis_http_control_playback(struct ast_variable *headers, struct ast_control_playback_args *args, struct stasis_http_response *response) +void stasis_http_control_playback(struct ast_variable *headers, + struct ast_control_playback_args *args, + struct stasis_http_response *response) { - ast_log(LOG_ERROR, "TODO: stasis_http_control_playback\n"); + RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup); + enum stasis_app_playback_media_operation oper; + enum stasis_playback_oper_results res; + + if (strcmp(args->operation, "unpause") == 0) { + oper = STASIS_PLAYBACK_UNPAUSE; + } else if (strcmp(args->operation, "pause") == 0) { + oper = STASIS_PLAYBACK_PAUSE; + } else if (strcmp(args->operation, "restart") == 0) { + oper = STASIS_PLAYBACK_RESTART; + } else if (strcmp(args->operation, "reverse") == 0) { + oper = STASIS_PLAYBACK_REVERSE; + } else if (strcmp(args->operation, "forward") == 0) { + oper = STASIS_PLAYBACK_FORWARD; + } else { + stasis_http_response_error(response, 400, + "Bad Request", "Invalid operation %s", + args->operation); + return; + + } + + playback = stasis_app_playback_find_by_id(args->playback_id); + if (playback == NULL) { + stasis_http_response_error(response, 404, "Not Found", + "Playback not found"); + return; + } + + res = stasis_app_playback_operation(playback, oper); + + switch (res) { + case STASIS_PLAYBACK_OPER_OK: + stasis_http_response_no_content(response); + return; + case STASIS_PLAYBACK_OPER_FAILED: + stasis_http_response_error(response, 500, + "Internal Server Error", "Could not %s playback", + args->operation); + return; + case STASIS_PLAYBACK_OPER_NOT_PLAYING: + stasis_http_response_error(response, 409, "Conflict", + "Can only %s while media is playing", args->operation); + return; + } } diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json index 3b4d4d486c50c91cd2bcb50bac5b1b4ba7d88634..cb0332414b1f3e9ffcbe78cddc9143a75b73b88e 100644 --- a/rest-api/api-docs/channels.json +++ b/rest-api/api-docs/channels.json @@ -408,11 +408,28 @@ }, { "name": "lang", - "description": "For sounds, selects language for sound", + "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" + }, + { + "name": "skipms", + "description": "Number of milliseconds to skip for forward/reverse operations.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "int", + "defaultValue": 3000 } ], "errorResponses": [ diff --git a/rest-api/api-docs/playback.json b/rest-api/api-docs/playback.json index aa758781cdd760916d86540323ce169c77e48866..38ca5e1a7aafbb817661cc997fb510472e2fb820 100644 --- a/rest-api/api-docs/playback.json +++ b/rest-api/api-docs/playback.json @@ -73,16 +73,29 @@ "allowableValues": { "valueType": "LIST", "values": [ - "play", + "restart", "pause", - "rewind", - "fast-forward", - "speed-up", - "slow-down" + "unpause", + "reverse", + "forward" ] } } - ] + ], + "errorResponses": [ + { + "code": 400, + "reason": "The provided operation parameter was invalid" + }, + { + "code": 404, + "reason": "The playback cannot be found" + }, + { + "code": 409, + "reason": "The operation cannot be performed in the playback's current state" + } +] } ] }