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 |