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 40namespace buzz { 41 42namespace { 43const char kPresenting[] = "s"; 44const char kNotPresenting[] = "o"; 45 46} // namespace 47 48// A simple serialiazer where presence of item => true, lack of item 49// => false. 50class BoolStateSerializer : public PubSubStateSerializer<bool> { 51 virtual XmlElement* Write(const QName& state_name, const bool& state) { 52 if (!state) { 53 return NULL; 54 } 55 56 return new XmlElement(state_name, true); 57 } 58 59 virtual void Parse(const XmlElement* state_elem, bool *state_out) { 60 *state_out = state_elem != NULL; 61 } 62}; 63 64class PresenterStateClient : public PubSubStateClient<bool> { 65 public: 66 PresenterStateClient(const std::string& publisher_nick, 67 PubSubClient* client, 68 const QName& state_name, 69 bool default_state) 70 : PubSubStateClient<bool>( 71 publisher_nick, client, state_name, default_state, 72 new PublishedNickKeySerializer(), NULL) { 73 } 74 75 virtual void Publish(const std::string& published_nick, 76 const bool& state, 77 std::string* task_id_out) { 78 XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true); 79 presenter_elem->AddAttr(QN_NICK, published_nick); 80 81 XmlElement* presentation_item_elem = 82 new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false); 83 const std::string& presentation_type = state ? kPresenting : kNotPresenting; 84 presentation_item_elem->AddAttr( 85 QN_PRESENTER_PRESENTATION_TYPE, presentation_type); 86 87 // The Presenter state is kind of dumb in that it doesn't always use 88 // retracts. It relies on setting the "type" to a special value. 89 std::string itemid = published_nick; 90 std::vector<XmlElement*> children; 91 children.push_back(presenter_elem); 92 children.push_back(presentation_item_elem); 93 client()->PublishItem(itemid, children, task_id_out); 94 } 95 96 protected: 97 virtual bool ParseStateItem(const PubSubItem& item, 98 StateItemInfo* info_out, 99 bool* state_out) { 100 const XmlElement* presenter_elem = 101 item.elem->FirstNamed(QN_PRESENTER_PRESENTER); 102 const XmlElement* presentation_item_elem = 103 item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM); 104 if (presentation_item_elem == NULL || presenter_elem == NULL) { 105 return false; 106 } 107 108 info_out->publisher_nick = 109 client()->GetPublisherNickFromPubSubItem(item.elem); 110 info_out->published_nick = presenter_elem->Attr(QN_NICK); 111 *state_out = (presentation_item_elem->Attr( 112 QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting); 113 return true; 114 } 115 116 virtual bool StatesEqual(const bool& state1, const bool& state2) { 117 // Make every item trigger an event, even if state doesn't change. 118 return false; 119 } 120}; 121 122HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent, 123 const Jid& mucjid, 124 const std::string& nick) 125 : mucjid_(mucjid), 126 nick_(nick) { 127 presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER)); 128 presenter_client_->SignalRequestError.connect( 129 this, &HangoutPubSubClient::OnPresenterRequestError); 130 131 media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA)); 132 media_client_->SignalRequestError.connect( 133 this, &HangoutPubSubClient::OnMediaRequestError); 134 135 presenter_state_client_.reset(new PresenterStateClient( 136 nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false)); 137 presenter_state_client_->SignalStateChange.connect( 138 this, &HangoutPubSubClient::OnPresenterStateChange); 139 presenter_state_client_->SignalPublishResult.connect( 140 this, &HangoutPubSubClient::OnPresenterPublishResult); 141 presenter_state_client_->SignalPublishError.connect( 142 this, &HangoutPubSubClient::OnPresenterPublishError); 143 144 audio_mute_state_client_.reset(new PubSubStateClient<bool>( 145 nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false, 146 new PublishedNickKeySerializer(), new BoolStateSerializer())); 147 // Can't just repeat because we need to watch for remote mutes. 148 audio_mute_state_client_->SignalStateChange.connect( 149 this, &HangoutPubSubClient::OnAudioMuteStateChange); 150 audio_mute_state_client_->SignalPublishResult.connect( 151 this, &HangoutPubSubClient::OnAudioMutePublishResult); 152 audio_mute_state_client_->SignalPublishError.connect( 153 this, &HangoutPubSubClient::OnAudioMutePublishError); 154 155 video_mute_state_client_.reset(new PubSubStateClient<bool>( 156 nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false, 157 new PublishedNickKeySerializer(), new BoolStateSerializer())); 158 // Can't just repeat because we need to watch for remote mutes. 159 video_mute_state_client_->SignalStateChange.connect( 160 this, &HangoutPubSubClient::OnVideoMuteStateChange); 161 video_mute_state_client_->SignalPublishResult.connect( 162 this, &HangoutPubSubClient::OnVideoMutePublishResult); 163 video_mute_state_client_->SignalPublishError.connect( 164 this, &HangoutPubSubClient::OnVideoMutePublishError); 165 166 video_pause_state_client_.reset(new PubSubStateClient<bool>( 167 nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false, 168 new PublishedNickKeySerializer(), new BoolStateSerializer())); 169 video_pause_state_client_->SignalStateChange.connect( 170 this, &HangoutPubSubClient::OnVideoPauseStateChange); 171 video_pause_state_client_->SignalPublishResult.connect( 172 this, &HangoutPubSubClient::OnVideoPausePublishResult); 173 video_pause_state_client_->SignalPublishError.connect( 174 this, &HangoutPubSubClient::OnVideoPausePublishError); 175 176 recording_state_client_.reset(new PubSubStateClient<bool>( 177 nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false, 178 new PublishedNickKeySerializer(), new BoolStateSerializer())); 179 recording_state_client_->SignalStateChange.connect( 180 this, &HangoutPubSubClient::OnRecordingStateChange); 181 recording_state_client_->SignalPublishResult.connect( 182 this, &HangoutPubSubClient::OnRecordingPublishResult); 183 recording_state_client_->SignalPublishError.connect( 184 this, &HangoutPubSubClient::OnRecordingPublishError); 185 186 media_block_state_client_.reset(new PubSubStateClient<bool>( 187 nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false, 188 new PublisherAndPublishedNicksKeySerializer(), 189 new BoolStateSerializer())); 190 media_block_state_client_->SignalStateChange.connect( 191 this, &HangoutPubSubClient::OnMediaBlockStateChange); 192 media_block_state_client_->SignalPublishResult.connect( 193 this, &HangoutPubSubClient::OnMediaBlockPublishResult); 194 media_block_state_client_->SignalPublishError.connect( 195 this, &HangoutPubSubClient::OnMediaBlockPublishError); 196} 197 198HangoutPubSubClient::~HangoutPubSubClient() { 199} 200 201void HangoutPubSubClient::RequestAll() { 202 presenter_client_->RequestItems(); 203 media_client_->RequestItems(); 204} 205 206void HangoutPubSubClient::OnPresenterRequestError( 207 PubSubClient* client, const XmlElement* stanza) { 208 SignalRequestError(client->node(), stanza); 209} 210 211void HangoutPubSubClient::OnMediaRequestError( 212 PubSubClient* client, const XmlElement* stanza) { 213 SignalRequestError(client->node(), stanza); 214} 215 216void HangoutPubSubClient::PublishPresenterState( 217 bool presenting, std::string* task_id_out) { 218 presenter_state_client_->Publish(nick_, presenting, task_id_out); 219} 220 221void HangoutPubSubClient::PublishAudioMuteState( 222 bool muted, std::string* task_id_out) { 223 audio_mute_state_client_->Publish(nick_, muted, task_id_out); 224} 225 226void HangoutPubSubClient::PublishVideoMuteState( 227 bool muted, std::string* task_id_out) { 228 video_mute_state_client_->Publish(nick_, muted, task_id_out); 229} 230 231void HangoutPubSubClient::PublishVideoPauseState( 232 bool paused, std::string* task_id_out) { 233 video_pause_state_client_->Publish(nick_, paused, task_id_out); 234} 235 236void HangoutPubSubClient::PublishRecordingState( 237 bool recording, std::string* task_id_out) { 238 recording_state_client_->Publish(nick_, recording, task_id_out); 239} 240 241// Remote mute is accomplished by setting another client's mute state. 242void HangoutPubSubClient::RemoteMute( 243 const std::string& mutee_nick, std::string* task_id_out) { 244 audio_mute_state_client_->Publish(mutee_nick, true, task_id_out); 245} 246 247// Block media is accomplished by setting another client's block 248// state, kind of like remote mute. 249void HangoutPubSubClient::BlockMedia( 250 const std::string& blockee_nick, std::string* task_id_out) { 251 media_block_state_client_->Publish(blockee_nick, true, task_id_out); 252} 253 254void HangoutPubSubClient::OnPresenterStateChange( 255 const PubSubStateChange<bool>& change) { 256 SignalPresenterStateChange( 257 change.published_nick, change.old_state, change.new_state); 258} 259 260void HangoutPubSubClient::OnPresenterPublishResult( 261 const std::string& task_id, const XmlElement* item) { 262 SignalPublishPresenterResult(task_id); 263} 264 265void HangoutPubSubClient::OnPresenterPublishError( 266 const std::string& task_id, const XmlElement* item, 267 const XmlElement* stanza) { 268 SignalPublishPresenterError(task_id, stanza); 269} 270 271// Since a remote mute is accomplished by another client setting our 272// mute state, if our state changes to muted, we should mute ourselves. 273// Note that remote un-muting is disallowed by the RoomServer. 274void HangoutPubSubClient::OnAudioMuteStateChange( 275 const PubSubStateChange<bool>& change) { 276 bool was_muted = change.old_state; 277 bool is_muted = change.new_state; 278 bool remote_action = (!change.publisher_nick.empty() && 279 (change.publisher_nick != change.published_nick)); 280 281 if (remote_action) { 282 const std::string& mutee_nick = change.published_nick; 283 const std::string& muter_nick = change.publisher_nick; 284 if (!is_muted) { 285 // The server should prevent remote un-mute. 286 LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick; 287 return; 288 } 289 bool should_mute_locally = (mutee_nick == nick_); 290 SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally); 291 } 292 SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted); 293} 294 295const std::string GetAudioMuteNickFromItem(const XmlElement* item) { 296 if (item != NULL) { 297 const XmlElement* audio_mute_state = 298 item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE); 299 if (audio_mute_state != NULL) { 300 return audio_mute_state->Attr(QN_NICK); 301 } 302 } 303 return std::string(); 304} 305 306const std::string GetBlockeeNickFromItem(const XmlElement* item) { 307 if (item != NULL) { 308 const XmlElement* media_block_state = 309 item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK); 310 if (media_block_state != NULL) { 311 return media_block_state->Attr(QN_NICK); 312 } 313 } 314 return std::string(); 315} 316 317void HangoutPubSubClient::OnAudioMutePublishResult( 318 const std::string& task_id, const XmlElement* item) { 319 const std::string& mutee_nick = GetAudioMuteNickFromItem(item); 320 if (mutee_nick != nick_) { 321 SignalRemoteMuteResult(task_id, mutee_nick); 322 } else { 323 SignalPublishAudioMuteResult(task_id); 324 } 325} 326 327void HangoutPubSubClient::OnAudioMutePublishError( 328 const std::string& task_id, const XmlElement* item, 329 const XmlElement* stanza) { 330 const std::string& mutee_nick = GetAudioMuteNickFromItem(item); 331 if (mutee_nick != nick_) { 332 SignalRemoteMuteError(task_id, mutee_nick, stanza); 333 } else { 334 SignalPublishAudioMuteError(task_id, stanza); 335 } 336} 337 338void HangoutPubSubClient::OnVideoMuteStateChange( 339 const PubSubStateChange<bool>& change) { 340 SignalVideoMuteStateChange( 341 change.published_nick, change.old_state, change.new_state); 342} 343 344void HangoutPubSubClient::OnVideoMutePublishResult( 345 const std::string& task_id, const XmlElement* item) { 346 SignalPublishVideoMuteResult(task_id); 347} 348 349void HangoutPubSubClient::OnVideoMutePublishError( 350 const std::string& task_id, const XmlElement* item, 351 const XmlElement* stanza) { 352 SignalPublishVideoMuteError(task_id, stanza); 353} 354 355void HangoutPubSubClient::OnVideoPauseStateChange( 356 const PubSubStateChange<bool>& change) { 357 SignalVideoPauseStateChange( 358 change.published_nick, change.old_state, change.new_state); 359} 360 361void HangoutPubSubClient::OnVideoPausePublishResult( 362 const std::string& task_id, const XmlElement* item) { 363 SignalPublishVideoPauseResult(task_id); 364} 365 366void HangoutPubSubClient::OnVideoPausePublishError( 367 const std::string& task_id, const XmlElement* item, 368 const XmlElement* stanza) { 369 SignalPublishVideoPauseError(task_id, stanza); 370} 371 372void HangoutPubSubClient::OnRecordingStateChange( 373 const PubSubStateChange<bool>& change) { 374 SignalRecordingStateChange( 375 change.published_nick, change.old_state, change.new_state); 376} 377 378void HangoutPubSubClient::OnRecordingPublishResult( 379 const std::string& task_id, const XmlElement* item) { 380 SignalPublishRecordingResult(task_id); 381} 382 383void HangoutPubSubClient::OnRecordingPublishError( 384 const std::string& task_id, const XmlElement* item, 385 const XmlElement* stanza) { 386 SignalPublishRecordingError(task_id, stanza); 387} 388 389void HangoutPubSubClient::OnMediaBlockStateChange( 390 const PubSubStateChange<bool>& change) { 391 const std::string& blockee_nick = change.published_nick; 392 const std::string& blocker_nick = change.publisher_nick; 393 394 bool was_blockee = change.old_state; 395 bool is_blockee = change.new_state; 396 if (!was_blockee && is_blockee) { 397 SignalMediaBlock(blockee_nick, blocker_nick); 398 } 399 // TODO: Should we bother signaling unblock? Currently 400 // it isn't allowed, but it might happen when a participant leaves 401 // the room and the item is retracted. 402} 403 404void HangoutPubSubClient::OnMediaBlockPublishResult( 405 const std::string& task_id, const XmlElement* item) { 406 const std::string& blockee_nick = GetBlockeeNickFromItem(item); 407 SignalMediaBlockResult(task_id, blockee_nick); 408} 409 410void HangoutPubSubClient::OnMediaBlockPublishError( 411 const std::string& task_id, const XmlElement* item, 412 const XmlElement* stanza) { 413 const std::string& blockee_nick = GetBlockeeNickFromItem(item); 414 SignalMediaBlockError(task_id, blockee_nick, stanza); 415} 416 417} // namespace buzz 418