/* * libjingle * Copyright 2012, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "talk/app/webrtc/peerconnection.h" #include #include "talk/app/webrtc/dtmfsender.h" #include "talk/app/webrtc/jsepicecandidate.h" #include "talk/app/webrtc/jsepsessiondescription.h" #include "talk/app/webrtc/mediaconstraintsinterface.h" #include "talk/app/webrtc/mediastreamhandler.h" #include "talk/app/webrtc/streamcollection.h" #include "webrtc/p2p/client/basicportallocator.h" #include "talk/session/media/channelmanager.h" #include "webrtc/base/logging.h" #include "webrtc/base/stringencode.h" #include "webrtc/system_wrappers/interface/field_trial.h" namespace { using webrtc::PeerConnectionInterface; // The min number of tokens must present in Turn host uri. // e.g. user@turn.example.org static const size_t kTurnHostTokensNum = 2; // Number of tokens must be preset when TURN uri has transport param. static const size_t kTurnTransportTokensNum = 2; // The default stun port. static const int kDefaultStunPort = 3478; static const int kDefaultStunTlsPort = 5349; static const char kTransport[] = "transport"; static const char kUdpTransportType[] = "udp"; static const char kTcpTransportType[] = "tcp"; // NOTE: Must be in the same order as the ServiceType enum. static const char* kValidIceServiceTypes[] = { "stun", "stuns", "turn", "turns", "invalid" }; enum ServiceType { STUN, // Indicates a STUN server. STUNS, // Indicates a STUN server used with a TLS session. TURN, // Indicates a TURN server TURNS, // Indicates a TURN server used with a TLS session. INVALID, // Unknown. }; enum { MSG_SET_SESSIONDESCRIPTION_SUCCESS = 0, MSG_SET_SESSIONDESCRIPTION_FAILED, MSG_GETSTATS, }; struct SetSessionDescriptionMsg : public rtc::MessageData { explicit SetSessionDescriptionMsg( webrtc::SetSessionDescriptionObserver* observer) : observer(observer) { } rtc::scoped_refptr observer; std::string error; }; struct GetStatsMsg : public rtc::MessageData { GetStatsMsg(webrtc::StatsObserver* observer, webrtc::MediaStreamTrackInterface* track) : observer(observer), track(track) { } rtc::scoped_refptr observer; rtc::scoped_refptr track; }; // |in_str| should be of format // stunURI = scheme ":" stun-host [ ":" stun-port ] // scheme = "stun" / "stuns" // stun-host = IP-literal / IPv4address / reg-name // stun-port = *DIGIT // draft-petithuguenin-behave-turn-uris-01 // turnURI = scheme ":" turn-host [ ":" turn-port ] // turn-host = username@IP-literal / IPv4address / reg-name bool GetServiceTypeAndHostnameFromUri(const std::string& in_str, ServiceType* service_type, std::string* hostname) { std::string::size_type colonpos = in_str.find(':'); if (colonpos == std::string::npos) { return false; } std::string type = in_str.substr(0, colonpos); for (size_t i = 0; i < ARRAY_SIZE(kValidIceServiceTypes); ++i) { if (type.compare(kValidIceServiceTypes[i]) == 0) { *service_type = static_cast(i); break; } } if (*service_type == INVALID) { return false; } *hostname = in_str.substr(colonpos + 1, std::string::npos); return true; } // This method parses IPv6 and IPv4 literal strings, along with hostnames in // standard hostname:port format. // Consider following formats as correct. // |hostname:port|, |[IPV6 address]:port|, |IPv4 address|:port, // |hostname|, |[IPv6 address]|, |IPv4 address| bool ParseHostnameAndPortFromString(const std::string& in_str, std::string* host, int* port) { if (in_str.at(0) == '[') { std::string::size_type closebracket = in_str.rfind(']'); if (closebracket != std::string::npos) { *host = in_str.substr(1, closebracket - 1); std::string::size_type colonpos = in_str.find(':', closebracket); if (std::string::npos != colonpos) { if (!rtc::FromString( in_str.substr(closebracket + 2, std::string::npos), port)) { return false; } } } else { return false; } } else { std::string::size_type colonpos = in_str.find(':'); if (std::string::npos != colonpos) { *host = in_str.substr(0, colonpos); if (!rtc::FromString( in_str.substr(colonpos + 1, std::string::npos), port)) { return false; } } else { *host = in_str; } } return true; } typedef webrtc::PortAllocatorFactoryInterface::StunConfiguration StunConfiguration; typedef webrtc::PortAllocatorFactoryInterface::TurnConfiguration TurnConfiguration; bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration, std::vector* stun_config, std::vector* turn_config) { // draft-nandakumar-rtcweb-stun-uri-01 // stunURI = scheme ":" stun-host [ ":" stun-port ] // scheme = "stun" / "stuns" // stun-host = IP-literal / IPv4address / reg-name // stun-port = *DIGIT // draft-petithuguenin-behave-turn-uris-01 // turnURI = scheme ":" turn-host [ ":" turn-port ] // [ "?transport=" transport ] // scheme = "turn" / "turns" // transport = "udp" / "tcp" / transport-ext // transport-ext = 1*unreserved // turn-host = IP-literal / IPv4address / reg-name // turn-port = *DIGIT for (size_t i = 0; i < configuration.size(); ++i) { webrtc::PeerConnectionInterface::IceServer server = configuration[i]; if (server.uri.empty()) { LOG(WARNING) << "Empty uri."; continue; } std::vector tokens; std::string turn_transport_type = kUdpTransportType; rtc::tokenize(server.uri, '?', &tokens); std::string uri_without_transport = tokens[0]; // Let's look into transport= param, if it exists. if (tokens.size() == kTurnTransportTokensNum) { // ?transport= is present. std::string uri_transport_param = tokens[1]; rtc::tokenize(uri_transport_param, '=', &tokens); if (tokens[0] == kTransport) { // As per above grammar transport param will be consist of lower case // letters. if (tokens[1] != kUdpTransportType && tokens[1] != kTcpTransportType) { LOG(LS_WARNING) << "Transport param should always be udp or tcp."; continue; } turn_transport_type = tokens[1]; } } std::string hoststring; ServiceType service_type = INVALID; if (!GetServiceTypeAndHostnameFromUri(uri_without_transport, &service_type, &hoststring)) { LOG(LS_WARNING) << "Invalid transport parameter in ICE URI: " << uri_without_transport; continue; } // Let's break hostname. tokens.clear(); rtc::tokenize(hoststring, '@', &tokens); hoststring = tokens[0]; if (tokens.size() == kTurnHostTokensNum) { server.username = rtc::s_url_decode(tokens[0]); hoststring = tokens[1]; } int port = kDefaultStunPort; if (service_type == TURNS) { port = kDefaultStunTlsPort; turn_transport_type = kTcpTransportType; } std::string address; if (!ParseHostnameAndPortFromString(hoststring, &address, &port)) { LOG(WARNING) << "Invalid Hostname format: " << uri_without_transport; continue; } if (port <= 0 || port > 0xffff) { LOG(WARNING) << "Invalid port: " << port; continue; } switch (service_type) { case STUN: case STUNS: stun_config->push_back(StunConfiguration(address, port)); break; case TURN: case TURNS: { if (server.username.empty()) { // Turn url example from the spec |url:"turn:user@turn.example.org"|. std::vector turn_tokens; rtc::tokenize(address, '@', &turn_tokens); if (turn_tokens.size() == kTurnHostTokensNum) { server.username = rtc::s_url_decode(turn_tokens[0]); address = turn_tokens[1]; } } bool secure = (service_type == TURNS); turn_config->push_back(TurnConfiguration(address, port, server.username, server.password, turn_transport_type, secure)); break; } case INVALID: default: LOG(WARNING) << "Configuration not supported: " << server.uri; return false; } } return true; } // Check if we can send |new_stream| on a PeerConnection. // Currently only one audio but multiple video track is supported per // PeerConnection. bool CanAddLocalMediaStream(webrtc::StreamCollectionInterface* current_streams, webrtc::MediaStreamInterface* new_stream) { if (!new_stream || !current_streams) return false; if (current_streams->find(new_stream->label()) != NULL) { LOG(LS_ERROR) << "MediaStream with label " << new_stream->label() << " is already added."; return false; } return true; } } // namespace namespace webrtc { PeerConnection::PeerConnection(PeerConnectionFactory* factory) : factory_(factory), observer_(NULL), uma_observer_(NULL), signaling_state_(kStable), ice_state_(kIceNew), ice_connection_state_(kIceConnectionNew), ice_gathering_state_(kIceGatheringNew) { } PeerConnection::~PeerConnection() { if (mediastream_signaling_) mediastream_signaling_->TearDown(); if (stream_handler_container_) stream_handler_container_->TearDown(); } bool PeerConnection::Initialize( const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, PeerConnectionObserver* observer) { std::vector stun_config; std::vector turn_config; if (!ParseIceServers(configuration.servers, &stun_config, &turn_config)) { return false; } return DoInitialize(configuration.type, stun_config, turn_config, constraints, allocator_factory, dtls_identity_service, observer); } bool PeerConnection::DoInitialize( IceTransportsType type, const StunConfigurations& stun_config, const TurnConfigurations& turn_config, const MediaConstraintsInterface* constraints, webrtc::PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, PeerConnectionObserver* observer) { ASSERT(observer != NULL); if (!observer) return false; observer_ = observer; port_allocator_.reset( allocator_factory->CreatePortAllocator(stun_config, turn_config)); // To handle both internal and externally created port allocator, we will // enable BUNDLE here. int portallocator_flags = port_allocator_->flags(); portallocator_flags |= cricket::PORTALLOCATOR_ENABLE_BUNDLE | cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET; bool value; // If IPv6 flag was specified, we'll not override it by experiment. if (FindConstraint( constraints, MediaConstraintsInterface::kEnableIPv6, &value, NULL)) { if (value) { portallocator_flags |= cricket::PORTALLOCATOR_ENABLE_IPV6; } } else if (webrtc::field_trial::FindFullName("WebRTC-IPv6Default") == "Enabled") { portallocator_flags |= cricket::PORTALLOCATOR_ENABLE_IPV6; } port_allocator_->set_flags(portallocator_flags); // No step delay is used while allocating ports. port_allocator_->set_step_delay(cricket::kMinimumStepDelay); mediastream_signaling_.reset(new MediaStreamSignaling( factory_->signaling_thread(), this, factory_->channel_manager())); session_.reset(new WebRtcSession(factory_->channel_manager(), factory_->signaling_thread(), factory_->worker_thread(), port_allocator_.get(), mediastream_signaling_.get())); stream_handler_container_.reset(new MediaStreamHandlerContainer( session_.get(), session_.get())); stats_.reset(new StatsCollector(session_.get())); // Initialize the WebRtcSession. It creates transport channels etc. if (!session_->Initialize(factory_->options(), constraints, dtls_identity_service, type)) return false; // Register PeerConnection as receiver of local ice candidates. // All the callbacks will be posted to the application from PeerConnection. session_->RegisterIceObserver(this); session_->SignalState.connect(this, &PeerConnection::OnSessionStateChange); return true; } rtc::scoped_refptr PeerConnection::local_streams() { return mediastream_signaling_->local_streams(); } rtc::scoped_refptr PeerConnection::remote_streams() { return mediastream_signaling_->remote_streams(); } bool PeerConnection::AddStream(MediaStreamInterface* local_stream) { if (IsClosed()) { return false; } if (!CanAddLocalMediaStream(mediastream_signaling_->local_streams(), local_stream)) return false; if (!mediastream_signaling_->AddLocalStream(local_stream)) { return false; } stats_->AddStream(local_stream); observer_->OnRenegotiationNeeded(); return true; } bool PeerConnection::AddStream(MediaStreamInterface* local_stream, const MediaConstraintsInterface* constraints) { return AddStream(local_stream); } void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) { mediastream_signaling_->RemoveLocalStream(local_stream); if (IsClosed()) { return; } observer_->OnRenegotiationNeeded(); } rtc::scoped_refptr PeerConnection::CreateDtmfSender( AudioTrackInterface* track) { if (!track) { LOG(LS_ERROR) << "CreateDtmfSender - track is NULL."; return NULL; } if (!mediastream_signaling_->local_streams()->FindAudioTrack(track->id())) { LOG(LS_ERROR) << "CreateDtmfSender is called with a non local audio track."; return NULL; } rtc::scoped_refptr sender( DtmfSender::Create(track, signaling_thread(), session_.get())); if (!sender.get()) { LOG(LS_ERROR) << "CreateDtmfSender failed on DtmfSender::Create."; return NULL; } return DtmfSenderProxy::Create(signaling_thread(), sender.get()); } bool PeerConnection::GetStats(StatsObserver* observer, MediaStreamTrackInterface* track, StatsOutputLevel level) { ASSERT(signaling_thread()->IsCurrent()); if (!VERIFY(observer != NULL)) { LOG(LS_ERROR) << "GetStats - observer is NULL."; return false; } stats_->UpdateStats(level); signaling_thread()->Post(this, MSG_GETSTATS, new GetStatsMsg(observer, track)); return true; } PeerConnectionInterface::SignalingState PeerConnection::signaling_state() { return signaling_state_; } PeerConnectionInterface::IceState PeerConnection::ice_state() { return ice_state_; } PeerConnectionInterface::IceConnectionState PeerConnection::ice_connection_state() { return ice_connection_state_; } PeerConnectionInterface::IceGatheringState PeerConnection::ice_gathering_state() { return ice_gathering_state_; } rtc::scoped_refptr PeerConnection::CreateDataChannel( const std::string& label, const DataChannelInit* config) { bool first_datachannel = !mediastream_signaling_->HasDataChannels(); rtc::scoped_ptr internal_config; if (config) { internal_config.reset(new InternalDataChannelInit(*config)); } rtc::scoped_refptr channel( session_->CreateDataChannel(label, internal_config.get())); if (!channel.get()) return NULL; // Trigger the onRenegotiationNeeded event for every new RTP DataChannel, or // the first SCTP DataChannel. if (session_->data_channel_type() == cricket::DCT_RTP || first_datachannel) { observer_->OnRenegotiationNeeded(); } return DataChannelProxy::Create(signaling_thread(), channel.get()); } void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, const MediaConstraintsInterface* constraints) { if (!VERIFY(observer != NULL)) { LOG(LS_ERROR) << "CreateOffer - observer is NULL."; return; } RTCOfferAnswerOptions options; bool value; size_t mandatory_constraints = 0; if (FindConstraint(constraints, MediaConstraintsInterface::kOfferToReceiveAudio, &value, &mandatory_constraints)) { options.offer_to_receive_audio = value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0; } if (FindConstraint(constraints, MediaConstraintsInterface::kOfferToReceiveVideo, &value, &mandatory_constraints)) { options.offer_to_receive_video = value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0; } if (FindConstraint(constraints, MediaConstraintsInterface::kVoiceActivityDetection, &value, &mandatory_constraints)) { options.voice_activity_detection = value; } if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart, &value, &mandatory_constraints)) { options.ice_restart = value; } if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value, &mandatory_constraints)) { options.use_rtp_mux = value; } CreateOffer(observer, options); } void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, const RTCOfferAnswerOptions& options) { if (!VERIFY(observer != NULL)) { LOG(LS_ERROR) << "CreateOffer - observer is NULL."; return; } session_->CreateOffer(observer, options); } void PeerConnection::CreateAnswer( CreateSessionDescriptionObserver* observer, const MediaConstraintsInterface* constraints) { if (!VERIFY(observer != NULL)) { LOG(LS_ERROR) << "CreateAnswer - observer is NULL."; return; } session_->CreateAnswer(observer, constraints); } void PeerConnection::SetLocalDescription( SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) { if (!VERIFY(observer != NULL)) { LOG(LS_ERROR) << "SetLocalDescription - observer is NULL."; return; } if (!desc) { PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); return; } // Update stats here so that we have the most recent stats for tracks and // streams that might be removed by updating the session description. stats_->UpdateStats(kStatsOutputLevelStandard); std::string error; if (!session_->SetLocalDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg); } void PeerConnection::SetRemoteDescription( SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) { if (!VERIFY(observer != NULL)) { LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL."; return; } if (!desc) { PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); return; } // Update stats here so that we have the most recent stats for tracks and // streams that might be removed by updating the session description. stats_->UpdateStats(kStatsOutputLevelStandard); std::string error; if (!session_->SetRemoteDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg); } void PeerConnection::PostSetSessionDescriptionFailure( SetSessionDescriptionObserver* observer, const std::string& error) { SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); msg->error = error; signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_FAILED, msg); } bool PeerConnection::UpdateIce(const IceServers& configuration, const MediaConstraintsInterface* constraints) { return false; } bool PeerConnection::UpdateIce(const RTCConfiguration& config) { if (port_allocator_) { std::vector stuns; std::vector turns; if (!ParseIceServers(config.servers, &stuns, &turns)) { return false; } std::vector stun_hosts; typedef std::vector::const_iterator StunIt; for (StunIt stun_it = stuns.begin(); stun_it != stuns.end(); ++stun_it) { stun_hosts.push_back(stun_it->server); } rtc::SocketAddress stun_addr; if (!stun_hosts.empty()) { stun_addr = stun_hosts.front(); LOG(LS_INFO) << "UpdateIce: StunServer Address: " << stun_addr.ToString(); } for (size_t i = 0; i < turns.size(); ++i) { cricket::RelayCredentials credentials(turns[i].username, turns[i].password); cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); cricket::ProtocolType protocol; if (cricket::StringToProto(turns[i].transport_type.c_str(), &protocol)) { relay_server.ports.push_back(cricket::ProtocolAddress( turns[i].server, protocol, turns[i].secure)); relay_server.credentials = credentials; LOG(LS_INFO) << "UpdateIce: TurnServer Address: " << turns[i].server.ToString(); } else { LOG(LS_WARNING) << "Ignoring TURN server " << turns[i].server << ". " << "Reason= Incorrect " << turns[i].transport_type << " transport parameter."; } } } return session_->SetIceTransports(config.type); } bool PeerConnection::AddIceCandidate( const IceCandidateInterface* ice_candidate) { return session_->ProcessIceMessage(ice_candidate); } void PeerConnection::RegisterUMAObserver(UMAObserver* observer) { uma_observer_ = observer; // Send information about IPv4/IPv6 status. if (uma_observer_ && port_allocator_) { if (port_allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_IPV6) { uma_observer_->IncrementCounter(kPeerConnection_IPv6); } else { uma_observer_->IncrementCounter(kPeerConnection_IPv4); } } } const SessionDescriptionInterface* PeerConnection::local_description() const { return session_->local_description(); } const SessionDescriptionInterface* PeerConnection::remote_description() const { return session_->remote_description(); } void PeerConnection::Close() { // Update stats here so that we have the most recent stats for tracks and // streams before the channels are closed. stats_->UpdateStats(kStatsOutputLevelStandard); session_->Terminate(); } void PeerConnection::OnSessionStateChange(cricket::BaseSession* /*session*/, cricket::BaseSession::State state) { switch (state) { case cricket::BaseSession::STATE_INIT: ChangeSignalingState(PeerConnectionInterface::kStable); break; case cricket::BaseSession::STATE_SENTINITIATE: ChangeSignalingState(PeerConnectionInterface::kHaveLocalOffer); break; case cricket::BaseSession::STATE_SENTPRACCEPT: ChangeSignalingState(PeerConnectionInterface::kHaveLocalPrAnswer); break; case cricket::BaseSession::STATE_RECEIVEDINITIATE: ChangeSignalingState(PeerConnectionInterface::kHaveRemoteOffer); break; case cricket::BaseSession::STATE_RECEIVEDPRACCEPT: ChangeSignalingState(PeerConnectionInterface::kHaveRemotePrAnswer); break; case cricket::BaseSession::STATE_SENTACCEPT: case cricket::BaseSession::STATE_RECEIVEDACCEPT: ChangeSignalingState(PeerConnectionInterface::kStable); break; case cricket::BaseSession::STATE_RECEIVEDTERMINATE: ChangeSignalingState(PeerConnectionInterface::kClosed); break; default: break; } } void PeerConnection::OnMessage(rtc::Message* msg) { switch (msg->message_id) { case MSG_SET_SESSIONDESCRIPTION_SUCCESS: { SetSessionDescriptionMsg* param = static_cast(msg->pdata); param->observer->OnSuccess(); delete param; break; } case MSG_SET_SESSIONDESCRIPTION_FAILED: { SetSessionDescriptionMsg* param = static_cast(msg->pdata); param->observer->OnFailure(param->error); delete param; break; } case MSG_GETSTATS: { GetStatsMsg* param = static_cast(msg->pdata); StatsReports reports; stats_->GetStats(param->track, &reports); param->observer->OnComplete(reports); delete param; break; } default: ASSERT(false && "Not implemented"); break; } } void PeerConnection::OnAddRemoteStream(MediaStreamInterface* stream) { stats_->AddStream(stream); observer_->OnAddStream(stream); } void PeerConnection::OnRemoveRemoteStream(MediaStreamInterface* stream) { stream_handler_container_->RemoveRemoteStream(stream); observer_->OnRemoveStream(stream); } void PeerConnection::OnAddDataChannel(DataChannelInterface* data_channel) { observer_->OnDataChannel(DataChannelProxy::Create(signaling_thread(), data_channel)); } void PeerConnection::OnAddRemoteAudioTrack(MediaStreamInterface* stream, AudioTrackInterface* audio_track, uint32 ssrc) { stream_handler_container_->AddRemoteAudioTrack(stream, audio_track, ssrc); } void PeerConnection::OnAddRemoteVideoTrack(MediaStreamInterface* stream, VideoTrackInterface* video_track, uint32 ssrc) { stream_handler_container_->AddRemoteVideoTrack(stream, video_track, ssrc); } void PeerConnection::OnRemoveRemoteAudioTrack( MediaStreamInterface* stream, AudioTrackInterface* audio_track) { stream_handler_container_->RemoveRemoteTrack(stream, audio_track); } void PeerConnection::OnRemoveRemoteVideoTrack( MediaStreamInterface* stream, VideoTrackInterface* video_track) { stream_handler_container_->RemoveRemoteTrack(stream, video_track); } void PeerConnection::OnAddLocalAudioTrack(MediaStreamInterface* stream, AudioTrackInterface* audio_track, uint32 ssrc) { stream_handler_container_->AddLocalAudioTrack(stream, audio_track, ssrc); stats_->AddLocalAudioTrack(audio_track, ssrc); } void PeerConnection::OnAddLocalVideoTrack(MediaStreamInterface* stream, VideoTrackInterface* video_track, uint32 ssrc) { stream_handler_container_->AddLocalVideoTrack(stream, video_track, ssrc); } void PeerConnection::OnRemoveLocalAudioTrack(MediaStreamInterface* stream, AudioTrackInterface* audio_track, uint32 ssrc) { stream_handler_container_->RemoveLocalTrack(stream, audio_track); stats_->RemoveLocalAudioTrack(audio_track, ssrc); } void PeerConnection::OnRemoveLocalVideoTrack(MediaStreamInterface* stream, VideoTrackInterface* video_track) { stream_handler_container_->RemoveLocalTrack(stream, video_track); } void PeerConnection::OnRemoveLocalStream(MediaStreamInterface* stream) { stream_handler_container_->RemoveLocalStream(stream); } void PeerConnection::OnIceConnectionChange( PeerConnectionInterface::IceConnectionState new_state) { ASSERT(signaling_thread()->IsCurrent()); ice_connection_state_ = new_state; observer_->OnIceConnectionChange(ice_connection_state_); } void PeerConnection::OnIceGatheringChange( PeerConnectionInterface::IceGatheringState new_state) { ASSERT(signaling_thread()->IsCurrent()); if (IsClosed()) { return; } ice_gathering_state_ = new_state; observer_->OnIceGatheringChange(ice_gathering_state_); } void PeerConnection::OnIceCandidate(const IceCandidateInterface* candidate) { ASSERT(signaling_thread()->IsCurrent()); observer_->OnIceCandidate(candidate); } void PeerConnection::OnIceComplete() { ASSERT(signaling_thread()->IsCurrent()); observer_->OnIceComplete(); } void PeerConnection::ChangeSignalingState( PeerConnectionInterface::SignalingState signaling_state) { signaling_state_ = signaling_state; if (signaling_state == kClosed) { ice_connection_state_ = kIceConnectionClosed; observer_->OnIceConnectionChange(ice_connection_state_); if (ice_gathering_state_ != kIceGatheringComplete) { ice_gathering_state_ = kIceGatheringComplete; observer_->OnIceGatheringChange(ice_gathering_state_); } } observer_->OnSignalingChange(signaling_state_); observer_->OnStateChange(PeerConnectionObserver::kSignalingState); } } // namespace webrtc