diff --git a/build/android/Android.mk b/build/android/Android.mk index 5c99892f2..2abfb5eb0 100755 --- a/build/android/Android.mk +++ b/build/android/Android.mk @@ -47,7 +47,7 @@ LOCAL_SRC_FILES := \ call_params.c \ chat.c \ chat_file_transfer.c \ - conference.c \ + conference.cc \ content.c \ ec-calibrator.c \ enum.c \ diff --git a/console/commands.c b/console/commands.c index f2c316325..cf462b990 100644 --- a/console/commands.c +++ b/console/commands.c @@ -2452,7 +2452,7 @@ static void lpc_display_call_states(LinphoneCore *lc){ const char *flag; bool_t in_conference; call=(LinphoneCall*)elem->data; - in_conference=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); + in_conference=(linphone_call_get_conference(call) != NULL); tmp=linphone_call_get_remote_address_as_string (call); flag=in_conference ? "conferencing" : ""; flag=linphone_call_has_transfer_pending(call) ? "transfer pending" : flag; diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt index 8ca65da17..f53b41c06 100644 --- a/coreapi/CMakeLists.txt +++ b/coreapi/CMakeLists.txt @@ -75,7 +75,6 @@ set(LINPHONE_SOURCE_FILES_C call_params.c chat.c chat_file_transfer.c - conference.c contactprovider.c content.c dict.c @@ -115,7 +114,7 @@ set(LINPHONE_SOURCE_FILES_C xmlrpc.c vtables.c ) -set(LINPHONE_SOURCE_FILES_CXX ) +set(LINPHONE_SOURCE_FILES_CXX conference.cc) set(LINPHONE_SOURCE_FILES_OBJC) if (IOS) diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index d9ce1d1f4..e24eba457 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -56,7 +56,7 @@ liblinphone_la_SOURCES=\ call_params.c \ chat.c \ chat_file_transfer.c \ - conference.c \ + conference.cc \ contactprovider.c contactprovider.h contact_providers_priv.h \ content.c \ dict.c \ diff --git a/coreapi/address.c b/coreapi/address.c index 1299a5351..79ed08d60 100644 --- a/coreapi/address.c +++ b/coreapi/address.c @@ -129,6 +129,20 @@ LinphoneTransportType linphone_address_get_transport(const LinphoneAddress *uri) return (LinphoneTransportType)sal_address_get_transport(uri); } +/** + * Set the value of the method parameter +**/ +void linphone_address_set_method_param(LinphoneAddress *addr, const char *method) { + sal_address_set_method_param(addr, method); +} + +/** + * Get the value of the method parameter +**/ +const char *linphone_address_get_method_param(const LinphoneAddress *addr) { + return sal_address_get_method_param(addr); +} + /** * Removes address's tags and uri headers so that it is displayable to the user. **/ diff --git a/coreapi/bellesip_sal/sal_address_impl.c b/coreapi/bellesip_sal/sal_address_impl.c index 2546e7850..b6ed5b77e 100644 --- a/coreapi/bellesip_sal/sal_address_impl.c +++ b/coreapi/bellesip_sal/sal_address_impl.c @@ -114,6 +114,15 @@ const char* sal_address_get_transport_name(const SalAddress* addr){ return NULL; } +const char *sal_address_get_method_param(const SalAddress *addr) { + belle_sip_header_address_t* header_addr = BELLE_SIP_HEADER_ADDRESS(addr); + belle_sip_uri_t* uri = belle_sip_header_address_get_uri(header_addr); + if (uri) { + return belle_sip_uri_get_method_param(uri); + } + return NULL; +} + void sal_address_set_display_name(SalAddress *addr, const char *display_name){ belle_sip_header_address_t* header_addr = BELLE_SIP_HEADER_ADDRESS(addr); belle_sip_header_address_set_displayname(header_addr,display_name); @@ -213,6 +222,10 @@ void sal_address_set_transport_name(SalAddress* addr,const char *transport){ SAL_ADDRESS_SET(addr,transport_param,transport); } +void sal_address_set_method_param(SalAddress *addr, const char *method) { + SAL_ADDRESS_SET(addr, method_param, method); +} + SalAddress *sal_address_ref(SalAddress *addr){ return (SalAddress*)belle_sip_object_ref(BELLE_SIP_HEADER_ADDRESS(addr)); } diff --git a/coreapi/bellesip_sal/sal_op_call_transfer.c b/coreapi/bellesip_sal/sal_op_call_transfer.c index 84e529091..ffc4b945a 100644 --- a/coreapi/bellesip_sal/sal_op_call_transfer.c +++ b/coreapi/bellesip_sal/sal_op_call_transfer.c @@ -40,7 +40,7 @@ static void sal_op_set_referred_by(SalOp* op,belle_sip_header_referred_by_t* ref int sal_call_refer_to(SalOp *op, belle_sip_header_refer_to_t* refer_to, belle_sip_header_referred_by_t* referred_by){ char* tmp; - belle_sip_request_t* req=op->dialog?belle_sip_dialog_create_request(op->dialog,"REFER"):NULL; /*cannot create request if dialog not set yet*/ + belle_sip_request_t* req=op->dialog?belle_sip_dialog_create_request(op->dialog,"REFER"):sal_op_build_request(op, "REFER"); if (!req) { tmp=belle_sip_uri_to_string(belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(refer_to))); ms_error("Cannot refer to [%s] for op [%p]",tmp,op); diff --git a/coreapi/call_params.h b/coreapi/call_params.h index e3e6d1ee4..e536280a4 100644 --- a/coreapi/call_params.h +++ b/coreapi/call_params.h @@ -125,6 +125,10 @@ LINPHONE_PUBLIC const char *linphone_call_params_get_custom_header(const Linphon /** * Tell whether the call is part of the locally managed conference. + * @warning If a conference server is used to manage conferences, + * that function does not return TRUE even if the conference is running.
+ * If you want to test whether the conference is running, you should test + * whether linphone_core_get_conference() return a non-null pointer. * @param[in] cp LinphoneCallParams object * @return A boolean value telling whether the call is part of the locally managed conference. **/ diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 8b5f8eff4..af1cfb919 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -1065,7 +1065,15 @@ static void dtmf_received(SalOp *op, char dtmf){ static void refer_received(Sal *sal, SalOp *op, const char *referto){ LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal); LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op); - if (call){ + LinphoneAddress *refer_to_addr = linphone_address_new(referto); + char method[20] = ""; + + if(refer_to_addr) { + const char *tmp = linphone_address_get_method_param(refer_to_addr); + if(tmp) strncpy(method, tmp, sizeof(method)); + linphone_address_destroy(refer_to_addr); + } + if (call && (strlen(method) == 0 || strcmp(method, "INVITE") == 0)) { if (call->refer_to!=NULL){ ms_free(call->refer_to); } diff --git a/coreapi/conference.c b/coreapi/conference.c deleted file mode 100644 index 66898d9a2..000000000 --- a/coreapi/conference.c +++ /dev/null @@ -1,430 +0,0 @@ -/*************************************************************************** - * conference.c - * - * Mon Sep 12, 2011 - * Copyright 2011 Belledonne Communications - * Author: Simon Morlat - * Email simon dot morlat at linphone dot org - ****************************************************************************/ - -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "private.h" -#include "lpconfig.h" - -#include "mediastreamer2/msvolume.h" - -/** - * @addtogroup conferencing - * @{ -**/ - - -static int convert_conference_to_call(LinphoneCore *lc); - -static void conference_check_init(LinphoneConference *ctx, int samplerate){ - if (ctx->conf==NULL){ - MSAudioConferenceParams params; - params.samplerate=samplerate; - ctx->conf=ms_audio_conference_new(¶ms); - ctx->terminated=FALSE; - } -} - -static void remove_local_endpoint(LinphoneConference *ctx){ - if (ctx->local_endpoint){ - ms_audio_conference_remove_member(ctx->conf,ctx->local_endpoint); - ms_audio_endpoint_release_from_stream(ctx->local_endpoint); - ctx->local_endpoint=NULL; - audio_stream_stop(ctx->local_participant); - ctx->local_participant=NULL; - rtp_profile_destroy(ctx->local_dummy_profile); - } -} - -static int linphone_conference_get_size(LinphoneConference *conf){ - if (conf->conf == NULL) { - return 0; - } - return ms_audio_conference_get_size(conf->conf) - (conf->record_endpoint ? 1 : 0); -} - -static int remote_participants_count(LinphoneConference *ctx) { - int count=linphone_conference_get_size(ctx); - if (count==0) return 0; - if (!ctx->local_participant) return count; - return count -1; -} - -void linphone_core_conference_check_uninit(LinphoneCore *lc){ - LinphoneConference *ctx=&lc->conf_ctx; - if (ctx->conf){ - int remote_count=remote_participants_count(ctx); - ms_message("conference_check_uninit(): size=%i",linphone_conference_get_size(ctx)); - if (remote_count==1 && !ctx->terminated){ - convert_conference_to_call(lc); - } - if (remote_count==0){ - if (ctx->local_participant!=NULL) - remove_local_endpoint(ctx); - if (ctx->record_endpoint){ - ms_audio_conference_remove_member(ctx->conf,ctx->record_endpoint); - ms_audio_endpoint_destroy(ctx->record_endpoint); - ctx->record_endpoint=NULL; - } - } - - if (ms_audio_conference_get_size(ctx->conf)==0){ - ms_audio_conference_destroy(ctx->conf); - ctx->conf=NULL; - } - } -} - -void linphone_call_add_to_conf(LinphoneCall *call, bool_t muted){ - LinphoneCore *lc=call->core; - LinphoneConference *conf=&lc->conf_ctx; - MSAudioEndpoint *ep; - call->params->has_video = FALSE; - call->camera_enabled = FALSE; - ep=ms_audio_endpoint_get_from_stream(call->audiostream,TRUE); - ms_audio_conference_add_member(conf->conf,ep); - ms_audio_conference_mute_member(conf->conf,ep,muted); - call->endpoint=ep; -} - -void linphone_call_remove_from_conf(LinphoneCall *call){ - LinphoneCore *lc=call->core; - LinphoneConference *conf=&lc->conf_ctx; - - ms_audio_conference_remove_member(conf->conf,call->endpoint); - ms_audio_endpoint_release_from_stream(call->endpoint); - call->endpoint=NULL; -} - -static RtpProfile *make_dummy_profile(int samplerate){ - RtpProfile *prof=rtp_profile_new("dummy"); - PayloadType *pt=payload_type_clone(&payload_type_l16_mono); - pt->clock_rate=samplerate; - rtp_profile_set_payload(prof,0,pt); - return prof; -} - -static void add_local_endpoint(LinphoneConference *conf,LinphoneCore *lc){ - /*create a dummy audiostream in order to extract the local part of it */ - /* network address and ports have no meaning and are not used here. */ - AudioStream *st=audio_stream_new(65000,65001,FALSE); - MSSndCard *playcard=lc->sound_conf.lsd_card ? - lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard; - MSSndCard *captcard=lc->sound_conf.capt_sndcard; - const MSAudioConferenceParams *params=ms_audio_conference_get_params(conf->conf); - conf->local_dummy_profile=make_dummy_profile(params->samplerate); - - audio_stream_start_full(st, conf->local_dummy_profile, - "127.0.0.1", - 65000, - "127.0.0.1", - 65001, - 0, - 40, - NULL, - NULL, - playcard, - captcard, - linphone_core_echo_cancellation_enabled(lc) - ); - _post_configure_audio_stream(st,lc,FALSE); - conf->local_participant=st; - conf->local_endpoint=ms_audio_endpoint_get_from_stream(st,FALSE); - ms_audio_conference_add_member(conf->conf,conf->local_endpoint); - -} - -/** - * Returns the sound volume (mic input) of the local participant of the conference. - * @param lc the linphone core - * @return the measured input volume expressed in dbm0. - **/ -float linphone_core_get_conference_local_input_volume(LinphoneCore *lc){ - LinphoneConference *conf=&lc->conf_ctx; - AudioStream *st=conf->local_participant; - if (st && st->volsend && !conf->local_muted){ - float vol=0; - ms_filter_call_method(st->volsend,MS_VOLUME_GET,&vol); - return vol; - - } - return LINPHONE_VOLUME_DB_LOWEST; -} - -/** - * Merge a call into a conference. - * @param lc the linphone core - * @param call an established call, either in LinphoneCallStreamsRunning or LinphoneCallPaused state. - * - * If this is the first call that enters the conference, the virtual conference will be created automatically. - * If the local user was actively part of the call (ie not in paused state), then the local user is automatically entered into the conference. - * If the call was in paused state, then it is automatically resumed when entering into the conference. - * - * @return 0 if successful, -1 otherwise. -**/ -int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call){ - LinphoneConference *conf=&lc->conf_ctx; - - if (call->current_params->in_conference){ - ms_error("Already in conference"); - return -1; - } - conference_check_init(&lc->conf_ctx, lp_config_get_int(lc->config, "sound","conference_rate",16000)); - - if (call->state==LinphoneCallPaused){ - call->params->in_conference=TRUE; - call->params->has_video=FALSE; - linphone_core_resume_call(lc,call); - }else if (call->state==LinphoneCallStreamsRunning){ - LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); - params->in_conference=TRUE; - params->has_video=FALSE; - - if (call->audiostream || call->videostream){ - linphone_call_stop_media_streams(call); /*free the audio & video local resources*/ - linphone_call_init_media_streams(call); - } - if (call==lc->current_call){ - lc->current_call=NULL; - } - /*this will trigger a reINVITE that will later redraw the streams */ - /*FIXME probably a bit too much to just redraw streams !*/ - linphone_core_update_call(lc,call,params); - linphone_call_params_destroy(params); - add_local_endpoint(conf,lc); - }else{ - ms_error("Call is in state %s, it cannot be added to the conference.",linphone_call_state_to_string(call->state)); - return -1; - } - return 0; -} - -static int remove_from_conference(LinphoneCore *lc, LinphoneCall *call, bool_t active){ - int err=0; - char *str; - - if (!call->current_params->in_conference){ - if (call->params->in_conference){ - ms_warning("Not (yet) in conference, be patient"); - return -1; - }else{ - ms_error("Not in a conference."); - return -1; - } - } - call->params->in_conference=FALSE; - - str=linphone_call_get_remote_address_as_string(call); - ms_message("%s will be removed from conference", str); - ms_free(str); - if (active){ - LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); - params->in_conference=FALSE; - // reconnect local audio with this call - if (linphone_core_is_in_conference(lc)){ - ms_message("Leaving conference for reconnecting with unique call."); - linphone_core_leave_conference(lc); - } - ms_message("Updating call to actually remove from conference"); - err=linphone_core_update_call(lc,call,params); - linphone_call_params_destroy(params); - } else{ - ms_message("Pausing call to actually remove from conference"); - err=_linphone_core_pause_call(lc,call); - } - return err; -} - -static int convert_conference_to_call(LinphoneCore *lc){ - int err=0; - MSList *calls=lc->calls; - - if (remote_participants_count(&lc->conf_ctx)!=1){ - ms_error("No unique call remaining in conference."); - return -1; - } - - while (calls) { - LinphoneCall *rc=(LinphoneCall*)calls->data; - calls=calls->next; - if (rc->params->in_conference) { // not using current_param - bool_t active_after_removed=linphone_core_is_in_conference(lc); - err=remove_from_conference(lc, rc, active_after_removed); - break; - } - } - return err; -} - -int linphone_core_remove_from_conference(LinphoneCore *lc, LinphoneCall *call){ - int err; - char * str=linphone_call_get_remote_address_as_string(call); - ms_message("Removing call %s from the conference", str); - ms_free(str); - err=remove_from_conference(lc,call, FALSE); - if (err){ - ms_error("Error removing participant from conference."); - return err; - } - - if (remote_participants_count(&lc->conf_ctx)==1){ - ms_message("conference size is 1: need to be converted to plain call"); - err=convert_conference_to_call(lc); - } else { - ms_message("the conference need not to be converted as size is %i", remote_participants_count(&lc->conf_ctx)); - } - return err; -} - -bool_t linphone_core_is_in_conference(const LinphoneCore *lc){ - return lc->conf_ctx.local_participant!=NULL; -} - -/** - * Moves the local participant out of the conference. - * @param lc the linphone core - * When the local participant is out of the conference, the remote participants can continue to talk normally. - * @return 0 if successful, -1 otherwise. -**/ -int linphone_core_leave_conference(LinphoneCore *lc){ - LinphoneConference *conf=&lc->conf_ctx; - if (linphone_core_is_in_conference(lc)) - remove_local_endpoint(conf); - return 0; -} - -/** - * Moves the local participant inside the conference. - * @param lc the linphone core - * - * Makes the local participant to join the conference. - * Typically, the local participant is by default always part of the conference when joining an active call into a conference. - * However, by calling linphone_core_leave_conference() and linphone_core_enter_conference() the application can decide to temporarily - * move out and in the local participant from the conference. - * - * @return 0 if successful, -1 otherwise -**/ -int linphone_core_enter_conference(LinphoneCore *lc){ - LinphoneConference *conf; - if (linphone_core_sound_resources_locked(lc)) { - return -1; - } - if (lc->current_call != NULL) { - _linphone_core_pause_call(lc, lc->current_call); - } - conf=&lc->conf_ctx; - if (conf->local_participant==NULL) add_local_endpoint(conf,lc); - return 0; -} - -/** - * Add all calls into a conference. - * @param lc the linphone core - * - * Merge all established calls (either in LinphoneCallStreamsRunning or LinphoneCallPaused) into a conference. - * - * @return 0 if successful, -1 otherwise -**/ -int linphone_core_add_all_to_conference(LinphoneCore *lc) { - MSList *calls=lc->calls; - while (calls) { - LinphoneCall *call=(LinphoneCall*)calls->data; - calls=calls->next; - if (!call->current_params->in_conference) { - linphone_core_add_to_conference(lc, call); - } - } - linphone_core_enter_conference(lc); - return 0; -} - -/** - * Terminates the conference and the calls associated with it. - * @param lc the linphone core - * - * All the calls that were merged to the conference are terminated, and the conference resources are destroyed. - * - * @return 0 if successful, -1 otherwise -**/ -int linphone_core_terminate_conference(LinphoneCore *lc) { - MSList *calls=lc->calls; - LinphoneConference *conf=&lc->conf_ctx; - conf->terminated=TRUE; - - while (calls) { - LinphoneCall *call=(LinphoneCall*)calls->data; - calls=calls->next; - if (call->current_params->in_conference) { - linphone_core_terminate_call(lc, call); - } - } - return 0; -} - -/** - * Returns the number of participants to the conference, including the local participant. - * @param lc the linphone core - * - * Typically, after merging two calls into the conference, there is total of 3 participants: - * the local participant (or local user), and two remote participants that were the destinations of the two previously establised calls. - * - * @return the number of participants to the conference -**/ -int linphone_core_get_conference_size(LinphoneCore *lc) { - LinphoneConference *conf=&lc->conf_ctx; - return linphone_conference_get_size(conf); -} - - -int linphone_core_start_conference_recording(LinphoneCore *lc, const char *path){ - LinphoneConference *conf=&lc->conf_ctx; - if (conf->conf == NULL) { - ms_warning("linphone_core_start_conference_recording(): no conference now."); - return -1; - } - if (conf->record_endpoint==NULL){ - conf->record_endpoint=ms_audio_endpoint_new_recorder(); - ms_audio_conference_add_member(conf->conf,conf->record_endpoint); - } - ms_audio_recorder_endpoint_start(conf->record_endpoint,path); - return 0; -} - -int linphone_core_stop_conference_recording(LinphoneCore *lc){ - LinphoneConference *conf=&lc->conf_ctx; - if (conf->conf == NULL) { - ms_warning("linphone_core_stop_conference_recording(): no conference now."); - return -1; - } - if (conf->record_endpoint==NULL){ - ms_warning("linphone_core_stop_conference_recording(): no record active."); - return -1; - } - ms_audio_recorder_endpoint_stop(conf->record_endpoint); - return 0; -} - -/** - * @} -**/ - diff --git a/coreapi/conference.cc b/coreapi/conference.cc new file mode 100644 index 000000000..6cc888a2f --- /dev/null +++ b/coreapi/conference.cc @@ -0,0 +1,883 @@ +/******************************************************************************* + * conference.cc + * + * Thu Nov 26, 2015 + * Copyright 2015 Belledonne Communications + * Author: Linphone's team + * Email info@belledonne-communications.com + ******************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "private.h" +#include "conference.h" +#include +#include +#include +#include + +namespace Linphone { + +class Participant { +public: + Participant(LinphoneCall *call); + Participant(const Participant &src); + ~Participant(); + bool operator==(const Participant &src) const; + const LinphoneAddress *getUri() const {return m_uri;} + LinphoneCall *getCall() const {return m_call;} + void setCall(LinphoneCall *call) {m_call = call;} + +private: + LinphoneAddress *m_uri; + LinphoneCall *m_call; +}; + +class Conference { +public: + Conference(LinphoneCore *core); + virtual ~Conference() {}; + + virtual int addParticipant(LinphoneCall *call) = 0; + virtual int removeParticipant(LinphoneCall *call) = 0; + virtual int removeParticipant(const LinphoneAddress *uri) = 0; + virtual int terminate() = 0; + + virtual int enter() = 0; + virtual int leave() = 0; + virtual bool isIn() const = 0; + + AudioStream *getAudioStream() const {return m_localParticipantStream;} + int muteMicrophone(bool val); + bool microphoneIsMuted() const {return m_isMuted;} + float getInputVolume() const; + + virtual int getParticipantCount() const {return m_participants.size();} + const std::list &getParticipants() const {return m_participants;} + + virtual int startRecording(const char *path) = 0; + virtual int stopRecording() = 0; + + virtual void onCallStreamStarting(LinphoneCall *call, bool isPausedByRemote) {}; + virtual void onCallStreamStopping(LinphoneCall *call) {}; + virtual void onCallTerminating(LinphoneCall *call) {}; + +protected: + Participant *find_participant(const LinphoneAddress *uri); + + LinphoneCore *m_core; + AudioStream *m_localParticipantStream; + bool m_isMuted; + std::list m_participants; +}; + +class LocalConference: public Conference { +public: + LocalConference(LinphoneCore *core); + virtual ~LocalConference(); + + virtual int addParticipant(LinphoneCall *call); + virtual int removeParticipant(LinphoneCall *call); + virtual int removeParticipant(const LinphoneAddress *uri); + virtual int terminate(); + + virtual int enter(); + virtual int leave(); + virtual bool isIn() const {return m_localParticipantStream!=NULL;} + virtual int getParticipantCount() const; + + virtual int startRecording(const char *path); + virtual int stopRecording(); + + void onCallStreamStarting(LinphoneCall *call, bool isPausedByRemote); + void onCallStreamStopping(LinphoneCall *call); + void onCallTerminating(LinphoneCall *call); + +private: + void addLocalEndpoint(); + int remoteParticipantsCount(); + void removeLocalEndpoint(); + int removeFromConference(LinphoneCall *call, bool_t active); + int convertConferenceToCall(); + static RtpProfile *sMakeDummyProfile(int samplerate); + + MSAudioConference *m_conf; + MSAudioEndpoint *m_localEndpoint; + MSAudioEndpoint *m_recordEndpoint; + RtpProfile *m_localDummyProfile; + bool_t m_terminated; +}; + +class RemoteConference: public Conference { +public: + RemoteConference(LinphoneCore *core); + virtual ~RemoteConference(); + + virtual int addParticipant(LinphoneCall *call); + virtual int removeParticipant(LinphoneCall *call) {return -1;} + virtual int removeParticipant(const LinphoneAddress *uri); + virtual int terminate(); + + virtual int enter(); + virtual int leave(); + virtual bool isIn() const; + + virtual int startRecording(const char *path) {return 0;} + virtual int stopRecording() {return 0;} + +private: + enum State { + NotConnectedToFocus, + ConnectingToFocus, + ConnectedToFocus, + }; + static const char *stateToString(State state); + + void onFocusCallSateChanged(LinphoneCallState state); + void onPendingCallStateChanged(LinphoneCall *call, LinphoneCallState state); + + static void callStateChangedCb(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *message); + static void transferStateChanged(LinphoneCore *lc, LinphoneCall *transfered, LinphoneCallState new_call_state); + + const char *m_focusAddr; + char *m_focusContact; + LinphoneCall *m_focusCall; + State m_state; + LinphoneCoreVTable *m_vtable; + MSList *m_pendingCalls; + MSList *m_transferingCalls; +}; + +}; + + +using namespace Linphone; +using namespace std; + + +Participant::Participant(LinphoneCall *call) { + m_uri = linphone_address_clone(linphone_call_get_remote_address(call)); + m_call = linphone_call_ref(call); +} + +Participant::Participant(const Participant &src) { + m_uri = linphone_address_clone(src.m_uri); + m_call = src.m_call ? linphone_call_ref(src.m_call) : NULL; +} + +Participant::~Participant() { + linphone_address_unref(m_uri); + if(m_call) linphone_call_unref(m_call); +} + +bool Participant::operator==(const Participant &src) const { + return linphone_address_equal(m_uri, src.m_uri); +} + + + +Conference::Conference(LinphoneCore *core) : + m_core(core), + m_localParticipantStream(NULL), + m_isMuted(false) { +} + +int Conference::addParticipant(LinphoneCall *call) { + Participant participant(call); + m_participants.push_back(participant); + call->conf_ref = (LinphoneConference *)this; + return 0; +} + +int Conference::removeParticipant(LinphoneCall *call) { + Participant participant(call); + m_participants.remove(participant); + call->conf_ref = NULL; + return 0; +} + +int Conference::removeParticipant(const LinphoneAddress *uri) { + Participant *participant = find_participant(uri); + if(participant == NULL) return -1; + LinphoneCall *call = participant->getCall(); + if(call) call->conf_ref = NULL; + m_participants.remove(Participant(*participant)); + return 0; +} + +int Conference::muteMicrophone(bool val) { + if (val) { + audio_stream_set_mic_gain(m_localParticipantStream, 0); + } else { + audio_stream_set_mic_gain_db(m_localParticipantStream, m_core->sound_conf.soft_mic_lev); + } + if ( linphone_core_get_rtp_no_xmit_on_audio_mute(m_core) ){ + audio_stream_mute_rtp(m_localParticipantStream, val); + } + m_isMuted=val; + return 0; +} + +float Conference::getInputVolume() const { + AudioStream *st=m_localParticipantStream; + if (st && st->volsend && !m_isMuted){ + float vol=0; + ms_filter_call_method(st->volsend,MS_VOLUME_GET,&vol); + return vol; + + } + return LINPHONE_VOLUME_DB_LOWEST; +} + +Participant *Conference::find_participant(const LinphoneAddress *uri) { + list::iterator it = m_participants.begin(); + while(it!=m_participants.end()) { + if(linphone_address_equal(uri, it->getUri())) break; + it++; + } + if(it == m_participants.end()) return NULL; + else return &*it; +} + + + +LocalConference::LocalConference(LinphoneCore *core): Conference(core), + m_conf(NULL), + m_localEndpoint(NULL), + m_recordEndpoint(NULL), + m_localDummyProfile(NULL), + m_terminated(FALSE) { + MSAudioConferenceParams params; + params.samplerate = lp_config_get_int(m_core->config, "sound","conference_rate",16000); + m_conf=ms_audio_conference_new(¶ms); +} + +LocalConference::~LocalConference() { + if(m_conf) terminate(); +} + +RtpProfile *LocalConference::sMakeDummyProfile(int samplerate){ + RtpProfile *prof=rtp_profile_new("dummy"); + PayloadType *pt=payload_type_clone(&payload_type_l16_mono); + pt->clock_rate=samplerate; + rtp_profile_set_payload(prof,0,pt); + return prof; +} + +void LocalConference::addLocalEndpoint() { + /*create a dummy audiostream in order to extract the local part of it */ + /* network address and ports have no meaning and are not used here. */ + AudioStream *st=audio_stream_new(65000,65001,FALSE); + MSSndCard *playcard=m_core->sound_conf.lsd_card ? + m_core->sound_conf.lsd_card : m_core->sound_conf.play_sndcard; + MSSndCard *captcard=m_core->sound_conf.capt_sndcard; + const MSAudioConferenceParams *params=ms_audio_conference_get_params(m_conf); + m_localDummyProfile=sMakeDummyProfile(params->samplerate); + + audio_stream_start_full(st, m_localDummyProfile, + "127.0.0.1", + 65000, + "127.0.0.1", + 65001, + 0, + 40, + NULL, + NULL, + playcard, + captcard, + linphone_core_echo_cancellation_enabled(m_core) + ); + _post_configure_audio_stream(st,m_core,FALSE); + m_localParticipantStream=st; + m_localEndpoint=ms_audio_endpoint_get_from_stream(st,FALSE); + ms_audio_conference_add_member(m_conf,m_localEndpoint); +} + +int LocalConference::addParticipant(LinphoneCall *call) { + if (call->current_params->in_conference){ + ms_error("Already in conference"); + return -1; + } + + if (call->state==LinphoneCallPaused){ + call->params->in_conference=TRUE; + call->params->has_video=FALSE; + linphone_core_resume_call(m_core,call); + }else if (call->state==LinphoneCallStreamsRunning){ + LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); + params->in_conference=TRUE; + params->has_video=FALSE; + + if (call->audiostream || call->videostream){ + linphone_call_stop_media_streams(call); /*free the audio & video local resources*/ + linphone_call_init_media_streams(call); + } + if (call==m_core->current_call){ + m_core->current_call=NULL; + } + /*this will trigger a reINVITE that will later redraw the streams */ + /*FIXME probably a bit too much to just redraw streams !*/ + linphone_core_update_call(m_core,call,params); + linphone_call_params_destroy(params); + addLocalEndpoint(); + }else{ + ms_error("Call is in state %s, it cannot be added to the conference.",linphone_call_state_to_string(call->state)); + return -1; + } + Conference::addParticipant(call); + return 0; +} + +int LocalConference::removeFromConference(LinphoneCall *call, bool_t active){ + int err=0; + char *str; + + if (!call->current_params->in_conference){ + if (call->params->in_conference){ + ms_warning("Not (yet) in conference, be patient"); + return -1; + }else{ + ms_error("Not in a conference."); + return -1; + } + } + call->params->in_conference=FALSE; + Conference::removeParticipant(call); + + str=linphone_call_get_remote_address_as_string(call); + ms_message("%s will be removed from conference", str); + ms_free(str); + if (active){ + LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); + params->in_conference=FALSE; + // reconnect local audio with this call + if (isIn()){ + ms_message("Leaving conference for reconnecting with unique call."); + leave(); + } + ms_message("Updating call to actually remove from conference"); + err=linphone_core_update_call(m_core,call,params); + linphone_call_params_destroy(params); + } else{ + ms_message("Pausing call to actually remove from conference"); + err=_linphone_core_pause_call(m_core,call); + } + return err; +} + +int LocalConference::remoteParticipantsCount() { + int count=getParticipantCount(); + if (count==0) return 0; + if (!m_localParticipantStream) return count; + return count -1; +} + +int LocalConference::convertConferenceToCall(){ + int err=0; + MSList *calls=m_core->calls; + + if (remoteParticipantsCount()!=1){ + ms_error("No unique call remaining in conference."); + return -1; + } + + while (calls) { + LinphoneCall *rc=(LinphoneCall*)calls->data; + calls=calls->next; + if (rc->params->in_conference) { // not using current_param + bool_t active_after_removed=isIn(); + err=removeFromConference(rc, active_after_removed); + break; + } + } + return err; +} + +int LocalConference::removeParticipant(LinphoneCall *call) { + int err; + char * str=linphone_call_get_remote_address_as_string(call); + ms_message("Removing call %s from the conference", str); + ms_free(str); + err=removeFromConference(call, FALSE); + if (err){ + ms_error("Error removing participant from conference."); + return err; + } + + if (remoteParticipantsCount()==1){ + ms_message("conference size is 1: need to be converted to plain call"); + err=convertConferenceToCall(); + } else { + ms_message("the conference need not to be converted as size is %i", remoteParticipantsCount()); + } + return err; +} + +int LocalConference::removeParticipant(const LinphoneAddress *uri) { + Participant *participant = find_participant(uri); + if(participant == NULL) return -1; + LinphoneCall *call = participant->getCall(); + if(call == NULL) return -1; + return removeParticipant(call); +} + +int LocalConference::terminate() { + MSList *calls=m_core->calls; + m_terminated=TRUE; + + while (calls) { + LinphoneCall *call=(LinphoneCall*)calls->data; + calls=calls->next; + if (call->current_params->in_conference) { + linphone_core_terminate_call(m_core, call); + } + } + return 0; +} + +int LocalConference::enter() { + if (linphone_core_sound_resources_locked(m_core)) { + return -1; + } + if (m_core->current_call != NULL) { + _linphone_core_pause_call(m_core, m_core->current_call); + } + if (m_localParticipantStream==NULL) addLocalEndpoint(); + return 0; +} + +void LocalConference::removeLocalEndpoint(){ + if (m_localEndpoint){ + ms_audio_conference_remove_member(m_conf,m_localEndpoint); + ms_audio_endpoint_release_from_stream(m_localEndpoint); + m_localEndpoint=NULL; + audio_stream_stop(m_localParticipantStream); + m_localParticipantStream=NULL; + rtp_profile_destroy(m_localDummyProfile); + } +} + +int LocalConference::leave() { + if (isIn()) + removeLocalEndpoint(); + return 0; +} + +int LocalConference::getParticipantCount() const { + if (m_conf == NULL) { + return 0; + } + return ms_audio_conference_get_size(m_conf) - (m_recordEndpoint ? 1 : 0); +} + +int LocalConference::startRecording(const char *path) { + if (m_conf == NULL) { + ms_warning("linphone_core_start_conference_recording(): no conference now."); + return -1; + } + if (m_recordEndpoint==NULL){ + m_recordEndpoint=ms_audio_endpoint_new_recorder(); + ms_audio_conference_add_member(m_conf,m_recordEndpoint); + } + ms_audio_recorder_endpoint_start(m_recordEndpoint,path); + return 0; +} + +int LocalConference::stopRecording() { + if (m_conf == NULL) { + ms_warning("linphone_core_stop_conference_recording(): no conference now."); + return -1; + } + if (m_recordEndpoint==NULL){ + ms_warning("linphone_core_stop_conference_recording(): no record active."); + return -1; + } + ms_audio_recorder_endpoint_stop(m_recordEndpoint); + return 0; +} + +void LocalConference::onCallStreamStarting(LinphoneCall *call, bool isPausedByRemote) { + call->params->has_video = FALSE; + call->camera_enabled = FALSE; + MSAudioEndpoint *ep=ms_audio_endpoint_get_from_stream(call->audiostream,TRUE); + ms_audio_conference_add_member(m_conf,ep); + ms_audio_conference_mute_member(m_conf,ep,isPausedByRemote); + call->endpoint=ep; +} + +void LocalConference::onCallStreamStopping(LinphoneCall *call) { + ms_audio_conference_remove_member(m_conf,call->endpoint); + ms_audio_endpoint_release_from_stream(call->endpoint); + call->endpoint=NULL; +} + +void LocalConference::onCallTerminating(LinphoneCall *call) { + int remote_count=remoteParticipantsCount(); + ms_message("conference_check_uninit(): size=%i", getParticipantCount()); + if (remote_count==1 && !m_terminated){ + convertConferenceToCall(); + } + if (remote_count==0){ + if (m_localParticipantStream) + removeLocalEndpoint(); + if (m_recordEndpoint){ + ms_audio_conference_remove_member(m_conf, m_recordEndpoint); + ms_audio_endpoint_destroy(m_recordEndpoint); + } + } + if (ms_audio_conference_get_size(m_conf)==0){ + ms_audio_conference_destroy(m_conf); + m_core->conf_ctx = NULL; + delete this; + } +} + + + +RemoteConference::RemoteConference(LinphoneCore *core): + Conference(core), + m_focusAddr(NULL), + m_focusContact(NULL), + m_focusCall(NULL), + m_state(NotConnectedToFocus), + m_vtable(NULL), + m_pendingCalls(NULL), + m_transferingCalls(NULL) { + m_focusAddr = lp_config_get_string(m_core->config, "misc", "conference_focus_addr", ""); + m_vtable = linphone_core_v_table_new(); + m_vtable->call_state_changed = callStateChangedCb; + m_vtable->transfer_state_changed = transferStateChanged; + linphone_core_v_table_set_user_data(m_vtable, this); + _linphone_core_add_listener(m_core, m_vtable, FALSE, TRUE); +} + +RemoteConference::~RemoteConference() { + if(m_state == ConnectedToFocus) terminate(); + linphone_core_remove_listener(m_core, m_vtable); + linphone_core_v_table_destroy(m_vtable); +} + +int RemoteConference::addParticipant(LinphoneCall *call) { + LinphoneAddress *addr; + + switch(m_state) { + case NotConnectedToFocus: + Conference::addParticipant(call); + ms_message("Calling the conference focus (%s)", m_focusAddr); + addr = linphone_address_new(m_focusAddr); + if(addr) { + m_focusCall = linphone_call_ref(linphone_core_invite_address(m_core, addr)); + m_localParticipantStream = m_focusCall->audiostream; + m_pendingCalls = ms_list_append(m_pendingCalls, linphone_call_ref(call)); + m_state = ConnectingToFocus; + linphone_address_unref(addr); + addParticipant(m_focusCall); + return 0; + } else return -1; + + case ConnectingToFocus: + Conference::addParticipant(call); + m_pendingCalls = ms_list_append(m_pendingCalls, linphone_call_ref(call)); + return 0; + + case ConnectedToFocus: + Conference::addParticipant(call); + m_transferingCalls = ms_list_append(m_transferingCalls, linphone_call_ref(call)); + linphone_core_transfer_call(m_core, call, m_focusContact); + return 0; + + default: + ms_error("Could not add call %p to the conference. Bad conference state (%s)", call, stateToString(m_state)); + return -1; + } +} + +int RemoteConference::removeParticipant(const LinphoneAddress *uri) { + char *tmp, *refer_to; + int res; + + switch(m_state) { + case ConnectedToFocus: + tmp = linphone_address_as_string_uri_only(uri); + refer_to = ms_strdup_printf("%s;method=BYE", tmp); + res = sal_call_refer(m_focusCall->op, refer_to); + ms_free(tmp); + ms_free(refer_to); + + if(res == 0) return Conference::removeParticipant(uri); + else return -1; + + default: + ms_error("Cannot remove %s from conference: Bad conference state (%s)", linphone_address_as_string(uri), stateToString(m_state)); + return -1; + } +} + +int RemoteConference::terminate() { + switch(m_state) { + case ConnectingToFocus: + case ConnectedToFocus: + Conference::removeParticipant(m_focusCall); + linphone_core_terminate_call(m_core, m_focusCall); + break; + default: + break; + } + return 0; +} + +int RemoteConference::enter() { + if(m_state != ConnectedToFocus) { + ms_error("Could not enter in the conference: bad conference state (%s)", stateToString(m_state)); + return -1; + } + LinphoneCallState callState = linphone_call_get_state(m_focusCall); + switch(callState) { + case LinphoneCallStreamsRunning: break; + case LinphoneCallPaused: + linphone_core_resume_call(m_core, m_focusCall); + break; + default: + ms_error("Could not join the conference: bad focus call state (%s)", linphone_call_state_to_string(callState)); + return -1; + } + return 0; +} + +int RemoteConference::leave() { + if(m_state != ConnectedToFocus) { + ms_error("Could not leave the conference: bad conference state (%s)", stateToString(m_state)); + return -1; + } + LinphoneCallState callState = linphone_call_get_state(m_focusCall); + switch(callState) { + case LinphoneCallPaused: break; + case LinphoneCallStreamsRunning: + linphone_core_pause_call(m_core, m_focusCall); + break; + default: + ms_error("Could not leave the conference: bad focus call state (%s)", linphone_call_state_to_string(callState)); + return -1; + } + return 0; +} + +bool RemoteConference::isIn() const { + if(m_state != ConnectedToFocus) return false; + LinphoneCallState callState = linphone_call_get_state(m_focusCall); + return callState == LinphoneCallStreamsRunning; +} + +const char *RemoteConference::stateToString(RemoteConference::State state) { + switch(state) { + case NotConnectedToFocus: return "NotConnectedToFocus"; + case ConnectingToFocus: return "ConnectingToFocus"; + case ConnectedToFocus: return "ConnectedToFocus"; + default: return "Unknown"; + } +} + +void RemoteConference::onFocusCallSateChanged(LinphoneCallState state) { + MSList *it; + + switch (state) { + case LinphoneCallConnected: + m_state = ConnectedToFocus; + m_focusContact = ms_strdup(linphone_call_get_remote_contact(m_focusCall)); + it = m_pendingCalls; + while (it) { + LinphoneCall *pendingCall = (LinphoneCall *)it->data; + LinphoneCallState pendingCallState = linphone_call_get_state(pendingCall); + if(pendingCallState == LinphoneCallStreamsRunning || pendingCallState == LinphoneCallPaused) { + MSList *current_elem = it; + it = it->next; + m_pendingCalls = ms_list_remove_link(m_pendingCalls, current_elem); + m_transferingCalls = ms_list_append(m_transferingCalls, pendingCall); + linphone_core_transfer_call(m_core, pendingCall, m_focusContact); + } else { + it = it->next; + } + } + break; + + case LinphoneCallError: + case LinphoneCallEnd: + Conference::removeParticipant(m_focusCall); + m_state = NotConnectedToFocus; + linphone_call_unref(m_focusCall); + m_focusCall = NULL; + m_localParticipantStream = NULL; + if(m_focusContact) { + ms_free(m_focusContact); + m_focusContact = NULL; + } + m_pendingCalls = ms_list_free_with_data(m_pendingCalls, (void (*)(void *))linphone_call_unref); + m_transferingCalls = ms_list_free_with_data(m_transferingCalls, (void (*)(void *))linphone_call_unref); + break; + + default: break; + } +} + +void RemoteConference::onPendingCallStateChanged(LinphoneCall *call, LinphoneCallState state) { + switch(state) { + case LinphoneCallStreamsRunning: + case LinphoneCallPaused: + if(m_state == ConnectedToFocus) { + m_pendingCalls = ms_list_remove(m_pendingCalls, call); + m_transferingCalls = ms_list_append(m_transferingCalls, call); + linphone_core_transfer_call(m_core, call, m_focusContact); + } + break; + + case LinphoneCallError: + case LinphoneCallEnd: + Conference::removeParticipant(call); + m_pendingCalls = ms_list_remove(m_pendingCalls, call); + linphone_call_unref(call); + break; + + default: break; + } +} + +void RemoteConference::callStateChangedCb(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *message) { + LinphoneCoreVTable *vtable = linphone_core_get_current_vtable(lc); + RemoteConference *conf = (RemoteConference *)linphone_core_v_table_get_user_data(vtable); + if (call == conf->m_focusCall) { + conf->onFocusCallSateChanged(cstate); + } else if(ms_list_find(conf->m_pendingCalls, call)) { + conf->onPendingCallStateChanged(call, cstate); + } +} + +void RemoteConference::transferStateChanged(LinphoneCore *lc, LinphoneCall *transfered, LinphoneCallState new_call_state) { + LinphoneCoreVTable *vtable = linphone_core_get_current_vtable(lc); + RemoteConference *conf = (RemoteConference *)linphone_core_v_table_get_user_data(vtable); + if (ms_list_find(conf->m_transferingCalls, transfered)) { + switch (new_call_state) { + case LinphoneCallConnected: + case LinphoneCallError: + conf->m_transferingCalls = ms_list_remove(conf->m_transferingCalls, transfered); + linphone_call_unref(transfered); + break; + + default: + break; + } + } +} + + + +LinphoneConference *linphone_local_conference_new(LinphoneCore *core) { + return (LinphoneConference *) new LocalConference(core); +} + +LinphoneConference *linphone_remote_conference_new(LinphoneCore *core) { + return (LinphoneConference *) new RemoteConference(core); +} + +void linphone_conference_free(LinphoneConference *obj) { + delete (Conference *)obj; +} + +int linphone_conference_add_participant(LinphoneConference *obj, LinphoneCall *call) { + return ((Conference *)obj)->addParticipant(call); +} + +int linphone_conference_remove_participant(LinphoneConference *obj, const LinphoneAddress *uri) { + return ((Conference *)obj)->removeParticipant(uri); +} + +int linphone_conference_remove_participant_with_call(LinphoneConference *obj, LinphoneCall *call) { + return ((Conference *)obj)->removeParticipant(call); +} + +int linphone_conference_terminate(LinphoneConference *obj) { + return ((Conference *)obj)->terminate(); +} + +int linphone_conference_enter(LinphoneConference *obj) { + return ((Conference *)obj)->enter(); +} + +int linphone_conference_leave(LinphoneConference *obj) { + return ((Conference *)obj)->leave(); +} + +bool_t linphone_conference_is_in(const LinphoneConference *obj) { + return ((Conference *)obj)->isIn() ? TRUE : FALSE; +} + +AudioStream *linphone_conference_get_audio_stream(const LinphoneConference *obj) { + return ((Conference *)obj)->getAudioStream(); +} + +int linphone_conference_mute_microphone(LinphoneConference *obj, bool_t val) { + return ((Conference *)obj)->muteMicrophone(val); +} + +bool_t linphone_conference_microphone_is_muted(const LinphoneConference *obj) { + return ((Conference *)obj)->microphoneIsMuted(); +} + +float linphone_conference_get_input_volume(const LinphoneConference *obj) { + return ((Conference *)obj)->getInputVolume(); +} + +int linphone_conference_get_participant_count(const LinphoneConference *obj) { + return ((Conference *)obj)->getParticipantCount(); +} + +MSList *linphone_conference_get_participants(const LinphoneConference *obj) { + const list participants = ((Conference *)obj)->getParticipants(); + MSList *participants_list = NULL; + for(list::const_iterator it=participants.begin();it!=participants.end();it++) { + LinphoneAddress *uri = linphone_address_clone(it->getUri()); + participants_list = ms_list_append(participants_list, uri); + } + return participants_list; +} + +int linphone_conference_start_recording(LinphoneConference *obj, const char *path) { + return ((Conference *)obj)->startRecording(path); +} + +int linphone_conference_stop_recording(LinphoneConference *obj) { + return ((Conference *)obj)->stopRecording(); +} + +void linphone_conference_on_call_stream_starting(LinphoneConference *obj, LinphoneCall *call, bool_t is_paused_by_remote) { + ((Conference *)obj)->onCallStreamStarting(call, is_paused_by_remote); +} + +void linphone_conference_on_call_stream_stopping(LinphoneConference *obj, LinphoneCall *call) { + ((Conference *)obj)->onCallStreamStopping(call); +} + +void linphone_conference_on_call_terminating(LinphoneConference *obj, LinphoneCall *call) { + ((Conference *)obj)->onCallTerminating(call); +} + +bool_t linphone_conference_check_class(LinphoneConference *obj, LinphoneConferenceClass _class) { + switch(_class) { + case LinphoneConferenceClassLocal: return typeid(obj) == typeid(LocalConference); + case LinphoneConferenceClassRemote: return typeid(obj) == typeid(RemoteConference); + default: return FALSE; + } +} diff --git a/coreapi/conference.h b/coreapi/conference.h new file mode 100644 index 000000000..b2996ea89 --- /dev/null +++ b/coreapi/conference.h @@ -0,0 +1,68 @@ +/******************************************************************************* + * conference.cc + * + * Thu Nov 26, 2015 + * Copyright 2015 Belledonne Communications + * Author: Linphone's team + * Email info@belledonne-communications.com + ******************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef CONFERENCE_H +#define CONFERENCE_H + +#include "linphonecore.h" + +typedef struct _LinphoneConference LinphoneConference; + +typedef enum { + LinphoneConferenceClassLocal, + LinphoneConferenceClassRemote +} LinphoneConferenceClass; + +LinphoneConference *linphone_local_conference_new(LinphoneCore *core); +LinphoneConference *linphone_remote_conference_new(LinphoneCore *core); +void linphone_conference_free(LinphoneConference *obj); + +int linphone_conference_add_participant(LinphoneConference *obj, LinphoneCall *call); +int linphone_conference_remove_participant_with_call(LinphoneConference *obj, LinphoneCall *call); +int linphone_conference_remove_participant(LinphoneConference *obj, const LinphoneAddress *uri); +int linphone_conference_terminate(LinphoneConference *obj); + +int linphone_conference_enter(LinphoneConference *obj); +int linphone_conference_leave(LinphoneConference *obj); +bool_t linphone_conference_is_in(const LinphoneConference *obj); +AudioStream *linphone_conference_get_audio_stream(const LinphoneConference *obj); + +int linphone_conference_mute_microphone(LinphoneConference *obj, bool_t val); +bool_t linphone_conference_microphone_is_muted(const LinphoneConference *obj); +float linphone_conference_get_input_volume(const LinphoneConference *obj); + +int linphone_conference_get_participant_count(const LinphoneConference *obj); +MSList *linphone_conference_get_participants(const LinphoneConference *obj); + +int linphone_conference_start_recording(LinphoneConference *obj, const char *path); +int linphone_conference_stop_recording(LinphoneConference *obj); + +void linphone_conference_on_call_stream_starting(LinphoneConference *obj, LinphoneCall *call, bool_t is_paused_by_remote); +void linphone_conference_on_call_stream_stopping(LinphoneConference *obj, LinphoneCall *call); +void linphone_conference_on_call_terminating(LinphoneConference *obj, LinphoneCall *call); + +bool_t linphone_conference_check_class(LinphoneConference *obj, LinphoneConferenceClass _class); + +#endif // CONFERENCE_H diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 087d8fe27..a13c65237 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1433,7 +1433,7 @@ static void linphone_call_set_terminated(LinphoneCall *call){ if (linphone_core_del_call(lc,call) != 0){ ms_error("Could not remove the call from the list !!!"); } - linphone_core_conference_check_uninit(lc); + if(lc->conf_ctx) linphone_conference_on_call_terminating(lc->conf_ctx, call); if (call->ringing_beep){ linphone_core_stop_dtmf(lc); call->ringing_beep=FALSE; @@ -3108,7 +3108,7 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, LinphoneCallSta if (call->params->in_conference){ /*transform the graph to connect it to the conference filter */ mute = stream->dir==SalStreamRecvOnly; - linphone_call_add_to_conf(call, mute); + linphone_conference_on_call_stream_starting(lc->conf_ctx, call, mute); } call->current_params->in_conference=call->params->in_conference; call->current_params->low_bandwidth=call->params->low_bandwidth; @@ -3600,6 +3600,7 @@ static void update_rtp_stats(LinphoneCall *call, int stream_index) { } static void linphone_call_stop_audio_stream(LinphoneCall *call) { + LinphoneCore *lc = call->core; if (call->audiostream!=NULL) { linphone_reporting_update_media_info(call, LINPHONE_CALL_STATS_AUDIO); media_stream_reclaim_sessions(&call->audiostream->ms,&call->sessions[call->main_audio_stream_index]); @@ -3615,7 +3616,7 @@ static void linphone_call_stop_audio_stream(LinphoneCall *call) { audio_stream_get_local_rtp_stats(call->audiostream,&call->log->local_stats); linphone_call_log_fill_stats (call->log,(MediaStream*)call->audiostream); if (call->endpoint){ - linphone_call_remove_from_conf(call); + linphone_conference_on_call_stream_stopping(lc->conf_ctx, call); } update_rtp_stats(call, call->main_audio_stream_index); audio_stream_stop(call->audiostream); @@ -4544,6 +4545,10 @@ bool_t linphone_call_is_in_conference(const LinphoneCall *call) { return call->params->in_conference; } +LinphoneConference *linphone_call_get_conference(const LinphoneCall *call) { + return call->conf_ref; +} + /** * Perform a zoom of the video displayed during a call. * @param call the call. @@ -4876,4 +4881,3 @@ void linphone_call_refresh_sockets(LinphoneCall *call){ } } } - diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 7d2440601..de27eaa38 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1698,7 +1698,7 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab internal_vtable->notify_received = linphone_core_internal_notify_received; _linphone_core_add_listener(lc, internal_vtable, TRUE); memcpy(local_vtable,vtable,sizeof(LinphoneCoreVTable)); - _linphone_core_add_listener(lc, local_vtable, TRUE); + _linphone_core_add_listener(lc, local_vtable, TRUE, TRUE); linphone_core_set_state(lc,LinphoneGlobalStartup,"Starting up"); ortp_init(); @@ -4890,8 +4890,7 @@ void linphone_core_enable_mic(LinphoneCore *lc, bool_t enable) { const MSList *elem; if (linphone_core_is_in_conference(lc)){ - lc->conf_ctx.local_muted=!enable; - linphone_core_mute_audio_stream(lc, lc->conf_ctx.local_participant, !enable); + linphone_conference_mute_microphone(lc->conf_ctx, !enable); } list = linphone_core_get_calls(lc); for (elem = list; elem != NULL; elem = elem->next) { @@ -4904,7 +4903,7 @@ void linphone_core_enable_mic(LinphoneCore *lc, bool_t enable) { bool_t linphone_core_mic_enabled(LinphoneCore *lc) { LinphoneCall *call=linphone_core_get_current_call(lc); if (linphone_core_is_in_conference(lc)){ - return !lc->conf_ctx.local_muted; + return linphone_conference_microphone_is_muted(lc->conf_ctx); }else if (call==NULL){ ms_warning("%s(): No current call!", __FUNCTION__); return TRUE; @@ -5925,7 +5924,7 @@ static MSFilter *get_audio_resource(LinphoneCore *lc, LinphoneAudioResourceType if (call){ stream=call->audiostream; }else if (linphone_core_is_in_conference(lc)){ - stream=lc->conf_ctx.local_participant; + stream=linphone_conference_get_audio_stream(lc->conf_ctx); } if (stream){ if (rtype==LinphoneToneGenerator) return stream->dtmfgen; @@ -7434,3 +7433,84 @@ const char *linphone_stream_type_to_string(const LinphoneStreamType type) { LinphoneRingtonePlayer *linphone_core_get_ringtoneplayer(LinphoneCore *lc) { return lc->ringtoneplayer; } + +int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call) { + const char *conf_method_name; + if(lc->conf_ctx == NULL) { + conf_method_name = lp_config_get_string(lc->config, "misc", "conference_type", "local"); + if(strcasecmp(conf_method_name, "local") == 0) { + lc->conf_ctx = linphone_local_conference_new(lc); + } else if(strcasecmp(conf_method_name, "remote") == 0) { + lc->conf_ctx = linphone_remote_conference_new(lc); + } else { + ms_error("'%s' is not a valid conference method", conf_method_name); + return -1; + } + } + return linphone_conference_add_participant(lc->conf_ctx, call); +} + +int linphone_core_add_all_to_conference(LinphoneCore *lc) { + MSList *calls=lc->calls; + while (calls) { + LinphoneCall *call=(LinphoneCall*)calls->data; + calls=calls->next; + linphone_core_add_to_conference(lc, call); + } + if(lc->conf_ctx && linphone_conference_check_class(lc->conf_ctx, LinphoneConferenceClassLocal)) { + linphone_core_enter_conference(lc); + } + return 0; +} + +int linphone_core_remove_from_conference(LinphoneCore *lc, LinphoneCall *call) { + if(lc->conf_ctx) return linphone_conference_remove_participant_with_call(lc->conf_ctx, call); + else return -1; +} + +int linphone_core_terminate_conference(LinphoneCore *lc) { + if(lc->conf_ctx == NULL) return -1; + linphone_conference_terminate(lc->conf_ctx); + linphone_conference_free(lc->conf_ctx); + lc->conf_ctx = NULL; + return 0; +} + +int linphone_core_enter_conference(LinphoneCore *lc) { + if(lc->conf_ctx) return linphone_conference_enter(lc->conf_ctx); + else return -1; +} + +int linphone_core_leave_conference(LinphoneCore *lc) { + if(lc->conf_ctx) return linphone_conference_leave(lc->conf_ctx); + else return -1; +} + +bool_t linphone_core_is_in_conference(const LinphoneCore *lc) { + if(lc->conf_ctx) return linphone_conference_is_in(lc->conf_ctx); + else return FALSE; +} + +int linphone_core_get_conference_size(LinphoneCore *lc) { + if(lc->conf_ctx) return linphone_conference_get_participant_count(lc->conf_ctx); + return 0; +} + +float linphone_core_get_conference_local_input_volume(LinphoneCore *lc) { + if(lc->conf_ctx) return linphone_conference_get_input_volume(lc->conf_ctx); + else return -1.0; +} + +int linphone_core_start_conference_recording(LinphoneCore *lc, const char *path) { + if(lc->conf_ctx) return linphone_conference_start_recording(lc->conf_ctx, path); + return -1; +} + +int linphone_core_stop_conference_recording(LinphoneCore *lc) { + if(lc->conf_ctx) return linphone_conference_stop_recording(lc->conf_ctx); + return -1; +} + +LinphoneConference *linphone_core_get_conference(LinphoneCore *lc) { + return lc->conf_ctx; +} diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index edcb6b751..f42930fc4 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -139,6 +139,13 @@ enum _LinphoneStreamType { * @ingroup initializing **/ typedef enum _LinphoneStreamType LinphoneStreamType; + +/** + * Internal object of LinphoneCore representing a conference + * @ingroup call_control + */ +typedef struct _LinphoneConference LinphoneConference; + /** * Function returning a human readable value for LinphoneStreamType. * @ingroup initializing @@ -441,6 +448,8 @@ LINPHONE_PUBLIC void linphone_address_set_secure(LinphoneAddress *addr, bool_t e LINPHONE_PUBLIC bool_t linphone_address_is_sip(const LinphoneAddress *uri); LINPHONE_PUBLIC LinphoneTransportType linphone_address_get_transport(const LinphoneAddress *uri); LINPHONE_PUBLIC void linphone_address_set_transport(LinphoneAddress *uri,LinphoneTransportType type); +LINPHONE_PUBLIC const char *linphone_address_get_method_param(const LinphoneAddress *addr); +LINPHONE_PUBLIC void linphone_address_set_method_param(LinphoneAddress *addr, const char *method); LINPHONE_PUBLIC char *linphone_address_as_string(const LinphoneAddress *u); LINPHONE_PUBLIC char *linphone_address_as_string_uri_only(const LinphoneAddress *u); LINPHONE_PUBLIC bool_t linphone_address_weak_equal(const LinphoneAddress *a1, const LinphoneAddress *a2); @@ -888,10 +897,19 @@ LINPHONE_PUBLIC void linphone_call_set_native_video_window_id(LinphoneCall *call * @param call #LinphoneCall * @return TRUE if part of a conference. * - * @deprecated Use linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)) instead. + * @deprecated Use linphone_call_get_conference() instead. * @ingroup call_control */ LINPHONE_PUBLIC LINPHONE_DEPRECATED bool_t linphone_call_is_in_conference(const LinphoneCall *call); + +/** + * Return the associated conference object + * @param call #LinphoneCall + * @return A pointer on #LinphoneConference or NULL if the call is not part + * of any conference. + * @ingroup call_control + */ +LINPHONE_PUBLIC LinphoneConference *linphone_call_get_conference(const LinphoneCall *call); /** * Enables or disable echo cancellation for this call * @param call @@ -3806,7 +3824,27 @@ LINPHONE_PUBLIC const char *linphone_core_get_user_certificates_path(LinphoneCor */ LINPHONE_PUBLIC LinphoneCall* linphone_core_find_call_from_uri(const LinphoneCore *lc, const char *uri); +/** + * @addtogroup call_control + * @{ + */ + +/** + * Add a participant to the conference. If no conference is going on + * a new internal conference context is created and the participant is + * added to it. + * @param lc #LinphoneCore + * @param call The current call with the participant to add + * @return 0 if succeeded. Negative number if failed + */ LINPHONE_PUBLIC int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call); +/** + * Add all current calls into the conference. If no conference is running + * a new internal conference context is created and all current calls + * are added to it. + * @param lc #LinphoneCore + * @return 0 if succeeded. Negative number if failed + */ LINPHONE_PUBLIC int linphone_core_add_all_to_conference(LinphoneCore *lc); /** * Remove a call from the conference. @@ -3826,18 +3864,84 @@ LINPHONE_PUBLIC int linphone_core_add_all_to_conference(LinphoneCore *lc); LINPHONE_PUBLIC int linphone_core_remove_from_conference(LinphoneCore *lc, LinphoneCall *call); /** * Indicates whether the local participant is part of a conference. + * @warning That function automatically fails in the case of conferences using a + * conferencet server (focus). If you use such a conference, you should use + * linphone_conference_remove_participant() instead. * @param lc the linphone core * @return TRUE if the local participant is in a conference, FALSE otherwise. -**/ +*/ LINPHONE_PUBLIC bool_t linphone_core_is_in_conference(const LinphoneCore *lc); +/** + * Join the local participant to the running conference + * @param lc #LinphoneCore + * @return 0 if succeeded. Negative number if failed + */ LINPHONE_PUBLIC int linphone_core_enter_conference(LinphoneCore *lc); +/** + * Make the local participant leave the running conference + * @param lc #LinphoneCore + * @return 0 if succeeded. Negative number if failed + */ LINPHONE_PUBLIC int linphone_core_leave_conference(LinphoneCore *lc); +/** + * Get the set input volume of the local participant + * @param lc #LinphoneCore + * @return A value inside [0.0 ; 1.0] + */ LINPHONE_PUBLIC float linphone_core_get_conference_local_input_volume(LinphoneCore *lc); - +/** + * Terminate the running conference. If it is a local conference, all calls + * inside it will become back separate calls and will be put in #LinphoneCallPaused state. + * If it is a conference involving a focus server, all calls inside the conference + * will be terminated. + * @param lc #LinphoneCore + * @return 0 if succeeded. Negative number if failed + */ LINPHONE_PUBLIC int linphone_core_terminate_conference(LinphoneCore *lc); +/** + * Get the number of remote participant in the running conference + * @param lc #LinphoneCore + * @return The number of remote participant + */ LINPHONE_PUBLIC int linphone_core_get_conference_size(LinphoneCore *lc); +/** + * Start recording the running conference + * @param lc #LinphoneCore + * @param path Path to the file where the recording will be written + * @return 0 if succeeded. Negative number if failed + */ LINPHONE_PUBLIC int linphone_core_start_conference_recording(LinphoneCore *lc, const char *path); +/** + * Stop recording the running conference + * @param #LinphoneCore + * @return 0 if succeeded. Negative number if failed + */ LINPHONE_PUBLIC int linphone_core_stop_conference_recording(LinphoneCore *lc); +/** + * Get a pointer on the internal conference object. + * @param lc #LinphoneCore + * @return A pointer on #LinphoneConference or NULL if no conference are going on + */ +LINPHONE_PUBLIC LinphoneConference *linphone_core_get_conference(LinphoneCore *lc); +/** + * Get URIs of all participants of one conference + * @param obj A #LinphoneConference + * @return A #MSList containing URIs of all participant. That list must be + * freed after utilisation and each URI must be unref with linphone_address_unref() + */ +LINPHONE_PUBLIC MSList *linphone_conference_get_participants(const LinphoneConference *obj); +/** + * Remove a participant from a conference + * @param obj A #LinphoneConference + * @param uri SIP URI of the participant to remove + * @return 0 if succeeded, -1 if failed + */ +LINPHONE_PUBLIC int linphone_conference_remove_participant(LinphoneConference *obj, const LinphoneAddress *uri); + +/** + * @} + */ + /** * Get the maximum number of simultaneous calls Linphone core can manage at a time. All new call above this limit are declined with a busy answer * @ingroup initializing diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index b3547355e..de7408496 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -1311,7 +1311,7 @@ extern "C" void Java_org_linphone_core_LinphoneCoreImpl_removeListener(JNIEnv* e VTableReference *ref=(VTableReference*)(iterator->data); LinphoneCoreVTable *vTable = ref->valid ? ref->vtable : NULL; iterator = iterator->next; //Because linphone_core_remove_listener may change the list - if (vTable) { + if (vTable && !ref->internal) { LinphoneCoreData *data = (LinphoneCoreData*) linphone_core_v_table_get_user_data(vTable); if (data && env->IsSameObject(data->listener, jlistener)) { linphone_core_remove_listener(core, vTable); @@ -4315,6 +4315,14 @@ extern "C" jint Java_org_linphone_core_LinphoneCoreImpl_getConferenceSize(JNIEnv return (jint)linphone_core_get_conference_size((LinphoneCore *) pCore); } +extern "C" jobject Jave_org_linphone_core_LinphoneCoreImpl_getConference(JNIEnv *env, jobject thiz, jlong pCore) { + jclass conference_class = env->FindClass("org/linphone/core/LinphoneConference"); + jmethodID conference_constructor = env->GetMethodID(conference_class, "", "(J)"); + LinphoneConference *conf = linphone_core_get_conference((LinphoneCore *)pCore); + if(conf) return env->NewObject(conference_class, conference_constructor, conf); + else return NULL; +} + extern "C" jint Java_org_linphone_core_LinphoneCoreImpl_startConferenceRecording(JNIEnv *env,jobject thiz,jlong pCore, jstring jpath){ int err=-1; if (jpath){ @@ -4449,6 +4457,14 @@ extern "C" void Java_org_linphone_core_LinphoneCallImpl_setAuthenticationTokenVe linphone_call_set_authentication_token_verified(call, verified); } +extern "C" jobject Java_org_linphnoe_core_LinphoneCallImpl_getConference(JNIEnv *env, jobject thiz, jlong ptr) { + jclass conference_class = env->FindClass("org/linphone/core/LinphoneConference"); + jmethodID conference_constructor = env->GetMethodID(conference_class, "", "(J)"); + LinphoneConference *conf = linphone_call_get_conference((LinphoneCall *)ptr); + if(conf) return env->NewObject(conference_class, conference_constructor, conf); + return NULL; +} + extern "C" jfloat Java_org_linphone_core_LinphoneCallImpl_getPlayVolume(JNIEnv* env, jobject thiz, jlong ptr) { LinphoneCall *call = (LinphoneCall *) ptr; return (jfloat)linphone_call_get_play_volume(call); @@ -6730,3 +6746,26 @@ JNIEXPORT jint JNICALL Java_org_linphone_core_LinphoneCoreImpl_getNortpTimeout(J +JNIEXPORT jobject JNICALL Java_org_linphone_core_LinphoneConferenceImpl_getParticipants(JNIEnv *env, jobject thiz, jlong pconference) { + MSList *participants, *it; + jclass addr_class = env->FindClass("org/linphone/core/LinphoneAddressImpl"); + jclass addr_list_class = env->FindClass("[Lorg/linphone/core/LinphoneAddressImpl;"); + jmethodID addr_constructor = env->GetMethodID(addr_class, "", "(J)"); + jmethodID addr_list_constructor = env->GetMethodID(addr_list_class, "", "(V)"); + jmethodID addr_list_append = env->GetMethodID(addr_list_class, "add", "(Lorg/linphone/core/LinphoneAddressImpl;)Z"); + jobject jaddr_list = env->NewObject(addr_list_class, addr_list_constructor); + + participants = linphone_conference_get_participants((LinphoneConference *)pconference); + for(it = participants; it; it = ms_list_next(it)) { + LinphoneAddress *addr = (LinphoneAddress *)it->data; + jobject jaddr = env->NewObject(addr_class, addr_constructor, addr); + env->CallBooleanMethod(jaddr_list, addr_list_append, jaddr); + } + return jaddr_list; +} + +JNIEXPORT jint JNICALL Java_org_linphone_core_LinphoneConferenteImpl_removeParticipant(JNIEnv *env, jobject thiz, jlong pconference, jobject uri) { + jfieldID native_ptr_attr = env->GetFieldID(env->GetObjectClass(uri), "nativePtr", "J"); + LinphoneAddress *addr = (LinphoneAddress *)env->GetLongField(uri, native_ptr_attr); + return linphone_conference_remove_participant((LinphoneConference *)pconference, addr); +} diff --git a/coreapi/private.h b/coreapi/private.h index ca1fd76f7..3cbe173de 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -33,6 +33,7 @@ extern "C" { #include "friendlist.h" #include "linphone_tunnel.h" #include "linphonecore_utils.h" +#include "conference.h" #include "sal/sal.h" #include "sipsetup.h" #include "quality_reporting.h" @@ -345,6 +346,8 @@ struct _LinphoneCall{ bool_t paused_by_app; bool_t broken; /*set to TRUE when the call is in broken state due to network disconnection or transport */ + + LinphoneConference *conf_ref; /**> Point on the associated conference if this call is part of a conference. NULL instead. */ }; BELLE_SIP_DECLARE_VPTR(LinphoneCall); @@ -820,16 +823,6 @@ typedef struct autoreplier_config const char *message; /* the path of the file to be played */ }autoreplier_config_t; -struct _LinphoneConference{ - MSAudioConference *conf; - AudioStream *local_participant; - MSAudioEndpoint *local_endpoint; - MSAudioEndpoint *record_endpoint; - RtpProfile *local_dummy_profile; - bool_t local_muted; - bool_t terminated; -}; - typedef struct _LinphoneToneDescription{ LinphoneReason reason; @@ -844,7 +837,6 @@ void linphone_core_play_call_error_tone(LinphoneCore *lc, LinphoneReason reason) void _linphone_core_set_tone(LinphoneCore *lc, LinphoneReason reason, LinphoneToneID id, const char *audiofile); const char *linphone_core_get_tone_file(const LinphoneCore *lc, LinphoneToneID id); int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params, LinphoneCallState next_state, const char *state_info); -typedef struct _LinphoneConference LinphoneConference; typedef struct _LinphoneTaskList{ MSList *hooks; @@ -906,7 +898,7 @@ struct _LinphoneCore time_t netup_time; /*time when network went reachable */ struct _EcCalibrator *ecc; LinphoneTaskList hooks; /*tasks periodically executed in linphone_core_iterate()*/ - LinphoneConference conf_ctx; + LinphoneConference *conf_ctx; char* zrtp_secrets_cache; char* user_certificates_path; LinphoneVideoPolicy video_policy; @@ -1040,13 +1032,6 @@ int _linphone_core_pause_call(LinphoneCore *lc, LinphoneCall *call); /*conferencing subsystem*/ void _post_configure_audio_stream(AudioStream *st, LinphoneCore *lc, bool_t muted); -/* When a conference participant pause the conference he may send a music. - * We don't want to hear that music or to send it to the other participants. - * Use muted=yes to handle this case. - */ -void linphone_call_add_to_conf(LinphoneCall *call, bool_t muted); -void linphone_call_remove_from_conf(LinphoneCall *call); -void linphone_core_conference_check_uninit(LinphoneCore *lc); bool_t linphone_core_sound_resources_available(LinphoneCore *lc); void linphone_core_notify_refer_state(LinphoneCore *lc, LinphoneCall *referer, LinphoneCall *newcall); unsigned int linphone_core_get_audio_features(LinphoneCore *lc); @@ -1420,17 +1405,18 @@ struct _VTableReference{ LinphoneCoreVTable *vtable; bool_t valid; bool_t autorelease; + bool_t internal; }; typedef struct _VTableReference VTableReference; void v_table_reference_destroy(VTableReference *ref); -void _linphone_core_add_listener(LinphoneCore *lc, LinphoneCoreVTable *vtable, bool_t autorelease); - LINPHONE_PUBLIC void linphone_core_v_table_set_internal(LinphoneCoreVTable *table, bool_t internal); bool_t linphone_core_v_table_is_internal(LinphoneCoreVTable *table); +void _linphone_core_add_listener(LinphoneCore *lc, LinphoneCoreVTable *vtable, bool_t autorelease, bool_t internal); + #ifdef VIDEO_ENABLED LINPHONE_PUBLIC MSWebCam *linphone_call_get_video_device(const LinphoneCall *call); MSWebCam *get_nowebcam_device(void); diff --git a/coreapi/vtables.c b/coreapi/vtables.c index dd146598c..9692240e2 100644 --- a/coreapi/vtables.c +++ b/coreapi/vtables.c @@ -272,10 +272,11 @@ void linphone_core_notify_log_collection_upload_progress_indication(LinphoneCore cleanup_dead_vtable_refs(lc); } -static VTableReference * v_table_reference_new(LinphoneCoreVTable *vtable, bool_t autorelease){ +static VTableReference * v_table_reference_new(LinphoneCoreVTable *vtable, bool_t autorelease, bool_t internal){ VTableReference *ref=ms_new0(VTableReference,1); ref->valid=1; ref->autorelease=autorelease; + ref->internal = internal; ref->vtable=vtable; return ref; } @@ -285,13 +286,13 @@ void v_table_reference_destroy(VTableReference *ref){ ms_free(ref); } -void _linphone_core_add_listener(LinphoneCore *lc, LinphoneCoreVTable *vtable, bool_t autorelease) { +void _linphone_core_add_listener(LinphoneCore *lc, LinphoneCoreVTable *vtable, bool_t autorelease, bool_t internal) { ms_message("Vtable [%p] registered on core [%p]",lc,vtable); - lc->vtable_refs=ms_list_append(lc->vtable_refs,v_table_reference_new(vtable, autorelease)); + lc->vtable_refs=ms_list_append(lc->vtable_refs,v_table_reference_new(vtable, autorelease, internal)); } void linphone_core_add_listener(LinphoneCore *lc, LinphoneCoreVTable *vtable){ - _linphone_core_add_listener(lc, vtable, FALSE); + _linphone_core_add_listener(lc, vtable, FALSE, FALSE); } void linphone_core_remove_listener(LinphoneCore *lc, const LinphoneCoreVTable *vtable) { diff --git a/gtk/conference.c b/gtk/conference.c index 175cfc482..9073d629e 100644 --- a/gtk/conference.c +++ b/gtk/conference.c @@ -178,4 +178,6 @@ void linphone_gtk_unset_from_conference(LinphoneCall *call){ } } - +bool_t linphone_gtk_call_is_in_conference_view(LinphoneCall *call) { + return (find_conferencee_from_call(call) != NULL); +} diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 5c7620840..d37602201 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -509,11 +509,10 @@ void linphone_gtk_remove_in_call_view(LinphoneCall *call){ GtkWidget *w=(GtkWidget*)linphone_call_get_user_pointer (call); GtkWidget *main_window=linphone_gtk_get_main_window (); GtkWidget *nb=linphone_gtk_get_widget(main_window,"viewswitch"); - gboolean in_conf=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); int idx; g_return_if_fail(w!=NULL); idx=gtk_notebook_page_num(GTK_NOTEBOOK(nb),w); - if (in_conf){ + if (linphone_gtk_call_is_in_conference_view(call)){ linphone_gtk_unset_from_conference(call); } linphone_call_set_user_pointer (call,NULL); @@ -776,7 +775,7 @@ void linphone_gtk_in_call_view_set_in_call(LinphoneCall *call){ GtkWidget *callee=linphone_gtk_get_widget(callview,"in_call_uri"); GtkWidget *duration=linphone_gtk_get_widget(callview,"in_call_duration"); guint taskid=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(callview),"taskid")); - gboolean in_conf=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); + gboolean in_conf=(linphone_call_get_conference(call) != NULL); GtkWidget *call_stats=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"call_stats"); linphone_gtk_in_call_show_video(call); @@ -854,7 +853,7 @@ void linphone_gtk_in_call_view_terminate(LinphoneCall *call, const char *error_m video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); status=linphone_gtk_get_widget(callview,"in_call_status"); taskid=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(callview),"taskid")); - in_conf=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); + in_conf=(linphone_call_get_conference(call) != NULL); if (video_window) gtk_widget_destroy(video_window); if (status==NULL) return; if (error_msg==NULL) diff --git a/gtk/linphone.h b/gtk/linphone.h index 4e9d7dcf6..5ef4b2ea9 100644 --- a/gtk/linphone.h +++ b/gtk/linphone.h @@ -198,6 +198,7 @@ LINPHONE_PUBLIC void linphone_gtk_enable_transfer_button(LinphoneCore *lc, gbool LINPHONE_PUBLIC void linphone_gtk_enable_conference_button(LinphoneCore *lc, gboolean value); LINPHONE_PUBLIC void linphone_gtk_set_in_conference(LinphoneCall *call); LINPHONE_PUBLIC void linphone_gtk_unset_from_conference(LinphoneCall *call); +LINPHONE_PUBLIC bool_t linphone_gtk_call_is_in_conference_view(LinphoneCall *call); LINPHONE_PUBLIC void linphone_gtk_terminate_conference_participant(LinphoneCall *call); LINPHONE_PUBLIC void linphone_gtk_in_call_view_show_encryption(LinphoneCall *call); LINPHONE_PUBLIC void linphone_gtk_in_call_view_hide_encryption(LinphoneCall *call); diff --git a/gtk/main.c b/gtk/main.c index f9f835d59..9ba13b2d9 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -814,7 +814,7 @@ static void linphone_gtk_update_call_buttons(LinphoneCall *call){ conf_frame=(GtkWidget *)g_object_get_data(G_OBJECT(mw),"conf_frame"); if(conf_frame==NULL){ linphone_gtk_enable_transfer_button(lc,call_list_size>1); - linphone_gtk_enable_conference_button(lc,call_list_size>1); + linphone_gtk_enable_conference_button(lc,call_list_size>0); } else { linphone_gtk_enable_transfer_button(lc,FALSE); linphone_gtk_enable_conference_button(lc,FALSE); @@ -1929,13 +1929,22 @@ void linphone_gtk_log_handler(OrtpLogLevel lev, const char *fmt, va_list args){ void linphone_gtk_refer_received(LinphoneCore *lc, const char *refer_to){ - GtkEntry * uri_bar =GTK_ENTRY(linphone_gtk_get_widget( - linphone_gtk_get_main_window(), "uribar")); - char *text; - linphone_gtk_notify(NULL,NULL,(text=ms_strdup_printf(_("We are transferred to %s"),refer_to))); - g_free(text); - gtk_entry_set_text(uri_bar, refer_to); - linphone_gtk_start_call(linphone_gtk_get_main_window()); + char method[20] = ""; + LinphoneAddress *addr = linphone_address_new(refer_to); + if(addr) { + const char *tmp = linphone_address_get_method_param(addr); + strncpy(method, tmp, sizeof(20)); + linphone_address_destroy(addr); + } + if(strlen(method) == 0 || strcmp(method, "INVITE") == 0) { + GtkEntry * uri_bar =GTK_ENTRY(linphone_gtk_get_widget( + linphone_gtk_get_main_window(), "uribar")); + char *text; + linphone_gtk_notify(NULL,NULL,(text=ms_strdup_printf(_("We are transferred to %s"),refer_to))); + g_free(text); + gtk_entry_set_text(uri_bar, refer_to); + linphone_gtk_start_call(linphone_gtk_get_main_window()); + } } static void linphone_gtk_check_soundcards(void){ diff --git a/include/sal/sal.h b/include/sal/sal.h index 21e311429..a5bb5e999 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -106,6 +106,7 @@ void sal_address_set_secure(SalAddress *addr, bool_t enabled); SalTransport sal_address_get_transport(const SalAddress* addr); const char* sal_address_get_transport_name(const SalAddress* addr); +const char *sal_address_get_method_param(const SalAddress *addr); void sal_address_set_display_name(SalAddress *addr, const char *display_name); void sal_address_set_username(SalAddress *addr, const char *username); @@ -118,6 +119,7 @@ void sal_address_destroy(SalAddress *u); void sal_address_set_param(SalAddress *u,const char* name,const char* value); void sal_address_set_transport(SalAddress* addr,SalTransport transport); void sal_address_set_transport_name(SalAddress* addr,const char* transport); +void sal_address_set_method_param(SalAddress *addr, const char *method); void sal_address_set_params(SalAddress *addr, const char *params); void sal_address_set_uri_params(SalAddress *addr, const char *params); bool_t sal_address_is_ipv6(const SalAddress *addr); diff --git a/java/common/org/linphone/core/LinphoneCall.java b/java/common/org/linphone/core/LinphoneCall.java index ad10b96b1..3ab28d437 100644 --- a/java/common/org/linphone/core/LinphoneCall.java +++ b/java/common/org/linphone/core/LinphoneCall.java @@ -273,8 +273,17 @@ public interface LinphoneCall { * @param verified true when displayed SAS is correct */ void setAuthenticationTokenVerified(boolean verified); - + + /** + * Checks wether the call is part of a conferece. + * @return A boolean + */ boolean isInConference(); + /** + * Get the pointer on the C conference instance associated to that call. + * @return A positive value if the call is part of a conference, 0 if not. + */ + long getConference(); /** * Indicates whether an operation is in progress at the media side. diff --git a/java/common/org/linphone/core/LinphoneConference.java b/java/common/org/linphone/core/LinphoneConference.java new file mode 100644 index 000000000..9fb25d518 --- /dev/null +++ b/java/common/org/linphone/core/LinphoneConference.java @@ -0,0 +1,39 @@ +/* +LinphoneConference.java +Copyright (C) 2015 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +package org.linphone.core; + +import org.linphone.core.LinphoneAddress; +import java.util.List; + +/** + * Interface to manipulate a running conference + */ +public interface LinphoneConference { + /** + * Get the URIs of all participants of the conference + */ + public List getParticipants(); + /** + * Remove a participant from the conference + * @param uri The URI of the participant to remove + * @return 0 if succeed, -1 if not. + */ + public int removeParticipant(LinphoneAddress uri); +} diff --git a/java/common/org/linphone/core/LinphoneCore.java b/java/common/org/linphone/core/LinphoneCore.java index bb36434da..ef5112622 100644 --- a/java/common/org/linphone/core/LinphoneCore.java +++ b/java/common/org/linphone/core/LinphoneCore.java @@ -1417,6 +1417,13 @@ public interface LinphoneCore { * @returns the number of participants to the conference **/ int getConferenceSize(); + /** + * Return the value of the C pointer on the conference instance. + * + * That function can be used to test whether a conference is running. + * @return A positive value if a conference is running, 0 if not. + **/ + LinphoneConference getConference(); /** * Request recording of the conference into a supplied file path. diff --git a/java/impl/org/linphone/core/LinphoneCallImpl.java b/java/impl/org/linphone/core/LinphoneCallImpl.java index f04ce86d8..28f6302d1 100644 --- a/java/impl/org/linphone/core/LinphoneCallImpl.java +++ b/java/impl/org/linphone/core/LinphoneCallImpl.java @@ -165,8 +165,11 @@ class LinphoneCallImpl implements LinphoneCall { } public boolean isInConference() { - LinphoneCallParamsImpl params = new LinphoneCallParamsImpl(getCurrentParamsCopy(nativePtr)); - return params.localConferenceMode(); + return getConference() != 0; + } + private native long getConference(long nativePtr); + public long getConference() { + return getConference(nativePtr); } public boolean mediaInProgress() { return mediaInProgress(nativePtr);} diff --git a/java/impl/org/linphone/core/LinphoneConferenceImpl.java b/java/impl/org/linphone/core/LinphoneConferenceImpl.java new file mode 100644 index 000000000..2a3519302 --- /dev/null +++ b/java/impl/org/linphone/core/LinphoneConferenceImpl.java @@ -0,0 +1,42 @@ +/* +LinphoneConferenceImpl.java +Copyright (C) 2015 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +package org.linphone.core; + +import org.linphone.core.LinphoneConference; +import java.util.List; + +public class LinphoneConferenceImpl implements LinphoneConference { + private final long nativePtr; + + + private LinphoneConferenceImpl(long nativePtr) { + this.nativePtr = nativePtr; + } + + private native List getParticipants(long nativePtr); + public List getParticipants() { + return getParticipants(nativePtr); + } + + private native int removeParticipant(long nativePtr, LinphoneAddress uri); + public int removeParticipant(LinphoneAddress uri) { + return removeParticipant(nativePtr, uri); + } +} diff --git a/java/impl/org/linphone/core/LinphoneCoreImpl.java b/java/impl/org/linphone/core/LinphoneCoreImpl.java index eb23d6002..1147305b2 100644 --- a/java/impl/org/linphone/core/LinphoneCoreImpl.java +++ b/java/impl/org/linphone/core/LinphoneCoreImpl.java @@ -721,6 +721,10 @@ class LinphoneCoreImpl implements LinphoneCore { public synchronized int getConferenceSize() { return getConferenceSize(nativePtr); } + private native LinphoneConference getConference(long nativePtr); + public synchronized LinphoneConference getConference() { + return getConference(nativePtr); + } private native int getCallsNb(long nativePtr); public synchronized int getCallsNb() { return getCallsNb(nativePtr); diff --git a/tester/flexisip_tester.c b/tester/flexisip_tester.c index bb6329556..80d21483f 100644 --- a/tester/flexisip_tester.c +++ b/tester/flexisip_tester.c @@ -723,7 +723,7 @@ static void call_with_ipv6(void) { static void file_transfer_message_rcs_to_external_body_client(void) { if (transport_supported(LinphoneTransportTls)) { - LinphoneCoreManager* marie = linphone_core_manager_init( "marie_rc"); + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneChatRoom* chat_room; LinphoneChatMessage* message; LinphoneChatMessageCbs *cbs; @@ -732,13 +732,13 @@ static void file_transfer_message_rcs_to_external_body_client(void) { size_t file_size; char *send_filepath = bc_tester_res("images/nowebcamCIF.jpg"); char *receive_filepath = bc_tester_file("receive_file.dump"); - LinphoneCoreManager* pauline = linphone_core_manager_init( "pauline_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); linphone_proxy_config_set_custom_header(marie->lc->default_proxy, "Accept", "application/sdp"); - linphone_core_manager_start(marie, "marie_rc", TRUE); + linphone_core_manager_start(marie, TRUE); linphone_proxy_config_set_custom_header(pauline->lc->default_proxy, "Accept", "application/sdp, text/plain, application/vnd.gsma.rcs-ft-http+xml"); - linphone_core_manager_start(pauline, "pauline_rc", TRUE); + linphone_core_manager_start(pauline, TRUE); reset_counters(&marie->stat); reset_counters(&pauline->stat); @@ -823,14 +823,14 @@ void send_file_transfer_message_using_external_body_url(LinphoneCoreManager *mar static void file_transfer_message_external_body_to_external_body_client(void) { if (transport_supported(LinphoneTransportTls)) { - LinphoneCoreManager* marie = linphone_core_manager_init( "marie_rc"); - LinphoneCoreManager* pauline = linphone_core_manager_init( "pauline_rc"); + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); linphone_proxy_config_set_custom_header(marie->lc->default_proxy, "Accept", "application/sdp"); - linphone_core_manager_start(marie, "marie_rc", TRUE); + linphone_core_manager_start(marie, TRUE); linphone_proxy_config_set_custom_header(pauline->lc->default_proxy, "Accept", "application/sdp"); - linphone_core_manager_start(pauline, "pauline_rc", TRUE); + linphone_core_manager_start(pauline, TRUE); reset_counters(&marie->stat); reset_counters(&pauline->stat); @@ -847,14 +847,14 @@ static void file_transfer_message_external_body_to_external_body_client(void) { static void file_transfer_message_external_body_to_rcs_client(void) { if (transport_supported(LinphoneTransportTls)) { - LinphoneCoreManager* marie = linphone_core_manager_init( "marie_rc"); - LinphoneCoreManager* pauline = linphone_core_manager_init( "pauline_rc"); + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); linphone_proxy_config_set_custom_header(marie->lc->default_proxy, "Accept", "application/sdp"); - linphone_core_manager_start(marie, "marie_rc", TRUE); + linphone_core_manager_start(marie, TRUE); linphone_proxy_config_set_custom_header(pauline->lc->default_proxy, "Accept", "application/sdp, text/plain, application/vnd.gsma.rcs-ft-http+xml"); - linphone_core_manager_start(pauline, "pauline_rc", TRUE); + linphone_core_manager_start(pauline, TRUE); reset_counters(&marie->stat); reset_counters(&pauline->stat); diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index b9aa9052d..d16134b4e 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -256,6 +256,12 @@ typedef struct _LinphoneCoreManager { int number_of_cunit_error_at_creation; } LinphoneCoreManager; +typedef struct _LinphoneConferenceServer { + LinphoneCoreManager base; + LinphoneCall *first_call; + LinphoneCoreVTable *vtable; +} LinphoneConferenceServer; + typedef struct _LinphoneCallTestParams { LinphoneCallParams *base; bool_t sdp_removal; @@ -265,11 +271,12 @@ typedef struct _LinphoneCallTestParams { void liblinphone_tester_add_suites(void); -LinphoneCoreManager* linphone_core_manager_init(const char* rc_file); -void linphone_core_manager_start(LinphoneCoreManager *mgr, const char* rc_file, int check_for_proxies); +void linphone_core_manager_init(LinphoneCoreManager *mgr, const char* rc_file); +void linphone_core_manager_start(LinphoneCoreManager *mgr, int check_for_proxies); LinphoneCoreManager* linphone_core_manager_new2(const char* rc_file, int check_for_proxies); LinphoneCoreManager* linphone_core_manager_new(const char* rc_file); void linphone_core_manager_stop(LinphoneCoreManager *mgr); +void linphone_core_manager_uninit(LinphoneCoreManager *mgr); void linphone_core_manager_destroy(LinphoneCoreManager* mgr); void reset_counters( stats* counters); @@ -356,6 +363,9 @@ void liblinphone_tester_uninit(void); int liblinphone_tester_set_log_file(const char *filename); bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee, LinphoneIceState state); +LinphoneConferenceServer* linphone_conference_server_new(const char *rc_file); +void linphone_conference_server_destroy(LinphoneConferenceServer *conf_srv); + extern const char *liblinphone_tester_mire_id; FILE *sip_start(const char *senario, const char* dest_username, LinphoneAddress* dest_addres); diff --git a/tester/multi_call_tester.c b/tester/multi_call_tester.c index c8021afc3..61135aecd 100644 --- a/tester/multi_call_tester.c +++ b/tester/multi_call_tester.c @@ -177,8 +177,7 @@ static void incoming_call_accepted_when_outgoing_call_in_outgoing_ringing_early_ incoming_call_accepted_when_outgoing_call_in_state(LinphoneCallOutgoingEarlyMedia); } -static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManager* pauline, LinphoneCoreManager* laure) { - +static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManager* pauline, LinphoneCoreManager* laure, LinphoneCoreManager *focus) { stats initial_marie_stat; stats initial_pauline_stat; stats initial_laure_stat; @@ -187,9 +186,14 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag LinphoneCall* pauline_called_by_marie; LinphoneCall* marie_call_laure; const MSList* calls; + bool_t is_remote_conf; MSList* lcs=ms_list_append(NULL,marie->lc); lcs=ms_list_append(lcs,pauline->lc); lcs=ms_list_append(lcs,laure->lc); + if(focus) lcs=ms_list_append(lcs,focus->lc); + + is_remote_conf = (strcmp(lp_config_get_string(marie->lc->config, "misc", "conference_type", "local"), "remote") == 0); + if(is_remote_conf) BC_ASSERT_PTR_NOT_NULL(focus); BC_ASSERT_TRUE(call(marie,pauline)); marie_call_pauline=linphone_core_get_current_call(marie->lc); @@ -205,12 +209,23 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag BC_ASSERT_PTR_NOT_NULL_FATAL(marie_call_laure); linphone_core_add_to_conference(marie->lc,marie_call_laure); - BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallUpdating,initial_marie_stat.number_of_LinphoneCallUpdating+1,5000)); + if(!is_remote_conf) { + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallUpdating,initial_marie_stat.number_of_LinphoneCallUpdating+1,5000)); + } else { + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,initial_marie_stat.number_of_LinphoneCallStreamsRunning+1,5000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneTransferCallConnected,initial_marie_stat.number_of_LinphoneTransferCallConnected+1,5000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,initial_marie_stat.number_of_LinphoneCallEnd+1,5000)); + } linphone_core_add_to_conference(marie->lc,marie_call_pauline); - BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallResuming,initial_marie_stat.number_of_LinphoneCallResuming+1,2000)); - + if(!is_remote_conf) { + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallResuming,initial_marie_stat.number_of_LinphoneCallResuming+1,2000)); + } else { + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneTransferCallConnected,initial_marie_stat.number_of_LinphoneTransferCallConnected+2,5000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,initial_marie_stat.number_of_LinphoneCallEnd+2,5000)); + } + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallStreamsRunning,initial_pauline_stat.number_of_LinphoneCallStreamsRunning+1,5000)); BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallStreamsRunning,initial_laure_stat.number_of_LinphoneCallStreamsRunning+1,2000)); BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,initial_marie_stat.number_of_LinphoneCallStreamsRunning+2,3000)); @@ -218,7 +233,11 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag BC_ASSERT_TRUE(linphone_core_is_in_conference(marie->lc)); BC_ASSERT_EQUAL(linphone_core_get_conference_size(marie->lc),3, int, "%d"); - BC_ASSERT_PTR_NULL(linphone_core_get_current_call(marie->lc)); + if(!is_remote_conf) { + BC_ASSERT_PTR_NULL(linphone_core_get_current_call(marie->lc)); + } else { + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(marie->lc)); + } BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(pauline->lc)); BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(laure->lc)); @@ -241,9 +260,9 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag linphone_core_terminate_conference(marie->lc); - BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,10000)); - BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,1,10000)); - BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,1,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,is_remote_conf?2:1,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,is_remote_conf?3:1,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,is_remote_conf?2:1,10000)); ms_list_free(lcs); } @@ -252,7 +271,7 @@ static void simple_conference(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc"); - simple_conference_base(marie,pauline,laure); + simple_conference_base(marie,pauline,laure, NULL); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); linphone_core_manager_destroy(laure); @@ -299,7 +318,7 @@ static void simple_encrypted_conference_with_ice(LinphoneMediaEncryption mode) { linphone_core_set_media_encryption(pauline->lc,mode); linphone_core_set_media_encryption(laure->lc,mode); - simple_conference_base(marie,pauline,laure); + simple_conference_base(marie,pauline,laure,NULL); } else { ms_warning("No [%s] support available",linphone_media_encryption_to_string(mode)); BC_PASS("Passed"); @@ -546,11 +565,7 @@ static void call_transfer_existing_call_outgoing_call(void) { ms_list_free(lcs); } -static void eject_from_3_participants_conference(void) { - LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); - LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc"); - +static void eject_from_3_participants_conference(LinphoneCoreManager *marie, LinphoneCoreManager *pauline, LinphoneCoreManager *laure, LinphoneCoreManager *focus) { stats initial_marie_stat; stats initial_pauline_stat; stats initial_laure_stat; @@ -558,10 +573,15 @@ static void eject_from_3_participants_conference(void) { LinphoneCall* marie_call_pauline; LinphoneCall* pauline_called_by_marie; LinphoneCall* marie_call_laure; + bool_t is_remote_conf; MSList* lcs=ms_list_append(NULL,marie->lc); lcs=ms_list_append(lcs,pauline->lc); lcs=ms_list_append(lcs,laure->lc); + if(focus) lcs=ms_list_append(lcs,focus->lc); + is_remote_conf = (strcmp(lp_config_get_string(marie->lc->config, "misc", "conference_type", "local"), "remote") == 0); + if(is_remote_conf) BC_ASSERT_PTR_NOT_NULL(focus); + BC_ASSERT_TRUE(call(marie,pauline)); marie_call_pauline=linphone_core_get_current_call(marie->lc); pauline_called_by_marie=linphone_core_get_current_call(pauline->lc); @@ -577,11 +597,24 @@ static void eject_from_3_participants_conference(void) { BC_ASSERT_PTR_NOT_NULL_FATAL(marie_call_laure); linphone_core_add_to_conference(marie->lc,marie_call_laure); - BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallUpdating,initial_marie_stat.number_of_LinphoneCallUpdating+1,5000)); + + if(!is_remote_conf) BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallUpdating,initial_marie_stat.number_of_LinphoneCallUpdating+1,5000)); + else { + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneTransferCallConnected,initial_marie_stat.number_of_LinphoneTransferCallConnected+1,5000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,initial_marie_stat.number_of_LinphoneCallEnd+1,5000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,initial_laure_stat.number_of_LinphoneCallEnd+1,5000)); + } + + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_conference(marie->lc)); linphone_core_add_to_conference(marie->lc,marie_call_pauline); - BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallResuming,initial_marie_stat.number_of_LinphoneCallResuming+1,2000)); + if(!is_remote_conf) BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallResuming,initial_marie_stat.number_of_LinphoneCallResuming+1,2000)); + else { + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneTransferCallConnected,initial_marie_stat.number_of_LinphoneTransferCallConnected+2,5000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,initial_marie_stat.number_of_LinphoneCallEnd+2,5000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,initial_pauline_stat.number_of_LinphoneCallEnd+1,5000)); + } BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallStreamsRunning,initial_pauline_stat.number_of_LinphoneCallStreamsRunning+1,5000)); BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallStreamsRunning,initial_laure_stat.number_of_LinphoneCallStreamsRunning+1,2000)); @@ -590,25 +623,49 @@ static void eject_from_3_participants_conference(void) { BC_ASSERT_TRUE(linphone_core_is_in_conference(marie->lc)); BC_ASSERT_EQUAL(linphone_core_get_conference_size(marie->lc),3, int, "%d"); - BC_ASSERT_PTR_NULL(linphone_core_get_current_call(marie->lc)); + if(!is_remote_conf) BC_ASSERT_PTR_NULL(linphone_core_get_current_call(marie->lc)); - linphone_core_remove_from_conference(marie->lc, marie_call_pauline); + if(!is_remote_conf) linphone_core_remove_from_conference(marie->lc, marie_call_pauline); + else { + LinphoneConference *conference = linphone_core_get_conference(marie->lc); + const LinphoneAddress *uri = linphone_call_get_remote_address(marie_call_pauline); + linphone_conference_remove_participant(conference, uri); + } - BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallPausedByRemote,1,10000)); - BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallStreamsRunning,3,10000)); - BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,5,10000)); - BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(marie->lc)); - BC_ASSERT_EQUAL(ms_list_size(linphone_core_get_calls(marie->lc)), 2, int, "%d"); - BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(pauline->lc)); - BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(laure->lc)); - end_call(laure, marie); - end_call(pauline, marie); + if(!is_remote_conf) { + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallPausedByRemote,1,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallStreamsRunning,3,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,5,10000)); + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(marie->lc)); + BC_ASSERT_EQUAL(ms_list_size(linphone_core_get_calls(marie->lc)), 2, int, "%d"); + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(pauline->lc)); + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(laure->lc)); + } else { + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,initial_pauline_stat.number_of_LinphoneCallEnd+2,5000)); + } + + if(!is_remote_conf) { + end_call(laure, marie); + end_call(pauline, marie); - BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,10000)); - BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,1,10000)); - BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,1,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,1,10000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,1,10000)); + } else { + linphone_core_terminate_conference(marie->lc); + BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,initial_laure_stat.number_of_LinphoneCallEnd+2,3000)); + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,initial_marie_stat.number_of_LinphoneCallEnd+3,3000)); + } ms_list_free(lcs); +} + +static void eject_from_3_participants_local_conference(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc"); + + eject_from_3_participants_conference(marie, pauline, laure, NULL); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); @@ -695,22 +752,87 @@ static void eject_from_4_participants_conference(void) { linphone_core_manager_destroy(laure); linphone_core_manager_destroy(michelle); } + + +void simple_remote_conference(void) { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_tcp_rc"); + LinphoneCoreManager *laure = linphone_core_manager_new("laure_rc"); + LinphoneConferenceServer *focus = linphone_conference_server_new("conference_focus_rc"); + LpConfig *marie_config = linphone_core_get_config(marie->lc); + LinphoneProxyConfig *focus_proxy_config = linphone_core_get_default_proxy_config(((LinphoneCoreManager *)focus)->lc); + LinphoneProxyConfig *laure_proxy_config = linphone_core_get_default_proxy_config(((LinphoneCoreManager *)laure)->lc); + const char *laure_proxy_uri = linphone_proxy_config_get_server_addr(laure_proxy_config); + const char *focus_uri = linphone_proxy_config_get_identity(focus_proxy_config); + int laure_n_register = laure->stat.number_of_LinphoneRegistrationOk; + MSList *lcs = NULL; + + lp_config_set_string(marie_config, "misc", "conference_type", "remote"); + lp_config_set_string(marie_config, "misc", "conference_focus_addr", focus_uri); + + linphone_proxy_config_edit(laure_proxy_config); + linphone_proxy_config_set_route(laure_proxy_config, laure_proxy_uri); + linphone_proxy_config_done(laure_proxy_config); + lcs = ms_list_append(lcs, laure->lc); + BC_ASSERT_TRUE(wait_for_list(lcs, &laure->stat.number_of_LinphoneRegistrationOk, laure_n_register+1, 5000)); + ms_list_free(lcs); + + simple_conference_base(marie, pauline, laure, (LinphoneCoreManager *)focus); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(laure); + linphone_conference_server_destroy(focus); +} + +void eject_from_3_participants_remote_conference(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc"); + LinphoneConferenceServer *focus = linphone_conference_server_new("conference_focus_rc"); + LpConfig *marie_config = linphone_core_get_config(marie->lc); + LinphoneProxyConfig *focus_proxy_config = linphone_core_get_default_proxy_config(((LinphoneCoreManager *)focus)->lc); + LinphoneProxyConfig *laure_proxy_config = linphone_core_get_default_proxy_config(((LinphoneCoreManager *)laure)->lc); + const char *laure_proxy_uri = linphone_proxy_config_get_server_addr(laure_proxy_config); + const char *focus_uri = linphone_proxy_config_get_identity(focus_proxy_config); + int laure_n_register = laure->stat.number_of_LinphoneRegistrationOk; + MSList *lcs = NULL; + + lp_config_set_string(marie_config, "misc", "conference_type", "remote"); + lp_config_set_string(marie_config, "misc", "conference_focus_addr", focus_uri); + + linphone_proxy_config_edit(laure_proxy_config); + linphone_proxy_config_set_route(laure_proxy_config, laure_proxy_uri); + linphone_proxy_config_done(laure_proxy_config); + lcs = ms_list_append(lcs, laure->lc); + BC_ASSERT_TRUE(wait_for_list(lcs, &laure->stat.number_of_LinphoneRegistrationOk, laure_n_register+1, 5000)); + ms_list_free(lcs); + + eject_from_3_participants_conference(marie, pauline, laure, (LinphoneCoreManager *)focus); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(laure); + linphone_conference_server_destroy(focus); +} test_t multi_call_tests[] = { { "Call waiting indication", call_waiting_indication }, { "Call waiting indication with privacy", call_waiting_indication_with_privacy }, - { "Incoming call accepted when outgoing call in progress",incoming_call_accepted_when_outgoing_call_in_progress}, - { "Incoming call accepted when outgoing call in outgoing ringing",incoming_call_accepted_when_outgoing_call_in_outgoing_ringing}, - { "Incoming call accepted when outgoing call in outgoing ringing early media",incoming_call_accepted_when_outgoing_call_in_outgoing_ringing_early_media}, + { "Incoming call accepted when outgoing call in progress", incoming_call_accepted_when_outgoing_call_in_progress}, + { "Incoming call accepted when outgoing call in outgoing ringing", incoming_call_accepted_when_outgoing_call_in_outgoing_ringing}, + { "Incoming call accepted when outgoing call in outgoing ringing early media", incoming_call_accepted_when_outgoing_call_in_outgoing_ringing_early_media}, { "Simple conference", simple_conference }, - { "Simple conference with ICE",simple_conference_with_ice}, + { "Simple conference with ICE", simple_conference_with_ice}, { "Simple ZRTP conference with ICE",simple_zrtp_conference_with_ice}, + { "Eject from 3 participants conference", eject_from_3_participants_local_conference }, + { "Eject from 4 participants conference", eject_from_4_participants_conference }, { "Simple call transfer", simple_call_transfer }, { "Unattended call transfer", unattended_call_transfer }, { "Unattended call transfer with error", unattended_call_transfer_with_error }, { "Call transfer existing call outgoing call", call_transfer_existing_call_outgoing_call }, - { "Eject from 3 participants conference", eject_from_3_participants_conference }, - { "Eject from 4 participants conference", eject_from_4_participants_conference }, + { "Simple remote conference", simple_remote_conference }, + { "Eject from 3 participants in remote conference", eject_from_3_participants_remote_conference } }; test_suite_t multi_call_test_suite = {"Multi call", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, diff --git a/tester/rcfiles/conference_focus_rc b/tester/rcfiles/conference_focus_rc new file mode 100644 index 000000000..c0cdd5aaa --- /dev/null +++ b/tester/rcfiles/conference_focus_rc @@ -0,0 +1,26 @@ +[sip] +default_proxy=0 +sip_port=-1 +sip_tcp_port=-1 +sip_dtls_port=-1 +sip_tls_port=-1 + +[rtp] +audio_rtp_port=18070-28000 +text_rtp_port=39000-49000 +video_rtp_port=28070-38000 + +[proxy_0] +reg_proxy=sip.example.org;transport=tcp +reg_route=sip.example.org;transport=tcp;lr +reg_identity="Focus" +reg_expires=3600 +reg_sendregister=1 +publish=0 + +[auth_info_0] +username=focus +userid=focus +passwd=secret +realm=sip.example.org + diff --git a/tester/tester.c b/tester/tester.c index ad3380993..40f1461e2 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -260,8 +260,7 @@ static void display_status(LinphoneCore *lc, const char *status){ ms_message("display_status(): %s",status); } -LinphoneCoreManager* linphone_core_manager_init(const char* rc_file) { - LinphoneCoreManager* mgr= ms_new0(LinphoneCoreManager,1); +void linphone_core_manager_init(LinphoneCoreManager *mgr, const char* rc_file) { char *rc_path = NULL; char *hellopath = bc_tester_res("sounds/hello8000.wav"); mgr->number_of_cunit_error_at_creation = CU_get_number_of_failures(); @@ -330,16 +329,14 @@ LinphoneCoreManager* linphone_core_manager_init(const char* rc_file) { linphone_core_set_user_certificates_path(mgr->lc,bc_tester_get_writable_dir_prefix()); if (rc_path) ms_free(rc_path); - - return mgr; } -void linphone_core_manager_start(LinphoneCoreManager *mgr, const char* rc_file, int check_for_proxies) { +void linphone_core_manager_start(LinphoneCoreManager *mgr, int check_for_proxies) { LinphoneProxyConfig* proxy; int proxy_count; /*BC_ASSERT_EQUAL(ms_list_size(linphone_core_get_proxy_config_list(lc)),proxy_count, int, "%d");*/ - if (check_for_proxies && rc_file) /**/ + if (check_for_proxies) /**/ proxy_count=ms_list_size(linphone_core_get_proxy_config_list(mgr->lc)); else proxy_count=0; @@ -363,14 +360,16 @@ void linphone_core_manager_start(LinphoneCoreManager *mgr, const char* rc_file, } LinphoneCoreManager* linphone_core_manager_new( const char* rc_file) { - LinphoneCoreManager *manager = linphone_core_manager_init(rc_file); - linphone_core_manager_start(manager, rc_file, TRUE); + LinphoneCoreManager *manager = ms_new0(LinphoneCoreManager, 1); + linphone_core_manager_init(manager, rc_file); + linphone_core_manager_start(manager, TRUE); return manager; } LinphoneCoreManager* linphone_core_manager_new2( const char* rc_file, int check_for_proxies) { - LinphoneCoreManager *manager = linphone_core_manager_init(rc_file); - linphone_core_manager_start(manager, rc_file, check_for_proxies); + LinphoneCoreManager *manager = ms_new0(LinphoneCoreManager, 1); + linphone_core_manager_init(manager, rc_file); + linphone_core_manager_start(manager, check_for_proxies); return manager; } @@ -381,7 +380,7 @@ void linphone_core_manager_stop(LinphoneCoreManager *mgr){ } } -void linphone_core_manager_destroy(LinphoneCoreManager* mgr) { +void linphone_core_manager_uninit(LinphoneCoreManager *mgr) { if (mgr->stat.last_received_chat_message) { linphone_chat_message_unref(mgr->stat.last_received_chat_message); } @@ -406,8 +405,12 @@ void linphone_core_manager_destroy(LinphoneCoreManager* mgr) { if (mgr->identity) { linphone_address_destroy(mgr->identity); } + manager_count--; +} +void linphone_core_manager_destroy(LinphoneCoreManager* mgr) { + linphone_core_manager_uninit(mgr); ms_free(mgr); } @@ -668,4 +671,69 @@ bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee, Linph return video_enabled ? (realtime_text_enabled ? text_success && audio_success && video_success : audio_success && video_success) : realtime_text_enabled ? text_success && audio_success : audio_success; } +void linphone_conference_server_call_state_changed(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *msg) { + LinphoneCoreVTable *vtable = linphone_core_get_current_vtable(lc); + LinphoneConferenceServer *conf_srv = (LinphoneConferenceServer *)vtable->user_data; + + switch(cstate) { + case LinphoneCallIncomingReceived: + linphone_core_accept_call(lc, call); + break; + + case LinphoneCallStreamsRunning: + if(linphone_call_get_conference(call) == NULL) { + linphone_core_add_to_conference(lc, call); + linphone_core_leave_conference(lc); + if(conf_srv->first_call == NULL) conf_srv->first_call = linphone_call_ref(call); + } + break; + + case LinphoneCallEnd: + if(call == conf_srv->first_call) { + linphone_core_terminate_conference(lc); + linphone_call_unref(call); + conf_srv->first_call = NULL; + } + break; + + default: break; + } +} +void linphone_conference_server_refer_received(LinphoneCore *core, const char *refer_to) { + char method[20]; + LinphoneAddress *refer_to_addr = linphone_address_new(refer_to); + char *uri; + LinphoneCall *call; + + if(refer_to_addr == NULL) return; + strncpy(method, linphone_address_get_method_param(refer_to_addr), sizeof(method)); + if(strcmp(method, "BYE") == 0) { + linphone_address_clean(refer_to_addr); + uri = linphone_address_as_string_uri_only(refer_to_addr); + call = linphone_core_find_call_from_uri(core, uri); + if(call) linphone_core_terminate_call(core, call); + ms_free(uri); + } + linphone_address_destroy(refer_to_addr); +} + +LinphoneConferenceServer* linphone_conference_server_new(const char *rc_file) { + LinphoneConferenceServer *conf_srv = (LinphoneConferenceServer *)ms_new0(LinphoneConferenceServer, 1); + LinphoneCoreManager *lm = (LinphoneCoreManager *)conf_srv; + + conf_srv->vtable = linphone_core_v_table_new(); + conf_srv->vtable->call_state_changed = linphone_conference_server_call_state_changed; + conf_srv->vtable->refer_received = linphone_conference_server_refer_received; + conf_srv->vtable->user_data = conf_srv; + linphone_core_manager_init(lm, rc_file); + linphone_core_add_listener(lm->lc, conf_srv->vtable); + linphone_core_manager_start(lm, TRUE); + return conf_srv; +} + +void linphone_conference_server_destroy(LinphoneConferenceServer *conf_srv) { + linphone_core_manager_uninit((LinphoneCoreManager *)conf_srv); + linphone_core_v_table_destroy(conf_srv->vtable); + ms_free(conf_srv); +}