@ -1630,6 +1630,26 @@ static void leave_conference(struct confbridge_user *user)
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 ;
@ -1656,23 +1676,8 @@ static int playback_task(void *data)
{
struct playback_task_data * ptd = data ;
/* Don't try to play if the playback channel has been hung up */
if ( ! ptd - > conference - > playback_chan ) {
goto end ;
}
ast_autoservice_stop ( ptd - > conference - > playback_chan ) ;
playback_common ( ptd - > conference , ptd - > filename , ptd - > say_number ) ;
/* The channel is all under our control, in goes the prompt */
if ( ! ast_strlen_zero ( ptd - > filename ) ) {
ast_stream_and_wait ( ptd - > conference - > playback_chan , ptd - > filename , " " ) ;
} else if ( ptd - > say_number > = 0 ) {
ast_say_number ( ptd - > conference - > playback_chan , ptd - > say_number , " " ,
ast_channel_language ( ptd - > conference - > playback_chan ) , NULL ) ;
}
ast_autoservice_start ( ptd - > conference - > playback_chan ) ;
end :
ast_mutex_lock ( & ptd - > lock ) ;
ptd - > playback_finished = 1 ;
ast_cond_signal ( & ptd - > cond ) ;
@ -1704,7 +1709,11 @@ static int play_sound_helper(struct confbridge_conference *conference, const cha
struct playback_task_data ptd ;
/* Do not waste resources trying to play files that do not exist */
if ( ! ast_strlen_zero ( filename ) & & ! sound_file_exists ( filename ) ) {
if ( ast_strlen_zero ( filename ) ) {
if ( say_number < 0 ) {
return 0 ;
}
} else if ( ! sound_file_exists ( filename ) ) {
return 0 ;
}
@ -1738,6 +1747,274 @@ int play_sound_file(struct confbridge_conference *conference, const char *filena
return play_sound_helper ( conference , filename , - 1 ) ;
}
struct async_playback_task_data {
struct confbridge_conference * conference ;
int say_number ;
struct ast_channel * initiator ;
char filename [ 0 ] ;
} ;
struct async_datastore_data {
ast_mutex_t lock ;
ast_cond_t cond ;
int wait ;
} ;
static void async_datastore_data_destroy ( void * data )
{
struct async_datastore_data * add = data ;
ast_mutex_destroy ( & add - > lock ) ;
ast_cond_destroy ( & add - > cond ) ;
ast_free ( add ) ;
}
/*!
* \ brief Datastore used for timing of async announcement playback
*
* Announcements that are played to the entire conference can be played
* asynchronously ( i . e . The channel that queues the playback does not wait
* for the playback to complete before continuing )
*
* The thing about async announcements is that the channel that queues the
* announcement is either not in the bridge or is in some other way " occupied "
* at the time the announcement is queued . Because of that , the initiator of
* the announcement may enter after the announcement has already started ,
* resulting in the sound being " clipped " .
*
* This datastore makes it so that the channel that queues the async announcement
* can say " I'm ready now " . This way the announcement does not start until the
* initiator of the announcement is ready to hear the sound .
*/
static struct ast_datastore_info async_datastore_info = {
. type = " Confbridge async playback " ,
. destroy = async_datastore_data_destroy ,
} ;
static struct async_datastore_data * async_datastore_data_alloc ( void )
{
struct async_datastore_data * add ;
add = ast_malloc ( sizeof ( * add ) ) ;
if ( ! add ) {
return NULL ;
}
ast_mutex_init ( & add - > lock ) ;
ast_cond_init ( & add - > cond , NULL ) ;
add - > wait = 1 ;
return add ;
}
/*!
* \ brief Prepare the async playback datastore
*
* This is done prior to queuing an async announcement . If the
* datastore has not yet been created , it is allocated and initialized .
* If it already exists , we set it to be in " waiting " mode .
*
* \ param initiator The channel that is queuing the async playback
* \ retval 0 Success
* \ retval - 1 Failure : (
*/
static int setup_async_playback_datastore ( struct ast_channel * initiator )
{
struct ast_datastore * async_datastore ;
async_datastore = ast_channel_datastore_find ( initiator , & async_datastore_info , NULL ) ;
if ( async_datastore ) {
struct async_datastore_data * add ;
add = async_datastore - > data ;
add - > wait = 1 ;
return 0 ;
}
async_datastore = ast_datastore_alloc ( & async_datastore_info , NULL ) ;
if ( ! async_datastore ) {
return - 1 ;
}
async_datastore - > data = async_datastore_data_alloc ( ) ;
if ( ! async_datastore - > data ) {
ast_datastore_free ( async_datastore ) ;
return - 1 ;
}
ast_channel_datastore_add ( initiator , async_datastore ) ;
return 0 ;
}
static struct async_playback_task_data * async_playback_task_data_alloc (
struct confbridge_conference * conference , const char * filename , int say_number ,
struct ast_channel * initiator )
{
struct async_playback_task_data * aptd ;
aptd = ast_malloc ( sizeof ( * aptd ) + strlen ( filename ) + 1 ) ;
if ( ! aptd ) {
return NULL ;
}
/* Safe */
strcpy ( aptd - > filename , filename ) ;
aptd - > say_number = say_number ;
/* You may think that we need to bump the conference refcount since we are pushing
* this task to the taskprocessor .
*
* In this case , that actually causes a problem . The destructor for the conference
* pushes a hangup task into the taskprocessor and waits for it to complete before
* continuing . If the destructor gets called from a taskprocessor task , we ' re
* deadlocked .
*
* So is there a risk of the conference being freed out from under us ? No . Since
* the destructor pushes a task into the taskprocessor and waits for it to complete ,
* the destructor cannot free the conference out from under us . No further tasks
* can be queued onto the taskprocessor after the hangup since no channels are referencing
* the conference at that point any more .
*/
aptd - > conference = conference ;
aptd - > initiator = initiator ;
if ( initiator ) {
ast_channel_ref ( initiator ) ;
ast_channel_lock ( aptd - > initiator ) ;
/* We don't really care if this fails. If the datastore fails to get set up
* we ' ll still play the announcement . It ' s possible that the sound will be
* clipped for the initiator , but that ' s not the end of the world .
*/
setup_async_playback_datastore ( aptd - > initiator ) ;
ast_channel_unlock ( aptd - > initiator ) ;
}
return aptd ;
}
static void async_playback_task_data_destroy ( struct async_playback_task_data * aptd )
{
ast_channel_cleanup ( aptd - > initiator ) ;
ast_free ( aptd ) ;
}
/*!
* \ brief Wait for the initiator of an async playback to be ready
*
* See the description on the async_datastore_info structure for more
* information about what this is about .
*
* \ param initiator The channel that queued the async announcement
*/
static void wait_for_initiator ( struct ast_channel * initiator )
{
struct ast_datastore * async_datastore ;
struct async_datastore_data * add ;
ast_channel_lock ( initiator ) ;
async_datastore = ast_channel_datastore_find ( initiator , & async_datastore_info , NULL ) ;
ast_channel_unlock ( initiator ) ;
if ( ! async_datastore ) {
return ;
}
add = async_datastore - > data ;
ast_mutex_lock ( & add - > lock ) ;
while ( add - > wait ) {
ast_cond_wait ( & add - > cond , & add - > lock ) ;
}
ast_mutex_unlock ( & add - > lock ) ;
}
/*!
* \ brief Play an announcement into a confbridge asynchronously
*
* This runs in the playback queue taskprocessor . This ensures that
* all playbacks are handled in sequence and do not play over top one
* another .
*
* \ param data An async_playback_task_data
* \ return 0
*/
static int async_playback_task ( void * data )
{
struct async_playback_task_data * aptd = data ;
/* Wait for the initiator to get back in the bridge or be hung up */
if ( aptd - > initiator ) {
wait_for_initiator ( aptd - > initiator ) ;
}
playback_common ( aptd - > conference , aptd - > filename , aptd - > say_number ) ;
async_playback_task_data_destroy ( aptd ) ;
return 0 ;
}
static int async_play_sound_helper ( struct confbridge_conference * conference ,
const char * filename , int say_number , struct ast_channel * initiator )
{
struct async_playback_task_data * aptd ;
/* 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 ) ) {
return 0 ;
}
aptd = async_playback_task_data_alloc ( conference , filename , say_number , initiator ) ;
if ( ! aptd ) {
return - 1 ;
}
if ( ast_taskprocessor_push ( conference - > playback_queue , async_playback_task , aptd ) ) {
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 ) ;
}
async_playback_task_data_destroy ( aptd ) ;
return - 1 ;
}
return 0 ;
}
int async_play_sound_file ( struct confbridge_conference * conference ,
const char * filename , struct ast_channel * initiator )
{
return async_play_sound_helper ( conference , filename , - 1 , initiator ) ;
}
void async_play_sound_ready ( struct ast_channel * chan )
{
struct ast_datastore * async_datastore ;
struct async_datastore_data * add ;
ast_channel_lock ( chan ) ;
async_datastore = ast_channel_datastore_find ( chan , & async_datastore_info , NULL ) ;
ast_channel_unlock ( chan ) ;
if ( ! async_datastore ) {
return ;
}
add = async_datastore - > data ;
ast_mutex_lock ( & add - > lock ) ;
add - > wait = 0 ;
ast_cond_signal ( & add - > cond ) ;
ast_mutex_unlock ( & add - > lock ) ;
}
/*!
* \ brief Play number into the conference bridge
*
@ -1869,6 +2146,12 @@ static int conf_rec_name(struct confbridge_user *user, const char *conf_name)
return 0 ;
}
static int join_callback ( struct ast_bridge_channel * bridge_channel , void * ignore )
{
async_play_sound_ready ( bridge_channel - > chan ) ;
return 0 ;
}
/*! \brief The ConfBridge application */
static int confbridge_exec ( struct ast_channel * chan , const char * data )
{
@ -2047,10 +2330,14 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
if ( ! quiet ) {
const char * join_sound = conf_get_sound ( CONF_SOUND_JOIN , conference - > b_profile . sounds ) ;
ast_stream_and_wait ( chan , join_sound , " " ) ;
ast_autoservice_start ( chan ) ;
play_sound_file ( conference , join_sound ) ;
ast_autoservice_stop ( chan ) ;
if ( strcmp ( conference - > b_profile . language , ast_channel_language ( chan ) ) ) {
ast_stream_and_wait ( chan , join_sound , " " ) ;
ast_autoservice_start ( chan ) ;
play_sound_file ( conference , join_sound ) ;
ast_autoservice_stop ( chan ) ;
} else {
async_play_sound_file ( conference , join_sound , chan ) ;
}
}
if ( user . u_profile . timeout ) {
@ -2070,6 +2357,11 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
/* Join our conference bridge for real */
send_join_event ( & user , conference ) ;
if ( ast_bridge_join_hook ( & user . features , join_callback , NULL , NULL , 0 ) ) {
async_play_sound_ready ( user . chan ) ;
}
ast_bridge_join ( conference - > bridge ,
chan ,
NULL ,
@ -2077,6 +2369,11 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
& user . tech_args ,
0 ) ;
/* This is a catch-all in case joining the bridge failed or for some reason
* an async announcement got queued up and hasn ' t been told to play yet
*/
async_play_sound_ready ( chan ) ;
if ( ! user . kicked & & ast_check_hangup ( chan ) ) {
pbx_builtin_setvar_helper ( chan , " CONFBRIDGE_RESULT " , " HANGUP " ) ;
}
@ -2101,19 +2398,15 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
/* if this user has a intro, play it when leaving */
if ( ! quiet & & ! ast_strlen_zero ( user . name_rec_location ) ) {
ast_autoservice_start ( chan ) ;
play_sound_file ( conference , user . name_rec_location ) ;
play_sound_file ( conference ,
conf_get_sound ( CONF_SOUND_HAS_LEFT , conference - > b_profile . sounds ) ) ;
ast_autoservice_stop ( chan ) ;
async_play_sound_file ( conference , user . name_rec_location , NULL ) ;
async_play_sound_file ( conference ,
conf_get_sound ( CONF_SOUND_HAS_LEFT , conference - > b_profile . sounds ) , NULL ) ;
}
/* play the leave sound */
if ( ! quiet ) {
const char * leave_sound = conf_get_sound ( CONF_SOUND_LEAVE , conference - > b_profile . sounds ) ;
ast_autoservice_start ( chan ) ;
play_sound_file ( conference , leave_sound ) ;
ast_autoservice_stop ( chan ) ;
async_play_sound_file ( conference , leave_sound , NULL ) ;
}
/* If the user was kicked from the conference play back the audio prompt for it */
@ -2186,13 +2479,18 @@ static int action_toggle_mute_participants(struct confbridge_conference *confere
mute ? CONF_SOUND_PARTICIPANTS_MUTED : CONF_SOUND_PARTICIPANTS_UNMUTED ,
conference - > b_profile . sounds ) ;
/* The host needs to hear it seperately, as they don't get the audio from play_sound_helper */
ast_stream_and_wait ( user - > chan , sound_to_play , " " ) ;
if ( strcmp ( conference - > b_profile . language , ast_channel_language ( user - > chan ) ) ) {
/* The host needs to hear it seperately, as they don't get the audio from play_sound_helper */
ast_stream_and_wait ( user - > chan , sound_to_play , " " ) ;
/* Announce to the group that all participants are muted */
ast_autoservice_start ( user - > chan ) ;
play_sound_helper ( conference , sound_to_play , 0 ) ;
ast_autoservice_stop ( user - > chan ) ;
/* Announce to the group that all participants are muted */
ast_autoservice_start ( user - > chan ) ;
play_sound_file ( conference , sound_to_play ) ;
ast_autoservice_stop ( user - > chan ) ;
} else {
/* Playing the sound asynchronously lets the sound be heard by everyone at once */
async_play_sound_file ( conference , sound_to_play , user - > chan ) ;
}
return 0 ;
}
@ -2477,6 +2775,8 @@ int conf_handle_dtmf(struct ast_bridge_channel *bridge_channel,
/* See if music on hold needs to be started back up again */
conf_moh_unsuspend ( user ) ;
async_play_sound_ready ( bridge_channel - > chan ) ;
return 0 ;
}