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 change.publisher_nick = info.publisher_nick; 242 change.published_nick = info.published_nick; 243 change.old_state = old_state; 244 change.new_state = new_state; 245 SignalStateChange(change); 246 } 247 248 void OnPublishResult(PubSubClient* pub_sub_client, 249 const std::string& task_id, 250 const XmlElement* item) { 251 SignalPublishResult(task_id, item); 252 } 253 254 void OnPublishError(PubSubClient* pub_sub_client, 255 const std::string& task_id, 256 const buzz::XmlElement* item, 257 const buzz::XmlElement* stanza) { 258 SignalPublishError(task_id, item, stanza); 259 } 260 261 void OnRetractResult(PubSubClient* pub_sub_client, 262 const std::string& task_id) { 263 // There's no point in differentiating between publish and retract 264 // errors, so we simplify by making them both signal a publish 265 // result. 266 const XmlElement* item = NULL; 267 SignalPublishResult(task_id, item); 268 } 269 270 void OnRetractError(PubSubClient* pub_sub_client, 271 const std::string& task_id, 272 const buzz::XmlElement* stanza) { 273 // There's no point in differentiating between publish and retract 274 // errors, so we simplify by making them both signal a publish 275 // error. 276 const XmlElement* item = NULL; 277 SignalPublishError(task_id, item, stanza); 278 } 279 280 std::string publisher_nick_; 281 PubSubClient* client_; 282 const QName state_name_; 283 C default_state_; 284 talk_base::scoped_ptr<PubSubStateKeySerializer> key_serializer_; 285 talk_base::scoped_ptr<PubSubStateSerializer<C> > state_serializer_; 286 // key => state 287 std::map<std::string, C> state_by_key_; 288 // itemid => StateItemInfo 289 std::map<std::string, StateItemInfo> info_by_itemid_; 290 }; 291 292 class PresenterStateClient : public PubSubStateClient<bool> { 293 public: 294 PresenterStateClient(const std::string& publisher_nick, 295 PubSubClient* client, 296 const QName& state_name, 297 bool default_state) 298 : PubSubStateClient<bool>( 299 publisher_nick, client, state_name, default_state, 300 new PublishedNickKeySerializer(), NULL) { 301 } 302 303 virtual void Publish(const std::string& published_nick, 304 const bool& state, 305 std::string* task_id_out) { 306 XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true); 307 presenter_elem->AddAttr(QN_NICK, published_nick); 308 309 XmlElement* presentation_item_elem = 310 new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false); 311 const std::string& presentation_type = state ? kPresenting : kNotPresenting; 312 presentation_item_elem->AddAttr( 313 QN_PRESENTER_PRESENTATION_TYPE, presentation_type); 314 315 // The Presenter state is kind of dumb in that it doesn't always use 316 // retracts. It relies on setting the "type" to a special value. 317 std::string itemid = published_nick; 318 std::vector<XmlElement*> children; 319 children.push_back(presenter_elem); 320 children.push_back(presentation_item_elem); 321 client()->PublishItem(itemid, children, task_id_out); 322 } 323 324 protected: 325 virtual bool ParseStateItem(const PubSubItem& item, 326 StateItemInfo* info_out, 327 bool* state_out) { 328 const XmlElement* presenter_elem = 329 item.elem->FirstNamed(QN_PRESENTER_PRESENTER); 330 const XmlElement* presentation_item_elem = 331 item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM); 332 if (presentation_item_elem == NULL || presenter_elem == NULL) { 333 return false; 334 } 335 336 info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem); 337 info_out->published_nick = presenter_elem->Attr(QN_NICK); 338 *state_out = (presentation_item_elem->Attr( 339 QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting); 340 return true; 341 } 342 343 virtual bool StatesEqual(bool state1, bool state2) { 344 return false; // Make every item trigger an event, even if state doesn't change. 345 } 346 }; 347 348 HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent, 349 const Jid& mucjid, 350 const std::string& nick) 351 : mucjid_(mucjid), 352 nick_(nick) { 353 presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER)); 354 presenter_client_->SignalRequestError.connect( 355 this, &HangoutPubSubClient::OnPresenterRequestError); 356 357 media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA)); 358 media_client_->SignalRequestError.connect( 359 this, &HangoutPubSubClient::OnMediaRequestError); 360 361 presenter_state_client_.reset(new PresenterStateClient( 362 nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false)); 363 presenter_state_client_->SignalStateChange.connect( 364 this, &HangoutPubSubClient::OnPresenterStateChange); 365 presenter_state_client_->SignalPublishResult.connect( 366 this, &HangoutPubSubClient::OnPresenterPublishResult); 367 presenter_state_client_->SignalPublishError.connect( 368 this, &HangoutPubSubClient::OnPresenterPublishError); 369 370 audio_mute_state_client_.reset(new PubSubStateClient<bool>( 371 nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false, 372 new PublishedNickKeySerializer(), new BoolStateSerializer())); 373 // Can't just repeat because we need to watch for remote mutes. 374 audio_mute_state_client_->SignalStateChange.connect( 375 this, &HangoutPubSubClient::OnAudioMuteStateChange); 376 audio_mute_state_client_->SignalPublishResult.connect( 377 this, &HangoutPubSubClient::OnAudioMutePublishResult); 378 audio_mute_state_client_->SignalPublishError.connect( 379 this, &HangoutPubSubClient::OnAudioMutePublishError); 380 381 video_mute_state_client_.reset(new PubSubStateClient<bool>( 382 nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false, 383 new PublishedNickKeySerializer(), new BoolStateSerializer())); 384 // Can't just repeat because we need to watch for remote mutes. 385 video_mute_state_client_->SignalStateChange.connect( 386 this, &HangoutPubSubClient::OnVideoMuteStateChange); 387 video_mute_state_client_->SignalPublishResult.connect( 388 this, &HangoutPubSubClient::OnVideoMutePublishResult); 389 video_mute_state_client_->SignalPublishError.connect( 390 this, &HangoutPubSubClient::OnVideoMutePublishError); 391 392 video_pause_state_client_.reset(new PubSubStateClient<bool>( 393 nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false, 394 new PublishedNickKeySerializer(), new BoolStateSerializer())); 395 video_pause_state_client_->SignalStateChange.connect( 396 this, &HangoutPubSubClient::OnVideoPauseStateChange); 397 video_pause_state_client_->SignalPublishResult.connect( 398 this, &HangoutPubSubClient::OnVideoPausePublishResult); 399 video_pause_state_client_->SignalPublishError.connect( 400 this, &HangoutPubSubClient::OnVideoPausePublishError); 401 402 recording_state_client_.reset(new PubSubStateClient<bool>( 403 nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false, 404 new PublishedNickKeySerializer(), new BoolStateSerializer())); 405 recording_state_client_->SignalStateChange.connect( 406 this, &HangoutPubSubClient::OnRecordingStateChange); 407 recording_state_client_->SignalPublishResult.connect( 408 this, &HangoutPubSubClient::OnRecordingPublishResult); 409 recording_state_client_->SignalPublishError.connect( 410 this, &HangoutPubSubClient::OnRecordingPublishError); 411 412 media_block_state_client_.reset(new PubSubStateClient<bool>( 413 nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false, 414 new PublisherAndPublishedNicksKeySerializer(), 415 new BoolStateSerializer())); 416 media_block_state_client_->SignalStateChange.connect( 417 this, &HangoutPubSubClient::OnMediaBlockStateChange); 418 media_block_state_client_->SignalPublishResult.connect( 419 this, &HangoutPubSubClient::OnMediaBlockPublishResult); 420 media_block_state_client_->SignalPublishError.connect( 421 this, &HangoutPubSubClient::OnMediaBlockPublishError); 422 } 423 424 HangoutPubSubClient::~HangoutPubSubClient() { 425 } 426 427 void HangoutPubSubClient::RequestAll() { 428 presenter_client_->RequestItems(); 429 media_client_->RequestItems(); 430 } 431 432 void HangoutPubSubClient::OnPresenterRequestError( 433 PubSubClient* client, const XmlElement* stanza) { 434 SignalRequestError(client->node(), stanza); 435 } 436 437 void HangoutPubSubClient::OnMediaRequestError( 438 PubSubClient* client, const XmlElement* stanza) { 439 SignalRequestError(client->node(), stanza); 440 } 441 442 void HangoutPubSubClient::PublishPresenterState( 443 bool presenting, std::string* task_id_out) { 444 presenter_state_client_->Publish(nick_, presenting, task_id_out); 445 } 446 447 void HangoutPubSubClient::PublishAudioMuteState( 448 bool muted, std::string* task_id_out) { 449 audio_mute_state_client_->Publish(nick_, muted, task_id_out); 450 } 451 452 void HangoutPubSubClient::PublishVideoMuteState( 453 bool muted, std::string* task_id_out) { 454 video_mute_state_client_->Publish(nick_, muted, task_id_out); 455 } 456 457 void HangoutPubSubClient::PublishVideoPauseState( 458 bool paused, std::string* task_id_out) { 459 video_pause_state_client_->Publish(nick_, paused, task_id_out); 460 } 461 462 void HangoutPubSubClient::PublishRecordingState( 463 bool recording, std::string* task_id_out) { 464 recording_state_client_->Publish(nick_, recording, task_id_out); 465 } 466 467 // Remote mute is accomplished by setting another client's mute state. 468 void HangoutPubSubClient::RemoteMute( 469 const std::string& mutee_nick, std::string* task_id_out) { 470 audio_mute_state_client_->Publish(mutee_nick, true, task_id_out); 471 } 472 473 // Block media is accomplished by setting another client's block 474 // state, kind of like remote mute. 475 void HangoutPubSubClient::BlockMedia( 476 const std::string& blockee_nick, std::string* task_id_out) { 477 media_block_state_client_->Publish(blockee_nick, true, task_id_out); 478 } 479 480 void HangoutPubSubClient::OnPresenterStateChange( 481 const PubSubStateChange<bool>& change) { 482 SignalPresenterStateChange( 483 change.published_nick, change.old_state, change.new_state); 484 } 485 486 void HangoutPubSubClient::OnPresenterPublishResult( 487 const std::string& task_id, const XmlElement* item) { 488 SignalPublishPresenterResult(task_id); 489 } 490 491 void HangoutPubSubClient::OnPresenterPublishError( 492 const std::string& task_id, const XmlElement* item, 493 const XmlElement* stanza) { 494 SignalPublishPresenterError(task_id, stanza); 495 } 496 497 // Since a remote mute is accomplished by another client setting our 498 // mute state, if our state changes to muted, we should mute ourselves. 499 // Note that remote un-muting is disallowed by the RoomServer. 500 void HangoutPubSubClient::OnAudioMuteStateChange( 501 const PubSubStateChange<bool>& change) { 502 bool was_muted = change.old_state; 503 bool is_muted = change.new_state; 504 bool remote_action = (!change.publisher_nick.empty() && 505 (change.publisher_nick != change.published_nick)); 506 if (remote_action) { 507 const std::string& mutee_nick = change.published_nick; 508 const std::string& muter_nick = change.publisher_nick; 509 if (!is_muted) { 510 // The server should prevent remote un-mute. 511 LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick; 512 return; 513 } 514 bool should_mute_locally = (mutee_nick == nick_); 515 SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally); 516 } else { 517 SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted); 518 } 519 } 520 521 const std::string GetAudioMuteNickFromItem(const XmlElement* item) { 522 if (item != NULL) { 523 const XmlElement* audio_mute_state = 524 item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE); 525 if (audio_mute_state != NULL) { 526 return audio_mute_state->Attr(QN_NICK); 527 } 528 } 529 return std::string(); 530 } 531 532 const std::string GetBlockeeNickFromItem(const XmlElement* item) { 533 if (item != NULL) { 534 const XmlElement* media_block_state = 535 item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK); 536 if (media_block_state != NULL) { 537 return media_block_state->Attr(QN_NICK); 538 } 539 } 540 return std::string(); 541 } 542 543 void HangoutPubSubClient::OnAudioMutePublishResult( 544 const std::string& task_id, const XmlElement* item) { 545 const std::string& mutee_nick = GetAudioMuteNickFromItem(item); 546 if (mutee_nick != nick_) { 547 SignalRemoteMuteResult(task_id, mutee_nick); 548 } else { 549 SignalPublishAudioMuteResult(task_id); 550 } 551 } 552 553 void HangoutPubSubClient::OnAudioMutePublishError( 554 const std::string& task_id, const XmlElement* item, 555 const XmlElement* stanza) { 556 const std::string& mutee_nick = GetAudioMuteNickFromItem(item); 557 if (mutee_nick != nick_) { 558 SignalRemoteMuteError(task_id, mutee_nick, stanza); 559 } else { 560 SignalPublishAudioMuteError(task_id, stanza); 561 } 562 } 563 564 void HangoutPubSubClient::OnVideoMuteStateChange( 565 const PubSubStateChange<bool>& change) { 566 SignalVideoMuteStateChange( 567 change.published_nick, change.old_state, change.new_state); 568 } 569 570 void HangoutPubSubClient::OnVideoMutePublishResult( 571 const std::string& task_id, const XmlElement* item) { 572 SignalPublishVideoMuteResult(task_id); 573 } 574 575 void HangoutPubSubClient::OnVideoMutePublishError( 576 const std::string& task_id, const XmlElement* item, 577 const XmlElement* stanza) { 578 SignalPublishVideoMuteError(task_id, stanza); 579 } 580 581 void HangoutPubSubClient::OnVideoPauseStateChange( 582 const PubSubStateChange<bool>& change) { 583 SignalVideoPauseStateChange( 584 change.published_nick, change.old_state, change.new_state); 585 } 586 587 void HangoutPubSubClient::OnVideoPausePublishResult( 588 const std::string& task_id, const XmlElement* item) { 589 SignalPublishVideoPauseResult(task_id); 590 } 591 592 void HangoutPubSubClient::OnVideoPausePublishError( 593 const std::string& task_id, const XmlElement* item, 594 const XmlElement* stanza) { 595 SignalPublishVideoPauseError(task_id, stanza); 596 } 597 598 void HangoutPubSubClient::OnRecordingStateChange( 599 const PubSubStateChange<bool>& change) { 600 SignalRecordingStateChange( 601 change.published_nick, change.old_state, change.new_state); 602 } 603 604 void HangoutPubSubClient::OnRecordingPublishResult( 605 const std::string& task_id, const XmlElement* item) { 606 SignalPublishRecordingResult(task_id); 607 } 608 609 void HangoutPubSubClient::OnRecordingPublishError( 610 const std::string& task_id, const XmlElement* item, 611 const XmlElement* stanza) { 612 SignalPublishRecordingError(task_id, stanza); 613 } 614 615 void HangoutPubSubClient::OnMediaBlockStateChange( 616 const PubSubStateChange<bool>& change) { 617 const std::string& blockee_nick = change.published_nick; 618 const std::string& blocker_nick = change.publisher_nick; 619 620 bool was_blockee = change.old_state; 621 bool is_blockee = change.new_state; 622 if (!was_blockee && is_blockee) { 623 SignalMediaBlock(blockee_nick, blocker_nick); 624 } 625 // TODO: Should we bother signaling unblock? Currently 626 // it isn't allowed, but it might happen when a participant leaves 627 // the room and the item is retracted. 628 } 629 630 void HangoutPubSubClient::OnMediaBlockPublishResult( 631 const std::string& task_id, const XmlElement* item) { 632 const std::string& blockee_nick = GetBlockeeNickFromItem(item); 633 SignalMediaBlockResult(task_id, blockee_nick); 634 } 635 636 void HangoutPubSubClient::OnMediaBlockPublishError( 637 const std::string& task_id, const XmlElement* item, 638 const XmlElement* stanza) { 639 const std::string& blockee_nick = GetBlockeeNickFromItem(item); 640 SignalMediaBlockError(task_id, blockee_nick, stanza); 641 } 642 643 } // namespace buzz 644