Merge "ARI: Add the ability to play multiple media URIs in a single operation"
This commit is contained in:
commit
e2fc83af50
15 changed files with 643 additions and 126 deletions
9
CHANGES
9
CHANGES
|
@ -23,6 +23,15 @@ ARI
|
|||
* To complement the "create" method, a "dial" method has been added to the channels
|
||||
resource in order to place a call to a created channel.
|
||||
|
||||
* All operations that initiate playback of media on a resource now support
|
||||
a list of media URIs. The list of URIs are played in the order they are
|
||||
presented to the resource. A new event, "PlaybackContinuing", is raised when
|
||||
a media URI finishes but before the next media URI starts. When a list is
|
||||
played, the "Playback" model will contain the optional attribute
|
||||
"next_media_uri", which specifies the next media URI in the list to be played
|
||||
back to the resource. The "PlaybackFinished" event is raised when all media
|
||||
URIs are done.
|
||||
|
||||
Applications
|
||||
------------------
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ enum stasis_app_playback_state {
|
|||
STASIS_PLAYBACK_STATE_PLAYING,
|
||||
/*! The media is currently playing */
|
||||
STASIS_PLAYBACK_STATE_PAUSED,
|
||||
/*! The media is transitioning to the next in the list */
|
||||
STASIS_PLAYBACK_STATE_CONTINUING,
|
||||
/*! The media has stopped playing */
|
||||
STASIS_PLAYBACK_STATE_COMPLETE,
|
||||
/*! The playback was canceled. */
|
||||
|
@ -84,7 +86,8 @@ enum stasis_app_playback_target_type {
|
|||
* available codecs for the channel.
|
||||
*
|
||||
* \param control Control for \c res_stasis.
|
||||
* \param file Base filename for the file to play.
|
||||
* \param media Array of const char * media files to play.
|
||||
* \param media_count The number of media files in \c media.
|
||||
* \param language Selects the file based on language.
|
||||
* \param target_id ID of the target bridge or channel.
|
||||
* \param target_type What the target type is
|
||||
|
@ -95,8 +98,8 @@ enum stasis_app_playback_target_type {
|
|||
* \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 *target_id,
|
||||
struct stasis_app_control *control, const char **media,
|
||||
size_t media_count, const char *language, const char *target_id,
|
||||
enum stasis_app_playback_target_type target_type,
|
||||
int skipms, long offsetms, const char *id);
|
||||
|
||||
|
@ -128,6 +131,14 @@ const char *stasis_app_playback_get_id(
|
|||
*/
|
||||
struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id);
|
||||
|
||||
/*!
|
||||
* \brief Convert a playback to its JSON representation
|
||||
*
|
||||
* \param playback The playback object to convert to JSON
|
||||
*
|
||||
* \retval \c NULL on error
|
||||
* \retval A JSON object on success
|
||||
*/
|
||||
struct ast_json *stasis_app_playback_to_json(
|
||||
const struct stasis_app_playback *playback);
|
||||
|
||||
|
|
|
@ -1744,6 +1744,15 @@ int ast_ari_validate_playback(struct ast_json *json)
|
|||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("next_media_uri", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI Playback field next_media_uri failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_state = 1;
|
||||
|
@ -4741,6 +4750,9 @@ int ast_ari_validate_event(struct ast_json *json)
|
|||
if (strcmp("PeerStatusChange", discriminator) == 0) {
|
||||
return ast_ari_validate_peer_status_change(json);
|
||||
} else
|
||||
if (strcmp("PlaybackContinuing", discriminator) == 0) {
|
||||
return ast_ari_validate_playback_continuing(json);
|
||||
} else
|
||||
if (strcmp("PlaybackFinished", discriminator) == 0) {
|
||||
return ast_ari_validate_playback_finished(json);
|
||||
} else
|
||||
|
@ -4930,6 +4942,9 @@ int ast_ari_validate_message(struct ast_json *json)
|
|||
if (strcmp("PeerStatusChange", discriminator) == 0) {
|
||||
return ast_ari_validate_peer_status_change(json);
|
||||
} else
|
||||
if (strcmp("PlaybackContinuing", discriminator) == 0) {
|
||||
return ast_ari_validate_playback_continuing(json);
|
||||
} else
|
||||
if (strcmp("PlaybackFinished", discriminator) == 0) {
|
||||
return ast_ari_validate_playback_finished(json);
|
||||
} else
|
||||
|
@ -5216,6 +5231,85 @@ ari_validator ast_ari_validate_peer_status_change_fn(void)
|
|||
return ast_ari_validate_peer_status_change;
|
||||
}
|
||||
|
||||
int ast_ari_validate_playback_continuing(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
struct ast_json_iter *iter;
|
||||
int has_type = 0;
|
||||
int has_application = 0;
|
||||
int has_playback = 0;
|
||||
|
||||
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
|
||||
if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_type = 1;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI PlaybackContinuing field type failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_application = 1;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI PlaybackContinuing field application failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
prop_is_valid = ast_ari_validate_date(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI PlaybackContinuing field timestamp failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("playback", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_playback = 1;
|
||||
prop_is_valid = ast_ari_validate_playback(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI PlaybackContinuing field playback failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
{
|
||||
ast_log(LOG_ERROR,
|
||||
"ARI PlaybackContinuing has undocumented field %s\n",
|
||||
ast_json_object_iter_key(iter));
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_type) {
|
||||
ast_log(LOG_ERROR, "ARI PlaybackContinuing missing required field type\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!has_application) {
|
||||
ast_log(LOG_ERROR, "ARI PlaybackContinuing missing required field application\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!has_playback) {
|
||||
ast_log(LOG_ERROR, "ARI PlaybackContinuing missing required field playback\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ari_validator ast_ari_validate_playback_continuing_fn(void)
|
||||
{
|
||||
return ast_ari_validate_playback_continuing;
|
||||
}
|
||||
|
||||
int ast_ari_validate_playback_finished(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
|
|
|
@ -1186,6 +1186,24 @@ int ast_ari_validate_peer_status_change(struct ast_json *json);
|
|||
*/
|
||||
ari_validator ast_ari_validate_peer_status_change_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for PlaybackContinuing.
|
||||
*
|
||||
* Event showing the continuation of a media playback operation from one media URI to the next in the list.
|
||||
*
|
||||
* \param json JSON object to validate.
|
||||
* \returns True (non-zero) if valid.
|
||||
* \returns False (zero) if invalid.
|
||||
*/
|
||||
int ast_ari_validate_playback_continuing(struct ast_json *json);
|
||||
|
||||
/*!
|
||||
* \brief Function pointer to ast_ari_validate_playback_continuing().
|
||||
*
|
||||
* See \ref ast_ari_model_validators.h for more details.
|
||||
*/
|
||||
ari_validator ast_ari_validate_playback_continuing_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for PlaybackFinished.
|
||||
*
|
||||
|
@ -1457,6 +1475,7 @@ ari_validator ast_ari_validate_application_fn(void);
|
|||
* - id: string (required)
|
||||
* - language: string
|
||||
* - media_uri: string (required)
|
||||
* - next_media_uri: string
|
||||
* - state: string (required)
|
||||
* - target_uri: string (required)
|
||||
* DeviceState
|
||||
|
@ -1670,6 +1689,11 @@ ari_validator ast_ari_validate_application_fn(void);
|
|||
* - timestamp: Date
|
||||
* - endpoint: Endpoint (required)
|
||||
* - peer: Peer (required)
|
||||
* PlaybackContinuing
|
||||
* - type: string (required)
|
||||
* - application: string (required)
|
||||
* - timestamp: Date
|
||||
* - playback: Playback (required)
|
||||
* PlaybackFinished
|
||||
* - type: string (required)
|
||||
* - application: string (required)
|
||||
|
|
|
@ -332,7 +332,8 @@ static struct ast_channel *prepare_bridge_media_channel(const char *type)
|
|||
* \brief Performs common setup for a bridge playback operation
|
||||
* with both new controls and when existing controls are found.
|
||||
*
|
||||
* \param args_media media string split from arguments
|
||||
* \param args_media medias to play
|
||||
* \param args_media_count number of media items in \c media
|
||||
* \param args_lang language string split from arguments
|
||||
* \param args_offset_ms milliseconds offset split from arguments
|
||||
* \param args_playback_id string to use for playback split from
|
||||
|
@ -346,7 +347,8 @@ static struct ast_channel *prepare_bridge_media_channel(const char *type)
|
|||
* \retval -1 operation failed
|
||||
* \retval operation was successful
|
||||
*/
|
||||
static int ari_bridges_play_helper(const char *args_media,
|
||||
static int ari_bridges_play_helper(const char **args_media,
|
||||
size_t args_media_count,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
|
@ -371,8 +373,8 @@ static int ari_bridges_play_helper(const char *args_media,
|
|||
|
||||
language = S_OR(args_lang, snapshot->language);
|
||||
|
||||
playback = stasis_app_control_play_uri(control, args_media, language,
|
||||
bridge->uniqueid, STASIS_PLAYBACK_TARGET_BRIDGE, args_skipms,
|
||||
playback = stasis_app_control_play_uri(control, args_media, args_media_count,
|
||||
language, bridge->uniqueid, STASIS_PLAYBACK_TARGET_BRIDGE, args_skipms,
|
||||
args_offset_ms, args_playback_id);
|
||||
|
||||
if (!playback) {
|
||||
|
@ -396,7 +398,8 @@ static int ari_bridges_play_helper(const char *args_media,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void ari_bridges_play_new(const char *args_media,
|
||||
static void ari_bridges_play_new(const char **args_media,
|
||||
size_t args_media_count,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
|
@ -449,9 +452,9 @@ static void ari_bridges_play_new(const char *args_media,
|
|||
}
|
||||
|
||||
ao2_lock(control);
|
||||
if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response, bridge, control,
|
||||
&json, &playback_url)) {
|
||||
if (ari_bridges_play_helper(args_media, args_media_count, args_lang,
|
||||
args_offset_ms, args_skipms, args_playback_id, response, bridge,
|
||||
control, &json, &playback_url)) {
|
||||
ao2_unlock(control);
|
||||
return;
|
||||
}
|
||||
|
@ -497,7 +500,8 @@ enum play_found_result {
|
|||
* \brief Performs common setup for a bridge playback operation
|
||||
* with both new controls and when existing controls are found.
|
||||
*
|
||||
* \param args_media media string split from arguments
|
||||
* \param args_media medias to play
|
||||
* \param args_media_count number of media items in \c media
|
||||
* \param args_lang language string split from arguments
|
||||
* \param args_offset_ms milliseconds offset split from arguments
|
||||
* \param args_playback_id string to use for playback split from
|
||||
|
@ -511,7 +515,8 @@ enum play_found_result {
|
|||
* \retval PLAY_FOUND_CHANNEL_UNAVAILABLE The operation failed because
|
||||
* the channel requested to playback with is breaking down.
|
||||
*/
|
||||
static enum play_found_result ari_bridges_play_found(const char *args_media,
|
||||
static enum play_found_result ari_bridges_play_found(const char **args_media,
|
||||
size_t args_media_count,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
|
@ -537,9 +542,9 @@ static enum play_found_result ari_bridges_play_found(const char *args_media,
|
|||
return PLAY_FOUND_CHANNEL_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response, bridge, control,
|
||||
&json, &playback_url)) {
|
||||
if (ari_bridges_play_helper(args_media, args_media_count,
|
||||
args_lang, args_offset_ms, args_skipms, args_playback_id,
|
||||
response, bridge, control, &json, &playback_url)) {
|
||||
ao2_unlock(control);
|
||||
return PLAY_FOUND_FAILURE;
|
||||
}
|
||||
|
@ -551,7 +556,8 @@ static enum play_found_result ari_bridges_play_found(const char *args_media,
|
|||
|
||||
static void ari_bridges_handle_play(
|
||||
const char *args_bridge_id,
|
||||
const char *args_media,
|
||||
const char **args_media,
|
||||
size_t args_media_count,
|
||||
const char *args_lang,
|
||||
int args_offset_ms,
|
||||
int args_skipms,
|
||||
|
@ -574,15 +580,15 @@ static void ari_bridges_handle_play(
|
|||
* that will work or else there isn't a channel for this bridge anymore,
|
||||
* in which case we'll revert to ari_bridges_play_new.
|
||||
*/
|
||||
if (ari_bridges_play_found(args_media, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response,bridge,
|
||||
if (ari_bridges_play_found(args_media, args_media_count, args_lang,
|
||||
args_offset_ms, args_skipms, args_playback_id, response,bridge,
|
||||
play_channel) == PLAY_FOUND_CHANNEL_UNAVAILABLE) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ari_bridges_play_new(args_media, args_lang, args_offset_ms,
|
||||
ari_bridges_play_new(args_media, args_media_count, args_lang, args_offset_ms,
|
||||
args_skipms, args_playback_id, response, bridge);
|
||||
}
|
||||
|
||||
|
@ -593,6 +599,7 @@ void ast_ari_bridges_play(struct ast_variable *headers,
|
|||
{
|
||||
ari_bridges_handle_play(args->bridge_id,
|
||||
args->media,
|
||||
args->media_count,
|
||||
args->lang,
|
||||
args->offsetms,
|
||||
args->skipms,
|
||||
|
@ -606,6 +613,7 @@ void ast_ari_bridges_play_with_id(struct ast_variable *headers,
|
|||
{
|
||||
ari_bridges_handle_play(args->bridge_id,
|
||||
args->media,
|
||||
args->media_count,
|
||||
args->lang,
|
||||
args->offsetms,
|
||||
args->skipms,
|
||||
|
|
|
@ -245,11 +245,15 @@ void ast_ari_bridges_stop_moh(struct ast_variable *headers, struct ast_ari_bridg
|
|||
struct ast_ari_bridges_play_args {
|
||||
/*! Bridge's id */
|
||||
const char *bridge_id;
|
||||
/*! Media's URI to play. */
|
||||
const char *media;
|
||||
/*! Array of Media URIs to play. */
|
||||
const char **media;
|
||||
/*! Length of media array. */
|
||||
size_t media_count;
|
||||
/*! Parsing context for media. */
|
||||
char *media_parse;
|
||||
/*! For sounds, selects language for sound. */
|
||||
const char *lang;
|
||||
/*! Number of media to skip before playing. */
|
||||
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
||||
int offsetms;
|
||||
/*! Number of milliseconds to skip for forward/reverse operations. */
|
||||
int skipms;
|
||||
|
@ -283,11 +287,15 @@ struct ast_ari_bridges_play_with_id_args {
|
|||
const char *bridge_id;
|
||||
/*! Playback ID. */
|
||||
const char *playback_id;
|
||||
/*! Media's URI to play. */
|
||||
const char *media;
|
||||
/*! Array of Media URIs to play. */
|
||||
const char **media;
|
||||
/*! Length of media array. */
|
||||
size_t media_count;
|
||||
/*! Parsing context for media. */
|
||||
char *media_parse;
|
||||
/*! For sounds, selects language for sound. */
|
||||
const char *lang;
|
||||
/*! Number of media to skip before playing. */
|
||||
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
||||
int offsetms;
|
||||
/*! Number of milliseconds to skip for forward/reverse operations. */
|
||||
int skipms;
|
||||
|
|
|
@ -469,7 +469,8 @@ void ast_ari_channels_stop_silence(struct ast_variable *headers,
|
|||
|
||||
static void ari_channels_handle_play(
|
||||
const char *args_channel_id,
|
||||
const char *args_media,
|
||||
const char **args_media,
|
||||
size_t args_media_count,
|
||||
const char *args_lang,
|
||||
int args_offsetms,
|
||||
int args_skipms,
|
||||
|
@ -515,7 +516,7 @@ static void ari_channels_handle_play(
|
|||
|
||||
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, args_media_count, language,
|
||||
args_channel_id, STASIS_PLAYBACK_TARGET_CHANNEL, args_skipms, args_offsetms, args_playback_id);
|
||||
if (!playback) {
|
||||
ast_ari_response_error(
|
||||
|
@ -551,6 +552,7 @@ void ast_ari_channels_play(struct ast_variable *headers,
|
|||
ari_channels_handle_play(
|
||||
args->channel_id,
|
||||
args->media,
|
||||
args->media_count,
|
||||
args->lang,
|
||||
args->offsetms,
|
||||
args->skipms,
|
||||
|
@ -565,6 +567,7 @@ void ast_ari_channels_play_with_id(struct ast_variable *headers,
|
|||
ari_channels_handle_play(
|
||||
args->channel_id,
|
||||
args->media,
|
||||
args->media_count,
|
||||
args->lang,
|
||||
args->offsetms,
|
||||
args->skipms,
|
||||
|
|
|
@ -505,11 +505,15 @@ void ast_ari_channels_stop_silence(struct ast_variable *headers, struct ast_ari_
|
|||
struct ast_ari_channels_play_args {
|
||||
/*! Channel's id */
|
||||
const char *channel_id;
|
||||
/*! Media's URI to play. */
|
||||
const char *media;
|
||||
/*! Array of Media URIs to play. */
|
||||
const char **media;
|
||||
/*! Length of media array. */
|
||||
size_t media_count;
|
||||
/*! Parsing context for media. */
|
||||
char *media_parse;
|
||||
/*! For sounds, selects language for sound. */
|
||||
const char *lang;
|
||||
/*! Number of media to skip before playing. */
|
||||
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
||||
int offsetms;
|
||||
/*! Number of milliseconds to skip for forward/reverse operations. */
|
||||
int skipms;
|
||||
|
@ -543,11 +547,15 @@ struct ast_ari_channels_play_with_id_args {
|
|||
const char *channel_id;
|
||||
/*! Playback ID. */
|
||||
const char *playback_id;
|
||||
/*! Media's URI to play. */
|
||||
const char *media;
|
||||
/*! Array of Media URIs to play. */
|
||||
const char **media;
|
||||
/*! Length of media array. */
|
||||
size_t media_count;
|
||||
/*! Parsing context for media. */
|
||||
char *media_parse;
|
||||
/*! For sounds, selects language for sound. */
|
||||
const char *lang;
|
||||
/*! Number of media to skip before playing. */
|
||||
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
||||
int offsetms;
|
||||
/*! Number of milliseconds to skip for forward/reverse operations. */
|
||||
int skipms;
|
||||
|
|
|
@ -935,7 +935,32 @@ int ast_ari_bridges_play_parse_body(
|
|||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "media");
|
||||
if (field) {
|
||||
args->media = ast_json_string_get(field);
|
||||
/* If they were silly enough to both pass in a query param and a
|
||||
* JSON body, free up the query value.
|
||||
*/
|
||||
ast_free(args->media);
|
||||
if (ast_json_typeof(field) == AST_JSON_ARRAY) {
|
||||
/* Multiple param passed as array */
|
||||
size_t i;
|
||||
args->media_count = ast_json_array_size(field);
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < args->media_count; ++i) {
|
||||
args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
|
||||
}
|
||||
} else {
|
||||
/* Multiple param passed as single value */
|
||||
args->media_count = 1;
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
args->media[0] = ast_json_string_get(field);
|
||||
}
|
||||
}
|
||||
field = ast_json_object_get(body, "lang");
|
||||
if (field) {
|
||||
|
@ -978,7 +1003,47 @@ static void ast_ari_bridges_play_cb(
|
|||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "media") == 0) {
|
||||
args.media = (i->value);
|
||||
/* Parse comma separated list */
|
||||
char *vals[MAX_VALS];
|
||||
size_t j;
|
||||
|
||||
args.media_parse = ast_strdup(i->value);
|
||||
if (!args.media_parse) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (strlen(args.media_parse) == 0) {
|
||||
/* ast_app_separate_args can't handle "" */
|
||||
args.media_count = 1;
|
||||
vals[0] = args.media_parse;
|
||||
} else {
|
||||
args.media_count = ast_app_separate_args(
|
||||
args.media_parse, ',', vals,
|
||||
ARRAY_LEN(vals));
|
||||
}
|
||||
|
||||
if (args.media_count == 0) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (args.media_count >= MAX_VALS) {
|
||||
ast_ari_response_error(response, 400,
|
||||
"Bad Request",
|
||||
"Too many values for media");
|
||||
goto fin;
|
||||
}
|
||||
|
||||
args.media = ast_malloc(sizeof(*args.media) * args.media_count);
|
||||
if (!args.media) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
for (j = 0; j < args.media_count; ++j) {
|
||||
args.media[j] = (vals[j]);
|
||||
}
|
||||
} else
|
||||
if (strcmp(i->name, "lang") == 0) {
|
||||
args.lang = (i->value);
|
||||
|
@ -1051,6 +1116,8 @@ static void ast_ari_bridges_play_cb(
|
|||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
ast_free(args.media_parse);
|
||||
ast_free(args.media);
|
||||
return;
|
||||
}
|
||||
int ast_ari_bridges_play_with_id_parse_body(
|
||||
|
@ -1061,7 +1128,32 @@ int ast_ari_bridges_play_with_id_parse_body(
|
|||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "media");
|
||||
if (field) {
|
||||
args->media = ast_json_string_get(field);
|
||||
/* If they were silly enough to both pass in a query param and a
|
||||
* JSON body, free up the query value.
|
||||
*/
|
||||
ast_free(args->media);
|
||||
if (ast_json_typeof(field) == AST_JSON_ARRAY) {
|
||||
/* Multiple param passed as array */
|
||||
size_t i;
|
||||
args->media_count = ast_json_array_size(field);
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < args->media_count; ++i) {
|
||||
args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
|
||||
}
|
||||
} else {
|
||||
/* Multiple param passed as single value */
|
||||
args->media_count = 1;
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
args->media[0] = ast_json_string_get(field);
|
||||
}
|
||||
}
|
||||
field = ast_json_object_get(body, "lang");
|
||||
if (field) {
|
||||
|
@ -1100,7 +1192,47 @@ static void ast_ari_bridges_play_with_id_cb(
|
|||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "media") == 0) {
|
||||
args.media = (i->value);
|
||||
/* Parse comma separated list */
|
||||
char *vals[MAX_VALS];
|
||||
size_t j;
|
||||
|
||||
args.media_parse = ast_strdup(i->value);
|
||||
if (!args.media_parse) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (strlen(args.media_parse) == 0) {
|
||||
/* ast_app_separate_args can't handle "" */
|
||||
args.media_count = 1;
|
||||
vals[0] = args.media_parse;
|
||||
} else {
|
||||
args.media_count = ast_app_separate_args(
|
||||
args.media_parse, ',', vals,
|
||||
ARRAY_LEN(vals));
|
||||
}
|
||||
|
||||
if (args.media_count == 0) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (args.media_count >= MAX_VALS) {
|
||||
ast_ari_response_error(response, 400,
|
||||
"Bad Request",
|
||||
"Too many values for media");
|
||||
goto fin;
|
||||
}
|
||||
|
||||
args.media = ast_malloc(sizeof(*args.media) * args.media_count);
|
||||
if (!args.media) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
for (j = 0; j < args.media_count; ++j) {
|
||||
args.media[j] = (vals[j]);
|
||||
}
|
||||
} else
|
||||
if (strcmp(i->name, "lang") == 0) {
|
||||
args.lang = (i->value);
|
||||
|
@ -1173,6 +1305,8 @@ static void ast_ari_bridges_play_with_id_cb(
|
|||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
ast_free(args.media_parse);
|
||||
ast_free(args.media);
|
||||
return;
|
||||
}
|
||||
int ast_ari_bridges_record_parse_body(
|
||||
|
|
|
@ -1842,7 +1842,32 @@ int ast_ari_channels_play_parse_body(
|
|||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "media");
|
||||
if (field) {
|
||||
args->media = ast_json_string_get(field);
|
||||
/* If they were silly enough to both pass in a query param and a
|
||||
* JSON body, free up the query value.
|
||||
*/
|
||||
ast_free(args->media);
|
||||
if (ast_json_typeof(field) == AST_JSON_ARRAY) {
|
||||
/* Multiple param passed as array */
|
||||
size_t i;
|
||||
args->media_count = ast_json_array_size(field);
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < args->media_count; ++i) {
|
||||
args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
|
||||
}
|
||||
} else {
|
||||
/* Multiple param passed as single value */
|
||||
args->media_count = 1;
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
args->media[0] = ast_json_string_get(field);
|
||||
}
|
||||
}
|
||||
field = ast_json_object_get(body, "lang");
|
||||
if (field) {
|
||||
|
@ -1885,7 +1910,47 @@ static void ast_ari_channels_play_cb(
|
|||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "media") == 0) {
|
||||
args.media = (i->value);
|
||||
/* Parse comma separated list */
|
||||
char *vals[MAX_VALS];
|
||||
size_t j;
|
||||
|
||||
args.media_parse = ast_strdup(i->value);
|
||||
if (!args.media_parse) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (strlen(args.media_parse) == 0) {
|
||||
/* ast_app_separate_args can't handle "" */
|
||||
args.media_count = 1;
|
||||
vals[0] = args.media_parse;
|
||||
} else {
|
||||
args.media_count = ast_app_separate_args(
|
||||
args.media_parse, ',', vals,
|
||||
ARRAY_LEN(vals));
|
||||
}
|
||||
|
||||
if (args.media_count == 0) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (args.media_count >= MAX_VALS) {
|
||||
ast_ari_response_error(response, 400,
|
||||
"Bad Request",
|
||||
"Too many values for media");
|
||||
goto fin;
|
||||
}
|
||||
|
||||
args.media = ast_malloc(sizeof(*args.media) * args.media_count);
|
||||
if (!args.media) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
for (j = 0; j < args.media_count; ++j) {
|
||||
args.media[j] = (vals[j]);
|
||||
}
|
||||
} else
|
||||
if (strcmp(i->name, "lang") == 0) {
|
||||
args.lang = (i->value);
|
||||
|
@ -1958,6 +2023,8 @@ static void ast_ari_channels_play_cb(
|
|||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
ast_free(args.media_parse);
|
||||
ast_free(args.media);
|
||||
return;
|
||||
}
|
||||
int ast_ari_channels_play_with_id_parse_body(
|
||||
|
@ -1968,7 +2035,32 @@ int ast_ari_channels_play_with_id_parse_body(
|
|||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "media");
|
||||
if (field) {
|
||||
args->media = ast_json_string_get(field);
|
||||
/* If they were silly enough to both pass in a query param and a
|
||||
* JSON body, free up the query value.
|
||||
*/
|
||||
ast_free(args->media);
|
||||
if (ast_json_typeof(field) == AST_JSON_ARRAY) {
|
||||
/* Multiple param passed as array */
|
||||
size_t i;
|
||||
args->media_count = ast_json_array_size(field);
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < args->media_count; ++i) {
|
||||
args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
|
||||
}
|
||||
} else {
|
||||
/* Multiple param passed as single value */
|
||||
args->media_count = 1;
|
||||
args->media = ast_malloc(sizeof(*args->media) * args->media_count);
|
||||
if (!args->media) {
|
||||
return -1;
|
||||
}
|
||||
args->media[0] = ast_json_string_get(field);
|
||||
}
|
||||
}
|
||||
field = ast_json_object_get(body, "lang");
|
||||
if (field) {
|
||||
|
@ -2007,7 +2099,47 @@ static void ast_ari_channels_play_with_id_cb(
|
|||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "media") == 0) {
|
||||
args.media = (i->value);
|
||||
/* Parse comma separated list */
|
||||
char *vals[MAX_VALS];
|
||||
size_t j;
|
||||
|
||||
args.media_parse = ast_strdup(i->value);
|
||||
if (!args.media_parse) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (strlen(args.media_parse) == 0) {
|
||||
/* ast_app_separate_args can't handle "" */
|
||||
args.media_count = 1;
|
||||
vals[0] = args.media_parse;
|
||||
} else {
|
||||
args.media_count = ast_app_separate_args(
|
||||
args.media_parse, ',', vals,
|
||||
ARRAY_LEN(vals));
|
||||
}
|
||||
|
||||
if (args.media_count == 0) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
if (args.media_count >= MAX_VALS) {
|
||||
ast_ari_response_error(response, 400,
|
||||
"Bad Request",
|
||||
"Too many values for media");
|
||||
goto fin;
|
||||
}
|
||||
|
||||
args.media = ast_malloc(sizeof(*args.media) * args.media_count);
|
||||
if (!args.media) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
|
||||
for (j = 0; j < args.media_count; ++j) {
|
||||
args.media[j] = (vals[j]);
|
||||
}
|
||||
} else
|
||||
if (strcmp(i->name, "lang") == 0) {
|
||||
args.lang = (i->value);
|
||||
|
@ -2080,6 +2212,8 @@ static void ast_ari_channels_play_with_id_cb(
|
|||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
ast_free(args.media_parse);
|
||||
ast_free(args.media);
|
||||
return;
|
||||
}
|
||||
int ast_ari_channels_record_parse_body(
|
||||
|
|
|
@ -70,10 +70,16 @@ static struct ao2_container *playbacks;
|
|||
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(media); /*!< The current media playing */
|
||||
AST_STRING_FIELD(language); /*!< Preferred language */
|
||||
AST_STRING_FIELD(target); /*!< Playback device uri */
|
||||
);
|
||||
);
|
||||
/*! The list of medias to play back */
|
||||
AST_VECTOR(, char *) medias;
|
||||
|
||||
/*! The current index in \c medias we're playing */
|
||||
size_t media_index;
|
||||
|
||||
/*! Control object for the channel we're playing back to */
|
||||
struct stasis_app_control *control;
|
||||
/*! Number of milliseconds to skip before playing */
|
||||
|
@ -99,6 +105,8 @@ static struct ast_json *playback_to_json(struct stasis_message *message,
|
|||
|
||||
if (!strcmp(state, "playing")) {
|
||||
type = "PlaybackStarted";
|
||||
} else if (!strcmp(state, "continuing")) {
|
||||
type = "PlaybackContinuing";
|
||||
} else if (!strcmp(state, "done")) {
|
||||
type = "PlaybackFinished";
|
||||
} else {
|
||||
|
@ -117,6 +125,14 @@ STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type,
|
|||
static void playback_dtor(void *obj)
|
||||
{
|
||||
struct stasis_app_playback *playback = obj;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AST_VECTOR_SIZE(&playback->medias); i++) {
|
||||
char *media = AST_VECTOR_GET(&playback->medias, i);
|
||||
|
||||
ast_free(media);
|
||||
}
|
||||
AST_VECTOR_FREE(&playback->medias);
|
||||
|
||||
ao2_cleanup(playback->control);
|
||||
ast_string_field_free_memory(playback);
|
||||
|
@ -137,6 +153,11 @@ static struct stasis_app_playback *playback_create(
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (AST_VECTOR_INIT(&playback->medias, 8)) {
|
||||
ao2_ref(playback, -1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(id)) {
|
||||
ast_string_field_set(playback, id, id);
|
||||
} else {
|
||||
|
@ -180,6 +201,8 @@ static const char *state_to_string(enum stasis_app_playback_state state)
|
|||
return "playing";
|
||||
case STASIS_PLAYBACK_STATE_PAUSED:
|
||||
return "paused";
|
||||
case STASIS_PLAYBACK_STATE_CONTINUING:
|
||||
return "continuing";
|
||||
case STASIS_PLAYBACK_STATE_STOPPED:
|
||||
case STASIS_PLAYBACK_STATE_COMPLETE:
|
||||
case STASIS_PLAYBACK_STATE_CANCELED:
|
||||
|
@ -241,7 +264,11 @@ static void playback_final_update(struct stasis_app_playback *playback,
|
|||
|
||||
playback->playedms = playedms;
|
||||
if (res == 0) {
|
||||
playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
|
||||
if (playback->media_index == AST_VECTOR_SIZE(&playback->medias) - 1) {
|
||||
playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
|
||||
} else {
|
||||
playback->state = STASIS_PLAYBACK_STATE_CONTINUING;
|
||||
}
|
||||
} else {
|
||||
if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
|
||||
ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
|
||||
|
@ -262,7 +289,7 @@ static void play_on_channel(struct stasis_app_playback *playback,
|
|||
int res;
|
||||
long offsetms;
|
||||
|
||||
/* Even though these local variables look fairly pointless, the avoid
|
||||
/* Even though these local variables look fairly pointless, they avoid
|
||||
* having a bunch of NULL's passed directly into
|
||||
* ast_control_streamfile() */
|
||||
const char *fwd = NULL;
|
||||
|
@ -273,73 +300,80 @@ static void play_on_channel(struct stasis_app_playback *playback,
|
|||
|
||||
ast_assert(playback != NULL);
|
||||
|
||||
offsetms = playback->offsetms;
|
||||
|
||||
res = playback_first_update(playback, ast_channel_uniqueid(chan));
|
||||
|
||||
if (res != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast_channel_state(chan) != AST_STATE_UP) {
|
||||
ast_indicate(chan, AST_CONTROL_PROGRESS);
|
||||
}
|
||||
|
||||
if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
|
||||
playback->controllable = 1;
|
||||
offsetms = playback->offsetms;
|
||||
|
||||
/* Play sound */
|
||||
res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
|
||||
fwd, rev, stop, pause, restart, playback->skipms, playback->language,
|
||||
&offsetms);
|
||||
} else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
|
||||
/* Play recording */
|
||||
RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
|
||||
ao2_cleanup);
|
||||
const char *relname =
|
||||
playback->media + strlen(RECORDING_URI_SCHEME);
|
||||
recording = stasis_app_stored_recording_find_by_name(relname);
|
||||
for (; playback->media_index < AST_VECTOR_SIZE(&playback->medias); playback->media_index++) {
|
||||
|
||||
if (!recording) {
|
||||
ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
|
||||
relname, ast_channel_name(chan));
|
||||
/* Set the current media to play */
|
||||
ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, playback->media_index));
|
||||
|
||||
res = playback_first_update(playback, ast_channel_uniqueid(chan));
|
||||
if (res != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
playback->controllable = 1;
|
||||
if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
|
||||
playback->controllable = 1;
|
||||
|
||||
res = ast_control_streamfile_lang(chan,
|
||||
stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
|
||||
restart, playback->skipms, playback->language, &offsetms);
|
||||
} else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
|
||||
int number;
|
||||
/* Play sound */
|
||||
res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
|
||||
fwd, rev, stop, pause, restart, playback->skipms, playback->language,
|
||||
&offsetms);
|
||||
} else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
|
||||
/* Play recording */
|
||||
RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
|
||||
ao2_cleanup);
|
||||
const char *relname =
|
||||
playback->media + strlen(RECORDING_URI_SCHEME);
|
||||
recording = stasis_app_stored_recording_find_by_name(relname);
|
||||
|
||||
if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
|
||||
ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
|
||||
playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
|
||||
return;
|
||||
if (!recording) {
|
||||
ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
|
||||
relname, ast_channel_name(chan));
|
||||
continue;
|
||||
}
|
||||
|
||||
playback->controllable = 1;
|
||||
|
||||
res = ast_control_streamfile_lang(chan,
|
||||
stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
|
||||
restart, playback->skipms, playback->language, &offsetms);
|
||||
} else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
|
||||
int number;
|
||||
|
||||
if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
|
||||
ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
|
||||
playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
|
||||
continue;
|
||||
}
|
||||
|
||||
res = ast_say_number(chan, number, stop, playback->language, NULL);
|
||||
} else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
|
||||
res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
|
||||
stop, playback->language);
|
||||
} else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
|
||||
res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
|
||||
stop, playback->language, AST_SAY_CASE_NONE);
|
||||
} else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
|
||||
playback->controllable = 1;
|
||||
res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
|
||||
} else {
|
||||
/* Play URL */
|
||||
ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
|
||||
playback->media, ast_channel_name(chan));
|
||||
continue;
|
||||
}
|
||||
|
||||
res = ast_say_number(chan, number, stop, playback->language, NULL);
|
||||
} else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
|
||||
res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
|
||||
stop, playback->language);
|
||||
} else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
|
||||
res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
|
||||
stop, playback->language, AST_SAY_CASE_NONE);
|
||||
} else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
|
||||
playback->controllable = 1;
|
||||
res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
|
||||
} else {
|
||||
/* Play URL */
|
||||
ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
|
||||
playback->media, ast_channel_name(chan));
|
||||
return;
|
||||
playback_final_update(playback, offsetms, res,
|
||||
ast_channel_uniqueid(chan));
|
||||
|
||||
/* Reset offset for any subsequent media */
|
||||
offsetms = 0;
|
||||
}
|
||||
|
||||
playback_final_update(playback, offsetms, res,
|
||||
ast_channel_uniqueid(chan));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -431,30 +465,45 @@ static void set_target_uri(
|
|||
}
|
||||
|
||||
struct stasis_app_playback *stasis_app_control_play_uri(
|
||||
struct stasis_app_control *control, const char *uri,
|
||||
const char *language, const char *target_id,
|
||||
struct stasis_app_control *control, const char **media,
|
||||
size_t media_count, const char *language, const char *target_id,
|
||||
enum stasis_app_playback_target_type target_type,
|
||||
int skipms, long offsetms, const char *id)
|
||||
{
|
||||
struct stasis_app_playback *playback;
|
||||
size_t i;
|
||||
|
||||
if (skipms < 0 || offsetms < 0) {
|
||||
if (skipms < 0 || offsetms < 0 || media_count == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_debug(3, "%s: Sending play(%s) command\n",
|
||||
stasis_app_control_get_channel_id(control), uri);
|
||||
|
||||
playback = playback_create(control, id);
|
||||
if (!playback) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < media_count; i++) {
|
||||
char *media_uri;
|
||||
|
||||
media_uri = ast_malloc(strlen(media[i]) + 1);
|
||||
if (!media_uri) {
|
||||
ao2_ref(playback, -1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_debug(3, "%s: Sending play(%s) command\n",
|
||||
stasis_app_control_get_channel_id(control), media[i]);
|
||||
|
||||
/* safe */
|
||||
strcpy(media_uri, media[i]);
|
||||
AST_VECTOR_APPEND(&playback->medias, media_uri);
|
||||
}
|
||||
|
||||
if (skipms == 0) {
|
||||
skipms = PLAYBACK_DEFAULT_SKIPMS;
|
||||
}
|
||||
|
||||
ast_string_field_set(playback, media, uri);
|
||||
ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, 0));
|
||||
ast_string_field_set(playback, language, language);
|
||||
set_target_uri(playback, target_type, target_id);
|
||||
playback->skipms = skipms;
|
||||
|
@ -497,12 +546,22 @@ struct ast_json *stasis_app_playback_to_json(
|
|||
return NULL;
|
||||
}
|
||||
|
||||
json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
|
||||
"id", playback->id,
|
||||
"media_uri", playback->media,
|
||||
"target_uri", playback->target,
|
||||
"language", playback->language,
|
||||
"state", state_to_string(playback->state));
|
||||
if (playback->media_index == AST_VECTOR_SIZE(&playback->medias) - 1) {
|
||||
json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
|
||||
"id", playback->id,
|
||||
"media_uri", playback->media,
|
||||
"target_uri", playback->target,
|
||||
"language", playback->language,
|
||||
"state", state_to_string(playback->state));
|
||||
} else {
|
||||
json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s}",
|
||||
"id", playback->id,
|
||||
"media_uri", playback->media,
|
||||
"next_media_uri", AST_VECTOR_GET(&playback->medias, playback->media_index + 1),
|
||||
"target_uri", playback->target,
|
||||
"language", playback->language,
|
||||
"state", state_to_string(playback->state));
|
||||
}
|
||||
|
||||
return ast_json_ref(json);
|
||||
}
|
||||
|
@ -615,6 +674,13 @@ playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDI
|
|||
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
|
||||
[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
|
||||
|
||||
[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_STOP] = playback_stop,
|
||||
[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_RESTART] = playback_restart,
|
||||
[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_PAUSE] = playback_pause,
|
||||
[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
|
||||
[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
|
||||
[STASIS_PLAYBACK_STATE_CONTINUING][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,
|
||||
|
|
|
@ -328,10 +328,10 @@
|
|||
},
|
||||
{
|
||||
"name": "media",
|
||||
"description": "Media's URI to play.",
|
||||
"description": "Media URIs to play.",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"allowMultiple": true,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
|
@ -344,7 +344,7 @@
|
|||
},
|
||||
{
|
||||
"name": "offsetms",
|
||||
"description": "Number of media to skip before playing.",
|
||||
"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
|
@ -420,10 +420,10 @@
|
|||
},
|
||||
{
|
||||
"name": "media",
|
||||
"description": "Media's URI to play.",
|
||||
"description": "Media URIs to play.",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"allowMultiple": true,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
|
@ -436,7 +436,7 @@
|
|||
},
|
||||
{
|
||||
"name": "offsetms",
|
||||
"description": "Number of media to skip before playing.",
|
||||
"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
|
|
|
@ -973,10 +973,10 @@
|
|||
},
|
||||
{
|
||||
"name": "media",
|
||||
"description": "Media's URI to play.",
|
||||
"description": "Media URIs to play.",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"allowMultiple": true,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
|
@ -989,7 +989,7 @@
|
|||
},
|
||||
{
|
||||
"name": "offsetms",
|
||||
"description": "Number of media to skip before playing.",
|
||||
"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
|
@ -1055,10 +1055,10 @@
|
|||
},
|
||||
{
|
||||
"name": "media",
|
||||
"description": "Media's URI to play.",
|
||||
"description": "Media URIs to play.",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"allowMultiple": true,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
|
@ -1071,7 +1071,7 @@
|
|||
},
|
||||
{
|
||||
"name": "offsetms",
|
||||
"description": "Number of media to skip before playing.",
|
||||
"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
|
|
|
@ -146,6 +146,7 @@
|
|||
"subTypes": [
|
||||
"DeviceStateChanged",
|
||||
"PlaybackStarted",
|
||||
"PlaybackContinuing",
|
||||
"PlaybackFinished",
|
||||
"RecordingStarted",
|
||||
"RecordingFinished",
|
||||
|
@ -270,6 +271,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"PlaybackContinuing": {
|
||||
"id": "PlaybackContinuing",
|
||||
"description": "Event showing the continuation of a media playback operation from one media URI to the next in the list.",
|
||||
"properties": {
|
||||
"playback": {
|
||||
"type": "Playback",
|
||||
"description": "Playback control object",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PlaybackFinished": {
|
||||
"id": "PlaybackFinished",
|
||||
"description": "Event showing the completion of a media playback operation.",
|
||||
|
|
|
@ -124,9 +124,14 @@
|
|||
},
|
||||
"media_uri": {
|
||||
"type": "string",
|
||||
"description": "URI for the media to play back.",
|
||||
"description": "The URI for the media currently being played back.",
|
||||
"required": true
|
||||
},
|
||||
"next_media_uri": {
|
||||
"type": "string",
|
||||
"description": "If a list of URIs is being played, the next media URI to be played back.",
|
||||
"required": false
|
||||
},
|
||||
"target_uri": {
|
||||
"type": "string",
|
||||
"description": "URI for the channel or bridge to play the media on",
|
||||
|
@ -145,7 +150,8 @@
|
|||
"values": [
|
||||
"queued",
|
||||
"playing",
|
||||
"complete"
|
||||
"continuing",
|
||||
"done"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue