webrtc/talk/xmpp/hangoutpubsubclient.cc

418 lines
16 KiB
C++

/*
* libjingle
* Copyright 2011, 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/xmpp/hangoutpubsubclient.h"
#include "webrtc/libjingle/xmllite/qname.h"
#include "webrtc/libjingle/xmllite/xmlelement.h"
#include "talk/xmpp/constants.h"
#include "talk/xmpp/jid.h"
#include "webrtc/base/logging.h"
// Gives a high-level API for MUC call PubSub needs such as
// presenter state, recording state, mute state, and remote mute.
namespace buzz {
namespace {
const char kPresenting[] = "s";
const char kNotPresenting[] = "o";
} // namespace
// A simple serialiazer where presence of item => true, lack of item
// => false.
class BoolStateSerializer : public PubSubStateSerializer<bool> {
virtual XmlElement* Write(const QName& state_name, const bool& state) {
if (!state) {
return NULL;
}
return new XmlElement(state_name, true);
}
virtual void Parse(const XmlElement* state_elem, bool *state_out) {
*state_out = state_elem != NULL;
}
};
class PresenterStateClient : public PubSubStateClient<bool> {
public:
PresenterStateClient(const std::string& publisher_nick,
PubSubClient* client,
const QName& state_name,
bool default_state)
: PubSubStateClient<bool>(
publisher_nick, client, state_name, default_state,
new PublishedNickKeySerializer(), NULL) {
}
virtual void Publish(const std::string& published_nick,
const bool& state,
std::string* task_id_out) {
XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
presenter_elem->AddAttr(QN_NICK, published_nick);
XmlElement* presentation_item_elem =
new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
const std::string& presentation_type = state ? kPresenting : kNotPresenting;
presentation_item_elem->AddAttr(
QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
// The Presenter state is kind of dumb in that it doesn't always use
// retracts. It relies on setting the "type" to a special value.
std::string itemid = published_nick;
std::vector<XmlElement*> children;
children.push_back(presenter_elem);
children.push_back(presentation_item_elem);
client()->PublishItem(itemid, children, task_id_out);
}
protected:
virtual bool ParseStateItem(const PubSubItem& item,
StateItemInfo* info_out,
bool* state_out) {
const XmlElement* presenter_elem =
item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
const XmlElement* presentation_item_elem =
item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
if (presentation_item_elem == NULL || presenter_elem == NULL) {
return false;
}
info_out->publisher_nick =
client()->GetPublisherNickFromPubSubItem(item.elem);
info_out->published_nick = presenter_elem->Attr(QN_NICK);
*state_out = (presentation_item_elem->Attr(
QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
return true;
}
virtual bool StatesEqual(const bool& state1, const bool& state2) {
// Make every item trigger an event, even if state doesn't change.
return false;
}
};
HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
const Jid& mucjid,
const std::string& nick)
: mucjid_(mucjid),
nick_(nick) {
presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
presenter_client_->SignalRequestError.connect(
this, &HangoutPubSubClient::OnPresenterRequestError);
media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
media_client_->SignalRequestError.connect(
this, &HangoutPubSubClient::OnMediaRequestError);
presenter_state_client_.reset(new PresenterStateClient(
nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
presenter_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnPresenterStateChange);
presenter_state_client_->SignalPublishResult.connect(
this, &HangoutPubSubClient::OnPresenterPublishResult);
presenter_state_client_->SignalPublishError.connect(
this, &HangoutPubSubClient::OnPresenterPublishError);
audio_mute_state_client_.reset(new PubSubStateClient<bool>(
nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
new PublishedNickKeySerializer(), new BoolStateSerializer()));
// Can't just repeat because we need to watch for remote mutes.
audio_mute_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnAudioMuteStateChange);
audio_mute_state_client_->SignalPublishResult.connect(
this, &HangoutPubSubClient::OnAudioMutePublishResult);
audio_mute_state_client_->SignalPublishError.connect(
this, &HangoutPubSubClient::OnAudioMutePublishError);
video_mute_state_client_.reset(new PubSubStateClient<bool>(
nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false,
new PublishedNickKeySerializer(), new BoolStateSerializer()));
// Can't just repeat because we need to watch for remote mutes.
video_mute_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnVideoMuteStateChange);
video_mute_state_client_->SignalPublishResult.connect(
this, &HangoutPubSubClient::OnVideoMutePublishResult);
video_mute_state_client_->SignalPublishError.connect(
this, &HangoutPubSubClient::OnVideoMutePublishError);
video_pause_state_client_.reset(new PubSubStateClient<bool>(
nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false,
new PublishedNickKeySerializer(), new BoolStateSerializer()));
video_pause_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnVideoPauseStateChange);
video_pause_state_client_->SignalPublishResult.connect(
this, &HangoutPubSubClient::OnVideoPausePublishResult);
video_pause_state_client_->SignalPublishError.connect(
this, &HangoutPubSubClient::OnVideoPausePublishError);
recording_state_client_.reset(new PubSubStateClient<bool>(
nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
new PublishedNickKeySerializer(), new BoolStateSerializer()));
recording_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnRecordingStateChange);
recording_state_client_->SignalPublishResult.connect(
this, &HangoutPubSubClient::OnRecordingPublishResult);
recording_state_client_->SignalPublishError.connect(
this, &HangoutPubSubClient::OnRecordingPublishError);
media_block_state_client_.reset(new PubSubStateClient<bool>(
nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false,
new PublisherAndPublishedNicksKeySerializer(),
new BoolStateSerializer()));
media_block_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnMediaBlockStateChange);
media_block_state_client_->SignalPublishResult.connect(
this, &HangoutPubSubClient::OnMediaBlockPublishResult);
media_block_state_client_->SignalPublishError.connect(
this, &HangoutPubSubClient::OnMediaBlockPublishError);
}
HangoutPubSubClient::~HangoutPubSubClient() {
}
void HangoutPubSubClient::RequestAll() {
presenter_client_->RequestItems();
media_client_->RequestItems();
}
void HangoutPubSubClient::OnPresenterRequestError(
PubSubClient* client, const XmlElement* stanza) {
SignalRequestError(client->node(), stanza);
}
void HangoutPubSubClient::OnMediaRequestError(
PubSubClient* client, const XmlElement* stanza) {
SignalRequestError(client->node(), stanza);
}
void HangoutPubSubClient::PublishPresenterState(
bool presenting, std::string* task_id_out) {
presenter_state_client_->Publish(nick_, presenting, task_id_out);
}
void HangoutPubSubClient::PublishAudioMuteState(
bool muted, std::string* task_id_out) {
audio_mute_state_client_->Publish(nick_, muted, task_id_out);
}
void HangoutPubSubClient::PublishVideoMuteState(
bool muted, std::string* task_id_out) {
video_mute_state_client_->Publish(nick_, muted, task_id_out);
}
void HangoutPubSubClient::PublishVideoPauseState(
bool paused, std::string* task_id_out) {
video_pause_state_client_->Publish(nick_, paused, task_id_out);
}
void HangoutPubSubClient::PublishRecordingState(
bool recording, std::string* task_id_out) {
recording_state_client_->Publish(nick_, recording, task_id_out);
}
// Remote mute is accomplished by setting another client's mute state.
void HangoutPubSubClient::RemoteMute(
const std::string& mutee_nick, std::string* task_id_out) {
audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
}
// Block media is accomplished by setting another client's block
// state, kind of like remote mute.
void HangoutPubSubClient::BlockMedia(
const std::string& blockee_nick, std::string* task_id_out) {
media_block_state_client_->Publish(blockee_nick, true, task_id_out);
}
void HangoutPubSubClient::OnPresenterStateChange(
const PubSubStateChange<bool>& change) {
SignalPresenterStateChange(
change.published_nick, change.old_state, change.new_state);
}
void HangoutPubSubClient::OnPresenterPublishResult(
const std::string& task_id, const XmlElement* item) {
SignalPublishPresenterResult(task_id);
}
void HangoutPubSubClient::OnPresenterPublishError(
const std::string& task_id, const XmlElement* item,
const XmlElement* stanza) {
SignalPublishPresenterError(task_id, stanza);
}
// Since a remote mute is accomplished by another client setting our
// mute state, if our state changes to muted, we should mute ourselves.
// Note that remote un-muting is disallowed by the RoomServer.
void HangoutPubSubClient::OnAudioMuteStateChange(
const PubSubStateChange<bool>& change) {
bool was_muted = change.old_state;
bool is_muted = change.new_state;
bool remote_action = (!change.publisher_nick.empty() &&
(change.publisher_nick != change.published_nick));
if (remote_action) {
const std::string& mutee_nick = change.published_nick;
const std::string& muter_nick = change.publisher_nick;
if (!is_muted) {
// The server should prevent remote un-mute.
LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick;
return;
}
bool should_mute_locally = (mutee_nick == nick_);
SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
}
SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
}
const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
if (item != NULL) {
const XmlElement* audio_mute_state =
item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
if (audio_mute_state != NULL) {
return audio_mute_state->Attr(QN_NICK);
}
}
return std::string();
}
const std::string GetBlockeeNickFromItem(const XmlElement* item) {
if (item != NULL) {
const XmlElement* media_block_state =
item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK);
if (media_block_state != NULL) {
return media_block_state->Attr(QN_NICK);
}
}
return std::string();
}
void HangoutPubSubClient::OnAudioMutePublishResult(
const std::string& task_id, const XmlElement* item) {
const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
if (mutee_nick != nick_) {
SignalRemoteMuteResult(task_id, mutee_nick);
} else {
SignalPublishAudioMuteResult(task_id);
}
}
void HangoutPubSubClient::OnAudioMutePublishError(
const std::string& task_id, const XmlElement* item,
const XmlElement* stanza) {
const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
if (mutee_nick != nick_) {
SignalRemoteMuteError(task_id, mutee_nick, stanza);
} else {
SignalPublishAudioMuteError(task_id, stanza);
}
}
void HangoutPubSubClient::OnVideoMuteStateChange(
const PubSubStateChange<bool>& change) {
SignalVideoMuteStateChange(
change.published_nick, change.old_state, change.new_state);
}
void HangoutPubSubClient::OnVideoMutePublishResult(
const std::string& task_id, const XmlElement* item) {
SignalPublishVideoMuteResult(task_id);
}
void HangoutPubSubClient::OnVideoMutePublishError(
const std::string& task_id, const XmlElement* item,
const XmlElement* stanza) {
SignalPublishVideoMuteError(task_id, stanza);
}
void HangoutPubSubClient::OnVideoPauseStateChange(
const PubSubStateChange<bool>& change) {
SignalVideoPauseStateChange(
change.published_nick, change.old_state, change.new_state);
}
void HangoutPubSubClient::OnVideoPausePublishResult(
const std::string& task_id, const XmlElement* item) {
SignalPublishVideoPauseResult(task_id);
}
void HangoutPubSubClient::OnVideoPausePublishError(
const std::string& task_id, const XmlElement* item,
const XmlElement* stanza) {
SignalPublishVideoPauseError(task_id, stanza);
}
void HangoutPubSubClient::OnRecordingStateChange(
const PubSubStateChange<bool>& change) {
SignalRecordingStateChange(
change.published_nick, change.old_state, change.new_state);
}
void HangoutPubSubClient::OnRecordingPublishResult(
const std::string& task_id, const XmlElement* item) {
SignalPublishRecordingResult(task_id);
}
void HangoutPubSubClient::OnRecordingPublishError(
const std::string& task_id, const XmlElement* item,
const XmlElement* stanza) {
SignalPublishRecordingError(task_id, stanza);
}
void HangoutPubSubClient::OnMediaBlockStateChange(
const PubSubStateChange<bool>& change) {
const std::string& blockee_nick = change.published_nick;
const std::string& blocker_nick = change.publisher_nick;
bool was_blockee = change.old_state;
bool is_blockee = change.new_state;
if (!was_blockee && is_blockee) {
SignalMediaBlock(blockee_nick, blocker_nick);
}
// TODO: Should we bother signaling unblock? Currently
// it isn't allowed, but it might happen when a participant leaves
// the room and the item is retracted.
}
void HangoutPubSubClient::OnMediaBlockPublishResult(
const std::string& task_id, const XmlElement* item) {
const std::string& blockee_nick = GetBlockeeNickFromItem(item);
SignalMediaBlockResult(task_id, blockee_nick);
}
void HangoutPubSubClient::OnMediaBlockPublishError(
const std::string& task_id, const XmlElement* item,
const XmlElement* stanza) {
const std::string& blockee_nick = GetBlockeeNickFromItem(item);
SignalMediaBlockError(task_id, blockee_nick, stanza);
}
} // namespace buzz