• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetPublisherNickFromPubSubItem(const XmlElement * item_elem)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:
~PubSubStateSerializer()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:
~PubSubStateKeySerializer()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:
GetKey(const std::string & publisher_nick,const std::string & published_nick)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:
GetKey(const std::string & publisher_nick,const std::string & published_nick)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> {
Write(const QName & state_name,const bool & state)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 
Parse(const XmlElement * state_elem)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.
PubSubStateClient(const std::string & publisher_nick,PubSubClient * client,const QName & state_name,C default_state,PubSubStateKeySerializer * key_serializer,PubSubStateSerializer<C> * state_serializer)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 
~PubSubStateClient()144   virtual ~PubSubStateClient() {}
145 
Publish(const std::string & published_nick,const C & state,std::string * task_id_out)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)
ParseStateItem(const PubSubItem & item,StateItemInfo * info_out,bool * state_out)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 
StatesEqual(C state1,C state2)185   virtual bool StatesEqual(C state1, C state2) {
186     return state1 == state2;
187   }
188 
client()189   PubSubClient* client() { return client_; }
190 
191  private:
OnItems(PubSubClient * pub_sub_client,const std::vector<PubSubItem> & items)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 
OnItem(const PubSubItem & item)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 
OnPublishResult(PubSubClient * pub_sub_client,const std::string & task_id,const XmlElement * item)251   void OnPublishResult(PubSubClient* pub_sub_client,
252                        const std::string& task_id,
253                        const XmlElement* item) {
254     SignalPublishResult(task_id, item);
255   }
256 
OnPublishError(PubSubClient * pub_sub_client,const std::string & task_id,const buzz::XmlElement * item,const buzz::XmlElement * stanza)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 
OnRetractResult(PubSubClient * pub_sub_client,const std::string & task_id)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 
OnRetractError(PubSubClient * pub_sub_client,const std::string & task_id,const buzz::XmlElement * stanza)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:
PresenterStateClient(const std::string & publisher_nick,PubSubClient * client,const QName & state_name,bool default_state)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 
Publish(const std::string & published_nick,const bool & state,std::string * task_id_out)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:
ParseStateItem(const PubSubItem & item,StateItemInfo * info_out,bool * state_out)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 
StatesEqual(bool state1,bool state2)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 
HangoutPubSubClient(XmppTaskParentInterface * parent,const Jid & mucjid,const std::string & nick)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 
~HangoutPubSubClient()427 HangoutPubSubClient::~HangoutPubSubClient() {
428 }
429 
RequestAll()430 void HangoutPubSubClient::RequestAll() {
431   presenter_client_->RequestItems();
432   media_client_->RequestItems();
433 }
434 
OnPresenterRequestError(PubSubClient * client,const XmlElement * stanza)435 void HangoutPubSubClient::OnPresenterRequestError(
436     PubSubClient* client, const XmlElement* stanza) {
437   SignalRequestError(client->node(), stanza);
438 }
439 
OnMediaRequestError(PubSubClient * client,const XmlElement * stanza)440 void HangoutPubSubClient::OnMediaRequestError(
441     PubSubClient* client, const XmlElement* stanza) {
442   SignalRequestError(client->node(), stanza);
443 }
444 
PublishPresenterState(bool presenting,std::string * task_id_out)445 void HangoutPubSubClient::PublishPresenterState(
446     bool presenting, std::string* task_id_out) {
447   presenter_state_client_->Publish(nick_, presenting, task_id_out);
448 }
449 
PublishAudioMuteState(bool muted,std::string * task_id_out)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 
PublishVideoMuteState(bool muted,std::string * task_id_out)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 
PublishVideoPauseState(bool paused,std::string * task_id_out)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 
PublishRecordingState(bool recording,std::string * task_id_out)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.
RemoteMute(const std::string & mutee_nick,std::string * task_id_out)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.
BlockMedia(const std::string & blockee_nick,std::string * task_id_out)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 
OnPresenterStateChange(const PubSubStateChange<bool> & change)483 void HangoutPubSubClient::OnPresenterStateChange(
484     const PubSubStateChange<bool>& change) {
485   SignalPresenterStateChange(
486       change.published_nick, change.old_state, change.new_state);
487 }
488 
OnPresenterPublishResult(const std::string & task_id,const XmlElement * item)489 void HangoutPubSubClient::OnPresenterPublishResult(
490     const std::string& task_id, const XmlElement* item) {
491   SignalPublishPresenterResult(task_id);
492 }
493 
OnPresenterPublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)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.
OnAudioMuteStateChange(const PubSubStateChange<bool> & change)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 
GetAudioMuteNickFromItem(const XmlElement * item)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 
GetBlockeeNickFromItem(const XmlElement * item)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 
OnAudioMutePublishResult(const std::string & task_id,const XmlElement * item)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 
OnAudioMutePublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)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 
OnVideoMuteStateChange(const PubSubStateChange<bool> & change)567 void HangoutPubSubClient::OnVideoMuteStateChange(
568     const PubSubStateChange<bool>& change) {
569   SignalVideoMuteStateChange(
570       change.published_nick, change.old_state, change.new_state);
571 }
572 
OnVideoMutePublishResult(const std::string & task_id,const XmlElement * item)573 void HangoutPubSubClient::OnVideoMutePublishResult(
574     const std::string& task_id, const XmlElement* item) {
575   SignalPublishVideoMuteResult(task_id);
576 }
577 
OnVideoMutePublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)578 void HangoutPubSubClient::OnVideoMutePublishError(
579     const std::string& task_id, const XmlElement* item,
580     const XmlElement* stanza) {
581   SignalPublishVideoMuteError(task_id, stanza);
582 }
583 
OnVideoPauseStateChange(const PubSubStateChange<bool> & change)584 void HangoutPubSubClient::OnVideoPauseStateChange(
585     const PubSubStateChange<bool>& change) {
586   SignalVideoPauseStateChange(
587       change.published_nick, change.old_state, change.new_state);
588 }
589 
OnVideoPausePublishResult(const std::string & task_id,const XmlElement * item)590 void HangoutPubSubClient::OnVideoPausePublishResult(
591     const std::string& task_id, const XmlElement* item) {
592   SignalPublishVideoPauseResult(task_id);
593 }
594 
OnVideoPausePublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)595 void HangoutPubSubClient::OnVideoPausePublishError(
596     const std::string& task_id, const XmlElement* item,
597     const XmlElement* stanza) {
598   SignalPublishVideoPauseError(task_id, stanza);
599 }
600 
OnRecordingStateChange(const PubSubStateChange<bool> & change)601 void HangoutPubSubClient::OnRecordingStateChange(
602     const PubSubStateChange<bool>& change) {
603   SignalRecordingStateChange(
604       change.published_nick, change.old_state, change.new_state);
605 }
606 
OnRecordingPublishResult(const std::string & task_id,const XmlElement * item)607 void HangoutPubSubClient::OnRecordingPublishResult(
608     const std::string& task_id, const XmlElement* item) {
609   SignalPublishRecordingResult(task_id);
610 }
611 
OnRecordingPublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)612 void HangoutPubSubClient::OnRecordingPublishError(
613     const std::string& task_id, const XmlElement* item,
614     const XmlElement* stanza) {
615   SignalPublishRecordingError(task_id, stanza);
616 }
617 
OnMediaBlockStateChange(const PubSubStateChange<bool> & change)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 
OnMediaBlockPublishResult(const std::string & task_id,const XmlElement * item)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 
OnMediaBlockPublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)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