Resolve issues in ConfBridge regarding marked, waitmarked, and unmarked users
[asterisk/asterisk.git] / apps / app_confbridge.c
index 480666e..c560ece 100644 (file)
@@ -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)
 {