| /* |
| * Jingle call example |
| * Copyright 2004--2005, Google Inc. |
| * |
| * 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 <string> |
| #include <vector> |
| |
| #include "talk/xmpp/constants.h" |
| #include "talk/base/helpers.h" |
| #include "talk/base/thread.h" |
| #include "talk/base/network.h" |
| #include "talk/base/socketaddress.h" |
| #include "talk/p2p/base/sessionmanager.h" |
| #include "talk/p2p/client/httpportallocator.h" |
| #include "talk/p2p/client/sessionmanagertask.h" |
| #include "talk/session/phone/phonesessionclient.h" |
| #include "talk/examples/call/callclient.h" |
| #include "talk/examples/call/console.h" |
| #include "talk/examples/login/presencepushtask.h" |
| #include "talk/examples/login/presenceouttask.h" |
| #include "talk/examples/login/jingleinfotask.h" |
| |
| namespace { |
| |
| const char* DescribeStatus(buzz::Status::Show show, const std::string& desc) { |
| switch (show) { |
| case buzz::Status::SHOW_XA: return desc.c_str(); |
| case buzz::Status::SHOW_ONLINE: return "online"; |
| case buzz::Status::SHOW_AWAY: return "away"; |
| case buzz::Status::SHOW_DND: return "do not disturb"; |
| case buzz::Status::SHOW_CHAT: return "ready to chat"; |
| default: return "offline"; |
| } |
| } |
| |
| } // namespace |
| |
| const char* CALL_COMMANDS = |
| "Available commands:\n" |
| "\n" |
| " hangup Ends the call.\n" |
| " mute Stops sending voice.\n" |
| " unmute Re-starts sending voice.\n" |
| " quit Quits the application.\n" |
| ""; |
| |
| const char* RECEIVE_COMMANDS = |
| "Available commands:\n" |
| "\n" |
| " accept Accepts the incoming call and switches to it.\n" |
| " reject Rejects the incoming call and stays with the current call.\n" |
| " quit Quits the application.\n" |
| ""; |
| |
| const char* CONSOLE_COMMANDS = |
| "Available commands:\n" |
| "\n" |
| " roster Prints the online friends from your roster.\n" |
| " call <name> Initiates a call to the friend with the given name.\n" |
| " quit Quits the application.\n" |
| ""; |
| |
| void CallClient::ParseLine(const std::string& line) { |
| std::vector<std::string> words; |
| int start = -1; |
| int state = 0; |
| for (int index = 0; index <= static_cast<int>(line.size()); ++index) { |
| if (state == 0) { |
| if (!isspace(line[index])) { |
| start = index; |
| state = 1; |
| } |
| } else { |
| assert(state == 1); |
| assert(start >= 0); |
| if (isspace(line[index])) { |
| std::string word(line, start, index - start); |
| words.push_back(word); |
| start = -1; |
| state = 0; |
| } |
| } |
| } |
| |
| // Global commands |
| if ((words.size() == 1) && (words[0] == "quit")) { |
| exit(0); |
| } |
| |
| if (call_ && incoming_call_) { |
| if ((words.size() == 1) && (words[0] == "accept")) { |
| assert(call_->sessions().size() == 1); |
| call_->AcceptSession(call_->sessions()[0]); |
| phone_client()->SetFocus(call_); |
| incoming_call_ = false; |
| } else if ((words.size() == 1) && (words[0] == "reject")) { |
| call_->RejectSession(call_->sessions()[0]); |
| incoming_call_ = false; |
| } else { |
| console_->Print(RECEIVE_COMMANDS); |
| } |
| } else if (call_) { |
| if ((words.size() == 1) && (words[0] == "hangup")) { |
| call_->Terminate(); |
| call_ = NULL; |
| session_ = NULL; |
| console_->SetPrompt(NULL); |
| } else if ((words.size() == 1) && (words[0] == "mute")) { |
| call_->Mute(true); |
| } else if ((words.size() == 1) && (words[0] == "unmute")) { |
| call_->Mute(false); |
| } else { |
| console_->Print(CALL_COMMANDS); |
| } |
| } else { |
| if ((words.size() == 1) && (words[0] == "roster")) { |
| PrintRoster(); |
| } else if ((words.size() == 2) && (words[0] == "call")) { |
| MakeCallTo(words[1]); |
| } else { |
| console_->Print(CONSOLE_COMMANDS); |
| } |
| } |
| } |
| |
| CallClient::CallClient(buzz::XmppClient* xmpp_client) |
| : xmpp_client_(xmpp_client), roster_(new RosterMap), call_(NULL), |
| incoming_call_(false) { |
| xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange); |
| } |
| |
| CallClient::~CallClient() { |
| delete roster_; |
| } |
| |
| const std::string CallClient::strerror(buzz::XmppEngine::Error err) { |
| switch (err) { |
| case buzz::XmppEngine::ERROR_NONE: |
| return ""; |
| case buzz::XmppEngine::ERROR_XML: |
| return "Malformed XML or encoding error"; |
| case buzz::XmppEngine::ERROR_STREAM: |
| return "XMPP stream error"; |
| case buzz::XmppEngine::ERROR_VERSION: |
| return "XMPP version error"; |
| case buzz::XmppEngine::ERROR_UNAUTHORIZED: |
| return "User is not authorized (Check your username and password)"; |
| case buzz::XmppEngine::ERROR_TLS: |
| return "TLS could not be negotiated"; |
| case buzz::XmppEngine::ERROR_AUTH: |
| return "Authentication could not be negotiated"; |
| case buzz::XmppEngine::ERROR_BIND: |
| return "Resource or session binding could not be negotiated"; |
| case buzz::XmppEngine::ERROR_CONNECTION_CLOSED: |
| return "Connection closed by output handler."; |
| case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED: |
| return "Closed by </stream:stream>"; |
| case buzz::XmppEngine::ERROR_SOCKET: |
| return "Socket error"; |
| default: |
| return "Unknown error"; |
| } |
| } |
| |
| void CallClient::OnCallDestroy(cricket::Call* call) { |
| if (call == call_) { |
| console_->SetPrompt(NULL); |
| console_->Print("call destroyed"); |
| call_ = NULL; |
| session_ = NULL; |
| } |
| } |
| |
| void CallClient::OnJingleInfo(const std::string &relay_token, |
| const std::vector<std::string> &relay_addresses, |
| const std::vector<talk_base::SocketAddress> &stun_addresses) { |
| port_allocator_->SetStunHosts(stun_addresses); |
| port_allocator_->SetRelayHosts(relay_addresses); |
| port_allocator_->SetRelayToken(relay_token); |
| } |
| |
| void CallClient::OnStateChange(buzz::XmppEngine::State state) { |
| switch (state) { |
| case buzz::XmppEngine::STATE_START: |
| console_->Print("connecting..."); |
| break; |
| |
| case buzz::XmppEngine::STATE_OPENING: |
| console_->Print("logging in..."); |
| break; |
| |
| case buzz::XmppEngine::STATE_OPEN: |
| console_->Print("logged in..."); |
| InitPhone(); |
| InitPresence(); |
| break; |
| |
| case buzz::XmppEngine::STATE_CLOSED: |
| buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL); |
| console_->Print("logged out..." + strerror(error)); |
| exit(0); |
| } |
| } |
| |
| void CallClient::InitPhone() { |
| std::string client_unique = xmpp_client_->jid().Str(); |
| cricket::InitRandom(client_unique.c_str(), client_unique.size()); |
| |
| worker_thread_ = new talk_base::Thread(); |
| |
| port_allocator_ = new cricket::HttpPortAllocator(&network_manager_, "call"); |
| |
| session_manager_ = new cricket::SessionManager( |
| port_allocator_, worker_thread_); |
| session_manager_->SignalRequestSignaling.connect( |
| this, &CallClient::OnRequestSignaling); |
| session_manager_->OnSignalingReady(); |
| |
| session_manager_task_ = |
| new cricket::SessionManagerTask(xmpp_client_, session_manager_); |
| session_manager_task_->EnableOutgoingMessages(); |
| session_manager_task_->Start(); |
| |
| buzz::JingleInfoTask *jit = new buzz::JingleInfoTask(xmpp_client_); |
| jit->RefreshJingleInfoNow(); |
| jit->SignalJingleInfo.connect(this, &CallClient::OnJingleInfo); |
| jit->Start(); |
| |
| phone_client_ = new cricket::PhoneSessionClient( |
| xmpp_client_->jid(),session_manager_); |
| phone_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate); |
| |
| worker_thread_->Start(); |
| } |
| |
| void CallClient::OnRequestSignaling() { |
| session_manager_->OnSignalingReady(); |
| } |
| |
| void CallClient::OnCallCreate(cricket::Call* call) { |
| call->SignalSessionState.connect(this, &CallClient::OnSessionState); |
| } |
| |
| void CallClient::OnSessionState(cricket::Call* call, |
| cricket::Session* session, |
| cricket::Session::State state) { |
| if (state == cricket::Session::STATE_RECEIVEDINITIATE) { |
| buzz::Jid jid(session->remote_name()); |
| console_->Printf("Incoming call from '%s'", jid.Str().c_str()); |
| call_ = call; |
| session_ = session; |
| incoming_call_ = true; |
| } else if (state == cricket::Session::STATE_SENTINITIATE) { |
| console_->Print("calling..."); |
| } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) { |
| console_->Print("call answered"); |
| } else if (state == cricket::Session::STATE_RECEIVEDREJECT) { |
| console_->Print("call not answered"); |
| } else if (state == cricket::Session::STATE_INPROGRESS) { |
| console_->Print("call in progress"); |
| } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) { |
| console_->Print("other side hung up"); |
| } |
| } |
| |
| void CallClient::InitPresence() { |
| presence_push_ = new buzz::PresencePushTask(xmpp_client_); |
| presence_push_->SignalStatusUpdate.connect( |
| this, &CallClient::OnStatusUpdate); |
| presence_push_->Start(); |
| |
| buzz::Status my_status; |
| my_status.set_jid(xmpp_client_->jid()); |
| my_status.set_available(true); |
| my_status.set_show(buzz::Status::SHOW_ONLINE); |
| my_status.set_priority(0); |
| my_status.set_know_capabilities(true); |
| my_status.set_phone_capability(true); |
| my_status.set_is_google_client(true); |
| my_status.set_version("1.0.0.66"); |
| |
| buzz::PresenceOutTask* presence_out_ = |
| new buzz::PresenceOutTask(xmpp_client_); |
| presence_out_->Send(my_status); |
| presence_out_->Start(); |
| } |
| |
| void CallClient::OnStatusUpdate(const buzz::Status& status) { |
| RosterItem item; |
| item.jid = status.jid(); |
| item.show = status.show(); |
| item.status = status.status(); |
| |
| std::string key = item.jid.Str(); |
| |
| if (status.available() && status.phone_capability()) { |
| console_->Printf("Adding to roster: %s", key.c_str()); |
| (*roster_)[key] = item; |
| } else { |
| console_->Printf("Removing from roster: %s", key.c_str()); |
| RosterMap::iterator iter = roster_->find(key); |
| if (iter != roster_->end()) |
| roster_->erase(iter); |
| } |
| } |
| |
| void CallClient::PrintRoster() { |
| console_->SetPrompting(false); |
| console_->Printf("Roster contains %d callable", roster_->size()); |
| RosterMap::iterator iter = roster_->begin(); |
| while (iter != roster_->end()) { |
| console_->Printf("%s - %s", |
| iter->second.jid.BareJid().Str().c_str(), |
| DescribeStatus(iter->second.show, iter->second.status)); |
| iter++; |
| } |
| console_->SetPrompting(true); |
| } |
| |
| void CallClient::MakeCallTo(const std::string& name) { |
| bool found = false; |
| buzz::Jid found_jid; |
| buzz::Jid callto_jid = buzz::Jid(name); |
| RosterMap::iterator iter = roster_->begin(); |
| while (iter != roster_->end()) { |
| if (iter->second.jid.BareEquals(callto_jid)) { |
| found = true; |
| found_jid = iter->second.jid; |
| break; |
| } |
| ++iter; |
| } |
| |
| |
| if (found) { |
| console_->Printf("Found online friend '%s'", found_jid.Str().c_str()); |
| phone_client()->SignalCallDestroy.connect( |
| this, &CallClient::OnCallDestroy); |
| if (!call_) { |
| call_ = phone_client()->CreateCall(); |
| console_->SetPrompt(found_jid.Str().c_str()); |
| call_->SignalSessionState.connect(this, &CallClient::OnSessionState); |
| session_ = call_->InitiateSession(found_jid, NULL); |
| } |
| phone_client()->SetFocus(call_); |
| } else { |
| console_->Printf("Could not find online friend '%s'", name.c_str()); |
| } |
| } |