1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/cast_extension_session.h"
6
7#include "base/bind.h"
8#include "base/json/json_reader.h"
9#include "base/json/json_writer.h"
10#include "base/logging.h"
11#include "base/synchronization/waitable_event.h"
12#include "net/url_request/url_request_context_getter.h"
13#include "remoting/host/cast_video_capturer_adapter.h"
14#include "remoting/host/chromium_port_allocator_factory.h"
15#include "remoting/host/client_session.h"
16#include "remoting/proto/control.pb.h"
17#include "remoting/protocol/client_stub.h"
18#include "third_party/libjingle/source/talk/app/webrtc/mediastreaminterface.h"
19#include "third_party/libjingle/source/talk/app/webrtc/test/fakeconstraints.h"
20#include "third_party/libjingle/source/talk/app/webrtc/videosourceinterface.h"
21
22namespace remoting {
23
24// Used as the type attribute of all Cast protocol::ExtensionMessages.
25const char kExtensionMessageType[] = "cast_message";
26
27// Top-level keys used in all extension messages between host and client.
28// Must keep synced with webapp.
29const char kTopLevelData[] = "chromoting_data";
30const char kTopLevelSubject[] = "subject";
31
32// Keys used to describe the subject of a cast extension message. WebRTC-related
33// message subjects are prepended with "webrtc_".
34// Must keep synced with webapp.
35const char kSubjectReady[] = "ready";
36const char kSubjectTest[] = "test";
37const char kSubjectNewCandidate[] = "webrtc_candidate";
38const char kSubjectOffer[] = "webrtc_offer";
39const char kSubjectAnswer[] = "webrtc_answer";
40
41// WebRTC headers used inside messages with subject = "webrtc_*".
42const char kWebRtcCandidate[] = "candidate";
43const char kWebRtcSessionDescType[] = "type";
44const char kWebRtcSessionDescSDP[] = "sdp";
45const char kWebRtcSDPMid[] = "sdpMid";
46const char kWebRtcSDPMLineIndex[] = "sdpMLineIndex";
47
48// Media labels used over the PeerConnection.
49const char kVideoLabel[] = "cast_video_label";
50const char kStreamLabel[] = "stream_label";
51
52// Default STUN server used to construct
53// webrtc::PeerConnectionInterface::RTCConfiguration for the PeerConnection.
54const char kDefaultStunURI[] = "stun:stun.l.google.com:19302";
55
56const char kWorkerThreadName[] = "CastExtensionSessionWorkerThread";
57
58// Interval between each call to PollPeerConnectionStats().
59const int kStatsLogIntervalSec = 10;
60
61// Minimum frame rate for video streaming over the PeerConnection in frames per
62// second, added as a media constraint when constructing the video source for
63// the Peer Connection.
64const int kMinFramesPerSecond = 5;
65
66// A webrtc::SetSessionDescriptionObserver implementation used to receive the
67// results of setting local and remote descriptions of the PeerConnection.
68class CastSetSessionDescriptionObserver
69    : public webrtc::SetSessionDescriptionObserver {
70 public:
71  static CastSetSessionDescriptionObserver* Create() {
72    return new rtc::RefCountedObject<CastSetSessionDescriptionObserver>();
73  }
74  virtual void OnSuccess() OVERRIDE {
75    VLOG(1) << "Setting session description succeeded.";
76  }
77  virtual void OnFailure(const std::string& error) OVERRIDE {
78    LOG(ERROR) << "Setting session description failed: " << error;
79  }
80
81 protected:
82  CastSetSessionDescriptionObserver() {}
83  virtual ~CastSetSessionDescriptionObserver() {}
84
85  DISALLOW_COPY_AND_ASSIGN(CastSetSessionDescriptionObserver);
86};
87
88// A webrtc::CreateSessionDescriptionObserver implementation used to receive the
89// results of creating descriptions for this end of the PeerConnection.
90class CastCreateSessionDescriptionObserver
91    : public webrtc::CreateSessionDescriptionObserver {
92 public:
93  static CastCreateSessionDescriptionObserver* Create(
94      CastExtensionSession* session) {
95    return new rtc::RefCountedObject<CastCreateSessionDescriptionObserver>(
96        session);
97  }
98  virtual void OnSuccess(webrtc::SessionDescriptionInterface* desc) OVERRIDE {
99    if (cast_extension_session_ == NULL) {
100      LOG(ERROR)
101          << "No CastExtensionSession. Creating session description succeeded.";
102      return;
103    }
104    cast_extension_session_->OnCreateSessionDescription(desc);
105  }
106  virtual void OnFailure(const std::string& error) OVERRIDE {
107    if (cast_extension_session_ == NULL) {
108      LOG(ERROR)
109          << "No CastExtensionSession. Creating session description failed.";
110      return;
111    }
112    cast_extension_session_->OnCreateSessionDescriptionFailure(error);
113  }
114  void SetCastExtensionSession(CastExtensionSession* cast_extension_session) {
115    cast_extension_session_ = cast_extension_session;
116  }
117
118 protected:
119  explicit CastCreateSessionDescriptionObserver(CastExtensionSession* session)
120      : cast_extension_session_(session) {}
121  virtual ~CastCreateSessionDescriptionObserver() {}
122
123 private:
124  CastExtensionSession* cast_extension_session_;
125
126  DISALLOW_COPY_AND_ASSIGN(CastCreateSessionDescriptionObserver);
127};
128
129// A webrtc::StatsObserver implementation used to receive statistics about the
130// current PeerConnection.
131class CastStatsObserver : public webrtc::StatsObserver {
132 public:
133  static CastStatsObserver* Create() {
134    return new rtc::RefCountedObject<CastStatsObserver>();
135  }
136
137  virtual void OnComplete(
138      const std::vector<webrtc::StatsReport>& reports) OVERRIDE {
139    typedef webrtc::StatsReport::Values::iterator ValuesIterator;
140
141    VLOG(1) << "Received " << reports.size() << " new StatsReports.";
142
143    int index;
144    std::vector<webrtc::StatsReport>::const_iterator it;
145    for (it = reports.begin(), index = 0; it != reports.end(); ++it, ++index) {
146      webrtc::StatsReport::Values v = it->values;
147      VLOG(1) << "Report " << index << ":";
148      for (ValuesIterator vIt = v.begin(); vIt != v.end(); ++vIt) {
149        VLOG(1) << "Stat: " << vIt->name << "=" << vIt->value << ".";
150      }
151    }
152  }
153
154 protected:
155  CastStatsObserver() {}
156  virtual ~CastStatsObserver() {}
157
158  DISALLOW_COPY_AND_ASSIGN(CastStatsObserver);
159};
160
161// TODO(aiguha): Fix PeerConnnection-related tear down crash caused by premature
162// destruction of cricket::CaptureManager (which occurs on releasing
163// |peer_conn_factory_|). See crbug.com/403840.
164CastExtensionSession::~CastExtensionSession() {
165  DCHECK(caller_task_runner_->BelongsToCurrentThread());
166
167  // Explicitly clear |create_session_desc_observer_|'s pointer to |this|,
168  // since the CastExtensionSession is destructing. Otherwise,
169  // |create_session_desc_observer_| would be left with a dangling pointer.
170  create_session_desc_observer_->SetCastExtensionSession(NULL);
171
172  CleanupPeerConnection();
173}
174
175// static
176scoped_ptr<CastExtensionSession> CastExtensionSession::Create(
177    scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
178    scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
179    const protocol::NetworkSettings& network_settings,
180    ClientSessionControl* client_session_control,
181    protocol::ClientStub* client_stub) {
182  scoped_ptr<CastExtensionSession> cast_extension_session(
183      new CastExtensionSession(caller_task_runner,
184                               url_request_context_getter,
185                               network_settings,
186                               client_session_control,
187                               client_stub));
188  if (!cast_extension_session->WrapTasksAndSave()) {
189    return scoped_ptr<CastExtensionSession>();
190  }
191  if (!cast_extension_session->InitializePeerConnection()) {
192    return scoped_ptr<CastExtensionSession>();
193  }
194  return cast_extension_session.Pass();
195}
196
197void CastExtensionSession::OnCreateSessionDescription(
198    webrtc::SessionDescriptionInterface* desc) {
199  if (!caller_task_runner_->BelongsToCurrentThread()) {
200    caller_task_runner_->PostTask(
201        FROM_HERE,
202        base::Bind(&CastExtensionSession::OnCreateSessionDescription,
203                   base::Unretained(this),
204                   desc));
205    return;
206  }
207
208  peer_connection_->SetLocalDescription(
209      CastSetSessionDescriptionObserver::Create(), desc);
210
211  scoped_ptr<base::DictionaryValue> json(new base::DictionaryValue());
212  json->SetString(kWebRtcSessionDescType, desc->type());
213  std::string subject =
214      (desc->type() == "offer") ? kSubjectOffer : kSubjectAnswer;
215  std::string desc_str;
216  desc->ToString(&desc_str);
217  json->SetString(kWebRtcSessionDescSDP, desc_str);
218  std::string json_str;
219  if (!base::JSONWriter::Write(json.get(), &json_str)) {
220    LOG(ERROR) << "Failed to serialize sdp message.";
221    return;
222  }
223
224  SendMessageToClient(subject.c_str(), json_str);
225}
226
227void CastExtensionSession::OnCreateSessionDescriptionFailure(
228    const std::string& error) {
229  VLOG(1) << "Creating Session Description failed: " << error;
230}
231
232// TODO(aiguha): Support the case(s) where we've grabbed the capturer already,
233// but another extension reset the video pipeline. We should remove the
234// stream from the peer connection here, and then attempt to re-setup the
235// peer connection in the OnRenegotiationNeeded() callback.
236// See crbug.com/403843.
237void CastExtensionSession::OnCreateVideoCapturer(
238    scoped_ptr<webrtc::DesktopCapturer>* capturer) {
239  if (has_grabbed_capturer_) {
240    LOG(ERROR) << "The video pipeline was reset unexpectedly.";
241    has_grabbed_capturer_ = false;
242    peer_connection_->RemoveStream(stream_.release());
243    return;
244  }
245
246  if (received_offer_) {
247    has_grabbed_capturer_ = true;
248    if (SetupVideoStream(capturer->Pass())) {
249      peer_connection_->CreateAnswer(create_session_desc_observer_, NULL);
250    } else {
251      has_grabbed_capturer_ = false;
252      // Ignore the received offer, since we failed to setup a video stream.
253      received_offer_ = false;
254    }
255    return;
256  }
257}
258
259bool CastExtensionSession::ModifiesVideoPipeline() const {
260  return true;
261}
262
263// Returns true if the |message| is a Cast ExtensionMessage, even if
264// it was badly formed or a resulting action failed. This is done so that
265// the host does not continue to attempt to pass |message| to other
266// HostExtensionSessions.
267bool CastExtensionSession::OnExtensionMessage(
268    ClientSessionControl* client_session_control,
269    protocol::ClientStub* client_stub,
270    const protocol::ExtensionMessage& message) {
271  if (message.type() != kExtensionMessageType) {
272    return false;
273  }
274
275  scoped_ptr<base::Value> value(base::JSONReader::Read(message.data()));
276  base::DictionaryValue* client_message;
277  if (!(value && value->GetAsDictionary(&client_message))) {
278    LOG(ERROR) << "Could not read cast extension message.";
279    return true;
280  }
281
282  std::string subject;
283  if (!client_message->GetString(kTopLevelSubject, &subject)) {
284    LOG(ERROR) << "Invalid Cast Extension Message (missing subject header).";
285    return true;
286  }
287
288  if (subject == kSubjectOffer && !received_offer_) {
289    // Reset the video pipeline so we can grab the screen capturer and setup
290    // a video stream.
291    if (ParseAndSetRemoteDescription(client_message)) {
292      received_offer_ = true;
293      LOG(INFO) << "About to ResetVideoPipeline.";
294      client_session_control_->ResetVideoPipeline();
295
296    }
297  } else if (subject == kSubjectAnswer) {
298    ParseAndSetRemoteDescription(client_message);
299  } else if (subject == kSubjectNewCandidate) {
300    ParseAndAddICECandidate(client_message);
301  } else {
302    VLOG(1) << "Unexpected CastExtension Message: " << message.data();
303  }
304  return true;
305}
306
307// Private methods ------------------------------------------------------------
308
309CastExtensionSession::CastExtensionSession(
310    scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
311    scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
312    const protocol::NetworkSettings& network_settings,
313    ClientSessionControl* client_session_control,
314    protocol::ClientStub* client_stub)
315    : caller_task_runner_(caller_task_runner),
316      url_request_context_getter_(url_request_context_getter),
317      network_settings_(network_settings),
318      client_session_control_(client_session_control),
319      client_stub_(client_stub),
320      stats_observer_(CastStatsObserver::Create()),
321      received_offer_(false),
322      has_grabbed_capturer_(false),
323      signaling_thread_wrapper_(NULL),
324      worker_thread_wrapper_(NULL),
325      worker_thread_(kWorkerThreadName) {
326  DCHECK(caller_task_runner_->BelongsToCurrentThread());
327  DCHECK(url_request_context_getter_.get());
328  DCHECK(client_session_control_);
329  DCHECK(client_stub_);
330
331  // The worker thread is created with base::MessageLoop::TYPE_IO because
332  // the PeerConnection performs some port allocation operations on this thread
333  // that require it. See crbug.com/404013.
334  base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
335  worker_thread_.StartWithOptions(options);
336  worker_task_runner_ = worker_thread_.task_runner();
337}
338
339bool CastExtensionSession::ParseAndSetRemoteDescription(
340    base::DictionaryValue* message) {
341  DCHECK(peer_connection_.get() != NULL);
342
343  base::DictionaryValue* message_data;
344  if (!message->GetDictionary(kTopLevelData, &message_data)) {
345    LOG(ERROR) << "Invalid Cast Extension Message (missing data).";
346    return false;
347  }
348
349  std::string webrtc_type;
350  if (!message_data->GetString(kWebRtcSessionDescType, &webrtc_type)) {
351    LOG(ERROR)
352        << "Invalid Cast Extension Message (missing webrtc type header).";
353    return false;
354  }
355
356  std::string sdp;
357  if (!message_data->GetString(kWebRtcSessionDescSDP, &sdp)) {
358    LOG(ERROR) << "Invalid Cast Extension Message (missing webrtc sdp header).";
359    return false;
360  }
361
362  webrtc::SdpParseError error;
363  webrtc::SessionDescriptionInterface* session_description(
364      webrtc::CreateSessionDescription(webrtc_type, sdp, &error));
365
366  if (!session_description) {
367    LOG(ERROR) << "Invalid Cast Extension Message (could not parse sdp).";
368    VLOG(1) << "SdpParseError was: " << error.description;
369    return false;
370  }
371
372  peer_connection_->SetRemoteDescription(
373      CastSetSessionDescriptionObserver::Create(), session_description);
374  return true;
375}
376
377bool CastExtensionSession::ParseAndAddICECandidate(
378    base::DictionaryValue* message) {
379  DCHECK(peer_connection_.get() != NULL);
380
381  base::DictionaryValue* message_data;
382  if (!message->GetDictionary(kTopLevelData, &message_data)) {
383    LOG(ERROR) << "Invalid Cast Extension Message (missing data).";
384    return false;
385  }
386
387  std::string candidate_str;
388  std::string sdp_mid;
389  int sdp_mlineindex = 0;
390  if (!message_data->GetString(kWebRtcSDPMid, &sdp_mid) ||
391      !message_data->GetInteger(kWebRtcSDPMLineIndex, &sdp_mlineindex) ||
392      !message_data->GetString(kWebRtcCandidate, &candidate_str)) {
393    LOG(ERROR) << "Invalid Cast Extension Message (could not parse).";
394    return false;
395  }
396
397  rtc::scoped_ptr<webrtc::IceCandidateInterface> candidate(
398      webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate_str));
399  if (!candidate.get()) {
400    LOG(ERROR)
401        << "Invalid Cast Extension Message (could not create candidate).";
402    return false;
403  }
404
405  if (!peer_connection_->AddIceCandidate(candidate.get())) {
406    LOG(ERROR) << "Failed to apply received ICE Candidate to PeerConnection.";
407    return false;
408  }
409
410  VLOG(1) << "Received and Added ICE Candidate: " << candidate_str;
411
412  return true;
413}
414
415bool CastExtensionSession::SendMessageToClient(const std::string& subject,
416                                               const std::string& data) {
417  DCHECK(caller_task_runner_->BelongsToCurrentThread());
418
419  if (client_stub_ == NULL) {
420    LOG(ERROR) << "No Client Stub. Cannot send message to client.";
421    return false;
422  }
423
424  base::DictionaryValue message_dict;
425  message_dict.SetString(kTopLevelSubject, subject);
426  message_dict.SetString(kTopLevelData, data);
427  std::string message_json;
428
429  if (!base::JSONWriter::Write(&message_dict, &message_json)) {
430    LOG(ERROR) << "Failed to serialize JSON message.";
431    return false;
432  }
433
434  protocol::ExtensionMessage message;
435  message.set_type(kExtensionMessageType);
436  message.set_data(message_json);
437  client_stub_->DeliverHostMessage(message);
438  return true;
439}
440
441void CastExtensionSession::EnsureTaskAndSetSend(rtc::Thread** ptr,
442                                                base::WaitableEvent* event) {
443  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
444  jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
445  *ptr = jingle_glue::JingleThreadWrapper::current();
446
447  if (event != NULL) {
448    event->Signal();
449  }
450}
451
452bool CastExtensionSession::WrapTasksAndSave() {
453  DCHECK(caller_task_runner_->BelongsToCurrentThread());
454
455  EnsureTaskAndSetSend(&signaling_thread_wrapper_);
456  if (signaling_thread_wrapper_ == NULL)
457    return false;
458
459  base::WaitableEvent wrap_worker_thread_event(true, false);
460  worker_task_runner_->PostTask(
461      FROM_HERE,
462      base::Bind(&CastExtensionSession::EnsureTaskAndSetSend,
463                 base::Unretained(this),
464                 &worker_thread_wrapper_,
465                 &wrap_worker_thread_event));
466  wrap_worker_thread_event.Wait();
467
468  return (worker_thread_wrapper_ != NULL);
469}
470
471bool CastExtensionSession::InitializePeerConnection() {
472  DCHECK(caller_task_runner_->BelongsToCurrentThread());
473  DCHECK(!peer_conn_factory_);
474  DCHECK(!peer_connection_);
475  DCHECK(worker_thread_wrapper_ != NULL);
476  DCHECK(signaling_thread_wrapper_ != NULL);
477
478  peer_conn_factory_ = webrtc::CreatePeerConnectionFactory(
479      worker_thread_wrapper_, signaling_thread_wrapper_, NULL, NULL, NULL);
480
481  if (!peer_conn_factory_.get()) {
482    CleanupPeerConnection();
483    return false;
484  }
485
486  VLOG(1) << "Created PeerConnectionFactory successfully.";
487
488  webrtc::PeerConnectionInterface::IceServers servers;
489  webrtc::PeerConnectionInterface::IceServer server;
490  server.uri = kDefaultStunURI;
491  servers.push_back(server);
492  webrtc::PeerConnectionInterface::RTCConfiguration rtc_config;
493  rtc_config.servers = servers;
494
495  // DTLS-SRTP is the preferred encryption method. If set to kValueFalse, the
496  // peer connection uses SDES. Disabling SDES as well will cause the peer
497  // connection to fail to connect.
498  // Note: For protection and unprotection of SRTP packets, the libjingle
499  // ENABLE_EXTERNAL_AUTH flag must not be set.
500  webrtc::FakeConstraints constraints;
501  constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
502                           webrtc::MediaConstraintsInterface::kValueTrue);
503
504  rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface>
505      port_allocator_factory = ChromiumPortAllocatorFactory::Create(
506          network_settings_, url_request_context_getter_);
507
508  peer_connection_ = peer_conn_factory_->CreatePeerConnection(
509      rtc_config, &constraints, port_allocator_factory, NULL, this);
510
511  if (!peer_connection_.get()) {
512    CleanupPeerConnection();
513    return false;
514  }
515
516  VLOG(1) << "Created PeerConnection successfully.";
517
518  create_session_desc_observer_ =
519      CastCreateSessionDescriptionObserver::Create(this);
520
521  // Send a test message to the client. Then, notify the client to start
522  // webrtc offer/answer negotiation.
523  if (!SendMessageToClient(kSubjectTest, "Hello, client.") ||
524      !SendMessageToClient(kSubjectReady, "Host ready to receive offers.")) {
525    LOG(ERROR) << "Failed to send messages to client.";
526    return false;
527  }
528
529  return true;
530}
531
532bool CastExtensionSession::SetupVideoStream(
533    scoped_ptr<webrtc::DesktopCapturer> desktop_capturer) {
534  DCHECK(caller_task_runner_->BelongsToCurrentThread());
535  DCHECK(desktop_capturer);
536
537  if (stream_) {
538    VLOG(1) << "Already added MediaStream. Aborting Setup.";
539    return false;
540  }
541
542  scoped_ptr<CastVideoCapturerAdapter> cast_video_capturer_adapter(
543      new CastVideoCapturerAdapter(desktop_capturer.Pass()));
544
545  // Set video stream constraints.
546  webrtc::FakeConstraints video_constraints;
547  video_constraints.AddMandatory(
548      webrtc::MediaConstraintsInterface::kMinFrameRate, kMinFramesPerSecond);
549
550  rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track =
551      peer_conn_factory_->CreateVideoTrack(
552          kVideoLabel,
553          peer_conn_factory_->CreateVideoSource(
554              cast_video_capturer_adapter.release(), &video_constraints));
555
556  stream_ = peer_conn_factory_->CreateLocalMediaStream(kStreamLabel);
557
558  if (!stream_->AddTrack(video_track) ||
559      !peer_connection_->AddStream(stream_, NULL)) {
560    return false;
561  }
562
563  VLOG(1) << "Setup video stream successfully.";
564
565  return true;
566}
567
568void CastExtensionSession::PollPeerConnectionStats() {
569  if (!connection_active()) {
570    VLOG(1) << "Cannot poll stats while PeerConnection is inactive.";
571  }
572  rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> video_track =
573      stream_->FindVideoTrack(kVideoLabel);
574  peer_connection_->GetStats(
575      stats_observer_,
576      video_track.release(),
577      webrtc::PeerConnectionInterface::kStatsOutputLevelStandard);
578}
579
580void CastExtensionSession::CleanupPeerConnection() {
581  peer_connection_->Close();
582  peer_connection_ = NULL;
583  stream_ = NULL;
584  peer_conn_factory_ = NULL;
585  worker_thread_.Stop();
586}
587
588bool CastExtensionSession::connection_active() const {
589  return peer_connection_.get() != NULL;
590}
591
592// webrtc::PeerConnectionObserver implementation -------------------------------
593
594void CastExtensionSession::OnError() {
595  VLOG(1) << "PeerConnectionObserver: an error occurred.";
596}
597
598void CastExtensionSession::OnSignalingChange(
599    webrtc::PeerConnectionInterface::SignalingState new_state) {
600  VLOG(1) << "PeerConnectionObserver: SignalingState changed to:" << new_state;
601}
602
603void CastExtensionSession::OnStateChange(
604    webrtc::PeerConnectionObserver::StateType state_changed) {
605  VLOG(1) << "PeerConnectionObserver: StateType changed to: " << state_changed;
606}
607
608void CastExtensionSession::OnAddStream(webrtc::MediaStreamInterface* stream) {
609  VLOG(1) << "PeerConnectionObserver: stream added: " << stream->label();
610}
611
612void CastExtensionSession::OnRemoveStream(
613    webrtc::MediaStreamInterface* stream) {
614  VLOG(1) << "PeerConnectionObserver: stream removed: " << stream->label();
615}
616
617void CastExtensionSession::OnDataChannel(
618    webrtc::DataChannelInterface* data_channel) {
619  VLOG(1) << "PeerConnectionObserver: data channel: " << data_channel->label();
620}
621
622void CastExtensionSession::OnRenegotiationNeeded() {
623  VLOG(1) << "PeerConnectionObserver: renegotiation needed.";
624}
625
626void CastExtensionSession::OnIceConnectionChange(
627    webrtc::PeerConnectionInterface::IceConnectionState new_state) {
628  VLOG(1) << "PeerConnectionObserver: IceConnectionState changed to: "
629          << new_state;
630
631  // TODO(aiguha): Maybe start timer only if enabled by command-line flag or
632  // at a particular verbosity level.
633  if (!stats_polling_timer_.IsRunning() &&
634      new_state == webrtc::PeerConnectionInterface::kIceConnectionConnected) {
635    stats_polling_timer_.Start(
636        FROM_HERE,
637        base::TimeDelta::FromSeconds(kStatsLogIntervalSec),
638        this,
639        &CastExtensionSession::PollPeerConnectionStats);
640  }
641}
642
643void CastExtensionSession::OnIceGatheringChange(
644    webrtc::PeerConnectionInterface::IceGatheringState new_state) {
645  VLOG(1) << "PeerConnectionObserver: IceGatheringState changed to: "
646          << new_state;
647}
648
649void CastExtensionSession::OnIceComplete() {
650  VLOG(1) << "PeerConnectionObserver: all ICE candidates found.";
651}
652
653void CastExtensionSession::OnIceCandidate(
654    const webrtc::IceCandidateInterface* candidate) {
655  std::string candidate_str;
656  if (!candidate->ToString(&candidate_str)) {
657    LOG(ERROR) << "PeerConnectionObserver: failed to serialize candidate.";
658    return;
659  }
660  scoped_ptr<base::DictionaryValue> json(new base::DictionaryValue());
661  json->SetString(kWebRtcSDPMid, candidate->sdp_mid());
662  json->SetInteger(kWebRtcSDPMLineIndex, candidate->sdp_mline_index());
663  json->SetString(kWebRtcCandidate, candidate_str);
664  std::string json_str;
665  if (!base::JSONWriter::Write(json.get(), &json_str)) {
666    LOG(ERROR) << "Failed to serialize candidate message.";
667    return;
668  }
669  SendMessageToClient(kSubjectNewCandidate, json_str);
670}
671
672}  // namespace remoting
673