Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(204)

Side by Side Diff: remoting/host/cast_extension_session.cc

Issue 399253002: CastExtension Impl for Chromoting Host (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Minor fix Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/callback.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/json_writer.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/synchronization/waitable_event.h"
14 #include "net/url_request/url_request_context_getter.h"
15 #include "remoting/host/chromium_port_allocator_factory.h"
16 // #include "remoting/host/cast_video_capturer.h"
17 #include "remoting/host/client_session.h"
18 #include "remoting/proto/control.pb.h"
19 #include "third_party/libjingle/source/talk/app/webrtc/peerconnectioninterface.h "
20 #include "third_party/libjingle/source/talk/app/webrtc/test/fakeconstraints.h"
21 #include "third_party/libjingle/source/talk/app/webrtc/videosourceinterface.h"
22 #include "third_party/webrtc/modules/desktop_capture/mouse_cursor_shape.h"
23
24 namespace remoting {
25
26 // Constant keys used in JSON messages to the host.
27 // Must keep synced with webapp.
28 const char kMessageData[] = "data"; // Use chromoting_data for CC v2.
29 const char kMessageSubject[] = "subject";
30 const char kMessageType[] = "cast_message";
31
32 const char kSubjectNewCandidate[] = "webrtc_candidate";
33 const char kSubjectReady[] = "ready";
34 const char kSubjectSDP[] = "webrtc_sdp";
35 const char kSubjectTest[] = "test";
36
37 const char kWebRtcPrefix[] = "webrtc_";
38
39 // WebRTC Headers inside cast extension messages.
40 const char kWebRtcCandidate[] = "candidate";
41 const char kWebRtcSessionDescType[] = "type";
42 const char kWebRtcSessionDescSDP[] = "sdp";
43 const char kWebRtcSDPMid[] = "sdpMid";
44 const char kWebRtcSDPMLineIndex[] = "sdpMLineIndex";
45
46 // Constants used by PeerConnection.
47 const char kVideoLabel[] = "cast_video_label";
48 const char kStreamLabel[] = "stream_label";
49 const char kDefaultStunURI[] = "stun:stun.l.google.com:19302";
50
51 //------------------------------------------------------------------------------
52
53 // webrtc::SetSessionDescriptionObserver implementation.
54 class CastSetSessionDescriptionObserver
55 : public webrtc::SetSessionDescriptionObserver {
56 public:
57 static CastSetSessionDescriptionObserver* Create() {
58 return new talk_base::RefCountedObject<CastSetSessionDescriptionObserver>();
59 }
60 virtual void OnSuccess() OVERRIDE {
61 VLOG(1) << "SetSessionDescriptionObserver success.";
62 }
63 virtual void OnFailure(const std::string& error) OVERRIDE {
64 LOG(ERROR) << "CastSetSessionDescriptionObserver" << __FUNCTION__ << " "
65 << error;
66 }
67
68 protected:
69 CastSetSessionDescriptionObserver() {}
70 virtual ~CastSetSessionDescriptionObserver() {}
71 };
72
73 // webrtc::CreateSessionDescriptionObserver implementation.
74 class CastCreateSessionDescriptionObserver
75 : public webrtc::CreateSessionDescriptionObserver {
76 public:
77 static CastCreateSessionDescriptionObserver* Create(
78 CastExtensionSession* session) {
79 return new talk_base::RefCountedObject<
80 CastCreateSessionDescriptionObserver>(session);
81 }
82 virtual void OnSuccess(webrtc::SessionDescriptionInterface* desc) OVERRIDE {
83 if (session_ == NULL) {
84 LOG(ERROR) << "No Session, cannot create session description.";
85 return;
86 }
87 session_->OnSuccess(desc);
88 }
89 virtual void OnFailure(const std::string& error) OVERRIDE {
90 if (session_ == NULL) {
91 LOG(ERROR) << "No Session, cannot create session description.";
92 return;
93 }
94 session_->OnFailure(error);
95 }
96
97 protected:
98 explicit CastCreateSessionDescriptionObserver(CastExtensionSession* session)
99 : session_(session) {}
100 virtual ~CastCreateSessionDescriptionObserver() {}
101
102 private:
103 CastExtensionSession* session_;
104 };
105
106 // webrtc::StatsObserver implementation.
107 class CastStatsObserver : public webrtc::StatsObserver {
108 public:
109 static CastStatsObserver* Create() {
110 return new talk_base::RefCountedObject<CastStatsObserver>();
111 }
112
113 virtual void OnComplete(
114 const std::vector<webrtc::StatsReport>& reports) OVERRIDE {
115 if (reports.empty()) {
116 VLOG(1) << "Received 0 StatsReports.";
117 }
118 VLOG(1) << "Received " << reports.size() << " new StatsReports.";
119 std::vector<webrtc::StatsReport>::const_iterator it;
120 for (it = reports.begin(); it != reports.end(); ++it) {
121 LogStatsReport(*it);
122 }
123 }
124
125 protected:
126 CastStatsObserver() {}
127 virtual ~CastStatsObserver() {}
128
129 void LogStatsReport(const webrtc::StatsReport& report) {
130 typedef webrtc::StatsReport StatsReport;
131 typedef webrtc::StatsReport::Values::iterator ValuesIterator;
132 webrtc::StatsReport::Values v = report.values;
133 for (ValuesIterator it = v.begin(); it != v.end(); ++it) {
134 VLOG(1) << "Param: " << it->name << "; Value: " << it->value << ".";
135 }
136 }
137 };
138
139 //------------------------------------------------------------------------------
140
141 // static
142 CastExtensionSession* CastExtensionSession::Create(
143 scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
144 scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner,
145 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
146 const protocol::NetworkSettings& network_settings,
147 ClientSession* client_session) {
148 scoped_ptr<CastExtensionSession> cast_extension_session(
149 new CastExtensionSession(network_task_runner,
150 video_capture_task_runner,
151 url_request_context_getter,
152 network_settings,
153 client_session));
154 bool success = cast_extension_session->WrapTasksAndSave();
155 success = cast_extension_session->InitializePeerConnection();
156 return (success ? cast_extension_session.release() : NULL);
157 }
158
159 CastExtensionSession::CastExtensionSession(
160 scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
161 scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner,
162 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
163 const protocol::NetworkSettings& network_settings,
164 ClientSession* client_session)
165 : network_task_runner_(network_task_runner),
166 capture_task_runner_(video_capture_task_runner),
167 network_thread_wrapper_(NULL),
168 capture_thread_wrapper_(NULL),
169 url_request_context_getter_(url_request_context_getter),
170 network_settings_(network_settings),
171 client_session_(client_session),
172 stats_observer_(CastStatsObserver::Create()) {
173 DCHECK(network_task_runner_.get() != NULL);
174 DCHECK(capture_task_runner_.get() != NULL);
175 DCHECK(url_request_context_getter_.get() != NULL);
176 DCHECK(client_session_);
177 }
178
179 CastExtensionSession::~CastExtensionSession() {
180 DeletePeerConnection();
181 }
182
183 std::string append_path(const char* first, const char* second) {
184 std::string path(first);
185 path.append(".");
186 path.append(second);
187 return path;
188 }
189
190 // Returns true if the ExtensionMessage was of type |kMessageType|, even if
191 // it was badly formed or a resulting action failed. This is done so that
192 // the host does not continue to attempt to pass |message| to other
193 // HostExtensionSessions.
194 bool CastExtensionSession::OnExtensionMessage(
195 ClientSession* client_session,
196 const protocol::ExtensionMessage& message) {
197 if (!message.has_type() || message.type().compare(kMessageType) != 0) {
198 return false;
199 }
200
201 scoped_ptr<base::Value> value(base::JSONReader::Read(message.data()));
202 base::DictionaryValue* client_message;
203
204 if (!(value && value->GetAsDictionary(&client_message))) {
205 LOG(ERROR) << "Could not read cast extension message.";
206 return true;
207 }
208
209 std::string subject;
210 if (!client_message->GetString(kMessageSubject, &subject)) {
211 LOG(ERROR) << "Invalid Cast Extension Message (missing subject header).";
212 return true;
213 }
214
215 if (subject == kSubjectSDP) {
216 std::string webrtc_type;
217 std::string sdp;
218 if (!client_message->GetString(
219 append_path(kMessageData, kWebRtcSessionDescType), &webrtc_type)) {
220 LOG(ERROR)
221 << "Invalid Cast Extension Message (missing webrtc type header).";
222 return true;
223 }
224 if (!client_message->GetString(
225 append_path(kMessageData, kWebRtcSessionDescSDP), &sdp)) {
226 LOG(ERROR)
227 << "Invalid Cast Extension Message (missing webrtc sdp header).";
228 return true;
229 }
230 webrtc::SdpParseError error;
231 webrtc::SessionDescriptionInterface* session_description(
232 webrtc::CreateSessionDescription(webrtc_type, sdp, &error));
233
234 if (!session_description) {
235 LOG(ERROR) << "Invalid Cast Extension Message (could not parse sdp).";
236 VLOG(1) << "SdpParseError was: " << error.line << "; "
237 << error.description + ". (Ignore if empty).";
238 return true;
239 }
240
241 // Save the type because session_description is going to be given off to
242 // PeerConnection.
243 std::string sdp_type = session_description->type();
244 VLOG(1) << "Setting Remote Description.";
245 peer_connection_->SetRemoteDescription(
246 CastSetSessionDescriptionObserver::Create(), session_description);
247 if (sdp_type == webrtc::SessionDescriptionInterface::kOffer) {
248 // Setup MediaStream in PeerConnection because client has made an offer.
249 // TODO(aiguha): Notify client of failure.
250 if (!InitializeAndAddMediaStream()) {
251 LOG(ERROR) << "InitializeAndAddMediaStream failed.";
252 return true;
253 }
254
255 VLOG(1) << "Received Offer. Creating Answer.";
256 peer_connection_->CreateAnswer(
257 CastCreateSessionDescriptionObserver::Create(this), NULL);
258 }
259 return true;
260 } else if (subject == kSubjectNewCandidate) {
261 std::string candidate_str;
262 std::string sdp_mid;
263 int sdp_mlineindex = 0;
264 if (!client_message->GetString(append_path(kMessageData, kWebRtcSDPMid),
265 &sdp_mid) ||
266 !client_message->GetInteger(
267 append_path(kMessageData, kWebRtcSDPMLineIndex), &sdp_mlineindex) ||
268 !client_message->GetString(append_path(kMessageData, kWebRtcCandidate),
269 &candidate_str)) {
270 LOG(ERROR) << "Invalid Cast Extension Message (could not parse).";
271 return true;
272 }
273 talk_base::scoped_ptr<webrtc::IceCandidateInterface> candidate(
274 webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate_str));
275 if (!candidate.get()) {
276 LOG(ERROR)
277 << "Invalid Cast Extension Message (could not create candidate).";
278 return true;
279 }
280 if (!peer_connection_->AddIceCandidate(candidate.get())) {
281 LOG(ERROR) << "Failed to apply received ICE Candidate to PeerConnection.";
282 return true;
283 }
284 VLOG(1) << "Received ICE Candidate: " << candidate_str;
285 } else {
286 VLOG(1) << "Unexpected CastExtension Message: " << message.data();
287 }
288 return true;
289 }
290
291 // Private Methods -------------------------------------------------------------
292
293 bool CastExtensionSession::SendMessageToClient(const char* subject,
294 const std::string& data) {
295 DCHECK(network_task_runner_->BelongsToCurrentThread());
296 if (client_session_ == NULL) {
297 LOG(ERROR) << "No Client Session. Cannot send message to client.";
298 return false;
299 }
300
301 base::DictionaryValue message_dict;
302 message_dict.SetString(kMessageSubject, subject);
303 message_dict.SetString(kMessageData, data);
304 std::string message_json;
305
306 if (!base::JSONWriter::Write(&message_dict, &message_json)) {
307 LOG(ERROR) << "Failed to create message json.";
308 return false;
309 }
310
311 protocol::ExtensionMessage message;
312 message.set_type(kMessageType);
313 message.set_data(message_json);
314 client_session_->connection()->client_stub()->DeliverHostMessage(message);
315 return true;
316 }
317
318 void CastExtensionSession::SendCursorShape(
319 scoped_ptr<protocol::CursorShapeInfo> cursor_shape) {
320 DCHECK(network_task_runner_->BelongsToCurrentThread());
321 if (client_session_ == NULL)
322 return;
323
324 protocol::ClientStub* client_stub =
325 client_session_->connection()->client_stub();
326
327 if (client_stub == NULL)
328 return;
329
330 client_stub->SetCursorShape(*cursor_shape);
331 }
332
333 void CastExtensionSession::EnsureTaskAndSetSend(talk_base::Thread** ptr,
334 base::WaitableEvent* event) {
335 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
336 jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
337 *ptr = jingle_glue::JingleThreadWrapper::current();
338
339 if (event != NULL)
340 event->Signal();
341 }
342
343 bool CastExtensionSession::WrapTasksAndSave() {
344 DCHECK(network_task_runner_->BelongsToCurrentThread());
345 EnsureTaskAndSetSend(&network_thread_wrapper_);
346
347 if (network_thread_wrapper_ == NULL)
348 return false;
349
350 base::WaitableEvent wrap_capture_thread_event(true, false);
351 capture_task_runner_->PostTask(
352 FROM_HERE,
353 base::Bind(&CastExtensionSession::EnsureTaskAndSetSend,
354 base::Unretained(this),
355 &capture_thread_wrapper_,
356 &wrap_capture_thread_event));
357 wrap_capture_thread_event.Wait();
358
359 return (capture_thread_wrapper_ != NULL);
360 }
361
362 bool CastExtensionSession::InitializePeerConnection() {
363 DCHECK(network_task_runner_->BelongsToCurrentThread());
364 DCHECK(peer_conn_factory_.get() == NULL);
365 DCHECK(peer_connection_.get() == NULL);
366 DCHECK(capture_thread_wrapper_ != NULL);
367 DCHECK(network_thread_wrapper_ != NULL);
368
369 // TODO(aiguha): Confirm that worker and signalling threads are being
370 // assigned appropriately. For the below configuration to work,
371 // |capture_task_runner_| was changed to have a TYPE_IO MessageLoop.
372 peer_conn_factory_ = webrtc::CreatePeerConnectionFactory(
373 capture_thread_wrapper_, network_thread_wrapper_, NULL, NULL, NULL);
374
375 if (!peer_conn_factory_.get()) {
376 LOG(ERROR) << "Failed to initialize PeerConnectionFactory";
377 DeletePeerConnection();
378 return false;
379 }
380
381 VLOG(1) << "Created PeerConnectionFactory successfully.";
382
383 webrtc::PeerConnectionInterface::IceServers servers;
384 webrtc::PeerConnectionInterface::IceServer server;
385 server.uri = kDefaultStunURI;
386 servers.push_back(server);
387 webrtc::PeerConnectionInterface::RTCConfiguration rtc_config;
388 rtc_config.servers = servers;
389 webrtc::FakeConstraints constraints;
390
391 // DTLS-SRTP is the preferred encryption method. If set to kValueFalse, the
392 // peer connection uses SDES. Disabling SDES as well will cause the peer
393 // connection to fail to connect.
394 // Note: For protection and unprotection of SRTP packets, the libjingle
395 // ENABLE_EXTERNAL_AUTH flag must not be set.
396 constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
397 webrtc::MediaConstraintsInterface::kValueTrue);
398
399 peer_connection_ = peer_conn_factory_->CreatePeerConnection(
400 rtc_config,
401 &constraints,
402 ChromiumPortAllocatorFactory::Create(network_settings_,
403 url_request_context_getter_),
404 NULL,
405 this);
406
407 if (!peer_connection_.get()) {
408 LOG(ERROR) << "Failed to initialize PeerConnection.";
409 DeletePeerConnection();
410 return false;
411 }
412
413 VLOG(1) << "Created PeerConnection successfully.";
414
415 if (!SendMessageToClient(kSubjectTest, "Hello, client.")) {
416 LOG(ERROR) << "Failed to send test message to client.";
417 return false;
418 }
419 // Sending this message triggers the client to start the peer connection
420 // offer/answer negotiation.
421 if (!SendMessageToClient(kSubjectReady, "Host ready to receive offers.")) {
422 LOG(ERROR) << "Failed to send ready message to client.";
423 return false;
424 }
425
426 return true;
427 }
428
429 bool CastExtensionSession::InitializeAndAddMediaStream() {
430 DCHECK(network_task_runner_->BelongsToCurrentThread());
431
432 if (stream_) {
433 VLOG(1) << "Already added MediaStream.";
434 return false; // Already added.
435 }
436 scoped_ptr<webrtc::ScreenCapturer> screen_capturer =
437 client_session_->RequestScreenCapturer();
438 screen_capturer->SetMouseShapeObserver(this);
439 // scoped_ptr<CastVideoCapturer> cast_video_capturer(
440 // new CastVideoCapturer(capture_task_runner_, screen_capturer.Pass()));
aiguha 2014/07/29 04:23:45 This is the only bit of code that relies on the ot
441 std::string minFrameRate = "3";
442 webrtc::FakeConstraints video_constraints;
443 video_constraints.AddMandatory(
444 webrtc::MediaConstraintsInterface::kMinFrameRate, minFrameRate);
445
446 talk_base::scoped_refptr<webrtc::VideoTrackInterface> video_track =
447 peer_conn_factory_->CreateVideoTrack(
448 kVideoLabel,
449 peer_conn_factory_->CreateVideoSource(NULL,
450 &video_constraints));
451
452 VLOG(1) << "Created VideoTrack successfully.";
453 stream_ = peer_conn_factory_->CreateLocalMediaStream(kStreamLabel);
454
455 if (!stream_->AddTrack(video_track)) {
456 LOG(ERROR) << "Failed to add VideoTrack to MediaStream.";
457 return false;
458 } else {
459 VLOG(1) << "Added VideoTrack to MediaStream successfully.";
460 }
461
462 if (!peer_connection_->AddStream(stream_, NULL)) {
463 VLOG(1) << "Failed to add MediaStream to PeerConnection.";
464 return false;
465 } else {
466 VLOG(1) << "Added Stream to PeerConnection successfully.";
467 }
468 return true;
469 }
470
471 void CastExtensionSession::PollPeerConnectionStats() {
472 if (!connection_active()) {
473 VLOG(1) << "Cannot poll stats while PeerConnection is inactive.";
474 }
475 talk_base::scoped_refptr<webrtc::MediaStreamTrackInterface> video_track =
476 stream_->FindVideoTrack(kVideoLabel);
477 peer_connection_->GetStats(
478 stats_observer_,
479 video_track.release(),
480 webrtc::PeerConnectionInterface::kStatsOutputLevelStandard);
481 }
482
483 void CastExtensionSession::DeletePeerConnection() {
484 peer_connection_->Close();
485 // peer_connection_ = NULL;
486 stream_ = NULL;
487 peer_conn_factory_ = NULL;
488 }
489
490 bool CastExtensionSession::connection_active() const {
491 return peer_connection_.get() != NULL;
492 }
493
494 // MouseShapeObserver implementation -------------------------------------------
495
496 // TODO(aiguha): To reduce duplication, it would perhaps be
497 // better to create a single MouseShapeObserver outside of VideoScheduler
498 // that can be attached to the ScreenCapturer on its creation, in
499 // ClientSession.
500 void CastExtensionSession::OnCursorShapeChanged(
501 webrtc::MouseCursorShape* cursor_shape) {
502 DCHECK(capture_task_runner_->BelongsToCurrentThread());
503 VLOG(1) << __FUNCTION__ << " called.";
504 scoped_ptr<webrtc::MouseCursorShape> owned_cursor(cursor_shape);
505
506 scoped_ptr<protocol::CursorShapeInfo> cursor_proto(
507 new protocol::CursorShapeInfo());
508 cursor_proto->set_width(cursor_shape->size.width());
509 cursor_proto->set_height(cursor_shape->size.height());
510 cursor_proto->set_hotspot_x(cursor_shape->hotspot.x());
511 cursor_proto->set_hotspot_y(cursor_shape->hotspot.y());
512 cursor_proto->set_data(cursor_shape->data);
513
514 network_task_runner_->PostTask(
515 FROM_HERE,
516 base::Bind(&CastExtensionSession::SendCursorShape,
517 base::Unretained(this),
518 base::Passed(&cursor_proto)));
519 }
520
521 // CreateSessionDescriptionObserver related methods ----------------------------
522
523 void CastExtensionSession::OnSuccess(
524 webrtc::SessionDescriptionInterface* desc) {
525 if (!network_task_runner_->BelongsToCurrentThread()) {
526 network_task_runner_->PostTask(
527 FROM_HERE,
528 base::Bind(
529 &CastExtensionSession::OnSuccess, base::Unretained(this), desc));
530 return;
531 }
532 peer_connection_->SetLocalDescription(
533 CastSetSessionDescriptionObserver::Create(), desc);
534 scoped_ptr<base::DictionaryValue> json(new base::DictionaryValue());
535 json->SetString(kWebRtcSessionDescType, desc->type());
536 std::string subject = kWebRtcPrefix + desc->type();
537 std::string desc_str;
538 desc->ToString(&desc_str);
539 json->SetString(kWebRtcSessionDescSDP, desc_str);
540 std::string json_str;
541 base::JSONWriter::Write(json.get(), &json_str);
542 SendMessageToClient(subject.c_str(), json_str);
543 }
544
545 void CastExtensionSession::OnFailure(const std::string& error) {
546 VLOG(1) << __FUNCTION__ << " called with: " << error;
547 }
548
549 // PeerConnectionObserver implementation ---------------------------------------
550
551 void CastExtensionSession::OnError() {
552 VLOG(1) << __FUNCTION__;
553 }
554 void CastExtensionSession::OnSignalingChange(
555 webrtc::PeerConnectionInterface::SignalingState new_state) {
556 VLOG(1) << "Function CastExtensionSession::OnSignalingChange called with "
557 << new_state;
558 }
559 void CastExtensionSession::OnStateChange(
560 webrtc::PeerConnectionObserver::StateType state_changed) {
561 VLOG(1) << __FUNCTION__ << " called with input " << state_changed;
562 }
563 void CastExtensionSession::OnAddStream(webrtc::MediaStreamInterface* stream) {
564 VLOG(1) << __FUNCTION__ << " " << stream->label();
565 }
566 void CastExtensionSession::OnRemoveStream(
567 webrtc::MediaStreamInterface* stream) {
568 VLOG(1) << __FUNCTION__ << " " << stream->label();
569 }
570 void CastExtensionSession::OnDataChannel(
571 webrtc::DataChannelInterface* data_channel) {
572 VLOG(1) << __FUNCTION__ << " called with " << data_channel->label();
573 }
574 void CastExtensionSession::OnRenegotiationNeeded() {
575 VLOG(1) << __FUNCTION__ << " called.";
576 }
577 void CastExtensionSession::OnIceConnectionChange(
578 webrtc::PeerConnectionInterface::IceConnectionState new_state) {
579 VLOG(1) << __FUNCTION__ << " called with new IceConnectionState "
580 << new_state;
581 }
582 void CastExtensionSession::OnIceGatheringChange(
583 webrtc::PeerConnectionInterface::IceGatheringState new_state) {
584 VLOG(1) << __FUNCTION__ << " called with new IceGatheringState " << new_state;
585 }
586
587 void CastExtensionSession::OnIceComplete() {
588 VLOG(1) << __FUNCTION__ << " called.";
589 // TODO(aiguha): Maybe start timer only if enabled by command-line flag or
590 // at a particular verbosity level.
591 stats_polling_timer_.Start(FROM_HERE,
592 base::TimeDelta::FromSeconds(10),
593 this,
594 &CastExtensionSession::PollPeerConnectionStats);
595 }
596
597 void CastExtensionSession::OnIceCandidate(
598 const webrtc::IceCandidateInterface* candidate) {
599 std::string candidate_str;
600 if (!candidate->ToString(&candidate_str)) {
601 LOG(ERROR) << __FUNCTION__ << " called, but could not serialize candidate.";
602 return;
603 }
604 VLOG(1) << __FUNCTION__ << " called with " << candidate_str;
605 scoped_ptr<base::DictionaryValue> json(new base::DictionaryValue());
606 json->SetString(kWebRtcSDPMid, candidate->sdp_mid());
607 json->SetInteger(kWebRtcSDPMLineIndex, candidate->sdp_mline_index());
608 json->SetString(kWebRtcCandidate, candidate_str);
609 std::string json_str;
610 base::JSONWriter::Write(json.get(), &json_str);
611 SendMessageToClient(kSubjectNewCandidate, json_str);
612 }
613
614 } // namespace remoting
615
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698