 f9277a9381
			
		
	
	f9277a9381
	
	
	
		
			
			git-svn-id: http://webrtc.googlecode.com/svn/trunk@6069 4adac7df-926f-26a2-2b94-8c16560cd09d
		
			
				
	
	
		
			418 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			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 "talk/base/logging.h"
 | |
| #include "talk/xmpp/constants.h"
 | |
| #include "talk/xmpp/jid.h"
 | |
| #include "talk/xmllite/qname.h"
 | |
| #include "talk/xmllite/xmlelement.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
 |