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