This patch implements the REST API's for POST /channels/{channelId}/play
[asterisk/asterisk.git] / res / res_stasis_playback.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief res_stasis playback support.
22  *
23  * \author David M. Lee, II <dlee@digium.com>
24  */
25
26 /*** MODULEINFO
27         <depend type="module">res_stasis</depend>
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include "asterisk/app.h"
36 #include "asterisk/astobj2.h"
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/module.h"
40 #include "asterisk/stasis_app_impl.h"
41 #include "asterisk/stasis_app_playback.h"
42 #include "asterisk/stasis_channels.h"
43 #include "asterisk/stringfields.h"
44 #include "asterisk/uuid.h"
45
46 /*! Number of hash buckets for playback container. Keep it prime! */
47 #define PLAYBACK_BUCKETS 127
48
49 /*! Number of milliseconds of media to skip */
50 #define PLAYBACK_SKIPMS 250
51
52 #define SOUND_URI_SCHEME "sound:"
53 #define RECORDING_URI_SCHEME "recording:"
54
55 STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type);
56
57 /*! Container of all current playbacks */
58 static struct ao2_container *playbacks;
59
60 /*! Playback control object for res_stasis */
61 struct stasis_app_playback {
62         AST_DECLARE_STRING_FIELDS(
63                 AST_STRING_FIELD(id);   /*!< Playback unique id */
64                 AST_STRING_FIELD(media);        /*!< Playback media uri */
65                 AST_STRING_FIELD(language);     /*!< Preferred language */
66                 );
67         /*! Current playback state */
68         enum stasis_app_playback_state state;
69         /*! Control object for the channel we're playing back to */
70         struct stasis_app_control *control;
71 };
72
73 static int playback_hash(const void *obj, int flags)
74 {
75         const struct stasis_app_playback *playback = obj;
76         const char *id = flags & OBJ_KEY ? obj : playback->id;
77         return ast_str_hash(id);
78 }
79
80 static int playback_cmp(void *obj, void *arg, int flags)
81 {
82         struct stasis_app_playback *lhs = obj;
83         struct stasis_app_playback *rhs = arg;
84         const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
85
86         if (strcmp(lhs->id, rhs_id) == 0) {
87                 return CMP_MATCH | CMP_STOP;
88         } else {
89                 return 0;
90         }
91 }
92
93 static const char *state_to_string(enum stasis_app_playback_state state)
94 {
95         switch (state) {
96         case STASIS_PLAYBACK_STATE_QUEUED:
97                 return "queued";
98         case STASIS_PLAYBACK_STATE_PLAYING:
99                 return "playing";
100         case STASIS_PLAYBACK_STATE_COMPLETE:
101                 return "done";
102         }
103
104         return "?";
105 }
106
107 static struct ast_json *playback_to_json(struct stasis_app_playback *playback)
108 {
109         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
110
111         if (playback == NULL) {
112                 return NULL;
113         }
114
115         json = ast_json_pack("{s: s, s: s, s: s, s: s}",
116                 "id", playback->id,
117                 "media_uri", playback->media,
118                 "language", playback->language,
119                 "state", state_to_string(playback->state));
120
121         return ast_json_ref(json);
122 }
123
124 static void playback_publish(struct stasis_app_playback *playback)
125 {
126         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
127         RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
128         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
129
130         ast_assert(playback != NULL);
131
132         json = playback_to_json(playback);
133         if (json == NULL) {
134                 return;
135         }
136
137         message = ast_channel_blob_create_from_cache(
138                 stasis_app_control_get_channel_id(playback->control),
139                 stasis_app_playback_snapshot_type(), json);
140         if (message == NULL) {
141                 return;
142         }
143
144         stasis_app_control_publish(playback->control, message);
145 }
146
147 static void playback_set_state(struct stasis_app_playback *playback,
148         enum stasis_app_playback_state state)
149 {
150         SCOPED_AO2LOCK(lock, playback);
151
152         playback->state = state;
153         playback_publish(playback);
154 }
155
156 static void playback_cleanup(struct stasis_app_playback *playback)
157 {
158         playback_set_state(playback, STASIS_PLAYBACK_STATE_COMPLETE);
159
160         ao2_unlink_flags(playbacks, playback,
161                 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
162 }
163
164 static void *__app_control_play_uri(struct stasis_app_control *control,
165         struct ast_channel *chan, void *data)
166 {
167         RAII_VAR(struct stasis_app_playback *, playback, NULL,
168                 playback_cleanup);
169         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
170         const char *file;
171         int res;
172         /* Even though these local variables look fairly pointless, the avoid
173          * having a bunch of NULL's passed directly into
174          * ast_control_streamfile() */
175         const char *fwd = NULL;
176         const char *rev = NULL;
177         const char *stop = NULL;
178         const char *pause = NULL;
179         const char *restart = NULL;
180         int skipms = PLAYBACK_SKIPMS;
181         long offsetms = 0;
182
183         playback = data;
184         ast_assert(playback != NULL);
185
186         playback_set_state(playback, STASIS_PLAYBACK_STATE_PLAYING);
187
188         if (ast_channel_state(chan) != AST_STATE_UP) {
189                 ast_answer(chan);
190         }
191
192         if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
193                 /* Play sound */
194                 file = playback->media + strlen(SOUND_URI_SCHEME);
195         } else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
196                 /* Play recording */
197                 file = playback->media + strlen(RECORDING_URI_SCHEME);
198         } else {
199                 /* Play URL */
200                 ast_log(LOG_ERROR, "Unimplemented\n");
201                 return NULL;
202         }
203
204         res = ast_control_streamfile(chan, file, fwd, rev, stop, pause,
205                 restart, skipms, &offsetms);
206
207         if (res != 0) {
208                 ast_log(LOG_WARNING, "%s: Playback failed for %s",
209                         ast_channel_uniqueid(chan), playback->media);
210         }
211
212         return NULL;
213 }
214
215 static void playback_dtor(void *obj)
216 {
217         struct stasis_app_playback *playback = obj;
218
219         ast_string_field_free_memory(playback);
220 }
221
222 struct stasis_app_playback *stasis_app_control_play_uri(
223         struct stasis_app_control *control, const char *uri,
224         const char *language)
225 {
226         RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
227         char id[AST_UUID_STR_LEN];
228
229         ast_debug(3, "%s: Sending play(%s) command\n",
230                 stasis_app_control_get_channel_id(control), uri);
231
232         playback = ao2_alloc(sizeof(*playback), playback_dtor);
233         if (!playback || ast_string_field_init(playback, 128) ){
234                 return NULL;
235         }
236
237         ast_uuid_generate_str(id, sizeof(id));
238         ast_string_field_set(playback, id, id);
239         ast_string_field_set(playback, media, uri);
240         ast_string_field_set(playback, language, language);
241         playback->control = control;
242         ao2_link(playbacks, playback);
243
244         playback_set_state(playback, STASIS_PLAYBACK_STATE_QUEUED);
245
246         ao2_ref(playback, +1);
247         stasis_app_send_command_async(
248                 control, __app_control_play_uri, playback);
249
250
251         ao2_ref(playback, +1);
252         return playback;
253 }
254
255 enum stasis_app_playback_state stasis_app_playback_get_state(
256         struct stasis_app_playback *control)
257 {
258         SCOPED_AO2LOCK(lock, control);
259         return control->state;
260 }
261
262 const char *stasis_app_playback_get_id(
263         struct stasis_app_playback *control)
264 {
265         /* id is immutable; no lock needed */
266         return control->id;
267 }
268
269 struct ast_json *stasis_app_playback_find_by_id(const char *id)
270 {
271         RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
272         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
273
274         playback = ao2_find(playbacks, id, OBJ_KEY);
275         if (playback == NULL) {
276                 return NULL;
277         }
278
279         json = playback_to_json(playback);
280         return ast_json_ref(json);
281 }
282
283 int stasis_app_playback_control(struct stasis_app_playback *playback,
284         enum stasis_app_playback_media_control control)
285 {
286         SCOPED_AO2LOCK(lock, playback);
287         ast_assert(0); /* TODO */
288         return -1;
289 }
290
291 static int load_module(void)
292 {
293         int r;
294
295         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
296         if (r != 0) {
297                 return AST_MODULE_LOAD_FAILURE;
298         }
299
300         playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash,
301                 playback_cmp);
302         if (!playbacks) {
303                 return AST_MODULE_LOAD_FAILURE;
304         }
305         return AST_MODULE_LOAD_SUCCESS;
306 }
307
308 static int unload_module(void)
309 {
310         ao2_cleanup(playbacks);
311         playbacks = NULL;
312         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
313         return 0;
314 }
315
316 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS,
317         "Stasis application playback support",
318         .load = load_module,
319         .unload = unload_module,
320         .nonoptreq = "res_stasis");