/* * 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 { 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 { public: PresenterStateClient(const std::string& publisher_nick, PubSubClient* client, const QName& state_name, bool default_state) : PubSubStateClient( 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 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( 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( 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( 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( 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( 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& 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& 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& 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& 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& 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& 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