1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/frontend/webrtc/lib/client_handler.h"
18
19 #include <optional>
20 #include <vector>
21
22 #include <json/json.h>
23 #include <netdb.h>
24 #include <openssl/rand.h>
25
26 #include <android-base/logging.h>
27
28 #include "host/frontend/webrtc/lib/keyboard.h"
29 #include "host/libs/config/cuttlefish_config.h"
30 #include "https/SafeCallbackable.h"
31
32 namespace cuttlefish {
33 namespace webrtc_streaming {
34
35 namespace {
36
37 static constexpr auto kInputChannelLabel = "input-channel";
38 static constexpr auto kAdbChannelLabel = "adb-channel";
39
40 class ValidationResult {
41 public:
42 ValidationResult() = default;
ValidationResult(const std::string & error)43 ValidationResult(const std::string &error) : error_(error) {}
44
ok() const45 bool ok() const { return !error_.has_value(); }
error() const46 std::string error() const { return error_.value_or(""); }
47
48 private:
49 std::optional<std::string> error_;
50 };
51
52 // helper method to ensure a json object has the required fields convertible
53 // to the appropriate types.
validateJsonObject(const Json::Value & obj,const std::string & type,const std::map<std::string,Json::ValueType> & fields)54 ValidationResult validateJsonObject(
55 const Json::Value &obj, const std::string &type,
56 const std::map<std::string, Json::ValueType> &fields) {
57 for (const auto &field_spec : fields) {
58 const auto &field_name = field_spec.first;
59 auto field_type = field_spec.second;
60 if (!(obj.isMember(field_name) &&
61 obj[field_name].isConvertibleTo(field_type))) {
62 std::string error_msg = "Expected a field named '";
63 error_msg += field_name + "' of type '";
64 error_msg += std::to_string(field_type);
65 error_msg += "'";
66 if (!type.empty()) {
67 error_msg += " in message of type '" + type + "'";
68 }
69 error_msg += ".";
70 return {error_msg};
71 }
72 }
73 return {};
74 }
75
76 class CvdCreateSessionDescriptionObserver
77 : public webrtc::CreateSessionDescriptionObserver {
78 public:
CvdCreateSessionDescriptionObserver(std::weak_ptr<ClientHandler> client_handler)79 CvdCreateSessionDescriptionObserver(
80 std::weak_ptr<ClientHandler> client_handler)
81 : client_handler_(client_handler) {}
82
OnSuccess(webrtc::SessionDescriptionInterface * desc)83 void OnSuccess(webrtc::SessionDescriptionInterface *desc) override {
84 auto client_handler = client_handler_.lock();
85 if (client_handler) {
86 client_handler->OnCreateSDPSuccess(desc);
87 }
88 }
OnFailure(webrtc::RTCError error)89 void OnFailure(webrtc::RTCError error) override {
90 auto client_handler = client_handler_.lock();
91 if (client_handler) {
92 client_handler->OnCreateSDPFailure(error);
93 }
94 }
95
96 private:
97 std::weak_ptr<ClientHandler> client_handler_;
98 };
99
100 class CvdSetSessionDescriptionObserver
101 : public webrtc::SetSessionDescriptionObserver {
102 public:
CvdSetSessionDescriptionObserver(std::weak_ptr<ClientHandler> client_handler)103 CvdSetSessionDescriptionObserver(std::weak_ptr<ClientHandler> client_handler)
104 : client_handler_(client_handler) {}
105
OnSuccess()106 void OnSuccess() override {
107 // local description set, nothing else to do
108 }
OnFailure(webrtc::RTCError error)109 void OnFailure(webrtc::RTCError error) override {
110 auto client_handler = client_handler_.lock();
111 if (client_handler) {
112 client_handler->OnSetSDPFailure(error);
113 }
114 }
115
116 private:
117 std::weak_ptr<ClientHandler> client_handler_;
118 };
119
120 class CvdOnSetRemoteDescription
121 : public webrtc::SetRemoteDescriptionObserverInterface {
122 public:
CvdOnSetRemoteDescription(std::function<void (webrtc::RTCError error)> on_error)123 CvdOnSetRemoteDescription(
124 std::function<void(webrtc::RTCError error)> on_error)
125 : on_error_(on_error) {}
126
OnSetRemoteDescriptionComplete(webrtc::RTCError error)127 void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override {
128 on_error_(error);
129 }
130
131 private:
132 std::function<void(webrtc::RTCError error)> on_error_;
133 };
134
135 } // namespace
136
InputHandler(rtc::scoped_refptr<webrtc::DataChannelInterface> input_channel,std::shared_ptr<ConnectionObserver> observer)137 ClientHandler::InputHandler::InputHandler(
138 rtc::scoped_refptr<webrtc::DataChannelInterface> input_channel,
139 std::shared_ptr<ConnectionObserver> observer)
140 : input_channel_(input_channel), observer_(observer) {
141 input_channel->RegisterObserver(this);
142 }
~InputHandler()143 ClientHandler::InputHandler::~InputHandler() {
144 input_channel_->UnregisterObserver();
145 }
OnStateChange()146 void ClientHandler::InputHandler::OnStateChange() {
147 LOG(VERBOSE) << "Input channel state changed to "
148 << webrtc::DataChannelInterface::DataStateString(
149 input_channel_->state());
150 }
151
OnMessage(const webrtc::DataBuffer & msg)152 void ClientHandler::InputHandler::OnMessage(const webrtc::DataBuffer &msg) {
153 if (msg.binary) {
154 // TODO (jemoreira) consider binary protocol to avoid JSON parsing overhead
155 LOG(ERROR) << "Received invalid (binary) data on input channel";
156 return;
157 }
158 auto size = msg.size();
159
160 Json::Value evt;
161 Json::Reader json_reader;
162 auto str = msg.data.cdata<char>();
163 if (!json_reader.parse(str, str + size, evt) < 0) {
164 LOG(ERROR) << "Received invalid JSON object over input channel";
165 return;
166 }
167 if (!evt.isMember("type") || !evt["type"].isString()) {
168 LOG(ERROR) << "Input event doesn't have a valid 'type' field: "
169 << evt.toStyledString();
170 return;
171 }
172 auto event_type = evt["type"].asString();
173 if (event_type == "mouse") {
174 auto result =
175 validateJsonObject(evt, "mouse",
176 {{"down", Json::ValueType::intValue},
177 {"x", Json::ValueType::intValue},
178 {"y", Json::ValueType::intValue},
179 {"display_label", Json::ValueType::stringValue}});
180 if (!result.ok()) {
181 LOG(ERROR) << result.error();
182 return;
183 }
184 auto label = evt["display_label"].asString();
185 int32_t down = evt["down"].asInt();
186 int32_t x = evt["x"].asInt();
187 int32_t y = evt["y"].asInt();
188
189 observer_->OnTouchEvent(label, x, y, down);
190 } else if (event_type == "multi-touch") {
191 auto result =
192 validateJsonObject(evt, "multi-touch",
193 {{"id", Json::ValueType::intValue},
194 {"initialDown", Json::ValueType::intValue},
195 {"x", Json::ValueType::intValue},
196 {"y", Json::ValueType::intValue},
197 {"slot", Json::ValueType::intValue},
198 {"display_label", Json::ValueType::stringValue}});
199 if (!result.ok()) {
200 LOG(ERROR) << result.error();
201 return;
202 }
203 auto label = evt["display_label"].asString();
204 int32_t id = evt["id"].asInt();
205 int32_t initialDown = evt["initialDown"].asInt();
206 int32_t x = evt["x"].asInt();
207 int32_t y = evt["y"].asInt();
208 int32_t slot = evt["slot"].asInt();
209
210 observer_->OnMultiTouchEvent(label, id, slot, x, y, initialDown);
211 } else if (event_type == "keyboard") {
212 auto result =
213 validateJsonObject(evt, "keyboard",
214 {{"event_type", Json::ValueType::stringValue},
215 {"keycode", Json::ValueType::stringValue}});
216 if (!result.ok()) {
217 LOG(ERROR) << result.error();
218 return;
219 }
220 auto down = evt["event_type"].asString() == std::string("keydown");
221 auto code = DomKeyCodeToLinux(evt["keycode"].asString());
222 observer_->OnKeyboardEvent(code, down);
223 } else {
224 LOG(ERROR) << "Unrecognized event type: " << event_type;
225 return;
226 }
227 }
228
AdbHandler(rtc::scoped_refptr<webrtc::DataChannelInterface> adb_channel,std::shared_ptr<ConnectionObserver> observer)229 ClientHandler::AdbHandler::AdbHandler(
230 rtc::scoped_refptr<webrtc::DataChannelInterface> adb_channel,
231 std::shared_ptr<ConnectionObserver> observer)
232 : adb_channel_(adb_channel), observer_(observer) {
233 adb_channel->RegisterObserver(this);
234 }
235
~AdbHandler()236 ClientHandler::AdbHandler::~AdbHandler() { adb_channel_->UnregisterObserver(); }
237
OnStateChange()238 void ClientHandler::AdbHandler::OnStateChange() {
239 LOG(VERBOSE) << "Input channel state changed to "
240 << webrtc::DataChannelInterface::DataStateString(
241 adb_channel_->state());
242 }
243
OnMessage(const webrtc::DataBuffer & msg)244 void ClientHandler::AdbHandler::OnMessage(const webrtc::DataBuffer &msg) {
245 // Report the adb channel as open on the first message received instead of at
246 // channel open, this avoids unnecessarily connecting to the adb daemon for
247 // clients that don't use ADB.
248 if (!channel_open_reported_) {
249 observer_->OnAdbChannelOpen([this](const uint8_t *msg, size_t size) {
250 webrtc::DataBuffer buffer(rtc::CopyOnWriteBuffer(msg, size),
251 true /*binary*/);
252 // TODO (jemoreira): When the SCTP channel is congested data channel
253 // messages are buffered up to 16MB, when the buffer is full the channel
254 // is abruptly closed. Keep track of the buffered data to avoid losing the
255 // adb data channel.
256 adb_channel_->Send(buffer);
257 return true;
258 });
259 channel_open_reported_ = true;
260 }
261 observer_->OnAdbMessage(msg.data.cdata(), msg.size());
262 }
263
Create(int client_id,std::shared_ptr<ConnectionObserver> observer,std::function<void (const Json::Value &)> send_to_client_cb,std::function<void ()> on_connection_closed_cb)264 std::shared_ptr<ClientHandler> ClientHandler::Create(
265 int client_id, std::shared_ptr<ConnectionObserver> observer,
266 std::function<void(const Json::Value &)> send_to_client_cb,
267 std::function<void()> on_connection_closed_cb) {
268 return std::shared_ptr<ClientHandler>(new ClientHandler(
269 client_id, observer, send_to_client_cb, on_connection_closed_cb));
270 }
271
ClientHandler(int client_id,std::shared_ptr<ConnectionObserver> observer,std::function<void (const Json::Value &)> send_to_client_cb,std::function<void ()> on_connection_closed_cb)272 ClientHandler::ClientHandler(
273 int client_id, std::shared_ptr<ConnectionObserver> observer,
274 std::function<void(const Json::Value &)> send_to_client_cb,
275 std::function<void()> on_connection_closed_cb)
276 : client_id_(client_id),
277 observer_(observer),
278 send_to_client_(send_to_client_cb),
279 on_connection_closed_cb_(on_connection_closed_cb) {}
280
~ClientHandler()281 ClientHandler::~ClientHandler() {
282 for (auto &data_channel : data_channels_) {
283 data_channel->UnregisterObserver();
284 }
285 }
286
SetPeerConnection(rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection)287 bool ClientHandler::SetPeerConnection(
288 rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection) {
289 peer_connection_ = peer_connection;
290
291 // If no channel is created on the peer connection the generated offer won't
292 // have an entry for data channels which breaks input and adb.
293 // This channel has no use now, but could be used in the future to exchange
294 // control data between client and device without going through the signaling
295 // server.
296 auto control_channel = peer_connection_->CreateDataChannel(
297 "device-control", nullptr /* config */);
298 if (!control_channel) {
299 LOG(ERROR) << "Failed to create control data channel";
300 return false;
301 }
302 return true;
303 }
304
AddDisplay(rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track,const std::string & label)305 bool ClientHandler::AddDisplay(
306 rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track,
307 const std::string &label) {
308 // Send each track as part of a different stream with the label as id
309 auto err_or_sender =
310 peer_connection_->AddTrack(video_track, {label} /* stream_id */);
311 if (!err_or_sender.ok()) {
312 LOG(ERROR) << "Failed to add video track to the peer connection";
313 return false;
314 }
315 // TODO (b/154138394): use the returned sender (err_or_sender.value()) to
316 // remove the display from the connection.
317 return true;
318 }
319
LogAndReplyError(const std::string & error_msg) const320 void ClientHandler::LogAndReplyError(const std::string &error_msg) const {
321 LOG(ERROR) << error_msg;
322 Json::Value reply;
323 reply["type"] = "error";
324 reply["error"] = error_msg;
325 send_to_client_(reply);
326 }
327
OnCreateSDPSuccess(webrtc::SessionDescriptionInterface * desc)328 void ClientHandler::OnCreateSDPSuccess(
329 webrtc::SessionDescriptionInterface *desc) {
330 std::string offer_str;
331 desc->ToString(&offer_str);
332 peer_connection_->SetLocalDescription(
333 // The peer connection wraps this raw pointer with a scoped_refptr, so
334 // it's guaranteed to be deleted at some point
335 new rtc::RefCountedObject<CvdSetSessionDescriptionObserver>(
336 weak_from_this()),
337 desc);
338 // The peer connection takes ownership of the description so it should not be
339 // used after this
340 desc = nullptr;
341
342 Json::Value reply;
343 reply["type"] = "offer";
344 reply["sdp"] = offer_str;
345
346 send_to_client_(reply);
347 }
348
OnCreateSDPFailure(webrtc::RTCError error)349 void ClientHandler::OnCreateSDPFailure(webrtc::RTCError error) {
350 LogAndReplyError(error.message());
351 Close();
352 }
353
OnSetSDPFailure(webrtc::RTCError error)354 void ClientHandler::OnSetSDPFailure(webrtc::RTCError error) {
355 LogAndReplyError(error.message());
356 LOG(ERROR) << "Error setting local description: Either there is a bug in "
357 "libwebrtc or the local description was (incorrectly) modified "
358 "after creating it";
359 Close();
360 }
361
HandleMessage(const Json::Value & message)362 void ClientHandler::HandleMessage(const Json::Value &message) {
363 {
364 auto result = validateJsonObject(message, "",
365 {{"type", Json::ValueType::stringValue}});
366 if (!result.ok()) {
367 LogAndReplyError(result.error());
368 return;
369 }
370 }
371 auto type = message["type"].asString();
372 if (type == "request-offer") {
373 peer_connection_->CreateOffer(
374 // No memory leak here because this is a ref counted objects and the
375 // peer connection immediately wraps it with a scoped_refptr
376 new rtc::RefCountedObject<CvdCreateSessionDescriptionObserver>(
377 weak_from_this()),
378 webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
379 // The created offer wil be sent to the client on
380 // OnSuccess(webrtc::SessionDescriptionInterface* desc)
381 } else if (type == "answer") {
382 auto result = validateJsonObject(message, type,
383 {{"sdp", Json::ValueType::stringValue}});
384 if (!result.ok()) {
385 LogAndReplyError(result.error());
386 return;
387 }
388 auto remote_desc_str = message["sdp"].asString();
389 auto remote_desc = webrtc::CreateSessionDescription(
390 webrtc::SdpType::kAnswer, remote_desc_str, nullptr /*error*/);
391 if (!remote_desc) {
392 LogAndReplyError("Failed to parse answer.");
393 return;
394 }
395 rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface> observer(
396 new rtc::RefCountedObject<CvdOnSetRemoteDescription>(
397 [this](webrtc::RTCError error) {
398 if (!error.ok()) {
399 LogAndReplyError(error.message());
400 // The remote description was rejected, this client can't be
401 // trusted anymore.
402 Close();
403 }
404 }));
405 peer_connection_->SetRemoteDescription(std::move(remote_desc), observer);
406
407 } else if (type == "ice-candidate") {
408 {
409 auto result = validateJsonObject(
410 message, type, {{"candidate", Json::ValueType::objectValue}});
411 if (!result.ok()) {
412 LogAndReplyError(result.error());
413 return;
414 }
415 }
416 auto candidate_json = message["candidate"];
417 {
418 auto result =
419 validateJsonObject(candidate_json, "ice-candidate/candidate",
420 {
421 {"sdpMid", Json::ValueType::stringValue},
422 {"candidate", Json::ValueType::stringValue},
423 {"sdpMLineIndex", Json::ValueType::intValue},
424 });
425 if (!result.ok()) {
426 LogAndReplyError(result.error());
427 return;
428 }
429 }
430 auto mid = candidate_json["sdpMid"].asString();
431 auto candidate_sdp = candidate_json["candidate"].asString();
432 auto line_index = candidate_json["sdpMLineIndex"].asInt();
433
434 std::unique_ptr<webrtc::IceCandidateInterface> candidate(
435 webrtc::CreateIceCandidate(mid, line_index, candidate_sdp,
436 nullptr /*error*/));
437 if (!candidate) {
438 LogAndReplyError("Failed to parse ICE candidate");
439 return;
440 }
441 peer_connection_->AddIceCandidate(std::move(candidate),
442 [this](webrtc::RTCError error) {
443 if (!error.ok()) {
444 LogAndReplyError(error.message());
445 }
446 });
447 } else {
448 LogAndReplyError("Unknown client message type: " + type);
449 return;
450 }
451 }
452
Close()453 void ClientHandler::Close() {
454 // We can't simply call peer_connection_->Close() here because this method
455 // could be called from one of the PeerConnectionObserver callbacks and that
456 // would lead to a deadlock (Close eventually tries to destroy an object that
457 // will then wait for the callback to return -> deadlock). Destroying the
458 // peer_connection_ has the same effect. The only alternative is to postpone
459 // that operation until after the callback returns.
460 on_connection_closed_cb_();
461 }
462
OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state)463 void ClientHandler::OnConnectionChange(
464 webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
465 switch (new_state) {
466 case webrtc::PeerConnectionInterface::PeerConnectionState::kNew:
467 break;
468 case webrtc::PeerConnectionInterface::PeerConnectionState::kConnecting:
469 break;
470 case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
471 LOG(VERBOSE) << "Client " << client_id_ << ": WebRTC connected";
472 observer_->OnConnected();
473 break;
474 case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected:
475 LOG(VERBOSE) << "Client " << client_id_ << ": Connection disconnected";
476 Close();
477 break;
478 case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
479 LOG(ERROR) << "Client " << client_id_ << ": Connection failed";
480 Close();
481 break;
482 case webrtc::PeerConnectionInterface::PeerConnectionState::kClosed:
483 LOG(VERBOSE) << "Client " << client_id_ << ": Connection closed";
484 Close();
485 break;
486 }
487 }
488
OnIceCandidate(const webrtc::IceCandidateInterface * candidate)489 void ClientHandler::OnIceCandidate(
490 const webrtc::IceCandidateInterface *candidate) {
491 std::string candidate_sdp;
492 candidate->ToString(&candidate_sdp);
493 auto sdp_mid = candidate->sdp_mid();
494 auto line_index = candidate->sdp_mline_index();
495
496 Json::Value reply;
497 reply["type"] = "ice-candidate";
498 reply["mid"] = sdp_mid;
499 reply["mLineIndex"] = static_cast<Json::UInt64>(line_index);
500 reply["candidate"] = candidate_sdp;
501
502 send_to_client_(reply);
503 }
504
OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)505 void ClientHandler::OnDataChannel(
506 rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
507 auto label = data_channel->label();
508 if (label == kInputChannelLabel) {
509 input_handler_.reset(new InputHandler(data_channel, observer_));
510 } else if (label == kAdbChannelLabel) {
511 adb_handler_.reset(new AdbHandler(data_channel, observer_));
512 } else {
513 LOG(VERBOSE) << "Data channel connected: " << label;
514 data_channels_.push_back(data_channel);
515 }
516 }
517
OnRenegotiationNeeded()518 void ClientHandler::OnRenegotiationNeeded() {
519 LOG(VERBOSE) << "Client " << client_id_ << " needs renegotiation";
520 }
521
OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state)522 void ClientHandler::OnIceGatheringChange(
523 webrtc::PeerConnectionInterface::IceGatheringState new_state) {
524 std::string state_str;
525 switch (new_state) {
526 case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringNew:
527 state_str = "NEW";
528 break;
529 case webrtc::PeerConnectionInterface::IceGatheringState::
530 kIceGatheringGathering:
531 state_str = "GATHERING";
532 break;
533 case webrtc::PeerConnectionInterface::IceGatheringState::
534 kIceGatheringComplete:
535 state_str = "COMPLETE";
536 break;
537 default:
538 state_str = "UNKNOWN";
539 }
540 LOG(VERBOSE) << "Client " << client_id_
541 << ": ICE Gathering state set to: " << state_str;
542 }
543
OnIceCandidateError(const std::string & host_candidate,const std::string & url,int error_code,const std::string & error_text)544 void ClientHandler::OnIceCandidateError(const std::string &host_candidate,
545 const std::string &url, int error_code,
546 const std::string &error_text) {
547 LOG(VERBOSE) << "Gathering of an ICE candidate (host candidate: "
548 << host_candidate << ", url: " << url
549 << ") failed: " << error_text;
550 }
551
OnIceCandidateError(const std::string & address,int port,const std::string & url,int error_code,const std::string & error_text)552 void ClientHandler::OnIceCandidateError(const std::string &address, int port,
553 const std::string &url, int error_code,
554 const std::string &error_text) {
555 LOG(VERBOSE) << "Gathering of an ICE candidate (address: " << address
556 << ", port: " << port << ", url: " << url
557 << ") failed: " << error_text;
558 }
559
OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state)560 void ClientHandler::OnSignalingChange(
561 webrtc::PeerConnectionInterface::SignalingState new_state) {
562 // ignore
563 }
OnStandardizedIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state)564 void ClientHandler::OnStandardizedIceConnectionChange(
565 webrtc::PeerConnectionInterface::IceConnectionState new_state) {
566 switch (new_state) {
567 case webrtc::PeerConnectionInterface::kIceConnectionNew:
568 LOG(DEBUG) << "ICE connection state: New";
569 break;
570 case webrtc::PeerConnectionInterface::kIceConnectionChecking:
571 LOG(DEBUG) << "ICE connection state: Checking";
572 break;
573 case webrtc::PeerConnectionInterface::kIceConnectionConnected:
574 LOG(DEBUG) << "ICE connection state: Connected";
575 break;
576 case webrtc::PeerConnectionInterface::kIceConnectionCompleted:
577 LOG(DEBUG) << "ICE connection state: Completed";
578 break;
579 case webrtc::PeerConnectionInterface::kIceConnectionFailed:
580 LOG(DEBUG) << "ICE connection state: Failed";
581 break;
582 case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
583 LOG(DEBUG) << "ICE connection state: Disconnected";
584 break;
585 case webrtc::PeerConnectionInterface::kIceConnectionClosed:
586 LOG(DEBUG) << "ICE connection state: Closed";
587 break;
588 case webrtc::PeerConnectionInterface::kIceConnectionMax:
589 LOG(DEBUG) << "ICE connection state: Max";
590 break;
591 }
592 }
OnIceCandidatesRemoved(const std::vector<cricket::Candidate> & candidates)593 void ClientHandler::OnIceCandidatesRemoved(
594 const std::vector<cricket::Candidate> &candidates) {
595 // ignore
596 }
OnTrack(rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver)597 void ClientHandler::OnTrack(
598 rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
599 // ignore
600 }
OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver)601 void ClientHandler::OnRemoveTrack(
602 rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
603 // ignore
604 }
605
606 } // namespace webrtc_streaming
607 } // namespace cuttlefish
608