Newer
Older
* \return Returns 0 on success, -1 if the user hung up
Matthew Jordan
committed
* \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_user(struct confbridge_user *user, const char *filename)
return ast_stream_and_wait(user->chan, filename, "");
static void handle_video_on_join(struct confbridge_conference *conference, struct ast_channel *chan, int marked)
David Vossel
committed
/* Right now, only marked users are automatically set as the single src of video.*/
if (!marked) {
if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED)) {
struct confbridge_user *user = NULL;
ao2_lock(conference);
/* see if anyone is already the video src */
AST_LIST_TRAVERSE(&conference->active_list, user, list) {
if (user->chan == chan) {
if (ast_bridge_is_video_src(conference->bridge, user->chan)) {
ao2_unlock(conference);
ast_bridge_set_single_src_video_mode(conference->bridge, chan);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED)) {
/* we joined and are video capable, we override anyone else that may have already been the video feed */
ast_bridge_set_single_src_video_mode(conference->bridge, chan);
static void handle_video_on_exit(struct confbridge_conference *conference, struct ast_channel *chan)
struct confbridge_user *user = NULL;
/* if this isn't a video source, nothing to update */
if (!ast_bridge_is_video_src(conference->bridge, chan)) {
ast_bridge_remove_video_src(conference->bridge, chan);
David Vossel
committed
/* If in follow talker mode, make sure to restore this mode on the
* bridge when a source is removed. It is possible this channel was
* only set temporarily as a video source by an AMI or DTMF action. */
if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
ast_bridge_set_talker_src_video_mode(conference->bridge);
David Vossel
committed
}
/* if the video_mode isn't set to automatically pick the video source, do nothing on exit. */
if (!ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED) &&
!ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED)) {
David Vossel
committed
/* Make the next available marked user the video src. */
ao2_lock(conference);
AST_LIST_TRAVERSE(&conference->active_list, user, list) {
if (user->chan == chan) {
if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
ast_bridge_set_single_src_video_mode(conference->bridge, user->chan);
ao2_unlock(conference);
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
struct hangup_data
{
struct confbridge_conference *conference;
ast_mutex_t lock;
ast_cond_t cond;
int hungup;
};
/*!
* \brief Hang up the announcer channel
*
* This hangs up the announcer channel in the conference. This
* runs in the playback queue taskprocessor since we do not want
* to hang up the channel while it's trying to play an announcement.
*
* This task is performed synchronously, so there is no need to
* perform any cleanup on the passed-in data.
*
* \param data A hangup_data structure
* \return 0
*/
static int hangup_playback(void *data)
{
struct hangup_data *hangup = data;
ast_autoservice_stop(hangup->conference->playback_chan);
ast_hangup(hangup->conference->playback_chan);
hangup->conference->playback_chan = NULL;
ast_mutex_lock(&hangup->lock);
hangup->hungup = 1;
ast_cond_signal(&hangup->cond);
ast_mutex_unlock(&hangup->lock);
return 0;
}
static void hangup_data_init(struct hangup_data *hangup, struct confbridge_conference *conference)
{
ast_mutex_init(&hangup->lock);
ast_cond_init(&hangup->cond, NULL);
hangup->conference = conference;
hangup->hungup = 0;
}
static void hangup_data_destroy(struct hangup_data *hangup)
{
ast_mutex_destroy(&hangup->lock);
ast_cond_destroy(&hangup->cond);
}
Matthew Jordan
committed
* \brief Destroy a conference bridge
Matthew Jordan
committed
* \param obj The conference bridge object
Matthew Jordan
committed
* \return Returns nothing
Matthew Jordan
committed
static void destroy_conference_bridge(void *obj)
struct confbridge_conference *conference = obj;
ast_debug(1, "Destroying conference bridge '%s'\n", conference->name);
if (conference->playback_chan) {
if (conference->playback_queue) {
struct hangup_data hangup;
hangup_data_init(&hangup, conference);
if (!ast_taskprocessor_push(conference->playback_queue, hangup_playback, &hangup)) {
ast_mutex_lock(&hangup.lock);
while (!hangup.hungup) {
ast_cond_wait(&hangup.cond, &hangup.lock);
}
ast_mutex_unlock(&hangup.lock);
hangup_data_destroy(&hangup);
} else {
/* Playback queue is not yet allocated. Just hang up the channel straight */
ast_hangup(conference->playback_chan);
conference->playback_chan = NULL;
}
Matthew Jordan
committed
}
Matthew Jordan
committed
/* Destroying a conference bridge is simple, all we have to do is destroy the bridging object */
if (conference->bridge) {
Richard Mudgett
committed
ast_bridge_destroy(conference->bridge, 0);
conference->bridge = NULL;
Matthew Jordan
committed
}
Richard Mudgett
committed
ast_channel_cleanup(conference->record_chan);
ast_free(conference->orig_rec_file);
ast_free(conference->record_filename);
conf_bridge_profile_destroy(&conference->b_profile);
ast_taskprocessor_unreference(conference->playback_queue);
Matthew Jordan
committed
}
/*! \brief Call the proper join event handler for the user for the conference bridge's current state
* \internal
* \param user The conference bridge user that is joining
Matthew Jordan
committed
* \retval 0 success
* \retval -1 failure
*/
static int handle_conf_user_join(struct confbridge_user *user)
Matthew Jordan
committed
{
conference_event_fn handler;
if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
handler = user->conference->state->join_marked;
} else if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
handler = user->conference->state->join_waitmarked;
handler = user->conference->state->join_unmarked;
Matthew Jordan
committed
}
ast_assert(handler != NULL);
if (!handler) {
conf_invalid_event_fn(user);
Matthew Jordan
committed
return -1;
Matthew Jordan
committed
Matthew Jordan
committed
Matthew Jordan
committed
/*! \brief Call the proper leave event handler for the user for the conference bridge's current state
* \internal
* \param user The conference bridge user that is leaving
Matthew Jordan
committed
* \retval 0 success
* \retval -1 failure
static int handle_conf_user_leave(struct confbridge_user *user)
Matthew Jordan
committed
{
conference_event_fn handler;
if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
handler = user->conference->state->leave_marked;
} else if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
handler = user->conference->state->leave_waitmarked;
Matthew Jordan
committed
} else {
handler = user->conference->state->leave_unmarked;
Matthew Jordan
committed
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(user);
Matthew Jordan
committed
return -1;
Matthew Jordan
committed
return 0;
}
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
void conf_update_user_mute(struct confbridge_user *user)
{
int mute_user;
int mute_system;
int mute_effective;
/* User level mute request. */
mute_user = user->muted;
/* System level mute request. */
mute_system = user->playing_moh
/*
* Do not allow waitmarked users to talk to anyone unless there
* is a marked user present.
*/
|| (!user->conference->markedusers
&& ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED));
mute_effective = mute_user || mute_system;
ast_debug(1, "User %s is %s: user:%d system:%d.\n",
ast_channel_name(user->chan), mute_effective ? "muted" : "unmuted",
mute_user, mute_system);
user->features.mute = mute_effective;
ast_test_suite_event_notify("CONF_MUTE_UPDATE",
"Mode: %s\r\n"
"Conference: %s\r\n"
"Channel: %s",
mute_effective ? "muted" : "unmuted",
user->conference->b_profile.name,
ast_channel_name(user->chan));
}
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
/*
* \internal
* \brief Mute/unmute a single user.
*/
static void generic_mute_unmute_user(struct confbridge_conference *conference, struct confbridge_user *user, int mute)
{
/* Set user level mute request. */
user->muted = mute ? 1 : 0;
conf_update_user_mute(user);
ast_test_suite_event_notify("CONF_MUTE",
"Message: participant %s %s\r\n"
"Conference: %s\r\n"
"Channel: %s",
ast_channel_name(user->chan),
mute ? "muted" : "unmuted",
conference->b_profile.name,
ast_channel_name(user->chan));
if (mute) {
send_mute_event(user, conference);
} else {
send_unmute_event(user, conference);
}
}
void conf_moh_stop(struct confbridge_user *user)
{
user->playing_moh = 0;
if (!user->suspended_moh) {
int in_bridge;
/*
* Locking the ast_bridge here is the only way to hold off the
* call to ast_bridge_join() in confbridge_exec() from
* interfering with the bridge and MOH operations here.
*/
ast_bridge_lock(user->conference->bridge);
/*
* Temporarily suspend the user from the bridge so we have
* control to stop MOH if needed.
*/
in_bridge = !ast_bridge_suspend(user->conference->bridge, user->chan);
ast_moh_stop(user->chan);
if (in_bridge) {
ast_bridge_unsuspend(user->conference->bridge, user->chan);
}
ast_bridge_unlock(user->conference->bridge);
}
}
void conf_moh_start(struct confbridge_user *user)
{
user->playing_moh = 1;
if (!user->suspended_moh) {
int in_bridge;
/*
* Locking the ast_bridge here is the only way to hold off the
* call to ast_bridge_join() in confbridge_exec() from
* interfering with the bridge and MOH operations here.
*/
ast_bridge_lock(user->conference->bridge);
/*
* Temporarily suspend the user from the bridge so we have
* control to start MOH if needed.
*/
in_bridge = !ast_bridge_suspend(user->conference->bridge, user->chan);
ast_moh_start(user->chan, user->u_profile.moh_class, NULL);
if (in_bridge) {
ast_bridge_unsuspend(user->conference->bridge, user->chan);
}
ast_bridge_unlock(user->conference->bridge);
}
}
/*!
* \internal
* \brief Unsuspend MOH for the conference user.
*
* \param user Conference user to unsuspend MOH on.
*
* \return Nothing
*/
static void conf_moh_unsuspend(struct confbridge_user *user)
{
ao2_lock(user->conference);
if (--user->suspended_moh == 0 && user->playing_moh) {
ast_moh_start(user->chan, user->u_profile.moh_class, NULL);
}
ao2_unlock(user->conference);
}
/*!
* \internal
* \brief Suspend MOH for the conference user.
*
* \param user Conference user to suspend MOH on.
*
* \return Nothing
*/
static void conf_moh_suspend(struct confbridge_user *user)
{
ao2_lock(user->conference);
if (user->suspended_moh++ == 0 && user->playing_moh) {
ast_moh_stop(user->chan);
}
ao2_unlock(user->conference);
}
int conf_handle_inactive_waitmarked(struct confbridge_user *user)
Matthew Jordan
committed
{
/* If we have not been quieted play back that they are waiting for the leader */
if (!ast_test_flag(&user->u_profile, USER_OPT_QUIET) && play_prompt_to_user(user,
conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, user->conference->b_profile.sounds))) {
Matthew Jordan
committed
/* user hungup while the sound was playing */
return -1;
}
return 0;
}
Alexei Gradinari
committed
int conf_handle_only_person(struct confbridge_user *user)
Matthew Jordan
committed
{
/* If audio prompts have not been quieted or this prompt quieted play it on out */
if (!ast_test_flag(&user->u_profile, USER_OPT_QUIET | USER_OPT_NOONLYPERSON)) {
if (play_prompt_to_user(user,
conf_get_sound(CONF_SOUND_ONLY_PERSON, user->conference->b_profile.sounds))) {
Matthew Jordan
committed
/* user hungup while the sound was playing */
int conf_add_post_join_action(struct confbridge_user *user, int (*func)(struct confbridge_user *user))
Matthew Jordan
committed
struct post_join_action *action;
if (!(action = ast_calloc(1, sizeof(*action)))) {
return -1;
}
action->func = func;
AST_LIST_INSERT_TAIL(&user->post_join_list, action, list);
Matthew Jordan
committed
return 0;
}
void conf_handle_first_join(struct confbridge_conference *conference)
Matthew Jordan
committed
{
ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "confbridge:%s", conference->name);
Matthew Jordan
committed
}
void conf_handle_second_active(struct confbridge_conference *conference)
Matthew Jordan
committed
{
/* If we are the second participant we may need to stop music on hold on the first */
struct confbridge_user *first_user = AST_LIST_FIRST(&conference->active_list);
if (ast_test_flag(&first_user->u_profile, USER_OPT_MUSICONHOLD)) {
conf_moh_stop(first_user);
Matthew Jordan
committed
}
conf_update_user_mute(first_user);
void conf_ended(struct confbridge_conference *conference)
Matthew Jordan
committed
{
struct pbx_find_info q = { .stacklen = 0 };
/* Called with a reference to conference */
ao2_unlink(conference_bridges, conference);
Richard Mudgett
committed
send_conf_end_event(conference);
if (!ast_strlen_zero(conference->b_profile.regcontext) &&
pbx_find_extension(NULL, NULL, &q, conference->b_profile.regcontext,
conference->name, 1, NULL, "", E_MATCH)) {
ast_context_remove_extension(conference->b_profile.regcontext,
conference->name, 1, NULL);
}
Richard Mudgett
committed
ao2_lock(conference);
conf_stop_record(conference);
ao2_unlock(conference);
Matthew Jordan
committed
}
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
/*!
* \internal
* \brief Allocate playback channel for a conference.
* \pre expects conference to be locked before calling this function
*/
static int alloc_playback_chan(struct confbridge_conference *conference)
{
struct ast_format_cap *cap;
char taskprocessor_name[AST_TASKPROCESSOR_MAX_NAME + 1];
cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
if (!cap) {
return -1;
}
ast_format_cap_append(cap, ast_format_slin, 0);
conference->playback_chan = ast_request("CBAnn", cap, NULL, NULL,
conference->name, NULL);
ao2_ref(cap, -1);
if (!conference->playback_chan) {
return -1;
}
/* To make sure playback_chan has the same language as the bridge */
ast_channel_lock(conference->playback_chan);
ast_channel_language_set(conference->playback_chan, conference->b_profile.language);
ast_channel_unlock(conference->playback_chan);
ast_debug(1, "Created announcer channel '%s' to conference bridge '%s'\n",
ast_channel_name(conference->playback_chan), conference->name);
ast_taskprocessor_build_name(taskprocessor_name, sizeof(taskprocessor_name),
"Confbridge/%s", conference->name);
conference->playback_queue = ast_taskprocessor_get(taskprocessor_name, TPS_REF_DEFAULT);
if (!conference->playback_queue) {
ast_hangup(conference->playback_chan);
conference->playback_chan = NULL;
return -1;
}
return 0;
}
/*!
* \brief Push the announcer channel into the bridge
*
Robert Mordec
committed
* \param conference Conference bridge to push the announcer to
* \retval 0 Success
* \retval -1 Failed to push the channel to the bridge
*/
Robert Mordec
committed
static int push_announcer(struct confbridge_conference *conference)
{
if (conf_announce_channel_push(conference->playback_chan)) {
ast_hangup(conference->playback_chan);
conference->playback_chan = NULL;
return -1;
}
ast_autoservice_start(conference->playback_chan);
return 0;
}
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
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
static void confbridge_unlock_and_unref(void *obj)
{
struct confbridge_conference *conference = obj;
if (!obj) {
return;
}
ao2_unlock(conference);
ao2_ref(conference, -1);
}
void confbridge_handle_atxfer(struct ast_attended_transfer_message *msg)
{
struct ast_channel_snapshot *old_snapshot;
struct ast_channel_snapshot *new_snapshot;
char *confbr_name = NULL;
char *comma;
RAII_VAR(struct confbridge_conference *, conference, NULL, confbridge_unlock_and_unref);
struct confbridge_user *user = NULL;
int found_user = 0;
struct ast_json *json_object;
if (msg->to_transferee.channel_snapshot
&& strcmp(msg->to_transferee.channel_snapshot->dialplan->appl, "ConfBridge") == 0
&& msg->target) {
/* We're transferring a bridge to an extension */
old_snapshot = msg->to_transferee.channel_snapshot;
new_snapshot = msg->target;
} else if (msg->to_transfer_target.channel_snapshot
&& strcmp(msg->to_transfer_target.channel_snapshot->dialplan->appl, "ConfBridge") == 0
&& msg->transferee) {
/* We're transferring a call to a bridge */
old_snapshot = msg->to_transfer_target.channel_snapshot;
new_snapshot = msg->transferee;
} else {
ast_log(LOG_ERROR, "Could not determine proper channels\n");
return;
}
/*
* old_snapshot->data should have the original parameters passed to
* the ConfBridge app:
* conference[,bridge_profile[,user_profile[,menu]]]
* We'll use "conference" to look up the bridge.
*
* We _could_ use old_snapshot->bridgeid to get the bridge but
* that would involve locking the conference_bridges container
* and iterating over it looking for a matching bridge.
*/
if (ast_strlen_zero(old_snapshot->dialplan->data)) {
ast_log(LOG_ERROR, "Channel '%s' didn't have app data set\n", old_snapshot->base->name);
return;
}
confbr_name = ast_strdupa(old_snapshot->dialplan->data);
comma = strchr(confbr_name, ',');
if (comma) {
*comma = '\0';
}
ast_debug(1, "Confbr: %s Leaving: %s Joining: %s\n", confbr_name, old_snapshot->base->name, new_snapshot->base->name);
conference = ao2_find(conference_bridges, confbr_name, OBJ_SEARCH_KEY);
if (!conference) {
ast_log(LOG_ERROR, "Conference bridge '%s' not found\n", confbr_name);
return;
}
ao2_lock(conference);
/*
* We need to grab the user profile for the departing user in order to
* properly format the join/leave messages.
*/
AST_LIST_TRAVERSE(&conference->active_list, user, list) {
if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) {
found_user = 1;
break;
}
}
/*
* If we didn't find the user in the active list, try the waiting list.
*/
if (!found_user && conference->waitingusers) {
AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) {
found_user = 1;
break;
}
}
}
if (!found_user) {
ast_log(LOG_ERROR, "Unable to find user profile for channel '%s' in bridge '%s'\n",
old_snapshot->base->name, confbr_name);
return;
}
/*
* We're going to use the existing user profile to create the messages.
*/
json_object = ast_json_pack("{s: b}",
"admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN)
);
if (!json_object) {
return;
}
send_conf_stasis_snapshots(conference, old_snapshot, confbridge_leave_type(), json_object);
ast_json_unref(json_object);
json_object = ast_json_pack("{s: b, s: b}",
"admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN),
"muted", user->muted);
if (!json_object) {
return;
}
send_conf_stasis_snapshots(conference, new_snapshot, confbridge_join_type(), json_object);
ast_json_unref(json_object);
}
/*!
* \brief Join a conference bridge
*
* \param conference_name The conference name
* \param user Conference bridge user structure
*
* \return A pointer to the conference bridge struct, or NULL if the conference room wasn't found.
*/
static struct confbridge_conference *join_conference_bridge(const char *conference_name, struct confbridge_user *user)
struct confbridge_conference *conference;
Matthew Jordan
committed
struct post_join_action *action;
int max_members_reached = 0;
/* We explictly lock the conference bridges container ourselves so that other callers can not create duplicate conferences at the same */
ao2_lock(conference_bridges);
ast_debug(1, "Trying to find conference bridge '%s'\n", conference_name);
/* Attempt to find an existing conference bridge */
conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
if (conference && conference->b_profile.max_members) {
max_members_reached = conference->b_profile.max_members > conference->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 */
if (conference && (max_members_reached || conference->locked) && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
ao2_unlock(conference_bridges);
ast_debug(1, "Conference '%s' is locked and caller is not an admin\n", conference_name);
ast_stream_and_wait(user->chan,
conf_get_sound(CONF_SOUND_LOCKED, conference->b_profile.sounds),
"");
ao2_ref(conference, -1);
return NULL;
}
/* If no conference bridge was found see if we can create one */
if (!conference) {
/* Try to allocate memory for a new conference bridge, if we fail... this won't end well. */
if (!(conference = ao2_alloc(sizeof(*conference), destroy_conference_bridge))) {
ao2_unlock(conference_bridges);
ast_log(LOG_ERROR, "Conference '%s' could not be created.\n", conference_name);
return NULL;
}
Richard Mudgett
committed
/* Setup for the record channel */
conference->record_filename = ast_str_create(RECORD_FILENAME_INITIAL_SPACE);
if (!conference->record_filename) {
ao2_ref(conference, -1);
ao2_unlock(conference_bridges);
return NULL;
}
/* Setup conference bridge parameters */
ast_copy_string(conference->name, conference_name, sizeof(conference->name));
conf_bridge_profile_copy(&conference->b_profile, &user->b_profile);
/* Create an actual bridge that will do the audio mixing */
Richard Mudgett
committed
conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX,
AST_BRIDGE_FLAG_MASQUERADE_ONLY | AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY,
app, conference_name, NULL);
Richard Mudgett
committed
if (!conference->bridge) {
ao2_ref(conference, -1);
ao2_unlock(conference_bridges);
ast_log(LOG_ERROR, "Conference '%s' mixing bridge could not be created.\n", conference_name);
return NULL;
}
/* Set the internal sample rate on the bridge from the bridge profile */
ast_bridge_set_internal_sample_rate(conference->bridge, conference->b_profile.internal_sample_rate);
/* Set the internal mixing interval on the bridge from the bridge profile */
ast_bridge_set_mixing_interval(conference->bridge, conference->b_profile.mix_interval);
ast_bridge_set_binaural_active(conference->bridge, ast_test_flag(&conference->b_profile, BRIDGE_OPT_BINAURAL_ACTIVE));
if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
ast_bridge_set_talker_src_video_mode(conference->bridge);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) {
ast_bridge_set_sfu_video_mode(conference->bridge);
ast_bridge_set_video_update_discard(conference->bridge, conference->b_profile.video_update_discard);
ast_bridge_set_remb_send_interval(conference->bridge, conference->b_profile.remb_send_interval);
if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE)) {
ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST)) {
ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_LOWEST);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST)) {
ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE_ALL)) {
ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE_ALL);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST_ALL)) {
ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_LOWEST_ALL);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL)) {
ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST_ALL);
if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_ENABLE_EVENTS)) {
ast_bridge_set_send_sdp_label(conference->bridge, 1);
}
/* Link it into the conference bridges container */
if (!ao2_link(conference_bridges, conference)) {
ao2_ref(conference, -1);
ao2_unlock(conference_bridges);
ast_log(LOG_ERROR,
"Conference '%s' could not be added to the conferences list.\n", conference_name);
return NULL;
}
Matthew Jordan
committed
/* Set the initial state to EMPTY */
conference->state = CONF_STATE_EMPTY;
Matthew Jordan
committed
if (alloc_playback_chan(conference)) {
ao2_unlink(conference_bridges, conference);
ao2_ref(conference, -1);
ao2_unlock(conference_bridges);
ast_log(LOG_ERROR, "Could not allocate announcer channel for conference '%s'\n", conference_name);
return NULL;
}
Robert Mordec
committed
if (push_announcer(conference)) {
ao2_unlink(conference_bridges, conference);
ao2_ref(conference, -1);
ao2_unlock(conference_bridges);
ast_log(LOG_ERROR, "Could not add announcer channel for conference '%s' bridge\n", conference_name);
return NULL;
}
if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_RECORD_CONFERENCE)) {
ao2_lock(conference);
Richard Mudgett
committed
conf_start_record(conference);
ao2_unlock(conference);
Matthew Jordan
committed
}
Richard Mudgett
committed
send_conf_start_event(conference);
if (!ast_strlen_zero(conference->b_profile.regcontext)) {
if (!ast_exists_extension(NULL, conference->b_profile.regcontext, conference->name, 1, NULL)) {
ast_add_extension(conference->b_profile.regcontext, 1, conference->name, 1, NULL, NULL, "Noop", NULL, NULL, "ConfBridge");
}
}
ast_debug(1, "Created conference '%s' and linked to container.\n", conference_name);
}
ao2_unlock(conference_bridges);
/* Setup conference bridge user parameters */
user->conference = conference;
ao2_lock(conference);
/* Determine if the new user should join the conference muted. */
if (ast_test_flag(&user->u_profile, USER_OPT_STARTMUTED)
|| (!ast_test_flag(&user->u_profile, USER_OPT_ADMIN) && conference->muted)) {
/* Set user level mute request. */
user->muted = 1;
}
/*
* Suspend any MOH until the user actually joins the bridge of
* the conference. This way any pre-join file playback does not
* need to worry about MOH.
*/
user->suspended_moh = 1;
if (handle_conf_user_join(user)) {
Matthew Jordan
committed
/* Invalid event, nothing was done, so we don't want to process a leave. */
ao2_unlock(conference);
ao2_ref(conference, -1);
user->conference = NULL;
Matthew Jordan
committed
return NULL;
if (ast_check_hangup(user->chan)) {
ao2_unlock(conference);
leave_conference(user);
Matthew Jordan
committed
return NULL;
}
ao2_unlock(conference);
Matthew Jordan
committed
Richard Mudgett
committed
/* If an announcement is to be played play it */
if (!ast_strlen_zero(user->u_profile.announcement)) {
if (play_prompt_to_user(user,
user->u_profile.announcement)) {
leave_conference(user);
Richard Mudgett
committed
return NULL;
}
}
Matthew Jordan
committed
/* Announce number of users if need be */
if (ast_test_flag(&user->u_profile, USER_OPT_ANNOUNCEUSERCOUNT)) {
if (announce_user_count(conference, user, NULL)) {
leave_conference(user);
return NULL;
}
}
if (ast_test_flag(&user->u_profile, USER_OPT_ANNOUNCEUSERCOUNTALL) &&
(conference->activeusers > user->u_profile.announce_user_count_all_after)) {
int user_count_res;
/*
* We have to autoservice the new user because he has not quite
* joined the conference yet.
*/
ast_autoservice_start(user->chan);
user_count_res = announce_user_count(conference, NULL, NULL);
ast_autoservice_stop(user->chan);
if (user_count_res) {
leave_conference(user);
Matthew Jordan
committed
/* Handle post-join actions */
while ((action = AST_LIST_REMOVE_HEAD(&user->post_join_list, list))) {
action->func(user);
Matthew Jordan
committed
ast_free(action);
return conference;
* \brief Leave a conference
* \param user The conference user
static void leave_conference(struct confbridge_user *user)
struct post_join_action *action;
ao2_lock(user->conference);
handle_conf_user_leave(user);
ao2_unlock(user->conference);
/* Discard any post-join actions */
while ((action = AST_LIST_REMOVE_HEAD(&user->post_join_list, list))) {
ast_free(action);
}
/* Done mucking with the conference, huzzah */
ao2_ref(user->conference, -1);
user->conference = NULL;
static void playback_common(struct confbridge_conference *conference, const char *filename, int say_number)
{
/* Don't try to play if the playback channel has been hung up */
if (!conference->playback_chan) {
return;
}
ast_autoservice_stop(conference->playback_chan);
/* The channel is all under our control, in goes the prompt */
if (!ast_strlen_zero(filename)) {
ast_stream_and_wait(conference->playback_chan, filename, "");
} else if (say_number >= 0) {
ast_say_number(conference->playback_chan, say_number, "",
ast_channel_language(conference->playback_chan), NULL);
}
ast_autoservice_start(conference->playback_chan);
}
struct playback_task_data {
struct confbridge_conference *conference;
const char *filename;
int say_number;
int playback_finished;
ast_mutex_t lock;
ast_cond_t cond;
};
* \brief Play an announcement into a confbridge
*
* This runs in the playback queue taskprocessor. This ensures that
* all playbacks are handled in sequence and do not play over top one
* another.
*
* This task runs synchronously so there is no need for performing any
* sort of cleanup on the input parameter.
*
* \param data A playback_task_data
* \return 0
static int playback_task(void *data)
struct playback_task_data *ptd = data;
playback_common(ptd->conference, ptd->filename, ptd->say_number);
ast_mutex_lock(&ptd->lock);
ptd->playback_finished = 1;
ast_cond_signal(&ptd->cond);
ast_mutex_unlock(&ptd->lock);
Richard Mudgett
committed
static void playback_task_data_init(struct playback_task_data *ptd, struct confbridge_conference *conference,
const char *filename, int say_number)
{
ast_mutex_init(&ptd->lock);
ast_cond_init(&ptd->cond, NULL);
ptd->filename = filename;
ptd->say_number = say_number;
ptd->conference = conference;
ptd->playback_finished = 0;
}
static void playback_task_data_destroy(struct playback_task_data *ptd)
{
ast_mutex_destroy(&ptd->lock);
ast_cond_destroy(&ptd->cond);
}
static int play_sound_helper(struct confbridge_conference *conference, const char *filename, int say_number)
struct playback_task_data ptd;
/* Do not waste resources trying to play files that do not exist */
if (ast_strlen_zero(filename)) {
if (say_number < 0) {
return 0;
}
} else if (!sound_file_exists(filename)) {
playback_task_data_init(&ptd, conference, filename, say_number);
if (ast_taskprocessor_push(conference->playback_queue, playback_task, &ptd)) {
if (!ast_strlen_zero(filename)) {
ast_log(LOG_WARNING, "Unable to play file '%s' to conference %s\n",
filename, conference->name);
} else {
ast_log(LOG_WARNING, "Unable to say number '%d' to conference %s\n",
say_number, conference->name);
}
playback_task_data_destroy(&ptd);
Richard Mudgett
committed
return -1;
/* Wait for the playback to complete */
ast_mutex_lock(&ptd.lock);
while (!ptd.playback_finished) {
ast_cond_wait(&ptd.cond, &ptd.lock);
ast_mutex_unlock(&ptd.lock);
playback_task_data_destroy(&ptd);
return 0;
}
int play_sound_file(struct confbridge_conference *conference, const char *filename)
return play_sound_helper(conference, filename, -1);