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