diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index 480666e98ca0b4727af85dc1749cc402d2c695a3..c560ecec26c1559b50a5b65d1a38e5ec1c729fd1 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -283,10 +283,16 @@ static const char app[] = "ConfBridge";
 /* Number of buckets our conference bridges container can have */
 #define CONFERENCE_BRIDGE_BUCKETS 53
 
+enum {
+	CONF_RECORD_EXIT = 0,
+	CONF_RECORD_START,
+	CONF_RECORD_STOP,
+};
+
 /*! \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);
+static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user);
 static int play_sound_number(struct conference_bridge *conference_bridge, int say_number);
 static int execute_menu_entry(struct conference_bridge *conference_bridge,
 	struct conference_bridge_user *conference_bridge_user,
@@ -404,136 +410,166 @@ static void *record_thread(void *obj)
 	struct ast_channel *chan;
 	struct ast_str *filename = ast_str_alloca(PATH_MAX);
 
+	ast_mutex_lock(&conference_bridge->record_lock);
 	if (!mixmonapp) {
-		ao2_ref(conference_bridge, -1);
-		return NULL;
-	}
-
-	ao2_lock(conference_bridge);
-	if (!(conference_bridge->record_chan)) {
+		ast_log(LOG_WARNING, "Can not record ConfBridge, MixMonitor app is not installed\n");
 		conference_bridge->record_thread = AST_PTHREADT_NULL;
-		ao2_unlock(conference_bridge);
+		ast_mutex_unlock(&conference_bridge->record_lock);
 		ao2_ref(conference_bridge, -1);
 		return NULL;
 	}
-	chan = ast_channel_ref(conference_bridge->record_chan);
 
-	if (!(ast_strlen_zero(conference_bridge->b_profile.rec_file))) {
-		ast_str_append(&filename, 0, "%s", conference_bridge->b_profile.rec_file);
-	} else {
-		time_t now;
-		time(&now);
-		ast_str_append(&filename, 0, "confbridge-%s-%u.wav",
-			conference_bridge->name,
-			(unsigned int) now);
-	}
-	ao2_unlock(conference_bridge);
-
-	ast_answer(chan);
-	pbx_exec(chan, mixmonapp, ast_str_buffer(filename));
-	ast_bridge_join(conference_bridge->bridge, chan, NULL, NULL, NULL);
+	/* XXX If we get an EXIT right here, START will essentially be a no-op */
+	while (conference_bridge->record_state != CONF_RECORD_EXIT) {
+		if (!(ast_strlen_zero(conference_bridge->b_profile.rec_file))) {
+			ast_str_append(&filename, 0, "%s", conference_bridge->b_profile.rec_file);
+		} else {
+			time_t now;
+			time(&now);
+			ast_str_append(&filename, 0, "confbridge-%s-%u.wav",
+				conference_bridge->name,
+				(unsigned int) now);
+		}
 
-	ao2_lock(conference_bridge);
-	conference_bridge->record_thread = AST_PTHREADT_NULL;
-	ao2_unlock(conference_bridge);
+		chan = ast_channel_ref(conference_bridge->record_chan);
+		ast_answer(chan);
+		pbx_exec(chan, mixmonapp, ast_str_buffer(filename));
+		ast_bridge_join(conference_bridge->bridge, chan, NULL, NULL, NULL);
 
-	ast_hangup(chan); /* This will eat this threads reference to the channel as well */
+		ast_hangup(chan); /* This will eat this thread's reference to the channel as well */
+		/* STOP has been called. Wait for either a START or an EXIT */
+		ast_cond_wait(&conference_bridge->record_cond, &conference_bridge->record_lock);
+	}
+	ast_mutex_unlock(&conference_bridge->record_lock);
 	ao2_ref(conference_bridge, -1);
 	return NULL;
 }
 
-/*!
- * \internal
- * \brief Returns whether or not conference is being recorded.
+/*! \brief Returns whether or not conference is being recorded.
+ * \param conference_bridge The bridge to check for recording
  * \retval 1, conference is recording.
  * \retval 0, conference is NOT recording.
  */
 static int conf_is_recording(struct conference_bridge *conference_bridge)
 {
-	int res = 0;
-	ao2_lock(conference_bridge);
-	if (conference_bridge->record_chan || conference_bridge->record_thread != AST_PTHREADT_NULL) {
-		res = 1;
+	return conference_bridge->record_state == CONF_RECORD_START;
+}
+
+/*! \brief Stop recording a conference bridge
+ * \internal
+ * \param conference_bridge The conference bridge on which to stop the recording
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+static int conf_stop_record(struct conference_bridge *conference_bridge)
+{
+	struct ast_channel *chan;
+	if (conference_bridge->record_thread == AST_PTHREADT_NULL || !conf_is_recording(conference_bridge)) {
+		return -1;
 	}
-	ao2_unlock(conference_bridge);
-	return res;
+	conference_bridge->record_state = CONF_RECORD_STOP;
+	chan = ast_channel_ref(conference_bridge->record_chan);
+	ast_bridge_remove(conference_bridge->bridge, chan);
+	ast_queue_frame(chan, &ast_null_frame);
+	chan = ast_channel_unref(chan);
+	ast_test_suite_event_notify("CONF_STOP_RECORD", "Message: stopped conference recording channel\r\nConference: %s", conference_bridge->b_profile.name);
+
+	return 0;
 }
 
 /*!
  * \internal
  * \brief Stops the confbridge recording thread.
  *
- * \note do not call this function with any locks
+ * \note Must be called with the conference_bridge locked
  */
-static int conf_stop_record(struct conference_bridge *conference_bridge)
+static int conf_stop_record_thread(struct conference_bridge *conference_bridge)
 {
-	ao2_lock(conference_bridge);
-
-	if (conference_bridge->record_thread != AST_PTHREADT_NULL) {
-		struct ast_channel *chan = ast_channel_ref(conference_bridge->record_chan);
-		pthread_t thread = conference_bridge->record_thread;
-		ao2_unlock(conference_bridge);
-
-		ast_bridge_remove(conference_bridge->bridge, chan);
-		ast_queue_frame(chan, &ast_null_frame);
+	if (conference_bridge->record_thread == AST_PTHREADT_NULL) {
+		return -1;
+	}
+	conf_stop_record(conference_bridge);
 
-		chan = ast_channel_unref(chan);
-		pthread_join(thread, NULL);
-		ast_test_suite_event_notify("CONF_STOP_RECORD", "Message: stopped conference recording channel\r\nConference: %s", conference_bridge->b_profile.name);
+	ast_mutex_lock(&conference_bridge->record_lock);
+	conference_bridge->record_state = CONF_RECORD_EXIT;
+	ast_cond_signal(&conference_bridge->record_cond);
+	ast_mutex_unlock(&conference_bridge->record_lock);
 
-		ao2_lock(conference_bridge);
-	}
+	pthread_join(conference_bridge->record_thread, NULL);
+	conference_bridge->record_thread = AST_PTHREADT_NULL;
 
 	/* this is the reference given to the channel during the channel alloc */
 	if (conference_bridge->record_chan) {
 		conference_bridge->record_chan = ast_channel_unref(conference_bridge->record_chan);
 	}
 
-	ao2_unlock(conference_bridge);
 	return 0;
 }
 
+/*! \brief Start recording the conference
+ * \internal
+ * \note conference_bridge must be locked when calling this function
+ * \param conference_bridge The conference bridge to start recording
+ * \retval 0 success
+ * \rteval non-zero failure
+ */
 static int conf_start_record(struct conference_bridge *conference_bridge)
 {
-	struct ast_format_cap *cap = ast_format_cap_alloc_nolock();
+	struct ast_format_cap *cap;
 	struct ast_format tmpfmt;
 	int cause;
 
-	ao2_lock(conference_bridge);
-	if (conference_bridge->record_chan || conference_bridge->record_thread != AST_PTHREADT_NULL) {
-		ao2_unlock(conference_bridge);
-		return -1; /* already recording */
-	}
-	if (!cap) {
-		ao2_unlock(conference_bridge);
+	if (conference_bridge->record_state != CONF_RECORD_STOP) {
 		return -1;
 	}
+
 	if (!pbx_findapp("MixMonitor")) {
 		ast_log(LOG_WARNING, "Can not record ConfBridge, MixMonitor app is not installed\n");
-		cap = ast_format_cap_destroy(cap);
-		ao2_unlock(conference_bridge);
 		return -1;
 	}
+
+	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->record_chan = ast_request("ConfBridgeRec", cap, NULL, conference_bridge->name, &cause))) {
 		cap = ast_format_cap_destroy(cap);
-		ao2_unlock(conference_bridge);
 		return -1;
 	}
 
 	cap = ast_format_cap_destroy(cap);
+
+	conference_bridge->record_state = CONF_RECORD_START;
+	ast_mutex_lock(&conference_bridge->record_lock);
+	ast_cond_signal(&conference_bridge->record_cond);
+	ast_mutex_unlock(&conference_bridge->record_lock);
+	ast_test_suite_event_notify("CONF_START_RECORD", "Message: started conference recording channel\r\nConference: %s", conference_bridge->b_profile.name);
+
+	return 0;
+}
+
+/*! \brief Start the recording thread on a conference bridge
+ * \internal
+ * \param conference_bridge The conference bridge on which to start the recording thread
+ * \retval 0 success
+ * \retval -1 failure
+ */
+static int start_conf_record_thread(struct conference_bridge *conference_bridge)
+{
 	ao2_ref(conference_bridge, +1); /* give the record thread a ref */
 
+	ao2_lock(conference_bridge);
+	conf_start_record(conference_bridge);
+	ao2_unlock(conference_bridge);
+
 	if (ast_pthread_create_background(&conference_bridge->record_thread, NULL, record_thread, conference_bridge)) {
 		ast_log(LOG_WARNING, "Failed to create recording channel for conference %s\n", conference_bridge->name);
-
-		ao2_unlock(conference_bridge);
 		ao2_ref(conference_bridge, -1); /* error so remove ref */
 		return -1;
 	}
 
-	ast_test_suite_event_notify("CONF_START_RECORD", "Message: started conference recording channel\r\nConference: %s", conference_bridge->b_profile.name);
-	ao2_unlock(conference_bridge);
 	return 0;
 }
 
