1 /*
2  * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3  * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
4  *
5  * Version: MPL 1.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18  *
19  * The Initial Developer of the Original Code is
20  * Anthony Minessale II <anthm@freeswitch.org>
21  * Portions created by the Initial Developer are Copyright (C)
22  * the Initial Developer. All Rights Reserved.
23  *
24  * Contributor(s):
25  *
26  * Anthony Minessale II <anthm@freeswitch.org>
27  * Neal Horman <neal at wanlink dot com>
28  * Bret McDanel <trixter at 0xdecafbad dot com>
29  * Dale Thatcher <freeswitch at dalethatcher dot com>
30  * Chris Danielson <chris at maxpowersoft dot com>
31  * Rupa Schomaker <rupa@rupa.com>
32  * David Weekly <david@weekly.org>
33  * Joao Mesquita <jmesquita@gmail.com>
34  * Raymond Chandler <intralanman@freeswitch.org>
35  * Seven Du <dujinfang@gmail.com>
36  * Emmanuel Schmidbauer <e.schmidbauer@gmail.com>
37  * William King <william.king@quentustech.com>
38  *
39  * mod_conference.c -- Software Conference Bridge
40  *
41  */
42 #include <mod_conference.h>
43 
44 
get_canvas_info(mcu_canvas_t * canvas)45 static cJSON *get_canvas_info(mcu_canvas_t *canvas)
46 {
47 	cJSON *obj = cJSON_CreateObject();
48 
49 	cJSON_AddItemToObject(obj, "canvasID", cJSON_CreateNumber(canvas->canvas_id));
50 	cJSON_AddItemToObject(obj, "totalLayers", cJSON_CreateNumber(canvas->total_layers));
51 	cJSON_AddItemToObject(obj, "layersUsed", cJSON_CreateNumber(canvas->layers_used));
52 	cJSON_AddItemToObject(obj, "layoutFloorID", cJSON_CreateNumber(canvas->layout_floor_id));
53 	if (canvas->vlayout) {
54 		cJSON_AddItemToObject(obj, "layoutName", cJSON_CreateString(canvas->vlayout->name));
55 	}
56 
57 	return obj;
58 }
59 
60 
conference_event_mod_channel_handler(const char * event_channel,cJSON * json,const char * key,switch_event_channel_id_t id,void * user_data)61 void conference_event_mod_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id, void *user_data)
62 {
63 	cJSON *data, *addobj = NULL;
64 	const char *action = NULL;
65 	char *value = NULL;
66 	cJSON *jid = 0;
67 	char *conference_name = strdup(event_channel + 15);
68 	char cid[32] = "";
69 	char *p;
70 	switch_stream_handle_t stream = { 0 };
71 	char *exec = NULL;
72 	cJSON *msg, *jdata, *jvalue;
73 	char *argv[10] = {0};
74 	int argc = 0;
75 
76 	if (conference_name && (p = strchr(conference_name, '@'))) {
77 		*p = '\0';
78 	}
79 
80 	if ((data = cJSON_GetObjectItem(json, "data"))) {
81 		action = cJSON_GetObjectCstr(data, "command");
82 
83 		if ((jid = cJSON_GetObjectItem(data, "id"))) {
84 			if (jid->valueint) {
85 				switch_snprintf(cid, sizeof(cid), "%d", jid->valueint);
86 			} else if (!zstr(jid->valuestring)) {
87 				switch_snprintf(cid, sizeof(cid), "%s", jid->valuestring);
88 			}
89 		}
90 
91 		if ((jvalue = cJSON_GetObjectItem(data, "value"))) {
92 
93 			if (jvalue->type == cJSON_Array) {
94 				int i;
95 				argc = cJSON_GetArraySize(jvalue);
96 				if (argc > 10) argc = 10;
97 
98 				for (i = 0; i < argc; i++) {
99 					cJSON *str = cJSON_GetArrayItem(jvalue, i);
100 					if (str->type == cJSON_String) {
101 						argv[i] = str->valuestring;
102 					}
103 				}
104 			} else if (jvalue->type == cJSON_String) {
105 				value = jvalue->valuestring;
106 				argv[argc++] = value;
107 			}
108 		}
109 	}
110 
111 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "conf %s CMD %s [%s] %s\n", conference_name, key, action ? action : "N/A", cid);
112 
113 	if (zstr(action)) {
114 		goto end;
115 	}
116 
117 	SWITCH_STANDARD_STREAM(stream);
118 
119 	if (!strcasecmp(action, "kick")) {
120 		exec = switch_mprintf("%s %s %s", conference_name, action, cid);
121 	} else if (!strcasecmp(action, "mute") ||
122 		!strcasecmp(action, "unmute") ||
123 		!strcasecmp(action, "tmute") ||
124 		!strcasecmp(action, "deaf") ||
125 		!strcasecmp(action, "undeaf") ||
126 		!strcasecmp(action, "vmute") ||
127 		!strcasecmp(action, "unvmute") ||
128 		!strcasecmp(action, "tvmute")) {
129 		if (argv[0]) {
130 		  exec = switch_mprintf("%s %s %s %s", conference_name, action, cid, argv[0]);
131 		} else {
132 		  exec = switch_mprintf("%s %s %s", conference_name, action, cid);
133 		}
134 	} else if (!strcasecmp(action, "volume_in") ||
135 			   !strcasecmp(action, "volume_out") ||
136 			   !strcasecmp(action, "vid-res-id") ||
137 			   !strcasecmp(action, "vid-floor") ||
138 			   !strcasecmp(action, "vid-layer") ||
139 			   !strcasecmp(action, "vid-canvas") ||
140 			   !strcasecmp(action, "vid-watching-canvas") ||
141 			   !strcasecmp(action, "vid-banner")) {
142 		exec = switch_mprintf("%s %s %s %s", conference_name, action, cid, argv[0]);
143 	} else if (!strcasecmp(action, "play") || !strcasecmp(action, "stop")) {
144 		exec = switch_mprintf("%s %s %s", conference_name, action, argv[0]);
145 	} else if (!strcasecmp(action, "recording") || !strcasecmp(action, "vid-layout") || !strcasecmp(action, "vid-write-png")) {
146 
147 		if (!argv[1]) {
148 			argv[1] = "all";
149 		}
150 
151 		exec = switch_mprintf("%s %s %s %s", conference_name, action, argv[0], argv[1]);
152 
153 	} else if (!strcasecmp(action, "transfer")) {
154 		conference_member_t *member;
155 		conference_obj_t *conference;
156 
157 		if (cid[0] == '\0') {
158 			stream.write_function(&stream, "-ERR Call transfer requires id");
159 			goto end;
160 		}
161 
162 		exec = switch_mprintf("%s %s %s", argv[0], switch_str_nil(argv[1]), switch_str_nil(argv[2]));
163 		stream.write_function(&stream, "+OK Call transferred to %s", argv[0]);
164 
165 		if ((conference = conference_find(conference_name, NULL))) {
166 			if ((member = conference_member_get(conference, atoi(cid)))) {
167 				switch_ivr_session_transfer(member->session, argv[0], argv[1], argv[2]);
168 				switch_thread_rwlock_unlock(member->rwlock);
169 			}
170 			switch_thread_rwlock_unlock(conference->rwlock);
171 		}
172 		goto end;
173 	} else if (!strcasecmp(action, "canvasInfo")) {
174 		cJSON *j_member_id;
175 		int member_id = 0;
176 		int i = 0;
177 		cJSON *array = cJSON_CreateArray();
178 		conference_obj_t *conference;
179 
180  		if ((conference = conference_find(conference_name, NULL))) {
181 
182 			if ((j_member_id = cJSON_GetObjectItem(data, "memberID"))) {
183 				if (j_member_id->valueint) {
184 					member_id = j_member_id->valueint;
185 				} else if (j_member_id->valuedouble) {
186 					member_id = (int) j_member_id->valuedouble;
187 				} else if (j_member_id->valuestring) {
188 					member_id = atoi(j_member_id->valuestring);
189 				}
190 				if (member_id < 0) member_id = 0;
191 			}
192 
193 			if (member_id > 0) {
194 				conference_member_t *member;
195 
196 				if ((member = conference_member_get(conference, member_id))) {
197 					mcu_canvas_t *canvas;
198 
199 					if ((canvas = conference_video_get_canvas_locked(member))) {
200 						cJSON *obj;
201 
202 						if ((obj = get_canvas_info(canvas))) {
203 							cJSON_AddItemToObject(obj, "layerID", cJSON_CreateNumber(member->video_layer_id));
204 							cJSON_AddItemToArray(array, obj);
205 						}
206 
207 						conference_video_release_canvas(&canvas);
208 					}
209 
210 					switch_thread_rwlock_unlock(member->rwlock);
211 				}
212 
213 			} else {
214 				switch_mutex_lock(conference->canvas_mutex);
215 
216 				for (i = 0; i <= conference->canvas_count; i++) {
217 					mcu_canvas_t *canvas = conference->canvases[i];
218 					if (canvas) {
219 						cJSON *obj;
220 
221 						if ((obj = get_canvas_info(canvas))) {
222 							cJSON_AddItemToArray(array, obj);
223 						}
224 					}
225 				}
226 
227 				switch_mutex_unlock(conference->canvas_mutex);
228 			}
229 
230 			switch_thread_rwlock_unlock(conference->rwlock);
231 		}
232 
233 		addobj = array;
234 
235 	} else if (!strcasecmp(action, "list-videoLayouts")) {
236 		switch_hash_index_t *hi;
237 		void *val;
238 		const void *vvar;
239 		cJSON *array = cJSON_CreateArray();
240 		conference_obj_t *conference = NULL;
241 		int i;
242 
243 		if ((conference = conference_find(conference_name, NULL))) {
244 			switch_mutex_lock(conference_globals.setup_mutex);
245 
246 			for (i = 0; i <= conference->canvas_count; i++) {
247 				if (conference->canvases[i]) {
248 					conference_event_adv_layout(conference, conference->canvases[i], conference->canvases[i]->vlayout);
249 				}
250 			}
251 
252 			if (conference->layout_hash) {
253 				for (hi = switch_core_hash_first(conference->layout_hash); hi; hi = switch_core_hash_next(&hi)) {
254 					video_layout_t *vlayout;
255 					cJSON *obj = cJSON_CreateObject();
256 					cJSON *resarray = cJSON_CreateArray();
257 
258 					switch_core_hash_this(hi, &vvar, NULL, &val);
259 					vlayout = (video_layout_t *)val;
260 					for (i = 0; i < vlayout->layers; i++) {
261 						if (vlayout->images[i].res_id) {
262 							cJSON_AddItemToArray(resarray, cJSON_CreateString((char *)vlayout->images[i].res_id));
263 						}
264 					}
265 
266 					cJSON_AddItemToObject(obj, "type", cJSON_CreateString("layout"));
267 					cJSON_AddItemToObject(obj, "name", cJSON_CreateString((char *)vvar));
268 					cJSON_AddItemToObject(obj, "resIDS", resarray);
269 
270 					cJSON_AddItemToArray(array, obj);
271 				}
272 			}
273 
274 			if (conference->layout_group_hash) {
275 				for (hi = switch_core_hash_first(conference->layout_group_hash); hi; hi = switch_core_hash_next(&hi)) {
276 					char *name;
277 					cJSON *obj = cJSON_CreateObject();
278 					cJSON *grouparray = cJSON_CreateArray();
279 					layout_group_t *lg;
280 					video_layout_node_t *vlnode;
281 
282 					switch_core_hash_this(hi, &vvar, NULL, &val);
283 					lg = (layout_group_t *) val;
284 
285 					name = switch_mprintf("group:%s", (char *)vvar);
286 
287 					for (vlnode = lg->layouts; vlnode; vlnode = vlnode->next) {
288 						cJSON_AddItemToArray(grouparray, cJSON_CreateString(vlnode->vlayout->name));
289 					}
290 
291 					cJSON_AddItemToObject(obj, "type", cJSON_CreateString("layoutGroup"));
292 					cJSON_AddItemToObject(obj, "name", cJSON_CreateString(name));
293 					cJSON_AddItemToObject(obj, "groupLayouts", grouparray);
294 
295 					cJSON_AddItemToArray(array, obj);
296 					free(name);
297 				}
298 			}
299 
300 			switch_mutex_unlock(conference_globals.setup_mutex);
301 			switch_thread_rwlock_unlock(conference->rwlock);
302 		}
303 		addobj = array;
304 	} else if (!strcasecmp(action, "click-layer")) {
305 	} else if (!strcasecmp(action, "shift-click-layer")) {
306 	} else if (!strcasecmp(action, "reset-layer") || !strcasecmp(action, "layer-pan-x") || !strcasecmp(action, "layer-pan-y")) {
307 		cJSON *v;
308 		int layer_id = 0, canvas_id = 0, metric = 0, absolute = 0;
309 		const char *i = "i", *xy = "";
310 
311 		if ((v = cJSON_GetObjectItem(data, "layerID"))) {
312 			layer_id = v->valueint;
313 		}
314 
315 		if ((v = cJSON_GetObjectItem(data, "canvasID"))) {
316 			canvas_id = v->valueint;
317 		}
318 
319 		if ((v = cJSON_GetObjectItem(data, "metric"))) {
320 			metric = v->valueint;
321 		}
322 
323 		if ((v = cJSON_GetObjectItem(data, "absolute"))) {
324 			if ((absolute = v->valueint)) {
325 				i = "";
326 			}
327 		}
328 
329 		if (canvas_id > -1 && layer_id > -1) {
330 			if (!strcasecmp(action, "layer-pan-x")) {
331 				xy = "x";
332 			} else if (!strcasecmp(action, "layer-pan-y")) {
333 				xy = "y";
334 			}
335 
336 			if (!strcasecmp(action, "reset-layer")) {
337 				exec = switch_mprintf("%s cam %d %d reset", conference_name, canvas_id, layer_id);
338 			} else {
339 				exec = switch_mprintf("%s cam %d %d pan=%s:%d%s", conference_name, canvas_id, layer_id, xy, metric, i);
340 			}
341 		}
342 
343 
344 	} else if (!strcasecmp(action, "zoom-layer")) {
345 		cJSON *v;
346 		int layer_id = -1, canvas_id = -1, x = -1, y = -1, w = -1, h = -1;
347 
348 		if ((v = cJSON_GetObjectItem(data, "layerID"))) {
349 			layer_id = v->valueint;
350 		}
351 
352 		if ((v = cJSON_GetObjectItem(data, "canvasID"))) {
353 			canvas_id = v->valueint;
354 		}
355 
356 
357 		if ((v = cJSON_GetObjectItem(data, "dimensions"))) {
358 			cJSON *d;
359 
360 			if ((d = cJSON_GetObjectItem(v, "w"))) {
361 				w = d->valueint;
362 			}
363 
364 			if ((d = cJSON_GetObjectItem(v, "h"))) {
365 				h = d->valueint;
366 			}
367 
368 			if ((d = cJSON_GetObjectItem(v, "x"))) {
369 				x = d->valueint;
370 			}
371 
372 			if ((d = cJSON_GetObjectItem(v, "y"))) {
373 				y = d->valueint;
374 			}
375 		}
376 
377 		if (canvas_id > -1 && layer_id > -1 && x > -1 && y > -1 && w > -1 && h > -1) {
378 			exec = switch_mprintf("%s cam %d %d zoom=%d:%d:%d:%d snap_factor=1 zoom_factor=1", conference_name, canvas_id, layer_id, x, y, w, h);
379 		}
380 
381 	}
382 
383 	if (exec) {
384 		conference_api_main_real(exec, NULL, &stream);
385 	}
386 
387  end:
388 
389 	msg = cJSON_CreateObject();
390 	jdata = json_add_child_obj(msg, "data", NULL);
391 
392 	cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(event_channel));
393 	cJSON_AddItemToObject(jdata, "action", cJSON_CreateString("response"));
394 
395 	if (addobj) {
396 		cJSON_AddItemToObject(jdata, "conf-command", cJSON_CreateString(action));
397 		cJSON_AddItemToObject(jdata, "response", cJSON_CreateString("OK"));
398 		cJSON_AddItemToObject(jdata, "responseData", addobj);
399 	} else if (exec) {
400 		cJSON_AddItemToObject(jdata, "conf-command", cJSON_CreateString(exec));
401 		cJSON_AddItemToObject(jdata, "response", cJSON_CreateString((char *)stream.data));
402 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,"RES [%s][%s]\n", exec, (char *)stream.data);
403 	} else {
404 		cJSON_AddItemToObject(jdata, "error", cJSON_CreateString("Invalid Command"));
405 	}
406 
407 	switch_event_channel_broadcast(event_channel, &msg, __FILE__, conference_globals.event_channel_id);
408 
409 
410 	switch_safe_free(stream.data);
411 	switch_safe_free(exec);
412 
413 	switch_safe_free(conference_name);
414 
415 }
416 
conference_event_chat_channel_handler(const char * event_channel,cJSON * json,const char * key,switch_event_channel_id_t id,void * user_data)417 void conference_event_chat_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id, void *user_data)
418 {
419 	cJSON *data;
420 	cJSON *jid = 0;
421 	const char *type = NULL;
422 	const char *action = NULL;
423 	cJSON *msg;
424 	char *conference_name = strdup(event_channel + 15);
425 	char *message = NULL;
426 	cJSON *jdata;
427 	char *p;
428 	const char *uid = NULL;
429 	const char *display = NULL;
430 
431 	if (conference_name && (p = strchr(conference_name, '@'))) {
432 		*p = '\0';
433 	}
434 
435 	uid = cJSON_GetObjectCstr(json, "userid");
436 	display = cJSON_GetObjectCstr(json, "fromDisplay");
437 
438 	if ((data = cJSON_GetObjectItem(json, "data"))) {
439 		type = cJSON_GetObjectCstr(data, "type");
440 		action = cJSON_GetObjectCstr(data, "action");
441 		if ((jid = cJSON_GetObjectItem(data, "message"))) {
442 			if (!zstr(jid->valuestring)) {
443 				message = jid->valuestring;
444 			}
445 		}
446 	}
447 
448 	if (action && !strcasecmp(action, "send")) {
449 		msg = cJSON_CreateObject();
450 		jdata = json_add_child_obj(msg, "data", NULL);
451 
452 		cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(event_channel));
453 		cJSON_AddItemToObject(jdata, "direction", cJSON_CreateString("outbound"));
454 
455 		if (message) {
456 			cJSON_AddItemToObject(jdata, "message", cJSON_CreateString(message));
457 		}
458 
459 		if (display) {
460 			cJSON_AddItemToObject(jdata, "fromDisplay", cJSON_CreateString(display));
461 		}
462 
463 		if (uid) {
464 			cJSON_AddItemToObject(jdata, "from", cJSON_CreateString(uid));
465 		}
466 
467 		if (type) {
468 			cJSON_AddItemToObject(jdata, "type", cJSON_CreateString(type));
469 		} else {
470 			cJSON_AddItemToObject(jdata, "type", cJSON_CreateString("message"));
471 		}
472 
473 		switch_event_channel_broadcast(event_channel, &msg, __FILE__, conference_globals.event_channel_id);
474 	}
475 
476 	switch_safe_free(conference_name);
477 }
478 
conference_event_la_channel_handler(const char * event_channel,cJSON * json,const char * key,switch_event_channel_id_t id,void * user_data)479 void conference_event_la_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id, void *user_data)
480 {
481 	switch_live_array_parse_json(json, conference_globals.event_channel_id);
482 }
483 
conference_event_channel_handler(const char * event_channel,cJSON * json,const char * key,switch_event_channel_id_t id,void * user_data)484 void conference_event_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id, void *user_data)
485 {
486 	char *domain = NULL, *name = NULL;
487 	conference_obj_t *conference = NULL;
488 	cJSON *data, *reply = NULL, *conference_desc = NULL;
489 	const char *action = NULL;
490 	char *dup = NULL;
491 
492 	if ((data = cJSON_GetObjectItem(json, "data"))) {
493 		action = cJSON_GetObjectCstr(data, "action");
494 	}
495 
496 	if (!action) action = "";
497 
498 	reply = cJSON_Duplicate(json, 1);
499 	cJSON_DeleteItemFromObject(reply, "data");
500 
501 	if ((name = strchr(event_channel, '.'))) {
502 		dup = strdup(name + 1);
503 		switch_assert(dup);
504 		name = dup;
505 
506 		if ((domain = strchr(name, '@'))) {
507 			*domain++ = '\0';
508 		}
509 	}
510 
511 	if (!strcasecmp(action, "bootstrap")) {
512 		if (!zstr(name) && (conference = conference_find(name, domain))) {
513 			conference_desc = conference_cdr_json_render(conference, json);
514 		} else {
515 			conference_desc = cJSON_CreateObject();
516 			json_add_child_string(conference_desc, "conferenceDescription", "FreeSWITCH Conference");
517 			json_add_child_string(conference_desc, "conferenceState", "inactive");
518 			json_add_child_array(conference_desc, "users");
519 			json_add_child_array(conference_desc, "oldUsers");
520 		}
521 	} else {
522 		conference_desc = cJSON_CreateObject();
523 		json_add_child_string(conference_desc, "error", "Invalid action");
524 	}
525 
526 	json_add_child_string(conference_desc, "action", "conferenceDescription");
527 
528 	cJSON_AddItemToObject(reply, "data", conference_desc);
529 
530 	switch_safe_free(dup);
531 
532 	switch_event_channel_broadcast(event_channel, &reply, "mod_conference", conference_globals.event_channel_id);
533 }
534 
conference_event_send_json(conference_obj_t * conference)535 void conference_event_send_json(conference_obj_t *conference)
536 {
537 	cJSON *event, *conference_desc = NULL;
538 	char *name = NULL, *domain = NULL, *dup_domain = NULL;
539 	char *event_channel = NULL;
540 
541 	if (!conference_utils_test_flag(conference, CFLAG_JSON_EVENTS)) {
542 		return;
543 	}
544 
545 	conference_desc = conference_cdr_json_render(conference, NULL);
546 
547 	if (!(name = conference->name)) {
548 		name = "conference";
549 	}
550 
551 	if (!(domain = conference->domain)) {
552 		dup_domain = switch_core_get_domain(SWITCH_TRUE);
553 		if (!(domain = dup_domain)) {
554 			domain = "cluecon.com";
555 		}
556 	}
557 
558 	event_channel = switch_mprintf("conference.%q@%q", name, domain);
559 
560 	event = cJSON_CreateObject();
561 
562 	json_add_child_string(event, "eventChannel", event_channel);
563 	cJSON_AddItemToObject(event, "data", conference_desc);
564 
565 	switch_event_channel_broadcast(event_channel, &event, "mod_conference", conference_globals.event_channel_id);
566 
567 	switch_safe_free(dup_domain);
568 	switch_safe_free(event_channel);
569 }
570 
conference_event_la_command_handler(switch_live_array_t * la,const char * cmd,const char * sessid,cJSON * jla,void * user_data)571 void conference_event_la_command_handler(switch_live_array_t *la, const char *cmd, const char *sessid, cJSON *jla, void *user_data)
572 {
573 }
574 
conference_event_adv_layout(conference_obj_t * conference,mcu_canvas_t * canvas,video_layout_t * vlayout)575 void conference_event_adv_layout(conference_obj_t *conference, mcu_canvas_t *canvas, video_layout_t *vlayout)
576 {
577 	cJSON *msg, *data, *obj;
578 	int i = 0;
579 
580 	if (!conference->info_event_channel) {
581 		return;
582 	}
583 
584 	msg = cJSON_CreateObject();
585 	data = json_add_child_obj(msg, "eventData", NULL);
586 
587 	cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(conference->info_event_channel));
588 	cJSON_AddItemToObject(data, "contentType", cJSON_CreateString("layout-info"));
589 
590 	switch_mutex_lock(canvas->mutex);
591 
592 	if ((obj = get_canvas_info(canvas))) {
593 		cJSON *array = cJSON_CreateArray();
594 
595 		for (i = 0; i < vlayout->layers; i++) {
596 			cJSON *layout = cJSON_CreateObject();
597 			int scale = vlayout->images[i].scale;
598 			int hscale = vlayout->images[i].hscale ? vlayout->images[i].hscale : scale;
599 
600 			cJSON_AddItemToObject(layout, "x", cJSON_CreateNumber(vlayout->images[i].x));
601 			cJSON_AddItemToObject(layout, "y", cJSON_CreateNumber(vlayout->images[i].y));
602 			cJSON_AddItemToObject(layout, "scale", cJSON_CreateNumber(vlayout->images[i].scale));
603 			cJSON_AddItemToObject(layout, "hscale", cJSON_CreateNumber(hscale));
604 			cJSON_AddItemToObject(layout, "scale", cJSON_CreateNumber(scale));
605 			cJSON_AddItemToObject(layout, "zoom", cJSON_CreateNumber(vlayout->images[i].zoom));
606 			cJSON_AddItemToObject(layout, "border", cJSON_CreateNumber(vlayout->images[i].border));
607 			cJSON_AddItemToObject(layout, "floor", cJSON_CreateNumber(vlayout->images[i].floor));
608 			cJSON_AddItemToObject(layout, "overlap", cJSON_CreateNumber(vlayout->images[i].overlap));
609 			cJSON_AddItemToObject(layout, "screenWidth", cJSON_CreateNumber((uint32_t)(canvas->width * scale / VIDEO_LAYOUT_SCALE)));
610 			cJSON_AddItemToObject(layout, "screenHeight", cJSON_CreateNumber((uint32_t)(canvas->height * hscale / VIDEO_LAYOUT_SCALE)));
611 			cJSON_AddItemToObject(layout, "xPOS", cJSON_CreateNumber((int)(canvas->width * vlayout->images[i].x / VIDEO_LAYOUT_SCALE)));
612 			cJSON_AddItemToObject(layout, "yPOS", cJSON_CreateNumber((int)(canvas->height * vlayout->images[i].y / VIDEO_LAYOUT_SCALE)));
613 			cJSON_AddItemToObject(layout, "resID", cJSON_CreateString(vlayout->images[i].res_id));
614 			cJSON_AddItemToObject(layout, "audioPOS", cJSON_CreateString(vlayout->images[i].audio_position));
615 			cJSON_AddItemToArray(array, layout);
616 		}
617 
618 
619 		cJSON_AddItemToObject(obj, "canvasLayouts", array);
620 
621 		cJSON_AddItemToObject(obj, "scale", cJSON_CreateNumber(VIDEO_LAYOUT_SCALE));
622 		cJSON_AddItemToObject(data, "canvasInfo", obj);
623 	}
624 
625 	switch_mutex_unlock(canvas->mutex);
626 
627 	switch_event_channel_broadcast(conference->info_event_channel, &msg, "mod_conference", conference_globals.event_channel_id);
628 
629 }
630 
conference_event_adv_la(conference_obj_t * conference,conference_member_t * member,switch_bool_t join)631 void conference_event_adv_la(conference_obj_t *conference, conference_member_t *member, switch_bool_t join)
632 {
633 
634 	switch_channel_set_flag(member->channel, CF_VIDEO_REFRESH_REQ);
635 	switch_core_media_gen_key_frame(member->session);
636 
637 	if (conference && conference->la && member->session && !switch_channel_test_flag(member->channel, CF_VIDEO_ONLY)) {
638 		cJSON *msg, *data;
639 		const char *uuid = switch_core_session_get_uuid(member->session);
640 		const char *cookie = switch_channel_get_variable(member->channel, "event_channel_cookie");
641 		const char *event_channel = cookie ? cookie : uuid;
642 		switch_event_t *variables;
643 		switch_event_header_t *hp;
644 		char idstr[128] = "";
645 		int i;
646 
647 		snprintf(idstr, sizeof(idstr), "%d", member->id);
648 		msg = cJSON_CreateObject();
649 		data = json_add_child_obj(msg, "pvtData", NULL);
650 
651 		cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(event_channel));
652 		cJSON_AddItemToObject(msg, "eventType", cJSON_CreateString("channelPvtData"));
653 
654 		cJSON_AddStringToObject(data, "callID", switch_core_session_get_uuid(member->session));
655 		cJSON_AddItemToObject(data, "action", cJSON_CreateString(join ? "conference-liveArray-join" : "conference-liveArray-part"));
656 		cJSON_AddItemToObject(data, "laChannel", cJSON_CreateString(conference->la_event_channel));
657 		cJSON_AddItemToObject(data, "laName", cJSON_CreateString(conference->la_name));
658 		cJSON_AddItemToObject(data, "role", cJSON_CreateString(conference_utils_member_test_flag(member, MFLAG_MOD) ? "moderator" : "participant"));
659 		cJSON_AddItemToObject(data, "chatID", cJSON_CreateString(conference->chat_id));
660 		cJSON_AddItemToObject(data, "conferenceMemberID", cJSON_CreateString(idstr));
661 		cJSON_AddItemToObject(data, "canvasCount", cJSON_CreateNumber(conference->canvas_count));
662 
663 		if (conference_utils_member_test_flag(member, MFLAG_SECOND_SCREEN)) {
664 			cJSON_AddItemToObject(data, "secondScreen", cJSON_CreateTrue());
665 		}
666 
667 		if (conference_utils_member_test_flag(member, MFLAG_MOD)) {
668 			cJSON_AddItemToObject(data, "modChannel", cJSON_CreateString(conference->mod_event_channel));
669 		}
670 
671 		cJSON_AddItemToObject(data, "chatChannel", cJSON_CreateString(conference->chat_event_channel));
672 		cJSON_AddItemToObject(data, "infoChannel", cJSON_CreateString(conference->info_event_channel));
673 
674 		switch_core_get_variables(&variables);
675 		for (hp = variables->headers; hp; hp = hp->next) {
676 			if (!strncasecmp(hp->name, "conference_verto_", 17)) {
677 				char *var = hp->name + 17;
678 				if (var) {
679 					cJSON_AddItemToObject(data, var, cJSON_CreateString(hp->value));
680 				}
681 			}
682 		}
683 		switch_event_destroy(&variables);
684 
685 		if (cookie) {
686 			switch_event_channel_permission_modify(cookie, conference->la_event_channel, join);
687 			switch_event_channel_permission_modify(cookie, conference->mod_event_channel, join);
688 			switch_event_channel_permission_modify(cookie, conference->chat_event_channel, join);
689 			switch_event_channel_permission_modify(cookie, conference->info_event_channel, join);
690 		}
691 
692 		switch_event_channel_broadcast(event_channel, &msg, "mod_conference", conference_globals.event_channel_id);
693 
694 		for (i = 0; i <= conference->canvas_count; i++) {
695 			if (conference->canvases[i]) {
696 				conference_event_adv_layout(conference, conference->canvases[i], conference->canvases[i]->vlayout);
697 			}
698 		}
699 	}
700 }
701 
conference_event_send_rfc(conference_obj_t * conference)702 void conference_event_send_rfc(conference_obj_t *conference)
703 {
704 	switch_event_t *event;
705 	char *body;
706 	char *name = NULL, *domain = NULL, *dup_domain = NULL;
707 
708 	if (!conference_utils_test_flag(conference, CFLAG_RFC4579)) {
709 		return;
710 	}
711 
712 	if (!(name = conference->name)) {
713 		name = "conference";
714 	}
715 
716 	if (!(domain = conference->domain)) {
717 		dup_domain = switch_core_get_domain(SWITCH_TRUE);
718 		if (!(domain = dup_domain)) {
719 			domain = "cluecon.com";
720 		}
721 	}
722 
723 
724 	if (switch_event_create(&event, SWITCH_EVENT_CONFERENCE_DATA) == SWITCH_STATUS_SUCCESS) {
725 		event->flags |= EF_UNIQ_HEADERS;
726 
727 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-name", name);
728 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-domain", domain);
729 
730 		body = conference_cdr_rfc4579_render(conference, NULL, event);
731 		switch_event_add_body(event, "%s", body);
732 		free(body);
733 		switch_event_fire(&event);
734 	}
735 
736 	switch_safe_free(dup_domain);
737 
738 }
739 
740 
conference_event_add_data(conference_obj_t * conference,switch_event_t * event)741 switch_status_t conference_event_add_data(conference_obj_t *conference, switch_event_t *event)
742 {
743 	switch_status_t status = SWITCH_STATUS_SUCCESS;
744 
745 	switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
746 	switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Conference-Domain", conference->domain);
747 	switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Size", "%u", conference->count);
748 	switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Ghosts", "%u", conference->count_ghosts);
749 	switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Conference-Profile-Name", conference->profile_name);
750 	switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Conference-Unique-ID", conference->uuid_str);
751 	switch_event_merge(event, conference->variables);
752 
753 	return status;
754 }
755 
756 /* send a message to every member of the conference */
conference_event_chat_message_broadcast(conference_obj_t * conference,switch_event_t * event)757 void conference_event_chat_message_broadcast(conference_obj_t *conference, switch_event_t *event)
758 {
759 	conference_member_t *member = NULL;
760 	switch_event_t *processed;
761 
762 	switch_assert(conference != NULL);
763 	switch_event_create(&processed, SWITCH_EVENT_CHANNEL_DATA);
764 
765 	switch_mutex_lock(conference->member_mutex);
766 	for (member = conference->members; member; member = member->next) {
767 		if (member->session && !conference_utils_member_test_flag(member, MFLAG_NOCHANNEL)) {
768 			const char *presence_id = switch_channel_get_variable(member->channel, "presence_id");
769 			const char *chat_proto = switch_channel_get_variable(member->channel, "chat_proto");
770 			switch_event_t *reply = NULL;
771 
772 			if (presence_id && chat_proto) {
773 				if (switch_event_get_header(processed, presence_id)) {
774 					continue;
775 				}
776 				switch_event_dup(&reply, event);
777 				switch_event_add_header_string(reply, SWITCH_STACK_BOTTOM, "to", presence_id);
778 				switch_event_add_header_string(reply, SWITCH_STACK_BOTTOM, "conference_name", conference->name);
779 				switch_event_add_header_string(reply, SWITCH_STACK_BOTTOM, "conference_domain", conference->domain);
780 
781 				switch_event_set_body(reply, switch_event_get_body(event));
782 
783 				switch_core_chat_deliver(chat_proto, &reply);
784 				switch_event_add_header_string(processed, SWITCH_STACK_BOTTOM, presence_id, "true");
785 			}
786 		}
787 	}
788 	switch_event_destroy(&processed);
789 	switch_mutex_unlock(conference->member_mutex);
790 }
791 
conference_event_call_setup_handler(switch_event_t * event)792 void conference_event_call_setup_handler(switch_event_t *event)
793 {
794 	switch_status_t status = SWITCH_STATUS_FALSE;
795 	conference_obj_t *conference = NULL;
796 	char *conf = switch_event_get_header(event, "Target-Component");
797 	char *domain = switch_event_get_header(event, "Target-Domain");
798 	char *dial_str = switch_event_get_header(event, "Request-Target");
799 	char *dial_uri = switch_event_get_header(event, "Request-Target-URI");
800 	char *action = switch_event_get_header(event, "Request-Action");
801 	char *ext = switch_event_get_header(event, "Request-Target-Extension");
802 	char *ext_domain = switch_event_get_header(event, "Request-Target-Domain");
803 	char *full_url = switch_event_get_header(event, "full_url");
804 	char *call_id = switch_event_get_header(event, "Request-Call-ID");
805 
806 	if (!ext) ext = dial_str;
807 
808 	if (!zstr(conf) && !zstr(dial_str) && !zstr(action) && (conference = conference_find(conf, domain))) {
809 		switch_event_t *var_event;
810 		switch_event_header_t *hp;
811 
812 		if (conference_utils_test_flag(conference, CFLAG_RFC4579)) {
813 			char *key = switch_mprintf("conference_%s_%s_%s_%s", conference->name, conference->domain, ext, ext_domain);
814 			char *expanded = NULL, *ostr = dial_str;;
815 
816 			if (!strcasecmp(action, "call")) {
817 				if((conference->max_members > 0) && (conference->count >= conference->max_members)) {
818 					// Conference member limit has been reached; do not proceed with setup request
819 					status = SWITCH_STATUS_FALSE;
820 				} else {
821 					if (switch_event_create_plain(&var_event, SWITCH_EVENT_CHANNEL_DATA) != SWITCH_STATUS_SUCCESS) {
822 						abort();
823 					}
824 
825 					for(hp = event->headers; hp; hp = hp->next) {
826 						if (!strncasecmp(hp->name, "var_", 4)) {
827 							switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, hp->name + 4, hp->value);
828 						}
829 					}
830 
831 					switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_call_key", key);
832 					switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_destination_number", ext);
833 
834 					switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_invite_uri", dial_uri);
835 
836 					switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_track_status", "true");
837 					switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_track_call_id", call_id);
838 					switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "sip_invite_domain", domain);
839 					switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "sip_invite_contact_params", "~isfocus");
840 
841 					if (!strncasecmp(ostr, "url+", 4)) {
842 						ostr += 4;
843 					} else if (!switch_true(full_url) && conference->outcall_templ) {
844 						if ((expanded = switch_event_expand_headers(var_event, conference->outcall_templ))) {
845 							ostr = expanded;
846 						}
847 					}
848 
849 					status = conference_outcall_bg(conference, NULL, NULL, ostr, 60, NULL, NULL, NULL, NULL, NULL, NULL, &var_event);
850 
851 					if (expanded && expanded != conference->outcall_templ) {
852 						switch_safe_free(expanded);
853 					}
854 				}
855 
856 			} else if (!strcasecmp(action, "end")) {
857 				if (switch_core_session_hupall_matching_var("conference_call_key", key, SWITCH_CAUSE_NORMAL_CLEARING)) {
858 					conference_send_notify(conference, "SIP/2.0 200 OK\r\n", call_id, SWITCH_TRUE);
859 				} else {
860 					conference_send_notify(conference, "SIP/2.0 481 Failure\r\n", call_id, SWITCH_TRUE);
861 				}
862 				status = SWITCH_STATUS_SUCCESS;
863 			}
864 
865 			switch_safe_free(key);
866 		} else { // Conference found but doesn't support referral.
867 			status = SWITCH_STATUS_FALSE;
868 		}
869 
870 
871 		switch_thread_rwlock_unlock(conference->rwlock);
872 	} else { // Couldn't find associated conference.  Indicate failure on refer subscription
873 		status = SWITCH_STATUS_FALSE;
874 	}
875 
876 	if(status != SWITCH_STATUS_SUCCESS) {
877 		// Unable to setup call, need to generate final NOTIFY
878 		if (switch_event_create(&event, SWITCH_EVENT_CONFERENCE_DATA) == SWITCH_STATUS_SUCCESS) {
879 			event->flags |= EF_UNIQ_HEADERS;
880 
881 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-name", conf);
882 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-domain", domain);
883 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-event", "refer");
884 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call_id", call_id);
885 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "final", "true");
886 			switch_event_add_body(event, "%s", "SIP/2.0 481 Failure\r\n");
887 			switch_event_fire(&event);
888 		}
889 	}
890 
891 }
892 
conference_data_event_handler(switch_event_t * event)893 void conference_data_event_handler(switch_event_t *event)
894 {
895 	switch_event_t *revent;
896 	char *name = switch_event_get_header(event, "conference-name");
897 	char *domain = switch_event_get_header(event, "conference-domain");
898 	conference_obj_t *conference = NULL;
899 	char *body = NULL;
900 
901 	if (!zstr(name) && (conference = conference_find(name, domain))) {
902 		if (conference_utils_test_flag(conference, CFLAG_RFC4579)) {
903 			switch_event_dup(&revent, event);
904 			revent->event_id = SWITCH_EVENT_CONFERENCE_DATA;
905 			revent->flags |= EF_UNIQ_HEADERS;
906 			switch_event_add_header(revent, SWITCH_STACK_TOP, "Event-Name", "CONFERENCE_DATA");
907 
908 			body = conference_cdr_rfc4579_render(conference, event, revent);
909 			switch_event_add_body(revent, "%s", body);
910 			switch_event_fire(&revent);
911 			switch_safe_free(body);
912 		}
913 		switch_thread_rwlock_unlock(conference->rwlock);
914 	}
915 }
916 
917 
conference_event_pres_handler(switch_event_t * event)918 void conference_event_pres_handler(switch_event_t *event)
919 {
920 	char *to = switch_event_get_header(event, "to");
921 	char *domain_name = NULL;
922 	char *dup_to = NULL, *conference_name, *dup_conference_name = NULL;
923 	conference_obj_t *conference;
924 
925 	if (!to || strncasecmp(to, "conf+", 5) || !strchr(to, '@')) {
926 		return;
927 	}
928 
929 	if (!(dup_to = strdup(to))) {
930 		return;
931 	}
932 
933 
934 	conference_name = dup_to + 5;
935 
936 	if ((domain_name = strchr(conference_name, '@'))) {
937 		*domain_name++ = '\0';
938 	}
939 
940 	dup_conference_name = switch_mprintf("%q@%q", conference_name, domain_name);
941 
942 
943 	if ((conference = conference_find(conference_name, NULL)) || (conference = conference_find(dup_conference_name, NULL))) {
944 		if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) {
945 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", CONF_CHAT_PROTO);
946 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", conference->name);
947 			switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", conference->name, conference->domain);
948 
949 
950 			switch_event_add_header(event, SWITCH_STACK_BOTTOM, "force-status", "Active (%d caller%s)", conference->count, conference->count == 1 ? "" : "s");
951 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence");
952 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog");
953 			switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++);
954 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", conference_name);
955 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_ROUTING");
956 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", conference->count == 1 ? "early" : "confirmed");
957 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-direction", conference->count == 1 ? "outbound" : "inbound");
958 			switch_event_fire(&event);
959 		}
960 		switch_thread_rwlock_unlock(conference->rwlock);
961 	} else if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) {
962 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", CONF_CHAT_PROTO);
963 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", conference_name);
964 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from", to);
965 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", "Idle");
966 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "unknown");
967 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence");
968 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog");
969 		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++);
970 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", conference_name);
971 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_HANGUP");
972 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "terminated");
973 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-direction", "inbound");
974 		switch_event_fire(&event);
975 	}
976 
977 	switch_safe_free(dup_to);
978 	switch_safe_free(dup_conference_name);
979 }
980 
981 
chat_send(switch_event_t * message_event)982 switch_status_t chat_send(switch_event_t *message_event)
983 {
984 	char name[512] = "", *p, *lbuf = NULL;
985 	conference_obj_t *conference = NULL;
986 	switch_stream_handle_t stream = { 0 };
987 	const char *proto;
988 	const char *from;
989 	const char *to;
990 	//const char *subject;
991 	const char *body;
992 	//const char *type;
993 	const char *hint;
994 
995 	proto = switch_event_get_header(message_event, "proto");
996 	from = switch_event_get_header(message_event, "from");
997 	to = switch_event_get_header(message_event, "to");
998 	body = switch_event_get_body(message_event);
999 	hint = switch_event_get_header(message_event, "hint");
1000 
1001 
1002 	if ((p = strchr(to, '+'))) {
1003 		to = ++p;
1004 	}
1005 
1006 	if (!body) {
1007 		return SWITCH_STATUS_SUCCESS;
1008 	}
1009 
1010 	if ((p = strchr(to, '@'))) {
1011 		switch_copy_string(name, to, ++p - to);
1012 	} else {
1013 		switch_copy_string(name, to, sizeof(name));
1014 	}
1015 
1016 	if (!(conference = conference_find(name, NULL))) {
1017 		switch_core_chat_send_args(proto, CONF_CHAT_PROTO, to, hint && strchr(hint, '/') ? hint : from, "",
1018 								   "Conference not active.", NULL, NULL, SWITCH_FALSE);
1019 		return SWITCH_STATUS_FALSE;
1020 	}
1021 
1022 	SWITCH_STANDARD_STREAM(stream);
1023 
1024 	if ((lbuf = strdup(body))) {
1025 		/* special case list */
1026 		if (conference->broadcast_chat_messages) {
1027 			conference_event_chat_message_broadcast(conference, message_event);
1028 		} else if (switch_stristr("list", lbuf)) {
1029 			conference_list_pretty(conference, &stream);
1030 			/* provide help */
1031 		} else {
1032 			goto done;
1033 		}
1034 	}
1035 
1036 	if (!conference->broadcast_chat_messages) {
1037 		switch_core_chat_send_args(proto, CONF_CHAT_PROTO, to, hint && strchr(hint, '/') ? hint : from, "", stream.data, NULL, NULL, SWITCH_FALSE);
1038 	}
1039 
1040 done:
1041 	switch_safe_free(lbuf);
1042 	switch_safe_free(stream.data);
1043 
1044 	switch_thread_rwlock_unlock(conference->rwlock);
1045 
1046 	return SWITCH_STATUS_SUCCESS;
1047 }
1048 
1049 /* For Emacs:
1050  * Local Variables:
1051  * mode:c
1052  * indent-tabs-mode:t
1053  * tab-width:4
1054  * c-basic-offset:4
1055  * End:
1056  * For VIM:
1057  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
1058  */
1059