Index: ./apps/app_confbridge.c =================================================================== --- ./apps/app_confbridge.c (revision 371641) +++ ./apps/app_confbridge.c (working copy) @@ -273,10 +273,16 @@ /* 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, @@ -390,136 +396,166 @@ 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; } @@ -580,10 +616,10 @@ 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, @@ -602,7 +638,7 @@ "")) { return -1; } - if (ast_say_number(conference_bridge_user->chan, conference_bridge->users - 1, "", conference_bridge_user->chan->language, NULL)) { + if (ast_say_number(conference_bridge_user->chan, conference_bridge->activeusers - 1, "", conference_bridge_user->chan->language, NULL)) { return -1; } if (ast_stream_and_wait(conference_bridge_user->chan, @@ -612,7 +648,7 @@ } } 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); } } @@ -627,16 +663,13 @@ * \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) @@ -651,7 +684,7 @@ 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; } @@ -696,7 +729,7 @@ /* 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; } @@ -709,183 +742,177 @@ } /*! - * \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 = conference_bridge->playback_chan->tech->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) +static int handle_conf_user_leave(struct conference_bridge_user *cbu) { - /* 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; + 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); + handler(cbu); - /* 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); - } + 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 = conference_bridge->playback_chan->tech->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 @@ -898,8 +925,8 @@ 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)); @@ -913,7 +940,7 @@ 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 */ @@ -962,9 +989,22 @@ /* 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); @@ -977,46 +1017,41 @@ 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 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); + 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; } - } 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; @@ -1033,73 +1068,10 @@ { 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); } @@ -1177,16 +1149,7 @@ 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); } @@ -1364,6 +1327,7 @@ 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 @@ -1663,7 +1627,7 @@ } 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, @@ -1901,7 +1865,7 @@ 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], participant->chan->name, strlen(participant->chan->name))) { break; } @@ -1942,7 +1906,7 @@ 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); @@ -1959,7 +1923,7 @@ ast_cli(a->fd, "Channel User Profile Bridge Profile Menu\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 ", participant->chan->name); ast_cli(a->fd, "%-17s", participant->u_profile.name); ast_cli(a->fd, "%-17s", participant->b_profile.name); @@ -2019,7 +1983,7 @@ return -1; } ao2_lock(bridge); - AST_LIST_TRAVERSE(&bridge->users_list, participant, list) { + AST_LIST_TRAVERSE(&bridge->active_list, participant, list) { if (!strncmp(user, participant->chan->name, strlen(user))) { break; } @@ -2182,21 +2146,25 @@ 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; @@ -2206,6 +2174,7 @@ { struct conference_bridge *bridge = NULL; struct conference_bridge tmp; + int ret; switch (cmd) { case CLI_INIT: @@ -2229,8 +2198,10 @@ 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; } @@ -2287,7 +2258,7 @@ 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" @@ -2355,7 +2326,7 @@ "\r\n", id_text, bridge->name, - bridge->users, + bridge->activeusers, bridge->markedusers, bridge->locked ? "Yes" : "No"); ao2_unlock(bridge); @@ -2470,7 +2441,7 @@ } ao2_lock(bridge); - AST_LIST_TRAVERSE(&bridge->users_list, participant, list) { + AST_LIST_TRAVERSE(&bridge->active_list, participant, list) { if (!strcasecmp(participant->chan->name, channel)) { participant->kicked = 1; ast_bridge_remove(bridge->bridge, participant->chan); @@ -2512,23 +2483,25 @@ 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."); @@ -2556,11 +2529,14 @@ 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."); @@ -2597,7 +2573,7 @@ /* 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, participant->chan->name, strlen(channel))) { ast_bridge_set_single_src_video_mode(bridge->bridge, participant->chan); break; @@ -2651,17 +2627,17 @@ /* 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++; } @@ -2677,6 +2653,61 @@ 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 (!only_participant->chan->bridge || !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 (only_participant->chan->bridge) { + 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) { Index: ./apps/confbridge/conf_state.c =================================================================== --- ./apps/confbridge/conf_state.c (revision 0) +++ ./apps/confbridge/conf_state.c (revision 0) @@ -0,0 +1,70 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * This file contains functions that are used from multiple conf_state + * files for handling stage change behavior. + * + * \ingroup applications + */ + +/*** MODULEINFO + core + ***/ + +#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); + } +} Property changes on: confbridge/conf_state.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/conf_state_empty.c =================================================================== --- ./apps/confbridge/conf_state_empty.c (revision 0) +++ ./apps/confbridge/conf_state_empty.c (revision 0) @@ -0,0 +1,86 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * \ingroup applications + */ + +/*** MODULEINFO + core + ***/ + +#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); +} Property changes on: confbridge/conf_state_empty.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/conf_state_inactive.c =================================================================== --- ./apps/confbridge/conf_state_inactive.c (revision 0) +++ ./apps/confbridge/conf_state_inactive.c (revision 0) @@ -0,0 +1,80 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * \ingroup applications + */ + +/*** MODULEINFO + core + ***/ + +#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; +} Property changes on: confbridge/conf_state_inactive.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/conf_state_multi.c =================================================================== --- ./apps/confbridge/conf_state_multi.c (revision 0) +++ ./apps/confbridge/conf_state_multi.c (revision 0) @@ -0,0 +1,77 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * \ingroup applications + */ + +/*** MODULEINFO + core + ***/ + +#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; +} Property changes on: confbridge/conf_state_multi.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/conf_state_multi_marked.c =================================================================== --- ./apps/confbridge/conf_state_multi_marked.c (revision 0) +++ ./apps/confbridge/conf_state_multi_marked.c (revision 0) @@ -0,0 +1,187 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * \ingroup applications + */ + +/*** MODULEINFO + core + ***/ + +#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; +} Property changes on: confbridge/conf_state_multi_marked.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/conf_state_single.c =================================================================== --- ./apps/confbridge/conf_state_single.c (revision 0) +++ ./apps/confbridge/conf_state_single.c (revision 0) @@ -0,0 +1,84 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * \ingroup applications + */ + +/*** MODULEINFO + core + ***/ + +#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); +} Property changes on: confbridge/conf_state_single.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/conf_state_single_marked.c =================================================================== --- ./apps/confbridge/conf_state_single_marked.c (revision 0) +++ ./apps/confbridge/conf_state_single_marked.c (revision 0) @@ -0,0 +1,79 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * \ingroup applications + */ + +/*** MODULEINFO + core + ***/ + +#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); +} Property changes on: confbridge/conf_state_single_marked.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/include/conf_state.h =================================================================== --- ./apps/confbridge/include/conf_state.h (revision 0) +++ ./apps/confbridge/include/conf_state.h (revision 0) @@ -0,0 +1,95 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Terry Wilson + * + * Terry Wilson + * + * 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 \endverbatim + * + * See https://wiki.asterisk.org/wiki/display/AST/Confbridge+state+changes for + * a more complete description of how conference states work. + */ + +/*** MODULEINFO + core + ***/ + +#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 Property changes on: confbridge/include/conf_state.h ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Index: ./apps/confbridge/include/confbridge.h =================================================================== --- ./apps/confbridge/include/confbridge.h (revision 371641) +++ ./apps/confbridge/include/confbridge.h (working copy) @@ -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 @@ -197,16 +198,27 @@ /*! \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 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 */ @@ -221,6 +233,7 @@ 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 */ }; @@ -323,4 +336,103 @@ 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