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