| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Asterisk -- An open source telephony toolkit. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2007-2008, Digium, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Joshua Colp <jcolp@digium.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * See http://www.asterisk.org for more information about
 | 
					
						
							|  |  |  |  * the Asterisk project. Please do not directly contact | 
					
						
							|  |  |  |  * any of the maintainers of this project for assistance; | 
					
						
							|  |  |  |  * the project provides a web site, mailing lists and IRC | 
					
						
							|  |  |  |  * channels for your use. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software, distributed under the terms of | 
					
						
							|  |  |  |  * the GNU General Public License Version 2. See the LICENSE file | 
					
						
							|  |  |  |  * at the top of the source tree. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \file
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \brief Conference Bridge application | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \author\verbatim Joshua Colp <jcolp@digium.com> \endverbatim | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This is a conference bridge application utilizing the bridging core. | 
					
						
							|  |  |  |  * \ingroup applications | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-14 20:13:06 +00:00
										 |  |  | /*** MODULEINFO
 | 
					
						
							| 
									
										
										
										
											2011-07-27 19:27:14 +00:00
										 |  |  | 	<support_level>extended</support_level> | 
					
						
							| 
									
										
										
										
											2011-07-14 20:13:06 +00:00
										 |  |  |  ***/ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | #include "asterisk.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ASTERISK_FILE_VERSION(__FILE__, "$Revision$") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <stdio.h>
 | 
					
						
							|  |  |  | #include <stdlib.h>
 | 
					
						
							|  |  |  | #include <unistd.h>
 | 
					
						
							|  |  |  | #include <string.h>
 | 
					
						
							|  |  |  | #include <signal.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "asterisk/file.h"
 | 
					
						
							|  |  |  | #include "asterisk/logger.h"
 | 
					
						
							|  |  |  | #include "asterisk/channel.h"
 | 
					
						
							|  |  |  | #include "asterisk/pbx.h"
 | 
					
						
							|  |  |  | #include "asterisk/module.h"
 | 
					
						
							|  |  |  | #include "asterisk/lock.h"
 | 
					
						
							|  |  |  | #include "asterisk/app.h"
 | 
					
						
							|  |  |  | #include "asterisk/bridging.h"
 | 
					
						
							|  |  |  | #include "asterisk/musiconhold.h"
 | 
					
						
							|  |  |  | #include "asterisk/say.h"
 | 
					
						
							|  |  |  | #include "asterisk/audiohook.h"
 | 
					
						
							|  |  |  | #include "asterisk/astobj2.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*** DOCUMENTATION
 | 
					
						
							|  |  |  |         <application name="ConfBridge" language="en_US"> | 
					
						
							|  |  |  |                 <synopsis> | 
					
						
							|  |  |  |                         Conference bridge application. | 
					
						
							|  |  |  |                 </synopsis> | 
					
						
							|  |  |  |                 <syntax> | 
					
						
							|  |  |  |                         <parameter name="confno"> | 
					
						
							|  |  |  |                                 <para>The conference number</para> | 
					
						
							|  |  |  |                         </parameter> | 
					
						
							|  |  |  |                         <parameter name="options"> | 
					
						
							|  |  |  |                                 <optionlist> | 
					
						
							|  |  |  |                                         <option name="a"> | 
					
						
							|  |  |  |                                                 <para>Set admin mode.</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="A"> | 
					
						
							|  |  |  |                                                 <para>Set marked mode.</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="c"> | 
					
						
							|  |  |  |                                                 <para>Announce user(s) count on joining a conference.</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="m"> | 
					
						
							|  |  |  |                                                 <para>Set initially muted.</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="M" hasparams="optional"> | 
					
						
							|  |  |  |                                                 <para>Enable music on hold when the conference has a single caller. Optionally, | 
					
						
							|  |  |  |                                                 specify a musiconhold class to use. If one is not provided, it will use the | 
					
						
							|  |  |  |                                                 channel's currently set music class, or <literal>default</literal>.</para> | 
					
						
							|  |  |  |                                                 <argument name="class" required="true" /> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="1"> | 
					
						
							|  |  |  |                                                 <para>Do not play message when first person enters</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="s"> | 
					
						
							|  |  |  |                                                 <para>Present menu (user or admin) when <literal>*</literal> is received | 
					
						
							|  |  |  |                                                 (send to menu).</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="w"> | 
					
						
							|  |  |  |                                                 <para>Wait until the marked user enters the conference.</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  |                                         <option name="q"> | 
					
						
							|  |  |  |                                                 <para>Quiet mode (don't play enter/leave sounds).</para> | 
					
						
							|  |  |  |                                         </option> | 
					
						
							|  |  |  | 				</optionlist> | 
					
						
							|  |  |  | 		      </parameter> | 
					
						
							|  |  |  |                 </syntax> | 
					
						
							|  |  |  |                 <description> | 
					
						
							|  |  |  |                         <para>Enters the user into a specified conference bridge. The user can exit the conference by hangup only.</para> | 
					
						
							|  |  |  |                         <para>The join sound can be set using the <literal>CONFBRIDGE_JOIN_SOUND</literal> variable and the leave sound can be set using the <literal>CONFBRIDGE_LEAVE_SOUND</literal> variable. These can be unique to the caller.</para> | 
					
						
							| 
									
										
										
										
											2010-03-02 19:02:56 +00:00
										 |  |  | 			<note><para>This application will not automatically answer the channel.</para></note> | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  |                 </description> | 
					
						
							|  |  |  |         </application> | 
					
						
							|  |  |  | ***/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \par Playing back a file to a channel in a conference | 
					
						
							|  |  |  |  * You might notice in this application that while playing a sound file | 
					
						
							|  |  |  |  * to a channel the actual conference bridge lock is not held. This is done so | 
					
						
							|  |  |  |  * that other channels are not blocked from interacting with the conference bridge. | 
					
						
							|  |  |  |  * Unfortunately because of this it is possible for things to change after the sound file | 
					
						
							|  |  |  |  * is done being played. Data must therefore be checked after reacquiring the conference | 
					
						
							|  |  |  |  * bridge lock if it is important. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-06-07 14:55:51 +00:00
										 |  |  | static const char app[] = "ConfBridge"; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  | 	OPTION_ADMIN = (1 << 0),             /*!< Set if the caller is an administrator */ | 
					
						
							|  |  |  | 	OPTION_MENU = (1 << 1),              /*!< Set if the caller should have access to the conference bridge IVR menu */ | 
					
						
							|  |  |  | 	OPTION_MUSICONHOLD = (1 << 2),       /*!< Set if music on hold should be played if nobody else is in the conference bridge */ | 
					
						
							|  |  |  | 	OPTION_NOONLYPERSON = (1 << 3),      /*!< Set if the "you are currently the only person in this conference" sound file should not be played */ | 
					
						
							|  |  |  | 	OPTION_STARTMUTED = (1 << 4),        /*!< Set if the caller should be initially set muted */ | 
					
						
							|  |  |  | 	OPTION_ANNOUNCEUSERCOUNT = (1 << 5), /*!< Set if the number of users should be announced to the caller */ | 
					
						
							|  |  |  | 	OPTION_MARKEDUSER = (1 << 6),        /*!< Set if the caller is a marked user */ | 
					
						
							|  |  |  | 	OPTION_WAITMARKED = (1 << 7),        /*!< Set if the conference must wait for a marked user before starting */ | 
					
						
							|  |  |  | 	OPTION_QUIET = (1 << 8),             /*!< Set if no audio prompts should be played */ | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  | 	OPTION_MUSICONHOLD_CLASS,            /*!< If the 'M' option is set, the music on hold class to play */ | 
					
						
							|  |  |  | 	/*This must be the last element */ | 
					
						
							|  |  |  | 	OPTION_ARRAY_SIZE, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AST_APP_OPTIONS(app_opts,{ | 
					
						
							|  |  |  | 	AST_APP_OPTION('A', OPTION_MARKEDUSER), | 
					
						
							|  |  |  | 	AST_APP_OPTION('a', OPTION_ADMIN), | 
					
						
							|  |  |  | 	AST_APP_OPTION('c', OPTION_ANNOUNCEUSERCOUNT), | 
					
						
							| 
									
										
										
										
											2009-09-29 19:49:02 +00:00
										 |  |  | 	AST_APP_OPTION('m', OPTION_STARTMUTED), | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	AST_APP_OPTION_ARG('M', OPTION_MUSICONHOLD, OPTION_MUSICONHOLD_CLASS), | 
					
						
							|  |  |  | 	AST_APP_OPTION('1', OPTION_NOONLYPERSON), | 
					
						
							| 
									
										
										
										
											2009-09-29 19:49:02 +00:00
										 |  |  | 	AST_APP_OPTION('s', OPTION_MENU), | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	AST_APP_OPTION('w', OPTION_WAITMARKED), | 
					
						
							|  |  |  | 	AST_APP_OPTION('q', OPTION_QUIET), | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Maximum length of a conference bridge name */ | 
					
						
							|  |  |  | #define MAX_CONF_NAME 32
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Number of buckets our conference bridges container can have */ | 
					
						
							|  |  |  | #define CONFERENCE_BRIDGE_BUCKETS 53
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief The structure that represents a conference bridge */ | 
					
						
							|  |  |  | struct conference_bridge { | 
					
						
							|  |  |  | 	char name[MAX_CONF_NAME];                                         /*!< Name of the conference bridge */ | 
					
						
							|  |  |  | 	struct ast_bridge *bridge;                                        /*!< Bridge structure doing the mixing */ | 
					
						
							|  |  |  | 	unsigned int users;                                               /*!< Number of users present */ | 
					
						
							|  |  |  | 	unsigned int markedusers;                                         /*!< Number of marked users present */ | 
					
						
							|  |  |  | 	unsigned int locked:1;                                            /*!< Is this conference bridge locked? */ | 
					
						
							|  |  |  | 	AST_LIST_HEAD_NOLOCK(, conference_bridge_user) users_list;        /*!< List of users participating in the conference bridge */ | 
					
						
							|  |  |  | 	struct ast_channel *playback_chan;                                /*!< Channel used for playback into the conference bridge */ | 
					
						
							|  |  |  | 	ast_mutex_t playback_lock;                                        /*!< Lock used for playback channel */ | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief The structure that represents a conference bridge user */ | 
					
						
							|  |  |  | struct conference_bridge_user { | 
					
						
							|  |  |  | 	struct conference_bridge *conference_bridge; /*!< Conference bridge they are participating in */ | 
					
						
							|  |  |  | 	struct ast_channel *chan;                    /*!< Asterisk channel participating */ | 
					
						
							|  |  |  | 	struct ast_flags flags;                      /*!< Flags passed in when the application was called */ | 
					
						
							|  |  |  | 	char *opt_args[OPTION_ARRAY_SIZE];           /*!< Arguments to options passed when application was called */ | 
					
						
							|  |  |  | 	struct ast_bridge_features features;         /*!< Bridge features structure */ | 
					
						
							|  |  |  | 	unsigned int kicked:1;                       /*!< User has been kicked from the conference */ | 
					
						
							|  |  |  | 	AST_LIST_ENTRY(conference_bridge_user) list; /*!< Linked list information */ | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief Container to hold all conference bridges in progress */ | 
					
						
							|  |  |  | static struct ao2_container *conference_bridges; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int play_sound_file(struct conference_bridge *conference_bridge, const char *filename); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief Hashing function used for conference bridges container */ | 
					
						
							|  |  |  | static int conference_bridge_hash_cb(const void *obj, const int flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const struct conference_bridge *conference_bridge = obj; | 
					
						
							|  |  |  | 	return ast_str_case_hash(conference_bridge->name); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief Comparison function used for conference bridges container */ | 
					
						
							|  |  |  | static int conference_bridge_cmp_cb(void *obj, void *arg, int flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const struct conference_bridge *conference_bridge0 = obj, *conference_bridge1 = arg; | 
					
						
							|  |  |  | 	return (!strcasecmp(conference_bridge0->name, conference_bridge1->name) ? CMP_MATCH | CMP_STOP : 0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Announce number of users in the conference bridge to the caller | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param conference_bridge Conference bridge to peek at | 
					
						
							|  |  |  |  * \param conference_bridge_user Caller | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  |  * \return Returns 0 on success, -1 if the user hung up | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | static int announce_user_count(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	if (conference_bridge->users == 1) { | 
					
						
							|  |  |  | 		/* Awww we are the only person in the conference bridge */ | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 		return 0; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	} else if (conference_bridge->users == 2) { | 
					
						
							|  |  |  | 		/* Eep, there is one other person */ | 
					
						
							|  |  |  | 		if (ast_stream_and_wait(conference_bridge_user->chan, "conf-onlyone", "")) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			return -1; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* Alas multiple others in here */ | 
					
						
							|  |  |  | 		if (ast_stream_and_wait(conference_bridge_user->chan, "conf-thereare", "")) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			return -1; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if (ast_say_number(conference_bridge_user->chan, conference_bridge->users - 1, "", conference_bridge_user->chan->language, NULL)) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			return -1; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if (ast_stream_and_wait(conference_bridge_user->chan, "conf-otherinparty", "")) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			return -1; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Play back an audio file to a channel | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param conference_bridge Conference bridge they are in | 
					
						
							|  |  |  |  * \param chan Channel to play audio prompt to | 
					
						
							|  |  |  |  * \param file Prompt to play | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  |  * \return Returns 0 on success, -1 if the user hung up | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  |  * | 
					
						
							|  |  |  |  * \note This function assumes that conference_bridge is locked | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | static int play_prompt_to_channel(struct conference_bridge *conference_bridge, struct ast_channel *chan, const char *file) | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 	int res; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	ao2_unlock(conference_bridge); | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 	res = ast_stream_and_wait(chan, file, ""); | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	ao2_lock(conference_bridge); | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 	return res; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Perform post-joining marked specific actions | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param conference_bridge Conference bridge being joined | 
					
						
							|  |  |  |  * \param conference_bridge_user Conference bridge user joining | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  |  * \return Returns 0 on success, -1 if the user hung up | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | static int post_join_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER)) { | 
					
						
							|  |  |  | 		struct conference_bridge_user *other_conference_bridge_user = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* If we are not the first marked user to join just bail out now */ | 
					
						
							|  |  |  | 		if (conference_bridge->markedusers >= 2) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			return 0; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Iterate through every participant stopping MOH on them if need be */ | 
					
						
							|  |  |  | 		AST_LIST_TRAVERSE(&conference_bridge->users_list, other_conference_bridge_user, list) { | 
					
						
							|  |  |  | 			if (other_conference_bridge_user == conference_bridge_user) { | 
					
						
							|  |  |  | 				continue; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (ast_test_flag(&other_conference_bridge_user->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_conference_bridge_user->chan)) { | 
					
						
							|  |  |  | 				ast_moh_stop(other_conference_bridge_user->chan); | 
					
						
							|  |  |  | 				ast_bridge_unsuspend(conference_bridge->bridge, other_conference_bridge_user->chan); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Next play the audio file stating they are going to be placed into the conference */ | 
					
						
							| 
									
										
										
										
											2009-03-11 13:44:42 +00:00
										 |  |  | 		if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET)) { | 
					
						
							|  |  |  | 			ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 			ast_autoservice_start(conference_bridge_user->chan); | 
					
						
							|  |  |  | 			play_sound_file(conference_bridge, "conf-placeintoconf"); | 
					
						
							|  |  |  | 			ast_autoservice_stop(conference_bridge_user->chan); | 
					
						
							|  |  |  | 			ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Finally iterate through and unmute them all */ | 
					
						
							|  |  |  | 		AST_LIST_TRAVERSE(&conference_bridge->users_list, other_conference_bridge_user, list) { | 
					
						
							|  |  |  | 			if (other_conference_bridge_user == conference_bridge_user) { | 
					
						
							|  |  |  | 				continue; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			other_conference_bridge_user->features.mute = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* If a marked user already exists in the conference bridge we can just bail out now */ | 
					
						
							|  |  |  | 		if (conference_bridge->markedusers) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			return 0; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		/* Be sure we are muted so we can't talk to anybody else waiting */ | 
					
						
							|  |  |  | 		conference_bridge_user->features.mute = 1; | 
					
						
							|  |  |  | 		/* If we have not been quieted play back that they are waiting for the leader */ | 
					
						
							|  |  |  | 		if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET)) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			if (play_prompt_to_channel(conference_bridge, conference_bridge_user->chan, "conf-waitforleader")) { | 
					
						
							|  |  |  | 				/* user hung up while the sound was playing */ | 
					
						
							|  |  |  | 				return -1; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		/* Start music on hold if needed */ | 
					
						
							|  |  |  | 		/* We need to recheck the markedusers value here. play_prompt_to_channel unlocks the conference bridge, potentially
 | 
					
						
							|  |  |  | 		 * allowing a marked user to enter while the prompt was playing | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		if (!conference_bridge->markedusers && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { | 
					
						
							|  |  |  | 			ast_moh_start(conference_bridge_user->chan, conference_bridge_user->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Perform post-joining non-marked specific actions | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param conference_bridge Conference bridge being joined | 
					
						
							|  |  |  |  * \param conference_bridge_user Conference bridge user joining | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  |  * \return Returns 0 on success, -1 if the user hung up | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | static int post_join_unmarked(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	/* Play back audio prompt and start MOH if need be if we are the first participant */ | 
					
						
							|  |  |  | 	if (conference_bridge->users == 1) { | 
					
						
							|  |  |  | 		/* If audio prompts have not been quieted or this prompt quieted play it on out */ | 
					
						
							|  |  |  | 		if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET | OPTION_NOONLYPERSON)) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 			if (play_prompt_to_channel(conference_bridge, conference_bridge_user->chan, "conf-onlyperson")) { | 
					
						
							|  |  |  | 				/* user hung up while the sound was playing */ | 
					
						
							|  |  |  | 				return -1; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		/* If we need to start music on hold on the channel do so now */ | 
					
						
							|  |  |  | 		/* We need to re-check the number of users in the conference bridge here because another conference bridge
 | 
					
						
							|  |  |  | 		 * participant could have joined while the above prompt was playing for the first user. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { | 
					
						
							|  |  |  | 			ast_moh_start(conference_bridge_user->chan, conference_bridge_user->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 		return 0; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Announce number of users if need be */ | 
					
						
							|  |  |  | 	if (ast_test_flag(&conference_bridge_user->flags, OPTION_ANNOUNCEUSERCOUNT)) { | 
					
						
							|  |  |  | 		ao2_unlock(conference_bridge); | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 		if (announce_user_count(conference_bridge, conference_bridge_user)) { | 
					
						
							|  |  |  | 			ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If we are the second participant we may need to stop music on hold on the first */ | 
					
						
							|  |  |  | 	if (conference_bridge->users == 2) { | 
					
						
							|  |  |  | 		struct conference_bridge_user *first_participant = AST_LIST_FIRST(&conference_bridge->users_list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Temporarily suspend the above participant from the bridge so we have control to stop MOH if needed */ | 
					
						
							|  |  |  | 		if (ast_test_flag(&first_participant->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) { | 
					
						
							|  |  |  | 			ast_moh_stop(first_participant->chan); | 
					
						
							|  |  |  | 			ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Destroy a conference bridge | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param obj The conference bridge object | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \return Returns nothing | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void destroy_conference_bridge(void *obj) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct conference_bridge *conference_bridge = obj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_debug(1, "Destroying conference bridge '%s'\n", conference_bridge->name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_mutex_destroy(&conference_bridge->playback_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (conference_bridge->playback_chan) { | 
					
						
							|  |  |  | 		struct ast_channel *underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL); | 
					
						
							|  |  |  | 		ast_hangup(underlying_channel); | 
					
						
							|  |  |  | 		ast_hangup(conference_bridge->playback_chan); | 
					
						
							|  |  |  | 		conference_bridge->playback_chan = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Destroying a conference bridge is simple, all we have to do is destroy the bridging object */ | 
					
						
							|  |  |  | 	if (conference_bridge->bridge) { | 
					
						
							|  |  |  | 		ast_bridge_destroy(conference_bridge->bridge); | 
					
						
							|  |  |  | 		conference_bridge->bridge = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Join a conference bridge | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param name The conference name | 
					
						
							|  |  |  |  * \param conference_bridge_user Conference bridge user structure | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \return A pointer to the conference bridge struct, or NULL if the conference room wasn't found. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static struct conference_bridge *join_conference_bridge(const char *name, struct conference_bridge_user *conference_bridge_user) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct conference_bridge *conference_bridge = NULL; | 
					
						
							|  |  |  | 	struct conference_bridge tmp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_copy_string(tmp.name, name, sizeof(tmp.name)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* We explictly lock the conference bridges container ourselves so that other callers can not create duplicate conferences at the same */ | 
					
						
							|  |  |  | 	ao2_lock(conference_bridges); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_debug(1, "Trying to find conference bridge '%s'\n", name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Attempt to find an existing conference bridge */ | 
					
						
							|  |  |  | 	conference_bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* When finding a conference bridge that already exists make sure that it is not locked, and if so that we are not an admin */ | 
					
						
							|  |  |  | 	if (conference_bridge && conference_bridge->locked && !ast_test_flag(&conference_bridge_user->flags, OPTION_ADMIN)) { | 
					
						
							|  |  |  | 		ao2_unlock(conference_bridges); | 
					
						
							|  |  |  | 		ao2_ref(conference_bridge, -1); | 
					
						
							|  |  |  | 		ast_debug(1, "Conference bridge '%s' is locked and caller is not an admin\n", name); | 
					
						
							|  |  |  | 		ast_stream_and_wait(conference_bridge_user->chan, "conf-locked", ""); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If no conference bridge was found see if we can create one */ | 
					
						
							|  |  |  | 	if (!conference_bridge) { | 
					
						
							|  |  |  | 		/* Try to allocate memory for a new conference bridge, if we fail... this won't end well. */ | 
					
						
							|  |  |  | 		if (!(conference_bridge = ao2_alloc(sizeof(*conference_bridge), destroy_conference_bridge))) { | 
					
						
							|  |  |  | 			ao2_unlock(conference_bridges); | 
					
						
							|  |  |  | 			ast_log(LOG_ERROR, "Conference bridge '%s' does not exist.\n", name); | 
					
						
							|  |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Setup conference bridge parameters */ | 
					
						
							|  |  |  | 		ast_copy_string(conference_bridge->name, name, sizeof(conference_bridge->name)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Create an actual bridge that will do the audio mixing */ | 
					
						
							|  |  |  | 		if (!(conference_bridge->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_SMART))) { | 
					
						
							|  |  |  | 			ao2_ref(conference_bridge, -1); | 
					
						
							|  |  |  | 			conference_bridge = NULL; | 
					
						
							|  |  |  | 			ao2_unlock(conference_bridges); | 
					
						
							|  |  |  | 			ast_log(LOG_ERROR, "Conference bridge '%s' could not be created.\n", name); | 
					
						
							|  |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Setup lock for playback channel */ | 
					
						
							|  |  |  | 		ast_mutex_init(&conference_bridge->playback_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Link it into the conference bridges container */ | 
					
						
							|  |  |  | 		ao2_link(conference_bridges, conference_bridge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ast_debug(1, "Created conference bridge '%s' and linked to container '%p'\n", name, conference_bridges); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_unlock(conference_bridges); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Setup conference bridge user parameters */ | 
					
						
							|  |  |  | 	conference_bridge_user->conference_bridge = conference_bridge; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* All good to go, add them in */ | 
					
						
							|  |  |  | 	AST_LIST_INSERT_TAIL(&conference_bridge->users_list, conference_bridge_user, list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Increment the users count on the bridge, but record it as it is going to need to be known right after this */ | 
					
						
							|  |  |  | 	conference_bridge->users++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If the caller is a marked user bump up the count */ | 
					
						
							|  |  |  | 	if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER)) { | 
					
						
							|  |  |  | 		conference_bridge->markedusers++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-03-29 14:07:44 +00:00
										 |  |  | 	/* Set the device state for this conference */ | 
					
						
							|  |  |  | 	if (conference_bridge->users == 1) { | 
					
						
							|  |  |  | 		ast_devstate_changed(AST_DEVICE_INUSE, "confbridge:%s", conference_bridge->name); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	/* 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 */ | 
					
						
							|  |  |  | 	if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER | OPTION_WAITMARKED)) { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 		if (post_join_marked(conference_bridge, conference_bridge_user)) { | 
					
						
							|  |  |  | 			ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 			leave_conference_bridge(conference_bridge, conference_bridge_user); | 
					
						
							|  |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2011-06-21 16:09:14 +00:00
										 |  |  | 		if (post_join_unmarked(conference_bridge, conference_bridge_user)) { | 
					
						
							|  |  |  | 			ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 			leave_conference_bridge(conference_bridge, conference_bridge_user); | 
					
						
							|  |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return conference_bridge; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Leave a conference bridge | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param conference_bridge The conference bridge to leave | 
					
						
							|  |  |  |  * \param conference_bridge_user The conference bridge user structure | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void  leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If this caller is a marked user bump down the count */ | 
					
						
							|  |  |  | 	if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER)) { | 
					
						
							|  |  |  | 		conference_bridge->markedusers--; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Decrement the users count while keeping the previous participant count */ | 
					
						
							|  |  |  | 	conference_bridge->users--; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Drop conference bridge user from the list, they be going bye bye */ | 
					
						
							|  |  |  | 	AST_LIST_REMOVE(&conference_bridge->users_list, conference_bridge_user, list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If there are still users in the conference bridge we may need to do things (such as start MOH on them) */ | 
					
						
							|  |  |  | 	if (conference_bridge->users) { | 
					
						
							|  |  |  | 		if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER) && !conference_bridge->markedusers) { | 
					
						
							|  |  |  | 			struct conference_bridge_user *other_participant = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Start out with muting everyone */ | 
					
						
							|  |  |  | 			AST_LIST_TRAVERSE(&conference_bridge->users_list, other_participant, list) { | 
					
						
							|  |  |  | 				other_participant->features.mute = 1; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Play back the audio prompt saying the leader has left the conference */ | 
					
						
							| 
									
										
										
										
											2009-03-11 13:44:42 +00:00
										 |  |  | 			if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET)) { | 
					
						
							|  |  |  | 				ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 				ast_autoservice_start(conference_bridge_user->chan); | 
					
						
							|  |  |  | 				play_sound_file(conference_bridge, "conf-leaderhasleft"); | 
					
						
							|  |  |  | 				ast_autoservice_stop(conference_bridge_user->chan); | 
					
						
							|  |  |  | 				ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			/* Now on to starting MOH if needed */ | 
					
						
							|  |  |  | 			AST_LIST_TRAVERSE(&conference_bridge->users_list, other_participant, list) { | 
					
						
							|  |  |  | 				if (ast_test_flag(&other_participant->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_participant->chan)) { | 
					
						
							|  |  |  | 					ast_moh_start(other_participant->chan, other_participant->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); | 
					
						
							|  |  |  | 					ast_bridge_unsuspend(conference_bridge->bridge, other_participant->chan); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else if (conference_bridge->users == 1) { | 
					
						
							|  |  |  | 			/* Of course if there is one other person in here we may need to start up MOH on them */ | 
					
						
							|  |  |  | 			struct conference_bridge_user *first_participant = AST_LIST_FIRST(&conference_bridge->users_list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (ast_test_flag(&first_participant->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) { | 
					
						
							|  |  |  | 				ast_moh_start(first_participant->chan, first_participant->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); | 
					
						
							|  |  |  | 				ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2010-03-29 14:07:44 +00:00
										 |  |  | 		/* Set device state to "not in use" */ | 
					
						
							|  |  |  | 		ast_devstate_changed(AST_DEVICE_NOT_INUSE, "confbridge:%s", conference_bridge->name); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 		ao2_unlink(conference_bridges, conference_bridge); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Done mucking with the conference bridge, huzzah */ | 
					
						
							|  |  |  | 	ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(conference_bridge, -1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Play sound file into conference bridge | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param conference_bridge The conference bridge to play sound file into | 
					
						
							|  |  |  |  * \param filename Sound file to play | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \retval 0 success | 
					
						
							|  |  |  |  * \retval -1 failure | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int play_sound_file(struct conference_bridge *conference_bridge, const char *filename) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_channel *underlying_channel; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_mutex_lock(&conference_bridge->playback_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!(conference_bridge->playback_chan)) { | 
					
						
							|  |  |  | 		int cause; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-06-26 15:28:53 +00:00
										 |  |  | 		if (!(conference_bridge->playback_chan = ast_request("Bridge", AST_FORMAT_SLINEAR, NULL, "", &cause))) { | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | 			ast_mutex_unlock(&conference_bridge->playback_lock); | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		conference_bridge->playback_chan->bridge = conference_bridge->bridge; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ast_call(conference_bridge->playback_chan, "", 0)) { | 
					
						
							|  |  |  | 			ast_hangup(conference_bridge->playback_chan); | 
					
						
							|  |  |  | 			conference_bridge->playback_chan = NULL; | 
					
						
							|  |  |  | 			ast_mutex_unlock(&conference_bridge->playback_lock); | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference_bridge->name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* Channel was already available so we just need to add it back into the bridge */ | 
					
						
							|  |  |  | 		underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL); | 
					
						
							|  |  |  | 		ast_bridge_impart(conference_bridge->bridge, underlying_channel, NULL, NULL); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* The channel is all under our control, in goes the prompt */ | 
					
						
							|  |  |  | 	ast_stream_and_wait(conference_bridge->playback_chan, filename, ""); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", underlying_channel->name, conference_bridge->bridge); | 
					
						
							|  |  |  | 	ast_bridge_depart(conference_bridge->bridge, underlying_channel); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_mutex_unlock(&conference_bridge->playback_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief DTMF Menu Callback | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param bridge Bridge this is involving | 
					
						
							|  |  |  |  * \param bridge_channel Bridged channel this is involving | 
					
						
							|  |  |  |  * \param hook_pvt User's conference bridge structure | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \retval 0 success | 
					
						
							|  |  |  |  * \retval -1 failure | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int menu_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct conference_bridge_user *conference_bridge_user = hook_pvt; | 
					
						
							|  |  |  | 	struct conference_bridge *conference_bridge = conference_bridge_user->conference_bridge; | 
					
						
							|  |  |  | 	int digit, res = 0, isadmin = ast_test_flag(&conference_bridge_user->flags, OPTION_ADMIN); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* See if music on hold is playing */ | 
					
						
							|  |  |  | 	ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 	if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { | 
					
						
							|  |  |  | 		/* Just us so MOH is probably indeed going, let's stop it */ | 
					
						
							|  |  |  | 		ast_moh_stop(bridge_channel->chan); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Try to play back the user menu, if it fails pass this back up so the bridging core will act on it */ | 
					
						
							|  |  |  | 	if (ast_streamfile(bridge_channel->chan, (isadmin ? "conf-adminmenu" : "conf-usermenu"), bridge_channel->chan->language)) { | 
					
						
							|  |  |  | 		res = -1; | 
					
						
							|  |  |  | 		goto finished; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Wait for them to enter a digit from the user menu options */ | 
					
						
							|  |  |  | 	digit = ast_waitstream(bridge_channel->chan, AST_DIGIT_ANY); | 
					
						
							|  |  |  | 	ast_stopstream(bridge_channel->chan); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (digit == '1') { | 
					
						
							|  |  |  | 		/* 1 - Mute or unmute yourself, note we only allow manipulation if they aren't waiting for a marked user or if marked users exist */ | 
					
						
							|  |  |  | 		if (!ast_test_flag(&conference_bridge_user->flags, OPTION_WAITMARKED) || conference_bridge->markedusers) { | 
					
						
							|  |  |  | 			conference_bridge_user->features.mute = (!conference_bridge_user->features.mute ? 1 : 0); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		res = ast_stream_and_wait(bridge_channel->chan, (conference_bridge_user->features.mute ? "conf-muted" : "conf-unmuted"), ""); | 
					
						
							|  |  |  | 	} else if (isadmin && digit == '2') { | 
					
						
							|  |  |  | 		/* 2 - Unlock or lock conference */ | 
					
						
							|  |  |  | 		conference_bridge->locked = (!conference_bridge->locked ? 1 : 0); | 
					
						
							|  |  |  | 		res = ast_stream_and_wait(bridge_channel->chan, (conference_bridge->locked ? "conf-lockednow" : "conf-unlockednow"), ""); | 
					
						
							|  |  |  | 	} else if (isadmin && digit == '3') { | 
					
						
							|  |  |  | 		/* 3 - Eject last user */ | 
					
						
							|  |  |  | 		struct conference_bridge_user *last_participant = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 		if (((last_participant = AST_LIST_LAST(&conference_bridge->users_list)) == conference_bridge_user) || (ast_test_flag(&last_participant->flags, OPTION_ADMIN))) { | 
					
						
							|  |  |  | 			ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 			res = ast_stream_and_wait(bridge_channel->chan, "conf-errormenu", ""); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			last_participant->kicked = 1; | 
					
						
							|  |  |  | 			ast_bridge_remove(conference_bridge->bridge, last_participant->chan); | 
					
						
							|  |  |  | 			ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else if (digit == '4') { | 
					
						
							|  |  |  | 		/* 4 - Decrease listening volume */ | 
					
						
							|  |  |  | 		ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, -1); | 
					
						
							|  |  |  | 	} else if (digit == '6') { | 
					
						
							|  |  |  | 		/* 6 - Increase listening volume */ | 
					
						
							|  |  |  | 		ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, 1); | 
					
						
							|  |  |  | 	} else if (digit == '7') { | 
					
						
							|  |  |  | 		/* 7 - Decrease talking volume */ | 
					
						
							|  |  |  | 		ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, -1); | 
					
						
							|  |  |  | 	} else if (digit == '8') { | 
					
						
							|  |  |  | 		/* 8 - Exit the IVR */ | 
					
						
							|  |  |  | 	} else if (digit == '9') { | 
					
						
							|  |  |  | 		/* 9 - Increase talking volume */ | 
					
						
							|  |  |  | 		ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, 1); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* No valid option was selected */ | 
					
						
							|  |  |  | 		res = ast_stream_and_wait(bridge_channel->chan, "conf-errormenu", ""); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  finished: | 
					
						
							|  |  |  | 	/* See if music on hold needs to be started back up again */ | 
					
						
							|  |  |  | 	ao2_lock(conference_bridge); | 
					
						
							|  |  |  | 	if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { | 
					
						
							|  |  |  | 		ast_moh_start(bridge_channel->chan, conference_bridge_user->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ao2_unlock(conference_bridge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bridge_channel->state = AST_BRIDGE_CHANNEL_STATE_WAIT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief The ConfBridge application */ | 
					
						
							| 
									
										
										
										
											2009-05-21 21:13:09 +00:00
										 |  |  | static int confbridge_exec(struct ast_channel *chan, const char *data) | 
					
						
							| 
									
										
										
										
											2009-03-05 18:18:27 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	int res = 0, volume_adjustments[2]; | 
					
						
							|  |  |  | 	char *parse; | 
					
						
							|  |  |  | 	struct conference_bridge *conference_bridge = NULL; | 
					
						
							|  |  |  | 	struct conference_bridge_user conference_bridge_user = { | 
					
						
							|  |  |  | 		.chan = chan, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	const char *tmp, *join_sound = NULL, *leave_sound = NULL; | 
					
						
							|  |  |  | 	AST_DECLARE_APP_ARGS(args, | 
					
						
							|  |  |  | 		AST_APP_ARG(conf_name); | 
					
						
							|  |  |  | 		AST_APP_ARG(options); | 
					
						
							|  |  |  | 	); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(data)) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* We need to make a copy of the input string if we are going to modify it! */ | 
					
						
							|  |  |  | 	parse = ast_strdupa(data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	AST_STANDARD_APP_ARGS(args, parse); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (args.argc == 2) { | 
					
						
							|  |  |  | 		ast_app_parse_options(app_opts, &conference_bridge_user.flags, conference_bridge_user.opt_args, args.options); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Look for a conference bridge matching the provided name */ | 
					
						
							|  |  |  | 	if (!(conference_bridge = join_conference_bridge(args.conf_name, &conference_bridge_user))) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Keep a copy of volume adjustments so we can restore them later if need be */ | 
					
						
							|  |  |  | 	volume_adjustments[0] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_READ); | 
					
						
							|  |  |  | 	volume_adjustments[1] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_WRITE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Always initialize the features structure, we are in most cases always going to need it. */ | 
					
						
							|  |  |  | 	ast_bridge_features_init(&conference_bridge_user.features); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If the menu option is enabled provide a user or admin menu as a custom feature hook */ | 
					
						
							|  |  |  | 	if (ast_test_flag(&conference_bridge_user.flags, OPTION_MENU)) { | 
					
						
							|  |  |  | 		ast_bridge_features_hook(&conference_bridge_user.features, "#", menu_callback, &conference_bridge_user); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If the caller should be joined already muted, make it so */ | 
					
						
							|  |  |  | 	if (ast_test_flag(&conference_bridge_user.flags, OPTION_STARTMUTED)) { | 
					
						
							|  |  |  | 		conference_bridge_user.features.mute = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Grab join/leave sounds from the channel */ | 
					
						
							|  |  |  | 	ast_channel_lock(chan); | 
					
						
							|  |  |  | 	if ((tmp = pbx_builtin_getvar_helper(chan, "CONFBRIDGE_JOIN_SOUND"))) { | 
					
						
							|  |  |  | 		join_sound = ast_strdupa(tmp); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if ((tmp = pbx_builtin_getvar_helper(chan, "CONFBRIDGE_LEAVE_SOUND"))) { | 
					
						
							|  |  |  | 		leave_sound = ast_strdupa(tmp); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ast_channel_unlock(chan); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If there is 1 or more people already in the conference then play our join sound unless overridden */ | 
					
						
							|  |  |  | 	if (!ast_test_flag(&conference_bridge_user.flags, OPTION_QUIET) && !ast_strlen_zero(join_sound) && conference_bridge->users >= 2) { | 
					
						
							|  |  |  | 		ast_autoservice_start(chan); | 
					
						
							|  |  |  | 		play_sound_file(conference_bridge, join_sound); | 
					
						
							|  |  |  | 		ast_autoservice_stop(chan); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Join our conference bridge for real */ | 
					
						
							|  |  |  | 	ast_bridge_join(conference_bridge->bridge, chan, NULL, &conference_bridge_user.features); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If there is 1 or more people (not including us) already in the conference then play our leave sound unless overridden */ | 
					
						
							|  |  |  | 	if (!ast_test_flag(&conference_bridge_user.flags, OPTION_QUIET) && !ast_strlen_zero(leave_sound) && conference_bridge->users >= 2) { | 
					
						
							|  |  |  | 		ast_autoservice_start(chan); | 
					
						
							|  |  |  | 		play_sound_file(conference_bridge, leave_sound); | 
					
						
							|  |  |  | 		ast_autoservice_stop(chan); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Easy as pie, depart this channel from the conference bridge */ | 
					
						
							|  |  |  | 	leave_conference_bridge(conference_bridge, &conference_bridge_user); | 
					
						
							|  |  |  | 	conference_bridge = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Can't forget to clean up the features structure, or else we risk a memory leak */ | 
					
						
							|  |  |  | 	ast_bridge_features_cleanup(&conference_bridge_user.features); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If the user was kicked from the conference play back the audio prompt for it */ | 
					
						
							|  |  |  | 	if (!ast_test_flag(&conference_bridge_user.flags, OPTION_QUIET) && conference_bridge_user.kicked) { | 
					
						
							|  |  |  | 		res = ast_stream_and_wait(chan, "conf-kicked", ""); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Restore volume adjustments to previous values in case they were changed */ | 
					
						
							|  |  |  | 	if (volume_adjustments[0]) { | 
					
						
							|  |  |  | 		ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_READ, volume_adjustments[0]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (volume_adjustments[1]) { | 
					
						
							|  |  |  | 		ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_WRITE, volume_adjustments[1]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief Called when module is being unloaded */ | 
					
						
							|  |  |  | static int unload_module(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int res = ast_unregister_application(app); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Get rid of the conference bridges container. Since we only allow dynamic ones none will be active. */ | 
					
						
							|  |  |  | 	ao2_ref(conference_bridges, -1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief Called when module is being loaded */ | 
					
						
							|  |  |  | static int load_module(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* Create a container to hold the conference bridges */ | 
					
						
							|  |  |  | 	if (!(conference_bridges = ao2_container_alloc(CONFERENCE_BRIDGE_BUCKETS, conference_bridge_hash_cb, conference_bridge_cmp_cb))) { | 
					
						
							|  |  |  | 		return AST_MODULE_LOAD_DECLINE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_register_application_xml(app, confbridge_exec)) { | 
					
						
							|  |  |  | 		ao2_ref(conference_bridges, -1); | 
					
						
							|  |  |  | 		return AST_MODULE_LOAD_DECLINE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return AST_MODULE_LOAD_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-07-20 19:35:02 +00:00
										 |  |  | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Conference Bridge Application", | 
					
						
							|  |  |  | 	.load = load_module, | 
					
						
							|  |  |  | 	.unload = unload_module, | 
					
						
							|  |  |  | 	.load_pri = AST_MODPRI_DEVSTATE_PROVIDER, | 
					
						
							|  |  |  | ); |