diff options
Diffstat (limited to 'apps/app_confbridge.c')
-rw-r--r-- | apps/app_confbridge.c | 734 |
1 files changed, 377 insertions, 357 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) { |