483aff8c2cde4b7e133ce28c5ce35dcd2dca5792
[asterisk/asterisk.git] / res / res_stasis_playback.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief res_stasis playback support.
22  *
23  * \author David M. Lee, II <dlee@digium.com>
24  */
25
26 /*** MODULEINFO
27         <depend type="module">res_stasis</depend>
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include "asterisk/app.h"
36 #include "asterisk/astobj2.h"
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/module.h"
40 #include "asterisk/paths.h"
41 #include "asterisk/stasis_app_impl.h"
42 #include "asterisk/stasis_app_playback.h"
43 #include "asterisk/stasis_channels.h"
44 #include "asterisk/stringfields.h"
45 #include "asterisk/uuid.h"
46
47 /*! Number of hash buckets for playback container. Keep it prime! */
48 #define PLAYBACK_BUCKETS 127
49
50 /*! Default number of milliseconds of media to skip */
51 #define PLAYBACK_DEFAULT_SKIPMS 3000
52
53 #define SOUND_URI_SCHEME "sound:"
54 #define RECORDING_URI_SCHEME "recording:"
55
56 STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type);
57
58 /*! Container of all current playbacks */
59 static struct ao2_container *playbacks;
60
61 /*! Playback control object for res_stasis */
62 struct stasis_app_playback {
63         AST_DECLARE_STRING_FIELDS(
64                 AST_STRING_FIELD(id);   /*!< Playback unique id */
65                 AST_STRING_FIELD(media);        /*!< Playback media uri */
66                 AST_STRING_FIELD(language);     /*!< Preferred language */
67                 AST_STRING_FIELD(target);       /*!< Playback device uri */
68                 );
69         /*! Control object for the channel we're playing back to */
70         struct stasis_app_control *control;
71         /*! Number of milliseconds to skip before playing */
72         long offsetms;
73         /*! Number of milliseconds to skip for forward/reverse operations */
74         int skipms;
75
76         /*! Number of milliseconds of media that has been played */
77         long playedms;
78         /*! Current playback state */
79         enum stasis_app_playback_state state;
80 };
81
82 static int playback_hash(const void *obj, int flags)
83 {
84         const struct stasis_app_playback *playback = obj;
85         const char *id = flags & OBJ_KEY ? obj : playback->id;
86         return ast_str_hash(id);
87 }
88
89 static int playback_cmp(void *obj, void *arg, int flags)
90 {
91         struct stasis_app_playback *lhs = obj;
92         struct stasis_app_playback *rhs = arg;
93         const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
94
95         if (strcmp(lhs->id, rhs_id) == 0) {
96                 return CMP_MATCH | CMP_STOP;
97         } else {
98                 return 0;
99         }
100 }
101
102 static const char *state_to_string(enum stasis_app_playback_state state)
103 {
104         switch (state) {
105         case STASIS_PLAYBACK_STATE_QUEUED:
106                 return "queued";
107         case STASIS_PLAYBACK_STATE_PLAYING:
108                 return "playing";
109         case STASIS_PLAYBACK_STATE_PAUSED:
110                 return "paused";
111         case STASIS_PLAYBACK_STATE_STOPPED:
112         case STASIS_PLAYBACK_STATE_COMPLETE:
113         case STASIS_PLAYBACK_STATE_CANCELED:
114                 /* It doesn't really matter how we got here, but all of these
115                  * states really just mean 'done' */
116                 return "done";
117         case STASIS_PLAYBACK_STATE_MAX:
118                 break;
119         }
120
121         return "?";
122 }
123
124 static void playback_publish(struct stasis_app_playback *playback)
125 {
126         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
127         RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
128         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
129
130         ast_assert(playback != NULL);
131
132         json = stasis_app_playback_to_json(playback);
133         if (json == NULL) {
134                 return;
135         }
136
137         message = ast_channel_blob_create_from_cache(
138                 stasis_app_control_get_channel_id(playback->control),
139                 stasis_app_playback_snapshot_type(), json);
140         if (message == NULL) {
141                 return;
142         }
143
144         stasis_app_control_publish(playback->control, message);
145 }
146
147 static void playback_cleanup(struct stasis_app_playback *playback)
148 {
149         ao2_unlink_flags(playbacks, playback,
150                 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
151 }
152
153 static int playback_first_update(struct stasis_app_playback *playback,
154         const char *uniqueid)
155 {
156         int res;
157         SCOPED_AO2LOCK(lock, playback);
158
159         if (playback->state == STASIS_PLAYBACK_STATE_CANCELED) {
160                 ast_log(LOG_NOTICE, "%s: Playback canceled for %s\n",
161                         uniqueid, playback->media);
162                 res = -1;
163         } else {
164                 res = 0;
165                 playback->state = STASIS_PLAYBACK_STATE_PLAYING;
166         }
167
168         playback_publish(playback);
169         return res;
170 }
171
172 static void playback_final_update(struct stasis_app_playback *playback,
173         long playedms, int res, const char *uniqueid)
174 {
175         SCOPED_AO2LOCK(lock, playback);
176
177         playback->playedms = playedms;
178         if (res == 0) {
179                 playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
180         } else {
181                 if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
182                         ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
183                                 uniqueid, playback->media);
184                 } else {
185                         ast_log(LOG_WARNING, "%s: Playback failed for %s\n",
186                                 uniqueid, playback->media);
187                         playback->state = STASIS_PLAYBACK_STATE_STOPPED;
188                 }
189         }
190
191         playback_publish(playback);
192 }
193
194 static void *play_uri(struct stasis_app_control *control,
195         struct ast_channel *chan, void *data)
196 {
197         RAII_VAR(struct stasis_app_playback *, playback, NULL,
198                 playback_cleanup);
199         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
200         RAII_VAR(char *, file, NULL, ast_free);
201         int res;
202         long offsetms;
203
204         /* Even though these local variables look fairly pointless, the avoid
205          * having a bunch of NULL's passed directly into
206          * ast_control_streamfile() */
207         const char *fwd = NULL;
208         const char *rev = NULL;
209         const char *stop = NULL;
210         const char *pause = NULL;
211         const char *restart = NULL;
212
213         playback = data;
214         ast_assert(playback != NULL);
215
216         offsetms = playback->offsetms;
217
218         res = playback_first_update(playback, ast_channel_uniqueid(chan));
219
220         if (res != 0) {
221                 return NULL;
222         }
223
224         if (ast_channel_state(chan) != AST_STATE_UP) {
225                 ast_answer(chan);
226         }
227
228         if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
229                 /* Play sound */
230                 file = ast_strdup(playback->media + strlen(SOUND_URI_SCHEME));
231         } else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
232                 /* Play recording */
233                 const char *relname =
234                         playback->media + strlen(RECORDING_URI_SCHEME);
235                 if (relname[0] == '/') {
236                         file = ast_strdup(relname);
237                 } else {
238                         ast_asprintf(&file, "%s/%s",
239                                 ast_config_AST_RECORDING_DIR, relname);
240                 }
241         } else {
242                 /* Play URL */
243                 ast_log(LOG_ERROR, "Unimplemented\n");
244                 return NULL;
245         }
246
247         if (!file) {
248                 return NULL;
249         }
250
251         res = ast_control_streamfile_lang(chan, file, fwd, rev, stop, pause,
252                 restart, playback->skipms, playback->language, &offsetms);
253
254         playback_final_update(playback, offsetms, res,
255                 ast_channel_uniqueid(chan));
256
257         return NULL;
258 }
259
260 static void playback_dtor(void *obj)
261 {
262         struct stasis_app_playback *playback = obj;
263
264         ast_string_field_free_memory(playback);
265 }
266
267 static void set_target_uri(
268         struct stasis_app_playback *playback,
269         enum stasis_app_playback_target_type target_type,
270         const char *target_id)
271 {
272         const char *type = NULL;
273         switch (target_type) {
274         case STASIS_PLAYBACK_TARGET_CHANNEL:
275                 type = "channel";
276                 break;
277         case STASIS_PLAYBACK_TARGET_BRIDGE:
278                 type = "bridge";
279                 break;
280         }
281
282         ast_assert(type != NULL);
283
284         ast_string_field_build(playback, target, "%s:%s", type, target_id);
285 }
286
287 struct stasis_app_playback *stasis_app_control_play_uri(
288         struct stasis_app_control *control, const char *uri,
289         const char *language, const char *target_id,
290         enum stasis_app_playback_target_type target_type,
291         int skipms, long offsetms)
292 {
293         RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
294         char id[AST_UUID_STR_LEN];
295
296         if (skipms < 0 || offsetms < 0) {
297                 return NULL;
298         }
299
300         ast_debug(3, "%s: Sending play(%s) command\n",
301                 stasis_app_control_get_channel_id(control), uri);
302
303         playback = ao2_alloc(sizeof(*playback), playback_dtor);
304         if (!playback || ast_string_field_init(playback, 128)) {
305                 return NULL;
306         }
307
308         if (skipms == 0) {
309                 skipms = PLAYBACK_DEFAULT_SKIPMS;
310         }
311
312         ast_uuid_generate_str(id, sizeof(id));
313         ast_string_field_set(playback, id, id);
314         ast_string_field_set(playback, media, uri);
315         ast_string_field_set(playback, language, language);
316         set_target_uri(playback, target_type, target_id);
317         playback->control = control;
318         playback->skipms = skipms;
319         playback->offsetms = offsetms;
320         ao2_link(playbacks, playback);
321
322         playback->state = STASIS_PLAYBACK_STATE_QUEUED;
323         playback_publish(playback);
324
325         /* A ref is kept in the playbacks container; no need to bump */
326         stasis_app_send_command_async(control, play_uri, playback);
327
328         /* Although this should be bumped for the caller */
329         ao2_ref(playback, +1);
330         return playback;
331 }
332
333 enum stasis_app_playback_state stasis_app_playback_get_state(
334         struct stasis_app_playback *control)
335 {
336         SCOPED_AO2LOCK(lock, control);
337         return control->state;
338 }
339
340 const char *stasis_app_playback_get_id(
341         struct stasis_app_playback *control)
342 {
343         /* id is immutable; no lock needed */
344         return control->id;
345 }
346
347 struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
348 {
349         RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
350
351         playback = ao2_find(playbacks, id, OBJ_KEY);
352         if (playback == NULL) {
353                 return NULL;
354         }
355
356         ao2_ref(playback, +1);
357         return playback;
358 }
359
360 struct ast_json *stasis_app_playback_to_json(
361         const struct stasis_app_playback *playback)
362 {
363         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
364
365         if (playback == NULL) {
366                 return NULL;
367         }
368
369         json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
370                 "id", playback->id,
371                 "media_uri", playback->media,
372                 "target_uri", playback->target,
373                 "language", playback->language,
374                 "state", state_to_string(playback->state));
375
376         return ast_json_ref(json);
377 }
378
379 typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
380
381 static int playback_noop(struct stasis_app_playback *playback)
382 {
383         return 0;
384 }
385
386 static int playback_cancel(struct stasis_app_playback *playback)
387 {
388         SCOPED_AO2LOCK(lock, playback);
389         playback->state = STASIS_PLAYBACK_STATE_CANCELED;
390         return 0;
391 }
392
393 static int playback_stop(struct stasis_app_playback *playback)
394 {
395         SCOPED_AO2LOCK(lock, playback);
396         playback->state = STASIS_PLAYBACK_STATE_STOPPED;
397         return stasis_app_control_queue_control(playback->control,
398                 AST_CONTROL_STREAM_STOP);
399 }
400
401 static int playback_restart(struct stasis_app_playback *playback)
402 {
403         return stasis_app_control_queue_control(playback->control,
404                 AST_CONTROL_STREAM_RESTART);
405 }
406
407 static int playback_pause(struct stasis_app_playback *playback)
408 {
409         SCOPED_AO2LOCK(lock, playback);
410         playback->state = STASIS_PLAYBACK_STATE_PAUSED;
411         playback_publish(playback);
412         return stasis_app_control_queue_control(playback->control,
413                 AST_CONTROL_STREAM_SUSPEND);
414 }
415
416 static int playback_unpause(struct stasis_app_playback *playback)
417 {
418         SCOPED_AO2LOCK(lock, playback);
419         playback->state = STASIS_PLAYBACK_STATE_PLAYING;
420         playback_publish(playback);
421         return stasis_app_control_queue_control(playback->control,
422                 AST_CONTROL_STREAM_SUSPEND);
423 }
424
425 static int playback_reverse(struct stasis_app_playback *playback)
426 {
427         return stasis_app_control_queue_control(playback->control,
428                 AST_CONTROL_STREAM_REVERSE);
429 }
430
431 static int playback_forward(struct stasis_app_playback *playback)
432 {
433         return stasis_app_control_queue_control(playback->control,
434                 AST_CONTROL_STREAM_FORWARD);
435 }
436
437 /*!
438  * \brief A sparse array detailing how commands should be handled in the
439  * various playback states. Unset entries imply invalid operations.
440  */
441 playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
442         [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
443         [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
444
445         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
446         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
447         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
448         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
449         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
450         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
451
452         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
453         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
454         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
455
456         [STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
457         [STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
458         [STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
459 };
460
461 enum stasis_playback_oper_results stasis_app_playback_operation(
462         struct stasis_app_playback *playback,
463         enum stasis_app_playback_media_operation operation)
464 {
465         playback_opreation_cb cb;
466         SCOPED_AO2LOCK(lock, playback);
467
468         ast_assert(playback->state >= 0 && playback->state < STASIS_PLAYBACK_STATE_MAX);
469
470         if (operation < 0 || operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
471                 ast_log(LOG_ERROR, "Invalid playback operation %d\n", operation);
472                 return -1;
473         }
474
475         cb = operations[playback->state][operation];
476
477         if (!cb) {
478                 if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
479                         /* So we can be specific in our error message. */
480                         return STASIS_PLAYBACK_OPER_NOT_PLAYING;
481                 } else {
482                         /* And, really, all operations should be valid during
483                          * playback */
484                         ast_log(LOG_ERROR,
485                                 "Unhandled operation during playback: %d\n",
486                                 operation);
487                         return STASIS_PLAYBACK_OPER_FAILED;
488                 }
489         }
490
491         return cb(playback) ?
492                 STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
493 }
494
495 static int load_module(void)
496 {
497         int r;
498
499         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
500         if (r != 0) {
501                 return AST_MODULE_LOAD_FAILURE;
502         }
503
504         playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash,
505                 playback_cmp);
506         if (!playbacks) {
507                 return AST_MODULE_LOAD_FAILURE;
508         }
509         return AST_MODULE_LOAD_SUCCESS;
510 }
511
512 static int unload_module(void)
513 {
514         ao2_cleanup(playbacks);
515         playbacks = NULL;
516         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
517         return 0;
518 }
519
520 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application playback support",
521         .load = load_module,
522         .unload = unload_module,
523         .nonoptreq = "res_stasis");