summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/app_confbridge.c734
-rw-r--r--apps/confbridge/conf_state.c70
-rw-r--r--apps/confbridge/conf_state_empty.c86
-rw-r--r--apps/confbridge/conf_state_inactive.c80
-rw-r--r--apps/confbridge/conf_state_multi.c77
-rw-r--r--apps/confbridge/conf_state_multi_marked.c187
-rw-r--r--apps/confbridge/conf_state_single.c84
-rw-r--r--apps/confbridge/conf_state_single_marked.c79
-rw-r--r--apps/confbridge/include/conf_state.h95
-rw-r--r--apps/confbridge/include/confbridge.h116
10 files changed, 1249 insertions, 359 deletions
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index 480666e98..c560ecec2 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 000000000..1d8750918
--- /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 000000000..22997ad2c
--- /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 000000000..80210fcb8
--- /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 000000000..5dcd8f412
--- /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 000000000..69850b18d
--- /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 000000000..806ed637b
--- /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 000000000..a7ac57816
--- /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 000000000..8a2585095
--- /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 54a9af329..0891f5d4e 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