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