/* * Copyright (C) 2007 iptego GmbH * * This file is part of SEMS, a free SIP media server. * * SEMS 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. * * For a license to use the sems software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * SEMS 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 "WebConferenceDialog.h" #include "WebConference.h" #include "AmUtils.h" #include "AmMediaProcessor.h" #define LONELY_USER_TIMER_ID 50 // room name unknown WebConferenceDialog::WebConferenceDialog(AmPromptCollection& prompts, WebConferenceFactory* my_f, UACAuthCred* cred) : play_list(this), separator(this, 0), prompts(prompts), state(None), factory(my_f), cred(cred), muted(false), connect_ts(-1), disconnect_ts(-1), local_input(NULL) { // with SIP credentials -> outgoing ('dialout') call is_dialout = (cred != NULL); accept_early_session = is_dialout; // set configured playout type RTPStream()->setPlayoutType(WebConferenceFactory::m_PlayoutType); } // room name known WebConferenceDialog::WebConferenceDialog(AmPromptCollection& prompts, WebConferenceFactory* my_f, const string& room) : play_list(this), separator(this, 0), prompts(prompts), state(None), factory(my_f), muted(false), connect_ts(-1), disconnect_ts(-1), local_input(NULL), cred(NULL), lonely_user(true) { conf_id = room; DBG("set conf_id to %s\n", conf_id.c_str()); is_dialout = false; // set configured playout type RTPStream()->setPlayoutType(WebConferenceFactory::m_PlayoutType); } WebConferenceDialog::~WebConferenceDialog() { // provide statistics if ((connect_ts == -1)||(disconnect_ts == -1)) { factory->callStats(false, 0); } else { factory->callStats(true, disconnect_ts - connect_ts); } prompts.cleanup((long)this); play_list.flush(); if (is_dialout || (InConference == state)) { factory->updateStatus(is_dialout?dlg->getUser():conf_id, getLocalTag(), ConferenceRoomParticipant::Finished, ""); } } void WebConferenceDialog::connectConference(const string& room) { // set the conference id ('conference room') conf_id = room; // disconnect in/out for safety setInOut(NULL, NULL); // we need to be in the same callgroup as the other // people in the conference (important if we have multiple // MediaProcessor threads changeCallgroup(conf_id); // get a channel from the status if (channel.get() == NULL) channel.reset(AmConferenceStatus::getChannel(conf_id,getLocalTag(),RTPStream()->getSampleRate())); else AmConferenceStatus::postConferenceEvent(conf_id, ConfNewParticipant,getLocalTag()); // clear the playlist play_list.flush(); // add the channel to our playlist play_list.addToPlaylist(new AmPlaylistItem(channel.get(), channel.get())); // set the playlist as output and input if (muted) setInOut(NULL, &play_list); else setInOut(&play_list, &play_list); } void WebConferenceDialog::onInvite(const AmSipRequest& req) { if (state == None) { if (WebConferenceFactory::participant_id_paramname.length()) { string appparams = getHeader(req.hdrs, PARAM_HDR); if (appparams.length()) { participant_id = get_header_param(appparams, WebConferenceFactory::participant_id_paramname); } } else if (WebConferenceFactory::participant_id_hdr.length()) { participant_id = getHeader(req.hdrs, WebConferenceFactory::participant_id_hdr); } if (participant_id.empty()) { DBG("no Participant ID set\n"); } else { DBG("Participant ID set to '%s'\n", participant_id.c_str()); } } AmSession::onInvite(req); } void WebConferenceDialog::onSessionStart() { DBG("WebConferenceDialog::onSessionStart (state = %d)\n", state); if (None == state || InConferenceRinging == state || InConferenceEarly == state) { // set the playlist as input and output setInOut(&play_list,&play_list); if (!is_dialout) { // direct room access? if (conf_id.empty()) { state = EnteringPin; prompts.addToPlaylist(ENTER_PIN, (long)this, play_list); } else { DBG("########## direct connect conference '%s' #########\n", conf_id.c_str()); string orig_pin_str = conf_id; pin_str = conf_id; if (WebConferenceFactory::room_pin_split) { if (pin_str.length() <= WebConferenceFactory::room_pin_split_pos) { DBG("short conference room/pin combination ('%s', want at least %d)\n", pin_str.c_str(), WebConferenceFactory::room_pin_split_pos); setInOut(&play_list,&play_list); play_list.flush(); prompts.addToPlaylist(WRONG_PIN, (long)this, play_list); pin_str =""; return; } participant_id = pin_str.substr(WebConferenceFactory::room_pin_split_pos); conf_id = pin_str.substr(0, WebConferenceFactory::room_pin_split_pos); DBG("split entered pin into room '%s' and PIN '%s'\n", conf_id.c_str(), participant_id.c_str()); } if (!factory->newParticipant(conf_id, getLocalTag(), dlg->getRemoteParty(), participant_id)) { DBG("inexisting conference room '%s\n", conf_id.c_str()); state = PlayErrorFinish; prompts.addToPlaylist(WRONG_PIN_BYE, (long)this, play_list); } else { factory->updateStatus(conf_id, getLocalTag(), ConferenceRoomParticipant::Connected, "direct access: entered"); state = InConference; time(&connect_ts); connectConference(conf_id); } } } else { setMute(false); DBG("########## dialout: connect to conference '%s' #########\n", dlg->getUser().c_str()); state = InConference; setLocalInput(NULL); time(&connect_ts); connectConference(dlg->getUser()); } } AmSession::onSessionStart(); } void WebConferenceDialog::onRinging(const AmSipReply& rep) { if (None == state || InConferenceEarly == state) { DBG("########## dialout: connect ringing session to conference '%s' #########\n", dlg->getUser().c_str()); if(!RingTone.get()) RingTone.reset(new AmRingTone(0,2000,4000,440,480)); // US setLocalInput(RingTone.get()); if (None == state) { connectConference(dlg->getUser()); } state = InConferenceRinging; } AmSession::onRinging(rep); } void WebConferenceDialog::onEarlySessionStart() { if (None == state || InConferenceRinging == state) { DBG("########## dialout: connect early session to conference '%s' #########\n", dlg->getUser().c_str()); setLocalInput(NULL); if (None == state) { connectConference(dlg->getUser()); } // setMute(true); state = InConferenceEarly; } AmSession::onEarlySessionStart(); } void WebConferenceDialog::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmBasicSipDialog::Status old_dlg_status) { AmSession::onSipReply(req,reply,old_dlg_status); DBG("reply: %u %s, old_dlg_status = %s, status = %s\n", reply.code, reply.reason.c_str(), AmBasicSipDialog::getStatusStr(old_dlg_status), dlg->getStatusStr()); if ((old_dlg_status < AmSipDialog::Connected) && (dlg->getStatus() == AmSipDialog::Disconnected)) { DBG("Call failed.\n"); setStopped(); } // update status to map if (is_dialout) { // map AmSipDialog state to WebConferenceState ConferenceRoomParticipant::ParticipantStatus rep_st = ConferenceRoomParticipant::Connecting; switch (dlg->getStatus()) { case AmSipDialog::Trying: case AmSipDialog::Proceeding: case AmSipDialog::Early: { rep_st = ConferenceRoomParticipant::Connecting; if (reply.code == 180 || reply.code == 183) rep_st = ConferenceRoomParticipant::Ringing; } break; case AmSipDialog::Connected: rep_st = ConferenceRoomParticipant::Connected; break; case AmSipDialog::Cancelling: case AmSipDialog::Disconnecting: rep_st = ConferenceRoomParticipant::Disconnecting; break; case AmSipDialog::Disconnected: rep_st = ConferenceRoomParticipant::Finished; break; default:break; } DBG("is dialout: updateing status\n"); factory->updateStatus(dlg->getUser(), getLocalTag(), rep_st, int2str(reply.code) + " " + reply.reason); } } void WebConferenceDialog::onBye(const AmSipRequest& req) { if (InConference == state) { factory->updateStatus(conf_id, getLocalTag(), ConferenceRoomParticipant::Disconnecting, req.method); } disconnectConference(); } void WebConferenceDialog::onRtpTimeout() { DBG("RTP timeout, removing from conference\n"); disconnectConference(); AmSession::onRtpTimeout(); } void WebConferenceDialog::onSessionTimeout() { DBG("Session Timer: Timeout, removing from conference.\n"); disconnectConference(); AmSession::onSessionTimeout(); } void WebConferenceDialog::disconnectConference() { play_list.flush(); setInOut(NULL,NULL); channel.reset(NULL); setStopped(); time(&disconnect_ts); } void WebConferenceDialog::process(AmEvent* ev) { // check conference events ConferenceEvent* ce = dynamic_cast(ev); if(ce && (conf_id == ce->conf_id)){ switch(ce->event_id){ case ConfNewParticipant: { DBG("########## new participant (%d) #########\n", ce->participants); if(ce->participants == 1){ prompts.addToPlaylist(FIRST_PARTICIPANT, (long)this, play_list, true, WebConferenceFactory::LoopFirstParticipantPrompt); if (WebConferenceFactory::LonelyUserTimer) { DBG("only person in the room - setting LonelyUserTimer %u sec\n", WebConferenceFactory::LonelyUserTimer); setTimer(LONELY_USER_TIMER_ID, WebConferenceFactory::LonelyUserTimer); lonely_user = true; } } else { // someone else joined lonely_user = false; if (WebConferenceFactory::LoopFirstParticipantPrompt) { // -- reset the channel (flush playlist) // get a channel from the status if (channel.get() == NULL) channel.reset(AmConferenceStatus::getChannel(conf_id,getLocalTag(),RTPStream()->getSampleRate())); // clear the playlist play_list.flush(); // add the channel to our playlist play_list.addToPlaylist(new AmPlaylistItem(channel.get(), channel.get())); } prompts.addToPlaylist(JOIN_SOUND, (long)this, play_list, true); } } break; case ConfParticipantLeft: { DBG("########## participant left ########\n"); prompts.addToPlaylist(DROP_SOUND, (long)this, play_list, true); if(ce->participants == 1) { if (WebConferenceFactory::LonelyUserTimer) { DBG("only person in the room - setting LonelyUserTimer %u sec\n", WebConferenceFactory::LonelyUserTimer); setTimer(LONELY_USER_TIMER_ID, WebConferenceFactory::LonelyUserTimer); lonely_user = true; } } } break; default: break; } return; } // our item will fire this event AmPlaylistSeparatorEvent* sep_ev = dynamic_cast(ev); if (NULL != sep_ev) { // don't care for the id here if (EnteringConference == state) { state = InConference; DBG("########## connectConference after pin entry #########\n"); if (!factory->newParticipant(pin_str, getLocalTag(), dlg->getRemoteParty(), participant_id)) { DBG("inexisting conference room '%s' or pin wrong\n", pin_str.c_str()); state = PlayErrorFinish; setInOut(&play_list,&play_list); prompts.addToPlaylist(WRONG_PIN_BYE, (long)this, play_list); return; } time(&connect_ts); connectConference(pin_str); factory->updateStatus(pin_str, getLocalTag(), ConferenceRoomParticipant::Connected, "entered"); } } // audio events AmAudioEvent* audio_ev = dynamic_cast(ev); if (audio_ev && audio_ev->event_id == AmAudioEvent::noAudio) { DBG("########## noAudio event #########\n"); if (PlayErrorFinish == state) { DBG("Finished playing bye message, ending call.\n"); dlg->bye(); setStopped(); return; } return; } WebConferenceEvent* webconf_ev = dynamic_cast(ev); if (NULL != webconf_ev) { switch(webconf_ev->event_id) { case WebConferenceEvent::Kick: onKicked(); break; case WebConferenceEvent::Mute: onMuted(true); break; case WebConferenceEvent::Unmute: onMuted(false); break; default: { WARN("ignoring unknown webconference event %d\n", webconf_ev->event_id); } break; } } AmPluginEvent* plugin_event = dynamic_cast(ev); if(plugin_event && plugin_event->name == "timer_timeout") { int timer_id = plugin_event->data.get(0).asInt(); if (timer_id == LONELY_USER_TIMER_ID && lonely_user) { DBG("LonelyUserTimer of %u sec expired - kicking lonely user\n", WebConferenceFactory::LonelyUserTimer); onKicked(); return; } } AmSession::process(ev); } void WebConferenceDialog::onDtmf(int event, int duration) { DBG("WebConferenceDialog::onDtmf: event %d duration %d\n", event, duration); if (EnteringPin == state) { // not yet in conference if (event<10) { pin_str += int2str(event); DBG("added '%s': PIN is now '%s'.\n", int2str(event).c_str(), pin_str.c_str()); play_list.flush(); } else if (event==10 || event==11) { // pound and star key if (!pin_str.length()) { prompts.addToPlaylist(WRONG_PIN, (long)this, play_list, true); return; } string orig_pin_str = pin_str; if (WebConferenceFactory::room_pin_split) { if (pin_str.length() <= WebConferenceFactory::room_pin_split_pos) { DBG("short conference room/pin combination ('%s', want at least %d)\n", pin_str.c_str(), WebConferenceFactory::room_pin_split_pos); setInOut(&play_list,&play_list); play_list.flush(); prompts.addToPlaylist(WRONG_PIN, (long)this, play_list); pin_str =""; return; } participant_id = pin_str.substr(WebConferenceFactory::room_pin_split_pos); pin_str = pin_str.substr(0, WebConferenceFactory::room_pin_split_pos); DBG("split entered pin into room '%s' and PIN '%s'\n", pin_str.c_str(), participant_id.c_str()); } if (!factory->isValidConference(pin_str, WebConferenceFactory::room_pin_split ? participant_id : "")) { setInOut(&play_list,&play_list); play_list.flush(); prompts.addToPlaylist(WRONG_PIN, (long)this, play_list); pin_str =""; return; } state = EnteringConference; setInOut(NULL, NULL); play_list.flush(); for (size_t i=0;ibye(); disconnectConference(); factory->updateStatus(conf_id, getLocalTag(), ConferenceRoomParticipant::Disconnecting, "disconnect"); } void WebConferenceDialog::onMuted(bool mute) { DBG("########## WebConference::onMuted('%s') #########\n", mute?"true":"false"); if (muted != mute) { muted = mute; switch (state) { case InConference: case InConferenceEarly: { if (muted) setInOut(NULL, &play_list); else setInOut(&play_list, &play_list); } break; case InConferenceRinging: { if (muted) { setLocalInput(NULL); } else { if(!RingTone.get()) RingTone.reset(new AmRingTone(0,2000,4000,440,480)); // US setLocalInput(RingTone.get()); if (isDetached()) AmMediaProcessor::instance()->addSession(this, callgroup); } } break; default: DBG("No default action for changing mute status.\n"); break; } } } void WebConferenceDialog::setLocalInput(AmAudio* in) { lockAudio(); local_input = in; unlockAudio(); } int WebConferenceDialog::readStreams(unsigned long long ts, unsigned char *buffer) { int res = 0; lockAudio(); AmRtpAudio *stream = RTPStream(); unsigned int f_size = stream->getFrameSize(); if (stream->checkInterval(ts)) { int got = 0; if (local_input) got = local_input->get(ts, buffer, stream->getSampleRate(), f_size); else got = stream->get(ts, buffer, stream->getSampleRate(), f_size); if (got < 0) res = -1; if (got > 0) { if (isDtmfDetectionEnabled()) putDtmfAudio(buffer, got, ts); if (input) res = input->put(ts, buffer, stream->getSampleRate(), got); } } unlockAudio(); return res; } bool WebConferenceDialog::isAudioSet() { lockAudio(); bool set = input || output || local_input; unlockAudio(); return set; } void WebConferenceDialog::clearAudio() { lockAudio(); if (local_input) { local_input->close(); local_input = NULL; } unlockAudio(); AmSession::clearAudio(); // locking second time but called not so often? }