build: Update config.guess and config.sub
[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", 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                 if (AST_VECTOR_APPEND(&playback->medias, media_uri)) {
501                         ao2_ref(playback, -1);
502                         ast_free(media_uri);
503                         return NULL;
504                 }
505         }
506
507         if (skipms == 0) {
508                 skipms = PLAYBACK_DEFAULT_SKIPMS;
509         }
510
511         ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, 0));
512         ast_string_field_set(playback, language, language);
513         set_target_uri(playback, target_type, target_id);
514         playback->skipms = skipms;
515         playback->offsetms = offsetms;
516         ao2_link(playbacks, playback);
517
518         playback->state = STASIS_PLAYBACK_STATE_QUEUED;
519         playback_publish(playback);
520
521         stasis_app_send_command_async(control, play_uri, ao2_bump(playback), remove_from_playbacks);
522
523         return playback;
524 }
525
526 enum stasis_app_playback_state stasis_app_playback_get_state(
527         struct stasis_app_playback *control)
528 {
529         SCOPED_AO2LOCK(lock, control);
530         return control->state;
531 }
532
533 const char *stasis_app_playback_get_id(
534         struct stasis_app_playback *control)
535 {
536         /* id is immutable; no lock needed */
537         return control->id;
538 }
539
540 struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
541 {
542         return ao2_find(playbacks, id, OBJ_KEY);
543 }
544
545 struct ast_json *stasis_app_playback_to_json(
546         const struct stasis_app_playback *playback)
547 {
548         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
549
550         if (playback == NULL) {
551                 return NULL;
552         }
553
554         if (playback->media_index == AST_VECTOR_SIZE(&playback->medias) - 1) {
555                 json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
556                         "id", playback->id,
557                         "media_uri", playback->media,
558                         "target_uri", playback->target,
559                         "language", playback->language,
560                         "state", state_to_string(playback->state));
561         } else {
562                 json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s}",
563                         "id", playback->id,
564                         "media_uri", playback->media,
565                         "next_media_uri", AST_VECTOR_GET(&playback->medias, playback->media_index + 1),
566                         "target_uri", playback->target,
567                         "language", playback->language,
568                         "state", state_to_string(playback->state));
569         }
570
571         return ast_json_ref(json);
572 }
573
574 typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
575
576 static int playback_noop(struct stasis_app_playback *playback)
577 {
578         return 0;
579 }
580
581 static int playback_cancel(struct stasis_app_playback *playback)
582 {
583         SCOPED_AO2LOCK(lock, playback);
584         playback->state = STASIS_PLAYBACK_STATE_CANCELED;
585         return 0;
586 }
587
588 static int playback_stop(struct stasis_app_playback *playback)
589 {
590         SCOPED_AO2LOCK(lock, playback);
591
592         if (!playback->controllable) {
593                 return -1;
594         }
595
596         playback->state = STASIS_PLAYBACK_STATE_STOPPED;
597         return stasis_app_control_queue_control(playback->control,
598                 AST_CONTROL_STREAM_STOP);
599 }
600
601 static int playback_restart(struct stasis_app_playback *playback)
602 {
603         SCOPED_AO2LOCK(lock, playback);
604
605         if (!playback->controllable) {
606                 return -1;
607         }
608
609         return stasis_app_control_queue_control(playback->control,
610                 AST_CONTROL_STREAM_RESTART);
611 }
612
613 static int playback_pause(struct stasis_app_playback *playback)
614 {
615         SCOPED_AO2LOCK(lock, playback);
616
617         if (!playback->controllable) {
618                 return -1;
619         }
620
621         playback->state = STASIS_PLAYBACK_STATE_PAUSED;
622         playback_publish(playback);
623
624         return stasis_app_control_queue_control(playback->control,
625                 AST_CONTROL_STREAM_SUSPEND);
626 }
627
628 static int playback_unpause(struct stasis_app_playback *playback)
629 {
630         SCOPED_AO2LOCK(lock, playback);
631
632         if (!playback->controllable) {
633                 return -1;
634         }
635
636         playback->state = STASIS_PLAYBACK_STATE_PLAYING;
637         playback_publish(playback);
638
639         return stasis_app_control_queue_control(playback->control,
640                 AST_CONTROL_STREAM_SUSPEND);
641 }
642
643 static int playback_reverse(struct stasis_app_playback *playback)
644 {
645         SCOPED_AO2LOCK(lock, playback);
646
647         if (!playback->controllable) {
648                 return -1;
649         }
650
651         return stasis_app_control_queue_control(playback->control,
652                 AST_CONTROL_STREAM_REVERSE);
653 }
654
655 static int playback_forward(struct stasis_app_playback *playback)
656 {
657         SCOPED_AO2LOCK(lock, playback);
658
659         if (!playback->controllable) {
660                 return -1;
661         }
662
663         return stasis_app_control_queue_control(playback->control,
664                 AST_CONTROL_STREAM_FORWARD);
665 }
666
667 /*!
668  * \brief A sparse array detailing how commands should be handled in the
669  * various playback states. Unset entries imply invalid operations.
670  */
671 playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
672         [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
673         [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
674
675         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
676         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
677         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
678         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
679         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
680         [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
681
682         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_STOP] = playback_stop,
683         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_RESTART] = playback_restart,
684         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_PAUSE] = playback_pause,
685         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
686         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
687         [STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_FORWARD] = playback_forward,
688
689         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
690         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
691         [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
692
693         [STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
694         [STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
695         [STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
696 };
697
698 enum stasis_playback_oper_results stasis_app_playback_operation(
699         struct stasis_app_playback *playback,
700         enum stasis_app_playback_media_operation operation)
701 {
702         playback_opreation_cb cb;
703         SCOPED_AO2LOCK(lock, playback);
704
705         ast_assert((unsigned int)playback->state < STASIS_PLAYBACK_STATE_MAX);
706
707         if (operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
708                 ast_log(LOG_ERROR, "Invalid playback operation %u\n", operation);
709                 return -1;
710         }
711
712         cb = operations[playback->state][operation];
713
714         if (!cb) {
715                 if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
716                         /* So we can be specific in our error message. */
717                         return STASIS_PLAYBACK_OPER_NOT_PLAYING;
718                 } else {
719                         /* And, really, all operations should be valid during
720                          * playback */
721                         ast_log(LOG_ERROR,
722                                 "Unhandled operation during playback: %u\n",
723                                 operation);
724                         return STASIS_PLAYBACK_OPER_FAILED;
725                 }
726         }
727
728         return cb(playback) ?
729                 STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
730 }
731
732 static int load_module(void)
733 {
734         int r;
735
736         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
737         if (r != 0) {
738                 return AST_MODULE_LOAD_DECLINE;
739         }
740
741         playbacks = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, PLAYBACK_BUCKETS,
742                 playback_hash, NULL, playback_cmp);
743         if (!playbacks) {
744                 STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
745                 return AST_MODULE_LOAD_DECLINE;
746         }
747         return AST_MODULE_LOAD_SUCCESS;
748 }
749
750 static int unload_module(void)
751 {
752         ao2_cleanup(playbacks);
753         playbacks = NULL;
754         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
755         return 0;
756 }
757
758 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application playback support",
759         .support_level = AST_MODULE_SUPPORT_CORE,
760         .load = load_module,
761         .unload = unload_module,
762         .requires = "res_stasis,res_stasis_recording"
763 );