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