Home | History | Annotate | Download | only in xmpp
      1 /*
      2  * libjingle
      3  * Copyright 2011, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include "talk/xmpp/hangoutpubsubclient.h"
     29 
     30 #include "talk/base/logging.h"
     31 #include "talk/xmpp/constants.h"
     32 #include "talk/xmpp/jid.h"
     33 #include "talk/xmllite/qname.h"
     34 #include "talk/xmllite/xmlelement.h"
     35 
     36 
     37 // Gives a high-level API for MUC call PubSub needs such as
     38 // presenter state, recording state, mute state, and remote mute.
     39 
     40 namespace buzz {
     41 
     42 namespace {
     43 const char kPresenting[] = "s";
     44 const char kNotPresenting[] = "o";
     45 const char kEmpty[] = "";
     46 
     47 const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem) {
     48   if (item_elem == NULL) {
     49     return "";
     50   }
     51 
     52   return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource();
     53 }
     54 
     55 }  // namespace
     56 
     57 
     58 // Knows how to handle specific states and XML.
     59 template <typename C>
     60 class PubSubStateSerializer {
     61  public:
     62   virtual ~PubSubStateSerializer() {}
     63   virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
     64   virtual C Parse(const XmlElement* state_elem) = 0;
     65 };
     66 
     67 // Knows how to create "keys" for states, which determines their
     68 // uniqueness.  Most states are per-nick, but block is
     69 // per-blocker-and-blockee.  This is independent of itemid, especially
     70 // in the case of presenter state.
     71 class PubSubStateKeySerializer {
     72  public:
     73   virtual ~PubSubStateKeySerializer() {}
     74   virtual std::string GetKey(const std::string& publisher_nick,
     75                              const std::string& published_nick) = 0;
     76 };
     77 
     78 class PublishedNickKeySerializer : public PubSubStateKeySerializer {
     79  public:
     80   virtual std::string GetKey(const std::string& publisher_nick,
     81                              const std::string& published_nick) {
     82     return published_nick;
     83   }
     84 };
     85 
     86 class PublisherAndPublishedNicksKeySerializer
     87     : public PubSubStateKeySerializer {
     88  public:
     89   virtual std::string GetKey(const std::string& publisher_nick,
     90                              const std::string& published_nick) {
     91     return publisher_nick + ":" + published_nick;
     92   }
     93 };
     94 
     95 // A simple serialiazer where presence of item => true, lack of item
     96 // => false.
     97 class BoolStateSerializer : public PubSubStateSerializer<bool> {
     98   virtual XmlElement* Write(const QName& state_name, const bool& state) {
     99     if (!state) {
    100       return NULL;
    101     }
    102 
    103     return new XmlElement(state_name, true);
    104   }
    105 
    106   virtual bool Parse(const XmlElement* state_elem) {
    107     return state_elem != NULL;
    108   }
    109 };
    110 
    111 // Adapts PubSubClient to be specifically suited for pub sub call
    112 // states.  Signals state changes and keeps track of keys, which are
    113 // normally nicks.
    114 // TODO: Expose this as a generally useful class, not just
    115 // private to hangouts.
    116 template <typename C>
    117 class PubSubStateClient : public sigslot::has_slots<> {
    118  public:
    119   // Gets ownership of the serializers, but not the client.
    120   PubSubStateClient(const std::string& publisher_nick,
    121                     PubSubClient* client,
    122                     const QName& state_name,
    123                     C default_state,
    124                     PubSubStateKeySerializer* key_serializer,
    125                     PubSubStateSerializer<C>* state_serializer)
    126       : publisher_nick_(publisher_nick),
    127         client_(client),
    128         state_name_(state_name),
    129         default_state_(default_state) {
    130     key_serializer_.reset(key_serializer);
    131     state_serializer_.reset(state_serializer);
    132     client_->SignalItems.connect(
    133         this, &PubSubStateClient<C>::OnItems);
    134     client_->SignalPublishResult.connect(
    135         this, &PubSubStateClient<C>::OnPublishResult);
    136     client_->SignalPublishError.connect(
    137         this, &PubSubStateClient<C>::OnPublishError);
    138     client_->SignalRetractResult.connect(
    139         this, &PubSubStateClient<C>::OnRetractResult);
    140     client_->SignalRetractError.connect(
    141         this, &PubSubStateClient<C>::OnRetractError);
    142   }
    143 
    144   virtual ~PubSubStateClient() {}
    145 
    146   virtual void Publish(const std::string& published_nick,
    147                        const C& state,
    148                        std::string* task_id_out) {
    149     std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
    150     std::string itemid = state_name_.LocalPart() + ":" + key;
    151     if (StatesEqual(state, default_state_)) {
    152       client_->RetractItem(itemid, task_id_out);
    153     } else {
    154       XmlElement* state_elem = state_serializer_->Write(state_name_, state);
    155       state_elem->AddAttr(QN_NICK, published_nick);
    156       client_->PublishItem(itemid, state_elem, task_id_out);
    157     }
    158   };
    159 
    160   sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
    161   // Signal (task_id, item).  item is NULL for retract.
    162   sigslot::signal2<const std::string&,
    163                    const XmlElement*> SignalPublishResult;
    164   // Signal (task_id, item, error stanza).  item is NULL for retract.
    165   sigslot::signal3<const std::string&,
    166                    const XmlElement*,
    167                    const XmlElement*> SignalPublishError;
    168 
    169  protected:
    170   // return false if retracted item (no info or state given)
    171   virtual bool ParseStateItem(const PubSubItem& item,
    172                               StateItemInfo* info_out,
    173                               bool* state_out) {
    174     const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
    175     if (state_elem == NULL) {
    176       return false;
    177     }
    178 
    179     info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
    180     info_out->published_nick = state_elem->Attr(QN_NICK);
    181     *state_out = state_serializer_->Parse(state_elem);
    182     return true;
    183   };
    184 
    185   virtual bool StatesEqual(C state1, C state2) {
    186     return state1 == state2;
    187   }
    188 
    189   PubSubClient* client() { return client_; }
    190 
    191  private:
    192   void OnItems(PubSubClient* pub_sub_client,
    193                const std::vector<PubSubItem>& items) {
    194     for (std::vector<PubSubItem>::const_iterator item = items.begin();
    195          item != items.end(); ++item) {
    196       OnItem(*item);
    197     }
    198   }
    199 
    200   void OnItem(const PubSubItem& item) {
    201     const std::string& itemid = item.itemid;
    202     StateItemInfo info;
    203     C new_state;
    204 
    205     bool retracted = !ParseStateItem(item, &info, &new_state);
    206     if (retracted) {
    207       bool known_itemid =
    208           (info_by_itemid_.find(itemid) != info_by_itemid_.end());
    209       if (!known_itemid) {
    210         // Nothing to retract, and nothing to publish.
    211         // Probably a different state type.
    212         return;
    213       } else {
    214         info = info_by_itemid_[itemid];
    215         info_by_itemid_.erase(itemid);
    216         new_state = default_state_;
    217       }
    218     } else {
    219       // TODO: Assert new key matches the known key. It
    220       // shouldn't change!
    221       info_by_itemid_[itemid] = info;
    222     }
    223 
    224     std::string key = key_serializer_->GetKey(
    225         info.publisher_nick, info.published_nick);
    226     bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
    227     C old_state = has_old_state ? state_by_key_[key] : default_state_;
    228     if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
    229       // Nothing change, so don't bother signalling.
    230       return;
    231     }
    232 
    233     if (retracted || StatesEqual(new_state, default_state_)) {
    234       // We treat a default state similar to a retract.
    235       state_by_key_.erase(key);
    236     } else {
    237       state_by_key_[key] = new_state;
    238     }
    239 
    240     PubSubStateChange<C> change;
    241     if (!retracted) {
    242       // Retracts do not have publisher information.
    243       change.publisher_nick = info.publisher_nick;
    244     }
    245     change.published_nick = info.published_nick;
    246     change.old_state = old_state;
    247     change.new_state = new_state;
    248     SignalStateChange(change);
    249  }
    250 
    251   void OnPublishResult(PubSubClient* pub_sub_client,
    252                        const std::string& task_id,
    253                        const XmlElement* item) {
    254     SignalPublishResult(task_id, item);
    255   }
    256 
    257   void OnPublishError(PubSubClient* pub_sub_client,
    258                       const std::string& task_id,
    259                       const buzz::XmlElement* item,
    260                       const buzz::XmlElement* stanza) {
    261     SignalPublishError(task_id, item, stanza);
    262   }
    263 
    264   void OnRetractResult(PubSubClient* pub_sub_client,
    265                        const std::string& task_id) {
    266     // There's no point in differentiating between publish and retract
    267     // errors, so we simplify by making them both signal a publish
    268     // result.
    269     const XmlElement* item = NULL;
    270     SignalPublishResult(task_id, item);
    271   }
    272 
    273   void OnRetractError(PubSubClient* pub_sub_client,
    274                       const std::string& task_id,
    275                       const buzz::XmlElement* stanza) {
    276     // There's no point in differentiating between publish and retract
    277     // errors, so we simplify by making them both signal a publish
    278     // error.
    279     const XmlElement* item = NULL;
    280     SignalPublishError(task_id, item, stanza);
    281   }
    282 
    283   std::string publisher_nick_;
    284   PubSubClient* client_;
    285   const QName state_name_;
    286   C default_state_;
    287   talk_base::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
    288   talk_base::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
    289   // key => state
    290   std::map<std::string, C> state_by_key_;
    291   // itemid => StateItemInfo
    292   std::map<std::string, StateItemInfo> info_by_itemid_;
    293 };
    294 
    295 class PresenterStateClient : public PubSubStateClient<bool> {
    296  public:
    297   PresenterStateClient(const std::string& publisher_nick,
    298                        PubSubClient* client,
    299                        const QName& state_name,
    300                        bool default_state)
    301       : PubSubStateClient<bool>(
    302           publisher_nick, client, state_name, default_state,
    303           new PublishedNickKeySerializer(), NULL) {
    304   }
    305 
    306   virtual void Publish(const std::string& published_nick,
    307                        const bool& state,
    308                        std::string* task_id_out) {
    309     XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
    310     presenter_elem->AddAttr(QN_NICK, published_nick);
    311 
    312     XmlElement* presentation_item_elem =
    313         new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
    314     const std::string& presentation_type = state ? kPresenting : kNotPresenting;
    315     presentation_item_elem->AddAttr(
    316         QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
    317 
    318     // The Presenter state is kind of dumb in that it doesn't always use
    319     // retracts.  It relies on setting the "type" to a special value.
    320     std::string itemid = published_nick;
    321     std::vector<XmlElement*> children;
    322     children.push_back(presenter_elem);
    323     children.push_back(presentation_item_elem);
    324     client()->PublishItem(itemid, children, task_id_out);
    325   }
    326 
    327  protected:
    328   virtual bool ParseStateItem(const PubSubItem& item,
    329                               StateItemInfo* info_out,
    330                               bool* state_out) {
    331     const XmlElement* presenter_elem =
    332         item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
    333     const XmlElement* presentation_item_elem =
    334         item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
    335     if (presentation_item_elem == NULL || presenter_elem == NULL) {
    336       return false;
    337     }
    338 
    339     info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
    340     info_out->published_nick = presenter_elem->Attr(QN_NICK);
    341     *state_out = (presentation_item_elem->Attr(
    342         QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
    343     return true;
    344   }
    345 
    346   virtual bool StatesEqual(bool state1, bool state2) {
    347     return false;  // Make every item trigger an event, even if state doesn't change.
    348   }
    349 };
    350 
    351 HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
    352                                          const Jid& mucjid,
    353                                          const std::string& nick)
    354     : mucjid_(mucjid),
    355       nick_(nick) {
    356   presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
    357   presenter_client_->SignalRequestError.connect(
    358       this, &HangoutPubSubClient::OnPresenterRequestError);
    359 
    360   media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
    361   media_client_->SignalRequestError.connect(
    362       this, &HangoutPubSubClient::OnMediaRequestError);
    363 
    364   presenter_state_client_.reset(new PresenterStateClient(
    365       nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
    366   presenter_state_client_->SignalStateChange.connect(
    367       this, &HangoutPubSubClient::OnPresenterStateChange);
    368   presenter_state_client_->SignalPublishResult.connect(
    369       this, &HangoutPubSubClient::OnPresenterPublishResult);
    370   presenter_state_client_->SignalPublishError.connect(
    371       this, &HangoutPubSubClient::OnPresenterPublishError);
    372 
    373   audio_mute_state_client_.reset(new PubSubStateClient<bool>(
    374       nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
    375       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    376   // Can't just repeat because we need to watch for remote mutes.
    377   audio_mute_state_client_->SignalStateChange.connect(
    378       this, &HangoutPubSubClient::OnAudioMuteStateChange);
    379   audio_mute_state_client_->SignalPublishResult.connect(
    380       this, &HangoutPubSubClient::OnAudioMutePublishResult);
    381   audio_mute_state_client_->SignalPublishError.connect(
    382       this, &HangoutPubSubClient::OnAudioMutePublishError);
    383 
    384   video_mute_state_client_.reset(new PubSubStateClient<bool>(
    385       nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false,
    386       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    387   // Can't just repeat because we need to watch for remote mutes.
    388   video_mute_state_client_->SignalStateChange.connect(
    389       this, &HangoutPubSubClient::OnVideoMuteStateChange);
    390   video_mute_state_client_->SignalPublishResult.connect(
    391       this, &HangoutPubSubClient::OnVideoMutePublishResult);
    392   video_mute_state_client_->SignalPublishError.connect(
    393       this, &HangoutPubSubClient::OnVideoMutePublishError);
    394 
    395   video_pause_state_client_.reset(new PubSubStateClient<bool>(
    396       nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false,
    397       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    398   video_pause_state_client_->SignalStateChange.connect(
    399       this, &HangoutPubSubClient::OnVideoPauseStateChange);
    400   video_pause_state_client_->SignalPublishResult.connect(
    401       this, &HangoutPubSubClient::OnVideoPausePublishResult);
    402   video_pause_state_client_->SignalPublishError.connect(
    403       this, &HangoutPubSubClient::OnVideoPausePublishError);
    404 
    405   recording_state_client_.reset(new PubSubStateClient<bool>(
    406       nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
    407       new PublishedNickKeySerializer(), new BoolStateSerializer()));
    408   recording_state_client_->SignalStateChange.connect(
    409       this, &HangoutPubSubClient::OnRecordingStateChange);
    410   recording_state_client_->SignalPublishResult.connect(
    411       this, &HangoutPubSubClient::OnRecordingPublishResult);
    412   recording_state_client_->SignalPublishError.connect(
    413       this, &HangoutPubSubClient::OnRecordingPublishError);
    414 
    415   media_block_state_client_.reset(new PubSubStateClient<bool>(
    416       nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false,
    417       new PublisherAndPublishedNicksKeySerializer(),
    418       new BoolStateSerializer()));
    419   media_block_state_client_->SignalStateChange.connect(
    420       this, &HangoutPubSubClient::OnMediaBlockStateChange);
    421   media_block_state_client_->SignalPublishResult.connect(
    422       this, &HangoutPubSubClient::OnMediaBlockPublishResult);
    423   media_block_state_client_->SignalPublishError.connect(
    424       this, &HangoutPubSubClient::OnMediaBlockPublishError);
    425 }
    426 
    427 HangoutPubSubClient::~HangoutPubSubClient() {
    428 }
    429 
    430 void HangoutPubSubClient::RequestAll() {
    431   presenter_client_->RequestItems();
    432   media_client_->RequestItems();
    433 }
    434 
    435 void HangoutPubSubClient::OnPresenterRequestError(
    436     PubSubClient* client, const XmlElement* stanza) {
    437   SignalRequestError(client->node(), stanza);
    438 }
    439 
    440 void HangoutPubSubClient::OnMediaRequestError(
    441     PubSubClient* client, const XmlElement* stanza) {
    442   SignalRequestError(client->node(), stanza);
    443 }
    444 
    445 void HangoutPubSubClient::PublishPresenterState(
    446     bool presenting, std::string* task_id_out) {
    447   presenter_state_client_->Publish(nick_, presenting, task_id_out);
    448 }
    449 
    450 void HangoutPubSubClient::PublishAudioMuteState(
    451     bool muted, std::string* task_id_out) {
    452   audio_mute_state_client_->Publish(nick_, muted, task_id_out);
    453 }
    454 
    455 void HangoutPubSubClient::PublishVideoMuteState(
    456     bool muted, std::string* task_id_out) {
    457   video_mute_state_client_->Publish(nick_, muted, task_id_out);
    458 }
    459 
    460 void HangoutPubSubClient::PublishVideoPauseState(
    461     bool paused, std::string* task_id_out) {
    462   video_pause_state_client_->Publish(nick_, paused, task_id_out);
    463 }
    464 
    465 void HangoutPubSubClient::PublishRecordingState(
    466     bool recording, std::string* task_id_out) {
    467   recording_state_client_->Publish(nick_, recording, task_id_out);
    468 }
    469 
    470 // Remote mute is accomplished by setting another client's mute state.
    471 void HangoutPubSubClient::RemoteMute(
    472     const std::string& mutee_nick, std::string* task_id_out) {
    473   audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
    474 }
    475 
    476 // Block media is accomplished by setting another client's block
    477 // state, kind of like remote mute.
    478 void HangoutPubSubClient::BlockMedia(
    479     const std::string& blockee_nick, std::string* task_id_out) {
    480   media_block_state_client_->Publish(blockee_nick, true, task_id_out);
    481 }
    482 
    483 void HangoutPubSubClient::OnPresenterStateChange(
    484     const PubSubStateChange<bool>& change) {
    485   SignalPresenterStateChange(
    486       change.published_nick, change.old_state, change.new_state);
    487 }
    488 
    489 void HangoutPubSubClient::OnPresenterPublishResult(
    490     const std::string& task_id, const XmlElement* item) {
    491   SignalPublishPresenterResult(task_id);
    492 }
    493 
    494 void HangoutPubSubClient::OnPresenterPublishError(
    495     const std::string& task_id, const XmlElement* item,
    496     const XmlElement* stanza) {
    497   SignalPublishPresenterError(task_id, stanza);
    498 }
    499 
    500 // Since a remote mute is accomplished by another client setting our
    501 // mute state, if our state changes to muted, we should mute ourselves.
    502 // Note that remote un-muting is disallowed by the RoomServer.
    503 void HangoutPubSubClient::OnAudioMuteStateChange(
    504     const PubSubStateChange<bool>& change) {
    505   bool was_muted = change.old_state;
    506   bool is_muted = change.new_state;
    507   bool remote_action = (!change.publisher_nick.empty() &&
    508                         (change.publisher_nick != change.published_nick));
    509   if (remote_action) {
    510     const std::string& mutee_nick = change.published_nick;
    511     const std::string& muter_nick = change.publisher_nick;
    512     if (!is_muted) {
    513       // The server should prevent remote un-mute.
    514       LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick;
    515       return;
    516     }
    517     bool should_mute_locally = (mutee_nick == nick_);
    518     SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
    519   } else {
    520     SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
    521   }
    522 }
    523 
    524 const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
    525   if (item != NULL) {
    526     const XmlElement* audio_mute_state =
    527         item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
    528     if (audio_mute_state != NULL) {
    529       return audio_mute_state->Attr(QN_NICK);
    530     }
    531   }
    532   return std::string();
    533 }
    534 
    535 const std::string GetBlockeeNickFromItem(const XmlElement* item) {
    536   if (item != NULL) {
    537     const XmlElement* media_block_state =
    538         item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK);
    539     if (media_block_state != NULL) {
    540       return media_block_state->Attr(QN_NICK);
    541     }
    542   }
    543   return std::string();
    544 }
    545 
    546 void HangoutPubSubClient::OnAudioMutePublishResult(
    547     const std::string& task_id, const XmlElement* item) {
    548   const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
    549   if (mutee_nick != nick_) {
    550     SignalRemoteMuteResult(task_id, mutee_nick);
    551   } else {
    552     SignalPublishAudioMuteResult(task_id);
    553   }
    554 }
    555 
    556 void HangoutPubSubClient::OnAudioMutePublishError(
    557     const std::string& task_id, const XmlElement* item,
    558     const XmlElement* stanza) {
    559   const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
    560   if (mutee_nick != nick_) {
    561     SignalRemoteMuteError(task_id, mutee_nick, stanza);
    562   } else {
    563     SignalPublishAudioMuteError(task_id, stanza);
    564   }
    565 }
    566 
    567 void HangoutPubSubClient::OnVideoMuteStateChange(
    568     const PubSubStateChange<bool>& change) {
    569   SignalVideoMuteStateChange(
    570       change.published_nick, change.old_state, change.new_state);
    571 }
    572 
    573 void HangoutPubSubClient::OnVideoMutePublishResult(
    574     const std::string& task_id, const XmlElement* item) {
    575   SignalPublishVideoMuteResult(task_id);
    576 }
    577 
    578 void HangoutPubSubClient::OnVideoMutePublishError(
    579     const std::string& task_id, const XmlElement* item,
    580     const XmlElement* stanza) {
    581   SignalPublishVideoMuteError(task_id, stanza);
    582 }
    583 
    584 void HangoutPubSubClient::OnVideoPauseStateChange(
    585     const PubSubStateChange<bool>& change) {
    586   SignalVideoPauseStateChange(
    587       change.published_nick, change.old_state, change.new_state);
    588 }
    589 
    590 void HangoutPubSubClient::OnVideoPausePublishResult(
    591     const std::string& task_id, const XmlElement* item) {
    592   SignalPublishVideoPauseResult(task_id);
    593 }
    594 
    595 void HangoutPubSubClient::OnVideoPausePublishError(
    596     const std::string& task_id, const XmlElement* item,
    597     const XmlElement* stanza) {
    598   SignalPublishVideoPauseError(task_id, stanza);
    599 }
    600 
    601 void HangoutPubSubClient::OnRecordingStateChange(
    602     const PubSubStateChange<bool>& change) {
    603   SignalRecordingStateChange(
    604       change.published_nick, change.old_state, change.new_state);
    605 }
    606 
    607 void HangoutPubSubClient::OnRecordingPublishResult(
    608     const std::string& task_id, const XmlElement* item) {
    609   SignalPublishRecordingResult(task_id);
    610 }
    611 
    612 void HangoutPubSubClient::OnRecordingPublishError(
    613     const std::string& task_id, const XmlElement* item,
    614     const XmlElement* stanza) {
    615   SignalPublishRecordingError(task_id, stanza);
    616 }
    617 
    618 void HangoutPubSubClient::OnMediaBlockStateChange(
    619     const PubSubStateChange<bool>& change) {
    620   const std::string& blockee_nick = change.published_nick;
    621   const std::string& blocker_nick = change.publisher_nick;
    622 
    623   bool was_blockee = change.old_state;
    624   bool is_blockee = change.new_state;
    625   if (!was_blockee && is_blockee) {
    626     SignalMediaBlock(blockee_nick, blocker_nick);
    627   }
    628   // TODO: Should we bother signaling unblock? Currently
    629   // it isn't allowed, but it might happen when a participant leaves
    630   // the room and the item is retracted.
    631 }
    632 
    633 void HangoutPubSubClient::OnMediaBlockPublishResult(
    634     const std::string& task_id, const XmlElement* item) {
    635   const std::string& blockee_nick = GetBlockeeNickFromItem(item);
    636   SignalMediaBlockResult(task_id, blockee_nick);
    637 }
    638 
    639 void HangoutPubSubClient::OnMediaBlockPublishError(
    640     const std::string& task_id, const XmlElement* item,
    641     const XmlElement* stanza) {
    642   const std::string& blockee_nick = GetBlockeeNickFromItem(item);
    643   SignalMediaBlockError(task_id, blockee_nick, stanza);
    644 }
    645 
    646 }  // namespace buzz
    647