summaryrefslogtreecommitdiff
path: root/res/res_stasis_playback.c
diff options
context:
space:
mode:
authorMatt Jordan <mjordan@digium.com>2016-04-18 18:17:08 -0500
committerJoshua Colp <jcolp@digium.com>2016-05-17 14:01:22 -0300
commit03d88b56565301d0552676ceb72f059b9267bca7 (patch)
treeb2dbce242a7c8323c8fd5110539fd6308af2bcb6 /res/res_stasis_playback.c
parent040522100b3332af877e496ab8316993f6f02b4e (diff)
ARI: Add the ability to play multiple media URIs in a single operation
Many ARI applications will want to play multiple media files in a row to a resource. The most common use case is when building long-ish IVR prompts made up of multiple, smaller sound files. Today, that requires building a small state machine, listening for each PlaybackFinished event, and triggering the next sound file to play. While not especially challenging, it is tedious work. Since requiring developers to write tedious code to do normal activities stinks, this patch adds the ability to play back a list of media files to a resource. Each of the 'play' operations on supported resources (channels and bridges) now accepts a comma delineated list of media URIs to play. A single Playback resource is created as a handle to the entire list. The operation of playing a list is identical to playing a single media URI, save that a new event, PlaybackContinuing, is raised instead of a PlaybackFinished for each non-final media URI. When the entire list is finished being played, a PlaybackFinished event is raised. In order to help inform applications where they are in the list playback, the Playback resource now includes a new, optional attribute, 'next_media_uri', that contains the next URI in the list to be played. It's important to note the following: - If an offset is provided to the 'play' operations, it only applies to the first media URI, as it would be weird to skip n seconds forward in every media resource. - Operations that control the position of the media only affect the current media being played. For example, once a media resource in the list completes, a 'reverse' operation on a subsequent media resource will not start a previously completed media resource at the appropiate offset. - This patch does not add any new operations to control the list. Hopefully, user feedback and/or future patches would add that if people want it. ASTERISK-26022 #close Change-Id: Ie1ea5356573447b8f51f2e7964915ea01792f16f
Diffstat (limited to 'res/res_stasis_playback.c')
-rw-r--r--res/res_stasis_playback.c210
1 files changed, 138 insertions, 72 deletions
diff --git a/res/res_stasis_playback.c b/res/res_stasis_playback.c
index 97191c26d..a64ecffa7 100644
--- a/res/res_stasis_playback.c
+++ b/res/res_stasis_playback.c
@@ -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;
-
- /* 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 (!recording) {
- ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
- relname, ast_channel_name(chan));
- return;
- }
+ offsetms = playback->offsetms;
- playback->controllable = 1;
+ for (; playback->media_index < AST_VECTOR_SIZE(&playback->medias); playback->media_index++) {
- 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;
+ /* Set the current media to play */
+ ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, playback->media_index));
- 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));
+ res = playback_first_update(playback, ast_channel_uniqueid(chan));
+ if (res != 0) {
return;
}
- 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;
- }
+ if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
+ playback->controllable = 1;
+
+ /* 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 (!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;
+ }
- playback_final_update(playback, offsetms, res,
- ast_channel_uniqueid(chan));
+ playback_final_update(playback, offsetms, res,
+ ast_channel_uniqueid(chan));
+ /* Reset offset for any subsequent media */
+ offsetms = 0;
+ }
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,