OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2012 The WebRTC project authors. All Rights Reserved. | 2 * Copyright 2012 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #include "webrtc/api/webrtcsession.h" | 11 #include "webrtc/api/webrtcsession.h" |
12 | 12 |
13 #include <limits.h> | 13 #include <limits.h> |
14 | 14 |
15 #include <algorithm> | 15 #include <algorithm> |
16 #include <set> | 16 #include <set> |
17 #include <utility> | 17 #include <utility> |
18 #include <vector> | 18 #include <vector> |
19 | 19 |
20 #include "webrtc/api/jsepicecandidate.h" | 20 #include "webrtc/api/jsepicecandidate.h" |
21 #include "webrtc/api/jsepsessiondescription.h" | 21 #include "webrtc/api/jsepsessiondescription.h" |
22 #include "webrtc/api/peerconnectioninterface.h" | 22 #include "webrtc/api/peerconnectioninterface.h" |
23 #include "webrtc/api/sctputils.h" | 23 #include "webrtc/api/sctputils.h" |
24 #include "webrtc/api/webrtcsessiondescriptionfactory.h" | 24 #include "webrtc/api/webrtcsessiondescriptionfactory.h" |
25 #include "webrtc/audio_sink.h" | 25 #include "webrtc/audio_sink.h" |
26 #include "webrtc/base/basictypes.h" | 26 #include "webrtc/base/basictypes.h" |
27 #include "webrtc/base/bind.h" | |
28 #include "webrtc/base/checks.h" | 27 #include "webrtc/base/checks.h" |
29 #include "webrtc/base/helpers.h" | 28 #include "webrtc/base/helpers.h" |
30 #include "webrtc/base/logging.h" | 29 #include "webrtc/base/logging.h" |
31 #include "webrtc/base/stringencode.h" | 30 #include "webrtc/base/stringencode.h" |
32 #include "webrtc/base/stringutils.h" | 31 #include "webrtc/base/stringutils.h" |
33 #include "webrtc/call.h" | 32 #include "webrtc/call.h" |
34 #include "webrtc/media/base/mediaconstants.h" | 33 #include "webrtc/media/base/mediaconstants.h" |
35 #include "webrtc/media/base/videocapturer.h" | 34 #include "webrtc/media/base/videocapturer.h" |
36 #include "webrtc/p2p/base/portallocator.h" | 35 #include "webrtc/p2p/base/portallocator.h" |
37 #include "webrtc/p2p/base/transportchannel.h" | 36 #include "webrtc/p2p/base/transportchannel.h" |
38 #include "webrtc/pc/channel.h" | 37 #include "webrtc/pc/channel.h" |
39 #include "webrtc/pc/channelmanager.h" | 38 #include "webrtc/pc/channelmanager.h" |
40 #include "webrtc/pc/mediasession.h" | 39 #include "webrtc/pc/mediasession.h" |
41 | 40 |
42 #ifdef HAVE_QUIC | |
43 #include "webrtc/p2p/quic/quictransportchannel.h" | |
44 #endif // HAVE_QUIC | |
45 | |
46 using cricket::ContentInfo; | 41 using cricket::ContentInfo; |
47 using cricket::ContentInfos; | 42 using cricket::ContentInfos; |
48 using cricket::MediaContentDescription; | 43 using cricket::MediaContentDescription; |
49 using cricket::SessionDescription; | 44 using cricket::SessionDescription; |
50 using cricket::TransportInfo; | 45 using cricket::TransportInfo; |
51 | 46 |
52 using cricket::LOCAL_PORT_TYPE; | 47 using cricket::LOCAL_PORT_TYPE; |
53 using cricket::STUN_PORT_TYPE; | 48 using cricket::STUN_PORT_TYPE; |
54 using cricket::RELAY_PORT_TYPE; | 49 using cricket::RELAY_PORT_TYPE; |
55 using cricket::PRFLX_PORT_TYPE; | 50 using cricket::PRFLX_PORT_TYPE; |
(...skipping 402 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
458 return false; | 453 return false; |
459 } | 454 } |
460 | 455 |
461 WebRtcSession::WebRtcSession( | 456 WebRtcSession::WebRtcSession( |
462 webrtc::MediaControllerInterface* media_controller, | 457 webrtc::MediaControllerInterface* media_controller, |
463 rtc::Thread* network_thread, | 458 rtc::Thread* network_thread, |
464 rtc::Thread* worker_thread, | 459 rtc::Thread* worker_thread, |
465 rtc::Thread* signaling_thread, | 460 rtc::Thread* signaling_thread, |
466 cricket::PortAllocator* port_allocator, | 461 cricket::PortAllocator* port_allocator, |
467 std::unique_ptr<cricket::TransportController> transport_controller) | 462 std::unique_ptr<cricket::TransportController> transport_controller) |
468 : network_thread_(network_thread), | 463 : worker_thread_(worker_thread), |
469 worker_thread_(worker_thread), | |
470 signaling_thread_(signaling_thread), | 464 signaling_thread_(signaling_thread), |
471 // RFC 3264: The numeric value of the session id and version in the | 465 // RFC 3264: The numeric value of the session id and version in the |
472 // o line MUST be representable with a "64 bit signed integer". | 466 // o line MUST be representable with a "64 bit signed integer". |
473 // Due to this constraint session id |sid_| is max limited to LLONG_MAX. | 467 // Due to this constraint session id |sid_| is max limited to LLONG_MAX. |
474 sid_(rtc::ToString(rtc::CreateRandomId64() & LLONG_MAX)), | 468 sid_(rtc::ToString(rtc::CreateRandomId64() & LLONG_MAX)), |
475 transport_controller_(std::move(transport_controller)), | 469 transport_controller_(std::move(transport_controller)), |
476 media_controller_(media_controller), | 470 media_controller_(media_controller), |
477 channel_manager_(media_controller_->channel_manager()), | 471 channel_manager_(media_controller_->channel_manager()), |
478 ice_observer_(NULL), | 472 ice_observer_(NULL), |
479 ice_connection_state_(PeerConnectionInterface::kIceConnectionNew), | 473 ice_connection_state_(PeerConnectionInterface::kIceConnectionNew), |
(...skipping 24 matching lines...) Expand all Loading... |
504 channel_manager_->DestroyVideoChannel(video_channel_.release()); | 498 channel_manager_->DestroyVideoChannel(video_channel_.release()); |
505 } | 499 } |
506 if (voice_channel_) { | 500 if (voice_channel_) { |
507 SignalVoiceChannelDestroyed(); | 501 SignalVoiceChannelDestroyed(); |
508 channel_manager_->DestroyVoiceChannel(voice_channel_.release()); | 502 channel_manager_->DestroyVoiceChannel(voice_channel_.release()); |
509 } | 503 } |
510 if (data_channel_) { | 504 if (data_channel_) { |
511 SignalDataChannelDestroyed(); | 505 SignalDataChannelDestroyed(); |
512 channel_manager_->DestroyDataChannel(data_channel_.release()); | 506 channel_manager_->DestroyDataChannel(data_channel_.release()); |
513 } | 507 } |
514 #ifdef HAVE_QUIC | |
515 if (quic_data_transport_) { | |
516 quic_data_transport_.reset(); | |
517 } | |
518 #endif | |
519 SignalDestroyed(); | 508 SignalDestroyed(); |
520 | 509 |
521 LOG(LS_INFO) << "Session: " << id() << " is destroyed."; | 510 LOG(LS_INFO) << "Session: " << id() << " is destroyed."; |
522 } | 511 } |
523 | 512 |
524 bool WebRtcSession::Initialize( | 513 bool WebRtcSession::Initialize( |
525 const PeerConnectionFactoryInterface::Options& options, | 514 const PeerConnectionFactoryInterface::Options& options, |
526 std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator, | 515 std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator, |
527 const PeerConnectionInterface::RTCConfiguration& rtc_configuration) { | 516 const PeerConnectionInterface::RTCConfiguration& rtc_configuration) { |
528 bundle_policy_ = rtc_configuration.bundle_policy; | 517 bundle_policy_ = rtc_configuration.bundle_policy; |
(...skipping 20 matching lines...) Expand all Loading... |
549 if (rtc_configuration.enable_dtls_srtp) { | 538 if (rtc_configuration.enable_dtls_srtp) { |
550 dtls_enabled_ = *(rtc_configuration.enable_dtls_srtp); | 539 dtls_enabled_ = *(rtc_configuration.enable_dtls_srtp); |
551 } | 540 } |
552 } | 541 } |
553 | 542 |
554 // Enable creation of RTP data channels if the kEnableRtpDataChannels is set. | 543 // Enable creation of RTP data channels if the kEnableRtpDataChannels is set. |
555 // It takes precendence over the disable_sctp_data_channels | 544 // It takes precendence over the disable_sctp_data_channels |
556 // PeerConnectionFactoryInterface::Options. | 545 // PeerConnectionFactoryInterface::Options. |
557 if (rtc_configuration.enable_rtp_data_channel) { | 546 if (rtc_configuration.enable_rtp_data_channel) { |
558 data_channel_type_ = cricket::DCT_RTP; | 547 data_channel_type_ = cricket::DCT_RTP; |
559 } | 548 } else { |
560 #ifdef HAVE_QUIC | |
561 else if (rtc_configuration.enable_quic) { | |
562 // Use QUIC instead of DTLS when |enable_quic| is true. | |
563 data_channel_type_ = cricket::DCT_QUIC; | |
564 transport_controller_->use_quic(); | |
565 if (dtls_enabled_) { | |
566 LOG(LS_INFO) << "Using QUIC instead of DTLS"; | |
567 } | |
568 quic_data_transport_.reset( | |
569 new QuicDataTransport(signaling_thread(), worker_thread(), | |
570 network_thread(), transport_controller_.get())); | |
571 } | |
572 #endif // HAVE_QUIC | |
573 else { | |
574 // DTLS has to be enabled to use SCTP. | 549 // DTLS has to be enabled to use SCTP. |
575 if (!options.disable_sctp_data_channels && dtls_enabled_) { | 550 if (!options.disable_sctp_data_channels && dtls_enabled_) { |
576 data_channel_type_ = cricket::DCT_SCTP; | 551 data_channel_type_ = cricket::DCT_SCTP; |
577 } | 552 } |
578 } | 553 } |
579 | 554 |
580 video_options_.screencast_min_bitrate_kbps = | 555 video_options_.screencast_min_bitrate_kbps = |
581 rtc_configuration.screencast_min_bitrate; | 556 rtc_configuration.screencast_min_bitrate; |
582 audio_options_.combined_audio_video_bwe = | 557 audio_options_.combined_audio_video_bwe = |
583 rtc_configuration.combined_audio_video_bwe; | 558 rtc_configuration.combined_audio_video_bwe; |
(...skipping 469 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1053 | 1028 |
1054 bool WebRtcSession::EnableBundle(const cricket::ContentGroup& bundle) { | 1029 bool WebRtcSession::EnableBundle(const cricket::ContentGroup& bundle) { |
1055 const std::string* first_content_name = bundle.FirstContentName(); | 1030 const std::string* first_content_name = bundle.FirstContentName(); |
1056 if (!first_content_name) { | 1031 if (!first_content_name) { |
1057 LOG(LS_WARNING) << "Tried to BUNDLE with no contents."; | 1032 LOG(LS_WARNING) << "Tried to BUNDLE with no contents."; |
1058 return false; | 1033 return false; |
1059 } | 1034 } |
1060 const std::string& transport_name = *first_content_name; | 1035 const std::string& transport_name = *first_content_name; |
1061 cricket::BaseChannel* first_channel = GetChannel(transport_name); | 1036 cricket::BaseChannel* first_channel = GetChannel(transport_name); |
1062 | 1037 |
1063 #ifdef HAVE_QUIC | |
1064 if (quic_data_transport_ && | |
1065 bundle.HasContentName(quic_data_transport_->content_name()) && | |
1066 quic_data_transport_->transport_name() != transport_name) { | |
1067 LOG(LS_ERROR) << "Unable to BUNDLE " << quic_data_transport_->content_name() | |
1068 << " on " << transport_name << "with QUIC."; | |
1069 } | |
1070 #endif | |
1071 | |
1072 auto maybe_set_transport = [this, bundle, transport_name, | 1038 auto maybe_set_transport = [this, bundle, transport_name, |
1073 first_channel](cricket::BaseChannel* ch) { | 1039 first_channel](cricket::BaseChannel* ch) { |
1074 if (!ch || !bundle.HasContentName(ch->content_name())) { | 1040 if (!ch || !bundle.HasContentName(ch->content_name())) { |
1075 return true; | 1041 return true; |
1076 } | 1042 } |
1077 | 1043 |
1078 if (ch->transport_name() == transport_name) { | 1044 if (ch->transport_name() == transport_name) { |
1079 LOG(LS_INFO) << "BUNDLE already enabled for " << ch->content_name() | 1045 LOG(LS_INFO) << "BUNDLE already enabled for " << ch->content_name() |
1080 << " on " << transport_name << "."; | 1046 << " on " << transport_name << "."; |
1081 return true; | 1047 return true; |
(...skipping 488 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1570 | 1536 |
1571 const cricket::ContentInfo* voice_info = | 1537 const cricket::ContentInfo* voice_info = |
1572 cricket::GetFirstAudioContent(desc); | 1538 cricket::GetFirstAudioContent(desc); |
1573 if ((!voice_info || voice_info->rejected) && voice_channel_) { | 1539 if ((!voice_info || voice_info->rejected) && voice_channel_) { |
1574 SignalVoiceChannelDestroyed(); | 1540 SignalVoiceChannelDestroyed(); |
1575 channel_manager_->DestroyVoiceChannel(voice_channel_.release()); | 1541 channel_manager_->DestroyVoiceChannel(voice_channel_.release()); |
1576 } | 1542 } |
1577 | 1543 |
1578 const cricket::ContentInfo* data_info = | 1544 const cricket::ContentInfo* data_info = |
1579 cricket::GetFirstDataContent(desc); | 1545 cricket::GetFirstDataContent(desc); |
1580 if (!data_info || data_info->rejected) { | 1546 if ((!data_info || data_info->rejected) && data_channel_) { |
1581 if (data_channel_) { | 1547 SignalDataChannelDestroyed(); |
1582 SignalDataChannelDestroyed(); | 1548 channel_manager_->DestroyDataChannel(data_channel_.release()); |
1583 channel_manager_->DestroyDataChannel(data_channel_.release()); | |
1584 } | |
1585 #ifdef HAVE_QUIC | |
1586 // Clean up the existing QuicDataTransport and its QuicTransportChannels. | |
1587 if (quic_data_transport_) { | |
1588 quic_data_transport_.reset(); | |
1589 } | |
1590 #endif | |
1591 } | 1549 } |
1592 } | 1550 } |
1593 | 1551 |
1594 // Returns the name of the transport channel when BUNDLE is enabled, or nullptr | 1552 // Returns the name of the transport channel when BUNDLE is enabled, or nullptr |
1595 // if the channel is not part of any bundle. | 1553 // if the channel is not part of any bundle. |
1596 const std::string* WebRtcSession::GetBundleTransportName( | 1554 const std::string* WebRtcSession::GetBundleTransportName( |
1597 const cricket::ContentInfo* content, | 1555 const cricket::ContentInfo* content, |
1598 const cricket::ContentGroup* bundle) { | 1556 const cricket::ContentGroup* bundle) { |
1599 if (!bundle) { | 1557 if (!bundle) { |
1600 return nullptr; | 1558 return nullptr; |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1694 this, &WebRtcSession::OnDtlsSetupFailure); | 1652 this, &WebRtcSession::OnDtlsSetupFailure); |
1695 | 1653 |
1696 SignalVideoChannelCreated(); | 1654 SignalVideoChannelCreated(); |
1697 video_channel_->SignalSentPacket.connect(this, | 1655 video_channel_->SignalSentPacket.connect(this, |
1698 &WebRtcSession::OnSentPacket_w); | 1656 &WebRtcSession::OnSentPacket_w); |
1699 return true; | 1657 return true; |
1700 } | 1658 } |
1701 | 1659 |
1702 bool WebRtcSession::CreateDataChannel(const cricket::ContentInfo* content, | 1660 bool WebRtcSession::CreateDataChannel(const cricket::ContentInfo* content, |
1703 const std::string* bundle_transport) { | 1661 const std::string* bundle_transport) { |
1704 #ifdef HAVE_QUIC | |
1705 if (data_channel_type_ == cricket::DCT_QUIC) { | |
1706 RTC_DCHECK(transport_controller_->quic()); | |
1707 const std::string transport_name = | |
1708 bundle_transport ? *bundle_transport : content->name; | |
1709 quic_data_transport_->SetTransport(transport_name); | |
1710 return true; | |
1711 } | |
1712 #endif // HAVE_QUIC | |
1713 bool sctp = (data_channel_type_ == cricket::DCT_SCTP); | 1662 bool sctp = (data_channel_type_ == cricket::DCT_SCTP); |
1714 bool require_rtcp_mux = | 1663 bool require_rtcp_mux = |
1715 rtcp_mux_policy_ == PeerConnectionInterface::kRtcpMuxPolicyRequire; | 1664 rtcp_mux_policy_ == PeerConnectionInterface::kRtcpMuxPolicyRequire; |
1716 bool create_rtcp_transport_channel = !sctp && !require_rtcp_mux; | 1665 bool create_rtcp_transport_channel = !sctp && !require_rtcp_mux; |
1717 data_channel_.reset(channel_manager_->CreateDataChannel( | 1666 data_channel_.reset(channel_manager_->CreateDataChannel( |
1718 transport_controller_.get(), content->name, bundle_transport, | 1667 transport_controller_.get(), content->name, bundle_transport, |
1719 create_rtcp_transport_channel, data_channel_type_)); | 1668 create_rtcp_transport_channel, data_channel_type_)); |
1720 if (!data_channel_) { | 1669 if (!data_channel_) { |
1721 return false; | 1670 return false; |
1722 } | 1671 } |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1886 | 1835 |
1887 // We need to check the local/remote description for the Transport instead of | 1836 // We need to check the local/remote description for the Transport instead of |
1888 // the session, because a new Transport added during renegotiation may have | 1837 // the session, because a new Transport added during renegotiation may have |
1889 // them unset while the session has them set from the previous negotiation. | 1838 // them unset while the session has them set from the previous negotiation. |
1890 // Not doing so may trigger the auto generation of transport description and | 1839 // Not doing so may trigger the auto generation of transport description and |
1891 // mess up DTLS identity information, ICE credential, etc. | 1840 // mess up DTLS identity information, ICE credential, etc. |
1892 bool WebRtcSession::ReadyToUseRemoteCandidate( | 1841 bool WebRtcSession::ReadyToUseRemoteCandidate( |
1893 const IceCandidateInterface* candidate, | 1842 const IceCandidateInterface* candidate, |
1894 const SessionDescriptionInterface* remote_desc, | 1843 const SessionDescriptionInterface* remote_desc, |
1895 bool* valid) { | 1844 bool* valid) { |
1896 *valid = true; | 1845 *valid = true;; |
1897 | 1846 |
1898 const SessionDescriptionInterface* current_remote_desc = | 1847 const SessionDescriptionInterface* current_remote_desc = |
1899 remote_desc ? remote_desc : remote_desc_.get(); | 1848 remote_desc ? remote_desc : remote_desc_.get(); |
1900 | 1849 |
1901 if (!current_remote_desc) { | 1850 if (!current_remote_desc) { |
1902 return false; | 1851 return false; |
1903 } | 1852 } |
1904 | 1853 |
1905 size_t mediacontent_index = | 1854 size_t mediacontent_index = |
1906 static_cast<size_t>(candidate->sdp_mline_index()); | 1855 static_cast<size_t>(candidate->sdp_mline_index()); |
1907 size_t remote_content_size = | 1856 size_t remote_content_size = |
1908 current_remote_desc->description()->contents().size(); | 1857 current_remote_desc->description()->contents().size(); |
1909 if (mediacontent_index >= remote_content_size) { | 1858 if (mediacontent_index >= remote_content_size) { |
1910 LOG(LS_ERROR) << "ReadyToUseRemoteCandidate: Invalid candidate media index " | 1859 LOG(LS_ERROR) << "ReadyToUseRemoteCandidate: Invalid candidate media index " |
1911 << mediacontent_index; | 1860 << mediacontent_index; |
1912 | 1861 |
1913 *valid = false; | 1862 *valid = false; |
1914 return false; | 1863 return false; |
1915 } | 1864 } |
1916 | 1865 |
1917 cricket::ContentInfo content = | 1866 cricket::ContentInfo content = |
1918 current_remote_desc->description()->contents()[mediacontent_index]; | 1867 current_remote_desc->description()->contents()[mediacontent_index]; |
1919 | 1868 cricket::BaseChannel* channel = GetChannel(content.name); |
1920 const std::string transport_name = GetTransportName(content.name); | 1869 if (!channel) { |
1921 if (transport_name.empty()) { | |
1922 return false; | 1870 return false; |
1923 } | 1871 } |
1924 return transport_controller_->ReadyForRemoteCandidates(transport_name); | 1872 |
| 1873 return transport_controller_->ReadyForRemoteCandidates( |
| 1874 channel->transport_name()); |
1925 } | 1875 } |
1926 | 1876 |
1927 void WebRtcSession::OnTransportControllerGatheringState( | 1877 void WebRtcSession::OnTransportControllerGatheringState( |
1928 cricket::IceGatheringState state) { | 1878 cricket::IceGatheringState state) { |
1929 ASSERT(signaling_thread()->IsCurrent()); | 1879 ASSERT(signaling_thread()->IsCurrent()); |
1930 if (state == cricket::kIceGatheringGathering) { | 1880 if (state == cricket::kIceGatheringGathering) { |
1931 if (ice_observer_) { | 1881 if (ice_observer_) { |
1932 ice_observer_->OnIceGatheringChange( | 1882 ice_observer_->OnIceGatheringChange( |
1933 PeerConnectionInterface::kIceGatheringGathering); | 1883 PeerConnectionInterface::kIceGatheringGathering); |
1934 } | 1884 } |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2051 metrics_observer_->IncrementSparseEnumCounter(ssl_counter_type, | 2001 metrics_observer_->IncrementSparseEnumCounter(ssl_counter_type, |
2052 ssl_cipher_suite); | 2002 ssl_cipher_suite); |
2053 } | 2003 } |
2054 } | 2004 } |
2055 | 2005 |
2056 void WebRtcSession::OnSentPacket_w(const rtc::SentPacket& sent_packet) { | 2006 void WebRtcSession::OnSentPacket_w(const rtc::SentPacket& sent_packet) { |
2057 RTC_DCHECK(worker_thread()->IsCurrent()); | 2007 RTC_DCHECK(worker_thread()->IsCurrent()); |
2058 media_controller_->call_w()->OnSentPacket(sent_packet); | 2008 media_controller_->call_w()->OnSentPacket(sent_packet); |
2059 } | 2009 } |
2060 | 2010 |
2061 const std::string WebRtcSession::GetTransportName( | |
2062 const std::string& content_name) { | |
2063 cricket::BaseChannel* channel = GetChannel(content_name); | |
2064 if (!channel) { | |
2065 #ifdef HAVE_QUIC | |
2066 if (data_channel_type_ == cricket::DCT_QUIC && quic_data_transport_ && | |
2067 content_name == quic_data_transport_->transport_name()) { | |
2068 return quic_data_transport_->transport_name(); | |
2069 } | |
2070 #endif | |
2071 // Return an empty string if failed to retrieve the transport name. | |
2072 return ""; | |
2073 } | |
2074 return channel->transport_name(); | |
2075 } | |
2076 } // namespace webrtc | 2011 } // namespace webrtc |
OLD | NEW |