2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2007-2008, Digium, Inc.
6 * Joshua Colp <jcolp@digium.com>
7 * David Vossel <dvossel@digium.com>
9 * See http://www.asterisk.org for more information about
10 * the Asterisk project. Please do not directly contact
11 * any of the maintainers of this project for assistance;
12 * the project provides a web site, mailing lists and IRC
13 * channels for your use.
15 * This program is free software, distributed under the terms of
16 * the GNU General Public License Version 2. See the LICENSE file
17 * at the top of the source tree.
22 * \brief Conference Bridge application
24 * \author\verbatim Joshua Colp <jcolp@digium.com> \endverbatim
25 * \author\verbatim David Vossel <dvossel@digium.com> \endverbatim
27 * This is a conference bridge application utilizing the bridging core.
28 * \ingroup applications
32 <support_level>core</support_level>
37 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
45 #include "asterisk/cli.h"
46 #include "asterisk/file.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/pbx.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/lock.h"
52 #include "asterisk/bridging.h"
53 #include "asterisk/musiconhold.h"
54 #include "asterisk/say.h"
55 #include "asterisk/audiohook.h"
56 #include "asterisk/astobj2.h"
57 #include "confbridge/include/confbridge.h"
58 #include "asterisk/paths.h"
59 #include "asterisk/manager.h"
60 #include "asterisk/test.h"
63 <application name="ConfBridge" language="en_US">
65 Conference bridge application.
68 <parameter name="confno">
69 <para>The conference number</para>
71 <parameter name="bridge_profile">
72 <para>The bridge profile name from confbridge.conf. When left blank, a dynamically built bridge profile created by the CONFBRIDGE dialplan function is searched for on the channel and used. If no dynamic profile is present, the 'default_bridge' profile found in confbridge.conf is used. </para>
73 <para>It is important to note that while user profiles may be unique for each participant, mixing bridge profiles on a single conference is _NOT_ recommended and will produce undefined results.</para>
75 <parameter name="user_profile">
76 <para>The user profile name from confbridge.conf. When left blank, a dynamically built user profile created by the CONFBRIDGE dialplan function is searched for on the channel and used. If no dynamic profile is present, the 'default_user' profile found in confbridge.conf is used.</para>
78 <parameter name="menu">
79 <para>The name of the DTMF menu in confbridge.conf to be applied to this channel. No menu is applied by default if this option is left blank.</para>
83 <para>Enters the user into a specified conference bridge. The user can exit the conference by hangup or DTMF menu option.</para>
86 <ref type="application">ConfBridge</ref>
87 <ref type="function">CONFBRIDGE</ref>
88 <ref type="function">CONFBRIDGE_INFO</ref>
91 <function name="CONFBRIDGE" language="en_US">
93 Set a custom dynamic bridge and user profile on a channel for the ConfBridge application using the same options defined in confbridge.conf.
96 <parameter name="type" required="true">
97 <para>Type refers to which type of profile the option belongs too. Type can be <literal>bridge</literal> or <literal>user</literal>.</para>
99 <parameter name="option" required="true">
100 <para>Option refers to <filename>confbridge.conf</filename> option that is being set dynamically on this channel.</para>
104 <para>---- Example 1 ----</para>
105 <para>In this example the custom set user profile on this channel will automatically be used by the ConfBridge app.</para>
106 <para>exten => 1,1,Answer() </para>
107 <para>exten => 1,n,Set(CONFBRIDGE(user,announce_join_leave)=yes)</para>
108 <para>exten => 1,n,Set(CONFBRIDGE(user,startmuted)=yes)</para>
109 <para>exten => 1,n,ConfBridge(1) </para>
110 <para>---- Example 2 ----</para>
111 <para>This example shows how to use a predefined user or bridge profile in confbridge.conf as a template for a dynamic profile. Here we make a admin/marked user out of the default_user profile that is already defined in confbridge.conf.</para>
112 <para>exten => 1,1,Answer() </para>
113 <para>exten => 1,n,Set(CONFBRIDGE(user,template)=default_user)</para>
114 <para>exten => 1,n,Set(CONFBRIDGE(user,admin)=yes)</para>
115 <para>exten => 1,n,Set(CONFBRIDGE(user,marked)=yes)</para>
116 <para>exten => 1,n,ConfBridge(1)</para>
119 <function name="CONFBRIDGE_INFO" language="en_US">
121 Get information about a ConfBridge conference.
124 <parameter name="type" required="true">
125 <para>Type can be <literal>parties</literal>, <literal>admins</literal>, <literal>marked</literal>, or <literal>locked</literal>.</para>
127 <parameter name="conf" required="true">
128 <para>Conf refers to the name of the conference being referenced.</para>
132 <para>This function returns a non-negative integer for valid conference identifiers (0 or 1 for <literal>locked</literal>) and "" for invalid conference identifiers.</para>
135 <manager name="ConfbridgeList" language="en_US">
137 List participants in a conference.
140 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
141 <parameter name="Conference" required="false">
142 <para>Conference number.</para>
146 <para>Lists all users in a particular ConfBridge conference.
147 ConfbridgeList will follow as separate events, followed by a final event called
148 ConfbridgeListComplete.</para>
151 <manager name="ConfbridgeListRooms" language="en_US">
153 List active conferences.
156 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
159 <para>Lists data about all active conferences.
160 ConfbridgeListRooms will follow as separate events, followed by a final event called
161 ConfbridgeListRoomsComplete.</para>
164 <manager name="ConfbridgeMute" language="en_US">
166 Mute a Confbridge user.
169 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
170 <parameter name="Conference" required="true" />
171 <parameter name="Channel" required="true" />
176 <manager name="ConfbridgeUnmute" language="en_US">
178 Unmute a Confbridge user.
181 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
182 <parameter name="Conference" required="true" />
183 <parameter name="Channel" required="true" />
188 <manager name="ConfbridgeKick" language="en_US">
190 Kick a Confbridge user.
193 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
194 <parameter name="Conference" required="true" />
195 <parameter name="Channel" required="true" />
200 <manager name="ConfbridgeLock" language="en_US">
202 Lock a Confbridge conference.
205 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
206 <parameter name="Conference" required="true" />
211 <manager name="ConfbridgeUnlock" language="en_US">
213 Unlock a Confbridge conference.
216 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
217 <parameter name="Conference" required="true" />
222 <manager name="ConfbridgeStartRecord" language="en_US">
224 Start recording a Confbridge conference.
227 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
228 <parameter name="Conference" required="true" />
229 <parameter name="RecordFile" required="false" />
232 <para>Start recording a conference. If recording is already present an error will be returned. If RecordFile is not provided, the default record file specified in the conference's bridge profile will be used, if that is not present either a file will automatically be generated in the monitor directory.</para>
235 <manager name="ConfbridgeStopRecord" language="en_US">
237 Stop recording a Confbridge conference.
240 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
241 <parameter name="Conference" required="true" />
246 <manager name="ConfbridgeSetSingleVideoSrc" language="en_US">
248 Set a conference user as the single video source distributed to all other participants.
251 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
252 <parameter name="Conference" required="true" />
253 <parameter name="Channel" required="true" />
262 * \par Playing back a file to a channel in a conference
263 * You might notice in this application that while playing a sound file
264 * to a channel the actual conference bridge lock is not held. This is done so
265 * that other channels are not blocked from interacting with the conference bridge.
266 * Unfortunately because of this it is possible for things to change after the sound file
267 * is done being played. Data must therefore be checked after reacquiring the conference
268 * bridge lock if it is important.
271 static const char app[] = "ConfBridge";
273 /* Number of buckets our conference bridges container can have */
274 #define CONFERENCE_BRIDGE_BUCKETS 53
276 /*! \brief Container to hold all conference bridges in progress */
277 static struct ao2_container *conference_bridges;
279 static int play_sound_file(struct conference_bridge *conference_bridge, const char *filename);
280 static int play_sound_number(struct conference_bridge *conference_bridge, int say_number);
281 static int execute_menu_entry(struct conference_bridge *conference_bridge,
282 struct conference_bridge_user *conference_bridge_user,
283 struct ast_bridge_channel *bridge_channel,
284 struct conf_menu_entry *menu_entry,
285 struct conf_menu *menu);
287 /*! \brief Hashing function used for conference bridges container */
288 static int conference_bridge_hash_cb(const void *obj, const int flags)
290 const struct conference_bridge *conference_bridge = obj;
291 return ast_str_case_hash(conference_bridge->name);
294 /*! \brief Comparison function used for conference bridges container */
295 static int conference_bridge_cmp_cb(void *obj, void *arg, int flags)
297 const struct conference_bridge *conference_bridge0 = obj, *conference_bridge1 = arg;
298 return (!strcasecmp(conference_bridge0->name, conference_bridge1->name) ? CMP_MATCH | CMP_STOP : 0);
301 const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds *custom_sounds)
304 case CONF_SOUND_HAS_JOINED:
305 return S_OR(custom_sounds->hasjoin, "conf-hasjoin");
306 case CONF_SOUND_HAS_LEFT:
307 return S_OR(custom_sounds->hasleft, "conf-hasleft");
308 case CONF_SOUND_KICKED:
309 return S_OR(custom_sounds->kicked, "conf-kicked");
310 case CONF_SOUND_MUTED:
311 return S_OR(custom_sounds->muted, "conf-muted");
312 case CONF_SOUND_UNMUTED:
313 return S_OR(custom_sounds->unmuted, "conf-unmuted");
314 case CONF_SOUND_ONLY_ONE:
315 return S_OR(custom_sounds->onlyone, "conf-onlyone");
316 case CONF_SOUND_THERE_ARE:
317 return S_OR(custom_sounds->thereare, "conf-thereare");
318 case CONF_SOUND_OTHER_IN_PARTY:
319 return S_OR(custom_sounds->otherinparty, "conf-otherinparty");
320 case CONF_SOUND_PLACE_IN_CONF:
321 return S_OR(custom_sounds->placeintoconf, "conf-placeintoconf");
322 case CONF_SOUND_WAIT_FOR_LEADER:
323 return S_OR(custom_sounds->waitforleader, "conf-waitforleader");
324 case CONF_SOUND_LEADER_HAS_LEFT:
325 return S_OR(custom_sounds->leaderhasleft, "conf-leaderhasleft");
326 case CONF_SOUND_GET_PIN:
327 return S_OR(custom_sounds->getpin, "conf-getpin");
328 case CONF_SOUND_INVALID_PIN:
329 return S_OR(custom_sounds->invalidpin, "conf-invalidpin");
330 case CONF_SOUND_ONLY_PERSON:
331 return S_OR(custom_sounds->onlyperson, "conf-onlyperson");
332 case CONF_SOUND_LOCKED:
333 return S_OR(custom_sounds->locked, "conf-locked");
334 case CONF_SOUND_LOCKED_NOW:
335 return S_OR(custom_sounds->lockednow, "conf-lockednow");
336 case CONF_SOUND_UNLOCKED_NOW:
337 return S_OR(custom_sounds->unlockednow, "conf-unlockednow");
338 case CONF_SOUND_ERROR_MENU:
339 return S_OR(custom_sounds->errormenu, "conf-errormenu");
340 case CONF_SOUND_JOIN:
341 return S_OR(custom_sounds->join, "confbridge-join");
342 case CONF_SOUND_LEAVE:
343 return S_OR(custom_sounds->leave, "confbridge-leave");
344 case CONF_SOUND_PARTICIPANTS_MUTED:
345 return S_OR(custom_sounds->participantsmuted, "conf-now-muted");
346 case CONF_SOUND_PARTICIPANTS_UNMUTED:
347 return S_OR(custom_sounds->participantsunmuted, "conf-now-unmuted");
353 static struct ast_frame *rec_read(struct ast_channel *ast)
355 return &ast_null_frame;
357 static int rec_write(struct ast_channel *ast, struct ast_frame *f)
361 static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
362 static struct ast_channel_tech record_tech = {
363 .type = "ConfBridgeRec",
364 .description = "Conference Bridge Recording Channel",
365 .requester = rec_request,
369 static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
371 struct ast_channel *tmp;
372 struct ast_format fmt;
373 const char *conf_name = data;
374 if (!(tmp = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", NULL, 0,
375 "ConfBridgeRecorder/conf-%s-uid-%d",
377 (int) ast_random()))) {
380 ast_format_set(&fmt, AST_FORMAT_SLINEAR, 0);
381 tmp->tech = &record_tech;
382 ast_format_cap_add_all(tmp->nativeformats);
383 ast_format_copy(&tmp->writeformat, &fmt);
384 ast_format_copy(&tmp->rawwriteformat, &fmt);
385 ast_format_copy(&tmp->readformat, &fmt);
386 ast_format_copy(&tmp->rawreadformat, &fmt);
390 static void *record_thread(void *obj)
392 struct conference_bridge *conference_bridge = obj;
393 struct ast_app *mixmonapp = pbx_findapp("MixMonitor");
394 struct ast_channel *chan;
395 struct ast_str *filename = ast_str_alloca(PATH_MAX);
398 ao2_ref(conference_bridge, -1);
402 ao2_lock(conference_bridge);
403 if (!(conference_bridge->record_chan)) {
404 conference_bridge->record_thread = AST_PTHREADT_NULL;
405 ao2_unlock(conference_bridge);
406 ao2_ref(conference_bridge, -1);
409 chan = ast_channel_ref(conference_bridge->record_chan);
411 if (!(ast_strlen_zero(conference_bridge->b_profile.rec_file))) {
412 ast_str_append(&filename, 0, "%s", conference_bridge->b_profile.rec_file);
416 ast_str_append(&filename, 0, "confbridge-%s-%u.wav",
417 conference_bridge->name,
420 ao2_unlock(conference_bridge);
423 pbx_exec(chan, mixmonapp, ast_str_buffer(filename));
424 ast_bridge_join(conference_bridge->bridge, chan, NULL, NULL, NULL);
426 ao2_lock(conference_bridge);
427 conference_bridge->record_thread = AST_PTHREADT_NULL;
428 ao2_unlock(conference_bridge);
430 ast_hangup(chan); /* This will eat this threads reference to the channel as well */
431 ao2_ref(conference_bridge, -1);
437 * \brief Returns whether or not conference is being recorded.
438 * \retval 1, conference is recording.
439 * \retval 0, conference is NOT recording.
441 static int conf_is_recording(struct conference_bridge *conference_bridge)
444 ao2_lock(conference_bridge);
445 if (conference_bridge->record_chan || conference_bridge->record_thread != AST_PTHREADT_NULL) {
448 ao2_unlock(conference_bridge);
454 * \brief Stops the confbridge recording thread.
456 * \note do not call this function with any locks
458 static int conf_stop_record(struct conference_bridge *conference_bridge)
460 ao2_lock(conference_bridge);
462 if (conference_bridge->record_thread != AST_PTHREADT_NULL) {
463 struct ast_channel *chan = ast_channel_ref(conference_bridge->record_chan);
464 pthread_t thread = conference_bridge->record_thread;
465 ao2_unlock(conference_bridge);
467 ast_bridge_remove(conference_bridge->bridge, chan);
468 ast_queue_frame(chan, &ast_null_frame);
470 chan = ast_channel_unref(chan);
471 pthread_join(thread, NULL);
472 ast_test_suite_event_notify("CONF_STOP_RECORD", "Message: stopped conference recording channel\r\nConference: %s", conference_bridge->b_profile.name);
474 ao2_lock(conference_bridge);
477 /* this is the reference given to the channel during the channel alloc */
478 if (conference_bridge->record_chan) {
479 conference_bridge->record_chan = ast_channel_unref(conference_bridge->record_chan);
482 ao2_unlock(conference_bridge);
486 static int conf_start_record(struct conference_bridge *conference_bridge)
488 struct ast_format_cap *cap = ast_format_cap_alloc_nolock();
489 struct ast_format tmpfmt;
492 ao2_lock(conference_bridge);
493 if (conference_bridge->record_chan || conference_bridge->record_thread != AST_PTHREADT_NULL) {
494 ao2_unlock(conference_bridge);
495 return -1; /* already recording */
498 ao2_unlock(conference_bridge);
501 if (!pbx_findapp("MixMonitor")) {
502 ast_log(LOG_WARNING, "Can not record ConfBridge, MixMonitor app is not installed\n");
503 cap = ast_format_cap_destroy(cap);
504 ao2_unlock(conference_bridge);
507 ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
508 if (!(conference_bridge->record_chan = ast_request("ConfBridgeRec", cap, NULL, conference_bridge->name, &cause))) {
509 cap = ast_format_cap_destroy(cap);
510 ao2_unlock(conference_bridge);
514 cap = ast_format_cap_destroy(cap);
515 ao2_ref(conference_bridge, +1); /* give the record thread a ref */
517 if (ast_pthread_create_background(&conference_bridge->record_thread, NULL, record_thread, conference_bridge)) {
518 ast_log(LOG_WARNING, "Failed to create recording channel for conference %s\n", conference_bridge->name);
520 ao2_unlock(conference_bridge);
521 ao2_ref(conference_bridge, -1); /* error so remove ref */
525 ast_test_suite_event_notify("CONF_START_RECORD", "Message: started conference recording channel\r\nConference: %s", conference_bridge->b_profile.name);
526 ao2_unlock(conference_bridge);
530 static void send_conf_start_event(const char *conf_name)
532 manager_event(EVENT_FLAG_CALL, "ConfbridgeStart", "Conference: %s\r\n", conf_name);
535 static void send_conf_end_event(const char *conf_name)
537 manager_event(EVENT_FLAG_CALL, "ConfbridgeEnd", "Conference: %s\r\n", conf_name);
540 static void send_join_event(struct ast_channel *chan, const char *conf_name)
542 ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeJoin",
546 "CallerIDnum: %s\r\n"
547 "CallerIDname: %s\r\n",
548 ast_channel_name(chan),
549 ast_channel_uniqueid(chan),
551 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, "<unknown>"),
552 S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, "<unknown>")
556 static void send_leave_event(struct ast_channel *chan, const char *conf_name)
558 ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeLeave",
562 "CallerIDnum: %s\r\n"
563 "CallerIDname: %s\r\n",
564 ast_channel_name(chan),
565 ast_channel_uniqueid(chan),
567 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, "<unknown>"),
568 S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, "<unknown>")
573 * \brief Announce number of users in the conference bridge to the caller
575 * \param conference_bridge Conference bridge to peek at
576 * \param (OPTIONAL) conference_bridge_user Caller
578 * \note if caller is NULL, the announcment will be sent to all participants in the conference.
579 * \return Returns 0 on success, -1 if the user hung up
581 static int announce_user_count(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
583 const char *other_in_party = conf_get_sound(CONF_SOUND_OTHER_IN_PARTY, conference_bridge->b_profile.sounds);
584 const char *only_one = conf_get_sound(CONF_SOUND_ONLY_ONE, conference_bridge->b_profile.sounds);
585 const char *there_are = conf_get_sound(CONF_SOUND_THERE_ARE, conference_bridge->b_profile.sounds);
587 if (conference_bridge->users == 1) {
588 /* Awww we are the only person in the conference bridge */
590 } else if (conference_bridge->users == 2) {
591 if (conference_bridge_user) {
592 /* Eep, there is one other person */
593 if (ast_stream_and_wait(conference_bridge_user->chan,
599 play_sound_file(conference_bridge, only_one);
602 /* Alas multiple others in here */
603 if (conference_bridge_user) {
604 if (ast_stream_and_wait(conference_bridge_user->chan,
609 if (ast_say_number(conference_bridge_user->chan, conference_bridge->users - 1, "", ast_channel_language(conference_bridge_user->chan), NULL)) {
612 if (ast_stream_and_wait(conference_bridge_user->chan,
617 } else if (ast_fileexists(there_are, NULL, NULL) && ast_fileexists(other_in_party, NULL, NULL)) {
618 play_sound_file(conference_bridge, there_are);
619 play_sound_number(conference_bridge, conference_bridge->users - 1);
620 play_sound_file(conference_bridge, other_in_party);
627 * \brief Play back an audio file to a channel
629 * \param conference_bridge Conference bridge they are in
630 * \param chan Channel to play audio prompt to
631 * \param file Prompt to play
633 * \return Returns 0 on success, -1 if the user hung up
635 * \note This function assumes that conference_bridge is locked
637 static int play_prompt_to_channel(struct conference_bridge *conference_bridge, struct ast_channel *chan, const char *file)
640 ao2_unlock(conference_bridge);
641 res = ast_stream_and_wait(chan, file, "");
642 ao2_lock(conference_bridge);
646 static void handle_video_on_join(struct conference_bridge *conference_bridge, struct ast_channel *chan, int marked)
648 /* Right now, only marked users are automatically set as the single src of video.*/
653 if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED)) {
655 struct conference_bridge_user *tmp_user = NULL;
656 ao2_lock(conference_bridge);
657 /* see if anyone is already the video src */
658 AST_LIST_TRAVERSE(&conference_bridge->users_list, tmp_user, list) {
659 if (tmp_user->chan == chan) {
662 if (ast_bridge_is_video_src(conference_bridge->bridge, tmp_user->chan)) {
667 ao2_unlock(conference_bridge);
669 ast_bridge_set_single_src_video_mode(conference_bridge->bridge, chan);
671 } else if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED)) {
672 /* we joined and are video capable, we override anyone else that may have already been the video feed */
673 ast_bridge_set_single_src_video_mode(conference_bridge->bridge, chan);
677 static void handle_video_on_exit(struct conference_bridge *conference_bridge, struct ast_channel *chan)
679 struct conference_bridge_user *tmp_user = NULL;
681 /* if this isn't a video source, nothing to update */
682 if (!ast_bridge_is_video_src(conference_bridge->bridge, chan)) {
686 ast_bridge_remove_video_src(conference_bridge->bridge, chan);
688 /* If in follow talker mode, make sure to restore this mode on the
689 * bridge when a source is removed. It is possible this channel was
690 * only set temporarily as a video source by an AMI or DTMF action. */
691 if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
692 ast_bridge_set_talker_src_video_mode(conference_bridge->bridge);
695 /* if the video_mode isn't set to automatically pick the video source, do nothing on exit. */
696 if (!ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED) &&
697 !ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED)) {
701 /* Make the next available marked user the video src. */
702 ao2_lock(conference_bridge);
703 AST_LIST_TRAVERSE(&conference_bridge->users_list, tmp_user, list) {
704 if (tmp_user->chan == chan) {
707 if (ast_test_flag(&tmp_user->u_profile, USER_OPT_MARKEDUSER)) {
708 ast_bridge_set_single_src_video_mode(conference_bridge->bridge, tmp_user->chan);
712 ao2_unlock(conference_bridge);
716 * \brief Perform post-joining marked specific actions
718 * \param conference_bridge Conference bridge being joined
719 * \param conference_bridge_user Conference bridge user joining
721 * \return Returns 0 on success, -1 if the user hung up
723 static int post_join_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
725 if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER)) {
726 struct conference_bridge_user *other_conference_bridge_user = NULL;
728 /* If we are not the first user to join, then the users are already
729 * in the conference so we do not need to update them. */
730 if (conference_bridge->markedusers >= 2) {
734 /* Iterate through every participant stopping MOH on them if need be */
735 AST_LIST_TRAVERSE(&conference_bridge->users_list, other_conference_bridge_user, list) {
736 if (other_conference_bridge_user == conference_bridge_user) {
739 if (other_conference_bridge_user->playing_moh && !ast_bridge_suspend(conference_bridge->bridge, other_conference_bridge_user->chan)) {
740 other_conference_bridge_user->playing_moh = 0;
741 ast_moh_stop(other_conference_bridge_user->chan);
742 ast_bridge_unsuspend(conference_bridge->bridge, other_conference_bridge_user->chan);
746 /* Next play the audio file stating they are going to be placed into the conference */
747 if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) {
748 ao2_unlock(conference_bridge);
749 ast_autoservice_start(conference_bridge_user->chan);
750 play_sound_file(conference_bridge,
751 conf_get_sound(CONF_SOUND_PLACE_IN_CONF, conference_bridge_user->b_profile.sounds));
752 ast_autoservice_stop(conference_bridge_user->chan);
753 ao2_lock(conference_bridge);
756 /* Finally iterate through and unmute them all */
757 AST_LIST_TRAVERSE(&conference_bridge->users_list, other_conference_bridge_user, list) {
758 if (other_conference_bridge_user == conference_bridge_user) {
761 /* only unmute them if they are not supposed to start muted */
762 if (!ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_STARTMUTED)) {
763 other_conference_bridge_user->features.mute = 0;
767 /* If a marked user already exists in the conference bridge we can just bail out now */
768 if (conference_bridge->markedusers) {
771 /* Be sure we are muted so we can't talk to anybody else waiting */
772 conference_bridge_user->features.mute = 1;
773 /* If we have not been quieted play back that they are waiting for the leader */
774 if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) {
775 if (play_prompt_to_channel(conference_bridge,
776 conference_bridge_user->chan,
777 conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, conference_bridge_user->b_profile.sounds))) {
778 /* user hungup while the sound was playing */
782 /* Start music on hold if needed */
783 /* We need to recheck the markedusers value here. play_prompt_to_channel unlocks the conference bridge, potentially
784 * allowing a marked user to enter while the prompt was playing
786 if (!conference_bridge->markedusers && ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) {
787 ast_moh_start(conference_bridge_user->chan, conference_bridge_user->u_profile.moh_class, NULL);
788 conference_bridge_user->playing_moh = 1;
795 * \brief Perform post-joining non-marked specific actions
797 * \param conference_bridge Conference bridge being joined
798 * \param conference_bridge_user Conference bridge user joining
800 * \return Returns 0 on success, -1 if the user hung up
802 static int post_join_unmarked(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
804 /* Play back audio prompt and start MOH if need be if we are the first participant */
805 if (conference_bridge->users == 1) {
806 /* If audio prompts have not been quieted or this prompt quieted play it on out */
807 if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET | USER_OPT_NOONLYPERSON)) {
808 if (play_prompt_to_channel(conference_bridge,
809 conference_bridge_user->chan,
810 conf_get_sound(CONF_SOUND_ONLY_PERSON, conference_bridge_user->b_profile.sounds))) {
811 /* user hungup while the sound was playing */
815 /* If we need to start music on hold on the channel do so now */
816 /* We need to re-check the number of users in the conference bridge here because another conference bridge
817 * participant could have joined while the above prompt was playing for the first user.
819 if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) {
820 ast_moh_start(conference_bridge_user->chan, conference_bridge_user->u_profile.moh_class, NULL);
821 conference_bridge_user->playing_moh = 1;
826 /* Announce number of users if need be */
827 if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNT)) {
828 ao2_unlock(conference_bridge);
829 if (announce_user_count(conference_bridge, conference_bridge_user)) {
830 ao2_lock(conference_bridge);
833 ao2_lock(conference_bridge);
836 /* If we are the second participant we may need to stop music on hold on the first */
837 if (conference_bridge->users == 2) {
838 struct conference_bridge_user *first_participant = AST_LIST_FIRST(&conference_bridge->users_list);
840 /* Temporarily suspend the above participant from the bridge so we have control to stop MOH if needed */
841 if (ast_test_flag(&first_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) {
842 first_participant->playing_moh = 0;
843 ast_moh_stop(first_participant->chan);
844 ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan);
848 if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNTALL) &&
849 (conference_bridge->users > conference_bridge_user->u_profile.announce_user_count_all_after)) {
850 ao2_unlock(conference_bridge);
851 if (announce_user_count(conference_bridge, NULL)) {
852 ao2_lock(conference_bridge);
855 ao2_lock(conference_bridge);
861 * \brief Destroy a conference bridge
863 * \param obj The conference bridge object
865 * \return Returns nothing
867 static void destroy_conference_bridge(void *obj)
869 struct conference_bridge *conference_bridge = obj;
871 ast_debug(1, "Destroying conference bridge '%s'\n", conference_bridge->name);
873 ast_mutex_destroy(&conference_bridge->playback_lock);
875 if (conference_bridge->playback_chan) {
876 struct ast_channel *underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL);
877 if (underlying_channel) {
878 ast_hangup(underlying_channel);
880 ast_hangup(conference_bridge->playback_chan);
881 conference_bridge->playback_chan = NULL;
884 /* Destroying a conference bridge is simple, all we have to do is destroy the bridging object */
885 if (conference_bridge->bridge) {
886 ast_bridge_destroy(conference_bridge->bridge);
887 conference_bridge->bridge = NULL;
889 conf_bridge_profile_destroy(&conference_bridge->b_profile);
892 static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user);
895 * \brief Join a conference bridge
897 * \param name The conference name
898 * \param conference_bridge_user Conference bridge user structure
900 * \return A pointer to the conference bridge struct, or NULL if the conference room wasn't found.
902 static struct conference_bridge *join_conference_bridge(const char *name, struct conference_bridge_user *conference_bridge_user)
904 struct conference_bridge *conference_bridge = NULL;
905 struct conference_bridge tmp;
906 int start_record = 0;
907 int max_members_reached = 0;
909 ast_copy_string(tmp.name, name, sizeof(tmp.name));
911 /* We explictly lock the conference bridges container ourselves so that other callers can not create duplicate conferences at the same */
912 ao2_lock(conference_bridges);
914 ast_debug(1, "Trying to find conference bridge '%s'\n", name);
916 /* Attempt to find an existing conference bridge */
917 conference_bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
919 if (conference_bridge && conference_bridge->b_profile.max_members) {
920 max_members_reached = conference_bridge->b_profile.max_members > conference_bridge->users ? 0 : 1;
923 /* When finding a conference bridge that already exists make sure that it is not locked, and if so that we are not an admin */
924 if (conference_bridge && (max_members_reached || conference_bridge->locked) && !ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN)) {
925 ao2_unlock(conference_bridges);
926 ao2_ref(conference_bridge, -1);
927 ast_debug(1, "Conference bridge '%s' is locked and caller is not an admin\n", name);
928 ast_stream_and_wait(conference_bridge_user->chan,
929 conf_get_sound(CONF_SOUND_LOCKED, conference_bridge_user->b_profile.sounds),
934 /* If no conference bridge was found see if we can create one */
935 if (!conference_bridge) {
936 /* Try to allocate memory for a new conference bridge, if we fail... this won't end well. */
937 if (!(conference_bridge = ao2_alloc(sizeof(*conference_bridge), destroy_conference_bridge))) {
938 ao2_unlock(conference_bridges);
939 ast_log(LOG_ERROR, "Conference bridge '%s' does not exist.\n", name);
943 /* Setup conference bridge parameters */
944 conference_bridge->record_thread = AST_PTHREADT_NULL;
945 ast_copy_string(conference_bridge->name, name, sizeof(conference_bridge->name));
946 conf_bridge_profile_copy(&conference_bridge->b_profile, &conference_bridge_user->b_profile);
948 /* Create an actual bridge that will do the audio mixing */
949 if (!(conference_bridge->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_MULTIMIX, 0))) {
950 ao2_ref(conference_bridge, -1);
951 conference_bridge = NULL;
952 ao2_unlock(conference_bridges);
953 ast_log(LOG_ERROR, "Conference bridge '%s' could not be created.\n", name);
957 /* Set the internal sample rate on the bridge from the bridge profile */
958 ast_bridge_set_internal_sample_rate(conference_bridge->bridge, conference_bridge->b_profile.internal_sample_rate);
959 /* Set the internal mixing interval on the bridge from the bridge profile */
960 ast_bridge_set_mixing_interval(conference_bridge->bridge, conference_bridge->b_profile.mix_interval);
962 if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
963 ast_bridge_set_talker_src_video_mode(conference_bridge->bridge);
966 /* Setup lock for playback channel */
967 ast_mutex_init(&conference_bridge->playback_lock);
969 /* Link it into the conference bridges container */
970 ao2_link(conference_bridges, conference_bridge);
973 send_conf_start_event(conference_bridge->name);
974 ast_debug(1, "Created conference bridge '%s' and linked to container '%p'\n", name, conference_bridges);
977 ao2_unlock(conference_bridges);
979 /* Setup conference bridge user parameters */
980 conference_bridge_user->conference_bridge = conference_bridge;
982 ao2_lock(conference_bridge);
984 /* All good to go, add them in */
985 AST_LIST_INSERT_TAIL(&conference_bridge->users_list, conference_bridge_user, list);
987 /* Increment the users count on the bridge, but record it as it is going to need to be known right after this */
988 conference_bridge->users++;
990 /* If the caller is a marked user bump up the count */
991 if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER)) {
992 conference_bridge->markedusers++;
995 /* Set the device state for this conference */
996 if (conference_bridge->users == 1) {
997 ast_devstate_changed(AST_DEVICE_INUSE, "confbridge:%s", conference_bridge->name);
1000 /* If the caller is a marked user or is waiting for a marked user to enter pass 'em off, otherwise pass them off to do regular joining stuff */
1001 if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER | USER_OPT_WAITMARKED)) {
1002 if (post_join_marked(conference_bridge, conference_bridge_user)) {
1003 ao2_unlock(conference_bridge);
1004 leave_conference_bridge(conference_bridge, conference_bridge_user);
1008 if (post_join_unmarked(conference_bridge, conference_bridge_user)) {
1009 ao2_unlock(conference_bridge);
1010 leave_conference_bridge(conference_bridge, conference_bridge_user);
1015 /* check to see if recording needs to be started or not */
1016 if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_RECORD_CONFERENCE) && !conf_is_recording(conference_bridge)) {
1020 ao2_unlock(conference_bridge);
1023 conf_start_record(conference_bridge);
1026 return conference_bridge;
1030 * \brief Leave a conference bridge
1032 * \param conference_bridge The conference bridge to leave
1033 * \param conference_bridge_user The conference bridge user structure
1036 static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
1038 ao2_lock(conference_bridge);
1040 /* If this caller is a marked user bump down the count */
1041 if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER)) {
1042 conference_bridge->markedusers--;
1045 /* Decrement the users count while keeping the previous participant count */
1046 conference_bridge->users--;
1048 /* Drop conference bridge user from the list, they be going bye bye */
1049 AST_LIST_REMOVE(&conference_bridge->users_list, conference_bridge_user, list);
1051 /* If there are still users in the conference bridge we may need to do things (such as start MOH on them) */
1052 if (conference_bridge->users) {
1053 if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER) && !conference_bridge->markedusers) {
1054 struct conference_bridge_user *other_participant = NULL;
1056 /* Start out with muting everyone */
1057 AST_LIST_TRAVERSE(&conference_bridge->users_list, other_participant, list) {
1058 other_participant->features.mute = 1;
1061 /* Play back the audio prompt saying the leader has left the conference */
1062 if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) {
1063 ao2_unlock(conference_bridge);
1064 ast_autoservice_start(conference_bridge_user->chan);
1065 play_sound_file(conference_bridge,
1066 conf_get_sound(CONF_SOUND_LEADER_HAS_LEFT, conference_bridge_user->b_profile.sounds));
1067 ast_autoservice_stop(conference_bridge_user->chan);
1068 ao2_lock(conference_bridge);
1071 /* Now on to starting MOH or kick if needed */
1072 AST_LIST_TRAVERSE(&conference_bridge->users_list, other_participant, list) {
1073 if (ast_test_flag(&other_participant->u_profile, USER_OPT_ENDMARKED)) {
1074 other_participant->kicked = 1;
1075 ast_bridge_remove(conference_bridge->bridge, other_participant->chan);
1076 } else if (ast_test_flag(&other_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_participant->chan)) {
1077 ast_moh_start(other_participant->chan, other_participant->u_profile.moh_class, NULL);
1078 other_participant->playing_moh = 1;
1079 ast_bridge_unsuspend(conference_bridge->bridge, other_participant->chan);
1082 } else if (conference_bridge->users == 1) {
1083 /* Of course if there is one other person in here we may need to start up MOH on them */
1084 struct conference_bridge_user *first_participant = AST_LIST_FIRST(&conference_bridge->users_list);
1086 if (ast_test_flag(&first_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) {
1087 ast_moh_start(first_participant->chan, first_participant->u_profile.moh_class, NULL);
1088 first_participant->playing_moh = 1;
1089 ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan);
1093 /* Set device state to "not in use" */
1094 ast_devstate_changed(AST_DEVICE_NOT_INUSE, "confbridge:%s", conference_bridge->name);
1096 ao2_unlink(conference_bridges, conference_bridge);
1097 send_conf_end_event(conference_bridge->name);
1100 /* Done mucking with the conference bridge, huzzah */
1101 ao2_unlock(conference_bridge);
1103 if (!conference_bridge->users) {
1104 conf_stop_record(conference_bridge);
1107 ao2_ref(conference_bridge, -1);
1112 * \brief allocates playback chan on a channel
1113 * \pre expects conference to be locked before calling this function
1115 static int alloc_playback_chan(struct conference_bridge *conference_bridge)
1118 struct ast_format_cap *cap;
1119 struct ast_format tmpfmt;
1121 if (conference_bridge->playback_chan) {
1124 if (!(cap = ast_format_cap_alloc_nolock())) {
1127 ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
1128 if (!(conference_bridge->playback_chan = ast_request("Bridge", cap, NULL, "", &cause))) {
1129 cap = ast_format_cap_destroy(cap);
1132 cap = ast_format_cap_destroy(cap);
1134 conference_bridge->playback_chan->bridge = conference_bridge->bridge;
1136 if (ast_call(conference_bridge->playback_chan, "", 0)) {
1137 ast_hangup(conference_bridge->playback_chan);
1138 conference_bridge->playback_chan = NULL;
1142 ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference_bridge->name);
1146 static int play_sound_helper(struct conference_bridge *conference_bridge, const char *filename, int say_number)
1148 struct ast_channel *underlying_channel;
1150 /* Do not waste resources trying to play files that do not exist */
1151 if (!ast_fileexists(filename, NULL, NULL)) {
1152 ast_log(LOG_WARNING, "File %s does not exist in any format\n", filename);
1156 ast_mutex_lock(&conference_bridge->playback_lock);
1157 if (!(conference_bridge->playback_chan)) {
1158 if (alloc_playback_chan(conference_bridge)) {
1159 ast_mutex_unlock(&conference_bridge->playback_lock);
1162 underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL);
1164 /* Channel was already available so we just need to add it back into the bridge */
1165 underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL);
1166 ast_bridge_impart(conference_bridge->bridge, underlying_channel, NULL, NULL, 0);
1169 /* The channel is all under our control, in goes the prompt */
1170 if (!ast_strlen_zero(filename)) {
1171 ast_stream_and_wait(conference_bridge->playback_chan, filename, "");
1173 ast_say_number(conference_bridge->playback_chan, say_number, "", ast_channel_language(conference_bridge->playback_chan), NULL);
1176 ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", ast_channel_name(underlying_channel), conference_bridge->bridge);
1177 ast_bridge_depart(conference_bridge->bridge, underlying_channel);
1179 ast_mutex_unlock(&conference_bridge->playback_lock);
1185 * \brief Play sound file into conference bridge
1187 * \param conference_bridge The conference bridge to play sound file into
1188 * \param filename Sound file to play
1191 * \retval -1 failure
1193 static int play_sound_file(struct conference_bridge *conference_bridge, const char *filename)
1195 return play_sound_helper(conference_bridge, filename, 0);
1199 * \brief Play number into the conference bridge
1201 * \param conference_bridge The conference bridge to say the number into
1202 * \param number to say
1205 * \retval -1 failure
1207 static int play_sound_number(struct conference_bridge *conference_bridge, int say_number)
1209 return play_sound_helper(conference_bridge, NULL, say_number);
1212 static void conf_handle_talker_destructor(void *pvt_data)
1217 static void conf_handle_talker_cb(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data)
1219 char *conf_name = pvt_data;
1222 switch (bridge_channel->state) {
1223 case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
1226 case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
1230 return; /* uhh this shouldn't happen, but bail if it does. */
1233 /* notify AMI someone is has either started or stopped talking */
1234 ast_manager_event(bridge_channel->chan, EVENT_FLAG_CALL, "ConfbridgeTalking",
1237 "Conference: %s\r\n"
1238 "TalkingStatus: %s\r\n",
1239 ast_channel_name(bridge_channel->chan), ast_channel_uniqueid(bridge_channel->chan), conf_name, talking ? "on" : "off");
1242 static int conf_get_pin(struct ast_channel *chan, struct conference_bridge_user *conference_bridge_user)
1244 char pin_guess[MAX_PIN+1] = { 0, };
1245 const char *pin = conference_bridge_user->u_profile.pin;
1246 char *tmp = pin_guess;
1248 unsigned int len = MAX_PIN ;
1250 /* give them three tries to get the pin right */
1251 for (i = 0; i < 3; i++) {
1252 if (ast_app_getdata(chan,
1253 conf_get_sound(CONF_SOUND_GET_PIN, conference_bridge_user->b_profile.sounds),
1254 tmp, len, 0) >= 0) {
1255 if (!strcasecmp(pin, pin_guess)) {
1259 ast_streamfile(chan,
1260 conf_get_sound(CONF_SOUND_INVALID_PIN, conference_bridge_user->b_profile.sounds),
1261 ast_channel_language(chan));
1262 res = ast_waitstream(chan, AST_DIGIT_ANY);
1264 /* Account for digit already read during ivalid pin playback
1265 * resetting pin buf. */
1267 pin_guess[1] = '\0';
1268 tmp = pin_guess + 1;
1271 /* reset pin buf as empty buffer. */
1279 static int conf_rec_name(struct conference_bridge_user *user, const char *conf_name)
1281 char destdir[PATH_MAX];
1285 snprintf(destdir, sizeof(destdir), "%s/confbridge", ast_config_AST_SPOOL_DIR);
1287 if (ast_mkdir(destdir, 0777) != 0) {
1288 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", destdir, strerror(errno));
1291 snprintf(user->name_rec_location, sizeof(user->name_rec_location),
1292 "%s/confbridge-name-%s-%s", destdir,
1293 conf_name, ast_channel_uniqueid(user->chan));
1295 res = ast_play_and_record(user->chan,
1297 user->name_rec_location,
1302 ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE),
1307 user->name_rec_location[0] = '\0';
1313 /*! \brief The ConfBridge application */
1314 static int confbridge_exec(struct ast_channel *chan, const char *data)
1316 int res = 0, volume_adjustments[2];
1319 const char *b_profile_name = DEFAULT_BRIDGE_PROFILE;
1320 const char *u_profile_name = DEFAULT_USER_PROFILE;
1321 struct conference_bridge *conference_bridge = NULL;
1322 struct conference_bridge_user conference_bridge_user = {
1324 .tech_args.talking_threshold = DEFAULT_TALKING_THRESHOLD,
1325 .tech_args.silence_threshold = DEFAULT_SILENCE_THRESHOLD,
1326 .tech_args.drop_silence = 0,
1328 AST_DECLARE_APP_ARGS(args,
1329 AST_APP_ARG(conf_name);
1330 AST_APP_ARG(b_profile_name);
1331 AST_APP_ARG(u_profile_name);
1332 AST_APP_ARG(menu_name);
1334 ast_bridge_features_init(&conference_bridge_user.features);
1336 if (chan->_state != AST_STATE_UP) {
1340 if (ast_strlen_zero(data)) {
1341 ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app);
1342 res = -1; /* invalid PIN */
1343 goto confbridge_cleanup;
1346 /* We need to make a copy of the input string if we are going to modify it! */
1347 parse = ast_strdupa(data);
1349 AST_STANDARD_APP_ARGS(args, parse);
1351 /* bridge profile name */
1352 if (args.argc > 1 && !ast_strlen_zero(args.b_profile_name)) {
1353 b_profile_name = args.b_profile_name;
1355 if (!conf_find_bridge_profile(chan, b_profile_name, &conference_bridge_user.b_profile)) {
1356 ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name);
1358 goto confbridge_cleanup;
1361 /* user profile name */
1362 if (args.argc > 2 && !ast_strlen_zero(args.u_profile_name)) {
1363 u_profile_name = args.u_profile_name;
1366 if (!conf_find_user_profile(chan, u_profile_name, &conference_bridge_user.u_profile)) {
1367 ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name);
1369 goto confbridge_cleanup;
1371 quiet = ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_QUIET);
1373 /* ask for a PIN immediately after finding user profile. This has to be
1374 * prompted for requardless of quiet setting. */
1375 if (!ast_strlen_zero(conference_bridge_user.u_profile.pin)) {
1376 if (conf_get_pin(chan, &conference_bridge_user)) {
1377 res = -1; /* invalid PIN */
1378 goto confbridge_cleanup;
1382 /* See if we need them to record a intro name */
1383 if (!quiet && ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE)) {
1384 conf_rec_name(&conference_bridge_user, args.conf_name);
1388 if (args.argc > 3 && !ast_strlen_zero(args.menu_name)) {
1389 ast_copy_string(conference_bridge_user.menu_name, args.menu_name, sizeof(conference_bridge_user.menu_name));
1390 if (conf_set_menu_to_user(conference_bridge_user.menu_name, &conference_bridge_user)) {
1391 ast_log(LOG_WARNING, "Conference menu %s does not exist and can not be applied to confbridge user.\n",
1393 res = -1; /* invalid PIN */
1394 goto confbridge_cleanup;
1398 /* Set if DTMF should pass through for this user or not */
1399 if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DTMF_PASS)) {
1400 conference_bridge_user.features.dtmf_passthrough = 1;
1403 /* Set dsp threshold values if present */
1404 if (conference_bridge_user.u_profile.talking_threshold) {
1405 conference_bridge_user.tech_args.talking_threshold = conference_bridge_user.u_profile.talking_threshold;
1407 if (conference_bridge_user.u_profile.silence_threshold) {
1408 conference_bridge_user.tech_args.silence_threshold = conference_bridge_user.u_profile.silence_threshold;
1411 /* Set a talker indicate call back if talking detection is requested */
1412 if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_TALKER_DETECT)) {
1413 char *conf_name = ast_strdup(args.conf_name); /* this is freed during feature cleanup */
1415 res = -1; /* invalid PIN */
1416 goto confbridge_cleanup;
1418 ast_bridge_features_set_talk_detector(&conference_bridge_user.features,
1419 conf_handle_talker_cb,
1420 conf_handle_talker_destructor,
1424 /* Look for a conference bridge matching the provided name */
1425 if (!(conference_bridge = join_conference_bridge(args.conf_name, &conference_bridge_user))) {
1426 res = -1; /* invalid PIN */
1427 goto confbridge_cleanup;
1430 /* Keep a copy of volume adjustments so we can restore them later if need be */
1431 volume_adjustments[0] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_READ);
1432 volume_adjustments[1] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_WRITE);
1434 /* If the caller should be joined already muted, make it so */
1435 if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_STARTMUTED)) {
1436 conference_bridge_user.features.mute = 1;
1439 if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DROP_SILENCE)) {
1440 conference_bridge_user.tech_args.drop_silence = 1;
1443 if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_JITTERBUFFER)) {
1445 if ((func_jb = ast_module_helper("", "func_jitterbuffer", 0, 0, 0, 0))) {
1447 ast_func_write(chan, "JITTERBUFFER(adaptive)", "default");
1451 if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DENOISE)) {
1453 /* Reduce background noise from each participant */
1454 if ((mod_speex = ast_module_helper("", "codec_speex", 0, 0, 0, 0))) {
1455 ast_free(mod_speex);
1456 ast_func_write(chan, "DENOISE(rx)", "on");
1460 /* if this user has a intro, play it before entering */
1461 if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) {
1462 ast_autoservice_start(chan);
1463 play_sound_file(conference_bridge, conference_bridge_user.name_rec_location);
1464 play_sound_file(conference_bridge,
1465 conf_get_sound(CONF_SOUND_HAS_JOINED, conference_bridge_user.b_profile.sounds));
1466 ast_autoservice_stop(chan);
1469 /* Play the Join sound to both the conference and the user entering. */
1471 const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference_bridge_user.b_profile.sounds);
1472 if (conference_bridge_user.playing_moh) {
1475 ast_stream_and_wait(chan, join_sound, "");
1476 ast_autoservice_start(chan);
1477 play_sound_file(conference_bridge, join_sound);
1478 ast_autoservice_stop(chan);
1479 if (conference_bridge_user.playing_moh) {
1480 ast_moh_start(chan, conference_bridge_user.u_profile.moh_class, NULL);
1484 /* See if we need to automatically set this user as a video source or not */
1485 handle_video_on_join(conference_bridge, conference_bridge_user.chan, ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_MARKEDUSER));
1487 /* Join our conference bridge for real */
1488 send_join_event(conference_bridge_user.chan, conference_bridge->name);
1489 ast_bridge_join(conference_bridge->bridge,
1492 &conference_bridge_user.features,
1493 &conference_bridge_user.tech_args);
1494 send_leave_event(conference_bridge_user.chan, conference_bridge->name);
1496 /* if we're shutting down, don't attempt to do further processing */
1497 if (ast_shutting_down()) {
1498 leave_conference_bridge(conference_bridge, &conference_bridge_user);
1499 conference_bridge = NULL;
1500 goto confbridge_cleanup;
1503 /* If this user was a video source, we need to clean up and possibly pick a new source. */
1504 handle_video_on_exit(conference_bridge, conference_bridge_user.chan);
1506 /* if this user has a intro, play it when leaving */
1507 if (!quiet && !ast_strlen_zero(conference_bridge_user.name_rec_location)) {
1508 ast_autoservice_start(chan);
1509 play_sound_file(conference_bridge, conference_bridge_user.name_rec_location);
1510 play_sound_file(conference_bridge,
1511 conf_get_sound(CONF_SOUND_HAS_LEFT, conference_bridge_user.b_profile.sounds));
1512 ast_autoservice_stop(chan);
1515 /* play the leave sound */
1517 const char *leave_sound = conf_get_sound(CONF_SOUND_LEAVE, conference_bridge_user.b_profile.sounds);
1518 ast_autoservice_start(chan);
1519 play_sound_file(conference_bridge, leave_sound);
1520 ast_autoservice_stop(chan);
1523 /* Easy as pie, depart this channel from the conference bridge */
1524 leave_conference_bridge(conference_bridge, &conference_bridge_user);
1525 conference_bridge = NULL;
1527 /* If the user was kicked from the conference play back the audio prompt for it */
1528 if (!quiet && conference_bridge_user.kicked) {
1529 res = ast_stream_and_wait(chan,
1530 conf_get_sound(CONF_SOUND_KICKED, conference_bridge_user.b_profile.sounds),
1534 /* Restore volume adjustments to previous values in case they were changed */
1535 if (volume_adjustments[0]) {
1536 ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_READ, volume_adjustments[0]);
1538 if (volume_adjustments[1]) {
1539 ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_WRITE, volume_adjustments[1]);
1542 if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) {
1543 ast_filedelete(conference_bridge_user.name_rec_location, NULL);
1547 ast_bridge_features_cleanup(&conference_bridge_user.features);
1548 conf_bridge_profile_destroy(&conference_bridge_user.b_profile);
1552 static int action_toggle_mute(struct conference_bridge *conference_bridge,
1553 struct conference_bridge_user *conference_bridge_user,
1554 struct ast_channel *chan)
1556 /* Mute or unmute yourself, note we only allow manipulation if they aren't waiting for a marked user or if marked users exist */
1557 if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_WAITMARKED) || conference_bridge->markedusers) {
1558 conference_bridge_user->features.mute = (!conference_bridge_user->features.mute ? 1 : 0);
1559 ast_test_suite_event_notify("CONF_MUTE", "Message: participant %s %s\r\nConference: %s\r\nChannel: %s", ast_channel_name(chan), conference_bridge_user->features.mute ? "muted" : "unmuted", conference_bridge_user->b_profile.name, ast_channel_name(chan));
1561 return ast_stream_and_wait(chan, (conference_bridge_user->features.mute ?
1562 conf_get_sound(CONF_SOUND_MUTED, conference_bridge_user->b_profile.sounds) :
1563 conf_get_sound(CONF_SOUND_UNMUTED, conference_bridge_user->b_profile.sounds)),
1567 static int action_toggle_mute_participants(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
1569 struct conference_bridge_user *participant = NULL;
1570 const char *sound_to_play;
1572 ao2_lock(conference_bridge);
1574 /* If already muted, then unmute */
1575 conference_bridge->muted = conference_bridge->muted ? 0 : 1;
1576 sound_to_play = conf_get_sound((conference_bridge->muted ? CONF_SOUND_PARTICIPANTS_MUTED : CONF_SOUND_PARTICIPANTS_UNMUTED),
1577 conference_bridge_user->b_profile.sounds);
1579 AST_LIST_TRAVERSE(&conference_bridge->users_list, participant, list) {
1580 if (!ast_test_flag(&participant->u_profile, USER_OPT_ADMIN)) {
1581 participant->features.mute = conference_bridge->muted;
1585 ao2_unlock(conference_bridge);
1587 /* The host needs to hear it seperately, as they don't get the audio from play_sound_helper */
1588 ast_stream_and_wait(conference_bridge_user->chan, sound_to_play, "");
1590 /* Announce to the group that all participants are muted */
1591 ast_autoservice_start(conference_bridge_user->chan);
1592 play_sound_helper(conference_bridge, sound_to_play, 0);
1593 ast_autoservice_stop(conference_bridge_user->chan);
1598 static int action_playback(struct ast_bridge_channel *bridge_channel, const char *playback_file)
1600 char *file_copy = ast_strdupa(playback_file);
1603 while ((file = strsep(&file_copy, "&"))) {
1604 if (ast_stream_and_wait(bridge_channel->chan, file, "")) {
1605 ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
1612 static int action_playback_and_continue(struct conference_bridge *conference_bridge,
1613 struct conference_bridge_user *conference_bridge_user,
1614 struct ast_bridge_channel *bridge_channel,
1615 struct conf_menu *menu,
1616 const char *playback_file,
1617 const char *cur_dtmf,
1622 char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
1623 struct conf_menu_entry new_menu_entry = { { 0, }, };
1624 char *file_copy = ast_strdupa(playback_file);
1627 while ((file = strsep(&file_copy, "&"))) {
1628 if (ast_streamfile(bridge_channel->chan, file, ast_channel_language(bridge_channel->chan))) {
1629 ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
1633 /* now wait for more digits. */
1634 if (!(digit = ast_waitstream(bridge_channel->chan, AST_DIGIT_ANY))) {
1635 /* streaming finished and no DTMF was entered */
1637 } else if (digit == -1) {
1641 break; /* dtmf was entered */
1645 /* streaming finished on all files and no DTMF was entered */
1648 ast_stopstream(bridge_channel->chan);
1650 /* If we get here, then DTMF has been entered, This means no
1651 * additional prompts should be played for this menu entry */
1654 /* If a digit was pressed during the payback, update
1655 * the dtmf string and look for a new menu entry in the
1657 ast_copy_string(dtmf, cur_dtmf, sizeof(dtmf));
1658 for (i = 0; i < (MAXIMUM_DTMF_FEATURE_STRING - 1); i++) {
1659 dtmf[i] = cur_dtmf[i];
1661 dtmf[i] = (char) digit;
1667 /* If i is not -1 then the new dtmf digit was _NOT_ added to the string.
1668 * If this is the case, no new DTMF sequence should be looked for. */
1673 if (conf_find_menu_entry_by_sequence(dtmf, menu, &new_menu_entry)) {
1674 execute_menu_entry(conference_bridge,
1675 conference_bridge_user,
1677 &new_menu_entry, menu);
1678 conf_menu_entry_destroy(&new_menu_entry);
1683 static int action_kick_last(struct conference_bridge *conference_bridge,
1684 struct ast_bridge_channel *bridge_channel,
1685 struct conference_bridge_user *conference_bridge_user)
1687 struct conference_bridge_user *last_participant = NULL;
1688 int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN);
1691 ast_stream_and_wait(bridge_channel->chan,
1692 conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds),
1694 ast_log(LOG_WARNING, "Only admin users can use the kick_last menu action. Channel %s of conf %s is not an admin.\n",
1695 ast_channel_name(bridge_channel->chan),
1696 conference_bridge->name);
1700 ao2_lock(conference_bridge);
1701 if (((last_participant = AST_LIST_LAST(&conference_bridge->users_list)) == conference_bridge_user)
1702 || (ast_test_flag(&last_participant->u_profile, USER_OPT_ADMIN))) {
1703 ao2_unlock(conference_bridge);
1704 ast_stream_and_wait(bridge_channel->chan,
1705 conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds),
1707 } else if (last_participant) {
1708 last_participant->kicked = 1;
1709 ast_bridge_remove(conference_bridge->bridge, last_participant->chan);
1710 ao2_unlock(conference_bridge);
1715 static int action_dialplan_exec(struct ast_bridge_channel *bridge_channel, struct conf_menu_action *menu_action)
1717 struct ast_pbx_args args;
1718 struct ast_pbx *pbx;
1724 memset(&args, 0, sizeof(args));
1725 args.no_hangup_chan = 1;
1727 ast_channel_lock(bridge_channel->chan);
1730 exten = ast_strdupa(bridge_channel->chan->exten);
1731 context = ast_strdupa(bridge_channel->chan->context);
1732 priority = bridge_channel->chan->priority;
1733 pbx = bridge_channel->chan->pbx;
1734 bridge_channel->chan->pbx = NULL;
1737 ast_copy_string(bridge_channel->chan->exten, menu_action->data.dialplan_args.exten, sizeof(bridge_channel->chan->exten));
1738 ast_copy_string(bridge_channel->chan->context, menu_action->data.dialplan_args.context, sizeof(bridge_channel->chan->context));
1739 bridge_channel->chan->priority = menu_action->data.dialplan_args.priority;
1741 ast_channel_unlock(bridge_channel->chan);
1744 res = ast_pbx_run_args(bridge_channel->chan, &args);
1747 ast_channel_lock(bridge_channel->chan);
1749 ast_copy_string(bridge_channel->chan->exten, exten, sizeof(bridge_channel->chan->exten));
1750 ast_copy_string(bridge_channel->chan->context, context, sizeof(bridge_channel->chan->context));
1751 bridge_channel->chan->priority = priority;
1752 bridge_channel->chan->pbx = pbx;
1754 ast_channel_unlock(bridge_channel->chan);
1759 static int execute_menu_entry(struct conference_bridge *conference_bridge,
1760 struct conference_bridge_user *conference_bridge_user,
1761 struct ast_bridge_channel *bridge_channel,
1762 struct conf_menu_entry *menu_entry,
1763 struct conf_menu *menu)
1765 struct conf_menu_action *menu_action;
1766 int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN);
1767 int stop_prompts = 0;
1770 AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) {
1771 switch (menu_action->id) {
1772 case MENU_ACTION_TOGGLE_MUTE:
1773 res |= action_toggle_mute(conference_bridge,
1774 conference_bridge_user,
1775 bridge_channel->chan);
1777 case MENU_ACTION_ADMIN_TOGGLE_MUTE_PARTICIPANTS:
1781 action_toggle_mute_participants(conference_bridge, conference_bridge_user);
1783 case MENU_ACTION_PARTICIPANT_COUNT:
1784 announce_user_count(conference_bridge, conference_bridge_user);
1786 case MENU_ACTION_PLAYBACK:
1787 if (!stop_prompts) {
1788 res |= action_playback(bridge_channel, menu_action->data.playback_file);
1791 case MENU_ACTION_RESET_LISTENING:
1792 ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, 0);
1794 case MENU_ACTION_RESET_TALKING:
1795 ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, 0);
1797 case MENU_ACTION_INCREASE_LISTENING:
1798 ast_audiohook_volume_adjust(conference_bridge_user->chan,
1799 AST_AUDIOHOOK_DIRECTION_WRITE, 1);
1801 case MENU_ACTION_DECREASE_LISTENING:
1802 ast_audiohook_volume_adjust(conference_bridge_user->chan,
1803 AST_AUDIOHOOK_DIRECTION_WRITE, -1);
1805 case MENU_ACTION_INCREASE_TALKING:
1806 ast_audiohook_volume_adjust(conference_bridge_user->chan,
1807 AST_AUDIOHOOK_DIRECTION_READ, 1);
1809 case MENU_ACTION_DECREASE_TALKING:
1810 ast_audiohook_volume_adjust(conference_bridge_user->chan,
1811 AST_AUDIOHOOK_DIRECTION_READ, -1);
1813 case MENU_ACTION_PLAYBACK_AND_CONTINUE:
1814 if (!(stop_prompts)) {
1815 res |= action_playback_and_continue(conference_bridge,
1816 conference_bridge_user,
1819 menu_action->data.playback_file,
1824 case MENU_ACTION_DIALPLAN_EXEC:
1825 res |= action_dialplan_exec(bridge_channel, menu_action);
1827 case MENU_ACTION_ADMIN_TOGGLE_LOCK:
1831 conference_bridge->locked = (!conference_bridge->locked ? 1 : 0);
1832 res |= ast_stream_and_wait(bridge_channel->chan,
1833 (conference_bridge->locked ?
1834 conf_get_sound(CONF_SOUND_LOCKED_NOW, conference_bridge_user->b_profile.sounds) :
1835 conf_get_sound(CONF_SOUND_UNLOCKED_NOW, conference_bridge_user->b_profile.sounds)),
1839 case MENU_ACTION_ADMIN_KICK_LAST:
1840 res |= action_kick_last(conference_bridge, bridge_channel, conference_bridge_user);
1842 case MENU_ACTION_LEAVE:
1843 ao2_lock(conference_bridge);
1844 ast_bridge_remove(conference_bridge->bridge, bridge_channel->chan);
1845 ao2_unlock(conference_bridge);
1847 case MENU_ACTION_NOOP:
1849 case MENU_ACTION_SET_SINGLE_VIDEO_SRC:
1850 ao2_lock(conference_bridge);
1851 ast_bridge_set_single_src_video_mode(conference_bridge->bridge, bridge_channel->chan);
1852 ao2_unlock(conference_bridge);
1854 case MENU_ACTION_RELEASE_SINGLE_VIDEO_SRC:
1855 handle_video_on_exit(conference_bridge, bridge_channel->chan);
1862 int conf_handle_dtmf(struct ast_bridge_channel *bridge_channel,
1863 struct conference_bridge_user *conference_bridge_user,
1864 struct conf_menu_entry *menu_entry,
1865 struct conf_menu *menu)
1867 struct conference_bridge *conference_bridge = conference_bridge_user->conference_bridge;
1869 /* See if music on hold is playing */
1870 ao2_lock(conference_bridge);
1871 if (conference_bridge_user->playing_moh) {
1872 /* MOH is going, let's stop it */
1873 ast_moh_stop(bridge_channel->chan);
1875 ao2_unlock(conference_bridge);
1877 /* execute the list of actions associated with this menu entry */
1878 execute_menu_entry(conference_bridge, conference_bridge_user, bridge_channel, menu_entry, menu);
1880 /* See if music on hold needs to be started back up again */
1881 ao2_lock(conference_bridge);
1882 if (conference_bridge_user->playing_moh) {
1883 ast_moh_start(bridge_channel->chan, conference_bridge_user->u_profile.moh_class, NULL);
1885 ao2_unlock(conference_bridge);
1890 static char *complete_confbridge_name(const char *line, const char *word, int pos, int state)
1893 struct conference_bridge *bridge = NULL;
1895 int wordlen = strlen(word);
1896 struct ao2_iterator i;
1898 i = ao2_iterator_init(conference_bridges, 0);
1899 while ((bridge = ao2_iterator_next(&i))) {
1900 if (!strncasecmp(bridge->name, word, wordlen) && ++which > state) {
1901 res = ast_strdup(bridge->name);
1902 ao2_ref(bridge, -1);
1905 ao2_ref(bridge, -1);
1907 ao2_iterator_destroy(&i);
1912 static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1914 struct conference_bridge *bridge = NULL;
1915 struct conference_bridge tmp;
1916 struct conference_bridge_user *participant = NULL;
1920 e->command = "confbridge kick";
1922 "Usage: confbridge kick <conference> <channel>\n"
1923 " Kicks a channel out of the conference bridge.\n";
1927 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
1931 return complete_confbridge_channel(a->line, a->word, a->pos, a->n);
1938 return CLI_SHOWUSAGE;
1941 ast_copy_string(tmp.name, a->argv[2], sizeof(tmp.name));
1942 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
1944 ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
1948 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
1949 if (!strncmp(a->argv[3], ast_channel_name(participant->chan), strlen(ast_channel_name(participant->chan)))) {
1954 ast_cli(a->fd, "Kicking %s from confbridge %s\n", ast_channel_name(participant->chan), bridge->name);
1955 participant->kicked = 1;
1956 ast_bridge_remove(bridge->bridge, participant->chan);
1959 ao2_ref(bridge, -1);
1963 static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1965 struct ao2_iterator i;
1966 struct conference_bridge *bridge = NULL;
1967 struct conference_bridge tmp;
1968 struct conference_bridge_user *participant = NULL;
1972 e->command = "confbridge list";
1974 "Usage: confbridge list [<name>]\n"
1975 " Lists all currently active conference bridges.\n";
1979 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
1985 ast_cli(a->fd, "Conference Bridge Name Users Marked Locked?\n");
1986 ast_cli(a->fd, "================================ ====== ====== ========\n");
1987 i = ao2_iterator_init(conference_bridges, 0);
1988 while ((bridge = ao2_iterator_next(&i))) {
1989 ast_cli(a->fd, "%-32s %6i %6i %s\n", bridge->name, bridge->users, bridge->markedusers, (bridge->locked ? "locked" : "unlocked"));
1990 ao2_ref(bridge, -1);
1992 ao2_iterator_destroy(&i);
1997 ast_copy_string(tmp.name, a->argv[2], sizeof(tmp.name));
1998 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2000 ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
2003 ast_cli(a->fd, "Channel User Profile Bridge Profile Menu CallerID\n");
2004 ast_cli(a->fd, "============================= ================ ================ ================ ================\n");
2006 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2007 ast_cli(a->fd, "%-29s ", ast_channel_name(participant->chan));
2008 ast_cli(a->fd, "%-17s", participant->u_profile.name);
2009 ast_cli(a->fd, "%-17s", participant->b_profile.name);
2010 ast_cli(a->fd, "%-17s", participant->menu_name);
2011 ast_cli(a->fd, "%-17s", S_COR(participant->chan->caller.id.number.valid, participant->chan->caller.id.number.str, "<unknown>"));
2012 ast_cli(a->fd, "\n");
2015 ao2_ref(bridge, -1);
2019 return CLI_SHOWUSAGE;
2023 * \brief finds a conference by name and locks/unlocks.
2026 * \retval -1 conference not found
2028 static int generic_lock_unlock_helper(int lock, const char *conference)
2030 struct conference_bridge *bridge = NULL;
2031 struct conference_bridge tmp;
2034 ast_copy_string(tmp.name, conference, sizeof(tmp.name));
2035 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2040 bridge->locked = lock;
2041 ast_test_suite_event_notify("CONF_LOCK", "Message: conference %s\r\nConference: %s", bridge->locked ? "locked" : "unlocked", bridge->b_profile.name);
2043 ao2_ref(bridge, -1);
2049 * \brief finds a conference user by channel name and mutes/unmutes them.
2052 * \retval -1 conference not found
2053 * \retval -2 user not found
2055 static int generic_mute_unmute_helper(int mute, const char *conference, const char *user)
2057 struct conference_bridge *bridge = NULL;
2058 struct conference_bridge tmp;
2059 struct conference_bridge_user *participant = NULL;
2061 ast_copy_string(tmp.name, conference, sizeof(tmp.name));
2062 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2067 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2068 if (!strncmp(user, ast_channel_name(participant->chan), strlen(user))) {
2073 participant->features.mute = mute;
2074 ast_test_suite_event_notify("CONF_MUTE", "Message: participant %s %s\r\nConference: %s\r\nChannel: %s", ast_channel_name(participant->chan), participant->features.mute ? "muted" : "unmuted", bridge->b_profile.name, ast_channel_name(participant->chan));
2079 ao2_ref(bridge, -1);
2084 static int cli_mute_unmute_helper(int mute, struct ast_cli_args *a)
2086 int res = generic_mute_unmute_helper(mute, a->argv[2], a->argv[3]);
2089 ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
2091 } else if (res == -2) {
2092 ast_cli(a->fd, "No channel named '%s' found in conference %s\n", a->argv[3], a->argv[2]);
2095 ast_cli(a->fd, "%s %s from confbridge %s\n", mute ? "Muting" : "Unmuting", a->argv[3], a->argv[2]);
2099 static char *handle_cli_confbridge_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2103 e->command = "confbridge mute";
2105 "Usage: confbridge mute <conference> <channel>\n";
2109 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
2114 return CLI_SHOWUSAGE;
2117 cli_mute_unmute_helper(1, a);
2122 static char *handle_cli_confbridge_unmute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2126 e->command = "confbridge unmute";
2128 "Usage: confbridge unmute <conference> <channel>\n";
2132 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
2137 return CLI_SHOWUSAGE;
2140 cli_mute_unmute_helper(0, a);
2145 static char *handle_cli_confbridge_lock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2149 e->command = "confbridge lock";
2151 "Usage: confbridge lock <conference>\n";
2155 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
2160 return CLI_SHOWUSAGE;
2162 if (generic_lock_unlock_helper(1, a->argv[2])) {
2163 ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]);
2165 ast_cli(a->fd, "Conference %s is locked.\n", a->argv[2]);
2170 static char *handle_cli_confbridge_unlock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2174 e->command = "confbridge unlock";
2176 "Usage: confbridge unlock <conference>\n";
2180 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
2185 return CLI_SHOWUSAGE;
2187 if (generic_lock_unlock_helper(0, a->argv[2])) {
2188 ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]);
2190 ast_cli(a->fd, "Conference %s is unlocked.\n", a->argv[2]);
2195 static char *handle_cli_confbridge_start_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2197 const char *rec_file = NULL;
2198 struct conference_bridge *bridge = NULL;
2199 struct conference_bridge tmp;
2203 e->command = "confbridge record start";
2205 "Usage: confbridge record start <conference> <file>\n"
2206 " <file> is optional, Otherwise the bridge profile\n"
2207 " record file will be used. If the bridge profile\n"
2208 " has no record file specified, a file will automatically\n"
2209 " be generated in the monitor directory\n";
2213 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
2218 return CLI_SHOWUSAGE;
2221 rec_file = a->argv[4];
2224 ast_copy_string(tmp.name, a->argv[3], sizeof(tmp.name));
2225 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2227 ast_cli(a->fd, "Conference not found.\n");
2230 if (conf_is_recording(bridge)) {
2231 ast_cli(a->fd, "Conference is already being recorded.\n");
2232 ao2_ref(bridge, -1);
2235 if (!ast_strlen_zero(rec_file)) {
2237 ast_copy_string(bridge->b_profile.rec_file, rec_file, sizeof(bridge->b_profile.rec_file));
2240 if (conf_start_record(bridge)) {
2241 ast_cli(a->fd, "Could not start recording due to internal error.\n");
2242 ao2_ref(bridge, -1);
2245 ast_cli(a->fd, "Recording started\n");
2246 ao2_ref(bridge, -1);
2250 static char *handle_cli_confbridge_stop_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2252 struct conference_bridge *bridge = NULL;
2253 struct conference_bridge tmp;
2257 e->command = "confbridge record stop";
2259 "Usage: confbridge record stop <conference>\n";
2263 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
2268 return CLI_SHOWUSAGE;
2271 ast_copy_string(tmp.name, a->argv[3], sizeof(tmp.name));
2272 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2274 ast_cli(a->fd, "Conference not found.\n");
2277 conf_stop_record(bridge);
2278 ast_cli(a->fd, "Recording stopped.\n");
2279 ao2_ref(bridge, -1);
2283 static struct ast_cli_entry cli_confbridge[] = {
2284 AST_CLI_DEFINE(handle_cli_confbridge_list, "List conference bridges and participants."),
2285 AST_CLI_DEFINE(handle_cli_confbridge_kick, "Kick participants out of conference bridges."),
2286 AST_CLI_DEFINE(handle_cli_confbridge_mute, "Mute a participant."),
2287 AST_CLI_DEFINE(handle_cli_confbridge_unmute, "Unmute a participant."),
2288 AST_CLI_DEFINE(handle_cli_confbridge_lock, "Lock a conference."),
2289 AST_CLI_DEFINE(handle_cli_confbridge_unlock, "Unlock a conference."),
2290 AST_CLI_DEFINE(handle_cli_confbridge_start_record, "Start recording a conference"),
2291 AST_CLI_DEFINE(handle_cli_confbridge_stop_record, "Stop recording a conference."),
2293 static struct ast_custom_function confbridge_function = {
2294 .name = "CONFBRIDGE",
2295 .write = func_confbridge_helper,
2298 static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
2299 static struct ast_custom_function confbridge_info_function = {
2300 .name = "CONFBRIDGE_INFO",
2301 .read = func_confbridge_info,
2304 static int action_confbridgelist(struct mansession *s, const struct message *m)
2306 const char *actionid = astman_get_header(m, "ActionID");
2307 const char *conference = astman_get_header(m, "Conference");
2308 struct conference_bridge_user *participant = NULL;
2309 struct conference_bridge *bridge = NULL;
2310 struct conference_bridge tmp;
2311 char id_text[80] = "";
2314 if (!ast_strlen_zero(actionid)) {
2315 snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid);
2317 if (ast_strlen_zero(conference)) {
2318 astman_send_error(s, m, "No Conference name provided.");
2321 if (!ao2_container_count(conference_bridges)) {
2322 astman_send_error(s, m, "No active conferences.");
2325 ast_copy_string(tmp.name, conference, sizeof(tmp.name));
2326 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2328 astman_send_error(s, m, "No Conference by that name found.");
2332 astman_send_listack(s, m, "Confbridge user list will follow", "start");
2335 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2338 "Event: ConfbridgeList\r\n"
2340 "Conference: %s\r\n"
2341 "CallerIDNum: %s\r\n"
2342 "CallerIDName: %s\r\n"
2345 "MarkedUser: %s\r\n"
2349 S_COR(participant->chan->caller.id.number.valid, participant->chan->caller.id.number.str, "<unknown>"),
2350 S_COR(participant->chan->caller.id.name.valid, participant->chan->caller.id.name.str, "<no name>"),
2351 ast_channel_name(participant->chan),
2352 ast_test_flag(&participant->u_profile, USER_OPT_ADMIN) ? "Yes" : "No",
2353 ast_test_flag(&participant->u_profile, USER_OPT_MARKEDUSER) ? "Yes" : "No");
2356 ao2_ref(bridge, -1);
2359 "Event: ConfbridgeListComplete\r\n"
2360 "EventList: Complete\r\n"
2363 "\r\n", total, id_text);
2368 static int action_confbridgelistrooms(struct mansession *s, const struct message *m)
2370 const char *actionid = astman_get_header(m, "ActionID");
2371 struct conference_bridge *bridge = NULL;
2372 struct ao2_iterator i;
2373 char id_text[512] = "";
2376 if (!ast_strlen_zero(actionid)) {
2377 snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid);
2380 if (!ao2_container_count(conference_bridges)) {
2381 astman_send_error(s, m, "No active conferences.");
2385 astman_send_listack(s, m, "Confbridge conferences will follow", "start");
2387 /* Traverse the conference list */
2388 i = ao2_iterator_init(conference_bridges, 0);
2389 while ((bridge = ao2_iterator_next(&i))) {
2394 "Event: ConfbridgeListRooms\r\n"
2396 "Conference: %s\r\n"
2404 bridge->markedusers,
2405 bridge->locked ? "Yes" : "No");
2408 ao2_ref(bridge, -1);
2410 ao2_iterator_destroy(&i);
2412 /* Send final confirmation */
2414 "Event: ConfbridgeListRoomsComplete\r\n"
2415 "EventList: Complete\r\n"
2418 "\r\n", totalitems, id_text);
2422 static int action_mute_unmute_helper(struct mansession *s, const struct message *m, int mute)
2424 const char *conference = astman_get_header(m, "Conference");
2425 const char *channel = astman_get_header(m, "Channel");
2428 if (ast_strlen_zero(conference)) {
2429 astman_send_error(s, m, "No Conference name provided.");
2432 if (ast_strlen_zero(channel)) {
2433 astman_send_error(s, m, "No channel name provided.");
2436 if (!ao2_container_count(conference_bridges)) {
2437 astman_send_error(s, m, "No active conferences.");
2441 res = generic_mute_unmute_helper(mute, conference, channel);
2444 astman_send_error(s, m, "No Conference by that name found.");
2446 } else if (res == -2) {
2447 astman_send_error(s, m, "No Channel by that name found in Conference.");
2451 astman_send_ack(s, m, mute ? "User muted" : "User unmuted");
2455 static int action_confbridgeunmute(struct mansession *s, const struct message *m)
2457 return action_mute_unmute_helper(s, m, 0);
2459 static int action_confbridgemute(struct mansession *s, const struct message *m)
2461 return action_mute_unmute_helper(s, m, 1);
2464 static int action_lock_unlock_helper(struct mansession *s, const struct message *m, int lock)
2466 const char *conference = astman_get_header(m, "Conference");
2469 if (ast_strlen_zero(conference)) {
2470 astman_send_error(s, m, "No Conference name provided.");
2473 if (!ao2_container_count(conference_bridges)) {
2474 astman_send_error(s, m, "No active conferences.");
2477 if ((res = generic_lock_unlock_helper(lock, conference))) {
2478 astman_send_error(s, m, "No Conference by that name found.");
2481 astman_send_ack(s, m, lock ? "Conference locked" : "Conference unlocked");
2484 static int action_confbridgeunlock(struct mansession *s, const struct message *m)
2486 return action_lock_unlock_helper(s, m, 0);
2488 static int action_confbridgelock(struct mansession *s, const struct message *m)
2490 return action_lock_unlock_helper(s, m, 1);
2493 static int action_confbridgekick(struct mansession *s, const struct message *m)
2495 const char *conference = astman_get_header(m, "Conference");
2496 const char *channel = astman_get_header(m, "Channel");
2497 struct conference_bridge_user *participant = NULL;
2498 struct conference_bridge *bridge = NULL;
2499 struct conference_bridge tmp;
2502 if (ast_strlen_zero(conference)) {
2503 astman_send_error(s, m, "No Conference name provided.");
2506 if (!ao2_container_count(conference_bridges)) {
2507 astman_send_error(s, m, "No active conferences.");
2510 ast_copy_string(tmp.name, conference, sizeof(tmp.name));
2511 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2513 astman_send_error(s, m, "No Conference by that name found.");
2518 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2519 if (!strcasecmp(ast_channel_name(participant->chan), channel)) {
2520 participant->kicked = 1;
2521 ast_bridge_remove(bridge->bridge, participant->chan);
2527 ao2_ref(bridge, -1);
2530 astman_send_ack(s, m, "User kicked");
2532 astman_send_error(s, m, "No Channel by that name found in Conference.");
2537 static int action_confbridgestartrecord(struct mansession *s, const struct message *m)
2539 const char *conference = astman_get_header(m, "Conference");
2540 const char *recordfile = astman_get_header(m, "RecordFile");
2541 struct conference_bridge *bridge = NULL;
2542 struct conference_bridge tmp;
2544 if (ast_strlen_zero(conference)) {
2545 astman_send_error(s, m, "No Conference name provided.");
2548 if (!ao2_container_count(conference_bridges)) {
2549 astman_send_error(s, m, "No active conferences.");
2553 ast_copy_string(tmp.name, conference, sizeof(tmp.name));
2554 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2556 astman_send_error(s, m, "No Conference by that name found.");
2560 if (conf_is_recording(bridge)) {
2561 astman_send_error(s, m, "Conference is already being recorded.");
2562 ao2_ref(bridge, -1);
2566 if (!ast_strlen_zero(recordfile)) {
2568 ast_copy_string(bridge->b_profile.rec_file, recordfile, sizeof(bridge->b_profile.rec_file));
2572 if (conf_start_record(bridge)) {
2573 astman_send_error(s, m, "Internal error starting conference recording.");
2574 ao2_ref(bridge, -1);
2578 ao2_ref(bridge, -1);
2579 astman_send_ack(s, m, "Conference Recording Started.");
2582 static int action_confbridgestoprecord(struct mansession *s, const struct message *m)
2584 const char *conference = astman_get_header(m, "Conference");
2585 struct conference_bridge *bridge = NULL;
2586 struct conference_bridge tmp;
2588 if (ast_strlen_zero(conference)) {
2589 astman_send_error(s, m, "No Conference name provided.");
2592 if (!ao2_container_count(conference_bridges)) {
2593 astman_send_error(s, m, "No active conferences.");
2597 ast_copy_string(tmp.name, conference, sizeof(tmp.name));
2598 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2600 astman_send_error(s, m, "No Conference by that name found.");
2604 if (conf_stop_record(bridge)) {
2605 astman_send_error(s, m, "Internal error while stopping recording.");
2606 ao2_ref(bridge, -1);
2610 ao2_ref(bridge, -1);
2611 astman_send_ack(s, m, "Conference Recording Stopped.");
2615 static int action_confbridgesetsinglevideosrc(struct mansession *s, const struct message *m)
2617 const char *conference = astman_get_header(m, "Conference");
2618 const char *channel = astman_get_header(m, "Channel");
2619 struct conference_bridge_user *participant = NULL;
2620 struct conference_bridge *bridge = NULL;
2621 struct conference_bridge tmp;
2623 if (ast_strlen_zero(conference)) {
2624 astman_send_error(s, m, "No Conference name provided.");
2627 if (ast_strlen_zero(channel)) {
2628 astman_send_error(s, m, "No channel name provided.");
2631 if (!ao2_container_count(conference_bridges)) {
2632 astman_send_error(s, m, "No active conferences.");
2636 ast_copy_string(tmp.name, conference, sizeof(tmp.name));
2637 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2639 astman_send_error(s, m, "No Conference by that name found.");
2643 /* find channel and set as video src. */
2645 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2646 if (!strncmp(channel, ast_channel_name(participant->chan), strlen(channel))) {
2647 ast_bridge_set_single_src_video_mode(bridge->bridge, participant->chan);
2652 ao2_ref(bridge, -1);
2654 /* do not access participant after bridge unlock. We are just
2655 * using this check to see if it was found or not */
2657 astman_send_error(s, m, "No channel by that name found in conference.");
2660 astman_send_ack(s, m, "Conference single video source set.");
2664 static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2667 struct conference_bridge *bridge = NULL;
2668 struct conference_bridge_user *participant = NULL;
2669 struct conference_bridge tmp;
2671 AST_DECLARE_APP_ARGS(args,
2673 AST_APP_ARG(confno);
2676 /* parse all the required arguments and make sure they exist. */
2677 if (ast_strlen_zero(data)) {
2680 parse = ast_strdupa(data);
2681 AST_STANDARD_APP_ARGS(args, parse);
2682 if (ast_strlen_zero(args.confno) || ast_strlen_zero(args.type)) {
2685 if (!ao2_container_count(conference_bridges)) {
2686 snprintf(buf, len, "0");
2689 ast_copy_string(tmp.name, args.confno, sizeof(tmp.name));
2690 bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
2692 snprintf(buf, len, "0");
2696 /* get the correct count for the type requested */
2698 if (!strncasecmp(args.type, "parties", 7)) {
2699 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2702 } else if (!strncasecmp(args.type, "admins", 6)) {
2703 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2704 if (ast_test_flag(&participant->u_profile, USER_OPT_ADMIN)) {
2708 } else if (!strncasecmp(args.type, "marked", 6)) {
2709 AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
2710 if (ast_test_flag(&participant->u_profile, USER_OPT_MARKEDUSER)) {
2714 } else if (!strncasecmp(args.type, "locked", 6)) {
2715 count = bridge->locked;
2717 ast_log(LOG_ERROR, "Invalid keyword '%s' passed to CONFBRIDGE_INFO. Should be one of: "
2718 "parties, admins, marked, or locked.\n", args.type);
2720 snprintf(buf, len, "%d", count);
2722 ao2_ref(bridge, -1);
2726 /*! \brief Called when module is being unloaded */
2727 static int unload_module(void)
2729 int res = ast_unregister_application(app);
2731 ast_custom_function_unregister(&confbridge_function);
2732 ast_custom_function_unregister(&confbridge_info_function);
2734 ast_cli_unregister_multiple(cli_confbridge, sizeof(cli_confbridge) / sizeof(struct ast_cli_entry));
2736 /* Get rid of the conference bridges container. Since we only allow dynamic ones none will be active. */
2737 ao2_ref(conference_bridges, -1);
2739 conf_destroy_config();
2741 ast_channel_unregister(&record_tech);
2742 record_tech.capabilities = ast_format_cap_destroy(record_tech.capabilities);
2744 res |= ast_manager_unregister("ConfbridgeList");
2745 res |= ast_manager_unregister("ConfbridgeListRooms");
2746 res |= ast_manager_unregister("ConfbridgeMute");
2747 res |= ast_manager_unregister("ConfbridgeUnmute");
2748 res |= ast_manager_unregister("ConfbridgeKick");
2749 res |= ast_manager_unregister("ConfbridgeUnlock");
2750 res |= ast_manager_unregister("ConfbridgeLock");
2751 res |= ast_manager_unregister("ConfbridgeStartRecord");
2752 res |= ast_manager_unregister("ConfbridgeStopRecord");
2757 /*! \brief Called when module is being loaded */
2758 static int load_module(void)
2761 if ((ast_custom_function_register(&confbridge_function))) {
2762 return AST_MODULE_LOAD_FAILURE;
2764 if ((ast_custom_function_register(&confbridge_info_function))) {
2765 return AST_MODULE_LOAD_FAILURE;
2767 if (!(record_tech.capabilities = ast_format_cap_alloc())) {
2768 return AST_MODULE_LOAD_FAILURE;
2770 ast_format_cap_add_all(record_tech.capabilities);
2771 if (ast_channel_register(&record_tech)) {
2772 ast_log(LOG_ERROR, "Unable to register ConfBridge recorder.\n");
2773 return AST_MODULE_LOAD_FAILURE;
2775 /* Create a container to hold the conference bridges */
2776 if (!(conference_bridges = ao2_container_alloc(CONFERENCE_BRIDGE_BUCKETS, conference_bridge_hash_cb, conference_bridge_cmp_cb))) {
2777 return AST_MODULE_LOAD_FAILURE;
2779 if (ast_register_application_xml(app, confbridge_exec)) {
2780 ao2_ref(conference_bridges, -1);
2781 return AST_MODULE_LOAD_FAILURE;
2784 res |= ast_cli_register_multiple(cli_confbridge, sizeof(cli_confbridge) / sizeof(struct ast_cli_entry));
2785 res |= ast_manager_register_xml("ConfbridgeList", EVENT_FLAG_REPORTING, action_confbridgelist);
2786 res |= ast_manager_register_xml("ConfbridgeListRooms", EVENT_FLAG_REPORTING, action_confbridgelistrooms);
2787 res |= ast_manager_register_xml("ConfbridgeMute", EVENT_FLAG_CALL, action_confbridgemute);
2788 res |= ast_manager_register_xml("ConfbridgeUnmute", EVENT_FLAG_CALL, action_confbridgeunmute);
2789 res |= ast_manager_register_xml("ConfbridgeKick", EVENT_FLAG_CALL, action_confbridgekick);
2790 res |= ast_manager_register_xml("ConfbridgeUnlock", EVENT_FLAG_CALL, action_confbridgeunlock);
2791 res |= ast_manager_register_xml("ConfbridgeLock", EVENT_FLAG_CALL, action_confbridgelock);
2792 res |= ast_manager_register_xml("ConfbridgeStartRecord", EVENT_FLAG_CALL, action_confbridgestartrecord);
2793 res |= ast_manager_register_xml("ConfbridgeStopRecord", EVENT_FLAG_CALL, action_confbridgestoprecord);
2794 res |= ast_manager_register_xml("ConfbridgeSetSingleVideoSrc", EVENT_FLAG_CALL, action_confbridgesetsinglevideosrc);
2796 conf_load_config(0);
2800 static int reload(void)
2802 return conf_load_config(1);
2805 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Conference Bridge Application",
2806 .load = load_module,
2807 .unload = unload_module,
2809 .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,