| Index: remoting/protocol/webrtc_transport.cc
 | 
| diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..4bc629bb0f829d15482e8147cf957acc91fe03f4
 | 
| --- /dev/null
 | 
| +++ b/remoting/protocol/webrtc_transport.cc
 | 
| @@ -0,0 +1,497 @@
 | 
| +// Copyright 2015 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +#include "remoting/protocol/webrtc_transport.h"
 | 
| +
 | 
| +#include "base/callback_helpers.h"
 | 
| +#include "base/single_thread_task_runner.h"
 | 
| +#include "base/strings/string_number_conversions.h"
 | 
| +#include "base/task_runner_util.h"
 | 
| +#include "jingle/glue/thread_wrapper.h"
 | 
| +#include "third_party/libjingle/source/talk/app/webrtc/test/fakeconstraints.h"
 | 
| +#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
 | 
| +#include "third_party/webrtc/modules/audio_device/include/fake_audio_device.h"
 | 
| +
 | 
| +using buzz::QName;
 | 
| +using buzz::XmlElement;
 | 
| +
 | 
| +namespace remoting {
 | 
| +namespace protocol {
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
| +// Delay after candidate creation before sending transport-info message to
 | 
| +// accumulate multiple candidates. This is an optimization to reduce number of
 | 
| +// transport-info messages.
 | 
| +const int kTransportInfoSendDelayMs = 20;
 | 
| +
 | 
| +// XML namespace for the transport elements.
 | 
| +const char kTransportNamespace[] = "google:remoting:webrtc";
 | 
| +
 | 
| +rtc::Thread* InitAndGetRtcThread() {
 | 
| +  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
 | 
| +
 | 
| +  // TODO(sergeyu): Investigate if it's possible to avoid Send().
 | 
| +  jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
 | 
| +
 | 
| +  return jingle_glue::JingleThreadWrapper::current();
 | 
| +}
 | 
| +
 | 
| +// A webrtc::CreateSessionDescriptionObserver implementation used to receive the
 | 
| +// results of creating descriptions for this end of the PeerConnection.
 | 
| +class CreateSessionDescriptionObserver
 | 
| +    : public webrtc::CreateSessionDescriptionObserver {
 | 
| + public:
 | 
| +  typedef base::Callback<void(
 | 
| +      scoped_ptr<webrtc::SessionDescriptionInterface> description,
 | 
| +      const std::string& error)> ResultCallback;
 | 
| +
 | 
| +  static CreateSessionDescriptionObserver* Create(
 | 
| +      const ResultCallback& result_callback) {
 | 
| +    return new rtc::RefCountedObject<CreateSessionDescriptionObserver>(
 | 
| +        result_callback);
 | 
| +  }
 | 
| +  void OnSuccess(webrtc::SessionDescriptionInterface* desc) override {
 | 
| +    base::ResetAndReturn(&result_callback_)
 | 
| +        .Run(make_scoped_ptr(desc), std::string());
 | 
| +  }
 | 
| +  void OnFailure(const std::string& error) override {
 | 
| +    base::ResetAndReturn(&result_callback_).Run(nullptr, error);
 | 
| +  }
 | 
| +
 | 
| + protected:
 | 
| +  explicit CreateSessionDescriptionObserver(
 | 
| +      const ResultCallback& result_callback)
 | 
| +      : result_callback_(result_callback) {}
 | 
| +  ~CreateSessionDescriptionObserver() override {}
 | 
| +
 | 
| + private:
 | 
| +  ResultCallback result_callback_;
 | 
| +
 | 
| +  DISALLOW_COPY_AND_ASSIGN(CreateSessionDescriptionObserver);
 | 
| +};
 | 
| +
 | 
| +// A webrtc::SetSessionDescriptionObserver implementation used to receive the
 | 
| +// results of setting local and remote descriptions of the PeerConnection.
 | 
| +class SetSessionDescriptionObserver
 | 
| +    : public webrtc::SetSessionDescriptionObserver {
 | 
| + public:
 | 
| +  typedef base::Callback<void(bool success, const std::string& error)>
 | 
| +      ResultCallback;
 | 
| +
 | 
| +  static SetSessionDescriptionObserver* Create(
 | 
| +      const ResultCallback& result_callback) {
 | 
| +    return new rtc::RefCountedObject<SetSessionDescriptionObserver>(
 | 
| +        result_callback);
 | 
| +  }
 | 
| +
 | 
| +  void OnSuccess() override {
 | 
| +    base::ResetAndReturn(&result_callback_).Run(true, std::string());
 | 
| +  }
 | 
| +
 | 
| +  void OnFailure(const std::string& error) override {
 | 
| +    base::ResetAndReturn(&result_callback_).Run(false, error);
 | 
| +  }
 | 
| +
 | 
| + protected:
 | 
| +  SetSessionDescriptionObserver(const ResultCallback& result_callback)
 | 
| +      : result_callback_(result_callback) {}
 | 
| +  ~SetSessionDescriptionObserver() override {}
 | 
| +
 | 
| + private:
 | 
| +  ResultCallback result_callback_;
 | 
| +
 | 
| +  DISALLOW_COPY_AND_ASSIGN(SetSessionDescriptionObserver);
 | 
| +};
 | 
| +
 | 
| +}  // namespace
 | 
| +
 | 
| +WebrtcTransport::WebrtcTransport(
 | 
| +    rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface>
 | 
| +        port_allocator_factory,
 | 
| +    TransportRole role,
 | 
| +    scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner)
 | 
| +    : port_allocator_factory_(port_allocator_factory),
 | 
| +      role_(role),
 | 
| +      worker_task_runner_(worker_task_runner),
 | 
| +      weak_factory_(this) {}
 | 
| +
 | 
| +WebrtcTransport::~WebrtcTransport() {}
 | 
| +
 | 
| +void WebrtcTransport::Start(EventHandler* event_handler,
 | 
| +                            Authenticator* authenticator) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  event_handler_ = event_handler;
 | 
| +
 | 
| +  // TODO(sergeyu): Use the |authenticator| to authenticate PeerConnection.
 | 
| +
 | 
| +  base::PostTaskAndReplyWithResult(
 | 
| +      worker_task_runner_.get(), FROM_HERE, base::Bind(&InitAndGetRtcThread),
 | 
| +      base::Bind(&WebrtcTransport::DoStart, weak_factory_.GetWeakPtr()));
 | 
| +}
 | 
