blob: 3657420e535905864daf1028312c3bbe2dd25536 [file] [log] [blame]
/*
* libjingle
* Copyright 2004--2005, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "talk/examples/call/callclient.h"
#include <string>
#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/base/stringutils.h"
#include "talk/p2p/base/sessionmanager.h"
#include "talk/p2p/client/basicportallocator.h"
#include "talk/p2p/client/sessionmanagertask.h"
#include "talk/session/phone/devicemanager.h"
#include "talk/session/phone/mediaengine.h"
#include "talk/session/phone/mediasessionclient.h"
#include "talk/examples/call/console.h"
#include "talk/examples/call/presencepushtask.h"
#include "talk/examples/call/presenceouttask.h"
#include "talk/examples/call/mucinviterecvtask.h"
#include "talk/examples/call/mucinvitesendtask.h"
#include "talk/examples/call/friendinvitesendtask.h"
#include "talk/examples/call/muc.h"
#include "talk/examples/call/voicemailjidrequester.h"
#ifdef USE_TALK_SOUND
#include "talk/sound/platformsoundsystemfactory.h"
#endif
#include "talk/base/logging.h"
class NullRenderer : public cricket::VideoRenderer {
public:
explicit NullRenderer(const char* s) : s_(s) {}
private:
bool SetSize(int width, int height, int reserved) {
LOG(LS_INFO) << "Video size for " << s_ << ": " << width << "x" << height;
return true;
}
bool RenderFrame(const cricket::VideoFrame *frame) {
return true;
}
const char* s_;
};
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"
" dtmf Sends a DTMF tone.\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"
" friend user Request to add a user to your roster.\n"
" call [jid] Initiates a call to the user[/room] with the\n"
" given JID.\n"
" vcall [jid] Initiates a video call to the user[/room] with\n"
" the given JID.\n"
" voicemail [jid] Leave a voicemail for the user with the given JID.\n"
" join [room] Joins a multi-user-chat.\n"
" invite user [room] Invites a friend to a multi-user-chat.\n"
" leave [room] Leaves a multi-user-chat.\n"
" getdevs Prints the available media devices.\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")) {
Quit();
} else if (call_ && incoming_call_) {
if ((words.size() == 1) && (words[0] == "accept")) {
Accept();
} else if ((words.size() == 1) && (words[0] == "reject")) {
Reject();
} else {
console_->Print(RECEIVE_COMMANDS);
}
} else if (call_) {
if ((words.size() == 1) && (words[0] == "hangup")) {
// TODO: do more shutdown here, move to Terminate()
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 if ((words.size() == 2) && (words[0] == "dtmf")) {
int ev = std::string("0123456789*#").find(words[1][0]);
call_->PressDTMF(ev);
} else {
console_->Print(CALL_COMMANDS);
}
} else {
if ((words.size() == 1) && (words[0] == "roster")) {
PrintRoster();
} else if ((words.size() >= 2) && (words[0] == "send")) {
buzz::Jid jid(words[1]);
if (jid.IsValid()) {
last_sent_to_ = words[1];
SendChat(words[1], words[2]);
} else if (!last_sent_to_.empty()) {
SendChat(last_sent_to_, words[1]);
} else {
console_->Printf(
"Invalid JID. JIDs should be in the form user@domain\n");
}
} else if ((words.size() == 2) && (words[0] == "friend")) {
InviteFriend(words[1]);
} else if ((words.size() >= 1) && (words[0] == "call")) {
MakeCallTo((words.size() >= 2) ? words[1] : "", false);
} else if ((words.size() >= 1) && (words[0] == "vcall")) {
MakeCallTo((words.size() >= 2) ? words[1] : "", true);
} else if ((words.size() >= 1) && (words[0] == "join")) {
JoinMuc((words.size() >= 2) ? words[1] : "");
} else if ((words.size() >= 2) && (words[0] == "invite")) {
InviteToMuc(words[1], (words.size() >= 3) ? words[2] : "");
} else if ((words.size() >= 1) && (words[0] == "leave")) {
LeaveMuc((words.size() >= 2) ? words[1] : "");
} else if ((words.size() == 1) && (words[0] == "getdevs")) {
GetDevices();
} else if ((words.size() == 2) && (words[0] == "setvol")) {
SetVolume(words[1]);
} else if ((words.size() >= 1) && (words[0] == "voicemail")) {
CallVoicemail((words.size() >= 2) ? words[1] : "");
} else {
console_->Print(CONSOLE_COMMANDS);
}
}
}
CallClient::CallClient(buzz::XmppClient* xmpp_client)
: xmpp_client_(xmpp_client), media_engine_(NULL), media_client_(NULL),
call_(NULL), incoming_call_(false),
auto_accept_(false), pmuc_domain_("groupchat.google.com"),
local_renderer_(NULL), remote_renderer_(NULL),
roster_(new RosterMap), portallocator_flags_(0),
allow_local_ips_(false), initial_protocol_(cricket::PROTOCOL_HYBRID)
#ifdef USE_TALK_SOUND
, sound_system_factory_(NULL)
#endif
{
xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
}
CallClient::~CallClient() {
delete media_client_;
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_) {
if (remote_renderer_) {
delete remote_renderer_;
remote_renderer_ = NULL;
}
if (local_renderer_) {
delete local_renderer_;
local_renderer_ = NULL;
}
console_->SetPrompt(NULL);
console_->Print("call destroyed");
call_ = NULL;
session_ = NULL;
}
}
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));
Quit();
}
}
void CallClient::InitPhone() {
std::string client_unique = xmpp_client_->jid().Str();
talk_base::InitRandom(client_unique.c_str(), client_unique.size());
worker_thread_ = new talk_base::Thread();
// The worker thread must be started here since initialization of
// the ChannelManager will generate messages that need to be
// dispatched by it.
worker_thread_->Start();
network_manager_ = new talk_base::NetworkManager();
// TODO: Decide if the relay address should be specified here.
talk_base::SocketAddress stun_addr("stun.l.google.com", 19302);
port_allocator_ =
new cricket::BasicPortAllocator(network_manager_, stun_addr,
talk_base::SocketAddress(), talk_base::SocketAddress(),
talk_base::SocketAddress());
if (portallocator_flags_ != 0) {
port_allocator_->set_flags(portallocator_flags_);
}
session_manager_ = new cricket::SessionManager(
port_allocator_, worker_thread_);
session_manager_->SignalRequestSignaling.connect(
this, &CallClient::OnRequestSignaling);
session_manager_->SignalSessionCreate.connect(
this, &CallClient::OnSessionCreate);
session_manager_->OnSignalingReady();
session_manager_task_ =
new cricket::SessionManagerTask(xmpp_client_, session_manager_);
session_manager_task_->EnableOutgoingMessages();
session_manager_task_->Start();
#ifdef USE_TALK_SOUND
if (!sound_system_factory_) {
sound_system_factory_ = new cricket::PlatformSoundSystemFactory();
}
#endif
if (!media_engine_) {
media_engine_ = cricket::MediaEngine::Create(
#ifdef USE_TALK_SOUND
sound_system_factory_
#endif
);
}
media_client_ = new cricket::MediaSessionClient(
xmpp_client_->jid(),
session_manager_,
media_engine_,
new cricket::DeviceManager(
#ifdef USE_TALK_SOUND
sound_system_factory_
#endif
));
media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
media_client_->SignalDevicesChange.connect(this,
&CallClient::OnDevicesChange);
}
void CallClient::OnRequestSignaling() {
session_manager_->OnSignalingReady();
}
void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
session->set_allow_local_ips(allow_local_ips_);
session->set_current_protocol(initial_protocol_);
}
void CallClient::OnCallCreate(cricket::Call* call) {
call->SignalSessionState.connect(this, &CallClient::OnSessionState);
if (call->video()) {
local_renderer_ = new NullRenderer("local");
remote_renderer_ = new NullRenderer("remote");
}
}
void CallClient::OnSessionState(cricket::Call* call,
cricket::BaseSession* session,
cricket::BaseSession::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;
if (auto_accept_) {
Accept();
}
} 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_, this);
presence_push_->SignalStatusUpdate.connect(
this, &CallClient::OnStatusUpdate);
presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined);
presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft);
presence_push_->SignalMucStatusUpdate.connect(
this, &CallClient::OnMucStatusUpdate);
presence_push_->Start();
presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
RefreshStatus();
presence_out_->Start();
muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
muc_invite_recv_->SignalInviteReceived.connect(this,
&CallClient::OnMucInviteReceived);
muc_invite_recv_->Start();
muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_);
muc_invite_send_->Start();
friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_);
friend_invite_send_->Start();
}
void CallClient::RefreshStatus() {
int media_caps = media_client_->GetCapabilities();
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_pmuc_capability(true);
my_status_.set_phone_capability(
(media_caps & cricket::MediaEngine::AUDIO_RECV) != 0);
my_status_.set_video_capability(
(media_caps & cricket::MediaEngine::VIDEO_RECV) != 0);
my_status_.set_camera_capability(
(media_caps & cricket::MediaEngine::VIDEO_SEND) != 0);
my_status_.set_is_google_client(true);
my_status_.set_version("1.0.0.67");
presence_out_->Send(my_status_);
}
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::SendChat(const std::string& to, const std::string msg) {
buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE);
stanza->AddAttr(buzz::QN_TO, to);
stanza->AddAttr(buzz::QN_ID, talk_base::CreateRandomString(16));
stanza->AddAttr(buzz::QN_TYPE, "chat");
buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY);
body->SetBodyText(msg);
stanza->AddElement(body);
xmpp_client_->SendStanza(stanza);
delete stanza;
}
void CallClient::InviteFriend(const std::string& name) {
buzz::Jid jid(name);
if (!jid.IsValid() || jid.node() == "") {
console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
return;
}
// Note: for some reason the Buzz backend does not forward our presence
// subscription requests to the end user when that user is another call
// client as opposed to a Smurf user. Thus, in that scenario, you must
// run the friend command as the other user too to create the linkage
// (and you won't be notified to do so).
friend_invite_send_->Send(jid);
console_->Printf("Requesting to befriend %s.\n", name.c_str());
}
void CallClient::MakeCallTo(const std::string& name, bool video) {
bool found = false;
bool is_muc = false;
buzz::Jid callto_jid(name);
buzz::Jid found_jid;
if (name.length() == 0 && mucs_.size() > 0) {
// if no name, and in a MUC, establish audio with the MUC
found_jid = mucs_.begin()->first;
found = true;
is_muc = true;
} else if (name[0] == '+') {
// if the first character is a +, assume it's a phone number
found_jid = callto_jid;
found = true;
} else if (callto_jid.resource() == "voicemail") {
// if the resource is /voicemail, allow that
found_jid = callto_jid;
found = true;
} else {
// otherwise, it's a friend
for (RosterMap::iterator iter = roster_->begin();
iter != roster_->end(); ++iter) {
if (iter->second.jid.BareEquals(callto_jid)) {
found = true;
found_jid = iter->second.jid;
break;
}
}
if (!found) {
if (mucs_.count(callto_jid) == 1 &&
mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) {
found = true;
found_jid = callto_jid;
is_muc = true;
}
}
}
if (found) {
console_->Printf("Found %s '%s'", is_muc ? "room" : "online friend",
found_jid.Str().c_str());
PlaceCall(found_jid, is_muc, video);
} else {
console_->Printf("Could not find online friend '%s'", name.c_str());
}
}
void CallClient::PlaceCall(const buzz::Jid& jid, bool is_muc, bool video) {
media_client_->SignalCallDestroy.connect(
this, &CallClient::OnCallDestroy);
if (!call_) {
call_ = media_client_->CreateCall(video, is_muc);
console_->SetPrompt(jid.Str().c_str());
session_ = call_->InitiateSession(jid);
if (is_muc) {
// If people in this room are already in a call, must add all their
// streams.
buzz::Muc::MemberMap& members = mucs_[jid]->members();
for (buzz::Muc::MemberMap::iterator elem = members.begin();
elem != members.end();
++elem) {
AddStream(elem->second.audio_src_id(), elem->second.video_src_id());
}
}
}
media_client_->SetFocus(call_);
if (call_->video()) {
call_->SetLocalRenderer(local_renderer_);
// TODO: Call this once for every different remote SSRC
// once we get to testing multiway video.
call_->SetVideoRenderer(session_, 0, remote_renderer_);
}
}
void CallClient::CallVoicemail(const std::string& name) {
buzz::Jid jid(name);
if (!jid.IsValid() || jid.node() == "") {
console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
return;
}
buzz::VoicemailJidRequester *request =
new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
request->SignalGotVoicemailJid.connect(this,
&CallClient::OnFoundVoicemailJid);
request->SignalVoicemailJidError.connect(this,
&CallClient::OnVoicemailJidError);
request->Start();
}
void CallClient::OnFoundVoicemailJid(const buzz::Jid& to,
const buzz::Jid& voicemail) {
console_->Printf("Calling %s's voicemail.\n", to.Str().c_str());
PlaceCall(voicemail, false, false);
}
void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
console_->Printf("Unable to voicemail %s.\n", to.Str().c_str());
}
void CallClient::AddStream(uint32 audio_src_id, uint32 video_src_id) {
if (audio_src_id || video_src_id) {
console_->Printf("Adding stream (%u, %u)\n", audio_src_id, video_src_id);
call_->AddStream(session_, audio_src_id, video_src_id);
}
}
void CallClient::RemoveStream(uint32 audio_src_id, uint32 video_src_id) {
if (audio_src_id || video_src_id) {
console_->Printf("Removing stream (%u, %u)\n", audio_src_id, video_src_id);
call_->RemoveStream(session_, audio_src_id, video_src_id);
}
}
void CallClient::Accept() {
ASSERT(call_ && incoming_call_);
ASSERT(call_->sessions().size() == 1);
call_->AcceptSession(call_->sessions()[0]);
media_client_->SetFocus(call_);
if (call_->video()) {
call_->SetLocalRenderer(local_renderer_);
// The client never does an accept for multiway, so this must be 1:1,
// so there's no SSRC.
call_->SetVideoRenderer(session_, 0, remote_renderer_);
}
incoming_call_ = false;
}
void CallClient::Reject() {
ASSERT(call_ && incoming_call_);
call_->RejectSession(call_->sessions()[0]);
incoming_call_ = false;
}
void CallClient::Quit() {
talk_base::Thread::Current()->Quit();
}
void CallClient::JoinMuc(const std::string& room) {
buzz::Jid room_jid;
if (room.length() > 0) {
room_jid = buzz::Jid(room);
} else {
// generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
// for an eventual JID of private-chat-<GUID>@groupchat.google.com
char guid[37], guid_room[256];
for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) {
if (i == 8 || i == 13 || i == 18 || i == 23) {
guid[i++] = '-';
} else {
sprintf(guid + i, "%04x", rand());
i += 4;
}
}
talk_base::sprintfn(guid_room, ARRAY_SIZE(guid_room),
"private-chat-%s@%s", guid, pmuc_domain_.c_str());
room_jid = buzz::Jid(guid_room);
}
if (!room_jid.IsValid()) {
console_->Printf("Unable to make valid muc endpoint for %s", room.c_str());
return;
}
MucMap::iterator elem = mucs_.find(room_jid);
if (elem != mucs_.end()) {
console_->Printf("This MUC already exists.");
return;
}
buzz::Muc* muc = new buzz::Muc(room_jid, xmpp_client_->jid().node());
mucs_[room_jid] = muc;
presence_out_->SendDirected(muc->local_jid(), my_status_);
}
void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
const buzz::Jid& room,
const std::vector<buzz::AvailableMediaEntry>& avail) {
console_->Printf("Invited to join %s by %s.\n", room.Str().c_str(),
inviter.Str().c_str());
console_->Printf("Available media:\n");
if (avail.size() > 0) {
for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
avail.begin();
i != avail.end();
++i) {
console_->Printf(" %s, %s\n",
buzz::AvailableMediaEntry::TypeAsString(i->type),
buzz::AvailableMediaEntry::StatusAsString(i->status));
}
} else {
console_->Printf(" None\n");
}
// We automatically join the room.
JoinMuc(room.Str());
}
void CallClient::OnMucJoined(const buzz::Jid& endpoint) {
MucMap::iterator elem = mucs_.find(endpoint);
ASSERT(elem != mucs_.end() &&
elem->second->state() == buzz::Muc::MUC_JOINING);
buzz::Muc* muc = elem->second;
muc->set_state(buzz::Muc::MUC_JOINED);
console_->Printf("Joined \"%s\"", muc->jid().Str().c_str());
}
void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
const buzz::MucStatus& status) {
// Look up this muc.
MucMap::iterator elem = mucs_.find(jid);
ASSERT(elem != mucs_.end() &&
elem->second->state() == buzz::Muc::MUC_JOINED);
buzz::Muc* muc = elem->second;
if (status.jid().IsBare() || status.jid() == muc->local_jid()) {
// We are only interested in status about other users.
return;
}
if (!status.available()) {
// User is leaving the room.
buzz::Muc::MemberMap::iterator elem =
muc->members().find(status.jid().resource());
ASSERT(elem != muc->members().end());
// If user had src-ids, they have the left the room without explicitly
// hanging-up; must tear down the stream if in a call to this room.
if (call_ && session_->remote_name() == muc->jid().Str()) {
RemoveStream(elem->second.audio_src_id(), elem->second.video_src_id());
}
// Remove them from the room.
muc->members().erase(elem);
} else {
// Either user has joined or something changed about them.
// Note: The [] operator here will create a new entry if it does not
// exist, which is what we want.
buzz::MucStatus& member_status(
muc->members()[status.jid().resource()]);
if (call_ && session_->remote_name() == muc->jid().Str()) {
// We are in a call to this muc. Must potentially update our streams.
// The following code will correctly update our streams regardless of
// whether the SSRCs have been removed, added, or changed and regardless
// of whether that has been done to both or just one. This relies on the
// fact that AddStream/RemoveStream do nothing for SSRC arguments that are
// zero.
uint32 remove_audio_src_id = 0;
uint32 remove_video_src_id = 0;
uint32 add_audio_src_id = 0;
uint32 add_video_src_id = 0;
if (member_status.audio_src_id() != status.audio_src_id()) {
remove_audio_src_id = member_status.audio_src_id();
add_audio_src_id = status.audio_src_id();
}
if (member_status.video_src_id() != status.video_src_id()) {
remove_video_src_id = member_status.video_src_id();
add_video_src_id = status.video_src_id();
}
// Remove the old SSRCs, if any.
RemoveStream(remove_audio_src_id, remove_video_src_id);
// Add the new SSRCs, if any.
AddStream(add_audio_src_id, add_video_src_id);
}
// Update the status. This will use the compiler-generated copy
// constructor, which is perfectly adequate for this class.
member_status = status;
}
}
void CallClient::LeaveMuc(const std::string& room) {
buzz::Jid room_jid;
if (room.length() > 0) {
room_jid = buzz::Jid(room);
} else if (mucs_.size() > 0) {
// leave the first MUC if no JID specified
room_jid = mucs_.begin()->first;
}
if (!room_jid.IsValid()) {
console_->Printf("Invalid MUC JID.");
return;
}
MucMap::iterator elem = mucs_.find(room_jid);
if (elem == mucs_.end()) {
console_->Printf("No such MUC.");
return;
}
buzz::Muc* muc = elem->second;
muc->set_state(buzz::Muc::MUC_LEAVING);
buzz::Status status;
status.set_jid(my_status_.jid());
status.set_available(false);
status.set_priority(0);
presence_out_->SendDirected(muc->local_jid(), status);
}
void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) {
// We could be kicked from a room from any state. We would hope this
// happens While in the MUC_LEAVING state
MucMap::iterator elem = mucs_.find(endpoint);
if (elem == mucs_.end())
return;
buzz::Muc* muc = elem->second;
if (muc->state() == buzz::Muc::MUC_JOINING) {
console_->Printf("Failed to join \"%s\", code=%d",
muc->jid().Str().c_str(), error);
} else if (muc->state() == buzz::Muc::MUC_JOINED) {
console_->Printf("Kicked from \"%s\"",
muc->jid().Str().c_str());
}
delete muc;
mucs_.erase(elem);
}
void CallClient::InviteToMuc(const std::string& user, const std::string& room) {
// First find the room.
const buzz::Muc* found_muc;
if (room.length() == 0) {
if (mucs_.size() == 0) {
console_->Printf("Not in a room yet; can't invite.\n");
return;
}
// Invite to the first muc
found_muc = mucs_.begin()->second;
} else {
MucMap::iterator elem = mucs_.find(buzz::Jid(room));
if (elem == mucs_.end()) {
console_->Printf("Not in room %s.\n", room.c_str());
return;
}
found_muc = elem->second;
}
// Now find the user. We invite all of their resources.
bool found_user = false;
buzz::Jid user_jid(user);
for (RosterMap::iterator iter = roster_->begin();
iter != roster_->end(); ++iter) {
if (iter->second.jid.BareEquals(user_jid)) {
muc_invite_send_->Send(iter->second.jid, *found_muc);
found_user = true;
}
}
if (!found_user) {
console_->Printf("No such friend as %s.\n", user.c_str());
return;
}
}
void CallClient::GetDevices() {
std::vector<std::string> names;
media_client_->GetAudioInputDevices(&names);
printf("Audio input devices:\n");
PrintDevices(names);
media_client_->GetAudioOutputDevices(&names);
printf("Audio output devices:\n");
PrintDevices(names);
media_client_->GetVideoCaptureDevices(&names);
printf("Video capture devices:\n");
PrintDevices(names);
}
void CallClient::PrintDevices(const std::vector<std::string>& names) {
for (size_t i = 0; i < names.size(); ++i) {
printf("%d: %s\n", static_cast<int>(i), names[i].c_str());
}
}
void CallClient::OnDevicesChange() {
printf("Devices changed.\n");
RefreshStatus();
}
void CallClient::SetVolume(const std::string& level) {
media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
}