| Index: webrtc/libjingle/xmpp/hangoutpubsubclient.cc
|
| diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient.cc b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..db1ac31491abc8b78e96e203865ff147d31f1122
|
| --- /dev/null
|
| +++ b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc
|
| @@ -0,0 +1,400 @@
|
| +/*
|
| + * Copyright 2011 The WebRTC Project Authors. All rights reserved.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license
|
| + * that can be found in the LICENSE file in the root of the source
|
| + * tree. An additional intellectual property rights grant can be found
|
| + * in the file PATENTS. All contributing project authors may
|
| + * be found in the AUTHORS file in the root of the source tree.
|
| + */
|
| +
|
| +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h"
|
| +
|
| +#include "webrtc/libjingle/xmllite/qname.h"
|
| +#include "webrtc/libjingle/xmllite/xmlelement.h"
|
| +#include "webrtc/libjingle/xmpp/constants.h"
|
| +#include "webrtc/libjingle/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
|
|
|