Merge "ARI: Add the ability to play multiple media URIs in a single operation"

This commit is contained in:
Joshua Colp 2016-05-18 18:35:20 -05:00 committed by Gerrit Code Review
commit e2fc83af50
15 changed files with 643 additions and 126 deletions

View file

@ -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
------------------

View file

@ -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);

View file

@ -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;

View file

@ -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)

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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(

View file

@ -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(

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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.",

View file

@ -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"
]
}
}