res_pjsip_outbound_registration: Fix leak on vector add failure.
[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         <depend type="module">res_stasis_recording</depend>
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 #include "asterisk/app.h"
35 #include "asterisk/astobj2.h"
36 #include "asterisk/bridge.h"
37 #include "asterisk/bridge_internal.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/module.h"
41 #include "asterisk/paths.h"
42 #include "asterisk/stasis_app_impl.h"
43 #include "asterisk/stasis_app_playback.h"
44 #include "asterisk/stasis_app_recording.h"
45 #include "asterisk/stasis_channels.h"
46 #include "asterisk/stringfields.h"
47 #include "asterisk/uuid.h"
48 #include "asterisk/say.h"
49 #include "asterisk/indications.h"
50
51 /*! Number of hash buckets for playback container. Keep it prime! */
52 #define PLAYBACK_BUCKETS 127
53
54 /*! Default number of milliseconds of media to skip */
55 #define PLAYBACK_DEFAULT_SKIPMS 3000
56
57 #define SOUND_URI_SCHEME "sound:"
58 #define RECORDING_URI_SCHEME "recording:"
59 #define NUMBER_URI_SCHEME "number:"
60 #define DIGITS_URI_SCHEME "digits:"
61 #define CHARACTERS_URI_SCHEME "characters:"
62 #define TONE_URI_SCHEME "tone:"
63
64 /*! Container of all current playbacks */
65 static struct ao2_container *playbacks;
66
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);        /*!< The current media playing */
72                 AST_STRING_FIELD(language);     /*!< Preferred language */
73                 AST_STRING_FIELD(target);       /*!< Playback device uri */
74         );
75         /*! The list of medias to play back */
76         AST_VECTOR(, char *) medias;
77
78         /*! The current index in \c medias we're playing */
79         size_t media_index;
80
81         /*! Control object for the channel we're playing back to */
82         struct stasis_app_control *control;
83         /*! Number of milliseconds to skip before playing */
84         long offsetms;
85         /*! Number of milliseconds to skip for forward/reverse operations */
86         int skipms;
87         /*! Number of milliseconds of media that has been played */
88         long playedms;
89         /*! Current playback state */
90         enum stasis_app_playback_state state;
91         /*! Set when the playback can be controlled */
92         unsigned int controllable:1;
93 };
94
95 static struct ast_json *playback_to_json(struct stasis_message *message,
96         const struct stasis_message_sanitizer *sanitize)
97 {
98         struct ast_channel_blob *channel_blob = stasis_message_data(message);
99         struct ast_json *blob = channel_blob->blob;
100         const char *state =
101                 ast_json_string_get(ast_json_object_get(blob, "state"));
102         const char *type;
103
104         if (!strcmp(state, "playing")) {
105                 type = "PlaybackStarted";
106         } else if (!strcmp(state, "continuing")) {
107                 type = "PlaybackContinuing";
108         } else if (!strcmp(state, "done")) {
109                 type = "PlaybackFinished";
110         } else {
111                 return NULL;
112         }
113
114         return ast_json_pack("{s: s, s: o}",
115                 "type", type,
116                 "playback", ast_json_deep_copy(blob));
117 }
118
119 STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type,
120         .to_json = playback_to_json,
121 );
122
123 static void playback_dtor(void *obj)
124 {
125         struct stasis_app_playback *playback = obj;
126         int i;
127
128         for (i = 0; i < AST_VECTOR_SIZE(&playback->medias); i++) {
129                 char *media = AST_VECTOR_GET(&playback->medias, i);
130
131                 ast_free(media);
132         }
133         AST_VECTOR_FREE(&playback->medias);
134
135         ao2_cleanup(playback->control);
136         ast_string_field_free_memory(playback);
137 }
138
139 static struct stasis_app_playback *playback_create(
140         struct stasis_app_control *control, const char *id)
141 {
142         RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
143         char uuid[AST_UUID_STR_LEN];
144
145         if (!control) {
146                 return NULL;
147         }
148
149         playback = ao2_alloc(sizeof(*playback), playback_dtor);
150         if (!playback || ast_string_field_init(playback, 128)) {
151                 return NULL;
152         }
153
154         if (AST_VECTOR_INIT(&playback->medias, 8)) {
155                 ao2_ref(playback, -1);
156                 return NULL;
157         }
158
159         if (!ast_strlen_zero(id)) {
160                 ast_string_field_set(playback, id, id);
161         } else {
162                 ast_uuid_generate_str(uuid, sizeof(uuid));
163                 ast_string_field_set(playback, id, uuid);
164         }
165
166         ao2_ref(control, +1);
167         playback->control = control;
168
169         ao2_ref(playback, +1);
170         return playback;
171 }
172
173 static int playback_hash(const void *obj, int flags)
174 {
175         const struct stasis_app_playback *playback = obj;
176         const char *id = flags & OBJ_KEY ? obj : playback->id;
177         return ast_str_hash(id);
178 }
179
180 static int playback_cmp(void *obj, void *arg, int flags)
181 {
182         struct stasis_app_playback *lhs = obj;
183         struct stasis_app_playback *rhs = arg;
184         const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
185
186         if (strcmp(lhs->id, rhs_id) == 0) {
187                 return CMP_MATCH | CMP_STOP;
188         } else {
189                 return 0;
190         }
191 }
192
193 static const char *state_to_string(enum stasis_app_playback_state state)
194 {
195         switch (state) {
196         case STASIS_PLAYBACK_STATE_QUEUED:
197                 return "queued";
198         case STASIS_PLAYBACK_STATE_PLAYING:
199                 return "playing";
200         case STASIS_PLAYBACK_STATE_PAUSED:
201                 return "paused";
202         case STASIS_PLAYBACK_STATE_CONTINUING:
203                 return "continuing";
204         case STASIS_PLAYBACK_STATE_STOPPED:
205         case STASIS_PLAYBACK_STATE_COMPLETE:
206         case STASIS_PLAYBACK_STATE_CANCELED:
207                 /* It doesn't really matter how we got here, but all of these
208                  * states really just mean 'done' */
209                 return "done";
210         case STASIS_PLAYBACK_STATE_MAX:
211                 break;
212         }
213
214         return "?";
215 }
216
217 static void playback_publish(struct stasis_app_playback *playback)
218 {
219         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
220         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
221
222         ast_assert(playback != NULL);
223
224         json = stasis_app_playback_to_json(playback);
225         if (json == NULL) {
226                 return;
227         }
228
229         message = ast_channel_blob_create_from_cache(
230                 stasis_app_control_get_channel_id(playback->control),
231                 stasis_app_playback_snapshot_type(), json);
232         if (message == NULL) {
233                 return;
234         }
235
236         stasis_app_control_publish(playback->control, message);
237 }
238
239 static int playback_first_update(struct stasis_app_playback *playback,
240         const char *uniqueid)
241 {
242         int res;
243         SCOPED_AO2LOCK(lock, playback);
244
245         if (playback->state == STASIS_PLAYBACK_STATE_CANCELED) {
246                 ast_log(LOG_NOTICE, "%s: Playback canceled for %s\n",
247                         uniqueid, playback->media);
248                 res = -1;
249         } else {
250                 res = 0;
251                 playback->state = STASIS_PLAYBACK_STATE_PLAYING;
252         }
253
254         playback_publish(playback);
255         return res;
256 }
257
258 static void playback_final_update(struct stasis_app_playback *playback,
259         long playedms, int res, const char *uniqueid)
260 {
261         SCOPED_AO2LOCK(lock, playback);
262
263         playback->playedms = playedms;
264         if (res == 0) {
265                 if (playback->media_index == AST_VECTOR_SIZE(&playback->medias) - 1) {
266                         playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
267                 } else {
268                         playback->state = STASIS_PLAYBACK_STATE_CONTINUING;
269                 }
270         } else {
271                 if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
272                         ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
273                                 uniqueid, playback->media);
274                 } else {
275                         ast_log(LOG_WARNING, "%s: Playback failed for %s\n",
276                                 uniqueid, playback->media);
277                         playback->state = STASIS_PLAYBACK_STATE_STOPPED;
278                 }
279         }
280
281         playback_publish(playback);
282 }
283
284 static void play_on_channel(struct stasis_app_playback *playback,
285         struct ast_channel *chan)
286 {
287         int res;
288         long offsetms;
289
290         /* Even though these local variables look fairly pointless, they avoid
291          * having a bunch of NULL's passed directly into
292          * ast_control_streamfile() */
293         const char *fwd = NULL;
294         const char *rev = NULL;
295         const char *stop = NULL;
296         const char *pause = NULL;
297         const char *restart = NULL;
298
299         ast_assert(playback != NULL);
300
301         if (ast_channel_state(chan) != AST_STATE_UP) {
302                 ast_indicate(chan, AST_CONTROL_PROGRESS);
303         }
304
305         offsetms = playback->offsetms;
306
307         for (; playback->media_index < AST_VECTOR_SIZE(&playback->medias); playback->media_index++) {
308
309                 /* Set the current media to play */
310                 ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, playback->media_index));
311
312                 res = playback_first_update(playback, ast_channel_uniqueid(chan));
313                 if (res != 0) {
314                         return;
315                 }
316
317                 if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
318                         playback->controllable = 1;
319
320                         /* Play sound */
321                         res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
322                                         fwd, rev, stop, pause, restart, playback->skipms, playback->language,
323                                         &offsetms);
324                 } else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
325                         /* Play recording */
326                         RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
327                                 ao2_cleanup);
328                         const char *relname =
329                                 playback->media + strlen(RECORDING_URI_SCHEME);
330                         recording = stasis_app_stored_recording_find_by_name(relname);
331
332                         if (!recording) {
333                                 ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
334                                         relname, ast_channel_name(chan));
335                                 continue;
336                         }
337
338                         playback->controllable = 1;
339
340                         res = ast_control_streamfile_lang(chan,
341                                 stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
342                                 restart, playback->skipms, playback->language, &offsetms);
343                 } else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
344                         int number;
345
346                         if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
347                                 ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
348                                         playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
349                                 continue;
350                         }
351
352                         res = ast_say_number(chan, number, stop, playback->language, NULL);
353                 } else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
354                         res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
355                                 stop, playback->language);
356                 } else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
357                         res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
358                                 stop, playback->language, AST_SAY_CASE_NONE);
359                 } else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
360                         playback->controllable = 1;
361                         res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
362                 } else {
363                         /* Play URL */
364                         ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
365                                 playback->media, ast_channel_name(chan));
366                         continue;
367                 }
368
369                 playback_final_update(playback, offsetms, res,
370                         ast_channel_uniqueid(chan));
371                 if (res == AST_CONTROL_STREAM_STOP) {
372                         break;
373                 }
374
375                 /* Reset offset for any subsequent media */
376                 offsetms = 0;
377         }
378         return;
379 }
380
381 /*!
382  * \brief Special case code to play while a channel is in a bridge.
383  *
384  * \param bridge_channel The channel's bridge_channel.
385  * \param playback_id Id of the playback to start.
386  */
387 static void play_on_channel_in_bridge(struct ast_bridge_channel *bridge_channel,
388         const char *playback_id)
389 {
390         RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
391
392         playback = stasis_app_playback_find_by_id(playback_id);
393         if (!playback) {
394                 ast_log(LOG_ERROR, "Couldn't find playback %s\n",
395                         playback_id);
396                 return;
397         }
398
399         play_on_channel(playback, bridge_channel->chan);
400 }
401
402 /*!
403  * \brief \ref RAII_VAR function to remove a playback from the global list when
404  * leaving scope.
405  */
406 static void remove_from_playbacks(void *data)
407 {
408         struct stasis_app_playback *playback = data;
409
410         ao2_unlink_flags(playbacks, playback,
411                 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
412         ao2_ref(playback, -1);
413 }
414
415 static int play_uri(struct stasis_app_control *control,
416         struct ast_channel *chan, void *data)
417 {
418         struct stasis_app_playback *playback = data;
419         struct ast_bridge *bridge;
420
421         if (!control) {
422                 return -1;
423         }
424
425         bridge = stasis_app_get_bridge(control);
426         if (bridge) {
427                 struct ast_bridge_channel *bridge_chan;
428
429                 /* Queue up playback on the bridge */
430                 ast_bridge_lock(bridge);
431                 bridge_chan = ao2_bump(bridge_find_channel(bridge, chan));
432                 ast_bridge_unlock(bridge);
433                 if (bridge_chan) {
434                         ast_bridge_channel_queue_playfile_sync(
435                                 bridge_chan,
436                                 play_on_channel_in_bridge,
437                                 playback->id,
438                                 NULL); /* moh_class */
439                 }
440                 ao2_cleanup(bridge_chan);
441         } else {
442                 play_on_channel(playback, chan);
443         }
444
445         return 0;
446 }
447
448 static void set_target_uri(
449         struct stasis_app_playback *playback,
450         enum stasis_app_playback_target_type target_type,
451         const char *target_id)
452 {
453         const char *type = NULL;
454         switch (target_type) {
455         case STASIS_PLAYBACK_TARGET_CHANNEL:
456                 type = "channel";
457                 break;
458         case STASIS_PLAYBACK_TARGET_BRIDGE:
459                 type = "bridge";
460                 break;
461         }
462
463         ast_assert(type != NULL);
464
465         ast_string_field_build(playback, target, "%s:%s", type, target_id);
466 }
467
468 struct stasis_app_playback *stasis_app_control_play_uri(
469         struct stasis_app_control *control, const char **media,
470         size_t media_count, const char *language, const char *target_id,
471         enum stasis_app_playback_target_type target_type,
472         int skipms, long offsetms, const char *id)
473 {
474         struct stasis_app_playback *playback;
475         size_t i;
476
477         if (skipms < 0 || offsetms < 0 || media_count == 0) {
478                 return NULL;
479         }
480
481         playback = playback_create(control, id);
482         if (!playback) {
483                 return NULL;
484         }
485
486         for (i = 0; i < media_count; i++) {
487                 char *media_uri;
488
489                 media_uri = ast_malloc(strlen(media[i]) + 1);
490                 if (!media_uri) {
491                         ao2_ref(playback, -1);
492                         return NULL;
493                 }
494
495                 ast_debug(3, "%s: Sending play(%s) command\n",
496                         stasis_app_control_get_channel_id(control), media[i]);
497
498             /* safe */
499                 strcpy(media_uri, media[i]);
500                 AST_VECTOR_APPEND(&playback->medias, media_uri);
501         }
502
503         if (skipms == 0) {
504                 skipms = PLAYBACK_DEFAULT_SKIPMS;
505         }
506
507         ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, 0));
508         ast_string_field_set(playback, language, language);
509         set_target_uri(playback, target_type, target_id);
510         playback->skipms = skipms;
511         playback->offsetms = offsetms;
512         ao2_link(playbacks, playback);
513
514         playback->state = STASIS_PLAYBACK_STATE_QUEUED;
515         playback_publish(playback);
516
517         stasis_app_send_command_async(control, play_uri, ao2_bump(playback), remove_from_playbacks);
518
519         return playback;
520 }
521
522 enum stasis_app_playback_state stasis_app_playback_get_state(
523         struct stasis_app_playback *control)
524 {
525         SCOPED_AO2LOCK(lock, control);
526         return control->state;
527 }
528
529 const char *stasis_app_playback_get_id(
530         struct stasis_app_playback *control)
531 {
532         /* id is immutable; no lock needed */
533         return control->id;
534 }
535
536 struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
537 {
538         return ao2_find(playbacks, id, OBJ_KEY);
539 }
540
541 struct ast_json *stasis_app_playback_to_json(
542         const struct stasis_app_playback *playback)
543 {
544         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
545
546         if (playback == NULL) {
547                 return NULL;
548         }
549
550         if (playback->media_index == AST_VECTOR_SIZE(&playback->medias) - 1) {
551                 json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
552                         "id", playback->id,
553                         "media_uri", playback->media,
554                         "target_uri", playback->target,
555                         "language", playback->language,
556                         "state", state_to_string(playback->state));
557         } else {
558                 json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s}",
559                         "id", playback->id,
560                         "media_uri", playback->media,
561                         "next_media_uri", AST_VECTOR_GET(&playback->medias, playback->media_index + 1),
562                         "target_uri", playback->target,
563                         "language", playback->language,
564                         "state", state_to_string(playback->state));
565         }
566
567         return ast_json_ref(json);
568 }
569
570 typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
571
572 static int playback_noop(struct stasis_app_playback *playback)
573 {
574         return 0;
575 }
576
577 static int playback_cancel(struct stasis_app_playback *playback)
578 {
579         SCOPED_AO2LOCK(lock, playback);
580         playback->state = STASIS_PLAYBACK_STATE_CANCELED;
581         return 0;
582 }
583
584 static int playback_stop(struct stasis_app_playback *playback)
585 {
586         SCOPED_AO2LOCK(lock, playback);
587
588         if (!playback->controllable) {
589                 return -1;
590         }
591
592         playback->state = STASIS_PLAYBACK_STATE_STOPPED;
593         return stasis_app_control_queue_control(playback->control,
594                 AST_CONTROL_STREAM_STOP);
595 }
596
597 static int playback_restart(struct stasis_app_playback *playback)
598 {
599         SCOPED_AO2LOCK(lock, playback);
600
601         if (!playback->controllable) {
602                 return -1;
603         }
604
605         return stasis_app_control_queue_control(playback->control,
606                 AST_CONTROL_STREAM_RESTART);
607 }
608
609 static int playback_pause(struct stasis_app_playback *playback)
610 {
611         SCOPED_AO2LOCK(lock, playback);
612
613         if (!playback->controllable) {
614                 return -1;
615         }
616
617         playback->state = STASIS_PLAYBACK_STATE_PAUSED;
618         playback_publish(playback);
619
620         return stasis_app_control_queue_control(playback->control,
621                 AST_CONTROL_STREAM_SUSPEND);
622 }
623
624 static int playback_unpause(struct stasis_app_playback *playback)
625 {
626         SCOPED_AO2LOCK(lock, playback);
627
628         if (!playback->controllable) {
629                 return -1;
630         }
631
632         playback->state = STASIS_PLAYBACK_STATE_PLAYING;
633         playback_publish(playback);
634
635         return stasis_app_control_queue_control(playback->control,
636                 AST_CONTROL_STREAM_SUSPEND);
637 }
638
639 static int playback_reverse(struct stasis_app_playback *playback)
640 {
641         SCOPED_AO2LOCK(lock, playback);
642
643         if (!playback->controllable) {
644                 return -1;
645         }
646
647         return stasis_app_control_queue_control(playback->control,
648                 AST_CONTROL_STREAM_REVERSE);
649 }
650
651 static int playback_forward(struct stasis_app_playback *playback)
652 {
653         SCOPED_AO2LOCK(lock, playback);
654
655         if (!playback->controllable) {
656                 return -1;
657         }
658
659         return stasis_app_control_queue_control(playback->control,
660                 AST_CONTROL_STREAM_FORWARD);
661 }
662
663 /*!
664  * \brief A sparse array detailing how commands should be handled in the
665  * various playback states. Unset entries imply invalid operations.
666  */
667 playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
668         [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
669         [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
670
671         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
672         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
673         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
674         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
675         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
676         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
677
678         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_STOP] = playback_stop,
679         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_RESTART] = playback_restart,
680         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_PAUSE] = playback_pause,
681         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
682         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
683         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_FORWARD] = playback_forward,
684
685         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
686         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
687         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
688
689         [STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
690         [STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
691         [STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
692 };
693
694 enum stasis_playback_oper_results stasis_app_playback_operation(
695         struct stasis_app_playback *playback,
696         enum stasis_app_playback_media_operation operation)
697 {
698         playback_opreation_cb cb;
699         SCOPED_AO2LOCK(lock, playback);
700
701         ast_assert((unsigned int)playback->state < STASIS_PLAYBACK_STATE_MAX);
702
703         if (operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
704                 ast_log(LOG_ERROR, "Invalid playback operation %u\n", operation);
705                 return -1;
706         }
707
708         cb = operations[playback->state][operation];
709
710         if (!cb) {
711                 if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
712                         /* So we can be specific in our error message. */
713                         return STASIS_PLAYBACK_OPER_NOT_PLAYING;
714                 } else {
715                         /* And, really, all operations should be valid during
716                          * playback */
717                         ast_log(LOG_ERROR,
718                                 "Unhandled operation during playback: %u\n",
719                                 operation);
720                         return STASIS_PLAYBACK_OPER_FAILED;
721                 }
722         }
723
724         return cb(playback) ?
725                 STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
726 }
727
728 static int load_module(void)
729 {
730         int r;
731
732         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
733         if (r != 0) {
734                 return AST_MODULE_LOAD_DECLINE;
735         }
736
737         playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash,
738                 playback_cmp);
739         if (!playbacks) {
740                 STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
741                 return AST_MODULE_LOAD_DECLINE;
742         }
743         return AST_MODULE_LOAD_SUCCESS;
744 }
745
746 static int unload_module(void)
747 {
748         ao2_cleanup(playbacks);
749         playbacks = NULL;
750         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
751         return 0;
752 }
753
754 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application playback support",
755         .support_level = AST_MODULE_SUPPORT_CORE,
756         .load = load_module,
757         .unload = unload_module,
758         .nonoptreq = "res_stasis,res_stasis_recording"
759 );