Newer
Older
ast_log(LOG_ERROR, "Conference bridge '%s' could not be created.\n", name);
return NULL;
}
/* Set the internal sample rate on the bridge from the bridge profile */
ast_bridge_set_internal_sample_rate(conference_bridge->bridge, conference_bridge->b_profile.internal_sample_rate);
/* Set the internal mixing interval on the bridge from the bridge profile */
ast_bridge_set_mixing_interval(conference_bridge->bridge, conference_bridge->b_profile.mix_interval);
if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
ast_bridge_set_talker_src_video_mode(conference_bridge->bridge);
}
/* Setup lock for playback channel */
ast_mutex_init(&conference_bridge->playback_lock);
/* Link it into the conference bridges container */
ao2_link(conference_bridges, 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);
}
ao2_unlock(conference_bridges);
/* Setup conference bridge user parameters */
conference_bridge_user->conference_bridge = conference_bridge;
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++;
}
/* Set the device state for this conference */
if (conference_bridge->users == 1) {
ast_devstate_changed(AST_DEVICE_INUSE, "confbridge:%s", conference_bridge->name);
}
/* 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);
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;
}
if (post_join_unmarked(conference_bridge, conference_bridge_user)) {
ao2_unlock(conference_bridge);
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);
}
return conference_bridge;
}
/*!
* \brief Leave a conference bridge
*
* \param conference_bridge The conference bridge to leave
* \param conference_bridge_user The conference bridge user structure
*
*/
static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
{
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);
}
/* 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);
}
/*!
* \internal
* \brief allocates playback chan on a channel
* \pre expects conference to be locked before calling this function
static int alloc_playback_chan(struct conference_bridge *conference_bridge)
int cause;
struct ast_format_cap *cap;
struct ast_format tmpfmt;
if (conference_bridge->playback_chan) {
return 0;
}
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->playback_chan = ast_request("Bridge", cap, NULL, "", &cause))) {
cap = ast_format_cap_destroy(cap);
return -1;
}
cap = ast_format_cap_destroy(cap);
ast_channel_internal_bridge_set(conference_bridge->playback_chan, conference_bridge->bridge);
if (ast_call(conference_bridge->playback_chan, "", 0)) {
ast_hangup(conference_bridge->playback_chan);
conference_bridge->playback_chan = NULL;
return -1;
}
ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference_bridge->name);
return 0;
}
static int play_sound_helper(struct conference_bridge *conference_bridge, const char *filename, int say_number)
{
struct ast_channel *underlying_channel;
/* Do not waste resources trying to play files that do not exist */
Matthew Jordan
committed
if (!ast_strlen_zero(filename) && !ast_fileexists(filename, NULL, NULL)) {
ast_log(LOG_WARNING, "File %s does not exist in any format\n", !ast_strlen_zero(filename) ? filename : "<unknown>");
ast_mutex_lock(&conference_bridge->playback_lock);
if (!(conference_bridge->playback_chan)) {
if (alloc_playback_chan(conference_bridge)) {
ast_mutex_unlock(&conference_bridge->playback_lock);
return -1;
}
underlying_channel = ast_channel_tech(conference_bridge->playback_chan)->bridged_channel(conference_bridge->playback_chan, NULL);
} else {
/* Channel was already available so we just need to add it back into the bridge */
underlying_channel = ast_channel_tech(conference_bridge->playback_chan)->bridged_channel(conference_bridge->playback_chan, NULL);
ast_bridge_impart(conference_bridge->bridge, underlying_channel, NULL, NULL, 0);
}
/* The channel is all under our control, in goes the prompt */
if (!ast_strlen_zero(filename)) {
ast_stream_and_wait(conference_bridge->playback_chan, filename, "");
Matthew Jordan
committed
} else if (say_number >= 0) {
ast_say_number(conference_bridge->playback_chan, say_number, "", ast_channel_language(conference_bridge->playback_chan), NULL);
ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", ast_channel_name(underlying_channel), conference_bridge->bridge);
ast_bridge_depart(conference_bridge->bridge, underlying_channel);
ast_mutex_unlock(&conference_bridge->playback_lock);
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)
Matthew Jordan
committed
return play_sound_helper(conference_bridge, filename, -1);
/*!
* \brief Play number into the conference bridge
*
* \param conference_bridge The conference bridge to say the number into
* \param number to say
*
* \retval 0 success
* \retval -1 failure
*/
static int play_sound_number(struct conference_bridge *conference_bridge, int say_number)
{
return play_sound_helper(conference_bridge, NULL, say_number);
}
static void conf_handle_talker_destructor(void *pvt_data)
{
ast_free(pvt_data);
}
static void conf_handle_talker_cb(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data)
{
char *conf_name = pvt_data;
int talking;
switch (bridge_channel->state) {
case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
talking = 1;
break;
case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
talking = 0;
break;
default:
return; /* uhh this shouldn't happen, but bail if it does. */
/* notify AMI someone is has either started or stopped talking */
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when a conference participant has started or stopped talking.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
<parameter name="TalkingStatus">
<enumlist>
<enum name="on"/>
<enum name="off"/>
</enumlist>
</parameter>
</syntax>
</managerEventInstance>
***/
ast_manager_event(bridge_channel->chan, EVENT_FLAG_CALL, "ConfbridgeTalking",
"Channel: %s\r\n"
"Uniqueid: %s\r\n"
"Conference: %s\r\n"
"TalkingStatus: %s\r\n",
ast_channel_name(bridge_channel->chan), ast_channel_uniqueid(bridge_channel->chan), conf_name, talking ? "on" : "off");
static int conf_get_pin(struct ast_channel *chan, struct conference_bridge_user *conference_bridge_user)
{
char pin_guess[MAX_PIN+1] = { 0, };
const char *pin = conference_bridge_user->u_profile.pin;
char *tmp = pin_guess;
int i, res;
unsigned int len = MAX_PIN ;
/* give them three tries to get the pin right */
for (i = 0; i < 3; i++) {
if (ast_app_getdata(chan,
conf_get_sound(CONF_SOUND_GET_PIN, conference_bridge_user->b_profile.sounds),
tmp, len, 0) >= 0) {
if (!strcasecmp(pin, pin_guess)) {
return 0;
}
ast_streamfile(chan,
conf_get_sound(CONF_SOUND_INVALID_PIN, conference_bridge_user->b_profile.sounds),
res = ast_waitstream(chan, AST_DIGIT_ANY);
if (res > 0) {
/* Account for digit already read during ivalid pin playback
* resetting pin buf. */
pin_guess[0] = res;
pin_guess[1] = '\0';
tmp = pin_guess + 1;
len = MAX_PIN - 1;
/* reset pin buf as empty buffer. */
tmp = pin_guess;
len = MAX_PIN;
static int conf_rec_name(struct conference_bridge_user *user, const char *conf_name)
{
char destdir[PATH_MAX];
int res;
int duration = 20;
snprintf(destdir, sizeof(destdir), "%s/confbridge", ast_config_AST_SPOOL_DIR);
if (ast_mkdir(destdir, 0777) != 0) {
ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", destdir, strerror(errno));
return -1;
}
snprintf(user->name_rec_location, sizeof(user->name_rec_location),
"%s/confbridge-name-%s-%s", destdir,
conf_name, ast_channel_uniqueid(user->chan));
res = ast_play_and_record(user->chan,
"vm-rec-name",
user->name_rec_location,
10,
"sln",
&duration,
ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE),
0,
NULL);
if (res == -1) {
user->name_rec_location[0] = '\0';
return -1;
}
return 0;
}
/*! \brief The ConfBridge application */
static int confbridge_exec(struct ast_channel *chan, const char *data)
{
int res = 0, volume_adjustments[2];
const char *b_profile_name = DEFAULT_BRIDGE_PROFILE;
const char *u_profile_name = DEFAULT_USER_PROFILE;
struct conference_bridge *conference_bridge = NULL;
struct conference_bridge_user conference_bridge_user = {
.chan = chan,
.tech_args.talking_threshold = DEFAULT_TALKING_THRESHOLD,
.tech_args.silence_threshold = DEFAULT_SILENCE_THRESHOLD,
.tech_args.drop_silence = 0,
};
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(conf_name);
AST_APP_ARG(b_profile_name);
AST_APP_ARG(u_profile_name);
AST_APP_ARG(menu_name);
ast_bridge_features_init(&conference_bridge_user.features);
if (ast_channel_state(chan) != AST_STATE_UP) {
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app);
res = -1; /* invalid PIN */
goto confbridge_cleanup;
}
/* We need to make a copy of the input string if we are going to modify it! */
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
/* bridge profile name */
if (args.argc > 1 && !ast_strlen_zero(args.b_profile_name)) {
b_profile_name = args.b_profile_name;
if (!conf_find_bridge_profile(chan, b_profile_name, &conference_bridge_user.b_profile)) {
ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name);
res = -1;
goto confbridge_cleanup;
}
/* user profile name */
if (args.argc > 2 && !ast_strlen_zero(args.u_profile_name)) {
u_profile_name = args.u_profile_name;
if (!conf_find_user_profile(chan, u_profile_name, &conference_bridge_user.u_profile)) {
ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name);
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
* prompted for requardless of quiet setting. */
if (!ast_strlen_zero(conference_bridge_user.u_profile.pin)) {
if (conf_get_pin(chan, &conference_bridge_user)) {
res = -1; /* invalid PIN */
goto confbridge_cleanup;
}
/* See if we need them to record a intro name */
if (!quiet && ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE)) {
conf_rec_name(&conference_bridge_user, args.conf_name);
/* menu name */
if (args.argc > 3 && !ast_strlen_zero(args.menu_name)) {
ast_copy_string(conference_bridge_user.menu_name, args.menu_name, sizeof(conference_bridge_user.menu_name));
if (conf_set_menu_to_user(conference_bridge_user.menu_name, &conference_bridge_user)) {
ast_log(LOG_WARNING, "Conference menu %s does not exist and can not be applied to confbridge user.\n",
args.menu_name);
res = -1; /* invalid PIN */
goto confbridge_cleanup;
}
/* Set if DTMF should pass through for this user or not */
if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DTMF_PASS)) {
conference_bridge_user.features.dtmf_passthrough = 1;
/* Set dsp threshold values if present */
if (conference_bridge_user.u_profile.talking_threshold) {
conference_bridge_user.tech_args.talking_threshold = conference_bridge_user.u_profile.talking_threshold;
}
if (conference_bridge_user.u_profile.silence_threshold) {
conference_bridge_user.tech_args.silence_threshold = conference_bridge_user.u_profile.silence_threshold;
/* Set a talker indicate call back if talking detection is requested */
if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_TALKER_DETECT)) {
char *conf_name = ast_strdup(args.conf_name); /* this is freed during feature cleanup */
if (!(conf_name)) {
res = -1; /* invalid PIN */
goto confbridge_cleanup;
}
ast_bridge_features_set_talk_detector(&conference_bridge_user.features,
conf_handle_talker_cb,
conf_handle_talker_destructor,
conf_name);
}
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
/* Look for a conference bridge matching the provided name */
if (!(conference_bridge = join_conference_bridge(args.conf_name, &conference_bridge_user))) {
res = -1; /* invalid PIN */
goto confbridge_cleanup;
}
/* Keep a copy of volume adjustments so we can restore them later if need be */
volume_adjustments[0] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_READ);
volume_adjustments[1] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_WRITE);
/* If the caller should be joined already muted, make it so */
if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_STARTMUTED)) {
conference_bridge_user.features.mute = 1;
}
if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DROP_SILENCE)) {
conference_bridge_user.tech_args.drop_silence = 1;
}
if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_JITTERBUFFER)) {
char *func_jb;
if ((func_jb = ast_module_helper("", "func_jitterbuffer", 0, 0, 0, 0))) {
ast_free(func_jb);
ast_func_write(chan, "JITTERBUFFER(adaptive)", "default");
}
}
if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DENOISE)) {
char *mod_speex;
/* Reduce background noise from each participant */
if ((mod_speex = ast_module_helper("", "codec_speex", 0, 0, 0, 0))) {
ast_free(mod_speex);
ast_func_write(chan, "DENOISE(rx)", "on");
}
}
/* if this user has a intro, play it before entering */
if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) {
ast_autoservice_start(chan);
play_sound_file(conference_bridge, conference_bridge_user.name_rec_location);
play_sound_file(conference_bridge,
conf_get_sound(CONF_SOUND_HAS_JOINED, conference_bridge_user.b_profile.sounds));
ast_autoservice_stop(chan);
}
/* Play the Join sound to both the conference and the user entering. */
if (!quiet) {
const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference_bridge_user.b_profile.sounds);
if (conference_bridge_user.playing_moh) {
ast_moh_stop(chan);
}
ast_stream_and_wait(chan, join_sound, "");
ast_autoservice_start(chan);
play_sound_file(conference_bridge, join_sound);
ast_autoservice_stop(chan);
David Vossel
committed
if (conference_bridge_user.playing_moh) {
ast_moh_start(chan, conference_bridge_user.u_profile.moh_class, NULL);
}
David Vossel
committed
/* See if we need to automatically set this user as a video source or not */
handle_video_on_join(conference_bridge, conference_bridge_user.chan, ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_MARKEDUSER));
/* Join our conference bridge for real */
send_join_event(conference_bridge_user.chan, conference_bridge->name);
ast_bridge_join(conference_bridge->bridge,
chan,
NULL,
&conference_bridge_user.features,
&conference_bridge_user.tech_args);
send_leave_event(conference_bridge_user.chan, conference_bridge->name);
/* if we're shutting down, don't attempt to do further processing */
if (ast_shutting_down()) {
leave_conference_bridge(conference_bridge, &conference_bridge_user);
conference_bridge = NULL;
goto confbridge_cleanup;
}
David Vossel
committed
/* If this user was a video source, we need to clean up and possibly pick a new source. */
handle_video_on_exit(conference_bridge, conference_bridge_user.chan);
/* if this user has a intro, play it when leaving */
if (!quiet && !ast_strlen_zero(conference_bridge_user.name_rec_location)) {
ast_autoservice_start(chan);
play_sound_file(conference_bridge, conference_bridge_user.name_rec_location);
play_sound_file(conference_bridge,
conf_get_sound(CONF_SOUND_HAS_LEFT, conference_bridge_user.b_profile.sounds));
ast_autoservice_stop(chan);
}
/* play the leave sound */
if (!quiet) {
const char *leave_sound = conf_get_sound(CONF_SOUND_LEAVE, conference_bridge_user.b_profile.sounds);
ast_autoservice_start(chan);
play_sound_file(conference_bridge, leave_sound);
ast_autoservice_stop(chan);
}
/* Easy as pie, depart this channel from the conference bridge */
leave_conference_bridge(conference_bridge, &conference_bridge_user);
conference_bridge = NULL;
/* If the user was kicked from the conference play back the audio prompt for it */
if (!quiet && conference_bridge_user.kicked) {
res = ast_stream_and_wait(chan,
conf_get_sound(CONF_SOUND_KICKED, conference_bridge_user.b_profile.sounds),
"");
}
/* Restore volume adjustments to previous values in case they were changed */
if (volume_adjustments[0]) {
ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_READ, volume_adjustments[0]);
}
if (volume_adjustments[1]) {
ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_WRITE, volume_adjustments[1]);
}
if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) {
ast_filedelete(conference_bridge_user.name_rec_location, NULL);
}
confbridge_cleanup:
ast_bridge_features_cleanup(&conference_bridge_user.features);
conf_bridge_profile_destroy(&conference_bridge_user.b_profile);
return res;
}
static int action_toggle_mute(struct conference_bridge *conference_bridge,
struct conference_bridge_user *conference_bridge_user,
struct ast_channel *chan)
{
/* Mute or unmute yourself, note we only allow manipulation if they aren't waiting for a marked user or if marked users exist */
if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_WAITMARKED) || conference_bridge->markedusers) {
conference_bridge_user->features.mute = (!conference_bridge_user->features.mute ? 1 : 0);
ast_test_suite_event_notify("CONF_MUTE", "Message: participant %s %s\r\nConference: %s\r\nChannel: %s", ast_channel_name(chan), conference_bridge_user->features.mute ? "muted" : "unmuted", conference_bridge_user->b_profile.name, ast_channel_name(chan));
}
return ast_stream_and_wait(chan, (conference_bridge_user->features.mute ?
conf_get_sound(CONF_SOUND_MUTED, conference_bridge_user->b_profile.sounds) :
conf_get_sound(CONF_SOUND_UNMUTED, conference_bridge_user->b_profile.sounds)),
"");
}
Matthew Jordan
committed
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
static int action_toggle_mute_participants(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user)
{
struct conference_bridge_user *participant = NULL;
const char *sound_to_play;
ao2_lock(conference_bridge);
/* If already muted, then unmute */
conference_bridge->muted = conference_bridge->muted ? 0 : 1;
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) {
if (!ast_test_flag(&participant->u_profile, USER_OPT_ADMIN)) {
participant->features.mute = conference_bridge->muted;
}
}
ao2_unlock(conference_bridge);
/* The host needs to hear it seperately, as they don't get the audio from play_sound_helper */
ast_stream_and_wait(conference_bridge_user->chan, sound_to_play, "");
/* Announce to the group that all participants are muted */
ast_autoservice_start(conference_bridge_user->chan);
play_sound_helper(conference_bridge, sound_to_play, 0);
ast_autoservice_stop(conference_bridge_user->chan);
return 0;
}
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
static int action_playback(struct ast_bridge_channel *bridge_channel, const char *playback_file)
{
char *file_copy = ast_strdupa(playback_file);
char *file = NULL;
while ((file = strsep(&file_copy, "&"))) {
if (ast_stream_and_wait(bridge_channel->chan, file, "")) {
ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
return -1;
}
}
return 0;
}
static int action_playback_and_continue(struct conference_bridge *conference_bridge,
struct conference_bridge_user *conference_bridge_user,
struct ast_bridge_channel *bridge_channel,
struct conf_menu *menu,
const char *playback_file,
const char *cur_dtmf,
int *stop_prompts)
{
int i;
int digit = 0;
char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
struct conf_menu_entry new_menu_entry = { { 0, }, };
char *file_copy = ast_strdupa(playback_file);
char *file = NULL;
while ((file = strsep(&file_copy, "&"))) {
if (ast_streamfile(bridge_channel->chan, file, ast_channel_language(bridge_channel->chan))) {
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
return -1;
}
/* now wait for more digits. */
if (!(digit = ast_waitstream(bridge_channel->chan, AST_DIGIT_ANY))) {
/* streaming finished and no DTMF was entered */
continue;
} else if (digit == -1) {
/* error */
return -1;
} else {
break; /* dtmf was entered */
}
}
if (!digit) {
/* streaming finished on all files and no DTMF was entered */
return -1;
}
ast_stopstream(bridge_channel->chan);
/* If we get here, then DTMF has been entered, This means no
* additional prompts should be played for this menu entry */
*stop_prompts = 1;
/* If a digit was pressed during the payback, update
* the dtmf string and look for a new menu entry in the
* menu structure */
ast_copy_string(dtmf, cur_dtmf, sizeof(dtmf));
for (i = 0; i < (MAXIMUM_DTMF_FEATURE_STRING - 1); i++) {
dtmf[i] = cur_dtmf[i];
if (!dtmf[i]) {
dtmf[i] = (char) digit;
dtmf[i + 1] = '\0';
i = -1;
break;
}
}
/* If i is not -1 then the new dtmf digit was _NOT_ added to the string.
* If this is the case, no new DTMF sequence should be looked for. */
if (i != -1) {
return 0;
}
if (conf_find_menu_entry_by_sequence(dtmf, menu, &new_menu_entry)) {
execute_menu_entry(conference_bridge,
conference_bridge_user,
bridge_channel,
&new_menu_entry, menu);
conf_menu_entry_destroy(&new_menu_entry);
}
return 0;
}
static int action_kick_last(struct conference_bridge *conference_bridge,
struct ast_bridge_channel *bridge_channel,
struct conference_bridge_user *conference_bridge_user)
{
struct conference_bridge_user *last_participant = NULL;
int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN);
if (!isadmin) {
ast_stream_and_wait(bridge_channel->chan,
conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds),
"");
ast_log(LOG_WARNING, "Only admin users can use the kick_last menu action. Channel %s of conf %s is not an admin.\n",
ast_channel_name(bridge_channel->chan),
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
conference_bridge->name);
return -1;
}
ao2_lock(conference_bridge);
if (((last_participant = AST_LIST_LAST(&conference_bridge->users_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,
conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds),
"");
} else if (last_participant) {
last_participant->kicked = 1;
ast_bridge_remove(conference_bridge->bridge, last_participant->chan);
ao2_unlock(conference_bridge);
}
return 0;
}
static int action_dialplan_exec(struct ast_bridge_channel *bridge_channel, struct conf_menu_action *menu_action)
{
struct ast_pbx_args args;
struct ast_pbx *pbx;
char *exten;
char *context;
int priority;
int res;
memset(&args, 0, sizeof(args));
args.no_hangup_chan = 1;
ast_channel_lock(bridge_channel->chan);
/*save off*/
exten = ast_strdupa(ast_channel_exten(bridge_channel->chan));
context = ast_strdupa(ast_channel_context(bridge_channel->chan));
priority = ast_channel_priority(bridge_channel->chan);
pbx = ast_channel_pbx(bridge_channel->chan);
ast_channel_pbx_set(bridge_channel->chan, NULL);
ast_channel_exten_set(bridge_channel->chan, menu_action->data.dialplan_args.exten);
ast_channel_context_set(bridge_channel->chan, menu_action->data.dialplan_args.context);
ast_channel_priority_set(bridge_channel->chan, menu_action->data.dialplan_args.priority);
ast_channel_unlock(bridge_channel->chan);
/*execute*/
res = ast_pbx_run_args(bridge_channel->chan, &args);
/*restore*/
ast_channel_lock(bridge_channel->chan);
ast_channel_exten_set(bridge_channel->chan, exten);
ast_channel_context_set(bridge_channel->chan, context);
ast_channel_priority_set(bridge_channel->chan, priority);
ast_channel_pbx_set(bridge_channel->chan, pbx);
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
ast_channel_unlock(bridge_channel->chan);
return res;
}
static int execute_menu_entry(struct conference_bridge *conference_bridge,
struct conference_bridge_user *conference_bridge_user,
struct ast_bridge_channel *bridge_channel,
struct conf_menu_entry *menu_entry,
struct conf_menu *menu)
{
struct conf_menu_action *menu_action;
int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN);
int stop_prompts = 0;
int res = 0;
AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) {
switch (menu_action->id) {
case MENU_ACTION_TOGGLE_MUTE:
res |= action_toggle_mute(conference_bridge,
conference_bridge_user,
bridge_channel->chan);
break;
Matthew Jordan
committed
case MENU_ACTION_ADMIN_TOGGLE_MUTE_PARTICIPANTS:
if (!isadmin) {
break;
}
action_toggle_mute_participants(conference_bridge, conference_bridge_user);
break;
case MENU_ACTION_PARTICIPANT_COUNT:
announce_user_count(conference_bridge, conference_bridge_user);
break;
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
case MENU_ACTION_PLAYBACK:
if (!stop_prompts) {
res |= action_playback(bridge_channel, menu_action->data.playback_file);
}
break;
case MENU_ACTION_RESET_LISTENING:
ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, 0);
break;
case MENU_ACTION_RESET_TALKING:
ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, 0);
break;
case MENU_ACTION_INCREASE_LISTENING:
ast_audiohook_volume_adjust(conference_bridge_user->chan,
AST_AUDIOHOOK_DIRECTION_WRITE, 1);
break;
case MENU_ACTION_DECREASE_LISTENING:
ast_audiohook_volume_adjust(conference_bridge_user->chan,
AST_AUDIOHOOK_DIRECTION_WRITE, -1);
break;
case MENU_ACTION_INCREASE_TALKING:
ast_audiohook_volume_adjust(conference_bridge_user->chan,
AST_AUDIOHOOK_DIRECTION_READ, 1);
break;
case MENU_ACTION_DECREASE_TALKING:
ast_audiohook_volume_adjust(conference_bridge_user->chan,
AST_AUDIOHOOK_DIRECTION_READ, -1);
break;
case MENU_ACTION_PLAYBACK_AND_CONTINUE:
if (!(stop_prompts)) {
res |= action_playback_and_continue(conference_bridge,
conference_bridge_user,
bridge_channel,
menu,
menu_action->data.playback_file,
menu_entry->dtmf,
&stop_prompts);
}
break;
case MENU_ACTION_DIALPLAN_EXEC:
res |= action_dialplan_exec(bridge_channel, menu_action);
break;
case MENU_ACTION_ADMIN_TOGGLE_LOCK:
if (!isadmin) {
break;
}
conference_bridge->locked = (!conference_bridge->locked ? 1 : 0);
res |= ast_stream_and_wait(bridge_channel->chan,
(conference_bridge->locked ?
conf_get_sound(CONF_SOUND_LOCKED_NOW, conference_bridge_user->b_profile.sounds) :
conf_get_sound(CONF_SOUND_UNLOCKED_NOW, conference_bridge_user->b_profile.sounds)),
"");
break;
case MENU_ACTION_ADMIN_KICK_LAST:
res |= action_kick_last(conference_bridge, bridge_channel, conference_bridge_user);
break;
case MENU_ACTION_LEAVE:
ao2_lock(conference_bridge);
ast_bridge_remove(conference_bridge->bridge, bridge_channel->chan);
ao2_unlock(conference_bridge);
break;
case MENU_ACTION_NOOP:
break;
case MENU_ACTION_SET_SINGLE_VIDEO_SRC:
ao2_lock(conference_bridge);
ast_bridge_set_single_src_video_mode(conference_bridge->bridge, bridge_channel->chan);
ao2_unlock(conference_bridge);
break;
David Vossel
committed
case MENU_ACTION_RELEASE_SINGLE_VIDEO_SRC:
handle_video_on_exit(conference_bridge, bridge_channel->chan);
break;
return res;
}
int conf_handle_dtmf(struct ast_bridge_channel *bridge_channel,
struct conference_bridge_user *conference_bridge_user,
struct conf_menu_entry *menu_entry,
struct conf_menu *menu)
{
struct conference_bridge *conference_bridge = conference_bridge_user->conference_bridge;
/* See if music on hold is playing */
ao2_lock(conference_bridge);
if (conference_bridge_user->playing_moh) {
/* MOH is going, let's stop it */
ast_moh_stop(bridge_channel->chan);
}
ao2_unlock(conference_bridge);
/* execute the list of actions associated with this menu entry */
execute_menu_entry(conference_bridge, conference_bridge_user, bridge_channel, menu_entry, menu);
/* See if music on hold needs to be started back up again */
ao2_lock(conference_bridge);
if (conference_bridge_user->playing_moh) {
ast_moh_start(bridge_channel->chan, conference_bridge_user->u_profile.moh_class, NULL);
}
ao2_unlock(conference_bridge);
return 0;
}
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
static char *complete_confbridge_name(const char *line, const char *word, int pos, int state)
{
int which = 0;
struct conference_bridge *bridge = NULL;
char *res = NULL;
int wordlen = strlen(word);
struct ao2_iterator i;
i = ao2_iterator_init(conference_bridges, 0);
while ((bridge = ao2_iterator_next(&i))) {
if (!strncasecmp(bridge->name, word, wordlen) && ++which > state) {
res = ast_strdup(bridge->name);
ao2_ref(bridge, -1);
break;
}
ao2_ref(bridge, -1);
}
ao2_iterator_destroy(&i);
return res;
}
static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct conference_bridge *bridge = NULL;
struct conference_bridge tmp;
struct conference_bridge_user *participant = NULL;
switch (cmd) {
case CLI_INIT:
e->command = "confbridge kick";
e->usage =
"Usage: confbridge kick <conference> <channel>\n"
" Kicks a channel out of the conference bridge.\n";
return NULL;
case CLI_GENERATE:
if (a->pos == 2) {
return complete_confbridge_name(a->line, a->word, a->pos, a->n);