@@ -642,10 +678,10 @@ static int announce_user_count(struct conference_bridge *conference_bridge, stru
 	const char *only_one = conf_get_sound(CONF_SOUND_ONLY_ONE, conference_bridge->b_profile.sounds);
 	const char *there_are = conf_get_sound(CONF_SOUND_THERE_ARE, conference_bridge->b_profile.sounds);
 
-	if (conference_bridge->users == 1) {
-		/* Awww we are the only person in the conference bridge */
+	if (conference_bridge->activeusers <= 1) {
+		/* Awww we are the only person in the conference bridge OR we only have waitmarked users */
 		return 0;
-	} else if (conference_bridge->users == 2) {
+	} else if (conference_bridge->activeusers == 2) {
 		if (conference_bridge_user) {
 			/* Eep, there is one other person */
 			if (ast_stream_and_wait(conference_bridge_user->chan,
@@ -664,7 +700,7 @@ static int announce_user_count(struct conference_bridge *conference_bridge, stru
 				"")) {
 				return -1;
 			}
-			if (ast_say_number(conference_bridge_user->chan, conference_bridge->users - 1, "", ast_channel_language(conference_bridge_user->chan), NULL)) {
+			if (ast_say_number(conference_bridge_user->chan, conference_bridge->activeusers - 1, "", ast_channel_language(conference_bridge_user->chan), NULL)) {
 				return -1;
 			}
 			if (ast_stream_and_wait(conference_bridge_user->chan,
@@ -674,7 +710,7 @@ static int announce_user_count(struct conference_bridge *conference_bridge, stru
 			}
 		} else if (ast_fileexists(there_are, NULL, NULL) && ast_fileexists(other_in_party, NULL, NULL)) {
 			play_sound_file(conference_bridge, there_are);
-			play_sound_number(conference_bridge, conference_bridge->users - 1);
+			play_sound_number(conference_bridge, conference_bridge->activeusers - 1);
 			play_sound_file(conference_bridge, other_in_party);
 		}
 	}
@@ -689,16 +725,13 @@ static int announce_user_count(struct conference_bridge *conference_bridge, stru
  * \param file Prompt to play
  *
  * \return Returns 0 on success, -1 if the user hung up
- *
- * \note This function assumes that conference_bridge is locked
+ * \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_channel(struct conference_bridge *conference_bridge, struct ast_channel *chan, const char *file)
+static int play_prompt_to_user(struct conference_bridge_user *cbu, const char *filename)
 {
-	int res;
-	ao2_unlock(conference_bridge);
-	res = ast_stream_and_wait(chan, file, "");
-	ao2_lock(conference_bridge);
-	return res;
+	return ast_stream_and_wait(cbu->chan, filename, "");
 }
 
 static void handle_video_on_join(struct conference_bridge *conference_bridge, struct ast_channel *chan, int marked)
@@ -713,7 +746,7 @@ static void handle_video_on_join(struct conference_bridge *conference_bridge, st
 		struct conference_bridge_user *tmp_user = NULL;
 		ao2_lock(conference_bridge);
 		/* see if anyone is already the video src */
-		AST_LIST_TRAVERSE(&conference_bridge->users_list, tmp_user, list) {
+		AST_LIST_TRAVERSE(&conference_bridge->active_list, tmp_user, list) {
 			if (tmp_user->chan == chan) {
 				continue;
 			}
@@ -758,7 +791,7 @@ static void handle_video_on_exit(struct conference_bridge *conference_bridge, st
 
 	/* Make the next available marked user the video src.  */
 	ao2_lock(conference_bridge);
-	AST_LIST_TRAVERSE(&conference_bridge->users_list, tmp_user, list) {
+	AST_LIST_TRAVERSE(&conference_bridge->active_list, tmp_user, list) {
 		if (tmp_user->chan == chan) {
 			continue;
 		}
@@ -771,183 +804,177 @@ static void handle_video_on_exit(struct conference_bridge *conference_bridge, st
 }
 
 /*!
- * \brief Perform post-joining marked specific actions
+ * \brief Destroy a conference bridge
  *
- * \param conference_bridge Conference bridge being joined
- * \param conference_bridge_user Conference bridge user joining
+ * \param obj The conference bridge object
  *
- * \return Returns 0 on success, -1 if the user hung up
+ * \return Returns nothing
  */
-static int post_join_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
+static void destroy_conference_bridge(void *obj)
 {
-	if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER)) {
-		struct conference_bridge_user *other_conference_bridge_user = NULL;
+	struct conference_bridge *conference_bridge = obj;
 
-		/* If we are not the first user to join, then the users are already
-		 * in the conference so we do not need to update them. */
-		if (conference_bridge->markedusers >= 2) {
-			return 0;
-		}
+	ast_debug(1, "Destroying conference bridge '%s'\n", conference_bridge->name);
 
-		/* 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 (other_conference_bridge_user->playing_moh && !ast_bridge_suspend(conference_bridge->bridge, other_conference_bridge_user->chan)) {
-				other_conference_bridge_user->playing_moh = 0;
-				ast_moh_stop(other_conference_bridge_user->chan);
-				ast_bridge_unsuspend(conference_bridge->bridge, other_conference_bridge_user->chan);
-			}
-		}
+	ast_mutex_destroy(&conference_bridge->playback_lock);
 
-		/* Next play the audio file stating they are going to be placed into the conference */
-		if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) {
-			if (play_prompt_to_channel(conference_bridge,
-				conference_bridge_user->chan,
-				conf_get_sound(CONF_SOUND_PLACE_IN_CONF, conference_bridge_user->b_profile.sounds))) {
-				/* user hungup while the sound was playing */
-				return -1;
-			}
+	if (conference_bridge->playback_chan) {
+		struct ast_channel *underlying_channel = ast_channel_tech(conference_bridge->playback_chan)->bridged_channel(conference_bridge->playback_chan, NULL);
+		if (underlying_channel) {
+			ast_hangup(underlying_channel);
 		}
+		ast_hangup(conference_bridge->playback_chan);
+		conference_bridge->playback_chan = NULL;
+	}
 
-		/* 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;
-			}
-			/* only unmute them if they are not supposed to start muted */
-			if (!ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_STARTMUTED)) {
-				other_conference_bridge_user->features.mute = 0;
-			}
-		}
+	/* 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;
+	}
+	conf_bridge_profile_destroy(&conference_bridge->b_profile);
+}
+
+/*! \brief Call the proper join event handler for the user for the conference bridge's current state
+ * \internal
+ * \param cbu The conference bridge user that is joining
+ * \retval 0 success
+ * \retval -1 failure
+ */
+static int handle_conf_user_join(struct conference_bridge_user *cbu)
+{
+	conference_event_fn handler;
+	if (ast_test_flag(&cbu->u_profile, USER_OPT_MARKEDUSER)) {
+		handler = cbu->conference_bridge->state->join_marked;
+	} else if (ast_test_flag(&cbu->u_profile, USER_OPT_WAITMARKED)) {
+		handler = cbu->conference_bridge->state->join_waitmarked;
 	} else {
-		/* If a marked user already exists in the conference bridge we can just bail out now */
-		if (conference_bridge->markedusers) {
-			return 0;
-		}
-		/* 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->u_profile, USER_OPT_QUIET)) {
-			if (play_prompt_to_channel(conference_bridge,
-				conference_bridge_user->chan,
-				conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, conference_bridge_user->b_profile.sounds))) {
-				/* user hungup while the sound was playing */
-				return -1;
-			}
-		}
-		/* 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->u_profile, USER_OPT_MUSICONHOLD)) {
-			ast_moh_start(conference_bridge_user->chan, conference_bridge_user->u_profile.moh_class, NULL);
-			conference_bridge_user->playing_moh = 1;
-		}
+		handler = cbu->conference_bridge->state->join_unmarked;
+	}
+
+	ast_assert(handler != NULL);
+
+	if (!handler) {
+		conf_invalid_event_fn(cbu);
+		return -1;
 	}
+
+	handler(cbu);
+
 	return 0;
 }
 
-/*!
- * \brief Perform post-joining non-marked specific actions
- *
- * \param conference_bridge Conference bridge being joined
- * \param conference_bridge_user Conference bridge user joining
- *
- * \return Returns 0 on success, -1 if the user hung up
+/*! \brief Call the proper leave event handler for the user for the conference bridge's current state
+ * \internal
+ * \param cbu The conference bridge user that is leaving
+ * \retval 0 success
+ * \retval -1 failure
  */
-static int post_join_unmarked(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
-{
-	/* 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->u_profile, USER_OPT_QUIET | USER_OPT_NOONLYPERSON)) {
-			if (play_prompt_to_channel(conference_bridge,
-				conference_bridge_user->chan,
-				conf_get_sound(CONF_SOUND_ONLY_PERSON, conference_bridge_user->b_profile.sounds))) {
-				/* user hungup while the sound was playing */
-				return -1;
-			}
-		}
-		/* 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->u_profile, USER_OPT_MUSICONHOLD)) {
-			ast_moh_start(conference_bridge_user->chan, conference_bridge_user->u_profile.moh_class, NULL);
-			conference_bridge_user->playing_moh = 1;
-		}
-		return 0;
+static int handle_conf_user_leave(struct conference_bridge_user *cbu)
+{
+	conference_event_fn handler;
+	if (ast_test_flag(&cbu->u_profile, USER_OPT_MARKEDUSER)) {
+		handler = cbu->conference_bridge->state->leave_marked;
+	} else if (ast_test_flag(&cbu->u_profile, USER_OPT_WAITMARKED)) {
+		handler = cbu->conference_bridge->state->leave_waitmarked;
+	} else {
+		handler = cbu->conference_bridge->state->leave_unmarked;
 	}
 
-	/* Announce number of users if need be */
-	if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNT)) {
-		ao2_unlock(conference_bridge);
-		if (announce_user_count(conference_bridge, conference_bridge_user)) {
-			ao2_lock(conference_bridge);
-			return -1;
-		}
-		ao2_lock(conference_bridge);
+	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(cbu);
+		return -1;
 	}
 
-	/* 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->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) {
-			first_participant->playing_moh = 0;
-			ast_moh_stop(first_participant->chan);
-			ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan);
-		}
+	handler(cbu);
+
+	return 0;
+}
+
+int conf_handle_first_marked_common(struct conference_bridge_user *cbu)
+{
+	if (!ast_test_flag(&cbu->u_profile, USER_OPT_QUIET) && play_prompt_to_user(cbu, conf_get_sound(CONF_SOUND_PLACE_IN_CONF, cbu->b_profile.sounds))) {
+		return -1;
 	}
+	return 0;
+}
 
-	if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNTALL) &&
-		(conference_bridge->users > conference_bridge_user->u_profile.announce_user_count_all_after)) {
-		ao2_unlock(conference_bridge);
-		if (announce_user_count(conference_bridge, NULL)) {
-			ao2_lock(conference_bridge);
+int conf_handle_inactive_waitmarked(struct conference_bridge_user *cbu)
+{
+	/* Be sure we are muted so we can't talk to anybody else waiting */
+	cbu->features.mute = 1;
+	/* If we have not been quieted play back that they are waiting for the leader */
+	if (!ast_test_flag(&cbu->u_profile, USER_OPT_QUIET) && play_prompt_to_user(cbu,
+			conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, cbu->b_profile.sounds))) {
+		/* user hungup while the sound was playing */
+		return -1;
+	}
+	/* Start music on hold if needed */
+	if (ast_test_flag(&cbu->u_profile, USER_OPT_MUSICONHOLD)) {
+		ast_moh_start(cbu->chan, cbu->u_profile.moh_class, NULL);
+		cbu->playing_moh = 1;
+	}
+	return 0;
+}
+
+int conf_handle_only_unmarked(struct conference_bridge_user *cbu)
+{
+	/* If audio prompts have not been quieted or this prompt quieted play it on out */
+	if (!ast_test_flag(&cbu->u_profile, USER_OPT_QUIET | USER_OPT_NOONLYPERSON)) {
+		if (play_prompt_to_user(cbu,
+			conf_get_sound(CONF_SOUND_ONLY_PERSON, cbu->b_profile.sounds))) {
+			/* user hungup while the sound was playing */
 			return -1;
 		}
-		ao2_lock(conference_bridge);
 	}
 	return 0;
 }
 
-/*!
- * \brief Destroy a conference bridge
- *
- * \param obj The conference bridge object
- *
- * \return Returns nothing
- */
-static void destroy_conference_bridge(void *obj)
+int conf_add_post_join_action(struct conference_bridge_user *cbu, int (*func)(struct conference_bridge_user *cbu))
 {
-	struct conference_bridge *conference_bridge = obj;
+	struct post_join_action *action;
+	if (!(action = ast_calloc(1, sizeof(*action)))) {
+		return -1;
+	}
+	action->func = func;
+	AST_LIST_INSERT_TAIL(&cbu->post_join_list, action, list);
+	return 0;
+}
 
-	ast_debug(1, "Destroying conference bridge '%s'\n", conference_bridge->name);
 
-	ast_mutex_destroy(&conference_bridge->playback_lock);
+void conf_handle_first_join(struct conference_bridge *conference_bridge)
+{
+	ast_devstate_changed(AST_DEVICE_INUSE, "confbridge:%s", conference_bridge->name);
+}
 
-	if (conference_bridge->playback_chan) {
-		struct ast_channel *underlying_channel = ast_channel_tech(conference_bridge->playback_chan)->bridged_channel(conference_bridge->playback_chan, NULL);
-		if (underlying_channel) {
-			ast_hangup(underlying_channel);
-		}
-		ast_hangup(conference_bridge->playback_chan);
-		conference_bridge->playback_chan = NULL;
-	}
+void conf_handle_second_active(struct conference_bridge *conference_bridge)
+{
+	/* If we are the second participant we may need to stop music on hold on the first */
+	struct conference_bridge_user *first_participant = AST_LIST_FIRST(&conference_bridge->active_list);
 
-	/* 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;
+	/* Temporarily suspend the above participant from the bridge so we have control to stop MOH if needed */
+	if (ast_test_flag(&first_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) {
+		first_participant->playing_moh = 0;
+		ast_moh_stop(first_participant->chan);
+		ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan);
+	}
+	if (!ast_test_flag(&first_participant->u_profile, USER_OPT_STARTMUTED)) {
+		first_participant->features.mute = 0;
 	}
-	conf_bridge_profile_destroy(&conference_bridge->b_profile);
 }
 
-static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user);
+void conf_ended(struct conference_bridge *conference_bridge)
+{
+	/* Called with a reference to conference_bridge */
+	ao2_unlink(conference_bridges, conference_bridge);
+	send_conf_end_event(conference_bridge->name);
+	conf_stop_record_thread(conference_bridge);
+}
 
 /*!
  * \brief Join a conference bridge
@@ -960,8 +987,8 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge,
 static struct conference_bridge *join_conference_bridge(const char *name, struct conference_bridge_user *conference_bridge_user)
 {
 	struct conference_bridge *conference_bridge = NULL;
+	struct post_join_action *action;
 	struct conference_bridge tmp;
-	int start_record = 0;
 	int max_members_reached = 0;
 
 	ast_copy_string(tmp.name, name, sizeof(tmp.name));
@@ -975,7 +1002,7 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct
 	conference_bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
 
 	if (conference_bridge && conference_bridge->b_profile.max_members) {
-		max_members_reached = conference_bridge->b_profile.max_members > conference_bridge->users ? 0 : 1;
+		max_members_reached = conference_bridge->b_profile.max_members > conference_bridge->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 */
@@ -1024,9 +1051,22 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct
 		/* Setup lock for playback channel */
 		ast_mutex_init(&conference_bridge->playback_lock);
 
+		/* Setup lock for the record channel */
+		ast_mutex_init(&conference_bridge->record_lock);
+		ast_cond_init(&conference_bridge->record_cond, NULL);
+
 		/* Link it into the conference bridges container */
 		ao2_link(conference_bridges, conference_bridge);
 
+		/* Set the initial state to EMPTY */
+		conference_bridge->state = CONF_STATE_EMPTY;
+
+		conference_bridge->record_state = CONF_RECORD_STOP;
+		if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_RECORD_CONFERENCE)) {
+			ao2_lock(conference_bridge);
+			start_conf_record_thread(conference_bridge);
+			ao2_unlock(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);
@@ -1039,57 +1079,41 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct
 
 	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++;
+	if (handle_conf_user_join(conference_bridge_user)) {
+		/* Invalid event, nothing was done, so we don't want to process a leave. */
+		ao2_unlock(conference_bridge);
+		ao2_ref(conference_bridge, -1);
+		return NULL;
 	}
 
-	/* Set the device state for this conference */
-	if (conference_bridge->users == 1) {
-		ast_devstate_changed(AST_DEVICE_INUSE, "confbridge:%s", conference_bridge->name);
+	if (ast_check_hangup(conference_bridge_user->chan)) {
+		ao2_unlock(conference_bridge);
+		leave_conference_bridge(conference_bridge, conference_bridge_user);
+		return NULL;
 	}
 
-	/* 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);
+	ao2_unlock(conference_bridge);
+
+	/* Announce number of users if need be */
+	if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNT)) {
+		if (announce_user_count(conference_bridge, conference_bridge_user)) {
 			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;
-		}
-	} else {
-		if (post_join_unmarked(conference_bridge, conference_bridge_user)) {
-			ao2_unlock(conference_bridge);
+	if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNTALL) &&
+		(conference_bridge->activeusers > conference_bridge_user->u_profile.announce_user_count_all_after)) {
+		if (announce_user_count(conference_bridge, NULL)) {
 			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);
+	/* Handle post-join actions */
+	while ((action = AST_LIST_REMOVE_HEAD(&conference_bridge_user->post_join_list, list))) {
+		action->func(conference_bridge_user);
+		ast_free(action);
 	}
 
 	return conference_bridge;
@@ -1106,73 +1130,10 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge,
 {
 	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);
-	}
+	handle_conf_user_leave(conference_bridge_user);
 
 	/* Done mucking with the conference bridge, huzzah */
 	ao2_unlock(conference_bridge);
-
-	if (!conference_bridge->users) {
-		conf_stop_record(conference_bridge);
-	}
-
 	ao2_ref(conference_bridge, -1);
 }
 
@@ -1250,16 +1211,7 @@ static int play_sound_helper(struct conference_bridge *conference_bridge, const
 	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)
+int play_sound_file(struct conference_bridge *conference_bridge, const char *filename)
 {
 	return play_sound_helper(conference_bridge, filename, -1);
 }
@@ -1451,6 +1403,7 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
 		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
@@ -1659,7 +1612,7 @@ static int action_toggle_mute_participants(struct conference_bridge *conference_
 	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) {
+	AST_LIST_TRAVERSE(&conference_bridge->active_list, participant, list) {
 		if (!ast_test_flag(&participant->u_profile, USER_OPT_ADMIN)) {
 			participant->features.mute = conference_bridge->muted;
 		}
@@ -1781,7 +1734,7 @@ static int action_kick_last(struct conference_bridge *conference_bridge,
 	}
 
 	ao2_lock(conference_bridge);
-	if (((last_participant = AST_LIST_LAST(&conference_bridge->users_list)) == conference_bridge_user)
+	if (((last_participant = AST_LIST_LAST(&conference_bridge->active_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,
@@ -2028,7 +1981,7 @@ static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct
 		return CLI_SUCCESS;
 	}
 	ao2_lock(bridge);
-	AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+	AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 		if (!strncmp(a->argv[3], ast_channel_name(participant->chan), strlen(ast_channel_name(participant->chan)))) {
 			break;
 		}
@@ -2069,7 +2022,7 @@ static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct
 		ast_cli(a->fd, "================================ ====== ====== ========\n");
 		i = ao2_iterator_init(conference_bridges, 0);
 		while ((bridge = ao2_iterator_next(&i))) {
-			ast_cli(a->fd, "%-32s %6i %6i %s\n", bridge->name, bridge->users, bridge->markedusers, (bridge->locked ? "locked" : "unlocked"));
+			ast_cli(a->fd, "%-32s %6i %6i %s\n", bridge->name, bridge->activeusers, bridge->markedusers, (bridge->locked ? "locked" : "unlocked"));
 			ao2_ref(bridge, -1);
 		}
 		ao2_iterator_destroy(&i);
@@ -2086,7 +2039,7 @@ static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct
 		ast_cli(a->fd, "Channel                       User Profile     Bridge Profile   Menu             CallerID\n");
 		ast_cli(a->fd, "============================= ================ ================ ================ ================\n");
 		ao2_lock(bridge);
-		AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+		AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 			ast_cli(a->fd, "%-29s ", ast_channel_name(participant->chan));
 			ast_cli(a->fd, "%-17s", participant->u_profile.name);
 			ast_cli(a->fd, "%-17s", participant->b_profile.name);
@@ -2147,7 +2100,7 @@ static int generic_mute_unmute_helper(int mute, const char *conference, const ch
 		return -1;
 	}
 	ao2_lock(bridge);
-	AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+	AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 		if (!strncmp(user, ast_channel_name(participant->chan), strlen(user))) {
 			break;
 		}
@@ -2310,21 +2263,25 @@ static char *handle_cli_confbridge_start_record(struct ast_cli_entry *e, int cmd
 		ast_cli(a->fd, "Conference not found.\n");
 		return CLI_FAILURE;
 	}
+	ao2_lock(bridge);
 	if (conf_is_recording(bridge)) {
 		ast_cli(a->fd, "Conference is already being recorded.\n");
+		ao2_unlock(bridge);
 		ao2_ref(bridge, -1);
 		return CLI_SUCCESS;
 	}
 	if (!ast_strlen_zero(rec_file)) {
-		ao2_lock(bridge);
 		ast_copy_string(bridge->b_profile.rec_file, rec_file, sizeof(bridge->b_profile.rec_file));
-		ao2_unlock(bridge);
 	}
+
 	if (conf_start_record(bridge)) {
 		ast_cli(a->fd, "Could not start recording due to internal error.\n");
+		ao2_unlock(bridge);
 		ao2_ref(bridge, -1);
 		return CLI_FAILURE;
 	}
+	ao2_unlock(bridge);
+
 	ast_cli(a->fd, "Recording started\n");
 	ao2_ref(bridge, -1);
 	return CLI_SUCCESS;
@@ -2334,6 +2291,7 @@ static char *handle_cli_confbridge_stop_record(struct ast_cli_entry *e, int cmd,
 {
 	struct conference_bridge *bridge = NULL;
 	struct conference_bridge tmp;
+	int ret;
 
 	switch (cmd) {
 	case CLI_INIT:
@@ -2357,8 +2315,10 @@ static char *handle_cli_confbridge_stop_record(struct ast_cli_entry *e, int cmd,
 		ast_cli(a->fd, "Conference not found.\n");
 		return CLI_SUCCESS;
 	}
-	conf_stop_record(bridge);
-	ast_cli(a->fd, "Recording stopped.\n");
+	ao2_lock(bridge);
+	ret = conf_stop_record(bridge);
+	ao2_unlock(bridge);
+	ast_cli(a->fd, "Recording %sstopped.\n", ret ? "could not be " : "");
 	ao2_ref(bridge, -1);
 	return CLI_SUCCESS;
 }
@@ -2415,7 +2375,7 @@ static int action_confbridgelist(struct mansession *s, const struct message *m)
 	astman_send_listack(s, m, "Confbridge user list will follow", "start");
 
 	ao2_lock(bridge);
-	AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+	AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 		total++;
 		astman_append(s,
 			"Event: ConfbridgeList\r\n"
@@ -2483,7 +2443,7 @@ static int action_confbridgelistrooms(struct mansession *s, const struct message
 		"\r\n",
 		id_text,
 		bridge->name,
-		bridge->users,
+		bridge->activeusers,
 		bridge->markedusers,
 		bridge->locked ? "Yes" : "No"); 
 		ao2_unlock(bridge);
@@ -2598,7 +2558,7 @@ static int action_confbridgekick(struct mansession *s, const struct message *m)
 	}
 
 	ao2_lock(bridge);
-	AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+	AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 		if (!strcasecmp(ast_channel_name(participant->chan), channel)) {
 			participant->kicked = 1;
 			ast_bridge_remove(bridge->bridge, participant->chan);
@@ -2640,23 +2600,25 @@ static int action_confbridgestartrecord(struct mansession *s, const struct messa
 		return 0;
 	}
 
+	ao2_lock(bridge);
 	if (conf_is_recording(bridge)) {
 		astman_send_error(s, m, "Conference is already being recorded.");
+		ao2_unlock(bridge);
 		ao2_ref(bridge, -1);
 		return 0;
 	}
 
 	if (!ast_strlen_zero(recordfile)) {
-		ao2_lock(bridge);
 		ast_copy_string(bridge->b_profile.rec_file, recordfile, sizeof(bridge->b_profile.rec_file));
-		ao2_unlock(bridge);
 	}
 
 	if (conf_start_record(bridge)) {
 		astman_send_error(s, m, "Internal error starting conference recording.");
+		ao2_unlock(bridge);
 		ao2_ref(bridge, -1);
 		return 0;
 	}
+	ao2_unlock(bridge);
 
 	ao2_ref(bridge, -1);
 	astman_send_ack(s, m, "Conference Recording Started.");
@@ -2684,11 +2646,14 @@ static int action_confbridgestoprecord(struct mansession *s, const struct messag
 		return 0;
 	}
 
+	ao2_lock(bridge);
 	if (conf_stop_record(bridge)) {
+		ao2_unlock(bridge);
 		astman_send_error(s, m, "Internal error while stopping recording.");
 		ao2_ref(bridge, -1);
 		return 0;
 	}
+	ao2_unlock(bridge);
 
 	ao2_ref(bridge, -1);
 	astman_send_ack(s, m, "Conference Recording Stopped.");
@@ -2725,7 +2690,7 @@ static int action_confbridgesetsinglevideosrc(struct mansession *s, const struct
 
 	/* find channel and set as video src. */
 	ao2_lock(bridge);
-	AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+	AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 		if (!strncmp(channel, ast_channel_name(participant->chan), strlen(channel))) {
 			ast_bridge_set_single_src_video_mode(bridge->bridge, participant->chan);
 			break;
@@ -2779,17 +2744,17 @@ static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char
 	/* get the correct count for the type requested */
 	ao2_lock(bridge);
 	if (!strncasecmp(args.type, "parties", 7)) {
-		AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+		AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 			count++;
 		}
 	} else if (!strncasecmp(args.type, "admins", 6)) {
-		AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+		AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 			if (ast_test_flag(&participant->u_profile, USER_OPT_ADMIN)) {
 				count++;
 			}
 		}
 	} else if (!strncasecmp(args.type, "marked", 6)) {
-		AST_LIST_TRAVERSE(&bridge->users_list, participant, list) {
+		AST_LIST_TRAVERSE(&bridge->active_list, participant, list) {
 			if (ast_test_flag(&participant->u_profile, USER_OPT_MARKEDUSER)) {
 				count++;
 			}
@@ -2806,6 +2771,61 @@ static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char
 	return 0;
 }
 
+void conf_add_user_active(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu)
+{
+	AST_LIST_INSERT_TAIL(&conference_bridge->active_list, cbu, list);
+	conference_bridge->activeusers++;
+}
+
+void conf_add_user_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu)
+{
+	AST_LIST_INSERT_TAIL(&conference_bridge->active_list, cbu, list);
+	conference_bridge->activeusers++;
+	conference_bridge->markedusers++;
+}
+
+void conf_add_user_waiting(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu)
+{
+	AST_LIST_INSERT_TAIL(&conference_bridge->waiting_list, cbu, list);
+	conference_bridge->waitingusers++;
+}
+
+void conf_remove_user_active(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu)
+{
+	AST_LIST_REMOVE(&conference_bridge->active_list, cbu, list);
+	conference_bridge->activeusers--;
+}
+
+void conf_remove_user_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu)
+{
+	AST_LIST_REMOVE(&conference_bridge->active_list, cbu, list);
+	conference_bridge->activeusers--;
+	conference_bridge->markedusers--;
+}
+
+void conf_mute_only_active(struct conference_bridge *conference_bridge)
+{
+	struct conference_bridge_user *only_participant = AST_LIST_FIRST(&conference_bridge->active_list);
+
+	/* Turn on MOH/mute if the single participant is set up for it */
+	if (ast_test_flag(&only_participant->u_profile, USER_OPT_MUSICONHOLD)) {
+		only_participant->features.mute = 1;
+		if (!ast_channel_internal_bridge(only_participant->chan) || !ast_bridge_suspend(conference_bridge->bridge, only_participant->chan)) {
+			ast_moh_start(only_participant->chan, only_participant->u_profile.moh_class, NULL);
+			only_participant->playing_moh = 1;
+			if (ast_channel_internal_bridge(only_participant->chan)) {
+				ast_bridge_unsuspend(conference_bridge->bridge, only_participant->chan);
+			}
+		}
+	}
+}
+
+void conf_remove_user_waiting(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu)
+{
+	AST_LIST_REMOVE(&conference_bridge->waiting_list, cbu, list);
+	conference_bridge->waitingusers--;
+}
+
 /*! \brief Called when module is being unloaded */
 static int unload_module(void)
 {
diff --git a/apps/confbridge/conf_state.c b/apps/confbridge/conf_state.c
new file mode 100644
index 0000000000000000000000000000000000000000..1d8750918afa6b51358df7fa33b0ce950f382106
--- /dev/null
+++ b/apps/confbridge/conf_state.c
@@ -0,0 +1,70 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * This file contains functions that are used from multiple conf_state
+ * files for handling stage change behavior.
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/logger.h"
+#include "include/conf_state.h"
+#include "include/confbridge.h"
+
+void conf_invalid_event_fn(struct conference_bridge_user *cbu)
+{
+	ast_log(LOG_ERROR, "Invalid event for confbridge user '%s'\n", cbu->u_profile.name);
+}
+
+void conf_default_join_waitmarked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_waiting(cbu->conference_bridge, cbu);
+	conf_add_post_join_action(cbu, conf_handle_inactive_waitmarked);
+}
+
+void conf_default_leave_waitmarked(struct conference_bridge_user *cbu)
+{
+	conf_remove_user_waiting(cbu->conference_bridge, cbu);
+}
+
+void conf_change_state(struct conference_bridge_user *cbu, struct conference_state *newstate)
+{
+	ast_debug(1, "Changing conference '%s' state from %s to %s\n", cbu->conference_bridge->name, cbu->conference_bridge->state->name, newstate->name);
+	if (cbu->conference_bridge->state->exit) {
+		cbu->conference_bridge->state->exit(cbu);
+	}
+	cbu->conference_bridge->state = newstate;
+	if (cbu->conference_bridge->state->entry) {
+		cbu->conference_bridge->state->entry(cbu);
+	}
+}
diff --git a/apps/confbridge/conf_state_empty.c b/apps/confbridge/conf_state_empty.c
new file mode 100644
index 0000000000000000000000000000000000000000..22997ad2c6cf08d7243e7020481bf29ae32da3b6
--- /dev/null
+++ b/apps/confbridge/conf_state_empty.c
@@ -0,0 +1,86 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling for the EMPTY state
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+#include "asterisk/devicestate.h"
+#include "include/confbridge.h"
+#include "include/conf_state.h"
+
+static void join_unmarked(struct conference_bridge_user *cbu);
+static void join_waitmarked(struct conference_bridge_user *cbu);
+static void join_marked(struct conference_bridge_user *cbu);
+static void transition_to_empty(struct conference_bridge_user *cbu);
+
+struct conference_state STATE_EMPTY = {
+	.name = "EMPTY",
+	.join_unmarked = join_unmarked,
+	.join_waitmarked = join_waitmarked,
+	.join_marked = join_marked,
+	.entry = transition_to_empty,
+};
+
+struct conference_state *CONF_STATE_EMPTY = &STATE_EMPTY;
+
+static void join_unmarked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_active(cbu->conference_bridge, cbu);
+	conf_handle_first_join(cbu->conference_bridge);
+	conf_add_post_join_action(cbu, conf_handle_only_unmarked);
+
+	conf_change_state(cbu, CONF_STATE_SINGLE);
+}
+
+static void join_waitmarked(struct conference_bridge_user *cbu)
+{
+	conf_default_join_waitmarked(cbu);
+	conf_handle_first_join(cbu->conference_bridge);
+
+	conf_change_state(cbu, CONF_STATE_INACTIVE);
+}
+
+static void join_marked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_marked(cbu->conference_bridge, cbu);
+	conf_handle_first_join(cbu->conference_bridge);
+	conf_add_post_join_action(cbu, conf_handle_first_marked_common);
+
+	conf_change_state(cbu, CONF_STATE_SINGLE_MARKED);
+}
+
+static void transition_to_empty(struct conference_bridge_user *cbu)
+{
+	/* Set device state to "not in use" */
+	ast_devstate_changed(AST_DEVICE_NOT_INUSE, "confbridge:%s", cbu->conference_bridge->name);
+	conf_ended(cbu->conference_bridge);
+}
diff --git a/apps/confbridge/conf_state_inactive.c b/apps/confbridge/conf_state_inactive.c
new file mode 100644
index 0000000000000000000000000000000000000000..80210fcb82ed01f6228c00bd2a2fb234cd963833
--- /dev/null
+++ b/apps/confbridge/conf_state_inactive.c
@@ -0,0 +1,80 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling for the INACTIVE state
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "include/confbridge.h"
+#include "include/conf_state.h"
+
+static void join_unmarked(struct conference_bridge_user *cbu);
+static void join_marked(struct conference_bridge_user *cbu);
+static void leave_waitmarked(struct conference_bridge_user *cbu);
+static void transition_to_inactive(struct conference_bridge_user *cbu);
+
+struct conference_state STATE_INACTIVE = {
+	.name = "INACTIVE",
+	.join_unmarked = join_unmarked,
+	.join_waitmarked = conf_default_join_waitmarked,
+	.join_marked = join_marked,
+	.leave_waitmarked = leave_waitmarked,
+	.entry = transition_to_inactive,
+};
+struct conference_state *CONF_STATE_INACTIVE = &STATE_INACTIVE;
+
+static void join_unmarked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_active(cbu->conference_bridge, cbu);
+	conf_add_post_join_action(cbu, conf_handle_only_unmarked);
+
+	conf_change_state(cbu, CONF_STATE_SINGLE);
+}
+
+static void join_marked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_marked(cbu->conference_bridge, cbu);
+	conf_handle_second_active(cbu->conference_bridge);
+
+	conf_change_state(cbu, CONF_STATE_MULTI_MARKED);
+}
+
+static void leave_waitmarked(struct conference_bridge_user *cbu)
+{
+	conf_remove_user_waiting(cbu->conference_bridge, cbu);
+	if (cbu->conference_bridge->waitingusers == 0) {
+		conf_change_state(cbu, CONF_STATE_EMPTY);
+	}
+}
+
+static void transition_to_inactive(struct conference_bridge_user *cbu)
+{
+	return;
+}
diff --git a/apps/confbridge/conf_state_multi.c b/apps/confbridge/conf_state_multi.c
new file mode 100644
index 0000000000000000000000000000000000000000..5dcd8f4129b8994308b5f5980110f98490afcee7
--- /dev/null
+++ b/apps/confbridge/conf_state_multi.c
@@ -0,0 +1,77 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling for the MULTI state
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "include/confbridge.h"
+#include "include/conf_state.h"
+
+static void join_unmarked(struct conference_bridge_user *cbu);
+static void join_marked(struct conference_bridge_user *cbu);
+static void leave_unmarked(struct conference_bridge_user *cbu);
+void transition_to_multi(struct conference_bridge_user *cbu);
+
+struct conference_state STATE_MULTI = {
+	.name = "MULTI",
+	.join_unmarked = join_unmarked,
+	.join_waitmarked = conf_default_join_waitmarked,
+	.join_marked = join_marked,
+	.leave_unmarked = leave_unmarked,
+	.leave_waitmarked = conf_default_leave_waitmarked,
+	.entry = transition_to_multi,
+};
+struct conference_state *CONF_STATE_MULTI = &STATE_MULTI;
+
+static void join_unmarked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_active(cbu->conference_bridge, cbu);
+}
+
+static void join_marked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_marked(cbu->conference_bridge, cbu);
+
+	conf_change_state(cbu, CONF_STATE_MULTI_MARKED);
+}
+
+static void leave_unmarked(struct conference_bridge_user *cbu)
+{
+	conf_remove_user_active(cbu->conference_bridge, cbu);
+	if (cbu->conference_bridge->activeusers == 1) {
+		conf_change_state(cbu, CONF_STATE_SINGLE);
+	}
+}
+
+void transition_to_multi(struct conference_bridge_user *cbu)
+{
+	return;
+}
diff --git a/apps/confbridge/conf_state_multi_marked.c b/apps/confbridge/conf_state_multi_marked.c
new file mode 100644
index 0000000000000000000000000000000000000000..69850b18dc4bdc3892a382b8cf7973871d427ec5
--- /dev/null
+++ b/apps/confbridge/conf_state_multi_marked.c
@@ -0,0 +1,187 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling for the MULTI_MARKED state
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+#include "asterisk/utils.h"
+#include "asterisk/linkedlists.h"
+#include "include/confbridge.h"
+#include "asterisk/musiconhold.h"
+#include "include/conf_state.h"
+
+static void join_active(struct conference_bridge_user *cbu);
+static void join_marked(struct conference_bridge_user *cbu);
+static void leave_active(struct conference_bridge_user *cbu);
+static void leave_marked(struct conference_bridge_user *cbu);
+static void transition_to_marked(struct conference_bridge_user *cbu);
+
+static struct conference_state STATE_MULTI_MARKED = {
+	.name = "MULTI_MARKED",
+	.join_unmarked = join_active,
+	.join_waitmarked = join_active,
+	.join_marked = join_marked,
+	.leave_unmarked = leave_active,
+	.leave_waitmarked = leave_active,
+	.leave_marked = leave_marked,
+	.entry = transition_to_marked,
+};
+struct conference_state *CONF_STATE_MULTI_MARKED = &STATE_MULTI_MARKED;
+
+static void join_active(struct conference_bridge_user *cbu)
+{
+	conf_add_user_active(cbu->conference_bridge, cbu);
+}
+
+static void join_marked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_marked(cbu->conference_bridge, cbu);
+}
+
+static void leave_active(struct conference_bridge_user *cbu)
+{
+	conf_remove_user_active(cbu->conference_bridge, cbu);
+	if (cbu->conference_bridge->activeusers == 1) {
+		conf_change_state(cbu, CONF_STATE_SINGLE_MARKED);
+	}
+}
+
+static void leave_marked(struct conference_bridge_user *cbu)
+{
+	struct conference_bridge_user *cbu_iter;
+
+	conf_remove_user_marked(cbu->conference_bridge, cbu);
+
+	if (cbu->conference_bridge->markedusers == 0) {
+		/* Play back the audio prompt saying the leader has left the conference */
+		if (!ast_test_flag(&cbu->u_profile, USER_OPT_QUIET)) {
+			ao2_unlock(cbu->conference_bridge);
+			ast_autoservice_start(cbu->chan);
+			play_sound_file(cbu->conference_bridge,
+				conf_get_sound(CONF_SOUND_LEADER_HAS_LEFT, cbu->b_profile.sounds));
+			ast_autoservice_stop(cbu->chan);
+			ao2_lock(cbu->conference_bridge);
+		}
+
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&cbu->conference_bridge->active_list, cbu_iter, list) {
+			/* Kick ENDMARKED cbu_iters */
+			if (ast_test_flag(&cbu_iter->u_profile, USER_OPT_ENDMARKED)) {
+				AST_LIST_REMOVE_CURRENT(list);
+				cbu_iter->conference_bridge->activeusers--;
+				cbu_iter->kicked = 1;
+				ast_bridge_remove(cbu_iter->conference_bridge->bridge, cbu_iter->chan);
+			} else if (ast_test_flag(&cbu_iter->u_profile, USER_OPT_WAITMARKED) &&
+					!ast_test_flag(&cbu_iter->u_profile, USER_OPT_MARKEDUSER)) {
+				AST_LIST_REMOVE_CURRENT(list);
+				cbu_iter->conference_bridge->activeusers--;
+				AST_LIST_INSERT_TAIL(&cbu_iter->conference_bridge->waiting_list, cbu_iter, list);
+				cbu_iter->conference_bridge->waitingusers++;
+				/* Handle muting/moh of cbu_iter if necessary */
+				if (ast_test_flag(&cbu_iter->u_profile, USER_OPT_MUSICONHOLD)) {
+				   cbu_iter->features.mute = 1;
+					if (!ast_bridge_suspend(cbu_iter->conference_bridge->bridge, cbu_iter->chan)) {
+						ast_moh_start(cbu_iter->chan, cbu_iter->u_profile.moh_class, NULL);
+						cbu_iter->playing_moh = 1;
+						ast_bridge_unsuspend(cbu_iter->conference_bridge->bridge, cbu_iter->chan);
+					}
+				}
+			}
+		}
+		AST_LIST_TRAVERSE_SAFE_END;
+	}
+
+	switch (cbu->conference_bridge->activeusers) {
+	case 0:
+		/* Implies markedusers == 0 */
+		switch (cbu->conference_bridge->waitingusers) {
+		case 0:
+			conf_change_state(cbu, CONF_STATE_EMPTY);
+			break;
+		default:
+			conf_change_state(cbu, CONF_STATE_INACTIVE);
+			break;
+		}
+		break;
+	case 1:
+		switch (cbu->conference_bridge->markedusers) {
+		case 0:
+			conf_change_state(cbu, CONF_STATE_SINGLE);
+			break;
+		case 1:
+			/* XXX I seem to remember doing this for a reason, but right now it escapes me
+			 * how we could possibly ever have a waiting user while we have a marked user */
+			switch (cbu->conference_bridge->waitingusers) {
+			case 0:
+				conf_change_state(cbu, CONF_STATE_SINGLE_MARKED);
+				break;
+			case 1: break; /* Stay in marked */
+			}
+			break;
+		}
+		break;
+	default:
+		switch (cbu->conference_bridge->markedusers) {
+		case 0:
+			conf_change_state(cbu, CONF_STATE_MULTI);
+			break;
+		default: break; /* Stay in marked */
+		}
+	}
+}
+
+static void transition_to_marked(struct conference_bridge_user *cbu)
+{
+	struct conference_bridge_user *cbu_iter;
+
+	/* Play the audio file stating they are going to be placed into the conference */
+	if (cbu->conference_bridge->markedusers == 1 && ast_test_flag(&cbu->u_profile, USER_OPT_MARKEDUSER)) {
+		conf_handle_first_marked_common(cbu);
+	}
+
+	/* Move all waiting users to active, stopping MOH and umuting if necessary */
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&cbu->conference_bridge->waiting_list, cbu_iter, list) {
+		AST_LIST_REMOVE_CURRENT(list);
+		cbu->conference_bridge->waitingusers--;
+		AST_LIST_INSERT_TAIL(&cbu->conference_bridge->active_list, cbu_iter, list);
+		cbu->conference_bridge->activeusers++;
+		if (cbu_iter->playing_moh && !ast_bridge_suspend(cbu->conference_bridge->bridge, cbu_iter->chan)) {
+			cbu_iter->playing_moh = 0;
+			ast_moh_stop(cbu_iter->chan);
+			ast_bridge_unsuspend(cbu->conference_bridge->bridge, cbu_iter->chan);
+		}
+		/* only unmute them if they are not supposed to start muted */
+		if (!ast_test_flag(&cbu_iter->u_profile, USER_OPT_STARTMUTED)) {
+			cbu_iter->features.mute = 0;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+}
diff --git a/apps/confbridge/conf_state_single.c b/apps/confbridge/conf_state_single.c
new file mode 100644
index 0000000000000000000000000000000000000000..806ed637bec65d117e277bf436753f7f9570dc33
--- /dev/null
+++ b/apps/confbridge/conf_state_single.c
@@ -0,0 +1,84 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling for the SINGLE state
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "include/confbridge.h"
+#include "include/conf_state.h"
+
+static void join_unmarked(struct conference_bridge_user *cbu);
+static void join_marked(struct conference_bridge_user *cbu);
+static void leave_unmarked(struct conference_bridge_user *cbu);
+static void transition_to_single(struct conference_bridge_user *cbu);
+
+struct conference_state STATE_SINGLE = {
+	.name = "SINGLE",
+	.join_unmarked = join_unmarked,
+	.join_waitmarked = conf_default_join_waitmarked,
+	.join_marked = join_marked,
+	.leave_unmarked = leave_unmarked,
+	.leave_waitmarked = conf_default_leave_waitmarked,
+	.entry = transition_to_single,
+};
+struct conference_state *CONF_STATE_SINGLE = &STATE_SINGLE;
+
+static void join_unmarked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_active(cbu->conference_bridge, cbu);
+	conf_handle_second_active(cbu->conference_bridge);
+
+	conf_change_state(cbu, CONF_STATE_MULTI);
+}
+
+static void join_marked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_marked(cbu->conference_bridge, cbu);
+	conf_handle_second_active(cbu->conference_bridge);
+
+	conf_change_state(cbu, CONF_STATE_MULTI_MARKED);
+}
+
+static void leave_unmarked(struct conference_bridge_user *cbu)
+{
+	conf_remove_user_active(cbu->conference_bridge, cbu);
+
+	if (cbu->conference_bridge->waitingusers) {
+		conf_change_state(cbu, CONF_STATE_INACTIVE);
+	} else {
+		conf_change_state(cbu, CONF_STATE_EMPTY);
+	}
+}
+
+static void transition_to_single(struct conference_bridge_user *cbu)
+{
+	conf_mute_only_active(cbu->conference_bridge);
+}
diff --git a/apps/confbridge/conf_state_single_marked.c b/apps/confbridge/conf_state_single_marked.c
new file mode 100644
index 0000000000000000000000000000000000000000..a7ac57816ea9f22cb796b79622fc19785f7285b3
--- /dev/null
+++ b/apps/confbridge/conf_state_single_marked.c
@@ -0,0 +1,79 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling for the SINGLE_MARKED state
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "include/confbridge.h"
+#include "include/conf_state.h"
+
+static void join_active(struct conference_bridge_user *cbu);
+static void join_marked(struct conference_bridge_user *cbu);
+static void leave_marked(struct conference_bridge_user *cbu);
+static void transition_to_single_marked(struct conference_bridge_user *cbu);
+
+struct conference_state STATE_SINGLE_MARKED = {
+	.name = "SINGLE_MARKED",
+	.join_unmarked = join_active,
+	.join_waitmarked = join_active,
+	.join_marked = join_marked,
+	.leave_marked = leave_marked,
+	.entry = transition_to_single_marked,
+};
+struct conference_state *CONF_STATE_SINGLE_MARKED = &STATE_SINGLE_MARKED;
+
+static void join_active(struct conference_bridge_user *cbu)
+{
+	conf_add_user_active(cbu->conference_bridge, cbu);
+	conf_handle_second_active(cbu->conference_bridge);
+
+	conf_change_state(cbu, CONF_STATE_MULTI_MARKED);
+}
+
+static void join_marked(struct conference_bridge_user *cbu)
+{
+	conf_add_user_marked(cbu->conference_bridge, cbu);
+	conf_handle_second_active(cbu->conference_bridge);
+
+	conf_change_state(cbu, CONF_STATE_MULTI_MARKED);
+}
+
+static void leave_marked(struct conference_bridge_user *cbu)
+{
+	conf_remove_user_marked(cbu->conference_bridge, cbu);
+
+	conf_change_state(cbu, CONF_STATE_EMPTY);
+}
+
+static void transition_to_single_marked(struct conference_bridge_user *cbu)
+{
+	conf_mute_only_active(cbu->conference_bridge);
+}
diff --git a/apps/confbridge/include/conf_state.h b/apps/confbridge/include/conf_state.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a25850958248d6d944e5615aff32a3c627f9332
--- /dev/null
+++ b/apps/confbridge/include/conf_state.h
@@ -0,0 +1,95 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Terry Wilson
+ *
+ * Terry Wilson <twilson@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.
+ *
+ * Please follow coding guidelines
+ * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
+ */
+
+/*! \file
+ *
+ * \brief Confbridge state handling
+ *
+ * \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
+ *
+ * See https://wiki.asterisk.org/wiki/display/AST/Confbridge+state+changes for
+ * a more complete description of how conference states work.
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#ifndef _CONF_STATE_H_
+#define _CONF_STATE_H_
+
+struct conference_state;
+struct conference_bridge;
+struct conference_bridge_user;
+
+typedef void (*conference_event_fn)(struct conference_bridge_user *cbu);
+typedef void (*conference_entry_fn)(struct conference_bridge_user *cbu);
+typedef void (*conference_exit_fn)(struct conference_bridge_user *cbu);
+
+/*! \brief A conference state object to hold the various state callback functions */
+struct conference_state {
+	const char *name;
+	conference_event_fn join_unmarked;    /*!< Handle an unmarked join event */
+	conference_event_fn join_waitmarked;  /*!< Handle a waitmarked join event */
+	conference_event_fn join_marked;      /*!< Handle a marked join event */
+	conference_event_fn leave_unmarked;   /*!< Handle an unmarked leave event */
+	conference_event_fn leave_waitmarked; /*!< Handle a waitmarked leave event */
+	conference_event_fn leave_marked;     /*!< Handle a marked leave event */
+	conference_entry_fn entry;            /*!< Function to handle entry to a state */
+	conference_exit_fn exit;              /*!< Function to handle exiting from a state */
+};
+
+/*! \brief Conference state with no active or waiting users */
+extern struct conference_state *CONF_STATE_EMPTY;
+
+/*! \brief Conference state with only waiting users */
+extern struct conference_state *CONF_STATE_INACTIVE;
+
+/*! \brief Conference state with only a single unmarked active user */
+extern struct conference_state *CONF_STATE_SINGLE;
+
+/*! \brief Conference state with only a single marked active user */
+extern struct conference_state *CONF_STATE_SINGLE_MARKED;
+
+/*! \brief Conference state with multiple active users, but no marked users */
+extern struct conference_state *CONF_STATE_MULTI;
+
+/*! \brief Conference state with multiple active users and at least one marked user */
+extern struct conference_state *CONF_STATE_MULTI_MARKED;
+
+/*! \brief Execute conference state transition because of a user action
+ * \param cbu The user that joined/left
+ * \param newstate The state to transition to
+ */
+void conf_change_state(struct conference_bridge_user *cbu, struct conference_state *newstate);
+
+/* Common event handlers shared between different states */
+
+/*! \brief Logic to execute every time a waitmarked user joins an unmarked conference */
+void conf_default_join_waitmarked(struct conference_bridge_user *cbu);
+
+/*! \brief Logic to execute every time a waitmarked user leaves an unmarked conference */
+void conf_default_leave_waitmarked(struct conference_bridge_user *cbu);
+
+/*! \brief A handler for join/leave events that are invalid in a particular state */
+void conf_invalid_event_fn(struct conference_bridge_user *cbu);
+
+#endif
diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h
index 54a9af32999c3b2d05f3bc804f2f2bf15b11b96f..0891f5d4efd652222d7415230e2c1b3ac22de469 100644
--- a/apps/confbridge/include/confbridge.h
+++ b/apps/confbridge/include/confbridge.h
@@ -28,6 +28,7 @@
 #include "asterisk/channel.h"
 #include "asterisk/bridging.h"
 #include "asterisk/bridging_features.h"
+#include "conf_state.h"
 
 /* Maximum length of a conference bridge name */
 #define MAX_CONF_NAME 32
@@ -201,17 +202,28 @@ struct bridge_profile {
 /*! \brief The structure that represents a conference bridge */
 struct conference_bridge {
 	char name[MAX_CONF_NAME];                                         /*!< Name of the conference bridge */
+	struct conference_state *state;                                   /*!< Conference state information */
 	struct ast_bridge *bridge;                                        /*!< Bridge structure doing the mixing */
 	struct bridge_profile b_profile;                                  /*!< The Bridge Configuration Profile */
-	unsigned int users;                                               /*!< Number of users present */
+	unsigned int activeusers;                                         /*!< Number of active users present */
 	unsigned int markedusers;                                         /*!< Number of marked users present */
+	unsigned int waitingusers;                                        /*!< Number of waiting users present */
 	unsigned int locked:1;                                            /*!< Is this conference bridge locked? */
 	unsigned int muted:1;                                            /*!< Is this conference bridge muted? */
+	unsigned int record_state:2;                                      /*!< Whether recording is started, stopped, or should exit */
 	struct ast_channel *playback_chan;                                /*!< Channel used for playback into the conference bridge */
 	struct ast_channel *record_chan;                                  /*!< Channel used for recording the conference */
 	pthread_t record_thread;                                          /*!< The thread the recording chan lives in */
 	ast_mutex_t playback_lock;                                        /*!< Lock used for playback channel */
-	AST_LIST_HEAD_NOLOCK(, conference_bridge_user) users_list;        /*!< List of users participating in the conference bridge */
+	ast_mutex_t record_lock;                                          /*!< Lock used for the record thread */
+	ast_cond_t record_cond;                                           /*!< Recording condition variable */
+	AST_LIST_HEAD_NOLOCK(, conference_bridge_user) active_list;       /*!< List of users participating in the conference bridge */
+	AST_LIST_HEAD_NOLOCK(, conference_bridge_user) waiting_list;      /*!< List of users waiting to join the conference bridge */
+};
+
+struct post_join_action {
+	int (*func)(struct conference_bridge_user *);
+	AST_LIST_ENTRY(post_join_action) list;
 };
 
 /*! \brief The structure that represents a conference bridge user */
@@ -226,6 +238,7 @@ struct conference_bridge_user {
 	struct ast_bridge_tech_optimizations tech_args; /*!< Bridge technology optimizations for talk detection */
 	unsigned int kicked:1;                       /*!< User has been kicked from the conference */
 	unsigned int playing_moh:1;                  /*!< MOH is currently being played to the user */
+	AST_LIST_HEAD_NOLOCK(, post_join_action) post_join_list; /*!< List of sounds to play after joining */;
 	AST_LIST_ENTRY(conference_bridge_user) list; /*!< Linked list information */
 };
 
@@ -328,4 +341,103 @@ int conf_handle_dtmf(
 const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds *custom_sounds);
 
 int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value);
+
+/*!
+ * \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
+ */
+int play_sound_file(struct conference_bridge *conference_bridge, const char *filename);
+
+/*! \brief Callback to be called when the conference has become empty
+ * \param conference_bridge The conference bridge
+ */
+void conf_ended(struct conference_bridge *conference_bridge);
+
+/*! \brief Attempt to mute/play MOH to the only user in the conference if they require it
+ * \param conference_bridge A conference bridge containing a single user
+ */
+void conf_mute_only_active(struct conference_bridge *conference_bridge);
+
+/*! \brief Callback to execute any time we transition from zero to one marked users
+ * \param cbu The first marked user joining the conference
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int conf_handle_first_marked_common(struct conference_bridge_user *cbu);
+
+/*! \brief Callback to execute any time we transition from zero to one active users
+ * \param conference_bridge The conference bridge with a single active user joined
+ * \retval 0 success
+ * \retval -1 failure
+ */
+void conf_handle_first_join(struct conference_bridge *conference_bridge);
+
+/*! \brief Handle actions every time a waitmarked user joins w/o a marked user present
+ * \param cbu The waitmarked user
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int conf_handle_inactive_waitmarked(struct conference_bridge_user *cbu);
+
+/*! \brief Handle actions whenever an unmarked user joins an inactive conference
+ * \note These actions seem like they could apply just as well to a marked user
+ * and possibly be made to happen any time transitioning to a single state.
+ *
+ * \param cbu The unmarked user
+ */
+int conf_handle_only_unmarked(struct conference_bridge_user *cbu);
+
+/*! \brief Handle when a conference moves to having more than one active participant
+ * \param conference_bridge The conference bridge with more than one active participant
+ */
+void conf_handle_second_active(struct conference_bridge *conference_bridge);
+
+/*! \brief Add a conference bridge user as an unmarked active user of the conference
+ * \param conference_bridge The conference bridge to add the user to
+ * \param cbu The conference bridge user to add to the conference
+ */
+void conf_add_user_active(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu);
+
+/*! \brief Add a conference bridge user as a marked active user of the conference
+ * \param conference_bridge The conference bridge to add the user to
+ * \param cbu The conference bridge user to add to the conference
+ */
+void conf_add_user_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu);
+
+/*! \brief Add a conference bridge user as an waiting user of the conference
+ * \param conference_bridge The conference bridge to add the user to
+ * \param cbu The conference bridge user to add to the conference
+ */
+void conf_add_user_waiting(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu);
+
+/*! \brief Remove a conference bridge user from the unmarked active conference users in the conference
+ * \param conference_bridge The conference bridge to remove the user from
+ * \param cbu The conference bridge user to remove from the conference
+ */
+void conf_remove_user_active(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu);
+
+/*! \brief Remove a conference bridge user from the marked active conference users in the conference
+ * \param conference_bridge The conference bridge to remove the user from
+ * \param cbu The conference bridge user to remove from the conference
+ */
+void conf_remove_user_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu);
+
+/*! \brief Remove a conference bridge user from the waiting conference users in the conference
+ * \param conference_bridge The conference bridge to remove the user from
+ * \param cbu The conference bridge user to remove from the conference
+ */
+void conf_remove_user_waiting(struct conference_bridge *conference_bridge, struct conference_bridge_user *cbu);
+
+/*! \brief Queue a function to run with the given conference bridge user as an argument once the state transition is complete
+ * \param cbu The conference bridge user to pass to the function
+ * \param func The function to queue
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int conf_add_post_join_action(struct conference_bridge_user *cbu, int (*func)(struct conference_bridge_user *cbu));
 #endif