• 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 "webrtc/libjingle/xmllite/qname.h"
31 #include "webrtc/libjingle/xmllite/xmlelement.h"
32 #include "talk/xmpp/constants.h"
33 #include "talk/xmpp/jid.h"
34 #include "webrtc/base/logging.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 
46 }  // namespace
47 
48 // A simple serialiazer where presence of item => true, lack of item
49 // => false.
50 class BoolStateSerializer : public PubSubStateSerializer<bool> {
Write(const QName & state_name,const bool & state)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 
Parse(const XmlElement * state_elem,bool * state_out)59   virtual void Parse(const XmlElement* state_elem, bool *state_out) {
60     *state_out = state_elem != NULL;
61   }
62 };
63 
64 class PresenterStateClient : public PubSubStateClient<bool> {
65  public:
PresenterStateClient(const std::string & publisher_nick,PubSubClient * client,const QName & state_name,bool default_state)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 
Publish(const std::string & published_nick,const bool & state,std::string * task_id_out)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:
ParseStateItem(const PubSubItem & item,StateItemInfo * info_out,bool * state_out)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 
StatesEqual(const bool & state1,const bool & state2)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 
HangoutPubSubClient(XmppTaskParentInterface * parent,const Jid & mucjid,const std::string & nick)122 HangoutPubSubClient::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 
~HangoutPubSubClient()198 HangoutPubSubClient::~HangoutPubSubClient() {
199 }
200 
RequestAll()201 void HangoutPubSubClient::RequestAll() {
202   presenter_client_->RequestItems();
203   media_client_->RequestItems();
204 }
205 
OnPresenterRequestError(PubSubClient * client,const XmlElement * stanza)206 void HangoutPubSubClient::OnPresenterRequestError(
207     PubSubClient* client, const XmlElement* stanza) {
208   SignalRequestError(client->node(), stanza);
209 }
210 
OnMediaRequestError(PubSubClient * client,const XmlElement * stanza)211 void HangoutPubSubClient::OnMediaRequestError(
212     PubSubClient* client, const XmlElement* stanza) {
213   SignalRequestError(client->node(), stanza);
214 }
215 
PublishPresenterState(bool presenting,std::string * task_id_out)216 void HangoutPubSubClient::PublishPresenterState(
217     bool presenting, std::string* task_id_out) {
218   presenter_state_client_->Publish(nick_, presenting, task_id_out);
219 }
220 
PublishAudioMuteState(bool muted,std::string * task_id_out)221 void HangoutPubSubClient::PublishAudioMuteState(
222     bool muted, std::string* task_id_out) {
223   audio_mute_state_client_->Publish(nick_, muted, task_id_out);
224 }
225 
PublishVideoMuteState(bool muted,std::string * task_id_out)226 void HangoutPubSubClient::PublishVideoMuteState(
227     bool muted, std::string* task_id_out) {
228   video_mute_state_client_->Publish(nick_, muted, task_id_out);
229 }
230 
PublishVideoPauseState(bool paused,std::string * task_id_out)231 void HangoutPubSubClient::PublishVideoPauseState(
232     bool paused, std::string* task_id_out) {
233   video_pause_state_client_->Publish(nick_, paused, task_id_out);
234 }
235 
PublishRecordingState(bool recording,std::string * task_id_out)236 void 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.
RemoteMute(const std::string & mutee_nick,std::string * task_id_out)242 void 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.
BlockMedia(const std::string & blockee_nick,std::string * task_id_out)249 void 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 
OnPresenterStateChange(const PubSubStateChange<bool> & change)254 void HangoutPubSubClient::OnPresenterStateChange(
255     const PubSubStateChange<bool>& change) {
256   SignalPresenterStateChange(
257       change.published_nick, change.old_state, change.new_state);
258 }
259 
OnPresenterPublishResult(const std::string & task_id,const XmlElement * item)260 void HangoutPubSubClient::OnPresenterPublishResult(
261     const std::string& task_id, const XmlElement* item) {
262   SignalPublishPresenterResult(task_id);
263 }
264 
OnPresenterPublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)265 void 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.
OnAudioMuteStateChange(const PubSubStateChange<bool> & change)274 void 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 
GetAudioMuteNickFromItem(const XmlElement * item)295 const 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 
GetBlockeeNickFromItem(const XmlElement * item)306 const 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 
OnAudioMutePublishResult(const std::string & task_id,const XmlElement * item)317 void 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 
OnAudioMutePublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)327 void 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 
OnVideoMuteStateChange(const PubSubStateChange<bool> & change)338 void HangoutPubSubClient::OnVideoMuteStateChange(
339     const PubSubStateChange<bool>& change) {
340   SignalVideoMuteStateChange(
341       change.published_nick, change.old_state, change.new_state);
342 }
343 
OnVideoMutePublishResult(const std::string & task_id,const XmlElement * item)344 void HangoutPubSubClient::OnVideoMutePublishResult(
345     const std::string& task_id, const XmlElement* item) {
346   SignalPublishVideoMuteResult(task_id);
347 }
348 
OnVideoMutePublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)349 void HangoutPubSubClient::OnVideoMutePublishError(
350     const std::string& task_id, const XmlElement* item,
351     const XmlElement* stanza) {
352   SignalPublishVideoMuteError(task_id, stanza);
353 }
354 
OnVideoPauseStateChange(const PubSubStateChange<bool> & change)355 void HangoutPubSubClient::OnVideoPauseStateChange(
356     const PubSubStateChange<bool>& change) {
357   SignalVideoPauseStateChange(
358       change.published_nick, change.old_state, change.new_state);
359 }
360 
OnVideoPausePublishResult(const std::string & task_id,const XmlElement * item)361 void HangoutPubSubClient::OnVideoPausePublishResult(
362     const std::string& task_id, const XmlElement* item) {
363   SignalPublishVideoPauseResult(task_id);
364 }
365 
OnVideoPausePublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)366 void HangoutPubSubClient::OnVideoPausePublishError(
367     const std::string& task_id, const XmlElement* item,
368     const XmlElement* stanza) {
369   SignalPublishVideoPauseError(task_id, stanza);
370 }
371 
OnRecordingStateChange(const PubSubStateChange<bool> & change)372 void HangoutPubSubClient::OnRecordingStateChange(
373     const PubSubStateChange<bool>& change) {
374   SignalRecordingStateChange(
375       change.published_nick, change.old_state, change.new_state);
376 }
377 
OnRecordingPublishResult(const std::string & task_id,const XmlElement * item)378 void HangoutPubSubClient::OnRecordingPublishResult(
379     const std::string& task_id, const XmlElement* item) {
380   SignalPublishRecordingResult(task_id);
381 }
382 
OnRecordingPublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)383 void HangoutPubSubClient::OnRecordingPublishError(
384     const std::string& task_id, const XmlElement* item,
385     const XmlElement* stanza) {
386   SignalPublishRecordingError(task_id, stanza);
387 }
388 
OnMediaBlockStateChange(const PubSubStateChange<bool> & change)389 void 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 
OnMediaBlockPublishResult(const std::string & task_id,const XmlElement * item)404 void 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 
OnMediaBlockPublishError(const std::string & task_id,const XmlElement * item,const XmlElement * stanza)410 void 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