blob: 251eafa205e326682c362c5716544d755bc6ec70 [file] [log] [blame]
/*
* libjingle
* Copyright 2004--2007, 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 <string>
#include "talk/base/helpers.h"
#include "talk/base/logging.h"
#include "talk/base/thread.h"
#include "talk/session/phone/call.h"
#include "talk/session/phone/mediasessionclient.h"
namespace cricket {
const uint32 MSG_CHECKAUTODESTROY = 1;
const uint32 MSG_TERMINATECALL = 2;
const uint32 MSG_PLAYDTMF = 3;
namespace {
const int kDTMFDelay = 300; // msec
const size_t kMaxDTMFDigits = 30;
const int kSendToVoicemailTimeout = 1000*20;
const int kNoVoicemailTimeout = 1000*180;
const int kMediaMonitorInterval = 1000*15;
}
Call::Call(MediaSessionClient* session_client)
: id_(talk_base::CreateRandomId()),
session_client_(session_client),
local_renderer_(NULL),
video_(false),
muted_(false),
send_to_voicemail_(true),
playing_dtmf_(false) {
}
Call::~Call() {
while (sessions_.begin() != sessions_.end()) {
Session *session = sessions_[0];
RemoveSession(session);
session_client_->session_manager()->DestroySession(session);
}
talk_base::Thread::Current()->Clear(this);
}
Session *Call::InitiateSession(const buzz::Jid &jid,
const CallOptions& options) {
const SessionDescription* offer = session_client_->CreateOffer(options);
Session *session = session_client_->CreateSession(this);
AddSession(session, offer);
session->Initiate(jid.Str(), offer);
// After this timeout, terminate the call because the callee isn't
// answering
session_client_->session_manager()->signaling_thread()->Clear(this,
MSG_TERMINATECALL);
session_client_->session_manager()->signaling_thread()->PostDelayed(
send_to_voicemail_ ? kSendToVoicemailTimeout : kNoVoicemailTimeout,
this, MSG_TERMINATECALL);
return session;
}
void Call::IncomingSession(
Session* session, const SessionDescription* offer) {
AddSession(session, offer);
// Missed the first state, the initiate, which is needed by
// call_client.
SignalSessionState(this, session, Session::STATE_RECEIVEDINITIATE);
}
void Call::AcceptSession(BaseSession* session,
const cricket::CallOptions& options) {
std::vector<Session *>::iterator it;
it = std::find(sessions_.begin(), sessions_.end(), session);
ASSERT(it != sessions_.end());
if (it != sessions_.end()) {
session->Accept(
session_client_->CreateAnswer(session->remote_description(), options));
}
}
void Call::RejectSession(BaseSession *session) {
std::vector<Session *>::iterator it;
it = std::find(sessions_.begin(), sessions_.end(), session);
ASSERT(it != sessions_.end());
// Assume polite decline.
if (it != sessions_.end())
session->Reject(STR_TERMINATE_DECLINE);
}
void Call::TerminateSession(BaseSession *session) {
ASSERT(std::find(sessions_.begin(), sessions_.end(), session)
!= sessions_.end());
std::vector<Session *>::iterator it;
it = std::find(sessions_.begin(), sessions_.end(), session);
// Assume polite terminations.
if (it != sessions_.end())
(*it)->Terminate();
}
void Call::Terminate() {
// Copy the list so that we can iterate over it in a stable way
std::vector<Session *> sessions = sessions_;
// There may be more than one session to terminate
std::vector<Session *>::iterator it;
for (it = sessions.begin(); it != sessions.end(); it++)
TerminateSession(*it);
}
bool Call::SendViewRequest(Session* session,
const ViewRequest& view_request) {
StaticVideoViews::const_iterator it;
for (it = view_request.static_video_views.begin();
it != view_request.static_video_views.end(); ++it) {
const NamedSource* found_source =
media_sources.GetVideoSourceBySsrc(it->ssrc);
if (!found_source) {
LOG(LS_WARNING) <<
"Tried sending view request for bad ssrc: " << it->ssrc;
return false;
}
}
XmlElements elems;
WriteError error;
if (!WriteViewRequest(CN_VIDEO, view_request, &elems, &error)) {
LOG(LS_ERROR) << "Couldn't write out view request: " << error.text;
return false;
}
return session->SendInfoMessage(elems);
}
void Call::SetLocalRenderer(VideoRenderer* renderer) {
local_renderer_ = renderer;
if (session_client_->GetFocus() == this) {
session_client_->channel_manager()->SetLocalRenderer(renderer);
}
}
void Call::SetVideoRenderer(BaseSession *session, uint32 ssrc,
VideoRenderer* renderer) {
VideoChannel *video_channel = GetVideoChannel(session);
if (video_channel) {
video_channel->SetRenderer(ssrc, renderer);
LOG(LS_INFO) << "Set renderer of ssrc " << ssrc
<< " to " << renderer << ".";
} else {
LOG(LS_INFO) << "Failed to set renderer of ssrc " << ssrc << ".";
}
}
void Call::AddVoiceStream(BaseSession *session, uint32 voice_ssrc) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
if (voice_channel && voice_ssrc) {
voice_channel->AddStream(voice_ssrc);
}
}
void Call::AddVideoStream(BaseSession *session, uint32 video_ssrc) {
VideoChannel *video_channel = GetVideoChannel(session);
if (video_channel && video_ssrc) {
// TODO: Do we need the audio_ssrc here?
// It doesn't seem to be used.
video_channel->AddStream(video_ssrc, 0U);
}
}
void Call::RemoveVoiceStream(BaseSession *session, uint32 voice_ssrc) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
if (voice_channel && voice_ssrc) {
voice_channel->RemoveStream(voice_ssrc);
}
}
void Call::RemoveVideoStream(BaseSession *session, uint32 video_ssrc) {
VideoChannel *video_channel = GetVideoChannel(session);
if (video_channel && video_ssrc) {
video_channel->RemoveStream(video_ssrc);
}
}
void Call::OnMessage(talk_base::Message *message) {
switch (message->message_id) {
case MSG_CHECKAUTODESTROY:
// If no more sessions for this call, delete it
if (sessions_.size() == 0)
session_client_->DestroyCall(this);
break;
case MSG_TERMINATECALL:
// Signal to the user that a timeout has happened and the call should
// be sent to voicemail.
if (send_to_voicemail_) {
SignalSetupToCallVoicemail();
}
// Callee didn't answer - terminate call
Terminate();
break;
case MSG_PLAYDTMF:
ContinuePlayDTMF();
}
}
const std::vector<Session *> &Call::sessions() {
return sessions_;
}
bool Call::AddSession(Session *session, const SessionDescription* offer) {
bool succeeded = true;
VoiceChannel *voice_channel = NULL;
VideoChannel *video_channel = NULL;
const ContentInfo* audio_offer = GetFirstAudioContent(offer);
const ContentInfo* video_offer = GetFirstVideoContent(offer);
video_ = (video_offer != NULL);
ASSERT(audio_offer != NULL);
// Create voice channel and start a media monitor
voice_channel = session_client_->channel_manager()->CreateVoiceChannel(
session, audio_offer->name, video_);
// voice_channel can be NULL in case of NullVoiceEngine.
if (voice_channel) {
voice_channel_map_[session->id()] = voice_channel;
voice_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
voice_channel->StartMediaMonitor(kMediaMonitorInterval);
} else {
succeeded = false;
}
// If desired, create video channel and start a media monitor
if (video_ && succeeded) {
video_channel = session_client_->channel_manager()->CreateVideoChannel(
session, video_offer->name, true, voice_channel);
// video_channel can be NULL in case of NullVideoEngine.
if (video_channel) {
video_channel_map_[session->id()] = video_channel;
video_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
video_channel->StartMediaMonitor(kMediaMonitorInterval);
} else {
succeeded = false;
}
}
if (succeeded) {
// Add session to list, create channels for this session
sessions_.push_back(session);
session->SignalState.connect(this, &Call::OnSessionState);
session->SignalError.connect(this, &Call::OnSessionError);
session->SignalInfoMessage.connect(this, &Call::OnSessionInfo);
session->SignalReceivedTerminateReason
.connect(this, &Call::OnReceivedTerminateReason);
// If this call has the focus, enable this channel
if (session_client_->GetFocus() == this) {
voice_channel->Enable(true);
if (video_channel) {
video_channel->Enable(true);
}
}
// Signal client
SignalAddSession(this, session);
}
return succeeded;
}
void Call::RemoveSession(Session *session) {
// Remove session from list
std::vector<Session *>::iterator it_session;
it_session = std::find(sessions_.begin(), sessions_.end(), session);
if (it_session == sessions_.end())
return;
sessions_.erase(it_session);
// Destroy video channel
std::map<std::string, VideoChannel *>::iterator it_vchannel;
it_vchannel = video_channel_map_.find(session->id());
if (it_vchannel != video_channel_map_.end()) {
VideoChannel *video_channel = it_vchannel->second;
video_channel_map_.erase(it_vchannel);
session_client_->channel_manager()->DestroyVideoChannel(video_channel);
}
// Destroy voice channel
std::map<std::string, VoiceChannel *>::iterator it_channel;
it_channel = voice_channel_map_.find(session->id());
if (it_channel != voice_channel_map_.end()) {
VoiceChannel *voice_channel = it_channel->second;
voice_channel_map_.erase(it_channel);
session_client_->channel_manager()->DestroyVoiceChannel(voice_channel);
}
// Signal client
SignalRemoveSession(this, session);
// The call auto destroys when the last session is removed
talk_base::Thread::Current()->Post(this, MSG_CHECKAUTODESTROY);
}
VoiceChannel* Call::GetVoiceChannel(BaseSession* session) {
std::map<std::string, VoiceChannel *>::iterator it
= voice_channel_map_.find(session->id());
return (it != voice_channel_map_.end()) ? it->second : NULL;
}
VideoChannel* Call::GetVideoChannel(BaseSession* session) {
std::map<std::string, VideoChannel *>::iterator it
= video_channel_map_.find(session->id());
return (it != video_channel_map_.end()) ? it->second : NULL;
}
void Call::EnableChannels(bool enable) {
std::vector<Session *>::iterator it;
for (it = sessions_.begin(); it != sessions_.end(); it++) {
VoiceChannel *voice_channel = GetVoiceChannel(*it);
VideoChannel *video_channel = GetVideoChannel(*it);
if (voice_channel != NULL)
voice_channel->Enable(enable);
if (video_channel != NULL)
video_channel->Enable(enable);
}
session_client_->channel_manager()->SetLocalRenderer(
(enable) ? local_renderer_ : NULL);
}
void Call::Mute(bool mute) {
muted_ = mute;
std::vector<Session *>::iterator it;
for (it = sessions_.begin(); it != sessions_.end(); it++) {
VoiceChannel *voice_channel = voice_channel_map_[(*it)->id()];
if (voice_channel != NULL)
voice_channel->Mute(mute);
}
}
void Call::PressDTMF(int event) {
// Queue up this digit
if (queued_dtmf_.size() < kMaxDTMFDigits) {
LOG(LS_INFO) << "Call::PressDTMF(" << event << ")";
queued_dtmf_.push_back(event);
if (!playing_dtmf_) {
ContinuePlayDTMF();
}
}
}
void Call::ContinuePlayDTMF() {
playing_dtmf_ = false;
// Check to see if we have a queued tone
if (queued_dtmf_.size() > 0) {
playing_dtmf_ = true;
int tone = queued_dtmf_.front();
queued_dtmf_.pop_front();
LOG(LS_INFO) << "Call::ContinuePlayDTMF(" << tone << ")";
std::vector<Session *>::iterator it;
for (it = sessions_.begin(); it != sessions_.end(); it++) {
VoiceChannel *voice_channel = voice_channel_map_[(*it)->id()];
if (voice_channel != NULL) {
voice_channel->PressDTMF(tone, true);
}
}
// Post a message to play the next tone or at least clear the playing_dtmf_
// bit.
talk_base::Thread::Current()->PostDelayed(kDTMFDelay, this, MSG_PLAYDTMF);
}
}
void Call::Join(Call *call, bool enable) {
while (call->sessions_.size() != 0) {
// Move session
Session *session = call->sessions_[0];
call->sessions_.erase(call->sessions_.begin());
sessions_.push_back(session);
session->SignalState.connect(this, &Call::OnSessionState);
session->SignalError.connect(this, &Call::OnSessionError);
session->SignalReceivedTerminateReason
.connect(this, &Call::OnReceivedTerminateReason);
// Move voice channel
std::map<std::string, VoiceChannel *>::iterator it_channel;
it_channel = call->voice_channel_map_.find(session->id());
if (it_channel != call->voice_channel_map_.end()) {
VoiceChannel *voice_channel = (*it_channel).second;
call->voice_channel_map_.erase(it_channel);
voice_channel_map_[session->id()] = voice_channel;
voice_channel->Enable(enable);
}
// Move video channel
std::map<std::string, VideoChannel *>::iterator it_vchannel;
it_vchannel = call->video_channel_map_.find(session->id());
if (it_vchannel != call->video_channel_map_.end()) {
VideoChannel *video_channel = (*it_vchannel).second;
call->video_channel_map_.erase(it_vchannel);
video_channel_map_[session->id()] = video_channel;
video_channel->Enable(enable);
}
}
}
void Call::StartConnectionMonitor(BaseSession *session, int cms) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
if (voice_channel) {
voice_channel->SignalConnectionMonitor.connect(this,
&Call::OnConnectionMonitor);
voice_channel->StartConnectionMonitor(cms);
}
VideoChannel *video_channel = GetVideoChannel(session);
if (video_channel) {
video_channel->SignalConnectionMonitor.connect(this,
&Call::OnConnectionMonitor);
video_channel->StartConnectionMonitor(cms);
}
}
void Call::StopConnectionMonitor(BaseSession *session) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
if (voice_channel) {
voice_channel->StopConnectionMonitor();
voice_channel->SignalConnectionMonitor.disconnect(this);
}
VideoChannel *video_channel = GetVideoChannel(session);
if (video_channel) {
video_channel->StopConnectionMonitor();
video_channel->SignalConnectionMonitor.disconnect(this);
}
}
void Call::StartAudioMonitor(BaseSession *session, int cms) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
if (voice_channel) {
voice_channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor);
voice_channel->StartAudioMonitor(cms);
}
}
void Call::StopAudioMonitor(BaseSession *session) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
if (voice_channel) {
voice_channel->StopAudioMonitor();
voice_channel->SignalAudioMonitor.disconnect(this);
}
}
void Call::OnConnectionMonitor(VoiceChannel *channel,
const std::vector<ConnectionInfo> &infos) {
SignalConnectionMonitor(this, infos);
}
void Call::OnMediaMonitor(VoiceChannel *channel, const VoiceMediaInfo& info) {
SignalMediaMonitor(this, info);
}
void Call::OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info) {
SignalAudioMonitor(this, info);
}
void Call::OnConnectionMonitor(VideoChannel *channel,
const std::vector<ConnectionInfo> &infos) {
SignalVideoConnectionMonitor(this, infos);
}
void Call::OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info) {
SignalVideoMediaMonitor(this, info);
}
uint32 Call::id() {
return id_;
}
void Call::OnSessionState(BaseSession *session, BaseSession::State state) {
switch (state) {
case Session::STATE_RECEIVEDACCEPT:
case Session::STATE_RECEIVEDREJECT:
case Session::STATE_RECEIVEDTERMINATE:
session_client_->session_manager()->signaling_thread()->Clear(this,
MSG_TERMINATECALL);
break;
default:
break;
}
SignalSessionState(this, session, state);
}
void Call::OnSessionError(BaseSession *session, Session::Error error) {
session_client_->session_manager()->signaling_thread()->Clear(this,
MSG_TERMINATECALL);
SignalSessionError(this, session, error);
}
void Call::OnSessionInfo(Session *session,
const buzz::XmlElement* action_elem) {
// We have a different list of "updates" because we only want to
// signal the sources that were added or removed. We want to filter
// out un-changed sources.
cricket::MediaSources updates;
if (IsSourcesNotify(action_elem)) {
MediaSources sources;
ParseError error;
if (!ParseSourcesNotify(action_elem, session->remote_description(),
&sources, &error)) {
// TODO: Is there a way we can signal an IQ error
// back to the sender?
LOG(LS_WARNING) << "Invalid sources notify message: " << error.text;
return;
}
NamedSources::iterator it;
for (it = sources.audio.begin(); it != sources.audio.end(); ++it) {
const NamedSource* found;
if (it->ssrc_set) {
found = media_sources.GetAudioSourceBySsrc(it->ssrc);
} else {
// For backwards compatibility, we remove by nick.
// TODO: Remove once all senders use explicit remove by ssrc.
found = media_sources.GetFirstAudioSourceByNick(it->nick);
if (found) {
it->SetSsrc(found->ssrc);
it->removed = true;
} else {
continue; // No ssrc to remove.
}
}
if (it->removed && found) {
RemoveVoiceStream(session, found->ssrc);
media_sources.RemoveAudioSourceBySsrc(it->ssrc);
updates.audio.push_back(*it);
LOG(LS_INFO) << "Removed voice stream: " << found->ssrc;
} else if (!it->removed && !found) {
AddVoiceStream(session, it->ssrc);
media_sources.AddAudioSource(*it);
updates.audio.push_back(*it);
LOG(LS_INFO) << "Added voice stream: " << it->ssrc;
}
}
for (it = sources.video.begin(); it != sources.video.end(); ++it) {
const NamedSource* found;
if (it->ssrc_set) {
found = media_sources.GetVideoSourceBySsrc(it->ssrc);
} else {
// For backwards compatibility, we remove by nick.
// TODO: Remove once all senders use explicit remove by ssrc.
found = media_sources.GetFirstVideoSourceByNick(it->nick);
if (found) {
it->SetSsrc(found->ssrc);
it->removed = true;
} else {
continue; // No ssrc to remove.
}
}
if (it->removed && found) {
RemoveVideoStream(session, found->ssrc);
media_sources.RemoveVideoSourceBySsrc(it->ssrc);
updates.video.push_back(*it);
LOG(LS_INFO) << "Removed video stream: " << found->ssrc;
} else if (!it->removed && !found) {
AddVideoStream(session, it->ssrc);
media_sources.AddVideoSource(*it);
updates.video.push_back(*it);
LOG(LS_INFO) << "Added video stream: " << it->ssrc;
}
}
if (!updates.audio.empty() || !updates.video.empty()) {
SignalMediaSourcesUpdate(this, session, updates);
}
}
}
void Call::OnReceivedTerminateReason(Session *session,
const std::string &reason) {
session_client_->session_manager()->signaling_thread()->Clear(this,
MSG_TERMINATECALL);
SignalReceivedTerminateReason(this, session, reason);
}
} // namespace cricket