diff --git a/apps/webconference/Makefile b/apps/webconference/Makefile new file mode 100644 index 00000000..b188b051 --- /dev/null +++ b/apps/webconference/Makefile @@ -0,0 +1,9 @@ +plug_in_name = webconference + +module_ldflags = +module_cflags = + +extra_install = $(plug_in_name)_audio + +COREPATH ?=../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/webconference/Readme.webconference b/apps/webconference/Readme.webconference new file mode 100644 index 00000000..4b89ca48 --- /dev/null +++ b/apps/webconference/Readme.webconference @@ -0,0 +1,78 @@ +webconference : conference with dial-in and dial-out over DI (xmlrpc) + +This conference module can do dial-in, dial-out and mixed +dial-in/dial-out conferences. It can be controlled over DI functions +from other modules, and, for example, using the xmlrpc2di module, +the conference rooms can be controlled via XMLRPC. + +It implements conference rooms with dial-in and conference room entry +via DTMF, and authenticated dial-out. + +Participants can be listed, kicked out, muted and unmuted over DI +(using xmlrpc2di over XMLRPC). + +implemented DI functions: + +---- +roomCreate(string room): + int code, string result, string adminpin + + code/result: + 0 OK + 1 room already open +---- +roomInfo(string room, string adminpin): + int code, string result, list participants + participant: string call_tag, string number, int status, string reason, int muted + + status: + 0 Disconnected + 1 Connecting + 2 Ringing + 3 Connected + 4 Disconnecting + reason: e.g. "200 OK", "606 Declined", ... + + code/result: + 0 OK + 1 wrong adminpin +---- +dialout(string room, string adminpin, string callee, + string from_user, string domain, + string auth_user, string auth_realm, string auth_pwd) : + int code, string result, string callid + + code/result: + 0 OK + 1 wrong adminpin + 2 failed +---- +kickout(string room, string adminpin, string call_tag) : + int code, string result + + code/result: + 0 OK + 1 wrong adminpin + 2 call does not exist in room +---- +mute(string room, string adminpin, string call_tag) : + int code, string result + + code/result: + 0 OK + 1 wrong adminpin + 2 call does not exist in room +---- +unmute(string room, string adminpin, string call_tag) : + int code, string result + + code/result: + 0 OK + 1 wrong adminpin + 2 call does not exist in room +---- +serverInfo(): + string name, + int load, + int cpu +---- \ No newline at end of file diff --git a/apps/webconference/RoomInfo.cpp b/apps/webconference/RoomInfo.cpp new file mode 100644 index 00000000..4e777acf --- /dev/null +++ b/apps/webconference/RoomInfo.cpp @@ -0,0 +1,114 @@ + +#include "RoomInfo.h" +#include +#include "log.h" + +void ConferenceRoomParticipant::updateAccess(const struct timeval& now) { + memcpy(&last_access_time, &now, sizeof(struct timeval)); +} + +bool ConferenceRoomParticipant::expired(const struct timeval& now) { + if (Finished != status) + return false; + + struct timeval diff; + timersub(&now,&last_access_time,&diff); + return (diff.tv_sec > 0) && + (unsigned int)diff.tv_sec > PARTICIPANT_EXPIRED_DELAY; +} + +AmArgArray* ConferenceRoomParticipant::asArgArray() { + AmArgArray* res = new AmArgArray(); + res->push(AmArg(localtag.c_str())); + res->push(AmArg(number.c_str())); + res->push(AmArg((int)status)); + res->push(AmArg(last_reason.c_str())); + res->push(AmArg((int)muted)); + return res; +} + +void ConferenceRoomParticipant::setMuted(int mute) { + muted = mute; +} + +void ConferenceRoomParticipant::updateStatus(ConferenceRoomParticipant::ParticipantStatus + new_status, + const string& reason) { + status = new_status; + last_reason = reason; + struct timeval now; + gettimeofday(&now, NULL); + updateAccess(now); +} + +void ConferenceRoom::cleanExpired() { + struct timeval now; + gettimeofday(&now, NULL); + + list::iterator it=participants.begin(); + while (it != participants.end()) { + if (it->expired(now)) { + participants.erase(it); + it=participants.begin(); + } else + it++; + } +} + +AmArgArray* ConferenceRoom::asArgArray() { + cleanExpired(); + AmArgArray* res = new AmArgArray(); + for (list::iterator it=participants.begin(); + it != participants.end(); it++) { + AmArg r; + r.setBorrowedPointer(it->asArgArray()); + res->push(r); + } + return res; +} + +void ConferenceRoom::newParticipant(const string& localtag, + const string& number) { + participants.push_back(ConferenceRoomParticipant()); + participants.back().localtag = localtag; + participants.back().number = number; +} + +bool ConferenceRoom::hasParticipant(const string& localtag) { + bool res = false; + + for (list::iterator it =participants.begin(); + it != participants.end();it++) + if (it->localtag == localtag) { + res = true; + break; + } + return res; +} + +void ConferenceRoom::setMuted(const string& localtag, int mute) { + for (list::iterator it =participants.begin(); + it != participants.end();it++) + if (it->localtag == localtag) { + it->setMuted(mute); + break; + } +} + +bool ConferenceRoom::updateStatus(const string& part_tag, + ConferenceRoomParticipant::ParticipantStatus newstatus, + const string& reason) { + cleanExpired(); + + bool res = false; + list::iterator it=participants.begin(); + while (it != participants.end()) { + if (it->localtag == part_tag) { + it->updateStatus(newstatus, reason); + res = true; + } + it++; + } + return res; +} + diff --git a/apps/webconference/RoomInfo.h b/apps/webconference/RoomInfo.h new file mode 100644 index 00000000..f8999838 --- /dev/null +++ b/apps/webconference/RoomInfo.h @@ -0,0 +1,78 @@ + +#ifndef ROOM_INFO_H +#define ROOM_INFO_H + +#include +using std::string; + +#include +using std::list; + +#include + + +#include "AmArg.h" +#include "AmThread.h" + +#define PARTICIPANT_EXPIRED_DELAY 10 + +struct ConferenceRoomParticipant { + enum ParticipantStatus { + Disconnected = 0, + Connecting, + Ringing, + Connected, + Disconnecting, + Finished + }; + + string localtag; + string number; + ParticipantStatus status; + string last_reason; + + int muted; + + struct timeval last_access_time; + + ConferenceRoomParticipant() + : status(Disconnected), muted(0) { } + + ~ConferenceRoomParticipant() { } + + inline void updateAccess(const struct timeval& now); + inline bool expired(const struct timeval& now); + + inline void updateStatus(ParticipantStatus new_status, + const string& reason); + + inline void setMuted(int mute); + + AmArgArray* asArgArray(); +}; + +struct ConferenceRoom { + string adminpin; + + list participants; + + ConferenceRoom() { } + ~ConferenceRoom() { } + + void cleanExpired(); + + AmArgArray* asArgArray(); + + void newParticipant(const string& localtag, const string& number); + + bool updateStatus(const string& part_tag, + ConferenceRoomParticipant::ParticipantStatus newstatus, + const string& reason); + + bool hasParticipant(const string& localtag); + + void setMuted(const string& localtag, int mute); +}; + + +#endif diff --git a/apps/webconference/WebConference.cpp b/apps/webconference/WebConference.cpp new file mode 100644 index 00000000..83e5b9f1 --- /dev/null +++ b/apps/webconference/WebConference.cpp @@ -0,0 +1,627 @@ +/* + * $Id: WebConference.cpp 288 2007-03-28 16:32:02Z sayer $ + * + * 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 "WebConference.h" +#include "AmConferenceStatus.h" +#include "AmUtils.h" +#include "log.h" +#include "AmUAC.h" +#include "AmPlugIn.h" +#include "AmSessionContainer.h" + +#include + +#define APP_NAME "webconference" + +EXPORT_SESSION_FACTORY(WebConferenceFactory,APP_NAME); +EXPORT_PLUGIN_CLASS_FACTORY(WebConferenceFactory,APP_NAME); + +WebConferenceFactory::WebConferenceFactory(const string& _app_name) + : AmSessionFactory(_app_name), + AmDynInvokeFactory(_app_name), + configured(false) +{ + if (NULL == _instance) { + _instance = this; + } +} + +WebConferenceFactory* WebConferenceFactory::_instance=0; + +string WebConferenceFactory::DigitsDir; +PlayoutType WebConferenceFactory::m_PlayoutType = ADAPTIVE_PLAYOUT; + +int WebConferenceFactory::onLoad() +{ + // only execute this once + if (configured) + return 0; + configured = true; + + AmConfigReader cfg; + if(cfg.loadFile(AmConfig::ModConfigPath + string(APP_NAME)+ ".conf")) + return -1; + + // get application specific global parameters + configureModule(cfg); + + // get prompts + AM_PROMPT_START; + AM_PROMPT_ADD(FIRST_PARTICIPANT, ANNOUNCE_PATH "first_paricipant.wav"); + AM_PROMPT_ADD(JOIN_SOUND, ANNOUNCE_PATH "beep.wav"); + AM_PROMPT_ADD(DROP_SOUND, ANNOUNCE_PATH "beep.wav"); + AM_PROMPT_ADD(ENTER_PIN, ANNOUNCE_PATH "enter_pin.wav"); + AM_PROMPT_ADD(WRONG_PIN, ANNOUNCE_PATH "wrong_pin.wav"); + AM_PROMPT_ADD(ENTERING_CONFERENCE, ANNOUNCE_PATH "entering_conference.wav"); + AM_PROMPT_END(prompts, cfg, APP_NAME); + + DigitsDir = cfg.getParameter("digits_dir"); + if (DigitsDir.length() && DigitsDir[DigitsDir.length()-1]!='/') + DigitsDir+='/'; + + if (!DigitsDir.length()) { + WARN("No digits_dir specified in configuration.\n"); + } + for (int i=0;i<10;i++) + prompts.setPrompt(int2str(i), DigitsDir+int2str(i)+".wav", APP_NAME); + + string playout_type = cfg.getParameter("playout_type"); + if (playout_type == "simple") { + m_PlayoutType = SIMPLE_PLAYOUT; + DBG("Using simple (fifo) buffer as playout technique.\n"); + } else if (playout_type == "adaptive_jb") { + m_PlayoutType = JB_PLAYOUT; + DBG("Using adaptive jitter buffer as playout technique.\n"); + } else { + DBG("Using adaptive playout buffer as playout technique.\n"); + } + + return 0; +} + +void WebConferenceFactory::newParticipant(const string& conf_id, + const string& localtag, + const string& number) { + DBG("newparticipant ------------------- %s %s %s\n", conf_id.c_str(), + localtag.c_str(), + number.c_str()); + rooms_mut.lock(); + rooms[conf_id].newParticipant(localtag, number); + rooms_mut.unlock(); +} + +void WebConferenceFactory::updateStatus(const string& conf_id, + const string& localtag, + ConferenceRoomParticipant::ParticipantStatus status, + const string& reason) { + rooms_mut.lock(); + rooms[conf_id].updateStatus(localtag, status, reason); + rooms_mut.unlock(); +} + +ConferenceRoom* WebConferenceFactory::getRoom(const string& room, + const string& adminpin) { + ConferenceRoom* res = NULL; + map::iterator it = rooms.find(room); + if (it == rooms.end()) { + // (re)open room + rooms[room] = ConferenceRoom(); + rooms[room].adminpin = adminpin; + res = &rooms[room]; + } else { + if (!it->second.adminpin.empty() && + (it->second.adminpin != adminpin)) { + // wrong pin + } else { + // update adminpin if room was created by dialin + if (it->second.adminpin.empty()) + it->second.adminpin = adminpin; + res = &it->second; + } + } + + return res; +} + +// incoming calls +AmSession* WebConferenceFactory::onInvite(const AmSipRequest& req) +{ + DBG("ONINVITE ----------------- INCOMING ---------------------\n"); + return new WebConferenceDialog(prompts, getInstance(), NULL); +} + +// outgoing calls - req is INVITE +AmSession* WebConferenceFactory::onInvite(const AmSipRequest& req, + AmArg& session_params) +{ + DBG("ONINVITE ----------------- OUTGOING ---------------------\n"); + + UACAuthCred* cred = NULL; + if (session_params.getType() == AmArg::AObject) { + ArgObject* cred_obj = session_params.asObject(); + if (cred_obj) + cred = dynamic_cast(cred_obj); + } + + AmSession* s = new WebConferenceDialog(prompts, getInstance(), cred); + + AmSessionEventHandlerFactory* uac_auth_f = + AmPlugIn::instance()->getFactory4Seh("uac_auth"); + if (uac_auth_f != NULL) { + DBG("UAC Auth enabled for new announcement session.\n"); + AmSessionEventHandler* h = uac_auth_f->getHandler(s); + if (h != NULL ) + s->addHandler(h); + } else { + ERROR("uac_auth interface not accessible. Load uac_auth for authenticated dialout.\n"); + } + + return s; +} + +void WebConferenceFactory::invoke(const string& method, + const AmArgArray& args, + AmArgArray& ret) +{ + if(method == "roomCreate"){ + roomCreate(args, ret); + } else if(method == "roomInfo"){ + roomInfo(args, ret); + } else if(method == "dialout"){ + dialout(args, ret); + } else if(method == "mute"){ + mute(args, ret); + } else if(method == "unmute"){ + unmute(args, ret); + } else if(method == "kickout"){ + kickout(args, ret); + } else if(method == "serverInfo"){ + serverInfo(args, ret); + } else if(method == "help"){ + ret.push("help text goes here"); + } else + throw AmDynInvoke::NotImplemented(method); +} + +string WebConferenceFactory::getRandomPin() { + string res; + for (int i=0;i<6;i++) + res+=(char)('0'+random()%10); + return res; +} + +void WebConferenceFactory::roomCreate(const AmArgArray& args, AmArgArray& ret) { + string room = args.get(0).asCStr(); + rooms_mut.lock(); + map::iterator it = rooms.find(room); + if (it == rooms.end()) { + rooms[room] = ConferenceRoom(); + rooms[room].adminpin = getRandomPin(); + ret.push(0); + ret.push("OK"); + ret.push(rooms[room].adminpin.c_str()); + } else { + ret.push(1); + ret.push("room already opened"); + } + rooms_mut.unlock(); +} + +void WebConferenceFactory::roomInfo(const AmArgArray& args, AmArgArray& ret) { + string room = args.get(0).asCStr(); + string adminpin = args.get(1).asCStr();; + + rooms_mut.lock(); + ConferenceRoom* r = getRoom(room, adminpin); + if (NULL == r) { + ret.push(1); + ret.push("wrong adminpin"); + // for consistency, add an empty array + AmArgArray* a = new AmArgArray(); + AmArg res; + res.setBorrowedPointer(a); + ret.push(res); + } else { + ret.push(0); + ret.push("OK"); + AmArg res; + res.setBorrowedPointer(r->asArgArray()); + ret.push(res); + } + rooms_mut.unlock(); + +// if (checkAdminpin(room, adminpin)) { +// ret.push(0); +// ret.push("OK"); +// AmArg r; +// r.setBorrowedPointer(it->second.asArgArray()); +// ret.push(r); +// } else { +// ret.push(1); +// ret.push("wrong adminpin"); +// // for consistency, add an empty array +// AmArgArray* a = new AmArgArray(); +// AmArg r; +// r.setBorrowedPointer(a); +// ret.push(r); +// } + +// rooms_mut.lock(); +// map::iterator it = rooms.find(room); +// if (it == rooms.end()) { +// // (re)open room +// rooms[room] = ConferenceRoom(); +// rooms[room].adminpin = adminpin; +// ret.push(0); +// ret.push("OK"); +// // add empty paricipants list +// AmArgArray* a = new AmArgArray(); +// AmArg r; +// r.setBorrowedPointer(a); +// ret.push(r); +// } else { +// if (!it->second.adminpin.empty() && +// (it->second.adminpin != adminpin)) { +// ret.push(1); +// ret.push("wrong adminpin"); +// } else { +// // update adminpin if room was created by dialin +// if (it->second.adminpin.empty()) +// it->second.adminpin = adminpin; +// // return room info +// ret.push(0); +// ret.push("OK"); +// AmArg r; +// r.setBorrowedPointer(it->second.asArgArray()); +// ret.push(r); +// } +// } + +// rooms_mut.unlock(); +} + +void WebConferenceFactory::dialout(const AmArgArray& args, AmArgArray& ret) { + string room = args.get(0).asCStr(); + string adminpin = args.get(1).asCStr(); + string callee = args.get(2).asCStr(); + string from_user = args.get(3).asCStr(); + string domain = args.get(4).asCStr(); + string auth_user = args.get(5).asCStr(); + string auth_realm = args.get(6).asCStr(); + string auth_pwd = args.get(7).asCStr(); + + string from = "sip:" + from_user + "@" + domain; + string to = "sip:" + callee + "@" + domain; + + // check adminpin + rooms_mut.lock(); + ConferenceRoom* r = getRoom(room, adminpin); + rooms_mut.unlock(); + if (NULL == r) { + ret.push(1); + ret.push("wrong adminpin"); + ret.push(""); + return; + } + + DBG("dialout webconference room '%s', from '%s', to '%s'", + room.c_str(), from.c_str(), to.c_str()); + + AmArg* a = new AmArg(); + a->setBorrowedPointer(new UACAuthCred(auth_realm, auth_user, auth_pwd)); + + AmSession* s = AmUAC::dialout(room.c_str(), APP_NAME, to, + "<" + from + ">", from, "<" + to + ">", + string(""), // callid + a); + if (s) { + string localtag = s->getLocalTag(); + ret.push(0); + ret.push("OK"); + ret.push(localtag.c_str()); + newParticipant(room, localtag, to); + updateStatus(room, localtag, + ConferenceRoomParticipant::Connecting, + "INVITE"); + } + else { + ret.push(1); + ret.push("internal error"); + ret.push(""); + } +} + +void WebConferenceFactory::postConfEvent(const AmArgArray& args, AmArgArray& ret, + int id, int mute) { + string room = args.get(0).asCStr(); + string adminpin = args.get(1).asCStr(); + string call_tag = args.get(2).asCStr(); + + // check adminpin + + rooms_mut.lock(); + ConferenceRoom* r = getRoom(room, adminpin); + if (NULL == r) { + ret.push(1); + ret.push("wrong adminpin"); + rooms_mut.unlock(); + return; + } + bool p_exists = r->hasParticipant(call_tag); + if (p_exists && (mute >= 0)) + r->setMuted(call_tag, mute); + + rooms_mut.unlock(); + + if (p_exists) { + AmSessionContainer::instance()->postEvent(call_tag, + new WebConferenceEvent(id)); + ret.push(0); + ret.push("OK"); + } else { + ret.push(2); + ret.push("call does not exist"); + } +} + +void WebConferenceFactory::kickout(const AmArgArray& args, AmArgArray& ret) { + postConfEvent(args, ret, WebConferenceEvent::Kick, -1); +} + +void WebConferenceFactory::mute(const AmArgArray& args, AmArgArray& ret) { + postConfEvent(args, ret, WebConferenceEvent::Mute, 1); +} + +void WebConferenceFactory::unmute(const AmArgArray& args, AmArgArray& ret) { + postConfEvent(args, ret, WebConferenceEvent::Unmute, 0); +} + +void WebConferenceFactory::serverInfo(const AmArgArray& args, AmArgArray& ret) { + ret.push("Not yet implemented"); +} + +WebConferenceDialog::WebConferenceDialog(AmPromptCollection& prompts, + WebConferenceFactory* my_f, + UACAuthCred* cred) + : play_list(this), separator(this, 0), prompts(prompts), state(None), + factory(my_f), cred(cred) +{ + is_dialout = (cred != NULL); + // set configured playout type + rtp_str.setPlayoutType(WebConferenceFactory::m_PlayoutType); +} + +WebConferenceDialog::~WebConferenceDialog() +{ + prompts.cleanup((long)this); + if (InConference == state) { + factory->updateStatus(conf_id, + getLocalTag(), + ConferenceRoomParticipant::Finished, + "out"); + } +} + +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 + channel.reset(AmConferenceStatus::getChannel(conf_id,getLocalTag())); + + // clear the playlist + play_list.close(); + + // add the channel to our playlist + play_list.addToPlaylist(new AmPlaylistItem(channel.get(), + channel.get())); + + // set the playlist as input and output + setInOut(&play_list,&play_list); + +} + +void WebConferenceDialog::onSessionStart(const AmSipRequest& req) { + state = EnteringPin; + + prompts.addToPlaylist(ENTER_PIN, (long)this, play_list); + + // set the playlist as input and output + setInOut(&play_list,&play_list); +} + +void WebConferenceDialog::onSessionStart(const AmSipReply& rep) { + state = InConference; + connectConference(dlg.user); +} + +void WebConferenceDialog::onSipReply(const AmSipReply& reply) { + DBG("SIP REPLY ----------------------\n"); + AmSession::onSipReply(reply); + + if (is_dialout) { + DBG("is_dialout"); + // map AmSipDialog state to WebConferenceState + ConferenceRoomParticipant::ParticipantStatus rep_st = ConferenceRoomParticipant::Connecting; + switch (dlg.getStatus()) { + case AmSipDialog::Pending: { + rep_st = ConferenceRoomParticipant::Connecting; + if (reply.code == 180) + rep_st = ConferenceRoomParticipant::Ringing; + } break; + case AmSipDialog::Connected: + rep_st = ConferenceRoomParticipant::Connected; break; + case AmSipDialog::Disconnecting: + rep_st = ConferenceRoomParticipant::Disconnecting; break; + } + DBG("is dialout: updateing status\n"); + factory->updateStatus(dlg.user, 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::disconnectConference() { + play_list.close(); + setInOut(NULL,NULL); + channel.reset(NULL); + setStopped(); +} + +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 #########\n"); + if(ce->participants == 1){ + prompts.addToPlaylist(FIRST_PARTICIPANT, (long)this, play_list, true); + } else { + 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); + } 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. **********************\n"); + connectConference(pin_str); + factory->newParticipant(pin_str, + getLocalTag(), + dlg.remote_party); + factory->updateStatus(pin_str, + getLocalTag(), + ConferenceRoomParticipant::Connected, + "200 OK"); + } + } + // audio events + AmAudioEvent* audio_ev = dynamic_cast(ev); + if (audio_ev && + audio_ev->event_id == AmAudioEvent::noAudio) { + DBG("received noAudio event. **********************\n"); + return; + } + + WebConferenceEvent* webconf_ev = dynamic_cast(ev); + if (NULL != webconf_ev) { + if (InConference == state) { + switch(webconf_ev->event_id) { + case WebConferenceEvent::Kick: { + dlg.bye(); + disconnectConference(); + factory->updateStatus(conf_id, + getLocalTag(), + ConferenceRoomParticipant::Disconnecting, + "disconnect"); + } break; + case WebConferenceEvent::Mute: { setInOut(NULL, &play_list); } break; + case WebConferenceEvent::Unmute:{ setInOut(&play_list, &play_list); } break; + default: { WARN("ignoring unknown webconference event %d\n", webconf_ev->event_id); } break; + } + } + 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()); + } else if (event==10 || event==11) { + // pound and star key + // if required add checking of pin here... + if (!pin_str.length()) { + + prompts.addToPlaylist(WRONG_PIN, (long)this, play_list, true); + } else { + state = EnteringConference; + setInOut(NULL, NULL); + play_list.close(); + for (size_t i=0;i +#include +using std::map; +using std::string; + +class ConferenceStatus; +class ConferenceStatusContainer; + +// configuration parameter names +#define ENTERING_CONFERENCE "entering_conference" +#define FIRST_PARTICIPANT "first_participant" +#define JOIN_SOUND "join_sound" +#define DROP_SOUND "drop_sound" +#define ENTER_PIN "enter_pin" +#define WRONG_PIN "wrong_pin" + +// default path for files +#define ANNOUNCE_PATH "../apps/examples/webconference/" + +class WebConferenceEvent : public AmEvent +{ +public: + WebConferenceEvent(int id) : AmEvent(id) { } + enum { Kick, + Mute, + Unmute + }; +}; + +class WebConferenceFactory + : public AmSessionFactory, + public AmDynInvokeFactory, + public AmDynInvoke +{ + AmPromptCollection prompts; + + map rooms; + AmMutex rooms_mut; + + // for DI + static WebConferenceFactory* _instance; + bool configured; + + string getRandomPin(); + /** returns NULL if adminpin wrong */ + ConferenceRoom* getRoom(const string& room, + const string& adminpin); + void postConfEvent(const AmArgArray& args, AmArgArray& ret, + int id, int mute); +public: + static string DigitsDir; + static PlayoutType m_PlayoutType; + + WebConferenceFactory(const string& _app_name); + AmSession* onInvite(const AmSipRequest&); + AmSession* onInvite(const AmSipRequest& req, + AmArg& session_params); + int onLoad(); + + inline void newParticipant(const string& conf_id, + const string& localtag, + const string& number); + inline void updateStatus(const string& conf_id, + const string& localtag, + ConferenceRoomParticipant::ParticipantStatus status, + const string& reason); + + // DI API + WebConferenceFactory* getInstance(){ + return _instance; + } + void invoke(const string& method, const AmArgArray& args, AmArgArray& ret); + + // DI functions + void roomCreate(const AmArgArray& args, AmArgArray& ret); + void roomInfo(const AmArgArray& args, AmArgArray& ret); + void dialout(const AmArgArray& args, AmArgArray& ret); + void kickout(const AmArgArray& args, AmArgArray& ret); + void mute(const AmArgArray& args, AmArgArray& ret); + void unmute(const AmArgArray& args, AmArgArray& ret); + void serverInfo(const AmArgArray& args, AmArgArray& ret); +}; + +class WebConferenceDialog + : public AmSession, + public CredentialHolder +{ +public: + enum WebConferenceState { + None, + EnteringPin, + EnteringConference, + InConference + }; + +private: + AmPlaylist play_list; + AmPlaylistSeparator separator; + + AmPromptCollection& prompts; + + // our connection to the conference + auto_ptr channel; + string conf_id; + string pin_str; + + void connectConference(const string& room); + void disconnectConference(); + + WebConferenceState state; + + WebConferenceFactory* factory; + bool is_dialout; + UACAuthCred* cred; + +public: + WebConferenceDialog(AmPromptCollection& prompts, + WebConferenceFactory* my_f, + UACAuthCred* cred); + ~WebConferenceDialog(); + + void process(AmEvent* ev); + void onSipReply(const AmSipReply& reply); + void onSessionStart(const AmSipRequest& req); + void onSessionStart(const AmSipReply& rep); + void onDtmf(int event, int duration); + void onBye(const AmSipRequest& req); + + UACAuthCred* getCredentials() { return cred; } + +}; + +#endif +// Local Variables: +// mode:C++ +// End: + diff --git a/apps/webconference/etc/webconference.conf b/apps/webconference/etc/webconference.conf new file mode 100644 index 00000000..95e3ede3 --- /dev/null +++ b/apps/webconference/etc/webconference.conf @@ -0,0 +1,12 @@ + +# configure the various announcements here +first_participant=/usr/local/lib/sems/audio/webconference/first_participant.wav +join_sound=/usr/local/lib/sems/audio/webconference/beep.wav +drop_sound=/usr/local/lib/sems/audio/webconference/beep.wav +enter_pin=/usr/local/lib/sems/audio/webconference/pin_prompt.wav +wrong_pin=/usr/local/lib/sems/audio/webconference/wrong_pin.wav +entering_conference=/usr/local/lib/sems/audio/webconference/entering_conference.wav + +# digits to play the room number +# must contain the files 0.wav, 1.wav, ..., 9.wav +digits_dir=/usr/local/lib/sems/audio/webconference/ \ No newline at end of file diff --git a/apps/webconference/wav/beep.wav b/apps/webconference/wav/beep.wav new file mode 100644 index 00000000..1151f909 Binary files /dev/null and b/apps/webconference/wav/beep.wav differ