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"
51 #include "asterisk/indications.h"
53 /*! Number of hash buckets for playback container. Keep it prime! */
54 #define PLAYBACK_BUCKETS 127
56 /*! Default number of milliseconds of media to skip */
57 #define PLAYBACK_DEFAULT_SKIPMS 3000
59 #define SOUND_URI_SCHEME "sound:"
60 #define RECORDING_URI_SCHEME "recording:"
61 #define NUMBER_URI_SCHEME "number:"
62 #define DIGITS_URI_SCHEME "digits:"
63 #define CHARACTERS_URI_SCHEME "characters:"
64 #define TONE_URI_SCHEME "tone:"
66 /*! Container of all current playbacks */
67 static struct ao2_container *playbacks;
69 /*! Playback control object for res_stasis */
70 struct stasis_app_playback {
71 AST_DECLARE_STRING_FIELDS(
72 AST_STRING_FIELD(id); /*!< Playback unique id */
73 AST_STRING_FIELD(media); /*!< Playback media uri */
74 AST_STRING_FIELD(language); /*!< Preferred language */
75 AST_STRING_FIELD(target); /*!< Playback device uri */
77 /*! Control object for the channel we're playing back to */
78 struct stasis_app_control *control;
79 /*! Number of milliseconds to skip before playing */
81 /*! Number of milliseconds to skip for forward/reverse operations */
83 /*! Number of milliseconds of media that has been played */
85 /*! Current playback state */
86 enum stasis_app_playback_state state;
87 /*! Set when the playback can be controlled */
88 unsigned int controllable:1;
91 static struct ast_json *playback_to_json(struct stasis_message *message,
92 const struct stasis_message_sanitizer *sanitize)
94 struct ast_channel_blob *channel_blob = stasis_message_data(message);
95 struct ast_json *blob = channel_blob->blob;
97 ast_json_string_get(ast_json_object_get(blob, "state"));
100 if (!strcmp(state, "playing")) {
101 type = "PlaybackStarted";
102 } else if (!strcmp(state, "done")) {
103 type = "PlaybackFinished";
108 return ast_json_pack("{s: s, s: O}",
113 STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type,
114 .to_json = playback_to_json,
117 static void playback_dtor(void *obj)
119 struct stasis_app_playback *playback = obj;
121 ast_string_field_free_memory(playback);
124 static struct stasis_app_playback *playback_create(
125 struct stasis_app_control *control, const char *id)
127 RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
128 char uuid[AST_UUID_STR_LEN];
134 playback = ao2_alloc(sizeof(*playback), playback_dtor);
135 if (!playback || ast_string_field_init(playback, 128)) {
139 if (!ast_strlen_zero(id)) {
140 ast_string_field_set(playback, id, id);
142 ast_uuid_generate_str(uuid, sizeof(uuid));
143 ast_string_field_set(playback, id, uuid);
146 playback->control = control;
148 ao2_ref(playback, +1);
152 static int playback_hash(const void *obj, int flags)
154 const struct stasis_app_playback *playback = obj;
155 const char *id = flags & OBJ_KEY ? obj : playback->id;
156 return ast_str_hash(id);
159 static int playback_cmp(void *obj, void *arg, int flags)
161 struct stasis_app_playback *lhs = obj;
162 struct stasis_app_playback *rhs = arg;
163 const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
165 if (strcmp(lhs->id, rhs_id) == 0) {
166 return CMP_MATCH | CMP_STOP;
172 static const char *state_to_string(enum stasis_app_playback_state state)
175 case STASIS_PLAYBACK_STATE_QUEUED:
177 case STASIS_PLAYBACK_STATE_PLAYING:
179 case STASIS_PLAYBACK_STATE_PAUSED:
181 case STASIS_PLAYBACK_STATE_STOPPED:
182 case STASIS_PLAYBACK_STATE_COMPLETE:
183 case STASIS_PLAYBACK_STATE_CANCELED:
184 /* It doesn't really matter how we got here, but all of these
185 * states really just mean 'done' */
187 case STASIS_PLAYBACK_STATE_MAX:
194 static void playback_publish(struct stasis_app_playback *playback)
196 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
197 RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
199 ast_assert(playback != NULL);
201 json = stasis_app_playback_to_json(playback);
206 message = ast_channel_blob_create_from_cache(
207 stasis_app_control_get_channel_id(playback->control),
208 stasis_app_playback_snapshot_type(), json);
209 if (message == NULL) {
213 stasis_app_control_publish(playback->control, message);
216 static int playback_first_update(struct stasis_app_playback *playback,
217 const char *uniqueid)
220 SCOPED_AO2LOCK(lock, playback);
222 if (playback->state == STASIS_PLAYBACK_STATE_CANCELED) {
223 ast_log(LOG_NOTICE, "%s: Playback canceled for %s\n",
224 uniqueid, playback->media);
228 playback->state = STASIS_PLAYBACK_STATE_PLAYING;
231 playback_publish(playback);
235 static void playback_final_update(struct stasis_app_playback *playback,
236 long playedms, int res, const char *uniqueid)
238 SCOPED_AO2LOCK(lock, playback);
240 playback->playedms = playedms;
242 playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
244 if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
245 ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
246 uniqueid, playback->media);
248 ast_log(LOG_WARNING, "%s: Playback failed for %s\n",
249 uniqueid, playback->media);
250 playback->state = STASIS_PLAYBACK_STATE_STOPPED;
254 playback_publish(playback);
257 static void play_on_channel(struct stasis_app_playback *playback,
258 struct ast_channel *chan)
263 /* Even though these local variables look fairly pointless, the avoid
264 * having a bunch of NULL's passed directly into
265 * ast_control_streamfile() */
266 const char *fwd = NULL;
267 const char *rev = NULL;
268 const char *stop = NULL;
269 const char *pause = NULL;
270 const char *restart = NULL;
272 ast_assert(playback != NULL);
274 offsetms = playback->offsetms;
276 res = playback_first_update(playback, ast_channel_uniqueid(chan));
282 if (ast_channel_state(chan) != AST_STATE_UP) {
283 ast_indicate(chan, AST_CONTROL_PROGRESS);
286 if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
287 playback->controllable = 1;
290 res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
291 fwd, rev, stop, pause, restart, playback->skipms, playback->language,
293 } else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
295 RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
297 const char *relname =
298 playback->media + strlen(RECORDING_URI_SCHEME);
299 recording = stasis_app_stored_recording_find_by_name(relname);
302 ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
303 relname, ast_channel_name(chan));
307 playback->controllable = 1;
309 res = ast_control_streamfile_lang(chan,
310 stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
311 restart, playback->skipms, playback->language, &offsetms);
312 } else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
315 if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
316 ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
317 playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
321 res = ast_say_number(chan, number, stop, playback->language, NULL);
322 } else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
323 res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
324 stop, playback->language);
325 } else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
326 res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
327 stop, playback->language, AST_SAY_CASE_NONE);
328 } else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
329 playback->controllable = 1;
330 res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
333 ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported",
334 playback->media, ast_channel_name(chan));
338 playback_final_update(playback, offsetms, res,
339 ast_channel_uniqueid(chan));
345 * \brief Special case code to play while a channel is in a bridge.
347 * \param bridge_channel The channel's bridge_channel.
348 * \param playback_id Id of the playback to start.
350 static void play_on_channel_in_bridge(struct ast_bridge_channel *bridge_channel,
351 const char *playback_id)
353 RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
355 playback = stasis_app_playback_find_by_id(playback_id);
357 ast_log(LOG_ERROR, "Couldn't find playback %s\n",
362 play_on_channel(playback, bridge_channel->chan);
366 * \brief \ref RAII_VAR function to remove a playback from the global list when
369 static void remove_from_playbacks(struct stasis_app_playback *playback)
371 ao2_unlink_flags(playbacks, playback,
372 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
375 static int play_uri(struct stasis_app_control *control,
376 struct ast_channel *chan, void *data)
378 RAII_VAR(struct stasis_app_playback *, playback, NULL,
379 remove_from_playbacks);
380 struct ast_bridge *bridge;
388 bridge = stasis_app_get_bridge(control);
390 struct ast_bridge_channel *bridge_chan;
392 /* Queue up playback on the bridge */
393 ast_bridge_lock(bridge);
394 bridge_chan = ao2_bump(bridge_find_channel(bridge, chan));
395 ast_bridge_unlock(bridge);
397 ast_bridge_channel_queue_playfile_sync(
399 play_on_channel_in_bridge,
401 NULL); /* moh_class */
403 ao2_cleanup(bridge_chan);
405 play_on_channel(playback, chan);
411 static void set_target_uri(
412 struct stasis_app_playback *playback,
413 enum stasis_app_playback_target_type target_type,
414 const char *target_id)
416 const char *type = NULL;
417 switch (target_type) {
418 case STASIS_PLAYBACK_TARGET_CHANNEL:
421 case STASIS_PLAYBACK_TARGET_BRIDGE:
426 ast_assert(type != NULL);
428 ast_string_field_build(playback, target, "%s:%s", type, target_id);
431 struct stasis_app_playback *stasis_app_control_play_uri(
432 struct stasis_app_control *control, const char *uri,
433 const char *language, const char *target_id,
434 enum stasis_app_playback_target_type target_type,
435 int skipms, long offsetms, const char *id)
437 RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
439 if (skipms < 0 || offsetms < 0) {
443 ast_debug(3, "%s: Sending play(%s) command\n",
444 stasis_app_control_get_channel_id(control), uri);
446 playback = playback_create(control, id);
449 skipms = PLAYBACK_DEFAULT_SKIPMS;
452 ast_string_field_set(playback, media, uri);
453 ast_string_field_set(playback, language, language);
454 set_target_uri(playback, target_type, target_id);
455 playback->skipms = skipms;
456 playback->offsetms = offsetms;
457 ao2_link(playbacks, playback);
459 playback->state = STASIS_PLAYBACK_STATE_QUEUED;
460 playback_publish(playback);
462 /* A ref is kept in the playbacks container; no need to bump */
463 stasis_app_send_command_async(control, play_uri, playback);
465 /* Although this should be bumped for the caller */
466 ao2_ref(playback, +1);
470 enum stasis_app_playback_state stasis_app_playback_get_state(
471 struct stasis_app_playback *control)
473 SCOPED_AO2LOCK(lock, control);
474 return control->state;
477 const char *stasis_app_playback_get_id(
478 struct stasis_app_playback *control)
480 /* id is immutable; no lock needed */
484 struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
486 return ao2_find(playbacks, id, OBJ_KEY);
489 struct ast_json *stasis_app_playback_to_json(
490 const struct stasis_app_playback *playback)
492 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
494 if (playback == NULL) {
498 json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
500 "media_uri", playback->media,
501 "target_uri", playback->target,
502 "language", playback->language,
503 "state", state_to_string(playback->state));
505 return ast_json_ref(json);
508 typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
510 static int playback_noop(struct stasis_app_playback *playback)
515 static int playback_cancel(struct stasis_app_playback *playback)
517 SCOPED_AO2LOCK(lock, playback);
518 playback->state = STASIS_PLAYBACK_STATE_CANCELED;
522 static int playback_stop(struct stasis_app_playback *playback)
524 SCOPED_AO2LOCK(lock, playback);
526 if (!playback->controllable) {
530 playback->state = STASIS_PLAYBACK_STATE_STOPPED;
531 return stasis_app_control_queue_control(playback->control,
532 AST_CONTROL_STREAM_STOP);
535 static int playback_restart(struct stasis_app_playback *playback)
537 SCOPED_AO2LOCK(lock, playback);
539 if (!playback->controllable) {
543 return stasis_app_control_queue_control(playback->control,
544 AST_CONTROL_STREAM_RESTART);
547 static int playback_pause(struct stasis_app_playback *playback)
549 SCOPED_AO2LOCK(lock, playback);
551 if (!playback->controllable) {
555 playback->state = STASIS_PLAYBACK_STATE_PAUSED;
556 playback_publish(playback);
558 return stasis_app_control_queue_control(playback->control,
559 AST_CONTROL_STREAM_SUSPEND);
562 static int playback_unpause(struct stasis_app_playback *playback)
564 SCOPED_AO2LOCK(lock, playback);
566 if (!playback->controllable) {
570 playback->state = STASIS_PLAYBACK_STATE_PLAYING;
571 playback_publish(playback);
573 return stasis_app_control_queue_control(playback->control,
574 AST_CONTROL_STREAM_SUSPEND);
577 static int playback_reverse(struct stasis_app_playback *playback)
579 SCOPED_AO2LOCK(lock, playback);
581 if (!playback->controllable) {
585 return stasis_app_control_queue_control(playback->control,
586 AST_CONTROL_STREAM_REVERSE);
589 static int playback_forward(struct stasis_app_playback *playback)
591 SCOPED_AO2LOCK(lock, playback);
593 if (!playback->controllable) {
597 return stasis_app_control_queue_control(playback->control,
598 AST_CONTROL_STREAM_FORWARD);
602 * \brief A sparse array detailing how commands should be handled in the
603 * various playback states. Unset entries imply invalid operations.
605 playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
606 [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
607 [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
609 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
610 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
611 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
612 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
613 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
614 [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
616 [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
617 [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
618 [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
620 [STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
621 [STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
622 [STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
625 enum stasis_playback_oper_results stasis_app_playback_operation(
626 struct stasis_app_playback *playback,
627 enum stasis_app_playback_media_operation operation)
629 playback_opreation_cb cb;
630 SCOPED_AO2LOCK(lock, playback);
632 ast_assert(playback->state >= 0 && playback->state < STASIS_PLAYBACK_STATE_MAX);
634 if (operation < 0 || operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
635 ast_log(LOG_ERROR, "Invalid playback operation %d\n", operation);
639 cb = operations[playback->state][operation];
642 if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
643 /* So we can be specific in our error message. */
644 return STASIS_PLAYBACK_OPER_NOT_PLAYING;
646 /* And, really, all operations should be valid during
649 "Unhandled operation during playback: %d\n",
651 return STASIS_PLAYBACK_OPER_FAILED;
655 return cb(playback) ?
656 STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
659 static int load_module(void)
663 r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
665 return AST_MODULE_LOAD_FAILURE;
668 playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash,
671 return AST_MODULE_LOAD_FAILURE;
673 return AST_MODULE_LOAD_SUCCESS;
676 static int unload_module(void)
678 ao2_cleanup(playbacks);
680 STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
684 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application playback support",
686 .unload = unload_module,
687 .nonoptreq = "res_stasis,res_stasis_recording");