| +
 | 
| +bool WebrtcTransport::ProcessTransportInfo(XmlElement* transport_info) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  if (transport_info->Name() != QName(kTransportNamespace, "transport"))
 | 
| +    return false;
 | 
| +
 | 
| +  if (!peer_connection_)
 | 
| +    return false;
 | 
| +
 | 
| +  XmlElement* session_description = transport_info->FirstNamed(
 | 
| +      QName(kTransportNamespace, "session-description"));
 | 
| +  if (session_description) {
 | 
| +    webrtc::PeerConnectionInterface::SignalingState expected_state =
 | 
| +        role_ == TransportRole::SERVER
 | 
| +            ? webrtc::PeerConnectionInterface::kStable
 | 
| +            : webrtc::PeerConnectionInterface::kHaveLocalOffer;
 | 
| +    if (peer_connection_->signaling_state() != expected_state) {
 | 
| +      LOG(ERROR) << "Received unexpected WebRTC session_description. ";
 | 
| +      return false;
 | 
| +    }
 | 
| +
 | 
| +    std::string type = session_description->Attr(QName(std::string(), "type"));
 | 
| +    std::string sdp = session_description->BodyText();
 | 
| +    if (type.empty() || sdp.empty()) {
 | 
| +      LOG(ERROR) << "Incorrect session_description format.";
 | 
| +      return false;
 | 
| +    }
 | 
| +
 | 
| +    webrtc::SdpParseError error;
 | 
| +    scoped_ptr<webrtc::SessionDescriptionInterface> session_description(
 | 
| +        webrtc::CreateSessionDescription(type, sdp, &error));
 | 
| +    if (!session_description) {
 | 
| +      LOG(ERROR) << "Failed to parse the offer: " << error.description
 | 
| +                 << " line: " << error.line;
 | 
| +      return false;
 | 
| +    }
 | 
| +
 | 
| +    peer_connection_->SetRemoteDescription(
 | 
| +        SetSessionDescriptionObserver::Create(
 | 
| +            base::Bind(&WebrtcTransport::OnRemoteDescriptionSet,
 | 
| +                       weak_factory_.GetWeakPtr())),
 | 
| +        session_description.release());
 | 
| +  }
 | 
