2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * David M. Lee, II <dlee@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief res_stasis playback support.
23 * \author David M. Lee, II <dlee@digium.com>
27 <depend type="module">res_stasis</depend>
28 <depend type="module">res_stasis_recording</depend>
29 <support_level>core</support_level>
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36 #include "asterisk/app.h"
37 #include "asterisk/astobj2.h"
38 #include "asterisk/bridge.h"
39 #include "asterisk/bridge_internal.h"
40 #include "asterisk/file.h"
41 #include "asterisk/logger.h"
42 #include "asterisk/module.h"
43 #include "asterisk/paths.h"
44 #include "asterisk/stasis_app_impl.h"
45 #include "asterisk/stasis_app_playback.h"
46 #include "asterisk/stasis_app_recording.h"
47 #include "asterisk/stasis_channels.h"
48 #include "asterisk/stringfields.h"
49 #include "asterisk/uuid.h"
50 #include "asterisk/say.h"
52 /*! Number of hash buckets for playback container. Keep it prime! */
53 #define PLAYBACK_BUCKETS 127
55 /*! Default number of milliseconds of media to skip */
56 #define PLAYBACK_DEFAULT_SKIPMS 3000
58 #define SOUND_URI_SCHEME "sound:"
59 #define RECORDING_URI_SCHEME "recording:"
60 #define NUMBER_URI_SCHEME "number:"
61 #define DIGITS_URI_SCHEME "digits:"
62 #define CHARACTERS_URI_SCHEME "characters:"
64 /*! Container of all current playbacks */
65 static struct ao2_container *playbacks;
67 /*! Playback control object for res_stasis */
68 struct stasis_app_playback {
69 AST_DECLARE_STRING_FIELDS(
70 AST_STRING_FIELD(id); /*!< Playback unique id */
71 AST_STRING_FIELD(media); /*!< Playback media uri */
72 AST_STRING_FIELD(language); /*!< Preferred language */
73 AST_STRING_FIELD(target); /*!< Playback device uri */
75 /*! Control object for the channel we're playing back to */
76 struct stasis_app_control *control;
77 /*! Number of milliseconds to skip before playing */
79 /*! Number of milliseconds to skip for forward/reverse operations */
81 /*! Number of milliseconds of media that has been played */
83 /*! Current playback state */
84 enum stasis_app_playback_state state;
85 /*! Set when the playback can be controlled */
86 unsigned int controllable:1;
89 static struct ast_json *playback_to_json(struct stasis_message *message,
90 const struct stasis_message_sanitizer *sanitize)
92 struct ast_channel_blob *channel_blob = stasis_message_data(message);
93 struct ast_json *blob = channel_blob->blob;
95 ast_json_string_get(ast_json_object_get(blob, "state"));
98 if (!strcmp(state, "playing")) {
99 type = "PlaybackStarted";
100 } else if (!strcmp(state, "done")) {
101 type = "PlaybackFinished";
106 return ast_json_pack("{s: s, s: O}",
111 STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type,
112 .to_json = playback_to_json,
115 static void playback_dtor(void *obj)
117 struct stasis_app_playback *playback = obj;
119 ast_string_field_free_memory(playback);
122 static struct stasis_app_playback *playback_create(
123 struct stasis_app_control *control, const char *id)
125 RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
126 char uuid[AST_UUID_STR_LEN];
132 playback = ao2_alloc(sizeof(*playback), playback_dtor);
133 if (!playback || ast_string_field_init(playback, 128)) {
137 if (!ast_strlen_zero(id)) {
138 ast_string_field_set(playback, id, id);
140 ast_uuid_generate_str(uuid, sizeof(uuid));
141 ast_string_field_set(playback, id, uuid);
144 playback->control = control;
146 ao2_ref(playback, +1);
150 static int playback_hash(const void *obj, int flags)
152 const struct stasis_app_playback *playback = obj;
153 const char *id = flags & OBJ_KEY ? obj : playback->id;
154 return ast_str_hash(id);
157 static int playback_cmp(void *obj, void *arg, int flags)
159 struct stasis_app_playback *lhs = obj;
160 struct stasis_app_playback *rhs = arg;
161 const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
163 if (strcmp(lhs->id, rhs_id) == 0) {
164 return CMP_MATCH | CMP_STOP;
170 static const char *state_to_string(enum stasis_app_playback_state state)
173 case STASIS_PLAYBACK_STATE_QUEUED:
175 case STASIS_PLAYBACK_STATE_PLAYING:
177 case STASIS_PLAYBACK_STATE_PAUSED:
179 case STASIS_PLAYBACK_STATE_STOPPED:
180 case STASIS_PLAYBACK_STATE_COMPLETE:
181 case STASIS_PLAYBACK_STATE_CANCELED:
182 /* It doesn't really matter how we got here, but all of these
183 * states really just mean 'done' */
185 case STASIS_PLAYBACK_STATE_MAX:
192 static void playback_publish(struct stasis_app_playback *playback)
194 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
195 RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
197 ast_assert(playback != NULL);
199 json = stasis_app_playback_to_json(playback);
204 message = ast_channel_blob_create_from_cache(
205 stasis_app_control_get_channel_id(playback->control),
206 stasis_app_playback_snapshot_type(), json);
207 if (message == NULL) {
211 stasis_app_control_publish(playback->control, message);
214 static int playback_first_update(struct stasis_app_playback *playback,
215 const char *uniqueid)
218 SCOPED_AO2LOCK(lock, playback);
220 if (playback->state == STASIS_PLAYBACK_STATE_CANCELED) {
221 ast_log(LOG_NOTICE, "%s: Playback canceled for %s\n",
222 uniqueid, playback->media);
226 playback->state = STASIS_PLAYBACK_STATE_PLAYING;
229 playback_publish(playback);
233 static void playback_final_update(struct stasis_app_playback *playback,
234 long playedms, int res, const char *uniqueid)
236 SCOPED_AO2LOCK(lock, playback);
238 playback->playedms = playedms;
240 playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
242 if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
243 ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
244 uniqueid, playback->media);
246 ast_log(LOG_WARNING, "%s: Playback failed for %s\n",
247 uniqueid, playback->media);
248 playback->state = STASIS_PLAYBACK_STATE_STOPPED;
252 playback_publish(playback);
255 static void play_on_channel(struct stasis_app_playback *playback,
256 struct ast_channel *chan)
261 /* Even though these local variables look fairly pointless, the avoid
262 * having a bunch of NULL's passed directly into
263 * ast_control_streamfile() */
264 const char *fwd = NULL;
265 const char *rev = NULL;
266 const char *stop = NULL;
267 const char *pause = NULL;
268 const char *restart = NULL;
270 ast_assert(playback != NULL);
272 offsetms = playback->offsetms;
274 res = playback_first_update(playback, ast_channel_uniqueid(chan));
280 if (ast_channel_state(chan) != AST_STATE_UP) {
281 ast_indicate(chan, AST_CONTROL_PROGRESS);
284 if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
285 playback->controllable = 1;
288 res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
289 fwd, rev, stop, pause, restart, playback->skipms, playback->language,
291 } else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
293 RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
295 const char *relname =
296 playback->media + strlen(RECORDING_URI_SCHEME);
297 recording = stasis_app_stored_recording_find_by_name(relname);
300 ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
301 relname, ast_channel_name(chan));
305 playback->controllable = 1;
307 res = ast_control_streamfile_lang(chan,
308 stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
309 restart, playback->skipms, playback->language, &offsetms);
310 } else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
313 if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
314 ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
315 playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
319 res = ast_say_number(chan, number, stop, playback->language, NULL);
320 } else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
321 res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
322 stop, playback->language);
323 } else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
324 res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
325 stop, playback->language, AST_SAY_CASE_NONE);
328 ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported",
329 playback->media, ast_channel_name(chan));
333 playback_final_update(playback, offsetms, res,
334 ast_channel_uniqueid(chan));
340 * \brief Special case code to play while a channel is in a bridge.
342 * \param bridge_channel The channel's bridge_channel.
343 * \param playback_id Id of the playback to start.
345 static void play_on_channel_in_bridge(struct ast_bridge_channel *bridge_channel,
346 const char *playback_id)
348 RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
350 playback = stasis_app_playback_find_by_id(playback_id);
352 ast_log(LOG_ERROR, "Couldn't find playback %s\n",
357 play_on_channel(playback, bridge_channel->chan);
361 * \brief \ref RAII_VAR function to remove a playback from the global list when
364 static void remove_from_playbacks(struct stasis_app_playback *playback)
366 ao2_unlink_flags(playbacks, playback,
367 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
370 static int play_uri(struct stasis_app_control *control,
371 struct ast_channel *chan, void *data)
373 RAII_VAR(struct stasis_app_playback *, playback, NULL,
374 remove_from_playbacks);
375 struct ast_bridge *bridge;
383 bridge = stasis_app_get_bridge(control);
385 struct ast_bridge_channel *bridge_chan;
387 /* Queue up playback on the bridge */
388 ast_bridge_lock(bridge);
389 bridge_chan = ao2_bump(bridge_find_channel(bridge, chan));
390 ast_bridge_unlock(bridge);
392 ast_bridge_channel_queue_playfile_sync(
394 play_on_channel_in_bridge,
396 NULL); /* moh_class */
398 ao2_cleanup(bridge_chan);
400 play_on_channel(playback, chan);
406 static void set_target_uri(
407 struct stasis_app_playback *playback,
408 enum stasis_app_playback_target_type target_type,
409 const char *target_id)
411 const char *type = NULL;
412 switch (target_type) {
413 case STASIS_PLAYBACK_TARGET_CHANNEL:
416 case STASIS_PLAYBACK_TARGET_BRIDGE:
421 ast_assert(type != NULL);
423 ast_string_field_build(playback, target, "%s:%s", type, target_id);
426 struct stasis_app_playback *stasis_app_control_play_uri(
427 struct stasis_app_control *control, const char *uri,
428 const char *language, const char *target_id,
429 enum stasis_app_playback_target_type target_type,
430 int skipms, long offsetms, const char *id)
432 RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
434 if (skipms < 0 || offsetms < 0) {
438 ast_debug(3, "%s: Sending play(%s) command\n",
439 stasis_app_control_get_channel_id(control), uri);
441 playback = playback_create(control, id);
444 skipms = PLAYBACK_DEFAULT_SKIPMS;
447 ast_string_field_set(playback, media, uri);
448 ast_string_field_set(playback, language, language);
449 set_target_uri(playback, target_type, target_id);
450 playback->skipms = skipms;
451 playback->offsetms = offsetms;
452 ao2_link(playbacks, playback);
454 playback->state = STASIS_PLAYBACK_STATE_QUEUED;
455 playback_publish(playback);
457 /* A ref is kept in the playbacks container; no need to bump */
458 stasis_app_send_command_async(control, play_uri, playback);
460 /* Although this should be bumped for the caller */
461 ao2_ref(playback, +1);
465 enum stasis_app_playback_state stasis_app_playback_get_state(
466 struct stasis_app_playback *control)
468 SCOPED_AO2LOCK(lock, control);
469 return control->state;
472 const char *stasis_app_playback_get_id(
473 struct stasis_app_playback *control)
475 /* id is immutable; no lock needed */
479 struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
481 return ao2_find(playbacks, id, OBJ_KEY);
484 struct ast_json *stasis_app_playback_to_json(
485 const struct stasis_app_playback *playback)
487 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
489 if (playback == NULL) {
493 json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
495 "media_uri", playback->media,
496 "target_uri", playback->target,
497 "language", playback->language,
498 "state", state_to_string(playback->state));
500 return ast_json_ref(json);
503 typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
505 static int playback_noop(struct stasis_app_playback *playback)
510 static int playback_cancel(struct stasis_app_playback *playback)
512 SCOPED_AO2LOCK(lock, playback);
513 playback->state = STASIS_PLAYBACK_STATE_CANCELED;
517 static int playback_stop(struct stasis_app_playback *playback)
519 SCOPED_AO2LOCK(lock, playback);
521 if (!playback->controllable) {
525 playback->state = STASIS_PLAYBACK_STATE_STOPPED;
526 return stasis_app_control_queue_control(playback->control,
527 AST_CONTROL_STREAM_STOP);
530 static int playback_restart(struct stasis_app_playback *playback)
532 SCOPED_AO2LOCK(lock, playback);
534 if (!playback->controllable) {
538 return stasis_app_control_queue_control(playback->control,
539 AST_CONTROL_STREAM_RESTART);
542 static int playback_pause(struct stasis_app_playback *playback)
544 SCOPED_AO2LOCK(lock, playback);
546 if (!playback->controllable) {
550 playback->state = STASIS_PLAYBACK_STATE_PAUSED;
551 playback_publish(playback);
553 return stasis_app_control_queue_control(playback->control,
554 AST_CONTROL_STREAM_SUSPEND);
557 static int playback_unpause(struct stasis_app_playback *playback)
559 SCOPED_AO2LOCK(lock, playback);
561 if (!playback->controllable) {
565 playback->state = STASIS_PLAYBACK_STATE_PLAYING;
566 playback_publish(playback);
568 return stasis_app_control_queue_control(playback->control,
569 AST_CONTROL_STREAM_SUSPEND);
572 static int playback_reverse(struct stasis_app_playback *playback)
574 SCOPED_AO2LOCK(lock, playback);
576 if (!playback->controllable) {
580 return stasis_app_control_queue_control(playback->control,
581 AST_CONTROL_STREAM_REVERSE);
584 static int playback_forward(struct stasis_app_playback *playback)
586 SCOPED_AO2LOCK(lock, playback);
588 if (!playback->controllable) {
592 return stasis_app_control_queue_control(playback->control,
593 AST_CONTROL_STREAM_FORWARD);
597 * \brief A sparse array detailing how commands should be handled in the
598 * various playback states. Unset entries imply invalid operations.
600 playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
601 [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
602 [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
604 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
605 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
606 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
607 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
608 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
609 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
611 [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
612 [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
613 [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
615 [STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
616 [STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
617 [STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
620 enum stasis_playback_oper_results stasis_app_playback_operation(
621 struct stasis_app_playback *playback,
622 enum stasis_app_playback_media_operation operation)
624 playback_opreation_cb cb;
625 SCOPED_AO2LOCK(lock, playback);
627 ast_assert(playback->state >= 0 && playback->state < STASIS_PLAYBACK_STATE_MAX);
629 if (operation < 0 || operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
630 ast_log(LOG_ERROR, "Invalid playback operation %d\n", operation);
634 cb = operations[playback->state][operation];
637 if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
638 /* So we can be specific in our error message. */
639 return STASIS_PLAYBACK_OPER_NOT_PLAYING;
641 /* And, really, all operations should be valid during
644 "Unhandled operation during playback: %d\n",
646 return STASIS_PLAYBACK_OPER_FAILED;
650 return cb(playback) ?
651 STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
654 static int load_module(void)
658 r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
660 return AST_MODULE_LOAD_FAILURE;
663 playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash,
666 return AST_MODULE_LOAD_FAILURE;
668 return AST_MODULE_LOAD_SUCCESS;
671 static int unload_module(void)
673 ao2_cleanup(playbacks);
675 STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
679 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application playback support",
681 .unload = unload_module,
682 .nonoptreq = "res_stasis,res_stasis_recording");