Skip to content
Snippets Groups Projects
app_confbridge.c 97 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			ast_log(LOG_ERROR, "Conference bridge '%s' could not be created.\n", name);
    			return NULL;
    		}
    
    
    		/* Set the internal sample rate on the bridge from the bridge profile */
    		ast_bridge_set_internal_sample_rate(conference_bridge->bridge, conference_bridge->b_profile.internal_sample_rate);
    		/* Set the internal mixing interval on the bridge from the bridge profile */
    		ast_bridge_set_mixing_interval(conference_bridge->bridge, conference_bridge->b_profile.mix_interval);
    
    
    		if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
    			ast_bridge_set_talker_src_video_mode(conference_bridge->bridge);
    		}
    
    
    		/* 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);
    
    
    
    		send_conf_start_event(conference_bridge->name);
    
    		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->u_profile, USER_OPT_MARKEDUSER)) {
    
    		conference_bridge->markedusers++;
    	}
    
    
    	/* Set the device state for this conference */
    	if (conference_bridge->users == 1) {
    		ast_devstate_changed(AST_DEVICE_INUSE, "confbridge:%s", conference_bridge->name);
    	}
    
    
    	/* If an announcement is to be played play it */
    	if (!ast_strlen_zero(conference_bridge_user->u_profile.announcement)) {
    		if (play_prompt_to_channel(conference_bridge,
    					   conference_bridge_user->chan,
    					   conference_bridge_user->u_profile.announcement)) {
    			ao2_unlock(conference_bridge);
    			leave_conference_bridge(conference_bridge, conference_bridge_user);
    			return NULL;
    		}
    	}
    
    
    	/* 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->u_profile, USER_OPT_MARKEDUSER | USER_OPT_WAITMARKED)) {
    
    		if (post_join_marked(conference_bridge, conference_bridge_user)) {
    			ao2_unlock(conference_bridge);
    			leave_conference_bridge(conference_bridge, conference_bridge_user);
    			return NULL;
    		}
    
    		if (post_join_unmarked(conference_bridge, conference_bridge_user)) {
    			ao2_unlock(conference_bridge);
    			leave_conference_bridge(conference_bridge, conference_bridge_user);
    			return NULL;
    		}
    
    	/* check to see if recording needs to be started or not */
    	if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_RECORD_CONFERENCE) && !conf_is_recording(conference_bridge)) {
    		start_record = 1;
    	}
    
    
    	ao2_unlock(conference_bridge);
    
    
    	if (start_record) {
    		conf_start_record(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->u_profile, USER_OPT_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->u_profile, USER_OPT_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 */
    
    			if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) {
    
    				ao2_unlock(conference_bridge);
    				ast_autoservice_start(conference_bridge_user->chan);
    
    				play_sound_file(conference_bridge,
    					conf_get_sound(CONF_SOUND_LEADER_HAS_LEFT, conference_bridge_user->b_profile.sounds));
    
    				ast_autoservice_stop(conference_bridge_user->chan);
    				ao2_lock(conference_bridge);
    			}
    
    			/* Now on to starting MOH or kick if needed */
    
    			AST_LIST_TRAVERSE(&conference_bridge->users_list, other_participant, list) {
    
    				if (ast_test_flag(&other_participant->u_profile, USER_OPT_ENDMARKED)) {
    					other_participant->kicked = 1;
    					ast_bridge_remove(conference_bridge->bridge, other_participant->chan);
    				} else if (ast_test_flag(&other_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_participant->chan)) {
    					ast_moh_start(other_participant->chan, other_participant->u_profile.moh_class, NULL);
    
    					other_participant->playing_moh = 1;
    
    					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->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) {
    				ast_moh_start(first_participant->chan, first_participant->u_profile.moh_class, NULL);
    
    				first_participant->playing_moh = 1;
    
    				ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan);
    			}
    		}
    	} else {
    
    		/* Set device state to "not in use" */
    		ast_devstate_changed(AST_DEVICE_NOT_INUSE, "confbridge:%s", conference_bridge->name);
    
    
    		ao2_unlink(conference_bridges, conference_bridge);
    
    		send_conf_end_event(conference_bridge->name);
    
    	}
    
    	/* Done mucking with the conference bridge, huzzah */
    	ao2_unlock(conference_bridge);
    
    
    	if (!conference_bridge->users) {
    		conf_stop_record(conference_bridge);
    	}
    
    
     * \internal
     * \brief allocates playback chan on a channel
     * \pre expects conference to be locked before calling this function
    
    static int alloc_playback_chan(struct conference_bridge *conference_bridge)
    
    	int cause;
    	struct ast_format_cap *cap;
    	struct ast_format tmpfmt;
    
    	if (conference_bridge->playback_chan) {
    		return 0;
    	}
    	if (!(cap = ast_format_cap_alloc_nolock())) {
    		return -1;
    	}
    	ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
    	if (!(conference_bridge->playback_chan = ast_request("Bridge", cap, NULL, "", &cause))) {
    
    		cap = ast_format_cap_destroy(cap);
    
    		return -1;
    	}
    	cap = ast_format_cap_destroy(cap);
    
    
    	ast_channel_internal_bridge_set(conference_bridge->playback_chan, conference_bridge->bridge);
    
    
    	if (ast_call(conference_bridge->playback_chan, "", 0)) {
    		ast_hangup(conference_bridge->playback_chan);
    		conference_bridge->playback_chan = NULL;
    		return -1;
    	}
    
    	ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference_bridge->name);
    	return 0;
    }
    
    static int play_sound_helper(struct conference_bridge *conference_bridge, const char *filename, int say_number)
    {
    	struct ast_channel *underlying_channel;
    
    	/* Do not waste resources trying to play files that do not exist */
    
    	if (!ast_strlen_zero(filename) && !ast_fileexists(filename, NULL, NULL)) {
    		ast_log(LOG_WARNING, "File %s does not exist in any format\n", !ast_strlen_zero(filename) ? filename : "<unknown>");
    
    	ast_mutex_lock(&conference_bridge->playback_lock);
    	if (!(conference_bridge->playback_chan)) {
    		if (alloc_playback_chan(conference_bridge)) {
    
    			ast_mutex_unlock(&conference_bridge->playback_lock);
    			return -1;
    		}
    
    		underlying_channel = ast_channel_tech(conference_bridge->playback_chan)->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 = ast_channel_tech(conference_bridge->playback_chan)->bridged_channel(conference_bridge->playback_chan, NULL);
    
    		ast_bridge_impart(conference_bridge->bridge, underlying_channel, NULL, NULL, 0);
    
    	}
    
    	/* The channel is all under our control, in goes the prompt */
    
    	if (!ast_strlen_zero(filename)) {
    		ast_stream_and_wait(conference_bridge->playback_chan, filename, "");
    
    		ast_say_number(conference_bridge->playback_chan, say_number, "", ast_channel_language(conference_bridge->playback_chan), NULL);
    
    	ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", ast_channel_name(underlying_channel), conference_bridge->bridge);
    
    	ast_bridge_depart(conference_bridge->bridge, underlying_channel);
    
    	ast_mutex_unlock(&conference_bridge->playback_lock);
    
    	return 0;
    }
    
    /*!
    
     * \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)
    
    	return play_sound_helper(conference_bridge, filename, -1);
    
    /*!
     * \brief Play number into the conference bridge
     *
     * \param conference_bridge The conference bridge to say the number into
     * \param number to say
     *
     * \retval 0 success
     * \retval -1 failure
     */
    static int play_sound_number(struct conference_bridge *conference_bridge, int say_number)
    {
    	return play_sound_helper(conference_bridge, NULL, say_number);
    }
    
    static void conf_handle_talker_destructor(void *pvt_data)
    {
    	ast_free(pvt_data);
    }
    
    static void conf_handle_talker_cb(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data)
    {
    	char *conf_name = pvt_data;
    	int talking;
    
    	switch (bridge_channel->state) {
    	case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
    		talking = 1;
    		break;
    	case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
    		talking = 0;
    		break;
    	default:
    		return; /* uhh this shouldn't happen, but bail if it does. */
    
    	/* notify AMI someone is has either started or stopped talking */
    
    	/*** DOCUMENTATION
    		<managerEventInstance>
    			<synopsis>Raised when a conference participant has started or stopped talking.</synopsis>
    			<syntax>
    				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
    				<parameter name="TalkingStatus">
    					<enumlist>
    						<enum name="on"/>
    						<enum name="off"/>
    					</enumlist>
    				</parameter>
    			</syntax>
    		</managerEventInstance>
    	***/
    
    	ast_manager_event(bridge_channel->chan, EVENT_FLAG_CALL, "ConfbridgeTalking",
    	      "Channel: %s\r\n"
    	      "Uniqueid: %s\r\n"
    	      "Conference: %s\r\n"
    	      "TalkingStatus: %s\r\n",
    
    	      ast_channel_name(bridge_channel->chan), ast_channel_uniqueid(bridge_channel->chan), conf_name, talking ? "on" : "off");
    
    static int conf_get_pin(struct ast_channel *chan, struct conference_bridge_user *conference_bridge_user)
    {
    	char pin_guess[MAX_PIN+1] = { 0, };
    	const char *pin = conference_bridge_user->u_profile.pin;
    	char *tmp = pin_guess;
    	int i, res;
    	unsigned int len = MAX_PIN ;
    
    	/* give them three tries to get the pin right */
    	for (i = 0; i < 3; i++) {
    		if (ast_app_getdata(chan,
    			conf_get_sound(CONF_SOUND_GET_PIN, conference_bridge_user->b_profile.sounds),
    			tmp, len, 0) >= 0) {
    			if (!strcasecmp(pin, pin_guess)) {
    				return 0;
    			}
    
    		ast_streamfile(chan,
    			conf_get_sound(CONF_SOUND_INVALID_PIN, conference_bridge_user->b_profile.sounds),
    
    			ast_channel_language(chan));
    
    		res = ast_waitstream(chan, AST_DIGIT_ANY);
    		if (res > 0) {
    			/* Account for digit already read during ivalid pin playback
    			 * resetting pin buf. */
    			pin_guess[0] = res;
    			pin_guess[1] = '\0';
    			tmp = pin_guess + 1;
    			len = MAX_PIN - 1;
    
    			/* reset pin buf as empty buffer. */
    			tmp = pin_guess;
    			len = MAX_PIN;
    
    static int conf_rec_name(struct conference_bridge_user *user, const char *conf_name)
    {
    	char destdir[PATH_MAX];
    	int res;
    	int duration = 20;
    
    	snprintf(destdir, sizeof(destdir), "%s/confbridge", ast_config_AST_SPOOL_DIR);
    
    	if (ast_mkdir(destdir, 0777) != 0) {
    		ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", destdir, strerror(errno));
    		return -1;
    	}
    	snprintf(user->name_rec_location, sizeof(user->name_rec_location),
    		 "%s/confbridge-name-%s-%s", destdir,
    
    		 conf_name, ast_channel_uniqueid(user->chan));
    
    
    	res = ast_play_and_record(user->chan,
    		"vm-rec-name",
    		user->name_rec_location,
    		10,
    		"sln",
    		&duration,
    
    		ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE),
    		0,
    		NULL);
    
    	if (res == -1) {
    		user->name_rec_location[0] = '\0';
    		return -1;
    	}
    	return 0;
    
    }
    
    /*! \brief The ConfBridge application */
    
    static int confbridge_exec(struct ast_channel *chan, const char *data)
    
    {
    	int res = 0, volume_adjustments[2];
    
    	const char *b_profile_name = DEFAULT_BRIDGE_PROFILE;
    	const char *u_profile_name = DEFAULT_USER_PROFILE;
    
    	struct conference_bridge *conference_bridge = NULL;
    	struct conference_bridge_user conference_bridge_user = {
    		.chan = chan,
    
    		.tech_args.talking_threshold = DEFAULT_TALKING_THRESHOLD,
    		.tech_args.silence_threshold = DEFAULT_SILENCE_THRESHOLD,
    		.tech_args.drop_silence = 0,
    
    	};
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(conf_name);
    
    		AST_APP_ARG(b_profile_name);
    		AST_APP_ARG(u_profile_name);
    		AST_APP_ARG(menu_name);
    
    	ast_bridge_features_init(&conference_bridge_user.features);
    
    
    	if (ast_channel_state(chan) != AST_STATE_UP) {
    
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app);
    
    		res = -1; /* invalid PIN */
    		goto confbridge_cleanup;
    
    	}
    
    	/* 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);
    
    
    	/* bridge profile name */
    	if (args.argc > 1 && !ast_strlen_zero(args.b_profile_name)) {
    		b_profile_name = args.b_profile_name;
    
    	if (!conf_find_bridge_profile(chan, b_profile_name, &conference_bridge_user.b_profile)) {
    		ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name);
    		res = -1;
    		goto confbridge_cleanup;
    	}
    
    	/* user profile name */
    	if (args.argc > 2 && !ast_strlen_zero(args.u_profile_name)) {
    		u_profile_name = args.u_profile_name;
    
    	if (!conf_find_user_profile(chan, u_profile_name, &conference_bridge_user.u_profile)) {
    		ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name);
    		res = -1;
    		goto confbridge_cleanup;
    	}
    
    	quiet = ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_QUIET);
    
    	/* ask for a PIN immediately after finding user profile.  This has to be
    	 * prompted for requardless of quiet setting. */
    	if (!ast_strlen_zero(conference_bridge_user.u_profile.pin)) {
    		if (conf_get_pin(chan, &conference_bridge_user)) {
    			res = -1; /* invalid PIN */
    			goto confbridge_cleanup;
    		}
    
    	/* See if we need them to record a intro name */
    	if (!quiet && ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE)) {
    		conf_rec_name(&conference_bridge_user, args.conf_name);
    
    	/* menu name */
    	if (args.argc > 3 && !ast_strlen_zero(args.menu_name)) {
    		ast_copy_string(conference_bridge_user.menu_name, args.menu_name, sizeof(conference_bridge_user.menu_name));
    		if (conf_set_menu_to_user(conference_bridge_user.menu_name, &conference_bridge_user)) {
    			ast_log(LOG_WARNING, "Conference menu %s does not exist and can not be applied to confbridge user.\n",
    				args.menu_name);
    			res = -1; /* invalid PIN */
    			goto confbridge_cleanup;
    		}
    
    
    	/* Set if DTMF should pass through for this user or not */
    	if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DTMF_PASS)) {
    		conference_bridge_user.features.dtmf_passthrough = 1;
    
    	/* Set dsp threshold values if present */
    	if (conference_bridge_user.u_profile.talking_threshold) {
    		conference_bridge_user.tech_args.talking_threshold = conference_bridge_user.u_profile.talking_threshold;
    	}
    	if (conference_bridge_user.u_profile.silence_threshold) {
    		conference_bridge_user.tech_args.silence_threshold = conference_bridge_user.u_profile.silence_threshold;
    
    	/* Set a talker indicate call back if talking detection is requested */
    	if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_TALKER_DETECT)) {
    		char *conf_name = ast_strdup(args.conf_name); /* this is freed during feature cleanup */
    		if (!(conf_name)) {
    			res = -1; /* invalid PIN */
    			goto confbridge_cleanup;
    		}
    		ast_bridge_features_set_talk_detector(&conference_bridge_user.features,
    			conf_handle_talker_cb,
    			conf_handle_talker_destructor,
    			conf_name);
    	}
    
    	/* Look for a conference bridge matching the provided name */
    	if (!(conference_bridge = join_conference_bridge(args.conf_name, &conference_bridge_user))) {
    		res = -1; /* invalid PIN */
    		goto confbridge_cleanup;
    	}
    
    	/* 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);
    
    	/* If the caller should be joined already muted, make it so */
    	if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_STARTMUTED)) {
    		conference_bridge_user.features.mute = 1;
    	}
    
    	if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DROP_SILENCE)) {
    		conference_bridge_user.tech_args.drop_silence = 1;
    	}
    
    	if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_JITTERBUFFER)) {
    		char *func_jb;
    		if ((func_jb = ast_module_helper("", "func_jitterbuffer", 0, 0, 0, 0))) {
    			ast_free(func_jb);
    			ast_func_write(chan, "JITTERBUFFER(adaptive)", "default");
    		}
    	}
    
    	if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DENOISE)) {
    		char *mod_speex;
    		/* Reduce background noise from each participant */
    		if ((mod_speex = ast_module_helper("", "codec_speex", 0, 0, 0, 0))) {
    			ast_free(mod_speex);
    			ast_func_write(chan, "DENOISE(rx)", "on");
    		}
    	}
    
    	/* if this user has a intro, play it before entering */
    	if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) {
    		ast_autoservice_start(chan);
    		play_sound_file(conference_bridge, conference_bridge_user.name_rec_location);
    		play_sound_file(conference_bridge,
    			conf_get_sound(CONF_SOUND_HAS_JOINED, conference_bridge_user.b_profile.sounds));
    		ast_autoservice_stop(chan);
    	}
    
    	/* Play the Join sound to both the conference and the user entering. */
    	if (!quiet) {
    		const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference_bridge_user.b_profile.sounds);
    
    		if (conference_bridge_user.playing_moh) {
    			ast_moh_stop(chan);
    		}
    
    		ast_stream_and_wait(chan, join_sound, "");
    		ast_autoservice_start(chan);
    		play_sound_file(conference_bridge, join_sound);
    		ast_autoservice_stop(chan);
    
    		if (conference_bridge_user.playing_moh) {
    			ast_moh_start(chan, conference_bridge_user.u_profile.moh_class, NULL);
    		}
    
    	/* See if we need to automatically set this user as a video source or not */
    	handle_video_on_join(conference_bridge, conference_bridge_user.chan, ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_MARKEDUSER));
    
    	/* Join our conference bridge for real */
    	send_join_event(conference_bridge_user.chan, conference_bridge->name);
    	ast_bridge_join(conference_bridge->bridge,
    		chan,
    		NULL,
    		&conference_bridge_user.features,
    		&conference_bridge_user.tech_args);
    	send_leave_event(conference_bridge_user.chan, conference_bridge->name);
    
    
    	/* if we're shutting down, don't attempt to do further processing */
    	if (ast_shutting_down()) {
    		leave_conference_bridge(conference_bridge, &conference_bridge_user);
    		conference_bridge = NULL;
    		goto confbridge_cleanup;
    	}
    
    
    	/* If this user was a video source, we need to clean up and possibly pick a new source. */
    	handle_video_on_exit(conference_bridge, conference_bridge_user.chan);
    
    	/* if this user has a intro, play it when leaving */
    	if (!quiet && !ast_strlen_zero(conference_bridge_user.name_rec_location)) {
    		ast_autoservice_start(chan);
    		play_sound_file(conference_bridge, conference_bridge_user.name_rec_location);
    		play_sound_file(conference_bridge,
    			conf_get_sound(CONF_SOUND_HAS_LEFT, conference_bridge_user.b_profile.sounds));
    		ast_autoservice_stop(chan);
    	}
    
    	/* play the leave sound */
    	if (!quiet) {
    		const char *leave_sound = conf_get_sound(CONF_SOUND_LEAVE, conference_bridge_user.b_profile.sounds);
    
    		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;
    
    	/* If the user was kicked from the conference play back the audio prompt for it */
    
    	if (!quiet && conference_bridge_user.kicked) {
    		res = ast_stream_and_wait(chan,
    			conf_get_sound(CONF_SOUND_KICKED, conference_bridge_user.b_profile.sounds),
    			"");
    
    	}
    
    	/* 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]);
    	}
    
    
    	if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) {
    		ast_filedelete(conference_bridge_user.name_rec_location, NULL);
    	}
    
    confbridge_cleanup:
    	ast_bridge_features_cleanup(&conference_bridge_user.features);
    	conf_bridge_profile_destroy(&conference_bridge_user.b_profile);
    	return res;
    }
    
    static int action_toggle_mute(struct conference_bridge *conference_bridge,
    	struct conference_bridge_user *conference_bridge_user,
    	struct ast_channel *chan)
    {
    	/* 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->u_profile, USER_OPT_WAITMARKED) || conference_bridge->markedusers) {
    		conference_bridge_user->features.mute = (!conference_bridge_user->features.mute ? 1 : 0);
    
    		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));
    
    	}
    	return ast_stream_and_wait(chan, (conference_bridge_user->features.mute ?
    		conf_get_sound(CONF_SOUND_MUTED, conference_bridge_user->b_profile.sounds) :
    		conf_get_sound(CONF_SOUND_UNMUTED, conference_bridge_user->b_profile.sounds)),
    		"");
    }
    
    
    static int action_toggle_mute_participants(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
    {
    	struct conference_bridge_user *participant = NULL;
    	const char *sound_to_play;
    
    	ao2_lock(conference_bridge);
    
    	/* If already muted, then unmute */
    	conference_bridge->muted = conference_bridge->muted ? 0 : 1;
    	sound_to_play = conf_get_sound((conference_bridge->muted ? CONF_SOUND_PARTICIPANTS_MUTED : CONF_SOUND_PARTICIPANTS_UNMUTED),
    		conference_bridge_user->b_profile.sounds);
    
    	AST_LIST_TRAVERSE(&conference_bridge->users_list, participant, list) {
    		if (!ast_test_flag(&participant->u_profile, USER_OPT_ADMIN)) {
    			participant->features.mute = conference_bridge->muted;
    		}
    	}
    
    	ao2_unlock(conference_bridge);
    
    	/* The host needs to hear it seperately, as they don't get the audio from play_sound_helper */
    	ast_stream_and_wait(conference_bridge_user->chan, sound_to_play, "");
    
    	/* Announce to the group that all participants are muted */
    	ast_autoservice_start(conference_bridge_user->chan);
    	play_sound_helper(conference_bridge, sound_to_play, 0);
    	ast_autoservice_stop(conference_bridge_user->chan);
    
    	return 0;
    }
    
    
    static int action_playback(struct ast_bridge_channel *bridge_channel, const char *playback_file)
    {
    	char *file_copy = ast_strdupa(playback_file);
    	char *file = NULL;
    
    	while ((file = strsep(&file_copy, "&"))) {
    		if (ast_stream_and_wait(bridge_channel->chan, file, "")) {
    			ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
    			return -1;
    		}
    	}
    	return 0;
    }
    
    static int action_playback_and_continue(struct conference_bridge *conference_bridge,
    	struct conference_bridge_user *conference_bridge_user,
    	struct ast_bridge_channel *bridge_channel,
    	struct conf_menu *menu,
    	const char *playback_file,
    	const char *cur_dtmf,
    	int *stop_prompts)
    {
    	int i;
    	int digit = 0;
    	char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
    	struct conf_menu_entry new_menu_entry = { { 0, }, };
    	char *file_copy = ast_strdupa(playback_file);
    	char *file = NULL;
    
    	while ((file = strsep(&file_copy, "&"))) {
    
    		if (ast_streamfile(bridge_channel->chan, file, ast_channel_language(bridge_channel->chan))) {
    
    			ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
    			return -1;
    		}
    
    		/* now wait for more digits. */
    		if (!(digit = ast_waitstream(bridge_channel->chan, AST_DIGIT_ANY))) {
    			/* streaming finished and no DTMF was entered */
    			continue;
    		} else if (digit == -1) {
    			/* error */
    			return -1;
    		} else {
    			break; /* dtmf was entered */
    		}
    	}
    	if (!digit) {
    		/* streaming finished on all files and no DTMF was entered */
    		return -1;
    	}
    	ast_stopstream(bridge_channel->chan);
    
    	/* If we get here, then DTMF has been entered, This means no
    	 * additional prompts should be played for this menu entry */
    	*stop_prompts = 1;
    
    	/* If a digit was pressed during the payback, update
    	 * the dtmf string and look for a new menu entry in the
    	 * menu structure */
    	ast_copy_string(dtmf, cur_dtmf, sizeof(dtmf));
    	for (i = 0; i < (MAXIMUM_DTMF_FEATURE_STRING - 1); i++) {
    		dtmf[i] = cur_dtmf[i];
    		if (!dtmf[i]) {
    			dtmf[i] = (char) digit;
    			dtmf[i + 1] = '\0';
    			i = -1;
    			break;
    		}
    	}
    	/* If i is not -1 then the new dtmf digit was _NOT_ added to the string.
    	 * If this is the case, no new DTMF sequence should be looked for. */
    	if (i != -1) {
    		return 0;
    	}
    
    	if (conf_find_menu_entry_by_sequence(dtmf, menu, &new_menu_entry)) {
    		execute_menu_entry(conference_bridge,
    			conference_bridge_user,
    			bridge_channel,
    			&new_menu_entry, menu);
    		conf_menu_entry_destroy(&new_menu_entry);
    	}
    	return 0;
    }
    
    static int action_kick_last(struct conference_bridge *conference_bridge,
    	struct ast_bridge_channel *bridge_channel,
    	struct conference_bridge_user *conference_bridge_user)
    {
    	struct conference_bridge_user *last_participant = NULL;
    	int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN);
    
    	if (!isadmin) {
    		ast_stream_and_wait(bridge_channel->chan,
    			conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds),
    			"");
    		ast_log(LOG_WARNING, "Only admin users can use the kick_last menu action. Channel %s of conf %s is not an admin.\n",
    
    			ast_channel_name(bridge_channel->chan),
    
    			conference_bridge->name);
    		return -1;
    	}
    
    	ao2_lock(conference_bridge);
    	if (((last_participant = AST_LIST_LAST(&conference_bridge->users_list)) == conference_bridge_user)
    		|| (ast_test_flag(&last_participant->u_profile, USER_OPT_ADMIN))) {
    		ao2_unlock(conference_bridge);
    		ast_stream_and_wait(bridge_channel->chan,
    			conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds),
    			"");
    	} else if (last_participant) {
    		last_participant->kicked = 1;
    		ast_bridge_remove(conference_bridge->bridge, last_participant->chan);
    		ao2_unlock(conference_bridge);
    	}
    	return 0;
    }
    
    static int action_dialplan_exec(struct ast_bridge_channel *bridge_channel, struct conf_menu_action *menu_action)
    {
    	struct ast_pbx_args args;
    	struct ast_pbx *pbx;
    	char *exten;
    	char *context;
    	int priority;
    	int res;
    
    	memset(&args, 0, sizeof(args));
    	args.no_hangup_chan = 1;
    
    	ast_channel_lock(bridge_channel->chan);
    
    	/*save off*/
    
    	exten = ast_strdupa(ast_channel_exten(bridge_channel->chan));
    	context = ast_strdupa(ast_channel_context(bridge_channel->chan));
    
    	priority = ast_channel_priority(bridge_channel->chan);
    	pbx = ast_channel_pbx(bridge_channel->chan);
    	ast_channel_pbx_set(bridge_channel->chan, NULL);
    
    	ast_channel_exten_set(bridge_channel->chan, menu_action->data.dialplan_args.exten);
    	ast_channel_context_set(bridge_channel->chan, menu_action->data.dialplan_args.context);
    
    	ast_channel_priority_set(bridge_channel->chan, menu_action->data.dialplan_args.priority);
    
    
    	ast_channel_unlock(bridge_channel->chan);
    
    	/*execute*/
    	res = ast_pbx_run_args(bridge_channel->chan, &args);
    
    	/*restore*/
    	ast_channel_lock(bridge_channel->chan);
    
    
    	ast_channel_exten_set(bridge_channel->chan, exten);
    	ast_channel_context_set(bridge_channel->chan, context);
    
    	ast_channel_priority_set(bridge_channel->chan, priority);
    	ast_channel_pbx_set(bridge_channel->chan, pbx);
    
    
    	ast_channel_unlock(bridge_channel->chan);
    
    	return res;
    }
    
    static int execute_menu_entry(struct conference_bridge *conference_bridge,
    	struct conference_bridge_user *conference_bridge_user,
    	struct ast_bridge_channel *bridge_channel,
    	struct conf_menu_entry *menu_entry,
    	struct conf_menu *menu)
    {
    	struct conf_menu_action *menu_action;
    	int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN);
    	int stop_prompts = 0;
    	int res = 0;
    
    	AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) {
    		switch (menu_action->id) {
    		case MENU_ACTION_TOGGLE_MUTE:
    			res |= action_toggle_mute(conference_bridge,
    				conference_bridge_user,
    				bridge_channel->chan);
    			break;
    
    		case MENU_ACTION_ADMIN_TOGGLE_MUTE_PARTICIPANTS:
    			if (!isadmin) {
    				break;
    			}
    			action_toggle_mute_participants(conference_bridge, conference_bridge_user);
    			break;
    		case MENU_ACTION_PARTICIPANT_COUNT:
    			announce_user_count(conference_bridge, conference_bridge_user);
    			break;
    
    		case MENU_ACTION_PLAYBACK:
    			if (!stop_prompts) {
    				res |= action_playback(bridge_channel, menu_action->data.playback_file);
    			}
    			break;
    		case MENU_ACTION_RESET_LISTENING:
    			ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, 0);
    			break;
    		case MENU_ACTION_RESET_TALKING:
    			ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, 0);
    			break;
    		case MENU_ACTION_INCREASE_LISTENING:
    			ast_audiohook_volume_adjust(conference_bridge_user->chan,
    				AST_AUDIOHOOK_DIRECTION_WRITE, 1);
    			break;
    		case MENU_ACTION_DECREASE_LISTENING:
    			ast_audiohook_volume_adjust(conference_bridge_user->chan,
    				AST_AUDIOHOOK_DIRECTION_WRITE, -1);
    			break;
    		case MENU_ACTION_INCREASE_TALKING:
    			ast_audiohook_volume_adjust(conference_bridge_user->chan,
    				AST_AUDIOHOOK_DIRECTION_READ, 1);
    			break;
    		case MENU_ACTION_DECREASE_TALKING:
    			ast_audiohook_volume_adjust(conference_bridge_user->chan,
    				AST_AUDIOHOOK_DIRECTION_READ, -1);
    			break;
    		case MENU_ACTION_PLAYBACK_AND_CONTINUE:
    			if (!(stop_prompts)) {
    				res |= action_playback_and_continue(conference_bridge,
    					conference_bridge_user,
    					bridge_channel,
    					menu,
    					menu_action->data.playback_file,
    					menu_entry->dtmf,
    					&stop_prompts);
    			}
    			break;
    		case MENU_ACTION_DIALPLAN_EXEC:
    			res |= action_dialplan_exec(bridge_channel, menu_action);
    			break;
    		case MENU_ACTION_ADMIN_TOGGLE_LOCK:
    			if (!isadmin) {
    				break;
    			}
    			conference_bridge->locked = (!conference_bridge->locked ? 1 : 0);
    			res |= ast_stream_and_wait(bridge_channel->chan,
    				(conference_bridge->locked ?
    				conf_get_sound(CONF_SOUND_LOCKED_NOW, conference_bridge_user->b_profile.sounds) :
    				conf_get_sound(CONF_SOUND_UNLOCKED_NOW, conference_bridge_user->b_profile.sounds)),
    				"");
    
    			break;
    		case MENU_ACTION_ADMIN_KICK_LAST:
    			res |= action_kick_last(conference_bridge, bridge_channel, conference_bridge_user);
    			break;
    		case MENU_ACTION_LEAVE:
    			ao2_lock(conference_bridge);
    			ast_bridge_remove(conference_bridge->bridge, bridge_channel->chan);
    			ao2_unlock(conference_bridge);
    			break;
    		case MENU_ACTION_NOOP:
    			break;
    
    		case MENU_ACTION_SET_SINGLE_VIDEO_SRC:
    			ao2_lock(conference_bridge);
    			ast_bridge_set_single_src_video_mode(conference_bridge->bridge, bridge_channel->chan);
    			ao2_unlock(conference_bridge);
    			break;
    
    		case MENU_ACTION_RELEASE_SINGLE_VIDEO_SRC:
    			handle_video_on_exit(conference_bridge, bridge_channel->chan);
    			break;
    
    int conf_handle_dtmf(struct ast_bridge_channel *bridge_channel,
    	struct conference_bridge_user *conference_bridge_user,
    	struct conf_menu_entry *menu_entry,
    	struct conf_menu *menu)
    {
    	struct conference_bridge *conference_bridge = conference_bridge_user->conference_bridge;
    
    	/* See if music on hold is playing */
    	ao2_lock(conference_bridge);
    
    	if (conference_bridge_user->playing_moh) {
    		/* MOH is going, let's stop it */
    
    		ast_moh_stop(bridge_channel->chan);
    	}
    	ao2_unlock(conference_bridge);
    
    	/* execute the list of actions associated with this menu entry */
    	execute_menu_entry(conference_bridge, conference_bridge_user, bridge_channel, menu_entry, menu);
    
    	/* See if music on hold needs to be started back up again */
    	ao2_lock(conference_bridge);
    
    	if (conference_bridge_user->playing_moh) {
    
    		ast_moh_start(bridge_channel->chan, conference_bridge_user->u_profile.moh_class, NULL);
    	}
    	ao2_unlock(conference_bridge);
    
    	return 0;
    }
    
    
    static char *complete_confbridge_name(const char *line, const char *word, int pos, int state)
    {
    	int which = 0;
    	struct conference_bridge *bridge = NULL;
    	char *res = NULL;
    	int wordlen = strlen(word);
    	struct ao2_iterator i;
    
    	i = ao2_iterator_init(conference_bridges, 0);
    	while ((bridge = ao2_iterator_next(&i))) {
    		if (!strncasecmp(bridge->name, word, wordlen) && ++which > state) {
    			res = ast_strdup(bridge->name);
    			ao2_ref(bridge, -1);
    			break;
    		}
    		ao2_ref(bridge, -1);
    	}
    	ao2_iterator_destroy(&i);
    
    	return res;
    }
    
    static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	struct conference_bridge *bridge = NULL;
    	struct conference_bridge tmp;
    	struct conference_bridge_user *participant = NULL;
    
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge kick";
    		e->usage =
    			"Usage: confbridge kick <conference> <channel>\n"
    			"       Kicks a channel out of the conference bridge.\n";
    		return NULL;
    	case CLI_GENERATE:
    
    		if (a->pos == 2) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);