| +
 | 
| +  XmlElement* candidate_element;
 | 
| +  QName candidate_qname(kTransportNamespace, "candidate");
 | 
| +  for (candidate_element = transport_info->FirstNamed(candidate_qname);
 | 
| +       candidate_element;
 | 
| +       candidate_element = candidate_element->NextNamed(candidate_qname)) {
 | 
| +    std::string candidate_str = candidate_element->BodyText();
 | 
| +    std::string sdp_mid =
 | 
| +        candidate_element->Attr(QName(std::string(), "sdpMid"));
 | 
| +    std::string sdp_mlineindex_str =
 | 
| +        candidate_element->Attr(QName(std::string(), "sdpMLineIndex"));
 | 
| +    int sdp_mlineindex;
 | 
| +    if (candidate_str.empty() || sdp_mid.empty() ||
 | 
| +        !base::StringToInt(sdp_mlineindex_str, &sdp_mlineindex)) {
 | 
| +      LOG(ERROR) << "Failed to parse incoming candidates.";
 | 
| +      return false;
 | 
| +    }
 | 
| +
 | 
| +    webrtc::SdpParseError error;
 | 
| +    scoped_ptr<webrtc::IceCandidateInterface> candidate(
 | 
| +        webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate_str,
 | 
| +                                   &error));
 | 
| +    if (!candidate) {
 | 
| +      LOG(ERROR) << "Failed to parse incoming candidate: " << error.description
 | 
| +                 << " line: " << error.line;
 | 
| +      return false;
 | 
| +    }
 | 
| +
 | 
| +    if (peer_connection_->signaling_state() ==
 | 
| +        webrtc::PeerConnectionInterface::kStable) {
 | 
| +      if (!peer_connection_->AddIceCandidate(candidate.get())) {
 | 
| +        LOG(ERROR) << "Failed to add incoming ICE candidate.";
 | 
| +        return false;
 | 
| +      }
 | 
| +    } else {
 | 
| +      pending_incoming_candidates_.push_back(candidate.Pass());
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  return true;
 | 
| +}
 | 
| +
 | 
| +DatagramChannelFactory* WebrtcTransport::GetDatagramChannelFactory() {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  NOTIMPLEMENTED();
 | 
| +  return nullptr;
 | 
| +}
 | 
| +
 | 
| +StreamChannelFactory* WebrtcTransport::GetStreamChannelFactory() {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  // TODO(sergeyu): Implement data stream support.
 | 
| +  NOTIMPLEMENTED();
 | 
| +  return nullptr;
 | 
| +}
 | 
| +
 | 
| +StreamChannelFactory* WebrtcTransport::GetMultiplexedChannelFactory() {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  return GetStreamChannelFactory();
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::DoStart(rtc::Thread* worker_thread) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
 | 
| +
 | 
| +  // TODO(sergeyu): Investigate if it's possible to avoid Send().
 | 
| +  jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
 | 
| +
 | 
| +  fake_audio_device_module_.reset(new webrtc::FakeAudioDeviceModule());
 | 
| +
 | 
| +  peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
 | 
| +      worker_thread, rtc::Thread::Current(),
 | 
| +      fake_audio_device_module_.get(), nullptr, nullptr);
 | 
| +
 | 
| +  webrtc::PeerConnectionInterface::IceServer stun_server;
 | 
| +  stun_server.urls.push_back("stun:stun.l.google.com:19302");
 | 
| +  webrtc::PeerConnectionInterface::RTCConfiguration rtc_config;
 | 
| +  rtc_config.servers.push_back(stun_server);
 | 
| +
 | 
| +  webrtc::FakeConstraints constraints;
 | 
| +  constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
 | 
| +                           webrtc::MediaConstraintsInterface::kValueTrue);
 | 
| +
 | 
| +  peer_connection_ = peer_connection_factory_->CreatePeerConnection(
 | 
| +      rtc_config, &constraints, port_allocator_factory_, nullptr, this);
 | 
| +
 | 
