Skip to content
Snippets Groups Projects
app_confbridge.c 132 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * \return Returns 0 on success, -1 if the user hung up
    
     * \note Generally this should be called when the conference is unlocked to avoid blocking
     * the entire conference while the sound is played. But don't unlock the conference bridge
     * in the middle of a state transition.
    
    static int play_prompt_to_user(struct confbridge_user *user, const char *filename)
    
    	return ast_stream_and_wait(user->chan, filename, "");
    
    static void handle_video_on_join(struct confbridge_conference *conference, struct ast_channel *chan, int marked)
    
    	/* Right now, only marked users are automatically set as the single src of video.*/
    	if (!marked) {
    
    	if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED)) {
    
    		int set = 1;
    
    		struct confbridge_user *user = NULL;
    
    		ao2_lock(conference);
    
    		/* see if anyone is already the video src */
    
    		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    			if (user->chan == chan) {
    
    				continue;
    			}
    
    			if (ast_bridge_is_video_src(conference->bridge, user->chan)) {
    
    		if (set) {
    
    			ast_bridge_set_single_src_video_mode(conference->bridge, chan);
    
    	} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED)) {
    
    		/* we joined and are video capable, we override anyone else that may have already been the video feed */
    
    		ast_bridge_set_single_src_video_mode(conference->bridge, chan);
    
    static void handle_video_on_exit(struct confbridge_conference *conference, struct ast_channel *chan)
    
    	struct confbridge_user *user = NULL;
    
    
    	/* if this isn't a video source, nothing to update */
    
    	if (!ast_bridge_is_video_src(conference->bridge, chan)) {
    
    	ast_bridge_remove_video_src(conference->bridge, chan);
    
    
    	/* If in follow talker mode, make sure to restore this mode on the
    	 * bridge when a source is removed.  It is possible this channel was
    	 * only set temporarily as a video source by an AMI or DTMF action. */
    
    	if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
    		ast_bridge_set_talker_src_video_mode(conference->bridge);
    
    
    	/* if the video_mode isn't set to automatically pick the video source, do nothing on exit. */
    
    	if (!ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED) &&
    		!ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED)) {
    
    	/* Make the next available marked user the video src.  */
    
    	ao2_lock(conference);
    	AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    		if (user->chan == chan) {
    
    			continue;
    		}
    
    		if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
    			ast_bridge_set_single_src_video_mode(conference->bridge, user->chan);
    
    struct hangup_data
    {
    	struct confbridge_conference *conference;
    	ast_mutex_t lock;
    	ast_cond_t cond;
    	int hungup;
    };
    
    /*!
     * \brief Hang up the announcer channel
     *
     * This hangs up the announcer channel in the conference. This
     * runs in the playback queue taskprocessor since we do not want
     * to hang up the channel while it's trying to play an announcement.
     *
     * This task is performed synchronously, so there is no need to
     * perform any cleanup on the passed-in data.
     *
     * \param data A hangup_data structure
     * \return 0
     */
    static int hangup_playback(void *data)
    {
    	struct hangup_data *hangup = data;
    
    	ast_autoservice_stop(hangup->conference->playback_chan);
    
    	ast_hangup(hangup->conference->playback_chan);
    	hangup->conference->playback_chan = NULL;
    
    	ast_mutex_lock(&hangup->lock);
    	hangup->hungup = 1;
    	ast_cond_signal(&hangup->cond);
    	ast_mutex_unlock(&hangup->lock);
    
    	return 0;
    }
    
    static void hangup_data_init(struct hangup_data *hangup, struct confbridge_conference *conference)
    {
    	ast_mutex_init(&hangup->lock);
    	ast_cond_init(&hangup->cond, NULL);
    
    	hangup->conference = conference;
    	hangup->hungup = 0;
    }
    
    static void hangup_data_destroy(struct hangup_data *hangup)
    {
    	ast_mutex_destroy(&hangup->lock);
    	ast_cond_destroy(&hangup->cond);
    }
    
    
     * \param obj The conference bridge object
    
    static void destroy_conference_bridge(void *obj)
    
    	struct confbridge_conference *conference = obj;
    
    	ast_debug(1, "Destroying conference bridge '%s'\n", conference->name);
    
    	if (conference->playback_chan) {
    
    		if (conference->playback_queue) {
    			struct hangup_data hangup;
    			hangup_data_init(&hangup, conference);
    
    
    			if (!ast_taskprocessor_push(conference->playback_queue, hangup_playback, &hangup)) {
    				ast_mutex_lock(&hangup.lock);
    				while (!hangup.hungup) {
    					ast_cond_wait(&hangup.cond, &hangup.lock);
    				}
    				ast_mutex_unlock(&hangup.lock);
    
    			hangup_data_destroy(&hangup);
    		} else {
    			/* Playback queue is not yet allocated. Just hang up the channel straight */
    			ast_hangup(conference->playback_chan);
    			conference->playback_chan = NULL;
    		}
    
    	/* Destroying a conference bridge is simple, all we have to do is destroy the bridging object */
    
    	if (conference->bridge) {
    
    		ast_bridge_destroy(conference->bridge, 0);
    
    		conference->bridge = NULL;
    
    	ast_channel_cleanup(conference->record_chan);
    	ast_free(conference->orig_rec_file);
    	ast_free(conference->record_filename);
    
    
    	conf_bridge_profile_destroy(&conference->b_profile);
    
    	ast_taskprocessor_unreference(conference->playback_queue);
    
    }
    
    /*! \brief Call the proper join event handler for the user for the conference bridge's current state
     * \internal
    
     * \param user The conference bridge user that is joining
    
    static int handle_conf_user_join(struct confbridge_user *user)
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
    		handler = user->conference->state->join_marked;
    	} else if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
    		handler = user->conference->state->join_waitmarked;
    
    		handler = user->conference->state->join_unmarked;
    
    		conf_invalid_event_fn(user);
    
    /*! \brief Call the proper leave event handler for the user for the conference bridge's current state
     * \internal
    
     * \param user The conference bridge user that is leaving
    
    static int handle_conf_user_leave(struct confbridge_user *user)
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
    		handler = user->conference->state->leave_marked;
    	} else if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
    		handler = user->conference->state->leave_waitmarked;
    
    		handler = user->conference->state->leave_unmarked;
    
    	ast_assert(handler != NULL);
    
    	if (!handler) {
    		/* This should never happen. If it does, though, it is bad. The user will not have been removed
    		 * from the appropriate list, so counts will be off and stuff. The conference won't be torn down, etc.
    		 * Shouldn't happen, though. */
    
    		conf_invalid_event_fn(user);
    
    void conf_update_user_mute(struct confbridge_user *user)
    {
    	int mute_user;
    	int mute_system;
    	int mute_effective;
    
    	/* User level mute request. */
    	mute_user = user->muted;
    
    	/* System level mute request. */
    	mute_system = user->playing_moh
    		/*
    		 * Do not allow waitmarked users to talk to anyone unless there
    		 * is a marked user present.
    		 */
    		|| (!user->conference->markedusers
    			&& ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED));
    
    	mute_effective = mute_user || mute_system;
    
    	ast_debug(1, "User %s is %s: user:%d system:%d.\n",
    		ast_channel_name(user->chan), mute_effective ? "muted" : "unmuted",
    		mute_user, mute_system);
    	user->features.mute = mute_effective;
    
    	ast_test_suite_event_notify("CONF_MUTE_UPDATE",
    		"Mode: %s\r\n"
    		"Conference: %s\r\n"
    		"Channel: %s",
    		mute_effective ? "muted" : "unmuted",
    
    		user->conference->b_profile.name,
    
    /*
     * \internal
     * \brief Mute/unmute a single user.
     */
    static void generic_mute_unmute_user(struct confbridge_conference *conference, struct confbridge_user *user, int mute)
    {
    	/* Set user level mute request. */
    	user->muted = mute ? 1 : 0;
    
    	conf_update_user_mute(user);
    	ast_test_suite_event_notify("CONF_MUTE",
    		"Message: participant %s %s\r\n"
    		"Conference: %s\r\n"
    		"Channel: %s",
    		ast_channel_name(user->chan),
    		mute ? "muted" : "unmuted",
    		conference->b_profile.name,
    		ast_channel_name(user->chan));
    	if (mute) {
    		send_mute_event(user, conference);
    	} else {
    		send_unmute_event(user, conference);
    	}
    }
    
    
    void conf_moh_stop(struct confbridge_user *user)
    
    {
    	user->playing_moh = 0;
    	if (!user->suspended_moh) {
    		int in_bridge;
    
    		/*
    		 * Locking the ast_bridge here is the only way to hold off the
    		 * call to ast_bridge_join() in confbridge_exec() from
    		 * interfering with the bridge and MOH operations here.
    		 */
    
    		ast_bridge_lock(user->conference->bridge);
    
    
    		/*
    		 * Temporarily suspend the user from the bridge so we have
    		 * control to stop MOH if needed.
    		 */
    
    		in_bridge = !ast_bridge_suspend(user->conference->bridge, user->chan);
    
    		ast_moh_stop(user->chan);
    		if (in_bridge) {
    
    			ast_bridge_unsuspend(user->conference->bridge, user->chan);
    
    		ast_bridge_unlock(user->conference->bridge);
    
    void conf_moh_start(struct confbridge_user *user)
    
    {
    	user->playing_moh = 1;
    	if (!user->suspended_moh) {
    		int in_bridge;
    
    		/*
    		 * Locking the ast_bridge here is the only way to hold off the
    		 * call to ast_bridge_join() in confbridge_exec() from
    		 * interfering with the bridge and MOH operations here.
    		 */
    
    		ast_bridge_lock(user->conference->bridge);
    
    
    		/*
    		 * Temporarily suspend the user from the bridge so we have
    		 * control to start MOH if needed.
    		 */
    
    		in_bridge = !ast_bridge_suspend(user->conference->bridge, user->chan);
    
    		ast_moh_start(user->chan, user->u_profile.moh_class, NULL);
    		if (in_bridge) {
    
    			ast_bridge_unsuspend(user->conference->bridge, user->chan);
    
    		ast_bridge_unlock(user->conference->bridge);
    
    	}
    }
    
    /*!
     * \internal
     * \brief Unsuspend MOH for the conference user.
     *
     * \param user Conference user to unsuspend MOH on.
     *
     * \return Nothing
     */
    
    static void conf_moh_unsuspend(struct confbridge_user *user)
    
    	ao2_lock(user->conference);
    
    	if (--user->suspended_moh == 0 && user->playing_moh) {
    		ast_moh_start(user->chan, user->u_profile.moh_class, NULL);
    	}
    
    	ao2_unlock(user->conference);
    
    }
    
    /*!
     * \internal
     * \brief Suspend MOH for the conference user.
     *
     * \param user Conference user to suspend MOH on.
     *
     * \return Nothing
     */
    
    static void conf_moh_suspend(struct confbridge_user *user)
    
    	ao2_lock(user->conference);
    
    	if (user->suspended_moh++ == 0 && user->playing_moh) {
    		ast_moh_stop(user->chan);
    	}
    
    	ao2_unlock(user->conference);
    
    int conf_handle_inactive_waitmarked(struct confbridge_user *user)
    
    {
    	/* If we have not been quieted play back that they are waiting for the leader */
    
    	if (!ast_test_flag(&user->u_profile, USER_OPT_QUIET) && play_prompt_to_user(user,
    
    			conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, user->conference->b_profile.sounds))) {
    
    		/* user hungup while the sound was playing */
    		return -1;
    	}
    	return 0;
    }
    
    
    int conf_handle_only_person(struct confbridge_user *user)
    
    {
    	/* If audio prompts have not been quieted or this prompt quieted play it on out */
    
    	if (!ast_test_flag(&user->u_profile, USER_OPT_QUIET | USER_OPT_NOONLYPERSON)) {
    		if (play_prompt_to_user(user,
    
    			conf_get_sound(CONF_SOUND_ONLY_PERSON, user->conference->b_profile.sounds))) {
    
    			/* user hungup while the sound was playing */
    
    int conf_add_post_join_action(struct confbridge_user *user, int (*func)(struct confbridge_user *user))
    
    	struct post_join_action *action;
    	if (!(action = ast_calloc(1, sizeof(*action)))) {
    		return -1;
    	}
    	action->func = func;
    
    	AST_LIST_INSERT_TAIL(&user->post_join_list, action, list);
    
    void conf_handle_first_join(struct confbridge_conference *conference)
    
    	ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "confbridge:%s", conference->name);
    
    void conf_handle_second_active(struct confbridge_conference *conference)
    
    {
    	/* If we are the second participant we may need to stop music on hold on the first */
    
    	struct confbridge_user *first_user = AST_LIST_FIRST(&conference->active_list);
    
    	if (ast_test_flag(&first_user->u_profile, USER_OPT_MUSICONHOLD)) {
    		conf_moh_stop(first_user);
    
    	conf_update_user_mute(first_user);
    
    void conf_ended(struct confbridge_conference *conference)
    
    	struct pbx_find_info q = { .stacklen = 0 };
    
    
    	/* Called with a reference to conference */
    	ao2_unlink(conference_bridges, conference);
    
    	if (!ast_strlen_zero(conference->b_profile.regcontext) &&
    			pbx_find_extension(NULL, NULL, &q, conference->b_profile.regcontext,
    				conference->name, 1, NULL, "", E_MATCH)) {
    		ast_context_remove_extension(conference->b_profile.regcontext,
    				conference->name, 1, NULL);
    	}
    
    	ao2_lock(conference);
    	conf_stop_record(conference);
    	ao2_unlock(conference);
    
    /*!
     * \internal
     * \brief Allocate playback channel for a conference.
     * \pre expects conference to be locked before calling this function
     */
    static int alloc_playback_chan(struct confbridge_conference *conference)
    {
    	struct ast_format_cap *cap;
    	char taskprocessor_name[AST_TASKPROCESSOR_MAX_NAME + 1];
    
    	cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
    	if (!cap) {
    		return -1;
    	}
    	ast_format_cap_append(cap, ast_format_slin, 0);
    	conference->playback_chan = ast_request("CBAnn", cap, NULL, NULL,
    		conference->name, NULL);
    	ao2_ref(cap, -1);
    	if (!conference->playback_chan) {
    		return -1;
    	}
    
    	/* To make sure playback_chan has the same language as the bridge */
    	ast_channel_lock(conference->playback_chan);
    	ast_channel_language_set(conference->playback_chan, conference->b_profile.language);
    	ast_channel_unlock(conference->playback_chan);
    
    	ast_debug(1, "Created announcer channel '%s' to conference bridge '%s'\n",
    		ast_channel_name(conference->playback_chan), conference->name);
    
    	ast_taskprocessor_build_name(taskprocessor_name, sizeof(taskprocessor_name),
    		"Confbridge/%s", conference->name);
    	conference->playback_queue = ast_taskprocessor_get(taskprocessor_name, TPS_REF_DEFAULT);
    	if (!conference->playback_queue) {
    		ast_hangup(conference->playback_chan);
    		conference->playback_chan = NULL;
    		return -1;
    	}
    	return 0;
    }
    
    /*!
     * \brief Push the announcer channel into the bridge
     *
    
     * \param conference Conference bridge to push the announcer to
    
     * \retval 0 Success
     * \retval -1 Failed to push the channel to the bridge
     */
    
    static int push_announcer(struct confbridge_conference *conference)
    
    {
    	if (conf_announce_channel_push(conference->playback_chan)) {
    		ast_hangup(conference->playback_chan);
    		conference->playback_chan = NULL;
    		return -1;
    	}
    
    	ast_autoservice_start(conference->playback_chan);
    	return 0;
    }
    
    
    static void confbridge_unlock_and_unref(void *obj)
    {
    	struct confbridge_conference *conference = obj;
    
    	if (!obj) {
    		return;
    	}
    	ao2_unlock(conference);
    	ao2_ref(conference, -1);
    }
    
    void confbridge_handle_atxfer(struct ast_attended_transfer_message *msg)
    {
    	struct ast_channel_snapshot *old_snapshot;
    	struct ast_channel_snapshot *new_snapshot;
    	char *confbr_name = NULL;
    	char *comma;
    	RAII_VAR(struct confbridge_conference *, conference, NULL, confbridge_unlock_and_unref);
    	struct confbridge_user *user = NULL;
    	int found_user = 0;
    	struct ast_json *json_object;
    
    	if (msg->to_transferee.channel_snapshot
    		&& strcmp(msg->to_transferee.channel_snapshot->dialplan->appl, "ConfBridge") == 0
    		&& msg->target) {
    		/* We're transferring a bridge to an extension */
    		old_snapshot = msg->to_transferee.channel_snapshot;
    		new_snapshot = msg->target;
    	} else if (msg->to_transfer_target.channel_snapshot
    		&& strcmp(msg->to_transfer_target.channel_snapshot->dialplan->appl, "ConfBridge") == 0
    		&& msg->transferee) {
    		/* We're transferring a call to a bridge */
    		old_snapshot = msg->to_transfer_target.channel_snapshot;
    		new_snapshot = msg->transferee;
    	} else {
    		ast_log(LOG_ERROR, "Could not determine proper channels\n");
    		return;
    	}
    
    	/*
    	 * old_snapshot->data should have the original parameters passed to
    	 * the ConfBridge app:
    	 * conference[,bridge_profile[,user_profile[,menu]]]
    	 * We'll use "conference" to look up the bridge.
    	 *
    	 * We _could_ use old_snapshot->bridgeid to get the bridge but
    	 * that would involve locking the conference_bridges container
    	 * and iterating over it looking for a matching bridge.
    	 */
    	if (ast_strlen_zero(old_snapshot->dialplan->data)) {
    		ast_log(LOG_ERROR, "Channel '%s' didn't have app data set\n", old_snapshot->base->name);
    		return;
    	}
    	confbr_name = ast_strdupa(old_snapshot->dialplan->data);
    	comma = strchr(confbr_name, ',');
    	if (comma) {
    		*comma = '\0';
    	}
    
    	ast_debug(1, "Confbr: %s  Leaving: %s  Joining: %s\n", confbr_name, old_snapshot->base->name, new_snapshot->base->name);
    
    	conference = ao2_find(conference_bridges, confbr_name, OBJ_SEARCH_KEY);
    	if (!conference) {
    		ast_log(LOG_ERROR, "Conference bridge '%s' not found\n", confbr_name);
    		return;
    	}
    	ao2_lock(conference);
    
    	/*
    	 * We need to grab the user profile for the departing user in order to
    	 * properly format the join/leave messages.
    	 */
    	AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    		if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) {
    			found_user = 1;
    			break;
    		}
    	}
    
    	/*
    	 * If we didn't find the user in the active list, try the waiting list.
    	 */
    	if (!found_user && conference->waitingusers) {
    		AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
    			if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) {
    				found_user = 1;
    				break;
    			}
    		}
    	}
    
    	if (!found_user) {
    		ast_log(LOG_ERROR, "Unable to find user profile for channel '%s' in bridge '%s'\n",
    			old_snapshot->base->name, confbr_name);
    		return;
    	}
    
    	/*
    	 * We're going to use the existing user profile to create the messages.
    	 */
    	json_object = ast_json_pack("{s: b}",
    		"admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN)
    	);
    	if (!json_object) {
    		return;
    	}
    
    	send_conf_stasis_snapshots(conference, old_snapshot, confbridge_leave_type(), json_object);
    	ast_json_unref(json_object);
    
    	json_object = ast_json_pack("{s: b, s: b}",
    		"admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN),
    		"muted", user->muted);
    	if (!json_object) {
    		return;
    	}
    	send_conf_stasis_snapshots(conference, new_snapshot, confbridge_join_type(), json_object);
    	ast_json_unref(json_object);
    }
    
    
    /*!
     * \brief Join a conference bridge
     *
    
     * \param conference_name The conference name
     * \param user Conference bridge user structure
    
     *
     * \return A pointer to the conference bridge struct, or NULL if the conference room wasn't found.
     */
    
    static struct confbridge_conference *join_conference_bridge(const char *conference_name, struct confbridge_user *user)
    
    	struct confbridge_conference *conference;
    
    	int max_members_reached = 0;
    
    
    	/* 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", conference_name);
    
    
    	/* Attempt to find an existing conference bridge */
    
    	conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
    
    	if (conference && conference->b_profile.max_members) {
    		max_members_reached = conference->b_profile.max_members > conference->activeusers ? 0 : 1;
    
    	/* 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 && (max_members_reached || conference->locked) && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
    
    		ao2_unlock(conference_bridges);
    
    		ast_debug(1, "Conference '%s' is locked and caller is not an admin\n", conference_name);
    		ast_stream_and_wait(user->chan,
    
    			conf_get_sound(CONF_SOUND_LOCKED, conference->b_profile.sounds),
    			"");
    		ao2_ref(conference, -1);
    
    		return NULL;
    	}
    
    	/* If no conference bridge was found see if we can create one */
    
    		/* Try to allocate memory for a new conference bridge, if we fail... this won't end well. */
    
    		if (!(conference = ao2_alloc(sizeof(*conference), destroy_conference_bridge))) {
    
    			ao2_unlock(conference_bridges);
    
    			ast_log(LOG_ERROR, "Conference '%s' could not be created.\n", conference_name);
    
    		/* Setup for the record channel */
    		conference->record_filename = ast_str_create(RECORD_FILENAME_INITIAL_SPACE);
    		if (!conference->record_filename) {
    			ao2_ref(conference, -1);
    			ao2_unlock(conference_bridges);
    			return NULL;
    		}
    
    		/* Setup conference bridge parameters */
    
    		ast_copy_string(conference->name, conference_name, sizeof(conference->name));
    		conf_bridge_profile_copy(&conference->b_profile, &user->b_profile);
    
    
    		/* Create an actual bridge that will do the audio mixing */
    
    		conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX,
    
    			AST_BRIDGE_FLAG_MASQUERADE_ONLY | AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY,
    
    			ao2_ref(conference, -1);
    
    			ao2_unlock(conference_bridges);
    
    			ast_log(LOG_ERROR, "Conference '%s' mixing bridge could not be created.\n", conference_name);
    
    		/* Set the internal sample rate on the bridge from the bridge profile */
    
    		ast_bridge_set_internal_sample_rate(conference->bridge, conference->b_profile.internal_sample_rate);
    
    		/* Set the internal mixing interval on the bridge from the bridge profile */
    
    		ast_bridge_set_mixing_interval(conference->bridge, conference->b_profile.mix_interval);
    
    		ast_bridge_set_binaural_active(conference->bridge, ast_test_flag(&conference->b_profile, BRIDGE_OPT_BINAURAL_ACTIVE));
    
    		if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
    			ast_bridge_set_talker_src_video_mode(conference->bridge);
    
    		} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) {
    			ast_bridge_set_sfu_video_mode(conference->bridge);
    
    			ast_bridge_set_video_update_discard(conference->bridge, conference->b_profile.video_update_discard);
    
    			ast_bridge_set_remb_send_interval(conference->bridge, conference->b_profile.remb_send_interval);
    
    			if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE)) {
    				ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE);
    			} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST)) {
    				ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_LOWEST);
    			} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST)) {
    				ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST);
    
    			} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE_ALL)) {
    				ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE_ALL);
    			} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST_ALL)) {
    				ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_LOWEST_ALL);
    			} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL)) {
    				ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST_ALL);
    
    		if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_ENABLE_EVENTS)) {
    			ast_bridge_set_send_sdp_label(conference->bridge, 1);
    		}
    
    
    		/* Link it into the conference bridges container */
    
    		if (!ao2_link(conference_bridges, conference)) {
    			ao2_ref(conference, -1);
    
    			ao2_unlock(conference_bridges);
    			ast_log(LOG_ERROR,
    
    				"Conference '%s' could not be added to the conferences list.\n", conference_name);
    
    		conference->state = CONF_STATE_EMPTY;
    
    		if (alloc_playback_chan(conference)) {
    			ao2_unlink(conference_bridges, conference);
    			ao2_ref(conference, -1);
    			ao2_unlock(conference_bridges);
    			ast_log(LOG_ERROR, "Could not allocate announcer channel for conference '%s'\n", conference_name);
    			return NULL;
    		}
    
    
    			ao2_unlink(conference_bridges, conference);
    			ao2_ref(conference, -1);
    			ao2_unlock(conference_bridges);
    			ast_log(LOG_ERROR, "Could not add announcer channel for conference '%s' bridge\n", conference_name);
    			return NULL;
    		}
    
    
    		if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_RECORD_CONFERENCE)) {
    			ao2_lock(conference);
    
    
    		if (!ast_strlen_zero(conference->b_profile.regcontext)) {
    			if (!ast_exists_extension(NULL, conference->b_profile.regcontext, conference->name, 1, NULL)) {
    				ast_add_extension(conference->b_profile.regcontext, 1, conference->name, 1, NULL, NULL, "Noop", NULL, NULL, "ConfBridge");
    			}
    		}
    
    
    		ast_debug(1, "Created conference '%s' and linked to container.\n", conference_name);
    
    	}
    
    	ao2_unlock(conference_bridges);
    
    	/* Setup conference bridge user parameters */
    
    	user->conference = conference;
    
    	/* Determine if the new user should join the conference muted. */
    	if (ast_test_flag(&user->u_profile, USER_OPT_STARTMUTED)
    		|| (!ast_test_flag(&user->u_profile, USER_OPT_ADMIN) && conference->muted)) {
    		/* Set user level mute request. */
    		user->muted = 1;
    	}
    
    
    	/*
    	 * Suspend any MOH until the user actually joins the bridge of
    	 * the conference.  This way any pre-join file playback does not
    	 * need to worry about MOH.
    	 */
    
    	user->suspended_moh = 1;
    
    	if (handle_conf_user_join(user)) {
    
    		/* Invalid event, nothing was done, so we don't want to process a leave. */
    
    		ao2_unlock(conference);
    		ao2_ref(conference, -1);
    
    	if (ast_check_hangup(user->chan)) {
    		ao2_unlock(conference);
    		leave_conference(user);
    
    	/* If an announcement is to be played play it */
    
    	if (!ast_strlen_zero(user->u_profile.announcement)) {
    		if (play_prompt_to_user(user,
    			user->u_profile.announcement)) {
    			leave_conference(user);
    
    	/* Announce number of users if need be */
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_ANNOUNCEUSERCOUNT)) {
    
    		if (announce_user_count(conference, user, NULL)) {
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_ANNOUNCEUSERCOUNTALL) &&
    		(conference->activeusers > user->u_profile.announce_user_count_all_after)) {
    
    		int user_count_res;
    
    		/*
    		 * We have to autoservice the new user because he has not quite
    		 * joined the conference yet.
    		 */
    
    		ast_autoservice_start(user->chan);
    
    		user_count_res = announce_user_count(conference, NULL, NULL);
    
    		ast_autoservice_stop(user->chan);
    
    	while ((action = AST_LIST_REMOVE_HEAD(&user->post_join_list, list))) {
    		action->func(user);
    
     * \param user The conference user
    
    static void leave_conference(struct confbridge_user *user)
    
    	struct post_join_action *action;
    
    	ao2_lock(user->conference);
    
    	handle_conf_user_leave(user);
    
    	ao2_unlock(user->conference);
    
    	/* Discard any post-join actions */
    	while ((action = AST_LIST_REMOVE_HEAD(&user->post_join_list, list))) {
    		ast_free(action);
    	}
    
    	/* Done mucking with the conference, huzzah */
    
    	ao2_ref(user->conference, -1);
    	user->conference = NULL;
    
    static void playback_common(struct confbridge_conference *conference, const char *filename, int say_number)
    {
    	/* Don't try to play if the playback channel has been hung up */
    	if (!conference->playback_chan) {
    		return;
    	}
    
    	ast_autoservice_stop(conference->playback_chan);
    
    	/* The channel is all under our control, in goes the prompt */
    	if (!ast_strlen_zero(filename)) {
    		ast_stream_and_wait(conference->playback_chan, filename, "");
    	} else if (say_number >= 0) {
    		ast_say_number(conference->playback_chan, say_number, "",
    			ast_channel_language(conference->playback_chan), NULL);
    	}
    
    	ast_autoservice_start(conference->playback_chan);
    }
    
    
    struct playback_task_data {
    	struct confbridge_conference *conference;
    	const char *filename;
    	int say_number;
    	int playback_finished;
    	ast_mutex_t lock;
    	ast_cond_t cond;
    };
    
    
     * \brief Play an announcement into a confbridge
     *
     * This runs in the playback queue taskprocessor. This ensures that
     * all playbacks are handled in sequence and do not play over top one
     * another.
     *
     * This task runs synchronously so there is no need for performing any
     * sort of cleanup on the input parameter.
     *
     * \param data A playback_task_data
     * \return 0
    
    static int playback_task(void *data)
    
    	struct playback_task_data *ptd = data;
    
    	playback_common(ptd->conference, ptd->filename, ptd->say_number);
    
    
    	ast_mutex_lock(&ptd->lock);
    	ptd->playback_finished = 1;
    	ast_cond_signal(&ptd->cond);
    	ast_mutex_unlock(&ptd->lock);
    
    static void playback_task_data_init(struct playback_task_data *ptd, struct confbridge_conference *conference,
    		const char *filename, int say_number)
    {
    	ast_mutex_init(&ptd->lock);
    	ast_cond_init(&ptd->cond, NULL);
    
    	ptd->filename = filename;
    	ptd->say_number = say_number;
    	ptd->conference = conference;
    	ptd->playback_finished = 0;
    }
    
    static void playback_task_data_destroy(struct playback_task_data *ptd)
    {
    	ast_mutex_destroy(&ptd->lock);
    	ast_cond_destroy(&ptd->cond);
    }
    
    
    static int play_sound_helper(struct confbridge_conference *conference, const char *filename, int say_number)
    
    	struct playback_task_data ptd;
    
    
    	/* Do not waste resources trying to play files that do not exist */
    
    	if (ast_strlen_zero(filename)) {
    		if (say_number < 0) {
    			return 0;
    		}
    	} else if (!sound_file_exists(filename)) {
    
    	playback_task_data_init(&ptd, conference, filename, say_number);
    	if (ast_taskprocessor_push(conference->playback_queue, playback_task, &ptd)) {
    		if (!ast_strlen_zero(filename)) {
    			ast_log(LOG_WARNING, "Unable to play file '%s' to conference %s\n",
    				filename, conference->name);
    		} else {
    			ast_log(LOG_WARNING, "Unable to say number '%d' to conference %s\n",
    				say_number, conference->name);
    		}
    		playback_task_data_destroy(&ptd);
    
    	/* Wait for the playback to complete */
    	ast_mutex_lock(&ptd.lock);
    	while (!ptd.playback_finished) {
    		ast_cond_wait(&ptd.cond, &ptd.lock);
    
    	ast_mutex_unlock(&ptd.lock);
    
    	playback_task_data_destroy(&ptd);
    
    int play_sound_file(struct confbridge_conference *conference, const char *filename)
    
    	return play_sound_helper(conference, filename, -1);