mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-06 12:36:58 +00:00
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
This commit is contained in:
@@ -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);
|
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.
|
* \brief Stream a file with fast forward, pause, reverse, restart.
|
||||||
* \param chan
|
* \param chan
|
||||||
|
@@ -1203,7 +1203,7 @@ int ast_queue_hangup(struct ast_channel *chan);
|
|||||||
int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause);
|
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 chan channel to queue frame onto
|
||||||
* \param control type of control frame
|
* \param control type of control frame
|
||||||
|
@@ -174,6 +174,18 @@ struct ast_channel_snapshot *stasis_app_control_get_snapshot(
|
|||||||
void stasis_app_control_publish(
|
void stasis_app_control_publish(
|
||||||
struct stasis_app_control *control, struct stasis_message *message);
|
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.
|
* \brief Increment the res_stasis reference count.
|
||||||
*
|
*
|
||||||
|
@@ -39,18 +39,34 @@ enum stasis_app_playback_state {
|
|||||||
STASIS_PLAYBACK_STATE_QUEUED,
|
STASIS_PLAYBACK_STATE_QUEUED,
|
||||||
/*! The media is currently playing */
|
/*! The media is currently playing */
|
||||||
STASIS_PLAYBACK_STATE_PLAYING,
|
STASIS_PLAYBACK_STATE_PLAYING,
|
||||||
|
/*! The media is currently playing */
|
||||||
|
STASIS_PLAYBACK_STATE_PAUSED,
|
||||||
/*! The media has stopped playing */
|
/*! The media has stopped playing */
|
||||||
STASIS_PLAYBACK_STATE_COMPLETE,
|
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,
|
STASIS_PLAYBACK_STOP,
|
||||||
|
/*! Restart the media from the beginning. */
|
||||||
|
STASIS_PLAYBACK_RESTART,
|
||||||
|
/*! Pause playback. */
|
||||||
STASIS_PLAYBACK_PAUSE,
|
STASIS_PLAYBACK_PAUSE,
|
||||||
STASIS_PLAYBACK_PLAY,
|
/*! Resume paused playback. */
|
||||||
STASIS_PLAYBACK_REWIND,
|
STASIS_PLAYBACK_UNPAUSE,
|
||||||
STASIS_PLAYBACK_FAST_FORWARD,
|
/*! Rewind playback. */
|
||||||
STASIS_PLAYBACK_SPEED_UP,
|
STASIS_PLAYBACK_REVERSE,
|
||||||
STASIS_PLAYBACK_SLOW_DOWN,
|
/*! 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 control Control for \c res_stasis.
|
||||||
* \param file Base filename for the file to play.
|
* \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 Playback control object.
|
||||||
* \return \c NULL on error.
|
* \return \c NULL on error.
|
||||||
*/
|
*/
|
||||||
struct stasis_app_playback *stasis_app_control_play_uri(
|
struct stasis_app_playback *stasis_app_control_play_uri(
|
||||||
struct stasis_app_control *control, const char *file,
|
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.
|
* \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 Associated \ref stasis_app_playback object.
|
||||||
* \return \c NULL if \a id not found.
|
* \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.
|
* \brief Controls the media for a given playback operation.
|
||||||
*
|
*
|
||||||
* \param playback Playback control object.
|
* \param playback Playback control object.
|
||||||
* \param control Media control operation.
|
* \param control Media control operation.
|
||||||
* \return 0 on success
|
* \return \c STASIS_PLAYBACK_OPER_OK on success.
|
||||||
* \return non-zero on error.
|
* \return \ref stasis_playback_oper_results indicating failure.
|
||||||
*/
|
*/
|
||||||
int stasis_app_playback_control(struct stasis_app_playback *playback,
|
enum stasis_playback_oper_results stasis_app_playback_operation(
|
||||||
enum stasis_app_playback_media_control control);
|
struct stasis_app_playback *playback,
|
||||||
|
enum stasis_app_playback_media_operation operation);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Message type for playback updates. The data is an
|
* \brief Message type for playback updates. The data is an
|
||||||
|
17
main/app.c
17
main/app.c
@@ -930,6 +930,7 @@ static int control_streamfile(struct ast_channel *chan,
|
|||||||
const char *restart,
|
const char *restart,
|
||||||
int skipms,
|
int skipms,
|
||||||
long *offsetms,
|
long *offsetms,
|
||||||
|
const char *lang,
|
||||||
ast_waitstream_fr_cb cb)
|
ast_waitstream_fr_cb cb)
|
||||||
{
|
{
|
||||||
char *breaks = NULL;
|
char *breaks = NULL;
|
||||||
@@ -945,6 +946,9 @@ static int control_streamfile(struct ast_channel *chan,
|
|||||||
if (offsetms) {
|
if (offsetms) {
|
||||||
offset = *offsetms * 8; /* XXX Assumes 8kHz */
|
offset = *offsetms * 8; /* XXX Assumes 8kHz */
|
||||||
}
|
}
|
||||||
|
if (lang == NULL) {
|
||||||
|
lang = ast_channel_language(chan);
|
||||||
|
}
|
||||||
|
|
||||||
if (stop) {
|
if (stop) {
|
||||||
blen += strlen(stop);
|
blen += strlen(stop);
|
||||||
@@ -982,7 +986,7 @@ static int control_streamfile(struct ast_channel *chan,
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ast_stopstream(chan);
|
ast_stopstream(chan);
|
||||||
res = ast_streamfile(chan, file, ast_channel_language(chan));
|
res = ast_streamfile(chan, file, lang);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
if (pause_restart_point) {
|
if (pause_restart_point) {
|
||||||
ast_seekstream(ast_channel_stream(chan), pause_restart_point, SEEK_SET);
|
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,
|
long *offsetms,
|
||||||
ast_waitstream_fr_cb cb)
|
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,
|
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 *stop, const char *suspend,
|
||||||
const char *restart, int skipms, long *offsetms)
|
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)
|
int ast_play_and_wait(struct ast_channel *chan, const char *fn)
|
||||||
|
@@ -330,6 +330,12 @@ static void stasis_http_play_on_channel_cb(
|
|||||||
if (strcmp(i->name, "lang") == 0) {
|
if (strcmp(i->name, "lang") == 0) {
|
||||||
args.lang = (i->value);
|
args.lang = (i->value);
|
||||||
} else
|
} 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) {
|
for (i = path_vars; i; i = i->next) {
|
||||||
|
@@ -46,8 +46,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||||||
/*! Number of hash buckets for playback container. Keep it prime! */
|
/*! Number of hash buckets for playback container. Keep it prime! */
|
||||||
#define PLAYBACK_BUCKETS 127
|
#define PLAYBACK_BUCKETS 127
|
||||||
|
|
||||||
/*! Number of milliseconds of media to skip */
|
/*! Default number of milliseconds of media to skip */
|
||||||
#define PLAYBACK_SKIPMS 250
|
#define PLAYBACK_DEFAULT_SKIPMS 3000
|
||||||
|
|
||||||
#define SOUND_URI_SCHEME "sound:"
|
#define SOUND_URI_SCHEME "sound:"
|
||||||
#define RECORDING_URI_SCHEME "recording:"
|
#define RECORDING_URI_SCHEME "recording:"
|
||||||
@@ -64,10 +64,17 @@ struct stasis_app_playback {
|
|||||||
AST_STRING_FIELD(media); /*!< Playback media uri */
|
AST_STRING_FIELD(media); /*!< Playback media uri */
|
||||||
AST_STRING_FIELD(language); /*!< Preferred language */
|
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 */
|
/*! Control object for the channel we're playing back to */
|
||||||
struct stasis_app_control *control;
|
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)
|
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";
|
return "queued";
|
||||||
case STASIS_PLAYBACK_STATE_PLAYING:
|
case STASIS_PLAYBACK_STATE_PLAYING:
|
||||||
return "playing";
|
return "playing";
|
||||||
|
case STASIS_PLAYBACK_STATE_PAUSED:
|
||||||
|
return "paused";
|
||||||
|
case STASIS_PLAYBACK_STATE_STOPPED:
|
||||||
case STASIS_PLAYBACK_STATE_COMPLETE:
|
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";
|
return "done";
|
||||||
|
case STASIS_PLAYBACK_STATE_MAX:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "?";
|
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)
|
static void playback_publish(struct stasis_app_playback *playback)
|
||||||
{
|
{
|
||||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
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);
|
ast_assert(playback != NULL);
|
||||||
|
|
||||||
json = playback_to_json(playback);
|
json = stasis_app_playback_to_json(playback);
|
||||||
if (json == NULL) {
|
if (json == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -144,24 +142,54 @@ static void playback_publish(struct stasis_app_playback *playback)
|
|||||||
stasis_app_control_publish(playback->control, message);
|
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)
|
static void playback_cleanup(struct stasis_app_playback *playback)
|
||||||
{
|
{
|
||||||
playback_set_state(playback, STASIS_PLAYBACK_STATE_COMPLETE);
|
|
||||||
|
|
||||||
ao2_unlink_flags(playbacks, playback,
|
ao2_unlink_flags(playbacks, playback,
|
||||||
OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
|
OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *__app_control_play_uri(struct stasis_app_control *control,
|
static int playback_first_update(struct stasis_app_playback *playback,
|
||||||
|
const char *uniqueid)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
SCOPED_AO2LOCK(lock, playback);
|
||||||
|
|
||||||
|
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_final_update(struct stasis_app_playback *playback,
|
||||||
|
long playedms, int res, const char *uniqueid)
|
||||||
|
{
|
||||||
|
SCOPED_AO2LOCK(lock, playback);
|
||||||
|
|
||||||
|
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 *play_uri(struct stasis_app_control *control,
|
||||||
struct ast_channel *chan, void *data)
|
struct ast_channel *chan, void *data)
|
||||||
{
|
{
|
||||||
RAII_VAR(struct stasis_app_playback *, playback, NULL,
|
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);
|
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||||
const char *file;
|
const char *file;
|
||||||
int res;
|
int res;
|
||||||
|
long offsetms;
|
||||||
|
|
||||||
/* Even though these local variables look fairly pointless, the avoid
|
/* Even though these local variables look fairly pointless, the avoid
|
||||||
* having a bunch of NULL's passed directly into
|
* having a bunch of NULL's passed directly into
|
||||||
* ast_control_streamfile() */
|
* ast_control_streamfile() */
|
||||||
@@ -177,13 +207,17 @@ static void *__app_control_play_uri(struct stasis_app_control *control,
|
|||||||
const char *stop = NULL;
|
const char *stop = NULL;
|
||||||
const char *pause = NULL;
|
const char *pause = NULL;
|
||||||
const char *restart = NULL;
|
const char *restart = NULL;
|
||||||
int skipms = PLAYBACK_SKIPMS;
|
|
||||||
long offsetms = 0;
|
|
||||||
|
|
||||||
playback = data;
|
playback = data;
|
||||||
ast_assert(playback != NULL);
|
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) {
|
if (ast_channel_state(chan) != AST_STATE_UP) {
|
||||||
ast_answer(chan);
|
ast_answer(chan);
|
||||||
@@ -201,13 +235,11 @@ static void *__app_control_play_uri(struct stasis_app_control *control,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = ast_control_streamfile(chan, file, fwd, rev, stop, pause,
|
res = ast_control_streamfile_lang(chan, file, fwd, rev, stop, pause,
|
||||||
restart, skipms, &offsetms);
|
restart, playback->skipms, playback->language, &offsetms);
|
||||||
|
|
||||||
if (res != 0) {
|
playback_final_update(playback, offsetms, res,
|
||||||
ast_log(LOG_WARNING, "%s: Playback failed for %s",
|
ast_channel_uniqueid(chan));
|
||||||
ast_channel_uniqueid(chan), playback->media);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -221,11 +253,15 @@ static void playback_dtor(void *obj)
|
|||||||
|
|
||||||
struct stasis_app_playback *stasis_app_control_play_uri(
|
struct stasis_app_playback *stasis_app_control_play_uri(
|
||||||
struct stasis_app_control *control, const char *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);
|
RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
|
||||||
char id[AST_UUID_STR_LEN];
|
char id[AST_UUID_STR_LEN];
|
||||||
|
|
||||||
|
if (skipms < 0 || offsetms < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
ast_debug(3, "%s: Sending play(%s) command\n",
|
ast_debug(3, "%s: Sending play(%s) command\n",
|
||||||
stasis_app_control_get_channel_id(control), uri);
|
stasis_app_control_get_channel_id(control), uri);
|
||||||
|
|
||||||
@@ -234,20 +270,26 @@ struct stasis_app_playback *stasis_app_control_play_uri(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skipms == 0) {
|
||||||
|
skipms = PLAYBACK_DEFAULT_SKIPMS;
|
||||||
|
}
|
||||||
|
|
||||||
ast_uuid_generate_str(id, sizeof(id));
|
ast_uuid_generate_str(id, sizeof(id));
|
||||||
ast_string_field_set(playback, id, id);
|
ast_string_field_set(playback, id, id);
|
||||||
ast_string_field_set(playback, media, uri);
|
ast_string_field_set(playback, media, uri);
|
||||||
ast_string_field_set(playback, language, language);
|
ast_string_field_set(playback, language, language);
|
||||||
playback->control = control;
|
playback->control = control;
|
||||||
|
playback->skipms = skipms;
|
||||||
|
playback->offsetms = offsetms;
|
||||||
ao2_link(playbacks, playback);
|
ao2_link(playbacks, playback);
|
||||||
|
|
||||||
playback_set_state(playback, STASIS_PLAYBACK_STATE_QUEUED);
|
playback->state = STASIS_PLAYBACK_STATE_QUEUED;
|
||||||
|
playback_publish(playback);
|
||||||
ao2_ref(playback, +1);
|
|
||||||
stasis_app_send_command_async(
|
|
||||||
control, __app_control_play_uri, 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);
|
ao2_ref(playback, +1);
|
||||||
return playback;
|
return playback;
|
||||||
}
|
}
|
||||||
@@ -266,26 +308,152 @@ const char *stasis_app_playback_get_id(
|
|||||||
return control->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 stasis_app_playback *, playback, NULL, ao2_cleanup);
|
||||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
|
||||||
|
|
||||||
playback = ao2_find(playbacks, id, OBJ_KEY);
|
playback = ao2_find(playbacks, id, OBJ_KEY);
|
||||||
if (playback == NULL) {
|
if (playback == NULL) {
|
||||||
return 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);
|
return ast_json_ref(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
int stasis_app_playback_control(struct stasis_app_playback *playback,
|
typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
|
||||||
enum stasis_app_playback_media_control control)
|
|
||||||
|
static int playback_noop(struct stasis_app_playback *playback)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int playback_cancel(struct stasis_app_playback *playback)
|
||||||
{
|
{
|
||||||
SCOPED_AO2LOCK(lock, playback);
|
SCOPED_AO2LOCK(lock, playback);
|
||||||
ast_assert(0); /* TODO */
|
playback->state = STASIS_PLAYBACK_STATE_CANCELED;
|
||||||
return -1;
|
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);
|
||||||
|
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)
|
static int load_module(void)
|
||||||
|
@@ -174,6 +174,12 @@ void stasis_app_control_publish(
|
|||||||
stasis_publish(ast_channel_topic(control->channel), message);
|
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,
|
int control_dispatch_all(struct stasis_app_control *control,
|
||||||
struct ast_channel *chan)
|
struct ast_channel *chan)
|
||||||
{
|
{
|
||||||
|
@@ -163,13 +163,28 @@ void stasis_http_play_on_channel(struct ast_variable *headers,
|
|||||||
return;
|
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);
|
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) {
|
if (!playback) {
|
||||||
stasis_http_response_error(
|
stasis_http_response_error(
|
||||||
response, 500, "Internal Server Error",
|
response, 500, "Internal Server Error",
|
||||||
"Failed to answer channel");
|
"Failed to queue media for playback");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -200,8 +200,12 @@ struct ast_play_on_channel_args {
|
|||||||
const char *channel_id;
|
const char *channel_id;
|
||||||
/*! \brief Media's URI to play. */
|
/*! \brief Media's URI to play. */
|
||||||
const char *media;
|
const char *media;
|
||||||
/*! \brief For sounds, selects language for sound */
|
/*! \brief For sounds, selects language for sound. */
|
||||||
const char *lang;
|
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.
|
* \brief Start playback of media.
|
||||||
|
@@ -34,7 +34,9 @@ void stasis_http_get_playback(struct ast_variable *headers,
|
|||||||
struct ast_get_playback_args *args,
|
struct ast_get_playback_args *args,
|
||||||
struct stasis_http_response *response)
|
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);
|
playback = stasis_app_playback_find_by_id(args->playback_id);
|
||||||
if (playback == NULL) {
|
if (playback == NULL) {
|
||||||
stasis_http_response_error(response, 404, "Not Found",
|
stasis_http_response_error(response, 404, "Not Found",
|
||||||
@@ -42,13 +44,94 @@ void stasis_http_get_playback(struct ast_variable *headers,
|
|||||||
return;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -408,11 +408,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "lang",
|
"name": "lang",
|
||||||
"description": "For sounds, selects language for sound",
|
"description": "For sounds, selects language for sound.",
|
||||||
"paramType": "query",
|
"paramType": "query",
|
||||||
"required": false,
|
"required": false,
|
||||||
"allowMultiple": false,
|
"allowMultiple": false,
|
||||||
"dataType": "string"
|
"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": [
|
"errorResponses": [
|
||||||
|
@@ -73,16 +73,29 @@
|
|||||||
"allowableValues": {
|
"allowableValues": {
|
||||||
"valueType": "LIST",
|
"valueType": "LIST",
|
||||||
"values": [
|
"values": [
|
||||||
"play",
|
"restart",
|
||||||
"pause",
|
"pause",
|
||||||
"rewind",
|
"unpause",
|
||||||
"fast-forward",
|
"reverse",
|
||||||
"speed-up",
|
"forward"
|
||||||
"slow-down"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user