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