| +  if (role_ == TransportRole::CLIENT) {
 | 
| +    webrtc::FakeConstraints offer_config;
 | 
| +    offer_config.AddMandatory(
 | 
| +        webrtc::MediaConstraintsInterface::kOfferToReceiveVideo,
 | 
| +        webrtc::MediaConstraintsInterface::kValueTrue);
 | 
| +    offer_config.AddMandatory(
 | 
| +        webrtc::MediaConstraintsInterface::kOfferToReceiveAudio,
 | 
| +        webrtc::MediaConstraintsInterface::kValueFalse);
 | 
| +    peer_connection_->CreateOffer(
 | 
| +        CreateSessionDescriptionObserver::Create(
 | 
| +            base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated,
 | 
| +                       weak_factory_.GetWeakPtr())),
 | 
| +        &offer_config);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnLocalSessionDescriptionCreated(
 | 
| +    scoped_ptr<webrtc::SessionDescriptionInterface> description,
 | 
| +    const std::string& error) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  if (!peer_connection_)
 | 
| +    return;
 | 
| +
 | 
| +  if (!description) {
 | 
| +    LOG(ERROR) << "PeerConnection offer creation failed: " << error;
 | 
| +    Close(CHANNEL_CONNECTION_ERROR);
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  std::string description_sdp;
 | 
| +  if (!description->ToString(&description_sdp)) {
 | 
| +    LOG(ERROR) << "Failed to serialize description.";
 | 
| +    Close(CHANNEL_CONNECTION_ERROR);
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  // Format and send the session description to the peer.
 | 
| +  scoped_ptr<XmlElement> transport_info(
 | 
| +      new XmlElement(QName(kTransportNamespace, "transport"), true));
 | 
| +  XmlElement* offer_tag =
 | 
| +      new XmlElement(QName(kTransportNamespace, "session-description"));
 | 
| +  transport_info->AddElement(offer_tag);
 | 
| +  offer_tag->SetAttr(QName(std::string(), "type"), description->type());
 | 
| +  offer_tag->SetBodyText(description_sdp);
 | 
| +
 | 
| +  event_handler_->OnOutgoingTransportInfo(transport_info.Pass());
 | 
| +
 | 
| +  peer_connection_->SetLocalDescription(
 | 
| +      SetSessionDescriptionObserver::Create(base::Bind(
 | 
| +          &WebrtcTransport::OnLocalDescriptionSet, weak_factory_.GetWeakPtr())),
 | 
| +      description.release());
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnLocalDescriptionSet(bool success,
 | 
| +                                            const std::string& error) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  if (!peer_connection_)
 | 
| +    return;
 | 
| +
 | 
| +  if (!success) {
 | 
| +    LOG(ERROR) << "Failed to set local description: " << error;
 | 
| +    Close(CHANNEL_CONNECTION_ERROR);
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  AddPendingCandidatesIfPossible();
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnRemoteDescriptionSet(bool success,
 | 
| +                                             const std::string& error) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  if (!peer_connection_)
 | 
| +    return;
 | 
| +
 | 
| +  if (!success) {
 | 
| +    LOG(ERROR) << "Failed to set local description: " << error;
 | 
| +    Close(CHANNEL_CONNECTION_ERROR);
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  // Create and send answer on the server.
 | 
| +  if (role_ == TransportRole::SERVER) {
 | 
| +    peer_connection_->CreateAnswer(
 | 
| +        CreateSessionDescriptionObserver::Create(
 | 
| +            base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated,
 | 
| +                       weak_factory_.GetWeakPtr())),
 | 
| +        nullptr);
 | 
| +  }
 | 
| +
 | 
| +  AddPendingCandidatesIfPossible();
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::Close(ErrorCode error) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  weak_factory_.InvalidateWeakPtrs();
 | 
| +  peer_connection_->Close();
 | 
| +  peer_connection_ = nullptr;
 | 
| +  peer_connection_factory_ = nullptr;
 | 
| +
 | 
| +  if (error != OK)
 | 
| +    event_handler_->OnTransportError(error);
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnSignalingChange(
 | 
| +    webrtc::PeerConnectionInterface::SignalingState new_state) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnAddStream(webrtc::MediaStreamInterface* stream) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  LOG(ERROR) << "Stream added " << stream->label();
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnRemoveStream(webrtc::MediaStreamInterface* stream) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  LOG(ERROR) << "Stream removed " << stream->label();
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnDataChannel(
 | 
| +    webrtc::DataChannelInterface* data_channel) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  // TODO(sergeyu): Use the data channel.
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnRenegotiationNeeded() {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  // TODO(sergeyu): Figure out what needs to happen here.
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnIceConnectionChange(
 | 
| +    webrtc::PeerConnectionInterface::IceConnectionState new_state) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  if (new_state == webrtc::PeerConnectionInterface::kIceConnectionConnected)
 | 
| +    event_handler_->OnTransportConnected();
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnIceGatheringChange(
 | 
| +    webrtc::PeerConnectionInterface::IceGatheringState new_state) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::OnIceCandidate(
 | 
| +    const webrtc::IceCandidateInterface* candidate) {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  scoped_ptr<XmlElement> candidate_element(
 | 
| +      new XmlElement(QName(kTransportNamespace, "candidate")));
 | 
| +  std::string candidate_str;
 | 
| +  if (!candidate->ToString(&candidate_str)) {
 | 
| +    LOG(ERROR) << "Failed to serialize local candidate.";
 | 
| +    return;
 | 
| +  }
 | 
| +  candidate_element->SetBodyText(candidate_str);
 | 
| +  candidate_element->SetAttr(QName(std::string(), "sdpMid"),
 | 
| +                             candidate->sdp_mid());
 | 
| +  candidate_element->SetAttr(QName(std::string(), "sdpMLineIndex"),
 | 
| +                             base::IntToString(candidate->sdp_mline_index()));
 | 
| +
 | 
| +  EnsurePendingTransportInfoMessage();
 | 
| +  pending_transport_info_message_->AddElement(candidate_element.release());
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::EnsurePendingTransportInfoMessage() {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  // |transport_info_timer_| must be running iff
 | 
| +  // |pending_transport_info_message_| exists.
 | 
| +  DCHECK_EQ(pending_transport_info_message_ != nullptr,
 | 
| +            transport_info_timer_.IsRunning());
 | 
| +
 | 
| +  if (!pending_transport_info_message_) {
 | 
| +    pending_transport_info_message_.reset(
 | 
| +        new XmlElement(QName(kTransportNamespace, "transport"), true));
 | 
| +
 | 
| +    // Delay sending the new candidates in case we get more candidates
 | 
| +    // that we can send in one message.
 | 
| +    transport_info_timer_.Start(
 | 
| +        FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs),
 | 
| +        this, &WebrtcTransport::SendTransportInfo);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::SendTransportInfo() {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +  DCHECK(pending_transport_info_message_);
 | 
| +
 | 
| +  event_handler_->OnOutgoingTransportInfo(
 | 
| +      pending_transport_info_message_.Pass());
 | 
| +  pending_transport_info_message_.reset();
 | 
| +}
 | 
| +
 | 
| +void WebrtcTransport::AddPendingCandidatesIfPossible() {
 | 
| +  DCHECK(thread_checker_.CalledOnValidThread());
 | 
| +
 | 
| +  if (peer_connection_->signaling_state() ==
 | 
| +      webrtc::PeerConnectionInterface::kStable) {
 | 
| +    for (auto candidate : pending_incoming_candidates_) {
 | 
| +      if (!peer_connection_->AddIceCandidate(candidate)) {
 | 
| +        LOG(ERROR) << "Failed to add incoming candidate";
 | 
| +        Close(INCOMPATIBLE_PROTOCOL);
 | 
| +        return;
 | 
| +      }
 | 
| +    }
 | 
| +    pending_incoming_candidates_.clear();
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +WebrtcTransportFactory::WebrtcTransportFactory(
 | 
| +    SignalStrategy* signal_strategy,
 | 
| +    rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface>
 | 
| +        port_allocator_factory,
 | 
| +    TransportRole role)
 | 
| +    : signal_strategy_(signal_strategy),
 | 
| +      port_allocator_factory_(port_allocator_factory),
 | 
| +      role_(role),
 | 
| +      worker_thread_("ChromotingWebrtcWorkerThread") {
 | 
| +  worker_thread_.StartWithOptions(
 | 
| +      base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
 | 
| +}
 | 
| +
 | 
| +WebrtcTransportFactory::~WebrtcTransportFactory() {}
 | 
| +
 | 
| +scoped_ptr<Transport> WebrtcTransportFactory::CreateTransport() {
 | 
| +  return make_scoped_ptr(new WebrtcTransport(port_allocator_factory_, role_,
 | 
| +                                             worker_thread_.task_runner()));
 | 
| +}
 | 
| +
 | 
| +}  // namespace protocol
 | 
| +}  // namespace remoting
 | 
| 
 |