diff --git a/src/native/portaudio/portaudio-hotplug-os.patch b/src/native/portaudio/portaudio-hotplug-os.patch new file mode 100644 index 000000000..91b4bfc2d --- /dev/null +++ b/src/native/portaudio/portaudio-hotplug-os.patch @@ -0,0 +1,1311 @@ +Index: test/patest_update_available_device_list.c +=================================================================== +--- test/patest_update_available_device_list.c (revision 1799) ++++ test/patest_update_available_device_list.c (working copy) +@@ -1,5 +1,6 @@ + #include + #include ++#include + + #include "portaudio.h" + +@@ -33,9 +34,10 @@ + + for(;;){ + printDevices(); +- ++ + printf( "press [enter] to update the device list. or q + [enter] to quit.\n" ); +- if( getchar() == 'q' ) ++ char ch = getchar(); ++ if( ch == 'q' ) + break; + + Pa_UpdateAvailableDeviceList(); +Index: configure +=================================================================== +--- configure (revision 1799) ++++ configure (working copy) +@@ -11341,7 +11341,7 @@ + fi + SHARED_FLAGS="$LIBS -dynamiclib $mac_arches $mac_sysroot $mac_version_min" + CFLAGS="-std=c99 $CFLAGS $mac_arches $mac_sysroot $mac_version_min" +- OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o" ++ OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o src/os/mac_osx/pa_osx_hotplug.o" + PADLL="libportaudio.dylib" + ;; + +@@ -11373,7 +11373,7 @@ + + if [ "x$with_wdmks" = "xyes" ]; then + DXDIR="$with_dxdir" +- add_objects src/hostapi/wdmks/pa_win_wdmks.o src/os/win/pa_win_hostapis.o src/os/win/pa_win_util.o ++ add_objects src/hostapi/wdmks/pa_win_wdmks.o src/os/win/pa_win_hostapis.o src/os/win/pa_win_util.o src/os/win/pa_win_wdmks_utils.o + LIBS="-lwinmm -lm -luuid -lsetupapi -lole32" + DLL_LIBS="${DLL_LIBS} -lwinmm -lm -L$DXDIR/lib -luuid -lsetupapi -lole32" + #VC98="\"/c/Program Files/Microsoft Visual Studio/VC98/Include\"" +@@ -11610,7 +11610,7 @@ + if [ "$have_alsa" = "yes" ] && [ "$with_alsa" != "no" ] ; then + DLL_LIBS="$DLL_LIBS -lasound" + LIBS="$LIBS -lasound" +- OTHER_OBJS="$OTHER_OBJS src/hostapi/alsa/pa_linux_alsa.o" ++ OTHER_OBJS="$OTHER_OBJS src/hostapi/alsa/pa_linux_alsa.o src/os/unix/pa_linux_hotplug.o" + INCLUDES="$INCLUDES pa_linux_alsa.h" + $as_echo "#define PA_USE_ALSA 1" >>confdefs.h + +Index: Makefile.in +=================================================================== +--- Makefile.in (revision 1799) ++++ Makefile.in (working copy) +@@ -107,6 +107,7 @@ + bin/patest_wire \ + bin/patest_write_sine \ + bin/patest_write_sine_nonint \ ++ bin/patest_update_available_device_list \ + bin/pa_devs \ + bin/pa_fuzz \ + bin/pa_minlat +@@ -146,6 +147,7 @@ + src/hostapi/wdmks \ + src/hostapi/wmme \ + src/os/unix \ ++ src/os/mac_osx \ + src/os/win + + SUBDIRS = +Index: configure.in +=================================================================== +--- configure.in (revision 1799) ++++ configure.in (working copy) +@@ -216,7 +216,7 @@ + fi + SHARED_FLAGS="$LIBS -dynamiclib $mac_arches $mac_sysroot $mac_version_min" + CFLAGS="-std=c99 $CFLAGS $mac_arches $mac_sysroot $mac_version_min" +- OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o" ++ OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o src/os/mac_osx/pa_osx_hotplug.o" + PADLL="libportaudio.dylib" + ;; + +@@ -230,7 +230,7 @@ + + if [[ "x$with_directx" = "xyes" ]]; then + DXDIR="$with_dxdir" +- add_objects src/hostapi/dsound/pa_win_ds.o src/hostapi/dsound/pa_win_ds_dynlink.o src/os/win/pa_win_hostapis.o src/os/win/pa_win_util.o src/os/win/pa_win_waveformat.o ++ add_objects src/hostapi/dsound/pa_win_ds.o src/hostapi/dsound/pa_win_ds_dynlink.o src/os/win/pa_win_hostapis.o src/os/win/pa_win_util.o src/os/win/pa_win_waveformat.o src/os/win/pa_win_hotplug.o + LIBS="-lwinmm -lm -ldsound -lole32" + DLL_LIBS="${DLL_LIBS} -lwinmm -lm -L$DXDIR/lib -ldsound -lole32" + #VC98="\"/c/Program Files/Microsoft Visual Studio/VC98/Include\"" +@@ -249,7 +249,7 @@ + + if [[ "x$with_wdmks" = "xyes" ]]; then + DXDIR="$with_dxdir" +- add_objects src/hostapi/wdmks/pa_win_wdmks.o src/os/win/pa_win_hostapis.o src/os/win/pa_win_util.o ++ add_objects src/hostapi/wdmks/pa_win_wdmks.o src/os/win/pa_win_hostapis.o src/os/win/pa_win_util.o src/os/win/pa_win_wdmks_utils.o + LIBS="-lwinmm -lm -luuid -lsetupapi -lole32" + DLL_LIBS="${DLL_LIBS} -lwinmm -lm -L$DXDIR/lib -luuid -lsetupapi -lole32" + #VC98="\"/c/Program Files/Microsoft Visual Studio/VC98/Include\"" +@@ -319,7 +319,7 @@ + if [[ "$have_alsa" = "yes" ] && [ "$with_alsa" != "no" ]] ; then + DLL_LIBS="$DLL_LIBS -lasound" + LIBS="$LIBS -lasound" +- OTHER_OBJS="$OTHER_OBJS src/hostapi/alsa/pa_linux_alsa.o" ++ OTHER_OBJS="$OTHER_OBJS src/hostapi/alsa/pa_linux_alsa.o src/os/unix/pa_linux_hotplug.o" + INCLUDES="$INCLUDES pa_linux_alsa.h" + AC_DEFINE(PA_USE_ALSA) + fi +Index: src/os/unix/pa_linux_hotplug.c +=================================================================== +--- src/os/unix/pa_linux_hotplug.c (revision 0) ++++ src/os/unix/pa_linux_hotplug.c (revision 0) +@@ -0,0 +1,147 @@ ++ ++#include "pa_util.h" ++#include "pa_debugprint.h" ++#include "pa_allocation.h" ++#include "pa_linux_alsa.h" ++#include "pa_hostapi.h" ++ ++#include ++ ++#include ++#include ++#include ++ ++/* Implemented in pa_front.c ++ @param first 0 = unknown, 1 = insertion, 2 = removal ++ @param second Host specific device change info (in windows it is the (unicode) device path) ++*/ ++extern void PaUtil_DevicesChanged(unsigned, void*); ++ ++static pthread_t g_thread_id; ++static pthread_mutex_t g_mutex; ++static volatile sig_atomic_t g_run = 0; ++ ++static int device_list_size(void) ++{ ++ snd_ctl_t *handle; ++ int card, err, dev, idx; ++ int nb = 0; ++ snd_ctl_card_info_t *info; ++ snd_pcm_info_t *pcminfo; ++ snd_ctl_card_info_alloca(&info); ++ snd_pcm_info_alloca(&pcminfo); ++ ++ card = -1; ++ if (snd_card_next(&card) < 0 || card < 0) ++ { ++ return nb; ++ } ++ ++ while (card >= 0) ++ { ++ char name[32]; ++ ++ sprintf(name, "hw:%d", card); ++ if ((err = snd_ctl_open(&handle, name, 0)) < 0) ++ { ++ goto next_card; ++ } ++ if ((err = snd_ctl_card_info(handle, info)) < 0) ++ { ++ snd_ctl_close(handle); ++ goto next_card; ++ } ++ dev = -1; ++ while (1) ++ { ++ unsigned int count; ++ int hasPlayback = 0; ++ int hasCapture = 0; ++ ++ snd_ctl_pcm_next_device(handle, &dev); ++ ++ if (dev < 0) ++ break; ++ snd_pcm_info_set_device(pcminfo, dev); ++ snd_pcm_info_set_subdevice(pcminfo, 0); ++ snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); ++ if ((err = snd_ctl_pcm_info(handle, pcminfo)) >= 0) ++ { ++ hasCapture = 1; ++ } ++ ++ snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); ++ if ((err = snd_ctl_pcm_info(handle, pcminfo)) >= 0) ++ { ++ hasPlayback = 1; ++ ++ count = snd_pcm_info_get_subdevices_count(pcminfo); ++ } ++ ++ if(hasPlayback == 0 && hasCapture == 0) ++ continue; ++ ++ nb++; ++ } ++ snd_ctl_close(handle); ++next_card: ++ if (snd_card_next(&card) < 0) ++ { ++ break; ++ } ++ } ++ return nb; ++} ++ ++static void* thread_fcn(void* data) ++{ ++ int currentDevices = 0; ++ ++ currentDevices = device_list_size(); ++ ++ while(g_run) ++ { ++ int count = 0; ++ ++ sleep(1); ++ count = device_list_size(); ++ if(count != currentDevices) ++ { ++ /* 1 = add device, 2 = remove device */ ++ int add = (count > currentDevices) ? 1 : 2; ++ ++ currentDevices = count; ++ ++ PaUtil_DevicesChanged(add, NULL); ++ } ++ } ++ ++ return NULL; ++} ++ ++void PaUtil_InitializeHotPlug() ++{ ++ pthread_mutex_init(&g_mutex, NULL); ++ g_run = 1; ++ pthread_create(&g_thread_id, NULL, thread_fcn, NULL); ++} ++ ++void PaUtil_TerminateHotPlug() ++{ ++ void* ret = NULL; ++ ++ g_run = 0; ++ pthread_join(g_thread_id, &ret); ++ pthread_mutex_destroy(&g_mutex); ++} ++ ++void PaUtil_LockHotPlug() ++{ ++ pthread_mutex_lock(&g_mutex); ++} ++ ++void PaUtil_UnlockHotPlug() ++{ ++ pthread_mutex_unlock(&g_mutex); ++} ++ +Index: src/os/mac_osx/pa_osx_hotplug.c +=================================================================== +--- src/os/mac_osx/pa_osx_hotplug.c (revision 0) ++++ src/os/mac_osx/pa_osx_hotplug.c (revision 0) +@@ -0,0 +1,76 @@ ++ ++#include "pa_util.h" ++#include "pa_debugprint.h" ++#include "pa_allocation.h" ++ ++#include ++ ++#include ++#include ++ ++/* Implemented in pa_front.c ++ @param first 0 = unknown, 1 = insertion, 2 = removal ++ @param second Host specific device change info (in windows it is the (unicode) device path) ++*/ ++extern void PaUtil_DevicesChanged(unsigned, void*); ++ ++/* Callback for audio hardware property changes. */ ++static OSStatus audioPropertyCallback(AudioHardwarePropertyID inPropertyID, ++ void *refCon) ++{ ++ (void)refCon; ++ switch (inPropertyID) ++ { ++ /* ++ * These are the other types of notifications we might receive, however, they are beyond ++ * the scope of this sample and we ignore them. ++ */ ++ case kAudioHardwarePropertyDefaultInputDevice: ++ PA_DEBUG(("audioPropertyCallback: default input device changed\n")); ++ break; ++ case kAudioHardwarePropertyDefaultOutputDevice: ++ PA_DEBUG(("audioPropertyCallback: default output device changed\n")); ++ break; ++ case kAudioHardwarePropertyDefaultSystemOutputDevice: ++ PA_DEBUG(("audioPropertyCallback: default system output device changed\n")); ++ break; ++ case kAudioHardwarePropertyDevices: ++ PA_DEBUG(("audioPropertyCallback: device list changed\n")); ++ PaUtil_DevicesChanged(1, NULL); ++ break; ++ default: ++ PA_DEBUG(("audioPropertyCallback: unknown message id=%08lx\n", inPropertyID)); ++ break; ++ } ++ ++ return noErr; ++} ++ ++void PaUtil_InitializeHotPlug() ++{ ++ AudioHardwareAddPropertyListener(kAudioHardwarePropertyDevices, ++ audioPropertyCallback, NULL); ++ AudioHardwareAddPropertyListener(kAudioHardwarePropertyDefaultInputDevice, ++ audioPropertyCallback, NULL); ++ AudioHardwareAddPropertyListener(kAudioHardwarePropertyDefaultOutputDevice, ++ audioPropertyCallback, NULL); ++} ++ ++void PaUtil_TerminateHotPlug() ++{ ++ AudioHardwareRemovePropertyListener(kAudioHardwarePropertyDevices, ++ audioPropertyCallback); ++ AudioHardwareRemovePropertyListener(kAudioHardwarePropertyDefaultInputDevice, ++ audioPropertyCallback); ++ AudioHardwareRemovePropertyListener(kAudioHardwarePropertyDefaultOutputDevice, ++ audioPropertyCallback); ++} ++ ++void PaUtil_LockHotPlug() ++{ ++} ++ ++void PaUtil_UnlockHotPlug() ++{ ++} ++ +Index: src/os/win/pa_win_hotplug.c +=================================================================== +--- src/os/win/pa_win_hotplug.c (revision 1799) ++++ src/os/win/pa_win_hotplug.c (working copy) +@@ -186,6 +186,8 @@ + { + PA_DEBUG(("Device inserted : %S\n", ptr->dbcc_name)); + InsertDeviceIntoCache(pInfo, ptr->dbcc_name); ++ /* yield some seconds because added device may not be completely configured */ ++ Sleep(2000); + PaUtil_DevicesChanged(1, ptr->dbcc_name); + } + } +@@ -202,6 +204,7 @@ + if (RemoveDeviceFromCache(pInfo, ptr->dbcc_name)) + { + PA_DEBUG(("Device removed : %S\n", ptr->dbcc_name)); ++ Sleep(2000); + PaUtil_DevicesChanged(2, ptr->dbcc_name); + } + } +Index: src/os/win/pa_win_hostapis.c +=================================================================== +--- src/os/win/pa_win_hostapis.c (revision 1799) ++++ src/os/win/pa_win_hostapis.c (working copy) +@@ -81,7 +81,7 @@ + #endif + + #if PA_USE_WDMKS +- PaWinWdm_Initialize, ++ //PaWinWdm_Initialize, + #endif + + #if PA_USE_SKELETON +Index: src/hostapi/alsa/pa_linux_alsa.c +=================================================================== +--- src/hostapi/alsa/pa_linux_alsa.c (revision 1799) ++++ src/hostapi/alsa/pa_linux_alsa.c (working copy) +@@ -668,6 +668,15 @@ + } + PaAlsaDeviceInfo; + ++/* used for tranferring device infos during scanning / rescanning */ ++typedef struct PaLinuxScanDeviceInfosResults ++{ ++ PaDeviceInfo **deviceInfos; ++ PaDeviceIndex defaultInputDevice; ++ PaDeviceIndex defaultOutputDevice; ++ int deviceCount; ++} PaLinuxScanDeviceInfosResults; ++ + /* prototypes for functions declared in this file */ + + static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +@@ -692,10 +701,17 @@ + static PaError IsStreamActive( PaStream *stream ); + static PaTime GetStreamTime( PaStream *stream ); + static double GetStreamCpuLoad( PaStream* stream ); +-static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi ); ++static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi, void** scanResults, int* deviceCount ); + static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ); + static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ); + ++static PaError ScanDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, ++ void **newDeviceInfos, int *newDeviceCount ); ++static PaError CommitDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, ++ void *deviceInfos, int deviceCount); ++static PaError DisposeDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, ++ int deviceCount ); ++ + /* Callback prototypes */ + static void *CallbackThreadFunc( void *userData ); + +@@ -722,6 +738,8 @@ + { + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = NULL; ++ void* scanResults = NULL; ++ int deviceCount = 0; + + /* Try loading Alsa library. */ + if (!PaAlsa_LoadLibrary()) +@@ -733,20 +751,29 @@ + alsaHostApi->hostApiIndex = hostApiIndex; + + *hostApi = (PaUtilHostApiRepresentation*)alsaHostApi; ++ (*hostApi)->deviceInfos = NULL; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paALSA; + (*hostApi)->info.name = "ALSA"; + ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; ++ (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; ++ (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; ++ (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; + + /** If AlsaErrorHandler is to be used, do not forget to unregister callback pointer in + Terminate function. + */ + /*ENSURE_( snd_lib_error_set_handler(AlsaErrorHandler), paUnanticipatedHostError );*/ + +- PA_ENSURE( BuildDeviceList( alsaHostApi ) ); ++ ScanDeviceInfos(&alsaHostApi->baseHostApiRep, hostApiIndex, &scanResults, ++ &deviceCount); ++ CommitDeviceInfos(&alsaHostApi->baseHostApiRep, hostApiIndex, scanResults, deviceCount); + + PaUtil_InitializeStreamInterface( &alsaHostApi->callbackStreamInterface, + CloseStream, StartStream, +@@ -802,7 +829,10 @@ + } + + PaUtil_FreeMemory( alsaHostApi ); +- alsa_snd_config_update_free_global(); ++// damencho, removed fo compability with pulseaudio versions before 0.9.16 ++// segfault application: ++// bugtrack alsa: 0002124: snd_config_update_free_global kills applications using user space alsa plugins ++// snd_config_update_free_global(); + + /* Close Alsa library. */ + PaAlsa_CloseLibrary(); +@@ -1075,7 +1105,7 @@ + } + + static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* deviceName, int blocking, +- PaAlsaDeviceInfo* devInfo, int* devIdx ) ++ PaAlsaDeviceInfo* devInfo, int* devIdx, PaLinuxScanDeviceInfosResults* out ) + { + PaError result = 0; + PaDeviceInfo *baseDeviceInfo = &devInfo->baseDeviceInfo; +@@ -1128,20 +1158,20 @@ + if( baseDeviceInfo->maxInputChannels > 0 || baseDeviceInfo->maxOutputChannels > 0 ) + { + /* Make device default if there isn't already one or it is the ALSA "default" device */ +- if( (baseApi->info.defaultInputDevice == paNoDevice || !strcmp(deviceName->alsaName, ++ if( (out->defaultInputDevice == paNoDevice || !strcmp(deviceName->alsaName, + "default" )) && baseDeviceInfo->maxInputChannels > 0 ) + { +- baseApi->info.defaultInputDevice = *devIdx; ++ out->defaultInputDevice = *devIdx; + PA_DEBUG(("Default input device: %s\n", deviceName->name)); + } +- if( (baseApi->info.defaultOutputDevice == paNoDevice || !strcmp(deviceName->alsaName, ++ if( (out->defaultOutputDevice == paNoDevice || !strcmp(deviceName->alsaName, + "default" )) && baseDeviceInfo->maxOutputChannels > 0 ) + { +- baseApi->info.defaultOutputDevice = *devIdx; ++ out->defaultOutputDevice = *devIdx; + PA_DEBUG(("Default output device: %s\n", deviceName->name)); + } + PA_DEBUG(("%s: Adding device %s: %d\n", __FUNCTION__, deviceName->name, *devIdx)); +- baseApi->deviceInfos[*devIdx] = (PaDeviceInfo *) devInfo; ++ out->deviceInfos[*devIdx] = (PaDeviceInfo *) devInfo; + (*devIdx) += 1; + } + else +@@ -1154,9 +1184,10 @@ + } + + /* Build PaDeviceInfo list, ignore devices for which we cannot determine capabilities (possibly busy, sigh) */ +-static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) ++static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi, void** scanResults, int* count) + { + PaUtilHostApiRepresentation *baseApi = &alsaApi->baseHostApiRep; ++ PaLinuxScanDeviceInfosResults *outArgument = NULL; + PaAlsaDeviceInfo *deviceInfoArray; + int cardIdx = -1, devIdx = 0; + snd_ctl_card_info_t *cardInfo; +@@ -1171,13 +1202,11 @@ + #ifdef PA_ENABLE_DEBUG_OUTPUT + PaTime startTime = PaUtil_GetTime(); + #endif ++ PaLinuxScanDeviceInfosResults* out = NULL; + + if( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) && atoi( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) ) ) + blocking = 0; + +- /* These two will be set to the first working input and output device, respectively */ +- baseApi->info.defaultInputDevice = paNoDevice; +- baseApi->info.defaultOutputDevice = paNoDevice; + + /* Gather info about hw devices + +@@ -1341,8 +1370,14 @@ + else + PA_DEBUG(( "%s: Iterating over ALSA plugins failed: %s\n", __FUNCTION__, alsa_snd_strerror( res ) )); + ++ out = (PaLinuxScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( ++ alsaApi->allocations, sizeof(PaLinuxScanDeviceInfosResults) ); ++ ++ out->defaultInputDevice = paNoDevice; ++ out->defaultOutputDevice = paNoDevice; ++ + /* allocate deviceInfo memory based on the number of devices */ +- PA_UNLESS( baseApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( ++ PA_UNLESS( out->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaDeviceInfo*) * (numDeviceNames) ), paInsufficientMemory ); + + /* allocate all device info structs in a contiguous block */ +@@ -1367,7 +1402,7 @@ + continue; + } + +- PA_ENSURE( FillInDevInfo( alsaApi, hwInfo, blocking, devInfo, &devIdx ) ); ++ PA_ENSURE( FillInDevInfo( alsaApi, hwInfo, blocking, devInfo, &devIdx, out ) ); + } + assert( devIdx < numDeviceNames ); + /* Now inspect 'dmix' and 'default' plugins */ +@@ -1381,11 +1416,13 @@ + } + + PA_ENSURE( FillInDevInfo( alsaApi, hwInfo, blocking, devInfo, +- &devIdx ) ); ++ &devIdx, out ) ); + } + free( hwDevInfos ); + +- baseApi->info.deviceCount = devIdx; /* Number of successfully queried devices */ ++ out->deviceCount = devIdx; /* Number of successfully queried devices */ ++ *scanResults = out; ++ *count = out->deviceCount; + + #ifdef PA_ENABLE_DEBUG_OUTPUT + PA_DEBUG(( "%s: Building device list took %f seconds\n", __FUNCTION__, PaUtil_GetTime() - startTime )); +@@ -2036,8 +2073,9 @@ + } + + ENSURE_( alsa_snd_pcm_sw_params_set_avail_min( self->pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); +- ENSURE_( alsa_snd_pcm_sw_params_set_xfer_align( self->pcm, swParams, 1 ), paUnanticipatedHostError ); ++// ENSURE_( alsa_snd_pcm_sw_params_set_xfeir_align( self->pcm, swParams, 1 ), paUnanticipatedHostError ); + ENSURE_( alsa_snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_ENABLE ), paUnanticipatedHostError ); ++ ENSURE_( alsa_snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_ENABLE ), paUnanticipatedHostError ); + + /* Set the parameters! */ + ENSURE_( alsa_snd_pcm_sw_params( self->pcm, swParams ), paUnanticipatedHostError ); +@@ -3544,9 +3582,20 @@ + snd_pcm_sframes_t framesAvail = alsa_snd_pcm_avail_update( self->pcm ); + *xrunOccurred = 0; + +- if( -EPIPE == framesAvail ) +- { ++ /* Get pcm_state and check for xrun condition. On playback I often see ++ * xrun but avail_update does not return -EPIPE but framesAvail larger ++ * than bufferSize. In case of xrun status set xrun flag, leave framesize ++ * as reported by avail_update, will be fixed below. In case avail_update ++ * returns -EPIPE process as usual. wd-xxx ++ */ ++ snd_pcm_state_t state = snd_pcm_state( self->pcm ); /* wd-xxx */ ++ if (state == SND_PCM_STATE_XRUN) { ++ // printf("xrun, fav %d\n", framesAvail); fflush(stdout); // DEBUG-WD + *xrunOccurred = 1; ++ } ++ if( -EPIPE == framesAvail) { ++ // printf("xrun-1, fav %d\n", framesAvail); fflush(stdout); // DEBUG-WD ++ *xrunOccurred = 1; + framesAvail = 0; + } + else +@@ -3554,6 +3603,11 @@ + ENSURE_( framesAvail, paUnanticipatedHostError ); + } + ++ /* Fix frames avail, should not be bigger than bufferSize wd-xxx */ ++ if (framesAvail > self->bufferSize) { ++ // printf("xrun-2, fav %d\n", framesAvail); fflush(stdout); // DEBUG-WD ++ framesAvail = self->bufferSize; ++ } + *numFrames = framesAvail; + + error: +@@ -3603,6 +3657,13 @@ + + *shouldPoll = 0; + } ++ else ++ { ++ // not actually used ++ unsigned long framesAvail = 0; ++ // now check for xrun ++ PaAlsaStreamComponent_GetAvailableFrames(self, &framesAvail, xrun ); ++ } + + error: + return result; +@@ -3693,6 +3754,10 @@ + framesAvail, &xrun ) ); + if( xrun ) + { ++ if(*frameAvail == 0) ++ { ++ result = paInternalError; ++ } + goto end; + } + +@@ -3740,6 +3805,7 @@ + Pa_Sleep( 1 ); /* avoid hot loop */ + continue; + } ++ result = paInternalError; + + /* TODO: Add macro for checking system calls */ + PA_ENSURE( paInternalError ); +@@ -3769,6 +3835,7 @@ + xrun = 1; /* try recovering device */ + + PA_DEBUG(( "%s: poll timed out\n", __FUNCTION__, timeouts )); ++ result = paTimedOut; + goto end;/*PA_ENSURE( paTimedOut );*/ + } + } +@@ -4326,9 +4393,24 @@ + while( frames > 0 ) + { + int xrun = 0; +- PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); ++ PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); ++ /* ++ * In case of overrun WaitForFrames leaves the capture stream in STATE_PREPARED ++ * most of the time. handleXrun() restarts the ALSA stream only in case ++ * snd_pcm_recover() fails, which usually does not happen. ++ * Here we start the pcm stream again and go for another try. Another ++ * option is: set result to paOverrun and return to caller. Then ++ * the caller needs to call ReadStream again. This takes more time and ++ * we lose even more frames. ++ */ ++ if (xrun) { /* wd-xxx */ ++ if( snd_pcm_state( stream->capture.pcm ) == SND_PCM_STATE_PREPARED ) { ++ ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError ); ++ } ++ continue; ++ } ++ + framesGot = PA_MIN( framesAvail, frames ); +- + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + if( framesGot > 0 ) + { +@@ -4546,3 +4628,75 @@ + busyRetries_ = retries; + return paNoError; + } ++ ++static PaError ScanDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, ++ void **scanResults, int *newDeviceCount) ++{ ++ PaAlsaHostApiRepresentation* alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; ++ PaError result = paNoError; ++ PA_ENSURE(BuildDeviceList( alsaHostApi, scanResults, newDeviceCount )); ++ ++ return paNoError; ++ ++error: ++ return result; ++} ++ ++static PaError CommitDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, ++ void *scanResults, int deviceCount) ++{ ++ PaAlsaHostApiRepresentation* alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; ++ PaError result = paNoError; ++ ++ /* These two will be set to the first working input and output device, respectively */ ++ hostApi->info.defaultInputDevice = paNoDevice; ++ hostApi->info.defaultOutputDevice = paNoDevice; ++ ++ /* Free any old memory which might be in the device info */ ++ if( hostApi->deviceInfos ) ++ { ++ /* all device info structs are allocated in a block so we can destroy them here */ ++ PaUtil_GroupFreeMemory( alsaHostApi->allocations, hostApi->deviceInfos[0] ); ++ PaUtil_GroupFreeMemory( alsaHostApi->allocations, hostApi->deviceInfos ); ++ hostApi->deviceInfos = NULL; ++ } ++ ++ if( scanResults != NULL ) ++ { ++ PaLinuxScanDeviceInfosResults *scanDeviceInfosResults = ( PaLinuxScanDeviceInfosResults * ) scanResults; ++ ++ if( deviceCount > 0 ) ++ { ++ /* use the array allocated in ScanDeviceInfos() as our deviceInfos */ ++ hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos; ++ hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice; ++ hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice; ++ hostApi->info.deviceCount = deviceCount; ++ } ++ ++ PaUtil_GroupFreeMemory( alsaHostApi->allocations, scanDeviceInfosResults ); ++ } ++ ++ return result; ++} ++ ++static PaError DisposeDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount) ++{ ++ PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; ++ ++ if( scanResults != NULL ) ++ { ++ PaLinuxScanDeviceInfosResults *scanDeviceInfosResults = ( PaLinuxScanDeviceInfosResults * ) scanResults; ++ if( scanDeviceInfosResults->deviceInfos ) ++ { ++ /* all device info structs are allocated in a block so we can destroy them here */ ++ PaUtil_GroupFreeMemory( alsaHostApi->allocations, scanDeviceInfosResults->deviceInfos[0] ); ++ PaUtil_GroupFreeMemory( alsaHostApi->allocations, scanDeviceInfosResults->deviceInfos ); ++ } ++ ++ PaUtil_GroupFreeMemory(alsaHostApi->allocations, scanDeviceInfosResults ); ++ } ++ ++ return paNoError; ++} ++ +Index: src/hostapi/oss/pa_unix_oss.c +=================================================================== +--- src/hostapi/oss/pa_unix_oss.c (revision 1799) ++++ src/hostapi/oss/pa_unix_oss.c (working copy) +@@ -256,6 +256,9 @@ + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + + PA_ENSURE( BuildDeviceList( ossHostApi ) ); + +Index: src/hostapi/skeleton/pa_hostapi_skeleton.c +=================================================================== +--- src/hostapi/skeleton/pa_hostapi_skeleton.c (revision 1799) ++++ src/hostapi/skeleton/pa_hostapi_skeleton.c (working copy) +@@ -206,9 +206,15 @@ + } + } + ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + + PaUtil_InitializeStreamInterface( &skeletonHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, +Index: src/hostapi/wasapi/pa_win_wasapi.c +=================================================================== +--- src/hostapi/wasapi/pa_win_wasapi.c (revision 1799) ++++ src/hostapi/wasapi/pa_win_wasapi.c (working copy) +@@ -1386,6 +1386,9 @@ + } + } + ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; +Index: src/hostapi/wdmks/pa_win_wdmks.c +=================================================================== +--- src/hostapi/wdmks/pa_win_wdmks.c (revision 1799) ++++ src/hostapi/wdmks/pa_win_wdmks.c (working copy) +@@ -1885,6 +1885,9 @@ + + (*hostApi)->info.deviceCount = deviceCount; + ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; +@@ -3305,4 +3308,4 @@ + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return 0; +-} +\ No newline at end of file ++} +Index: src/hostapi/jack/pa_jack.c +=================================================================== +--- src/hostapi/jack/pa_jack.c (revision 1799) ++++ src/hostapi/jack/pa_jack.c (working copy) +@@ -749,6 +749,9 @@ + + /* Register functions */ + ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; +Index: src/hostapi/coreaudio/pa_mac_core.c +=================================================================== +--- src/hostapi/coreaudio/pa_mac_core.c (revision 1799) ++++ src/hostapi/coreaudio/pa_mac_core.c (working copy) +@@ -80,7 +80,28 @@ + /* prototypes for functions declared in this file */ + + PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); ++static PaError ScanDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, ++ void **newDeviceInfos, int *newDeviceCount ); ++static PaError CommitDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, ++ void *deviceInfos, int deviceCount); ++static PaError DisposeDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, ++ int deviceCount ); + ++/* structures */ ++ ++/* used for tranferring device infos during scanning / rescanning */ ++typedef struct PaMacScanDeviceInfosResults ++{ ++ PaDeviceInfo **deviceInfos; ++ PaDeviceIndex defaultInputDevice; ++ PaDeviceIndex defaultOutputDevice; ++ ++ AudioDeviceID* devIds; ++ int devCount; ++ AudioDeviceID devInputDevice; ++ AudioDeviceID devOutputDevice; ++} PaMacScanDeviceInfosResults; ++ + /* + * Function declared in pa_mac_core.h. Sets up a PaMacCoreStreamInfoStruct + * with the requested flags and initializes channel map. +@@ -132,7 +153,6 @@ + return NULL; + PaMacAUHAL *macCoreHostApi = (PaMacAUHAL*)hostApi; + AudioDeviceID hostApiDevice = macCoreHostApi->devIds[device]; +- + UInt32 size = 0; + + error = AudioDeviceGetPropertyInfo( hostApiDevice, +@@ -311,46 +331,57 @@ + } + + +-/*currently, this is only used in initialization, but it might be modified +- to be used when the list of devices changes.*/ +-static PaError gatherDeviceInfo(PaMacAUHAL *auhalHostApi) ++//static PaError gatherDeviceInfo(PaMacAUHAL *auhalHostApi) ++static PaError gatherDeviceInfo(PaMacAUHAL* auhalHostApi, void** scanResults, int* count) + { +- UInt32 size; +- UInt32 propsize; ++ UInt32 propsize = 0; ++ UInt32 size = sizeof(AudioDeviceID); ++ PaMacScanDeviceInfosResults *outArgument = NULL; ++ PaError result = paNoError; ++ + VVDBUG(("gatherDeviceInfo()\n")); +- /* -- free any previous allocations -- */ +- if( auhalHostApi->devIds ) +- PaUtil_GroupFreeMemory(auhalHostApi->allocations, auhalHostApi->devIds); +- auhalHostApi->devIds = NULL; +- + /* -- figure out how many devices there are -- */ + AudioHardwareGetPropertyInfo( kAudioHardwarePropertyDevices, + &propsize, + NULL ); +- auhalHostApi->devCount = propsize / sizeof( AudioDeviceID ); ++ *count = (propsize / sizeof(AudioDeviceID)); + +- VDBUG( ( "Found %ld device(s).\n", auhalHostApi->devCount ) ); ++ VDBUG( ( "Found %ld device(s).\n", *count ) ); + ++ if(*count == 0) ++ return paNoError; ++ ++ /* Allocate the out param for all the info we need */ ++ outArgument = (PaMacScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( ++ auhalHostApi->allocations, sizeof(PaMacScanDeviceInfosResults) ); ++ ++ if( !outArgument ) ++ { ++ result = paInsufficientMemory; ++ return result; ++ } ++ ++ outArgument->devCount = *count; ++ + /* -- copy the device IDs -- */ +- auhalHostApi->devIds = (AudioDeviceID *)PaUtil_GroupAllocateMemory( ++ outArgument->devIds = (AudioDeviceID *)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, + propsize ); +- if( !auhalHostApi->devIds ) ++ if( !outArgument->devIds ) + return paInsufficientMemory; + AudioHardwareGetProperty( kAudioHardwarePropertyDevices, + &propsize, +- auhalHostApi->devIds ); ++ outArgument->devIds ); + #ifdef MAC_CORE_VERBOSE_DEBUG + { + int i; +- for( i=0; idevCount; ++i ) +- printf( "Device %d\t: %ld\n", i, auhalHostApi->devIds[i] ); ++ for( i=0; idevCount; ++i ) ++ printf( "Device %d\t: %ld\n", i, outArgument->devIds[i] ); + } + #endif + +- size = sizeof(AudioDeviceID); +- auhalHostApi->defaultIn = kAudioDeviceUnknown; +- auhalHostApi->defaultOut = kAudioDeviceUnknown; ++ outArgument->devInputDevice = kAudioDeviceUnknown; ++ outArgument->devOutputDevice = kAudioDeviceUnknown; + + /* determine the default device. */ + /* I am not sure how these calls to AudioHardwareGetProperty() +@@ -358,42 +389,44 @@ + device as the default. */ + if( 0 != AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, + &size, +- &auhalHostApi->defaultIn) ) { ++ &outArgument->devInputDevice) ) { + int i; +- auhalHostApi->defaultIn = kAudioDeviceUnknown; ++ outArgument->devInputDevice = kAudioDeviceUnknown; + VDBUG(("Failed to get default input device from OS.")); + VDBUG((" I will substitute the first available input Device.")); +- for( i=0; idevCount; ++i ) { ++ ++ for( i=0; i< outArgument->devCount; ++i ) { + PaDeviceInfo devInfo; + if( 0 != GetChannelInfo( auhalHostApi, &devInfo, +- auhalHostApi->devIds[i], TRUE ) ) ++ outArgument->devIds[i], TRUE ) ) + if( devInfo.maxInputChannels ) { +- auhalHostApi->defaultIn = auhalHostApi->devIds[i]; ++ outArgument->devInputDevice = outArgument->devIds[i]; + break; + } + } + } + if( 0 != AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, + &size, +- &auhalHostApi->defaultOut) ) { ++ &outArgument->devOutputDevice) ) { + int i; +- auhalHostApi->defaultIn = kAudioDeviceUnknown; ++ outArgument->devOutputDevice = kAudioDeviceUnknown; + VDBUG(("Failed to get default output device from OS.")); + VDBUG((" I will substitute the first available output Device.")); +- for( i=0; idevCount; ++i ) { ++ for( i=0; idevCount; ++i ) { + PaDeviceInfo devInfo; + if( 0 != GetChannelInfo( auhalHostApi, &devInfo, +- auhalHostApi->devIds[i], FALSE ) ) ++ outArgument->devIds[i], FALSE ) ) + if( devInfo.maxOutputChannels ) { +- auhalHostApi->defaultOut = auhalHostApi->devIds[i]; ++ outArgument->devOutputDevice = outArgument->devIds[i]; + break; + } + } + } + +- VDBUG( ( "Default in : %ld\n", auhalHostApi->defaultIn ) ); +- VDBUG( ( "Default out: %ld\n", auhalHostApi->defaultOut ) ); ++ VDBUG( ( "Default in : %ld\n", outArgument->devInputDevice ) ); ++ VDBUG( ( "Default out: %ld\n", outArgument->devOutputDevice ) ); + ++ *scanResults = outArgument; + return paNoError; + } + +@@ -402,9 +435,9 @@ + AudioDeviceID macCoreDeviceId, + int isInput) + { +- UInt32 propSize; ++ UInt32 propSize = 0; + PaError err = paNoError; +- UInt32 i; ++ UInt32 i = 0; + int numChannels = 0; + AudioBufferList *buflist = NULL; + UInt32 frameLatency; +@@ -528,10 +561,10 @@ + PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) + { + PaError result = paNoError; +- int i; + PaMacAUHAL *auhalHostApi = NULL; +- PaDeviceInfo *deviceInfoArray; + int unixErr; ++ void* scanResults = NULL; ++ int deviceCount = 0; + + VVDBUG(("PaMacCore_Initialize(): hostApiIndex=%d\n", hostApiIndex)); + +@@ -572,65 +605,29 @@ + auhalHostApi->devIds = NULL; + auhalHostApi->devCount = 0; + +- /* get the info we need about the devices */ +- result = gatherDeviceInfo( auhalHostApi ); +- if( result != paNoError ) +- goto error; +- + *hostApi = &auhalHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "Core Audio"; +- ++ ++ (*hostApi)->deviceInfos = NULL; ++ + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; +- + (*hostApi)->info.deviceCount = 0; + +- if( auhalHostApi->devCount > 0 ) +- { +- (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( +- auhalHostApi->allocations, sizeof(PaDeviceInfo*) * auhalHostApi->devCount); +- if( !(*hostApi)->deviceInfos ) +- { +- result = paInsufficientMemory; +- goto error; +- } ++ (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; ++ (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; ++ (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; + +- /* allocate all device info structs in a contiguous block */ +- deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( +- auhalHostApi->allocations, sizeof(PaDeviceInfo) * auhalHostApi->devCount ); +- if( !deviceInfoArray ) +- { +- result = paInsufficientMemory; +- goto error; +- } ++ result = ScanDeviceInfos(&auhalHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, ++ &deviceCount); + +- for( i=0; i < auhalHostApi->devCount; ++i ) +- { +- int err; +- err = InitializeDeviceInfo( auhalHostApi, &deviceInfoArray[i], +- auhalHostApi->devIds[i], +- hostApiIndex ); +- if (err == paNoError) +- { /* copy some info and set the defaults */ +- (*hostApi)->deviceInfos[(*hostApi)->info.deviceCount] = &deviceInfoArray[i]; +- if (auhalHostApi->devIds[i] == auhalHostApi->defaultIn) +- (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; +- if (auhalHostApi->devIds[i] == auhalHostApi->defaultOut) +- (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; +- (*hostApi)->info.deviceCount++; +- } +- else +- { /* there was an error. we need to shift the devices down, so we ignore this one */ +- int j; +- auhalHostApi->devCount--; +- for( j=i; jdevCount; ++j ) +- auhalHostApi->devIds[j] = auhalHostApi->devIds[j+1]; +- i--; +- } +- } +- } ++ if(result != paNoError) ++ goto error; ++ ++ /* FIXME for now we ignore the result of CommitDeviceInfos(), it should probably be an atomic non-failing operation */ ++ CommitDeviceInfos( &auhalHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount ); + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; +@@ -2468,3 +2465,148 @@ + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); + } ++ ++static PaError ScanDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, ++ void **scanResults, int *newDeviceCount) ++{ ++ PaMacAUHAL* auhalHostApi = (PaMacAUHAL*)hostApi; ++ PaDeviceInfo *deviceInfoArray = NULL; ++ PaError result = paNoError; ++ int i = 0; ++ PaMacScanDeviceInfosResults* out = NULL; ++ ++ /* get the info we need about the devices */ ++ result = gatherDeviceInfo( auhalHostApi, scanResults, newDeviceCount ); ++ ++ if( result != paNoError ) ++ return result; ++ ++ out = (PaMacScanDeviceInfosResults*)*scanResults; ++ ++ if( out->devCount > 0 ) ++ { ++ int count = 0; ++ ++ /* allocate array for pointers to PaDeviceInfo structs */ ++ out->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( ++ auhalHostApi->allocations, sizeof(PaDeviceInfo*) * out->devCount); ++ if( !out->deviceInfos ) ++ { ++ result = paInsufficientMemory; ++ return result; ++ } ++ ++ /* allocate all device info structs in a contiguous block */ ++ deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( ++ auhalHostApi->allocations, sizeof(PaDeviceInfo) * out->devCount ); ++ if( !deviceInfoArray ) ++ { ++ result = paInsufficientMemory; ++ return result; ++ } ++ ++ for( i=0; i < out->devCount; ++i ) ++ { ++ int err; ++ err = InitializeDeviceInfo( auhalHostApi, &deviceInfoArray[i], ++ out->devIds[i], ++ hostApiIndex ); ++ if (err == paNoError) ++ { ++ /* copy some info and set the defaults */ ++ out->deviceInfos[count] = &deviceInfoArray[i]; ++ ++ if (out->devIds[i] == out->devInputDevice) ++ { ++ out->defaultInputDevice = count; ++ } ++ if (out->devIds[i] == out->devOutputDevice) ++ { ++ out->defaultOutputDevice = count; ++ } ++ count++; ++ } ++ else ++ { ++ /* there was an error. we need to shift the devices down, so we ignore this one */ ++ int j; ++ out->devCount--; ++ for( j=i; jdevCount; ++j ) ++ out->devIds[j] = out->devIds[j+1]; ++ i--; ++ } ++ } ++ } ++ *newDeviceCount = out->devCount; ++ ++ return paNoError; ++} ++ ++static PaError CommitDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, ++ void *scanResults, int deviceCount) ++{ ++ PaMacAUHAL* auhalHostApi = (PaMacAUHAL*)hostApi; ++ PaError result = paNoError; ++ ++ hostApi->info.deviceCount = 0; ++ hostApi->info.defaultInputDevice = paNoDevice; ++ hostApi->info.defaultOutputDevice = paNoDevice; ++ ++ /* -- free any previous allocations -- */ ++ if( auhalHostApi->devIds ) ++ { ++ PaUtil_GroupFreeMemory(auhalHostApi->allocations, auhalHostApi->devIds); ++ } ++ auhalHostApi->devIds = NULL; ++ ++ /* Free any old memory which might be in the device info */ ++ if( hostApi->deviceInfos ) ++ { ++ PaUtil_GroupFreeMemory( auhalHostApi->allocations, hostApi->deviceInfos[0] ); ++ PaUtil_GroupFreeMemory( auhalHostApi->allocations, hostApi->deviceInfos ); ++ hostApi->deviceInfos = NULL; ++ } ++ ++ if( scanResults != NULL ) ++ { ++ PaMacScanDeviceInfosResults *scanDeviceInfosResults = ( PaMacScanDeviceInfosResults * ) scanResults; ++ ++ if( deviceCount > 0 ) ++ { ++ /* use the array allocated in ScanDeviceInfos() as our deviceInfos */ ++ hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos; ++ hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice; ++ hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice; ++ hostApi->info.deviceCount = deviceCount; ++ auhalHostApi->devIds = scanDeviceInfosResults->devIds; ++ auhalHostApi->devCount = scanDeviceInfosResults->devCount; ++ auhalHostApi->defaultIn = scanDeviceInfosResults->devInputDevice; ++ auhalHostApi->defaultOut = scanDeviceInfosResults->devOutputDevice; ++ } ++ ++ PaUtil_GroupFreeMemory( auhalHostApi->allocations, scanDeviceInfosResults ); ++ } ++ ++ return result; ++} ++ ++static PaError DisposeDeviceInfos(struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount) ++{ ++ PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi; ++ ++ if( scanResults != NULL ) ++ { ++ PaMacScanDeviceInfosResults *scanDeviceInfosResults = ( PaMacScanDeviceInfosResults * ) scanResults; ++ if( scanDeviceInfosResults->deviceInfos ) ++ { ++ /* all device info structs are allocated in a block so we can destroy them here */ ++ PaUtil_GroupFreeMemory( auhalHostApi->allocations, scanDeviceInfosResults->deviceInfos[0] ); ++ PaUtil_GroupFreeMemory( auhalHostApi->allocations, scanDeviceInfosResults->deviceInfos ); ++ } ++ ++ PaUtil_GroupFreeMemory(auhalHostApi->allocations, scanDeviceInfosResults ); ++ } ++ ++ return paNoError; ++} ++ +Index: src/hostapi/asio/pa_asio.cpp +=================================================================== +--- src/hostapi/asio/pa_asio.cpp (revision 1799) ++++ src/hostapi/asio/pa_asio.cpp (working copy) +@@ -1315,6 +1315,9 @@ + } + + ++ (*hostApi)->ScanDeviceInfos = NULL; ++ (*hostApi)->CommitDeviceInfos = NULL; ++ (*hostApi)->DisposeDeviceInfos = NULL; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; +Index: src/common/pa_front.c +=================================================================== +--- src/common/pa_front.c (revision 1799) ++++ src/common/pa_front.c (working copy) +@@ -737,13 +737,12 @@ + for( i = 0 ; i < paInternalInfo_.hostApisCount_ ; ++i ) + { + PaUtilHostApiRepresentation *hostApi = paInternalInfo_.hostApis_[i]; ++ PA_DEBUG(( "Scanning new device list for host api %d.\n",i)); + if( hostApi->ScanDeviceInfos == NULL ) + continue; + +- PA_DEBUG(( "Scanning new device list for host api %d.\n",i)); + if( hostApi->ScanDeviceInfos( hostApi, i, &scanResults[ i ], &deviceCounts[ i ] ) != paNoError ) + break; +- + } + + /* Check the result of the scan operation */ diff --git a/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java b/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java index 08cbe0588..8b3725e92 100644 --- a/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java +++ b/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java @@ -209,8 +209,23 @@ public void run() { try { - opSet.hangupCallPeer( - cCall.getCallPeers().next()); + Iterator peers = + cCall.getCrossProtocolCallPeers(); + + while(peers.hasNext()) + { + CallPeer peer = peers.next(); + + peer.getProtocolProvider().getOperationSet( + OperationSetBasicTelephony.class). + hangupCallPeer(peer); + } + + peers = cCall.getCallPeers(); + + while(peers.hasNext()) + opSet.hangupCallPeer( + peers.next()); } catch(OperationFailedException e) { @@ -262,7 +277,8 @@ public void outgoingCallCreated(CallEvent event) { synchronized(outgoingCalls) { - outgoingCalls.add(event.getSourceCall()); + if(event.getSourceCall().getCallGroup() == null) + outgoingCalls.add(event.getSourceCall()); } } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java index d938d1531..61b43061f 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java @@ -755,6 +755,23 @@ public static void inviteToConferenceCall( String[] callees, new InviteToConferenceCallThread(callees, call).start(); } + /** + * Invites the given list of callees to the given conference + * call. + * + * @param provider provider to which the callee belongs + * @param callee the list of contacts to invite + * @param call existing call + */ + public static void inviteToCrossProtocolConferenceCall( + ProtocolProviderService provider, + String callee, + Call call) + { + new InviteToCrossProtocolConferenceCallThread(provider, callee, call). + start(); + } + /** * Puts on or off hold the given callPeer. * @param callPeer the peer to put on/off hold @@ -1493,6 +1510,79 @@ public void run() } } + /** + * Invites a list of callees to a conference call. + */ + private static class InviteToCrossProtocolConferenceCallThread + extends Thread + { + private final ProtocolProviderService provider; + + private final String callee; + + private final Call call; + + public InviteToCrossProtocolConferenceCallThread( + ProtocolProviderService provider, + String callee, + Call call) + { + this.provider = provider; + this.callee = callee; + this.call = call; + } + + @Override + public void run() + { + CallGroup group = null; + + if(call.getCallGroup() == null) + { + group = new CallGroup(); + group.addCall(call); + } + else + { + group = call.getCallGroup(); + } + + OperationSetBasicTelephony opSetTelephony = + provider.getOperationSet(OperationSetBasicTelephony.class); + + if(opSetTelephony != null) + { + Exception exception = null; + + try + { + Call c = opSetTelephony.createCall(callee, group); + group.addCall(c); + } + catch(Exception e) + { + exception = e; + logger.info("Failed to call " + callee, e); + } + + if (exception != null) + { + logger + .error("Failed to invite callee: " + callee, exception); + + new ErrorDialog( + null, + GuiActivator + .getResources() + .getI18NString("service.gui.ERROR"), + exception.getMessage(), + ErrorDialog.ERROR) + .showDialog(); + } + } + } + } + /** * Hang-ups all call peers in the given call. */ @@ -1509,23 +1599,51 @@ public HangupCallThread(Call call) @Override public void run() { - ProtocolProviderService pps = call.getProtocolProvider(); - Iterator peers = call.getCallPeers(); + Iterator peers = null; - while (peers.hasNext()) + if(call.getCallGroup() != null) { - CallPeer peer = peers.next(); - OperationSetBasicTelephony telephony - = pps.getOperationSet(OperationSetBasicTelephony.class); + List calls = call.getCallGroup().getCalls(); - try + for(Call c : calls) { - telephony.hangupCallPeer(peer); + peers = c.getCallPeers(); + CallPeer peer = peers.next(); + OperationSetBasicTelephony telephony + = peer.getCall().getProtocolProvider(). + getOperationSet(OperationSetBasicTelephony.class); + + try + { + telephony.hangupCallPeer(peer); + } + catch (OperationFailedException e) + { + logger.error("Could not hang up : " + peer + + " caused by the following exception: " + e); + } } - catch (OperationFailedException e) + } + else + { + ProtocolProviderService pps = call.getProtocolProvider(); + peers = call.getCallPeers(); + + while (peers.hasNext()) { - logger.error("Could not hang up : " + peer - + " caused by the following exception: " + e); + CallPeer peer = peers.next(); + OperationSetBasicTelephony telephony + = pps.getOperationSet(OperationSetBasicTelephony.class); + + try + { + telephony.hangupCallPeer(peer); + } + catch (OperationFailedException e) + { + logger.error("Could not hang up : " + peer + + " caused by the following exception: " + e); + } } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java index b97753ef9..7f0350ef5 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java @@ -137,6 +137,13 @@ public ConferenceCallPanel(CallPanel callPanel, Call c) while (iterator.hasNext()) this.addCallPeerPanel(iterator.next()); + iterator = this.call.getCrossProtocolCallPeers(); + + while (iterator.hasNext()) + { + this.addCallPeerPanel(iterator.next()); + } + scrollPane.setBorder(null); /* * The scrollPane seems to receive only a few pixels of width at times diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java index 271c18be4..fbc0dcc0d 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java @@ -616,10 +616,9 @@ public void soundLevelChanged( { ConferenceMember key = e.getKey(); Integer value = e.getValue(); - Contact contact = focusPeerPanel.getCallPeerContact(); + String address = focusPeerPanel.getCallPeerContactAddress(); - if(key.getAddress().equals( - contact.getAddress())) + if(key.getAddress().equals(address)) { focusPeerPanel.updateSoundBar(value); break; diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java index 52e00aa78..0b032ff40 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java @@ -479,12 +479,12 @@ public UIVideoHandler getVideoHandler() } /** - * Returns CallPeer contact. + * Returns CallPeer contact address. * - * @return CallPeer contact + * @return CallPeer contact address */ - public Contact getCallPeerContact() + public String getCallPeerContactAddress() { - return callPeer.getContact(); + return callPeer.getURI(); } } diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index b06b66763..997f96466 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -1394,7 +1394,7 @@ public void setDevice(MediaDevice device) * Copy the playback from the old MediaDeviceSession into the new * MediaDeviceSession in order to prevent the recreation of the * playback of the ReceiveStream(s) when just changing the - * MediaDevice of this MediaSteam. + * MediaDevice of this MediaSteam. */ if (oldValue != null) deviceSession.copyPlayback(oldValue); @@ -1696,7 +1696,7 @@ private void startReceiveStreams() * It turns out that the receiveStreams list of rtpManager can be * empty. As a workaround, use the receiveStreams of this instance. */ - if (receiveStreams.isEmpty() && (this.receiveStreams != null)) + if (receiveStreams.isEmpty() && (this.receiveStreams != null)) receiveStreams = this.receiveStreams; for (ReceiveStream receiveStream : receiveStreams) @@ -1869,7 +1869,7 @@ private void stopReceiveStreams() * It turns out that the receiveStreams list of rtpManager can be * empty. As a workaround, use the receiveStreams of this instance. */ - if (receiveStreams.isEmpty() && (this.receiveStreams != null)) + if (receiveStreams.isEmpty() && (this.receiveStreams != null)) receiveStreams = this.receiveStreams; for (ReceiveStream receiveStream : receiveStreams) diff --git a/src/net/java/sip/communicator/impl/neomedia/device/VideoTranslatorMediaDevice.java b/src/net/java/sip/communicator/impl/neomedia/device/VideoTranslatorMediaDevice.java index 204d0f831..bdc11c2f0 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/VideoTranslatorMediaDevice.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/VideoTranslatorMediaDevice.java @@ -74,7 +74,8 @@ private synchronized void close( streamDeviceSessions.remove(streamDeviceSession); if (streamDeviceSessions.isEmpty()) { - deviceSession.close(); + if(deviceSession != null) + deviceSession.close(); deviceSession = null; } else diff --git a/src/net/java/sip/communicator/impl/protocol/gibberish/CallGibberishImpl.java b/src/net/java/sip/communicator/impl/protocol/gibberish/CallGibberishImpl.java index 7191a3175..f82882f4c 100644 --- a/src/net/java/sip/communicator/impl/protocol/gibberish/CallGibberishImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/gibberish/CallGibberishImpl.java @@ -176,4 +176,22 @@ public void removeLocalUserSoundLevelListener( SoundLevelListener l) { } + + /** + * Notified when a call are added to a CallGroup. + * + * @param evt event + */ + public void callAdded(CallGroupEvent evt) + { + } + + /** + * Notified when a call are removed from a CallGroup. + * + * @param evt event + */ + public void callRemoved(CallGroupEvent evt) + { + } } diff --git a/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java b/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java index da087a0d5..473822667 100644 --- a/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java @@ -109,6 +109,16 @@ public String getAddress() return peerAddress; } + /** + * Returns full URI of the address. + * + * @return full URI of the address + */ + public String getURI() + { + return "gibberish:" + peerAddress; + } + /** * Returns a reference to the call that this peer belongs to. * diff --git a/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java b/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java index aec40e47e..b192fc253 100644 --- a/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java @@ -120,6 +120,43 @@ public Call createCall(Contact callee) return createCall(callee.getAddress()); } + /** + * Creates a new Call and invites a specific CallPeer to + * it given by her String URI. + * + * @param callee the address of the callee who we should invite to a new + * Call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + */ + public Call createCall(String callee, CallGroup group) + throws OperationFailedException + { + return createCall(callee); + } + + /** + * Creates a new Call and invites a specific CallPeer to + * it given by her Contact. + * + * @param callee the address of the callee who we should invite to a new + * call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + */ + public Call createCall(Contact callee, CallGroup group) + throws OperationFailedException + { + return createCall(callee); + } + + /** * Returns an iterator over all currently active calls. * diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java index cfcdfb67d..f0b5d9061 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallGTalkImpl.java @@ -134,7 +134,7 @@ public CallPeerGTalkImpl processGTalkInitiate(SessionIQ sessionIQ) // if this was the first peer we added in this call then the call is // new and we also need to notify everyone of its creation. - if(this.getCallPeerCount() == 1) + if(this.getCallPeerCount() == 1 && this.getCallGroup() == null) parentOpSet.fireCallEvent( CallEvent.CALL_RECEIVED, this); return callPeer; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java index d1a4cc60c..19d6dd78d 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java @@ -289,8 +289,21 @@ public CallPeerJabberImpl initiateSession( // if this was the first peer we added in this call then the call is // new and we also need to notify everyone of its creation. - if(getCallPeerCount() == 1) + if(getCallPeerCount() == 1 && getCallGroup() == null) + { parentOpSet.fireCallEvent(CallEvent.CALL_INITIATED, this); + } + else if(getCallGroup() != null) + { + // only TelephonyConferencing OperationSet should know about it + CallEvent cEvent = new CallEvent(this, CallEvent.CALL_INITIATED); + AbstractOperationSetTelephonyConferencing opSet = + (AbstractOperationSetTelephonyConferencing) + getProtocolProvider().getOperationSet( + OperationSetTelephonyConferencing.class); + if(opSet != null) + opSet.outgoingCallCreated(cEvent); + } CallPeerMediaHandlerJabberImpl mediaHandler = callPeer.getMediaHandler(); @@ -395,4 +408,24 @@ public CallPeerJabberImpl getPeerBySessInitPacketID(String id) } return null; } + + /** + * Notified when a call are added to a CallGroup. + * + * @param evt event + */ + public void callAdded(CallGroupEvent evt) + { + Iterator peers = getCallPeers(); + + // reflect conference focus + while(peers.hasNext()) + { + setConferenceFocus(true); + CallPeerJabberImpl callPeer = peers.next(); + callPeer.sendCoinSessionInfo(true); + } + + super.callAdded(evt); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java index 13e74a305..acf39d702 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java @@ -84,6 +84,16 @@ public String getAddress() return peerJID; } + /** + * Returns full URI of the address. + * + * @return full URI of the address + */ + public String getURI() + { + return "xmpp:" + peerJID; + } + /** * Specifies the address, phone number, or other protocol specific * identifier that represents this call peer. This method is to be @@ -269,7 +279,7 @@ protected synchronized void initiateSession( { return; } - + getMediaHandler().harvestCandidates(offer.getPayloadTypes(), new CandidatesSender() { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java index 94ddefd50..e21b67079 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java @@ -119,6 +119,16 @@ public String getAddress() return peerJID; } + /** + * Returns full URI of the address. + * + * @return full URI of the address + */ + public String getURI() + { + return "xmpp:" + peerJID; + } + /** * Specifies the address, phone number, or other protocol specific * identifier that represents this call peer. This method is to be diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java index 2105b98b3..361490888 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java @@ -126,18 +126,21 @@ else if (registrationState == RegistrationState.UNREGISTERED) * * @param callee the address of the callee who we should invite to a new * Call + * @param group CallGroup from which the Call will belong * @return a newly created Call. The specified callee is * available in the Call as a CallPeer * @throws OperationFailedException with the corresponding code if we fail * to create the call * @see OperationSetBasicTelephony#createCall(String) */ - public Call createCall(String callee) + public Call createCall(String callee, CallGroup group) throws OperationFailedException { CallJabberImpl call = new CallJabberImpl(this); CallPeer callPeer = null; + call.setCallGroup(group); + callPeer = createOutgoingCall(call, callee); if (callPeer == null) { @@ -155,6 +158,43 @@ public Call createCall(String callee) return call; } + /** + * Creates a new Call and invites a specific CallPeer + * to it given by her Contact. + * + * @param callee the address of the callee who we should invite to a new + * call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + * @see OperationSetBasicTelephony#createCall(Contact) + */ + public Call createCall(Contact callee, CallGroup group) + throws OperationFailedException + { + return createCall(callee.getAddress(), group); + } + + /** + * Creates a new Call and invites a specific CallPeer to + * it given by her String URI. + * + * @param callee the address of the callee who we should invite to a new + * Call + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + * @see OperationSetBasicTelephony#createCall(String) + */ + public Call createCall(String callee) + throws OperationFailedException + { + return createCall(callee, null); + } + /** * Creates a new Call and invites a specific CallPeer * to it given by her Contact. @@ -170,7 +210,7 @@ public Call createCall(String callee) public Call createCall(Contact callee) throws OperationFailedException { - return createCall(callee.getAddress()); + return createCall(callee.getAddress(), null); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java index ee427b75d..f28e504a9 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java @@ -101,7 +101,7 @@ protected void basicTelephonyChanged( */ protected void notifyAll(Call call) { - if(!call.isConferenceFocus()) + if(call.getCallGroup() == null && !call.isConferenceFocus()) { return; } @@ -128,6 +128,9 @@ protected void notifyAll(Call call) */ private void notify(CallPeer callPeer) { + if(!(callPeer instanceof CallPeerJabberImpl)) + return; + // check that callPeer supports COIN before sending him a // conference-info String to = getBasicTelephony().getFullCalleeURI(callPeer.getAddress()); @@ -166,11 +169,11 @@ private void notify(CallPeer callPeer) * @return list of media packet extension */ private List getMedia( - CallPeerJabberImpl callPeer, + MediaAwareCallPeer callPeer, boolean remote) { MediaPacketExtension ext = null; - CallPeerMediaHandlerJabberImpl mediaHandler = + CallPeerMediaHandler mediaHandler = callPeer.getMediaHandler(); List ret = new ArrayList(); @@ -212,7 +215,7 @@ private List getMedia( * @return user packet extension */ private UserPacketExtension getUser( - CallPeerJabberImpl callPeer) + MediaAwareCallPeer callPeer) { UserPacketExtension ext = new UserPacketExtension( callPeer.getAddress()); @@ -222,7 +225,7 @@ private UserPacketExtension getUser( ext.setDisplayText(callPeer.getDisplayName()); EndpointStatusType status = getEndpointStatus(callPeer); - endpoint = new EndpointPacketExtension(callPeer.getAddress()); + endpoint = new EndpointPacketExtension(callPeer.getURI()); endpoint.setStatus(status); medias = getMedia(callPeer, true); @@ -251,7 +254,8 @@ private UserPacketExtension getUser( * an endpoint XML element and which describes the state of the * specified callPeer */ - private EndpointStatusType getEndpointStatus(CallPeerJabberImpl callPeer) + private EndpointStatusType getEndpointStatus( + MediaAwareCallPeer callPeer) { CallPeerState callPeerState = callPeer.getState(); @@ -314,7 +318,8 @@ private IQ getConferenceInfo(CallPeerJabberImpl callPeer, int version) // conference-state StatePacketExtension state = new StatePacketExtension(); - state.setUserCount(call.getCallPeerCount() + 1); + state.setUserCount(call.getCallPeerCount() + 1 + + call.getCrossProtocolCallPeerCount()); iq.addExtension(state); // users @@ -322,11 +327,11 @@ private IQ getConferenceInfo(CallPeerJabberImpl callPeer, int version) // user UserPacketExtension user = new UserPacketExtension( - parentProvider.getOurJID()); + "xmpp:" + parentProvider.getOurJID()); // endpoint EndpointPacketExtension endpoint = new EndpointPacketExtension( - parentProvider.getOurJID()); + "xmpp:" + parentProvider.getOurJID()); endpoint.setStatus(EndpointStatusType.connected); // media @@ -348,6 +353,16 @@ private IQ getConferenceInfo(CallPeerJabberImpl callPeer, int version) users.addChildExtension(ext); } + Iterator crossProtocolCallPeerIter = + call.getCrossProtocolCallPeers(); + + while (crossProtocolCallPeerIter.hasNext()) + { + UserPacketExtension ext = getUser( + (MediaAwareCallPeer)crossProtocolCallPeerIter.next()); + users.addChildExtension(ext); + } + iq.addExtension(users); return iq; } @@ -531,27 +546,6 @@ public void processPacket(Packet packet) handleCoin(coinIQ, callPeer); } - /** - * Removes the parameters (specified after a slash) from a specific - * address String if any are present in it. - * - * @param address the String value representing an address from - * which any parameters are to be removed - * @return a String representing the specified address - * without any parameters - */ - private static String stripParametersFromAddress(String address) - { - if (address != null) - { - int parametersBeginIndex = address.indexOf('/'); - - if (parametersBeginIndex > -1) - address = address.substring(0, parametersBeginIndex); - } - return address; - } - /** * Handle Coin IQ. * @@ -594,9 +588,7 @@ private void handleCoin(CoinIQ coinIQ, CallPeerJabberImpl callPeer) if(u.getAttribute("entity") != null) { - address - = stripParametersFromAddress( - (String)u.getAttribute("entity")); + address = (String)u.getAttribute("entity"); } if ((address == null) || (address.length() < 1)) diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockCall.java b/src/net/java/sip/communicator/impl/protocol/mock/MockCall.java index baea70fe0..45edcf6b9 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockCall.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockCall.java @@ -26,6 +26,11 @@ public class MockCall */ private static final Logger logger = Logger.getLogger(MockCall.class); + /** + * Constructs a new MockCall. + * + * @param sourceProvider Provider + */ public MockCall(MockProvider sourceProvider) { super(sourceProvider); @@ -150,4 +155,22 @@ public void removeLocalUserSoundLevelListener( SoundLevelListener l) { } + + /** + * Notified when a call are added to a CallGroup. + * + * @param evt event + */ + public void callAdded(CallGroupEvent evt) + { + } + + /** + * Notified when a call are removed from a CallGroup. + * + * @param evt event + */ + public void callRemoved(CallGroupEvent evt) + { + } } diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockCallPeer.java b/src/net/java/sip/communicator/impl/protocol/mock/MockCallPeer.java index e7f8109d7..dfdc52b39 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockCallPeer.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockCallPeer.java @@ -52,6 +52,16 @@ public String getAddress() return peerAddress; } + /** + * Returns full URI of the address. + * + * @return full URI of the address + */ + public String getURI() + { + return "mock:" + peerAddress; + } + /** * Returns a reference to the call that this peer belongs to. * diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java index 9ac56d483..7c4295935 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java @@ -103,6 +103,43 @@ public Call createCall(Contact callee) throws OperationFailedException return createNewCall(callee.getAddress()); } + /** + * Creates a new Call and invites a specific CallPeer to + * it given by her String URI. + * + * @param callee the address of the callee who we should invite to a new + * Call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + */ + public Call createCall(String callee, CallGroup group) + throws OperationFailedException, + ParseException + { + return createCall(callee); + } + + /** + * Creates a new Call and invites a specific CallPeer to + * it given by her Contact. + * + * @param callee the address of the callee who we should invite to a new + * call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + */ + public Call createCall(Contact callee, CallGroup group) + throws OperationFailedException + { + return createCall(callee); + } + private Call createNewCall(String address) { MockCall newCall = new MockCall(protocolProvider); diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java index f6fc23418..97cacf3e1 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java @@ -150,6 +150,16 @@ public String getAddress() return sipURI.getUser() + "@" + sipURI.getHost(); } + /** + * Returns full URI of the address. + * + * @return full URI of the address + */ + public String getURI() + { + return getPeerAddress().getURI().toString(); + } + /** * Returns the address of the remote party (making sure that it corresponds * to the latest address we've received) and caches it. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java index 4005d97e8..6c1fb62a8 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java @@ -301,11 +301,14 @@ else if(mediaType.equals(MediaType.AUDIO)) logger.warn("Error getting media types", t); } - getParentOperationSet().fireCallEvent( (incomingCall + if(this.getCallGroup() == null) + { + getParentOperationSet().fireCallEvent( (incomingCall ? CallEvent.CALL_RECEIVED : CallEvent.CALL_INITIATED), this, mediaDirections); + } } return callPeer; @@ -407,4 +410,33 @@ public void setInitialQualityPreferences(QualityPreset qualityPreferences) { this.initialQualityPreferences = qualityPreferences; } + + /** + * Notified when a call are added to a CallGroup. + * + * @param evt event + */ + public void callAdded(CallGroupEvent evt) + { + Iterator peers = getCallPeers(); + + // reinvite peers to reflect conference focus + while(peers.hasNext()) + { + setConferenceFocus(true); + CallPeerSipImpl callPeer = peers.next(); + + try + { + callPeer.sendReInvite(); + } + catch(OperationFailedException e) + { + logger.info("Failed to reinvite peer: " + + callPeer.getAddress()); + } + } + + super.callAdded(evt); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java index c9b4ccbe3..4bff06e2e 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java @@ -111,9 +111,7 @@ public Call createCall(String callee) throws OperationFailedException, ParseException { - Address toAddress = protocolProvider.parseAddressString(callee); - - return createOutgoingCall(toAddress, null); + return createCall(callee, null); } /** @@ -129,6 +127,47 @@ public Call createCall(String callee) * @see OperationSetBasicTelephony#createCall(Contact) */ public Call createCall(Contact callee) throws OperationFailedException + { + return createCall(callee, null); + } + + /** + * Creates a new Call and invites a specific CallPeer to + * it given by her String URI. + * + * @param callee the address of the callee who we should invite to a new + * Call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + * @throws ParseException if callee is not a valid SIP address + * String + */ + public Call createCall(String callee, CallGroup group) + throws OperationFailedException, + ParseException + { + Address toAddress = protocolProvider.parseAddressString(callee); + + return createOutgoingCall(toAddress, null, group); + } + + /** + * Creates a new Call and invites a specific CallPeer + * to it given by her Contact. + * + * @param callee the address of the callee who we should invite to a new + * call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + */ + public Call createCall(Contact callee, CallGroup group) + throws OperationFailedException { Address toAddress; @@ -143,7 +182,7 @@ public Call createCall(Contact callee) throws OperationFailedException throw new IllegalArgumentException(ex.getMessage()); } - return createOutgoingCall(toAddress, null); + return createOutgoingCall(toAddress, null, group); } /** @@ -185,10 +224,12 @@ protected synchronized CallSipImpl createOutgoingCall() * to create the call. */ private synchronized CallSipImpl createOutgoingCall(Address calleeAddress, - javax.sip.message.Message cause) throws OperationFailedException + javax.sip.message.Message cause, CallGroup group) + throws OperationFailedException { CallSipImpl call = createOutgoingCall(); + call.setCallGroup(group); call.invite(calleeAddress, cause); return call; @@ -563,7 +604,7 @@ public boolean processResponse(ResponseEvent responseEvent) * We have to bypass SIP specifications in the SIP NOTIFY * message is desktop sharing specific and thus do not close the * call. - * + * * XXX this is not an optimal solution, the ideal will be * to prevent disordering. */ @@ -1328,7 +1369,8 @@ private void processRefer(ServerTransaction serverTransaction, CallSipImpl referToCall; try { - referToCall = createOutgoingCall(referToAddress, referRequest); + referToCall = createOutgoingCall(referToAddress, referRequest, + null); } catch (OperationFailedException ex) { diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java index 2d4224ed1..635300466 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java @@ -252,9 +252,13 @@ protected void basicTelephonyChanged( */ public void callPeerAdded(CallPeerEvent event) { + super.callPeerAdded(event); + + if(!(event.getSourceCallPeer() instanceof CallPeerSipImpl)) + return; + CallPeerSipImpl callPeer = (CallPeerSipImpl) event.getSourceCallPeer(); callPeer.addMethodProcessorListener(this); - super.callPeerAdded(event); } /** @@ -331,7 +335,8 @@ private String getConferenceInfoXML(CallPeerSipImpl callPeer, int version) append(xml, "<", ELEMENT_CONFERENCE_STATE, ">"); // append(xml, "<", ELEMENT_USER_COUNT, ">"); - xml.append(call.getCallPeerCount() + 1); + xml.append(call.getCallPeerCount() + 1 + + call.getCrossProtocolCallPeerCount()); // append(xml, ""); // @@ -375,6 +380,15 @@ private String getConferenceInfoXML(CallPeerSipImpl callPeer, int version) while (callPeerIter.hasNext()) getUserXML(callPeerIter.next(), xml); + // cross-protocol CallPeer + Iterator crossProtocolCallPeerIter = + call.getCrossProtocolCallPeers(); + + while (crossProtocolCallPeerIter.hasNext()) + getUserXML( + (MediaAwareCallPeer)crossProtocolCallPeerIter.next(), + xml); + // append(xml, ""); // @@ -477,7 +491,7 @@ private String getEndpointStatus(Node endpoint) * an endpoint XML element and which describes the state of the * specified callPeer */ - private String getEndpointStatusXML(CallPeerSipImpl callPeer) + private String getEndpointStatusXML(MediaAwareCallPeer callPeer) { CallPeerState callPeerState = callPeer.getState(); @@ -523,11 +537,11 @@ private String getEndpointStatusXML(CallPeerSipImpl callPeer) * callPeer */ private void getMediaXML( - CallPeerSipImpl callPeer, + MediaAwareCallPeer callPeer, boolean remote, StringBuffer xml) { - CallPeerMediaHandlerSipImpl mediaHandler = callPeer.getMediaHandler(); + CallPeerMediaHandler mediaHandler = callPeer.getMediaHandler(); for (MediaType mediaType : MediaType.values()) { @@ -585,7 +599,8 @@ private void getMediaXML( * tree describing the conference participation of the specified * callPeer to */ - private void getUserXML(CallPeerSipImpl callPeer, StringBuffer xml) + private void getUserXML(MediaAwareCallPeer callPeer, + StringBuffer xml) { // append(xml, "<", ELEMENT_USER); @@ -594,7 +609,7 @@ private void getUserXML(CallPeerSipImpl callPeer, StringBuffer xml) xml, " entity=\"", domElementWriter - .encode(stripParametersFromAddress(callPeer.getAddress())), + .encode(stripParametersFromAddress(callPeer.getURI())), "\""); // state xml.append(" state=\"full\">"); diff --git a/src/net/java/sip/communicator/service/protocol/AbstractCall.java b/src/net/java/sip/communicator/service/protocol/AbstractCall.java index cef57c239..ec20c22f5 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractCall.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractCall.java @@ -29,6 +29,17 @@ public abstract class AbstractCall callPeers = new Vector(); + /** + * List of CallPeer from other protocol (cross-protocol conference + * call). + */ + private Vector crossProtocolCallPeers = new Vector(); + + /** + * The CallGroup of this Call. + */ + protected CallGroup callGroup = null; + /** * Creates a new Call instance. * @@ -73,6 +84,43 @@ protected Vector getCallPeersVector() return callPeers; } + /** + * Returns an iterator over all cross-protocol call peers. + * + * @return an Iterator over all cross-protocol peers currently involved in + * the call. + */ + public Iterator getCrossProtocolCallPeers() + { + return new LinkedList(getCrossProtocolCallPeersVector()). + iterator(); + } + + /** + * Returns the number of cross-protocol peers currently associated with this + * call. + * + * @return an int indicating the number of cross-protocol peers + * currently associated with this call. + */ + public int getCrossProtocolCallPeerCount() + { + return crossProtocolCallPeers.size(); + } + + /** + * Returns the {@link Vector} containing cross-protocol {@link CallPeer}s + * currently part of this call. This method should eventually be removed and + * code that is using it in the descendants should be brought here. + * + * @return the {@link Vector} containing cross-protocol {@link CallPeer}s + * currently participating in this call. + */ + protected Vector getCrossProtocolCallPeersVector() + { + return crossProtocolCallPeers; + } + /** * Returns a reference to the ProtocolProviderService instance * that created this call. @@ -85,4 +133,25 @@ public U getProtocolProvider() { return (U) super.getProtocolProvider(); } + + /** + * Returns the CallGroup from which this Call belongs. + * + * @return CallGroup or null if the Call does not belongs + * to a CallGroup + */ + public CallGroup getCallGroup() + { + return callGroup; + } + + /** + * Sets the CallGroup of this Call. + * + * @param callGroup CallGroup to set + */ + public void setCallGroup(CallGroup callGroup) + { + this.callGroup = callGroup; + } } diff --git a/src/net/java/sip/communicator/service/protocol/Call.java b/src/net/java/sip/communicator/service/protocol/Call.java index 2995fc07a..cc34de9ad 100644 --- a/src/net/java/sip/communicator/service/protocol/Call.java +++ b/src/net/java/sip/communicator/service/protocol/Call.java @@ -21,6 +21,7 @@ * @author Emanuel Onica */ public abstract class Call + implements CallGroupListener { /** * Our class logger. @@ -301,7 +302,7 @@ protected void setCallState(CallState newState) * * @param newState a reference to the CallState instance that the * call is to enter. - * @param cause the event that is the cause of the current change of state. + * @param cause the event that is the cause of the current change of state. */ protected void setCallState(CallState newState, CallPeerChangeEvent cause) { @@ -336,6 +337,21 @@ public boolean isSipZrtpAttribute() return sipZrtpAttribute; } + /** + * Sets the CallGroup of this Call. + * + * @param callGroup CallGroup to set + */ + public abstract void setCallGroup(CallGroup callGroup); + + /** + * Returns the CallGroup from which this Call belongs. + * + * @return CallGroup or null if the Call does not belongs + * to a CallGroup + */ + public abstract CallGroup getCallGroup(); + /** * Returns an iterator over all call peers. * @@ -351,6 +367,23 @@ public boolean isSipZrtpAttribute() */ public abstract int getCallPeerCount(); + /** + * Returns an iterator over all cross-protocol call peers. + * + * @return an Iterator over all cross-protocol peers currently involved in + * the call. + */ + public abstract Iterator getCrossProtocolCallPeers(); + + /** + * Returns the number of cross-protocol peers currently associated with this + * call. + * + * @return an int indicating the number of cross-protocol peers + * currently associated with this call. + */ + public abstract int getCrossProtocolCallPeerCount(); + /** * Gets the indicator which determines whether the local peer represented by * this Call is acting as a conference focus and thus should send @@ -376,5 +409,6 @@ public boolean isSipZrtpAttribute() * related information. * @param l the SoundLevelListener to remove */ - public abstract void removeLocalUserSoundLevelListener(SoundLevelListener l); + public abstract void removeLocalUserSoundLevelListener( + SoundLevelListener l); } diff --git a/src/net/java/sip/communicator/service/protocol/CallGroup.java b/src/net/java/sip/communicator/service/protocol/CallGroup.java new file mode 100644 index 000000000..4615781a4 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/CallGroup.java @@ -0,0 +1,158 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.event.*; + +/** + * This class represents group of several calls. It is used to make + * cross-protocol conference calls. + * + * @author Sebastien Vincent + */ +public class CallGroup + implements CallChangeListener +{ + /** + * List of Calls. + */ + private List calls = new ArrayList(); + + /** + * Synchronization object. + */ + private final Object syncRoot = new Object(); + + /** + * Returns list of existing Calls in this CallGroup. + * + * @return list of existing Calls in this CallGroup. + */ + public List getCalls() + { + return calls; + } + + /** + * Fires CallGroupEvent. + * + * @param call source Call + * @param eventID event ID + */ + public void fireCallGroupEvent(Call call, int eventID) + { + CallGroupEvent evt = new CallGroupEvent(call, eventID); + + for(Call c : calls) + { + if(c == call) + continue; + + switch(eventID) + { + case CallGroupEvent.CALLGROUP_CALL_ADDED: + c.callAdded(evt); + call.callAdded(new CallGroupEvent(c, eventID)); + break; + case CallGroupEvent.CALLGROUP_CALL_REMOVED: + c.callRemoved(evt); + call.callRemoved(new CallGroupEvent(c, eventID)); + break; + default: + break; + } + } + } + + /** + * Adds a call. + * + * @param call call to add + */ + public void addCall(Call call) + { + synchronized(syncRoot) + { + if(!calls.contains(call)) + { + call.addCallChangeListener(this); + call.setCallGroup(this); + calls.add(call); + } + } + } + + /** + * Removes a call. + * + * @param call call to remove + */ + public void removeCall(Call call) + { + synchronized(syncRoot) + { + if(calls.contains(call)) + { + call.removeCallChangeListener(this); + calls.remove(call); + call.setCallGroup(null); + + } + } + } + + /** + * Indicates that a new call peer has joined the source call. + * + * @param evt the CallPeerEvent containing the source call + * and call peer. + */ + public void callPeerAdded(CallPeerEvent evt) + { + /* not used */ + } + + /** + * Indicates that a call peer has left the source call. + * + * @param evt the CallPeerEvent containing the source call + * and call peer. + */ + public void callPeerRemoved(CallPeerEvent evt) + { + /* not used */ + } + + /** + * Indicates that a change has occurred in the state of the source call. + * + * @param evt the CallChangeEvent instance containing the source + * calls and its old and new state. + */ + public void callStateChanged(CallChangeEvent evt) + { + Call call = evt.getSourceCall(); + if(evt.getEventType() == CallChangeEvent.CALL_STATE_CHANGE) + { + CallState state = (CallState)evt.getNewValue(); + + if(evt.getNewValue() == evt.getOldValue()) + return; + + if(state == CallState.CALL_ENDED) + { + fireCallGroupEvent(call, CallGroupEvent.CALLGROUP_CALL_REMOVED); + } + else if(state == CallState.CALL_IN_PROGRESS) + { + fireCallGroupEvent(call, CallGroupEvent.CALLGROUP_CALL_ADDED); + } + } + } +} diff --git a/src/net/java/sip/communicator/service/protocol/CallPeer.java b/src/net/java/sip/communicator/service/protocol/CallPeer.java index 832b439ea..855dd68ea 100644 --- a/src/net/java/sip/communicator/service/protocol/CallPeer.java +++ b/src/net/java/sip/communicator/service/protocol/CallPeer.java @@ -72,6 +72,14 @@ public interface CallPeer */ public String getAddress(); + /** + * Returns full URI of the address. For example sip:user@domain.org or + * xmpp:user@domain.org. + * + * @return full URI of the address + */ + public String getURI(); + /** * Returns an object representing the current state of that peer. * CallPeerState may vary among CONNECTING, RINGING, CALLING, BUSY, diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java index e1c00f5bc..657021428 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java @@ -88,6 +88,39 @@ public Call createCall(String uri) public Call createCall(Contact callee) throws OperationFailedException; + /** + * Creates a new Call and invites a specific CallPeer to + * it given by her String URI. + * + * @param uri the address of the callee who we should invite to a new + * Call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + * @throws ParseException if callee is not a valid SIP address + * String + */ + public Call createCall(String uri, CallGroup group) + throws OperationFailedException, + ParseException; + + /** + * Creates a new Call and invites a specific CallPeer + * to it given by her Contact. + * + * @param callee the address of the callee who we should invite to a new + * call + * @param group CallGroup from which the Call will belong + * @return a newly created Call. The specified callee is + * available in the Call as a CallPeer + * @throws OperationFailedException with the corresponding code if we fail + * to create the call + */ + public Call createCall(Contact callee, CallGroup group) + throws OperationFailedException; + /** * Indicates a user request to answer an incoming call from the specified * CallPeer. diff --git a/src/net/java/sip/communicator/service/protocol/event/CallGroupEvent.java b/src/net/java/sip/communicator/service/protocol/event/CallGroupEvent.java new file mode 100644 index 000000000..fe264bb44 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/event/CallGroupEvent.java @@ -0,0 +1,74 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol.event; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * An event class representing that an Call or CallPeer is + * added/removed to/from a CallGroup. + * + * @author Sebastien Vincent + */ +public class CallGroupEvent + extends EventObject +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * CALLGROUP_CALL_ADDED event name. + */ + public static final int CALLGROUP_CALL_ADDED = 0; + + /** + * CALLGROUP_CALL_REMOVED event name. + */ + public static final int CALLGROUP_CALL_REMOVED = 1; + + /** + * The id indicating the type of this event. + */ + private final int eventID; + + /** + * Constructor. + * + * @param call call source + * @param eventID event ID + */ + public CallGroupEvent(Call call, int eventID) + { + super(call); + this.eventID = eventID; + } + + /** + * Returns one of the CALLGROUP_XXX member ints indicating + * the type of this event. + * @return one of the CALLGROUP_XXX member ints indicating + * the type of this event. + */ + public int getEventID() + { + return this.eventID; + } + + /** + * Returns the source call. + * + * @return The source call + */ + public Call getSourceCall() + { + return (Call)getSource(); + } +} diff --git a/src/net/java/sip/communicator/service/protocol/event/CallGroupListener.java b/src/net/java/sip/communicator/service/protocol/event/CallGroupListener.java new file mode 100644 index 000000000..5a6e3ffcd --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/event/CallGroupListener.java @@ -0,0 +1,33 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol.event; + +import java.util.*; + +/** + * Instances of this class are used for listening for notifications coming out + * of a CallGroup. + * + * @author Sebastien Vincent + */ +public interface CallGroupListener + extends EventListener +{ + /** + * Notified when a call are added to a CallGroup. + * + * @param evt event + */ + public void callAdded(CallGroupEvent evt); + + /** + * Notified when a call are removed from a CallGroup. + * + * @param evt event + */ + public void callRemoved(CallGroupEvent evt); +} diff --git a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java index e34922fa8..793b58bee 100644 --- a/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java +++ b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetTelephonyConferencing.java @@ -318,11 +318,10 @@ else if (RegistrationState.UNREGISTERED.equals(newState)) * @param event a CallPeerEvent which specifies the * CallPeer which has been added to a Call */ - @SuppressWarnings("unchecked") public void callPeerAdded(CallPeerEvent event) { - MediaAwareCallPeerT callPeer = - (MediaAwareCallPeerT)event.getSourceCallPeer(); + MediaAwareCallPeer callPeer = + (MediaAwareCallPeer)event.getSourceCallPeer(); callPeer.addCallPeerListener(callPeerListener); callPeer.getMediaHandler().addPropertyChangeListener(this); @@ -357,7 +356,10 @@ public void callPeerRemoved(CallPeerEvent event) */ private void callPeersChanged(CallPeerEvent event) { - notifyAll(event.getSourceCall()); + Call call = event.getSourceCall(); + + notifyAll(call); + notifyCallsInGroup(call); } /** @@ -389,6 +391,7 @@ public void propertyChange(PropertyChangeEvent event) if (call != null) { notifyAll(call); + notifyCallsInGroup(call); } } } @@ -483,6 +486,33 @@ public void callStateChanged(CallChangeEvent event) { } + /** + * Notify the Calls in the CallGroup if any. + * + * @param call the Call + */ + private void notifyCallsInGroup(Call call) + { + if(call.getCallGroup() != null) + { + CallGroup group = call.getCallGroup(); + for(Call c : group.getCalls()) + { + if(c == call) + continue; + + AbstractOperationSetTelephonyConferencing opSet = + (AbstractOperationSetTelephonyConferencing) + c.getProtocolProvider().getOperationSet( + OperationSetTelephonyConferencing.class); + if(opSet != null) + { + opSet.notifyAll(c); + } + } + } + } + /** * Notifies all CallPeer associated with and established in a * specific call for conference information. diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java index 00d5407c7..f657e73d7 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java @@ -41,6 +41,7 @@ public abstract class MediaAwareCall< V extends ProtocolProviderService> extends AbstractCall implements CallPeerListener, + CallChangeListener, PropertyChangeListener { /** @@ -429,6 +430,27 @@ public MediaDevice getDefaultDevice(MediaType mediaType) { MediaDevice device; + if(conferenceAudioMixer == null && mediaType == MediaType.AUDIO && + callGroup != null && callGroup.getCalls().size() > 0) + { + conferenceAudioMixer = + ((MediaAwareCall)callGroup.getCalls().get(0)). + conferenceAudioMixer; + device = conferenceAudioMixer; + } + /* + else if(conferenceVideoMixer == null && mediaType == MediaType.VIDEO && + callGroup != null && callGroup.getCalls().size() > 0 && + isConferenceFocus()) + { + conferenceVideoMixer = + ((MediaAwareCall)callGroup.getCalls().get(0)). + conferenceVideoMixer; + device = conferenceVideoMixer; + } + */ + + switch (mediaType) { case AUDIO: @@ -469,7 +491,7 @@ public MediaDevice getDefaultDevice(MediaType mediaType) case VIDEO: if (isConferenceFocus()) { - if ((conferenceVideoMixer == null) && (device != null)) + if ((conferenceVideoMixer == null) && (device != null)) conferenceVideoMixer = mediaService.createMixer(device); if (conferenceVideoMixer != null) device = conferenceVideoMixer; @@ -925,6 +947,7 @@ public void propertyChange(PropertyChangeEvent evt) if(propertyName.equals("CHANGE_CAPTURE_DEV")) { conferenceAudioMixer = null; + audioDevice = null; for(MediaAwareCallPeer p : getCallPeersVector()) { MediaStream stream = @@ -938,4 +961,76 @@ public void propertyChange(PropertyChangeEvent evt) } } } + + /** + * Indicates that a new call peer has joined the source call. + * + * @param evt the CallPeerEvent containing the source call + * and call peer. + */ + public void callPeerAdded(CallPeerEvent evt) + { + /* peer from another protocol (for cross-protocol conference call) */ + getCrossProtocolCallPeersVector().add(evt.getSourceCallPeer()); + } + + /** + * Indicates that a call peer has left the source call. + * + * @param evt the CallPeerEvent containing the source call + * and call peer. + */ + public void callPeerRemoved(CallPeerEvent evt) + { + /* peer from another protocol (for cross-protocol conference call) */ + getCrossProtocolCallPeersVector().remove(evt.getSourceCallPeer()); + } + + /** + * Indicates that a change has occurred in the state of the source call. + * + * @param evt the CallChangeEvent instance containing the source + * calls and its old and new state. + */ + public void callStateChanged(CallChangeEvent evt) + { + } + + /** + * Notified when a call are added to a CallGroup. + * + * @param evt event + */ + public void callAdded(CallGroupEvent evt) + { + Call c = evt.getSourceCall(); + c.addCallChangeListener(this); + + Iterator peers = c.getCallPeers(); + while(peers.hasNext()) + { + CallPeer p = peers.next(); + getCrossProtocolCallPeersVector().add(p); + fireCallPeerEvent(p, CallPeerEvent.CALL_PEER_ADDED); + } + } + + /** + * Notified when a call are removed from a CallGroup. + * + * @param evt event + */ + public void callRemoved(CallGroupEvent evt) + { + Call c = evt.getSourceCall(); + c.removeCallChangeListener(this); + + Iterator peers = c.getCallPeers(); + while(peers.hasNext()) + { + CallPeer p = peers.next(); + getCrossProtocolCallPeersVector().remove(p); + fireCallPeerEvent(p, CallPeerEvent.CALL_PEER_REMOVED); + } + } }