Home | History | Annotate | Download | only in xmpp
      1 /*
      2  *  Copyright 2011 The WebRTC Project Authors. All rights reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #include "webrtc/libjingle/xmpp/hangoutpubsubclient.h"
     12 
     13 #include "webrtc/libjingle/xmllite/qname.h"
     14 #include "webrtc/libjingle/xmllite/xmlelement.h"
     15 #include "webrtc/libjingle/xmpp/constants.h"
     16 #include "webrtc/libjingle/xmpp/jid.h"
     17 #include "webrtc/base/logging.h"
     18 
     19 
     20 // Gives a high-level API for MUC call PubSub needs such as
     21 // presenter state, recording state, mute state, and remote mute.
     22 
     23 namespace buzz {
     24 
     25 namespace {
     26 const char kPresenting[] = "s";
     27 const char kNotPresenting[] = "o";
     28 
     29 }  // namespace
     30 
     31 // A simple serialiazer where presence of item => true, lack of item
     32 // => false.
     33 class BoolStateSerializer : public PubSubStateSerializer<bool> {
     34   virtual XmlElement* Write(const QName& state_name, const bool& state) {
     35     if (!state) {
     36       return NULL;
     37     }
     38 
     39     return new XmlElement(state_name, true);
     40   }
     41 
     42   virtual void Parse(const XmlElement* state_elem, bool *state_out) {
     43     *state_out = state_elem != NULL;
     44   }
     45 };
     46 
     47 class PresenterStateClient : public PubSubStateClient<bool> {
     48  public:
     49   PresenterStateClient(const std::string& publisher_nick,
     50                        PubSubClient* client,
     51                        const QName& state_name,
     52                        bool default_state)
     53       : PubSubStateClient<bool>(
     54           publisher_nick, client, state_name, default_state,
     55           new PublishedNickKeySerializer(), NULL) {
     56   }
     57 
     58   virtual void Publish(const std::string& published_nick,
     59                        const bool& state,
     60                        std::string* task_id_out) {
     61     XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
     62     presenter_elem->AddAttr(QN_NICK, published_nick);
     63 
     64     XmlElement* presentation_item_elem =
     65         new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
     66     const std::string& presentation_type = state ? kPresenting : kNotPresenting;
     67     presentation_item_elem->AddAttr(
     68         QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
     69 
     70     // The Presenter state is kind of dumb in that it doesn't always use
     71     // retracts.  It relies on setting the "type" to a special value.
     72     std::string itemid = published_nick;
     73     std::vector<XmlElement*> children;
     74     children.push_back(presenter_elem);
     75     children.push_back(presentation_item_elem);
     76     client()->PublishItem(itemid, children, task_id_out);
     77   }
     78 
     79  protected:
     80   virtual bool ParseStateItem(const PubSubItem& item,
     81                               StateItemInfo* info_out,
     82                               bool* state_out) {
     83     const XmlElement* presenter_elem =
     84         item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
     85     const XmlElement* presentation_item_elem =
     86         item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
     87     if (presentation_item_elem == NULL || presenter_elem == NULL) {
     88       return false;
     89     }
     90 
     91     info_out->publisher_nick =
     92         client()->GetPublisherNickFromPubSubItem(item.elem);
     93     info_out->published_nick = presenter_elem->Attr(QN_NICK);
     94     *state_out = (presentation_item_elem->Attr(
     95         QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
     96     return true;
     97   }
     98 
     99   virtual bool StatesEqual(const bool& state1, const bool& state2) {
    100     // Make every item trigger an event, even if state doesn't change.
    101     return false;
    102   }
    103 };
    104 
    105 HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
    106                                          const Jid& mucjid,
    107                                          const std::string& nick)
    108     : mucjid_(mucjid),
    109       nick_(nick) {
    110   presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
    111   presenter_client_->SignalRequestError.connect(
    112       this, &HangoutPubSubClient::OnPresenterRequestError);
    113 
    114   media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
    115   media_client_->SignalRequestError.connect(
    116       this, &HangoutPubSubClient::OnMediaRequestError);
    117 
    118   presenter_state_client_.reset(new PresenterStateClient(
    119       nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
    120   presenter_state_client_->SignalStateChange.connect(
    121       this, &HangoutPubSubClient::OnPresenterStateChange);
    122   presenter_state_client_->SignalPublishResult.connect(
    123       this, &HangoutPubSubClient::OnPresenterPublishResult);
    124   presenter_state_client_->SignalPublishError.connect(
    125       this, &HangoutPubSubClient::OnPresenterPublishError);
    126 
    127   audio_mute_state_client_.reset(new PubSubStateClient<bool>(
    128       nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
    129       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    130   // Can't just repeat because we need to watch for remote mutes.
    131   audio_mute_state_client_->SignalStateChange.connect(
    132       this, &HangoutPubSubClient::OnAudioMuteStateChange);
    133   audio_mute_state_client_->SignalPublishResult.connect(
    134       this, &HangoutPubSubClient::OnAudioMutePublishResult);
    135   audio_mute_state_client_->SignalPublishError.connect(
    136       this, &HangoutPubSubClient::OnAudioMutePublishError);
    137 
    138   video_mute_state_client_.reset(new PubSubStateClient<bool>(
    139       nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false,
    140       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    141   // Can't just repeat because we need to watch for remote mutes.
    142   video_mute_state_client_->SignalStateChange.connect(
    143       this, &HangoutPubSubClient::OnVideoMuteStateChange);
    144   video_mute_state_client_->SignalPublishResult.connect(
    145       this, &HangoutPubSubClient::OnVideoMutePublishResult);
    146   video_mute_state_client_->SignalPublishError.connect(
    147       this, &HangoutPubSubClient::OnVideoMutePublishError);
    148 
    149   video_pause_state_client_.reset(new PubSubStateClient<bool>(
    150       nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false,
    151       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    152   video_pause_state_client_->SignalStateChange.connect(
    153       this, &HangoutPubSubClient::OnVideoPauseStateChange);
    154   video_pause_state_client_->SignalPublishResult.connect(
    155       this, &HangoutPubSubClient::OnVideoPausePublishResult);
    156   video_pause_state_client_->SignalPublishError.connect(
    157       this, &HangoutPubSubClient::OnVideoPausePublishError);
    158 
    159   recording_state_client_.reset(new PubSubStateClient<bool>(
    160       nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
    161       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    162   recording_state_client_->SignalStateChange.connect(
    163       this, &HangoutPubSubClient::OnRecordingStateChange);
    164   recording_state_client_->SignalPublishResult.connect(
    165       this, &HangoutPubSubClient::OnRecordingPublishResult);
    166   recording_state_client_->SignalPublishError.connect(
    167       this, &HangoutPubSubClient::OnRecordingPublishError);
    168 
    169   media_block_state_client_.reset(new PubSubStateClient<bool>(
    170       nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false,
    171       new PublisherAndPublishedNicksKeySerializer(),
    172       new BoolStateSerializer()));
    173   media_block_state_client_->SignalStateChange.connect(
    174       this, &HangoutPubSubClient::OnMediaBlockStateChange);
    175   media_block_state_client_->SignalPublishResult.connect(
    176       this, &HangoutPubSubClient::OnMediaBlockPublishResult);
    177   media_block_state_client_->SignalPublishError.connect(
    178       this, &HangoutPubSubClient::OnMediaBlockPublishError);
    179 }
    180 
    181 HangoutPubSubClient::~HangoutPubSubClient() {
    182 }
    183 
    184 void HangoutPubSubClient::RequestAll() {
    185   presenter_client_->RequestItems();
    186   media_client_->RequestItems();
    187 }
    188 
    189 void HangoutPubSubClient::OnPresenterRequestError(
    190     PubSubClient* client, const XmlElement* stanza) {
    191   SignalRequestError(client->node(), stanza);
    192 }
    193 
    194 void HangoutPubSubClient::OnMediaRequestError(
    195     PubSubClient* client, const XmlElement* stanza) {
    196   SignalRequestError(client->node(), stanza);
    197 }
    198 
    199 void HangoutPubSubClient::PublishPresenterState(
    200     bool presenting, std::string* task_id_out) {
    201   presenter_state_client_->Publish(nick_, presenting, task_id_out);
    202 }
    203 
    204 void HangoutPubSubClient::PublishAudioMuteState(
    205     bool muted, std::string* task_id_out) {
    206   audio_mute_state_client_->Publish(nick_, muted, task_id_out);
    207 }
    208 
    209 void HangoutPubSubClient::PublishVideoMuteState(
    210     bool muted, std::string* task_id_out) {
    211   video_mute_state_client_->Publish(nick_, muted, task_id_out);
    212 }
    213 
    214 void HangoutPubSubClient::PublishVideoPauseState(
    215     bool paused, std::string* task_id_out) {
    216   video_pause_state_client_->Publish(nick_, paused, task_id_out);
    217 }
    218 
    219 void HangoutPubSubClient::PublishRecordingState(
    220     bool recording, std::string* task_id_out) {
    221   recording_state_client_->Publish(nick_, recording, task_id_out);
    222 }
    223 
    224 // Remote mute is accomplished by setting another client's mute state.
    225 void HangoutPubSubClient::RemoteMute(
    226     const std::string& mutee_nick, std::string* task_id_out) {
    227   audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
    228 }
    229 
    230 // Block media is accomplished by setting another client's block
    231 // state, kind of like remote mute.
    232 void HangoutPubSubClient::BlockMedia(
    233     const std::string& blockee_nick, std::string* task_id_out) {
    234   media_block_state_client_->Publish(blockee_nick, true, task_id_out);
    235 }
    236 
    237 void HangoutPubSubClient::OnPresenterStateChange(
    238     const PubSubStateChange<bool>& change) {
    239   SignalPresenterStateChange(
    240       change.published_nick, change.old_state, change.new_state);
    241 }
    242 
    243 void HangoutPubSubClient::OnPresenterPublishResult(
    244     const std::string& task_id, const XmlElement* item) {
    245   SignalPublishPresenterResult(task_id);
    246 }
    247 
    248 void HangoutPubSubClient::OnPresenterPublishError(
    249     const std::string& task_id, const XmlElement* item,
    250     const XmlElement* stanza) {
    251   SignalPublishPresenterError(task_id, stanza);
    252 }
    253 
    254 // Since a remote mute is accomplished by another client setting our
    255 // mute state, if our state changes to muted, we should mute ourselves.
    256 // Note that remote un-muting is disallowed by the RoomServer.
    257 void HangoutPubSubClient::OnAudioMuteStateChange(
    258     const PubSubStateChange<bool>& change) {
    259   bool was_muted = change.old_state;
    260   bool is_muted = change.new_state;
    261   bool remote_action = (!change.publisher_nick.empty() &&
    262                         (change.publisher_nick != change.published_nick));
    263 
    264   if (remote_action) {
    265     const std::string& mutee_nick = change.published_nick;
    266     const std::string& muter_nick = change.publisher_nick;
    267     if (!is_muted) {
    268       // The server should prevent remote un-mute.
    269       LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick;
    270       return;
    271     }
    272     bool should_mute_locally = (mutee_nick == nick_);
    273     SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
    274   }
    275   SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
    276 }
    277 
    278 const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
    279   if (item != NULL) {
    280     const XmlElement* audio_mute_state =
    281         item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
    282     if (audio_mute_state != NULL) {
    283       return audio_mute_state->Attr(QN_NICK);
    284     }
    285   }
    286   return std::string();
    287 }
    288 
    289 const std::string GetBlockeeNickFromItem(const XmlElement* item) {
    290   if (item != NULL) {
    291     const XmlElement* media_block_state =
    292         item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK);
    293     if (media_block_state != NULL) {
    294       return media_block_state->Attr(QN_NICK);
    295     }
    296   }
    297   return std::string();
    298 }
    299 
    300 void HangoutPubSubClient::OnAudioMutePublishResult(
    301     const std::string& task_id, const XmlElement* item) {
    302   const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
    303   if (mutee_nick != nick_) {
    304     SignalRemoteMuteResult(task_id, mutee_nick);
    305   } else {
    306     SignalPublishAudioMuteResult(task_id);
    307   }
    308 }
    309 
    310 void HangoutPubSubClient::OnAudioMutePublishError(
    311     const std::string& task_id, const XmlElement* item,
    312     const XmlElement* stanza) {
    313   const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
    314   if (mutee_nick != nick_) {
    315     SignalRemoteMuteError(task_id, mutee_nick, stanza);
    316   } else {
    317     SignalPublishAudioMuteError(task_id, stanza);
    318   }
    319 }
    320 
    321 void HangoutPubSubClient::OnVideoMuteStateChange(
    322     const PubSubStateChange<bool>& change) {
    323   SignalVideoMuteStateChange(
    324       change.published_nick, change.old_state, change.new_state);
    325 }
    326 
    327 void HangoutPubSubClient::OnVideoMutePublishResult(
    328     const std::string& task_id, const XmlElement* item) {
    329   SignalPublishVideoMuteResult(task_id);
    330 }
    331 
    332 void HangoutPubSubClient::OnVideoMutePublishError(
    333     const std::string& task_id, const XmlElement* item,
    334     const XmlElement* stanza) {
    335   SignalPublishVideoMuteError(task_id, stanza);
    336 }
    337 
    338 void HangoutPubSubClient::OnVideoPauseStateChange(
    339     const PubSubStateChange<bool>& change) {
    340   SignalVideoPauseStateChange(
    341       change.published_nick, change.old_state, change.new_state);
    342 }
    343 
    344 void HangoutPubSubClient::OnVideoPausePublishResult(
    345     const std::string& task_id, const XmlElement* item) {
    346   SignalPublishVideoPauseResult(task_id);
    347 }
    348 
    349 void HangoutPubSubClient::OnVideoPausePublishError(
    350     const std::string& task_id, const XmlElement* item,
    351     const XmlElement* stanza) {
    352   SignalPublishVideoPauseError(task_id, stanza);
    353 }
    354 
    355 void HangoutPubSubClient::OnRecordingStateChange(
    356     const PubSubStateChange<bool>& change) {
    357   SignalRecordingStateChange(
    358       change.published_nick, change.old_state, change.new_state);
    359 }
    360 
    361 void HangoutPubSubClient::OnRecordingPublishResult(
    362     const std::string& task_id, const XmlElement* item) {
    363   SignalPublishRecordingResult(task_id);
    364 }
    365 
    366 void HangoutPubSubClient::OnRecordingPublishError(
    367     const std::string& task_id, const XmlElement* item,
    368     const XmlElement* stanza) {
    369   SignalPublishRecordingError(task_id, stanza);
    370 }
    371 
    372 void HangoutPubSubClient::OnMediaBlockStateChange(
    373     const PubSubStateChange<bool>& change) {
    374   const std::string& blockee_nick = change.published_nick;
    375   const std::string& blocker_nick = change.publisher_nick;
    376 
    377   bool was_blockee = change.old_state;
    378   bool is_blockee = change.new_state;
    379   if (!was_blockee && is_blockee) {
    380     SignalMediaBlock(blockee_nick, blocker_nick);
    381   }
    382   // TODO: Should we bother signaling unblock? Currently
    383   // it isn't allowed, but it might happen when a participant leaves
    384   // the room and the item is retracted.
    385 }
    386 
    387 void HangoutPubSubClient::OnMediaBlockPublishResult(
    388     const std::string& task_id, const XmlElement* item) {
    389   const std::string& blockee_nick = GetBlockeeNickFromItem(item);
    390   SignalMediaBlockResult(task_id, blockee_nick);
    391 }
    392 
    393 void HangoutPubSubClient::OnMediaBlockPublishError(
    394     const std::string& task_id, const XmlElement* item,
    395     const XmlElement* stanza) {
    396   const std::string& blockee_nick = GetBlockeeNickFromItem(item);
    397   SignalMediaBlockError(task_id, blockee_nick, stanza);
    398 }
    399 
    400 }  // namespace buzz
    401