summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/examples/peerconnection
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/examples/peerconnection
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/examples/peerconnection')
-rw-r--r--third_party/libwebrtc/examples/peerconnection/OWNERS1
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/conductor.cc599
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/conductor.h136
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/defaults.cc59
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/defaults.h29
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/flag_defs.h52
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/linux/main.cc121
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.cc545
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.h128
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/main.cc133
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/main_wnd.cc633
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/main_wnd.h206
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.cc489
-rw-r--r--third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.h129
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/data_socket.cc299
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/data_socket.h152
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/main.cc193
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc360
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/peer_channel.h118
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/server_test.html237
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/utils.cc25
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/utils.h25
22 files changed, 4669 insertions, 0 deletions
diff --git a/third_party/libwebrtc/examples/peerconnection/OWNERS b/third_party/libwebrtc/examples/peerconnection/OWNERS
new file mode 100644
index 0000000000..0fba125734
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/OWNERS
@@ -0,0 +1 @@
+tommi@webrtc.org
diff --git a/third_party/libwebrtc/examples/peerconnection/client/conductor.cc b/third_party/libwebrtc/examples/peerconnection/client/conductor.cc
new file mode 100644
index 0000000000..965525abff
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/conductor.cc
@@ -0,0 +1,599 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/client/conductor.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "absl/types/optional.h"
+#include "api/audio/audio_mixer.h"
+#include "api/audio_codecs/audio_decoder_factory.h"
+#include "api/audio_codecs/audio_encoder_factory.h"
+#include "api/audio_codecs/builtin_audio_decoder_factory.h"
+#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "api/audio_options.h"
+#include "api/create_peerconnection_factory.h"
+#include "api/rtp_sender_interface.h"
+#include "api/video_codecs/builtin_video_decoder_factory.h"
+#include "api/video_codecs/builtin_video_encoder_factory.h"
+#include "api/video_codecs/video_decoder_factory.h"
+#include "api/video_codecs/video_encoder_factory.h"
+#include "examples/peerconnection/client/defaults.h"
+#include "modules/audio_device/include/audio_device.h"
+#include "modules/audio_processing/include/audio_processing.h"
+#include "modules/video_capture/video_capture.h"
+#include "modules/video_capture/video_capture_factory.h"
+#include "p2p/base/port_allocator.h"
+#include "pc/video_track_source.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/rtc_certificate_generator.h"
+#include "rtc_base/strings/json.h"
+#include "test/vcm_capturer.h"
+
+namespace {
+// Names used for a IceCandidate JSON object.
+const char kCandidateSdpMidName[] = "sdpMid";
+const char kCandidateSdpMlineIndexName[] = "sdpMLineIndex";
+const char kCandidateSdpName[] = "candidate";
+
+// Names used for a SessionDescription JSON object.
+const char kSessionDescriptionTypeName[] = "type";
+const char kSessionDescriptionSdpName[] = "sdp";
+
+class DummySetSessionDescriptionObserver
+ : public webrtc::SetSessionDescriptionObserver {
+ public:
+ static rtc::scoped_refptr<DummySetSessionDescriptionObserver> Create() {
+ return rtc::make_ref_counted<DummySetSessionDescriptionObserver>();
+ }
+ virtual void OnSuccess() { RTC_LOG(LS_INFO) << __FUNCTION__; }
+ virtual void OnFailure(webrtc::RTCError error) {
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << ToString(error.type()) << ": "
+ << error.message();
+ }
+};
+
+class CapturerTrackSource : public webrtc::VideoTrackSource {
+ public:
+ static rtc::scoped_refptr<CapturerTrackSource> Create() {
+ const size_t kWidth = 640;
+ const size_t kHeight = 480;
+ const size_t kFps = 30;
+ std::unique_ptr<webrtc::test::VcmCapturer> capturer;
+ std::unique_ptr<webrtc::VideoCaptureModule::DeviceInfo> info(
+ webrtc::VideoCaptureFactory::CreateDeviceInfo());
+ if (!info) {
+ return nullptr;
+ }
+ int num_devices = info->NumberOfDevices();
+ for (int i = 0; i < num_devices; ++i) {
+ capturer = absl::WrapUnique(
+ webrtc::test::VcmCapturer::Create(kWidth, kHeight, kFps, i));
+ if (capturer) {
+ return rtc::make_ref_counted<CapturerTrackSource>(std::move(capturer));
+ }
+ }
+
+ return nullptr;
+ }
+
+ protected:
+ explicit CapturerTrackSource(
+ std::unique_ptr<webrtc::test::VcmCapturer> capturer)
+ : VideoTrackSource(/*remote=*/false), capturer_(std::move(capturer)) {}
+
+ private:
+ rtc::VideoSourceInterface<webrtc::VideoFrame>* source() override {
+ return capturer_.get();
+ }
+ std::unique_ptr<webrtc::test::VcmCapturer> capturer_;
+};
+
+} // namespace
+
+Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd)
+ : peer_id_(-1), loopback_(false), client_(client), main_wnd_(main_wnd) {
+ client_->RegisterObserver(this);
+ main_wnd->RegisterObserver(this);
+}
+
+Conductor::~Conductor() {
+ RTC_DCHECK(!peer_connection_);
+}
+
+bool Conductor::connection_active() const {
+ return peer_connection_ != nullptr;
+}
+
+void Conductor::Close() {
+ client_->SignOut();
+ DeletePeerConnection();
+}
+
+bool Conductor::InitializePeerConnection() {
+ RTC_DCHECK(!peer_connection_factory_);
+ RTC_DCHECK(!peer_connection_);
+
+ if (!signaling_thread_.get()) {
+ signaling_thread_ = rtc::Thread::CreateWithSocketServer();
+ signaling_thread_->Start();
+ }
+ peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
+ nullptr /* network_thread */, nullptr /* worker_thread */,
+ signaling_thread_.get(), nullptr /* default_adm */,
+ webrtc::CreateBuiltinAudioEncoderFactory(),
+ webrtc::CreateBuiltinAudioDecoderFactory(),
+ webrtc::CreateBuiltinVideoEncoderFactory(),
+ webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,
+ nullptr /* audio_processing */);
+
+ if (!peer_connection_factory_) {
+ main_wnd_->MessageBox("Error", "Failed to initialize PeerConnectionFactory",
+ true);
+ DeletePeerConnection();
+ return false;
+ }
+
+ if (!CreatePeerConnection()) {
+ main_wnd_->MessageBox("Error", "CreatePeerConnection failed", true);
+ DeletePeerConnection();
+ }
+
+ AddTracks();
+
+ return peer_connection_ != nullptr;
+}
+
+bool Conductor::ReinitializePeerConnectionForLoopback() {
+ loopback_ = true;
+ std::vector<rtc::scoped_refptr<webrtc::RtpSenderInterface>> senders =
+ peer_connection_->GetSenders();
+ peer_connection_ = nullptr;
+ // Loopback is only possible if encryption is disabled.
+ webrtc::PeerConnectionFactoryInterface::Options options;
+ options.disable_encryption = true;
+ peer_connection_factory_->SetOptions(options);
+ if (CreatePeerConnection()) {
+ for (const auto& sender : senders) {
+ peer_connection_->AddTrack(sender->track(), sender->stream_ids());
+ }
+ peer_connection_->CreateOffer(
+ this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
+ }
+ options.disable_encryption = false;
+ peer_connection_factory_->SetOptions(options);
+ return peer_connection_ != nullptr;
+}
+
+bool Conductor::CreatePeerConnection() {
+ RTC_DCHECK(peer_connection_factory_);
+ RTC_DCHECK(!peer_connection_);
+
+ webrtc::PeerConnectionInterface::RTCConfiguration config;
+ config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
+ webrtc::PeerConnectionInterface::IceServer server;
+ server.uri = GetPeerConnectionString();
+ config.servers.push_back(server);
+
+ webrtc::PeerConnectionDependencies pc_dependencies(this);
+ auto error_or_peer_connection =
+ peer_connection_factory_->CreatePeerConnectionOrError(
+ config, std::move(pc_dependencies));
+ if (error_or_peer_connection.ok()) {
+ peer_connection_ = std::move(error_or_peer_connection.value());
+ }
+ return peer_connection_ != nullptr;
+}
+
+void Conductor::DeletePeerConnection() {
+ main_wnd_->StopLocalRenderer();
+ main_wnd_->StopRemoteRenderer();
+ peer_connection_ = nullptr;
+ peer_connection_factory_ = nullptr;
+ peer_id_ = -1;
+ loopback_ = false;
+}
+
+void Conductor::EnsureStreamingUI() {
+ RTC_DCHECK(peer_connection_);
+ if (main_wnd_->IsWindow()) {
+ if (main_wnd_->current_ui() != MainWindow::STREAMING)
+ main_wnd_->SwitchToStreamingUI();
+ }
+}
+
+//
+// PeerConnectionObserver implementation.
+//
+
+void Conductor::OnAddTrack(
+ rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
+ const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>&
+ streams) {
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id();
+ main_wnd_->QueueUIThreadCallback(NEW_TRACK_ADDED,
+ receiver->track().release());
+}
+
+void Conductor::OnRemoveTrack(
+ rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id();
+ main_wnd_->QueueUIThreadCallback(TRACK_REMOVED, receiver->track().release());
+}
+
+void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
+ // For loopback test. To save some connecting delay.
+ if (loopback_) {
+ if (!peer_connection_->AddIceCandidate(candidate)) {
+ RTC_LOG(LS_WARNING) << "Failed to apply the received candidate";
+ }
+ return;
+ }
+
+ Json::Value jmessage;
+ jmessage[kCandidateSdpMidName] = candidate->sdp_mid();
+ jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index();
+ std::string sdp;
+ if (!candidate->ToString(&sdp)) {
+ RTC_LOG(LS_ERROR) << "Failed to serialize candidate";
+ return;
+ }
+ jmessage[kCandidateSdpName] = sdp;
+
+ Json::StreamWriterBuilder factory;
+ SendMessage(Json::writeString(factory, jmessage));
+}
+
+//
+// PeerConnectionClientObserver implementation.
+//
+
+void Conductor::OnSignedIn() {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+ main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnDisconnected() {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+
+ DeletePeerConnection();
+
+ if (main_wnd_->IsWindow())
+ main_wnd_->SwitchToConnectUI();
+}
+
+void Conductor::OnPeerConnected(int id, const std::string& name) {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+ // Refresh the list if we're showing it.
+ if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
+ main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnPeerDisconnected(int id) {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+ if (id == peer_id_) {
+ RTC_LOG(LS_INFO) << "Our peer disconnected";
+ main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL);
+ } else {
+ // Refresh the list if we're showing it.
+ if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
+ main_wnd_->SwitchToPeerList(client_->peers());
+ }
+}
+
+void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) {
+ RTC_DCHECK(peer_id_ == peer_id || peer_id_ == -1);
+ RTC_DCHECK(!message.empty());
+
+ if (!peer_connection_.get()) {
+ RTC_DCHECK(peer_id_ == -1);
+ peer_id_ = peer_id;
+
+ if (!InitializePeerConnection()) {
+ RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";
+ client_->SignOut();
+ return;
+ }
+ } else if (peer_id != peer_id_) {
+ RTC_DCHECK(peer_id_ != -1);
+ RTC_LOG(LS_WARNING)
+ << "Received a message from unknown peer while already in a "
+ "conversation with a different peer.";
+ return;
+ }
+
+ Json::CharReaderBuilder factory;
+ std::unique_ptr<Json::CharReader> reader =
+ absl::WrapUnique(factory.newCharReader());
+ Json::Value jmessage;
+ if (!reader->parse(message.data(), message.data() + message.length(),
+ &jmessage, nullptr)) {
+ RTC_LOG(LS_WARNING) << "Received unknown message. " << message;
+ return;
+ }
+ std::string type_str;
+ std::string json_object;
+
+ rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName,
+ &type_str);
+ if (!type_str.empty()) {
+ if (type_str == "offer-loopback") {
+ // This is a loopback call.
+ // Recreate the peerconnection with DTLS disabled.
+ if (!ReinitializePeerConnectionForLoopback()) {
+ RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";
+ DeletePeerConnection();
+ client_->SignOut();
+ }
+ return;
+ }
+ absl::optional<webrtc::SdpType> type_maybe =
+ webrtc::SdpTypeFromString(type_str);
+ if (!type_maybe) {
+ RTC_LOG(LS_ERROR) << "Unknown SDP type: " << type_str;
+ return;
+ }
+ webrtc::SdpType type = *type_maybe;
+ std::string sdp;
+ if (!rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName,
+ &sdp)) {
+ RTC_LOG(LS_WARNING)
+ << "Can't parse received session description message.";
+ return;
+ }
+ webrtc::SdpParseError error;
+ std::unique_ptr<webrtc::SessionDescriptionInterface> session_description =
+ webrtc::CreateSessionDescription(type, sdp, &error);
+ if (!session_description) {
+ RTC_LOG(LS_WARNING)
+ << "Can't parse received session description message. "
+ "SdpParseError was: "
+ << error.description;
+ return;
+ }
+ RTC_LOG(LS_INFO) << " Received session description :" << message;
+ peer_connection_->SetRemoteDescription(
+ DummySetSessionDescriptionObserver::Create().get(),
+ session_description.release());
+ if (type == webrtc::SdpType::kOffer) {
+ peer_connection_->CreateAnswer(
+ this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
+ }
+ } else {
+ std::string sdp_mid;
+ int sdp_mlineindex = 0;
+ std::string sdp;
+ if (!rtc::GetStringFromJsonObject(jmessage, kCandidateSdpMidName,
+ &sdp_mid) ||
+ !rtc::GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName,
+ &sdp_mlineindex) ||
+ !rtc::GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) {
+ RTC_LOG(LS_WARNING) << "Can't parse received message.";
+ return;
+ }
+ webrtc::SdpParseError error;
+ std::unique_ptr<webrtc::IceCandidateInterface> candidate(
+ webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error));
+ if (!candidate.get()) {
+ RTC_LOG(LS_WARNING) << "Can't parse received candidate message. "
+ "SdpParseError was: "
+ << error.description;
+ return;
+ }
+ if (!peer_connection_->AddIceCandidate(candidate.get())) {
+ RTC_LOG(LS_WARNING) << "Failed to apply the received candidate";
+ return;
+ }
+ RTC_LOG(LS_INFO) << " Received candidate :" << message;
+ }
+}
+
+void Conductor::OnMessageSent(int err) {
+ // Process the next pending message if any.
+ main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL);
+}
+
+void Conductor::OnServerConnectionFailure() {
+ main_wnd_->MessageBox("Error", ("Failed to connect to " + server_).c_str(),
+ true);
+}
+
+//
+// MainWndCallback implementation.
+//
+
+void Conductor::StartLogin(const std::string& server, int port) {
+ if (client_->is_connected())
+ return;
+ server_ = server;
+ client_->Connect(server, port, GetPeerName());
+}
+
+void Conductor::DisconnectFromServer() {
+ if (client_->is_connected())
+ client_->SignOut();
+}
+
+void Conductor::ConnectToPeer(int peer_id) {
+ RTC_DCHECK(peer_id_ == -1);
+ RTC_DCHECK(peer_id != -1);
+
+ if (peer_connection_.get()) {
+ main_wnd_->MessageBox(
+ "Error", "We only support connecting to one peer at a time", true);
+ return;
+ }
+
+ if (InitializePeerConnection()) {
+ peer_id_ = peer_id;
+ peer_connection_->CreateOffer(
+ this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
+ } else {
+ main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
+ }
+}
+
+void Conductor::AddTracks() {
+ if (!peer_connection_->GetSenders().empty()) {
+ return; // Already added tracks.
+ }
+
+ rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+ peer_connection_factory_->CreateAudioTrack(
+ kAudioLabel,
+ peer_connection_factory_->CreateAudioSource(cricket::AudioOptions())
+ .get()));
+ auto result_or_error = peer_connection_->AddTrack(audio_track, {kStreamId});
+ if (!result_or_error.ok()) {
+ RTC_LOG(LS_ERROR) << "Failed to add audio track to PeerConnection: "
+ << result_or_error.error().message();
+ }
+
+ rtc::scoped_refptr<CapturerTrackSource> video_device =
+ CapturerTrackSource::Create();
+ if (video_device) {
+ rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track_(
+ peer_connection_factory_->CreateVideoTrack(kVideoLabel,
+ video_device.get()));
+ main_wnd_->StartLocalRenderer(video_track_.get());
+
+ result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId});
+ if (!result_or_error.ok()) {
+ RTC_LOG(LS_ERROR) << "Failed to add video track to PeerConnection: "
+ << result_or_error.error().message();
+ }
+ } else {
+ RTC_LOG(LS_ERROR) << "OpenVideoCaptureDevice failed";
+ }
+
+ main_wnd_->SwitchToStreamingUI();
+}
+
+void Conductor::DisconnectFromCurrentPeer() {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+ if (peer_connection_.get()) {
+ client_->SendHangUp(peer_id_);
+ DeletePeerConnection();
+ }
+
+ if (main_wnd_->IsWindow())
+ main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::UIThreadCallback(int msg_id, void* data) {
+ switch (msg_id) {
+ case PEER_CONNECTION_CLOSED:
+ RTC_LOG(LS_INFO) << "PEER_CONNECTION_CLOSED";
+ DeletePeerConnection();
+
+ if (main_wnd_->IsWindow()) {
+ if (client_->is_connected()) {
+ main_wnd_->SwitchToPeerList(client_->peers());
+ } else {
+ main_wnd_->SwitchToConnectUI();
+ }
+ } else {
+ DisconnectFromServer();
+ }
+ break;
+
+ case SEND_MESSAGE_TO_PEER: {
+ RTC_LOG(LS_INFO) << "SEND_MESSAGE_TO_PEER";
+ std::string* msg = reinterpret_cast<std::string*>(data);
+ if (msg) {
+ // For convenience, we always run the message through the queue.
+ // This way we can be sure that messages are sent to the server
+ // in the same order they were signaled without much hassle.
+ pending_messages_.push_back(msg);
+ }
+
+ if (!pending_messages_.empty() && !client_->IsSendingMessage()) {
+ msg = pending_messages_.front();
+ pending_messages_.pop_front();
+
+ if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) {
+ RTC_LOG(LS_ERROR) << "SendToPeer failed";
+ DisconnectFromServer();
+ }
+ delete msg;
+ }
+
+ if (!peer_connection_.get())
+ peer_id_ = -1;
+
+ break;
+ }
+
+ case NEW_TRACK_ADDED: {
+ auto* track = reinterpret_cast<webrtc::MediaStreamTrackInterface*>(data);
+ if (track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
+ auto* video_track = static_cast<webrtc::VideoTrackInterface*>(track);
+ main_wnd_->StartRemoteRenderer(video_track);
+ }
+ track->Release();
+ break;
+ }
+
+ case TRACK_REMOVED: {
+ // Remote peer stopped sending a track.
+ auto* track = reinterpret_cast<webrtc::MediaStreamTrackInterface*>(data);
+ track->Release();
+ break;
+ }
+
+ default:
+ RTC_DCHECK_NOTREACHED();
+ break;
+ }
+}
+
+void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) {
+ peer_connection_->SetLocalDescription(
+ DummySetSessionDescriptionObserver::Create().get(), desc);
+
+ std::string sdp;
+ desc->ToString(&sdp);
+
+ // For loopback test. To save some connecting delay.
+ if (loopback_) {
+ // Replace message type from "offer" to "answer"
+ std::unique_ptr<webrtc::SessionDescriptionInterface> session_description =
+ webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp);
+ peer_connection_->SetRemoteDescription(
+ DummySetSessionDescriptionObserver::Create().get(),
+ session_description.release());
+ return;
+ }
+
+ Json::Value jmessage;
+ jmessage[kSessionDescriptionTypeName] =
+ webrtc::SdpTypeToString(desc->GetType());
+ jmessage[kSessionDescriptionSdpName] = sdp;
+
+ Json::StreamWriterBuilder factory;
+ SendMessage(Json::writeString(factory, jmessage));
+}
+
+void Conductor::OnFailure(webrtc::RTCError error) {
+ RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message();
+}
+
+void Conductor::SendMessage(const std::string& json_object) {
+ std::string* msg = new std::string(json_object);
+ main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg);
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/client/conductor.h b/third_party/libwebrtc/examples/peerconnection/client/conductor.h
new file mode 100644
index 0000000000..80617d3cf4
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/conductor.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_
+#define EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_
+
+#include <deque>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "api/media_stream_interface.h"
+#include "api/peer_connection_interface.h"
+#include "examples/peerconnection/client/main_wnd.h"
+#include "examples/peerconnection/client/peer_connection_client.h"
+#include "rtc_base/thread.h"
+
+namespace webrtc {
+class VideoCaptureModule;
+} // namespace webrtc
+
+namespace cricket {
+class VideoRenderer;
+} // namespace cricket
+
+class Conductor : public webrtc::PeerConnectionObserver,
+ public webrtc::CreateSessionDescriptionObserver,
+ public PeerConnectionClientObserver,
+ public MainWndCallback {
+ public:
+ enum CallbackID {
+ MEDIA_CHANNELS_INITIALIZED = 1,
+ PEER_CONNECTION_CLOSED,
+ SEND_MESSAGE_TO_PEER,
+ NEW_TRACK_ADDED,
+ TRACK_REMOVED,
+ };
+
+ Conductor(PeerConnectionClient* client, MainWindow* main_wnd);
+
+ bool connection_active() const;
+
+ void Close() override;
+
+ protected:
+ ~Conductor();
+ bool InitializePeerConnection();
+ bool ReinitializePeerConnectionForLoopback();
+ bool CreatePeerConnection();
+ void DeletePeerConnection();
+ void EnsureStreamingUI();
+ void AddTracks();
+
+ //
+ // PeerConnectionObserver implementation.
+ //
+
+ void OnSignalingChange(
+ webrtc::PeerConnectionInterface::SignalingState new_state) override {}
+ void OnAddTrack(
+ rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
+ const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>&
+ streams) override;
+ void OnRemoveTrack(
+ rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override;
+ void OnDataChannel(
+ rtc::scoped_refptr<webrtc::DataChannelInterface> channel) override {}
+ void OnRenegotiationNeeded() override {}
+ void OnIceConnectionChange(
+ webrtc::PeerConnectionInterface::IceConnectionState new_state) override {}
+ void OnIceGatheringChange(
+ webrtc::PeerConnectionInterface::IceGatheringState new_state) override {}
+ void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
+ void OnIceConnectionReceivingChange(bool receiving) override {}
+
+ //
+ // PeerConnectionClientObserver implementation.
+ //
+
+ void OnSignedIn() override;
+
+ void OnDisconnected() override;
+
+ void OnPeerConnected(int id, const std::string& name) override;
+
+ void OnPeerDisconnected(int id) override;
+
+ void OnMessageFromPeer(int peer_id, const std::string& message) override;
+
+ void OnMessageSent(int err) override;
+
+ void OnServerConnectionFailure() override;
+
+ //
+ // MainWndCallback implementation.
+ //
+
+ void StartLogin(const std::string& server, int port) override;
+
+ void DisconnectFromServer() override;
+
+ void ConnectToPeer(int peer_id) override;
+
+ void DisconnectFromCurrentPeer() override;
+
+ void UIThreadCallback(int msg_id, void* data) override;
+
+ // CreateSessionDescriptionObserver implementation.
+ void OnSuccess(webrtc::SessionDescriptionInterface* desc) override;
+ void OnFailure(webrtc::RTCError error) override;
+
+ protected:
+ // Send a message to the remote peer.
+ void SendMessage(const std::string& json_object);
+
+ int peer_id_;
+ bool loopback_;
+ std::unique_ptr<rtc::Thread> signaling_thread_;
+ rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+ rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+ peer_connection_factory_;
+ PeerConnectionClient* client_;
+ MainWindow* main_wnd_;
+ std::deque<std::string*> pending_messages_;
+ std::string server_;
+};
+
+#endif // EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/client/defaults.cc b/third_party/libwebrtc/examples/peerconnection/client/defaults.cc
new file mode 100644
index 0000000000..ee3a9e1f0a
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/defaults.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/client/defaults.h"
+
+#include <stdlib.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "rtc_base/arraysize.h"
+
+const char kAudioLabel[] = "audio_label";
+const char kVideoLabel[] = "video_label";
+const char kStreamId[] = "stream_id";
+const uint16_t kDefaultServerPort = 8888;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+ const char* default_value) {
+ std::string value;
+ const char* env_var = getenv(env_var_name);
+ if (env_var)
+ value = env_var;
+
+ if (value.empty())
+ value = default_value;
+
+ return value;
+}
+
+std::string GetPeerConnectionString() {
+ return GetEnvVarOrDefault("WEBRTC_CONNECT", "stun:stun.l.google.com:19302");
+}
+
+std::string GetDefaultServerName() {
+ return GetEnvVarOrDefault("WEBRTC_SERVER", "localhost");
+}
+
+std::string GetPeerName() {
+ char computer_name[256];
+ std::string ret(GetEnvVarOrDefault("USERNAME", "user"));
+ ret += '@';
+ if (gethostname(computer_name, arraysize(computer_name)) == 0) {
+ ret += computer_name;
+ } else {
+ ret += "host";
+ }
+ return ret;
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/client/defaults.h b/third_party/libwebrtc/examples/peerconnection/client/defaults.h
new file mode 100644
index 0000000000..30936fd9d4
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/defaults.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_
+#define EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+extern const char kAudioLabel[];
+extern const char kVideoLabel[];
+extern const char kStreamId[];
+extern const uint16_t kDefaultServerPort;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+ const char* default_value);
+std::string GetPeerConnectionString();
+std::string GetDefaultServerName();
+std::string GetPeerName();
+
+#endif // EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/client/flag_defs.h b/third_party/libwebrtc/examples/peerconnection/client/flag_defs.h
new file mode 100644
index 0000000000..986daf64ce
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/flag_defs.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_
+#define EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_
+
+#include <string>
+
+#include "absl/flags/flag.h"
+
+extern const uint16_t kDefaultServerPort; // From defaults.[h|cc]
+
+// Define flags for the peerconnect_client testing tool, in a separate
+// header file so that they can be shared across the different main.cc's
+// for each platform.
+
+ABSL_FLAG(bool,
+ autoconnect,
+ false,
+ "Connect to the server without user "
+ "intervention.");
+ABSL_FLAG(std::string, server, "localhost", "The server to connect to.");
+ABSL_FLAG(int,
+ port,
+ kDefaultServerPort,
+ "The port on which the server is listening.");
+ABSL_FLAG(
+ bool,
+ autocall,
+ false,
+ "Call the first available other client on "
+ "the server without user intervention. Note: this flag should only be set "
+ "to true on one of the two clients.");
+
+ABSL_FLAG(
+ std::string,
+ force_fieldtrials,
+ "",
+ "Field trials control experimental features. This flag specifies the field "
+ "trials in effect. E.g. running with "
+ "--force_fieldtrials=WebRTC-FooFeature/Enabled/ "
+ "will assign the group Enabled to field trial WebRTC-FooFeature. Multiple "
+ "trials are separated by \"/\"");
+
+#endif // EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/client/linux/main.cc b/third_party/libwebrtc/examples/peerconnection/client/linux/main.cc
new file mode 100644
index 0000000000..ad3d671073
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/linux/main.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <stdio.h>
+
+#include "absl/flags/parse.h"
+#include "api/scoped_refptr.h"
+#include "examples/peerconnection/client/conductor.h"
+#include "examples/peerconnection/client/flag_defs.h"
+#include "examples/peerconnection/client/linux/main_wnd.h"
+#include "examples/peerconnection/client/peer_connection_client.h"
+#include "rtc_base/physical_socket_server.h"
+#include "rtc_base/ssl_adapter.h"
+#include "rtc_base/thread.h"
+#include "system_wrappers/include/field_trial.h"
+#include "test/field_trial.h"
+
+class CustomSocketServer : public rtc::PhysicalSocketServer {
+ public:
+ explicit CustomSocketServer(GtkMainWnd* wnd)
+ : wnd_(wnd), conductor_(NULL), client_(NULL) {}
+ virtual ~CustomSocketServer() {}
+
+ void SetMessageQueue(rtc::Thread* queue) override { message_queue_ = queue; }
+
+ void set_client(PeerConnectionClient* client) { client_ = client; }
+ void set_conductor(Conductor* conductor) { conductor_ = conductor; }
+
+ // Override so that we can also pump the GTK message loop.
+ // This function never waits.
+ bool Wait(webrtc::TimeDelta max_wait_duration, bool process_io) override {
+ // Pump GTK events.
+ // TODO(henrike): We really should move either the socket server or UI to a
+ // different thread. Alternatively we could look at merging the two loops
+ // by implementing a dispatcher for the socket server and/or use
+ // g_main_context_set_poll_func.
+ while (gtk_events_pending())
+ gtk_main_iteration();
+
+ if (!wnd_->IsWindow() && !conductor_->connection_active() &&
+ client_ != NULL && !client_->is_connected()) {
+ message_queue_->Quit();
+ }
+ return rtc::PhysicalSocketServer::Wait(webrtc::TimeDelta::Zero(),
+ process_io);
+ }
+
+ protected:
+ rtc::Thread* message_queue_;
+ GtkMainWnd* wnd_;
+ Conductor* conductor_;
+ PeerConnectionClient* client_;
+};
+
+int main(int argc, char* argv[]) {
+ gtk_init(&argc, &argv);
+// g_type_init API is deprecated (and does nothing) since glib 2.35.0, see:
+// https://mail.gnome.org/archives/commits-list/2012-November/msg07809.html
+#if !GLIB_CHECK_VERSION(2, 35, 0)
+ g_type_init();
+#endif
+// g_thread_init API is deprecated since glib 2.31.0, see release note:
+// http://mail.gnome.org/archives/gnome-announce-list/2011-October/msg00041.html
+#if !GLIB_CHECK_VERSION(2, 31, 0)
+ g_thread_init(NULL);
+#endif
+
+ absl::ParseCommandLine(argc, argv);
+
+ // InitFieldTrialsFromString stores the char*, so the char array must outlive
+ // the application.
+ const std::string forced_field_trials =
+ absl::GetFlag(FLAGS_force_fieldtrials);
+ webrtc::field_trial::InitFieldTrialsFromString(forced_field_trials.c_str());
+
+ // Abort if the user specifies a port that is outside the allowed
+ // range [1, 65535].
+ if ((absl::GetFlag(FLAGS_port) < 1) || (absl::GetFlag(FLAGS_port) > 65535)) {
+ printf("Error: %i is not a valid port.\n", absl::GetFlag(FLAGS_port));
+ return -1;
+ }
+
+ const std::string server = absl::GetFlag(FLAGS_server);
+ GtkMainWnd wnd(server.c_str(), absl::GetFlag(FLAGS_port),
+ absl::GetFlag(FLAGS_autoconnect),
+ absl::GetFlag(FLAGS_autocall));
+ wnd.Create();
+
+ CustomSocketServer socket_server(&wnd);
+ rtc::AutoSocketServerThread thread(&socket_server);
+
+ rtc::InitializeSSL();
+ // Must be constructed after we set the socketserver.
+ PeerConnectionClient client;
+ auto conductor = rtc::make_ref_counted<Conductor>(&client, &wnd);
+ socket_server.set_client(&client);
+ socket_server.set_conductor(conductor.get());
+
+ thread.Run();
+
+ // gtk_main();
+ wnd.Destroy();
+
+ // TODO(henrike): Run the Gtk main loop to tear down the connection.
+ /*
+ while (gtk_events_pending()) {
+ gtk_main_iteration();
+ }
+ */
+ rtc::CleanupSSL();
+ return 0;
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.cc b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.cc
new file mode 100644
index 0000000000..2be75d8f8d
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.cc
@@ -0,0 +1,545 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/client/linux/main_wnd.h"
+
+#include <cairo.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib-object.h>
+#include <glib.h>
+#include <gobject/gclosure.h>
+#include <gtk/gtk.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cstdint>
+#include <map>
+#include <utility>
+
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame_buffer.h"
+#include "api/video/video_rotation.h"
+#include "api/video/video_source_interface.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "third_party/libyuv/include/libyuv/convert.h"
+#include "third_party/libyuv/include/libyuv/convert_from.h"
+
+namespace {
+
+//
+// Simple static functions that simply forward the callback to the
+// GtkMainWnd instance.
+//
+
+gboolean OnDestroyedCallback(GtkWidget* widget,
+ GdkEvent* event,
+ gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnDestroyed(widget, event);
+ return FALSE;
+}
+
+void OnClickedCallback(GtkWidget* widget, gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnClicked(widget);
+}
+
+gboolean SimulateButtonClick(gpointer button) {
+ g_signal_emit_by_name(button, "clicked");
+ return false;
+}
+
+gboolean OnKeyPressCallback(GtkWidget* widget,
+ GdkEventKey* key,
+ gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnKeyPress(widget, key);
+ return false;
+}
+
+void OnRowActivatedCallback(GtkTreeView* tree_view,
+ GtkTreePath* path,
+ GtkTreeViewColumn* column,
+ gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnRowActivated(tree_view, path, column);
+}
+
+gboolean SimulateLastRowActivated(gpointer data) {
+ GtkTreeView* tree_view = reinterpret_cast<GtkTreeView*>(data);
+ GtkTreeModel* model = gtk_tree_view_get_model(tree_view);
+
+ // "if iter is NULL, then the number of toplevel nodes is returned."
+ int rows = gtk_tree_model_iter_n_children(model, NULL);
+ GtkTreePath* lastpath = gtk_tree_path_new_from_indices(rows - 1, -1);
+
+ // Select the last item in the list
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view);
+ gtk_tree_selection_select_path(selection, lastpath);
+
+ // Our TreeView only has one column, so it is column 0.
+ GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view, 0);
+
+ gtk_tree_view_row_activated(tree_view, lastpath, column);
+
+ gtk_tree_path_free(lastpath);
+ return false;
+}
+
+// Creates a tree view, that we use to display the list of peers.
+void InitializeList(GtkWidget* list) {
+ GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
+ GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes(
+ "List Items", renderer, "text", 0, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+ GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+ g_object_unref(store);
+}
+
+// Adds an entry to a tree view.
+void AddToList(GtkWidget* list, const gchar* str, int value) {
+ GtkListStore* store =
+ GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
+
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, str, 1, value, -1);
+}
+
+struct UIThreadCallbackData {
+ explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d)
+ : callback(cb), msg_id(id), data(d) {}
+ MainWndCallback* callback;
+ int msg_id;
+ void* data;
+};
+
+gboolean HandleUIThreadCallback(gpointer data) {
+ UIThreadCallbackData* cb_data = reinterpret_cast<UIThreadCallbackData*>(data);
+ cb_data->callback->UIThreadCallback(cb_data->msg_id, cb_data->data);
+ delete cb_data;
+ return false;
+}
+
+gboolean Redraw(gpointer data) {
+ GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data);
+ wnd->OnRedraw();
+ return false;
+}
+
+gboolean Draw(GtkWidget* widget, cairo_t* cr, gpointer data) {
+ GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data);
+ wnd->Draw(widget, cr);
+ return false;
+}
+
+} // namespace
+
+//
+// GtkMainWnd implementation.
+//
+
+GtkMainWnd::GtkMainWnd(const char* server,
+ int port,
+ bool autoconnect,
+ bool autocall)
+ : window_(NULL),
+ draw_area_(NULL),
+ vbox_(NULL),
+ server_edit_(NULL),
+ port_edit_(NULL),
+ peer_list_(NULL),
+ callback_(NULL),
+ server_(server),
+ autoconnect_(autoconnect),
+ autocall_(autocall) {
+ char buffer[10];
+ snprintf(buffer, sizeof(buffer), "%i", port);
+ port_ = buffer;
+}
+
+GtkMainWnd::~GtkMainWnd() {
+ RTC_DCHECK(!IsWindow());
+}
+
+void GtkMainWnd::RegisterObserver(MainWndCallback* callback) {
+ callback_ = callback;
+}
+
+bool GtkMainWnd::IsWindow() {
+ return window_ != NULL && GTK_IS_WINDOW(window_);
+}
+
+void GtkMainWnd::MessageBox(const char* caption,
+ const char* text,
+ bool is_error) {
+ GtkWidget* dialog = gtk_message_dialog_new(
+ GTK_WINDOW(window_), GTK_DIALOG_DESTROY_WITH_PARENT,
+ is_error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s",
+ text);
+ gtk_window_set_title(GTK_WINDOW(dialog), caption);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+MainWindow::UI GtkMainWnd::current_ui() {
+ if (vbox_)
+ return CONNECT_TO_SERVER;
+
+ if (peer_list_)
+ return LIST_PEERS;
+
+ return STREAMING;
+}
+
+void GtkMainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) {
+ local_renderer_.reset(new VideoRenderer(this, local_video));
+}
+
+void GtkMainWnd::StopLocalRenderer() {
+ local_renderer_.reset();
+}
+
+void GtkMainWnd::StartRemoteRenderer(
+ webrtc::VideoTrackInterface* remote_video) {
+ remote_renderer_.reset(new VideoRenderer(this, remote_video));
+}
+
+void GtkMainWnd::StopRemoteRenderer() {
+ remote_renderer_.reset();
+}
+
+void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) {
+ g_idle_add(HandleUIThreadCallback,
+ new UIThreadCallbackData(callback_, msg_id, data));
+}
+
+bool GtkMainWnd::Create() {
+ RTC_DCHECK(window_ == NULL);
+
+ window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ if (window_) {
+ gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
+ gtk_window_set_default_size(GTK_WINDOW(window_), 640, 480);
+ gtk_window_set_title(GTK_WINDOW(window_), "PeerConnection client");
+ g_signal_connect(G_OBJECT(window_), "delete-event",
+ G_CALLBACK(&OnDestroyedCallback), this);
+ g_signal_connect(window_, "key-press-event", G_CALLBACK(OnKeyPressCallback),
+ this);
+
+ SwitchToConnectUI();
+ }
+
+ return window_ != NULL;
+}
+
+bool GtkMainWnd::Destroy() {
+ if (!IsWindow())
+ return false;
+
+ gtk_widget_destroy(window_);
+ window_ = NULL;
+
+ return true;
+}
+
+void GtkMainWnd::SwitchToConnectUI() {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+
+ RTC_DCHECK(IsWindow());
+ RTC_DCHECK(vbox_ == NULL);
+
+ gtk_container_set_border_width(GTK_CONTAINER(window_), 10);
+
+ if (peer_list_) {
+ gtk_widget_destroy(peer_list_);
+ peer_list_ = NULL;
+ }
+
+ vbox_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
+ GtkWidget* valign = gtk_alignment_new(0, 1, 0, 0);
+ gtk_container_add(GTK_CONTAINER(vbox_), valign);
+ gtk_container_add(GTK_CONTAINER(window_), vbox_);
+
+ GtkWidget* hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
+
+ GtkWidget* label = gtk_label_new("Server");
+ gtk_container_add(GTK_CONTAINER(hbox), label);
+
+ server_edit_ = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str());
+ gtk_widget_set_size_request(server_edit_, 400, 30);
+ gtk_container_add(GTK_CONTAINER(hbox), server_edit_);
+
+ port_edit_ = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str());
+ gtk_widget_set_size_request(port_edit_, 70, 30);
+ gtk_container_add(GTK_CONTAINER(hbox), port_edit_);
+
+ GtkWidget* button = gtk_button_new_with_label("Connect");
+ gtk_widget_set_size_request(button, 70, 30);
+ g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this);
+ gtk_container_add(GTK_CONTAINER(hbox), button);
+
+ GtkWidget* halign = gtk_alignment_new(1, 0, 0, 0);
+ gtk_container_add(GTK_CONTAINER(halign), hbox);
+ gtk_box_pack_start(GTK_BOX(vbox_), halign, FALSE, FALSE, 0);
+
+ gtk_widget_show_all(window_);
+
+ if (autoconnect_)
+ g_idle_add(SimulateButtonClick, button);
+}
+
+void GtkMainWnd::SwitchToPeerList(const Peers& peers) {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+
+ if (!peer_list_) {
+ gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
+ if (vbox_) {
+ gtk_widget_destroy(vbox_);
+ vbox_ = NULL;
+ server_edit_ = NULL;
+ port_edit_ = NULL;
+ } else if (draw_area_) {
+ gtk_widget_destroy(draw_area_);
+ draw_area_ = NULL;
+ draw_buffer_.reset();
+ }
+
+ peer_list_ = gtk_tree_view_new();
+ g_signal_connect(peer_list_, "row-activated",
+ G_CALLBACK(OnRowActivatedCallback), this);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE);
+ InitializeList(peer_list_);
+ gtk_container_add(GTK_CONTAINER(window_), peer_list_);
+ gtk_widget_show_all(window_);
+ } else {
+ GtkListStore* store =
+ GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_)));
+ gtk_list_store_clear(store);
+ }
+
+ AddToList(peer_list_, "List of currently connected peers:", -1);
+ for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i)
+ AddToList(peer_list_, i->second.c_str(), i->first);
+
+ if (autocall_ && peers.begin() != peers.end())
+ g_idle_add(SimulateLastRowActivated, peer_list_);
+}
+
+void GtkMainWnd::SwitchToStreamingUI() {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+
+ RTC_DCHECK(draw_area_ == NULL);
+
+ gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
+ if (peer_list_) {
+ gtk_widget_destroy(peer_list_);
+ peer_list_ = NULL;
+ }
+
+ draw_area_ = gtk_drawing_area_new();
+ gtk_container_add(GTK_CONTAINER(window_), draw_area_);
+ g_signal_connect(G_OBJECT(draw_area_), "draw", G_CALLBACK(&::Draw), this);
+
+ gtk_widget_show_all(window_);
+}
+
+void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) {
+ callback_->Close();
+ window_ = NULL;
+ draw_area_ = NULL;
+ vbox_ = NULL;
+ server_edit_ = NULL;
+ port_edit_ = NULL;
+ peer_list_ = NULL;
+}
+
+void GtkMainWnd::OnClicked(GtkWidget* widget) {
+ // Make the connect button insensitive, so that it cannot be clicked more than
+ // once. Now that the connection includes auto-retry, it should not be
+ // necessary to click it more than once.
+ gtk_widget_set_sensitive(widget, false);
+ server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_));
+ port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_));
+ int port = port_.length() ? atoi(port_.c_str()) : 0;
+ callback_->StartLogin(server_, port);
+}
+
+void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) {
+ if (key->type == GDK_KEY_PRESS) {
+ switch (key->keyval) {
+ case GDK_KEY_Escape:
+ if (draw_area_) {
+ callback_->DisconnectFromCurrentPeer();
+ } else if (peer_list_) {
+ callback_->DisconnectFromServer();
+ }
+ break;
+
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_Return:
+ if (vbox_) {
+ OnClicked(NULL);
+ } else if (peer_list_) {
+ // OnRowActivated will be called automatically when the user
+ // presses enter.
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view,
+ GtkTreePath* path,
+ GtkTreeViewColumn* column) {
+ RTC_DCHECK(peer_list_ != NULL);
+ GtkTreeIter iter;
+ GtkTreeModel* model;
+ GtkTreeSelection* selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ char* text;
+ int id = -1;
+ gtk_tree_model_get(model, &iter, 0, &text, 1, &id, -1);
+ if (id != -1)
+ callback_->ConnectToPeer(id);
+ g_free(text);
+ }
+}
+
+void GtkMainWnd::OnRedraw() {
+ gdk_threads_enter();
+
+ VideoRenderer* remote_renderer = remote_renderer_.get();
+ if (remote_renderer && remote_renderer->image() != NULL &&
+ draw_area_ != NULL) {
+ width_ = remote_renderer->width();
+ height_ = remote_renderer->height();
+
+ if (!draw_buffer_.get()) {
+ draw_buffer_size_ = (width_ * height_ * 4) * 4;
+ draw_buffer_.reset(new uint8_t[draw_buffer_size_]);
+ gtk_widget_set_size_request(draw_area_, width_ * 2, height_ * 2);
+ }
+
+ const uint32_t* image =
+ reinterpret_cast<const uint32_t*>(remote_renderer->image());
+ uint32_t* scaled = reinterpret_cast<uint32_t*>(draw_buffer_.get());
+ for (int r = 0; r < height_; ++r) {
+ for (int c = 0; c < width_; ++c) {
+ int x = c * 2;
+ scaled[x] = scaled[x + 1] = image[c];
+ }
+
+ uint32_t* prev_line = scaled;
+ scaled += width_ * 2;
+ memcpy(scaled, prev_line, (width_ * 2) * 4);
+
+ image += width_;
+ scaled += width_ * 2;
+ }
+
+ VideoRenderer* local_renderer = local_renderer_.get();
+ if (local_renderer && local_renderer->image()) {
+ image = reinterpret_cast<const uint32_t*>(local_renderer->image());
+ scaled = reinterpret_cast<uint32_t*>(draw_buffer_.get());
+ // Position the local preview on the right side.
+ scaled += (width_ * 2) - (local_renderer->width() / 2);
+ // right margin...
+ scaled -= 10;
+ // ... towards the bottom.
+ scaled += (height_ * width_ * 4) - ((local_renderer->height() / 2) *
+ (local_renderer->width() / 2) * 4);
+ // bottom margin...
+ scaled -= (width_ * 2) * 5;
+ for (int r = 0; r < local_renderer->height(); r += 2) {
+ for (int c = 0; c < local_renderer->width(); c += 2) {
+ scaled[c / 2] = image[c + r * local_renderer->width()];
+ }
+ scaled += width_ * 2;
+ }
+ }
+
+ gtk_widget_queue_draw(draw_area_);
+ }
+
+ gdk_threads_leave();
+}
+
+void GtkMainWnd::Draw(GtkWidget* widget, cairo_t* cr) {
+ cairo_format_t format = CAIRO_FORMAT_ARGB32;
+ cairo_surface_t* surface = cairo_image_surface_create_for_data(
+ draw_buffer_.get(), format, width_ * 2, height_ * 2,
+ cairo_format_stride_for_width(format, width_ * 2));
+ cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_rectangle(cr, 0, 0, width_ * 2, height_ * 2);
+ cairo_fill(cr);
+ cairo_surface_destroy(surface);
+}
+
+GtkMainWnd::VideoRenderer::VideoRenderer(
+ GtkMainWnd* main_wnd,
+ webrtc::VideoTrackInterface* track_to_render)
+ : width_(0),
+ height_(0),
+ main_wnd_(main_wnd),
+ rendered_track_(track_to_render) {
+ rendered_track_->AddOrUpdateSink(this, rtc::VideoSinkWants());
+}
+
+GtkMainWnd::VideoRenderer::~VideoRenderer() {
+ rendered_track_->RemoveSink(this);
+}
+
+void GtkMainWnd::VideoRenderer::SetSize(int width, int height) {
+ gdk_threads_enter();
+
+ if (width_ == width && height_ == height) {
+ return;
+ }
+
+ width_ = width;
+ height_ = height;
+ image_.reset(new uint8_t[width * height * 4]);
+ gdk_threads_leave();
+}
+
+void GtkMainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) {
+ gdk_threads_enter();
+
+ rtc::scoped_refptr<webrtc::I420BufferInterface> buffer(
+ video_frame.video_frame_buffer()->ToI420());
+ if (video_frame.rotation() != webrtc::kVideoRotation_0) {
+ buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation());
+ }
+ SetSize(buffer->width(), buffer->height());
+
+ // TODO(bugs.webrtc.org/6857): This conversion is correct for little-endian
+ // only. Cairo ARGB32 treats pixels as 32-bit values in *native* byte order,
+ // with B in the least significant byte of the 32-bit value. Which on
+ // little-endian means that memory layout is BGRA, with the B byte stored at
+ // lowest address. Libyuv's ARGB format (surprisingly?) uses the same
+ // little-endian format, with B in the first byte in memory, regardless of
+ // native endianness.
+ libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(),
+ buffer->StrideU(), buffer->DataV(), buffer->StrideV(),
+ image_.get(), width_ * 4, buffer->width(),
+ buffer->height());
+
+ gdk_threads_leave();
+
+ g_idle_add(Redraw, main_wnd_);
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.h b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.h
new file mode 100644
index 0000000000..3b31e1be3b
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_
+#define EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "api/media_stream_interface.h"
+#include "api/scoped_refptr.h"
+#include "api/video/video_frame.h"
+#include "api/video/video_sink_interface.h"
+#include "examples/peerconnection/client/main_wnd.h"
+#include "examples/peerconnection/client/peer_connection_client.h"
+
+// Forward declarations.
+typedef struct _GtkWidget GtkWidget;
+typedef union _GdkEvent GdkEvent;
+typedef struct _GdkEventKey GdkEventKey;
+typedef struct _GtkTreeView GtkTreeView;
+typedef struct _GtkTreePath GtkTreePath;
+typedef struct _GtkTreeViewColumn GtkTreeViewColumn;
+typedef struct _cairo cairo_t;
+
+// Implements the main UI of the peer connection client.
+// This is functionally equivalent to the MainWnd class in the Windows
+// implementation.
+class GtkMainWnd : public MainWindow {
+ public:
+ GtkMainWnd(const char* server, int port, bool autoconnect, bool autocall);
+ ~GtkMainWnd();
+
+ virtual void RegisterObserver(MainWndCallback* callback);
+ virtual bool IsWindow();
+ virtual void SwitchToConnectUI();
+ virtual void SwitchToPeerList(const Peers& peers);
+ virtual void SwitchToStreamingUI();
+ virtual void MessageBox(const char* caption, const char* text, bool is_error);
+ virtual MainWindow::UI current_ui();
+ virtual void StartLocalRenderer(webrtc::VideoTrackInterface* local_video);
+ virtual void StopLocalRenderer();
+ virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video);
+ virtual void StopRemoteRenderer();
+
+ virtual void QueueUIThreadCallback(int msg_id, void* data);
+
+ // Creates and shows the main window with the |Connect UI| enabled.
+ bool Create();
+
+ // Destroys the window. When the window is destroyed, it ends the
+ // main message loop.
+ bool Destroy();
+
+ // Callback for when the main window is destroyed.
+ void OnDestroyed(GtkWidget* widget, GdkEvent* event);
+
+ // Callback for when the user clicks the "Connect" button.
+ void OnClicked(GtkWidget* widget);
+
+ // Callback for keystrokes. Used to capture Esc and Return.
+ void OnKeyPress(GtkWidget* widget, GdkEventKey* key);
+
+ // Callback when the user double clicks a peer in order to initiate a
+ // connection.
+ void OnRowActivated(GtkTreeView* tree_view,
+ GtkTreePath* path,
+ GtkTreeViewColumn* column);
+
+ void OnRedraw();
+
+ void Draw(GtkWidget* widget, cairo_t* cr);
+
+ protected:
+ class VideoRenderer : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
+ public:
+ VideoRenderer(GtkMainWnd* main_wnd,
+ webrtc::VideoTrackInterface* track_to_render);
+ virtual ~VideoRenderer();
+
+ // VideoSinkInterface implementation
+ void OnFrame(const webrtc::VideoFrame& frame) override;
+
+ const uint8_t* image() const { return image_.get(); }
+
+ int width() const { return width_; }
+
+ int height() const { return height_; }
+
+ protected:
+ void SetSize(int width, int height);
+ std::unique_ptr<uint8_t[]> image_;
+ int width_;
+ int height_;
+ GtkMainWnd* main_wnd_;
+ rtc::scoped_refptr<webrtc::VideoTrackInterface> rendered_track_;
+ };
+
+ protected:
+ GtkWidget* window_; // Our main window.
+ GtkWidget* draw_area_; // The drawing surface for rendering video streams.
+ GtkWidget* vbox_; // Container for the Connect UI.
+ GtkWidget* server_edit_;
+ GtkWidget* port_edit_;
+ GtkWidget* peer_list_; // The list of peers.
+ MainWndCallback* callback_;
+ std::string server_;
+ std::string port_;
+ bool autoconnect_;
+ bool autocall_;
+ std::unique_ptr<VideoRenderer> local_renderer_;
+ std::unique_ptr<VideoRenderer> remote_renderer_;
+ int width_;
+ int height_;
+ std::unique_ptr<uint8_t[]> draw_buffer_;
+ int draw_buffer_size_;
+};
+
+#endif // EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/client/main.cc b/third_party/libwebrtc/examples/peerconnection/client/main.cc
new file mode 100644
index 0000000000..32bc52bda4
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/main.cc
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+// clang-format off
+// clang formating would change include order.
+#include <windows.h>
+#include <shellapi.h> // must come after windows.h
+// clang-format on
+
+#include <string>
+#include <vector>
+
+#include "absl/flags/parse.h"
+#include "examples/peerconnection/client/conductor.h"
+#include "examples/peerconnection/client/flag_defs.h"
+#include "examples/peerconnection/client/main_wnd.h"
+#include "examples/peerconnection/client/peer_connection_client.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/ssl_adapter.h"
+#include "rtc_base/string_utils.h" // For ToUtf8
+#include "rtc_base/win32_socket_init.h"
+#include "system_wrappers/include/field_trial.h"
+#include "test/field_trial.h"
+
+namespace {
+// A helper class to translate Windows command line arguments into UTF8,
+// which then allows us to just pass them to the flags system.
+// This encapsulates all the work of getting the command line and translating
+// it to an array of 8-bit strings; all you have to do is create one of these,
+// and then call argc() and argv().
+class WindowsCommandLineArguments {
+ public:
+ WindowsCommandLineArguments();
+
+ WindowsCommandLineArguments(const WindowsCommandLineArguments&) = delete;
+ WindowsCommandLineArguments& operator=(WindowsCommandLineArguments&) = delete;
+
+ int argc() { return argv_.size(); }
+ char** argv() { return argv_.data(); }
+
+ private:
+ // Owned argument strings.
+ std::vector<std::string> args_;
+ // Pointers, to get layout compatible with char** argv.
+ std::vector<char*> argv_;
+};
+
+WindowsCommandLineArguments::WindowsCommandLineArguments() {
+ // start by getting the command line.
+ LPCWSTR command_line = ::GetCommandLineW();
+ // now, convert it to a list of wide char strings.
+ int argc;
+ LPWSTR* wide_argv = ::CommandLineToArgvW(command_line, &argc);
+
+ // iterate over the returned wide strings;
+ for (int i = 0; i < argc; ++i) {
+ args_.push_back(rtc::ToUtf8(wide_argv[i], wcslen(wide_argv[i])));
+ // make sure the argv array points to the string data.
+ argv_.push_back(const_cast<char*>(args_.back().c_str()));
+ }
+ LocalFree(wide_argv);
+}
+
+} // namespace
+int PASCAL wWinMain(HINSTANCE instance,
+ HINSTANCE prev_instance,
+ wchar_t* cmd_line,
+ int cmd_show) {
+ rtc::WinsockInitializer winsock_init;
+ rtc::PhysicalSocketServer ss;
+ rtc::AutoSocketServerThread main_thread(&ss);
+
+ WindowsCommandLineArguments win_args;
+ int argc = win_args.argc();
+ char** argv = win_args.argv();
+
+ absl::ParseCommandLine(argc, argv);
+
+ // InitFieldTrialsFromString stores the char*, so the char array must outlive
+ // the application.
+ const std::string forced_field_trials =
+ absl::GetFlag(FLAGS_force_fieldtrials);
+ webrtc::field_trial::InitFieldTrialsFromString(forced_field_trials.c_str());
+
+ // Abort if the user specifies a port that is outside the allowed
+ // range [1, 65535].
+ if ((absl::GetFlag(FLAGS_port) < 1) || (absl::GetFlag(FLAGS_port) > 65535)) {
+ printf("Error: %i is not a valid port.\n", absl::GetFlag(FLAGS_port));
+ return -1;
+ }
+
+ const std::string server = absl::GetFlag(FLAGS_server);
+ MainWnd wnd(server.c_str(), absl::GetFlag(FLAGS_port),
+ absl::GetFlag(FLAGS_autoconnect), absl::GetFlag(FLAGS_autocall));
+ if (!wnd.Create()) {
+ RTC_DCHECK_NOTREACHED();
+ return -1;
+ }
+
+ rtc::InitializeSSL();
+ PeerConnectionClient client;
+ auto conductor = rtc::make_ref_counted<Conductor>(&client, &wnd);
+
+ // Main loop.
+ MSG msg;
+ BOOL gm;
+ while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
+ if (!wnd.PreTranslateMessage(&msg)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ }
+
+ if (conductor->connection_active() || client.is_connected()) {
+ while ((conductor->connection_active() || client.is_connected()) &&
+ (gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
+ if (!wnd.PreTranslateMessage(&msg)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ }
+ }
+
+ rtc::CleanupSSL();
+ return 0;
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/client/main_wnd.cc b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.cc
new file mode 100644
index 0000000000..afafa621b3
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.cc
@@ -0,0 +1,633 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/client/main_wnd.h"
+
+#include <math.h>
+
+#include "api/video/i420_buffer.h"
+#include "examples/peerconnection/client/defaults.h"
+#include "rtc_base/arraysize.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "third_party/libyuv/include/libyuv/convert_argb.h"
+
+ATOM MainWnd::wnd_class_ = 0;
+const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd";
+
+namespace {
+
+const char kConnecting[] = "Connecting... ";
+const char kNoVideoStreams[] = "(no video streams either way)";
+const char kNoIncomingStream[] = "(no incoming video)";
+
+void CalculateWindowSizeForText(HWND wnd,
+ const wchar_t* text,
+ size_t* width,
+ size_t* height) {
+ HDC dc = ::GetDC(wnd);
+ RECT text_rc = {0};
+ ::DrawTextW(dc, text, -1, &text_rc, DT_CALCRECT | DT_SINGLELINE);
+ ::ReleaseDC(wnd, dc);
+ RECT client, window;
+ ::GetClientRect(wnd, &client);
+ ::GetWindowRect(wnd, &window);
+
+ *width = text_rc.right - text_rc.left;
+ *width += (window.right - window.left) - (client.right - client.left);
+ *height = text_rc.bottom - text_rc.top;
+ *height += (window.bottom - window.top) - (client.bottom - client.top);
+}
+
+HFONT GetDefaultFont() {
+ static HFONT font = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
+ return font;
+}
+
+std::string GetWindowText(HWND wnd) {
+ char text[MAX_PATH] = {0};
+ ::GetWindowTextA(wnd, &text[0], ARRAYSIZE(text));
+ return text;
+}
+
+void AddListBoxItem(HWND listbox, const std::string& str, LPARAM item_data) {
+ LRESULT index = ::SendMessageA(listbox, LB_ADDSTRING, 0,
+ reinterpret_cast<LPARAM>(str.c_str()));
+ ::SendMessageA(listbox, LB_SETITEMDATA, index, item_data);
+}
+
+} // namespace
+
+MainWnd::MainWnd(const char* server,
+ int port,
+ bool auto_connect,
+ bool auto_call)
+ : ui_(CONNECT_TO_SERVER),
+ wnd_(NULL),
+ edit1_(NULL),
+ edit2_(NULL),
+ label1_(NULL),
+ label2_(NULL),
+ button_(NULL),
+ listbox_(NULL),
+ destroyed_(false),
+ nested_msg_(NULL),
+ callback_(NULL),
+ server_(server),
+ auto_connect_(auto_connect),
+ auto_call_(auto_call) {
+ char buffer[10];
+ snprintf(buffer, sizeof(buffer), "%i", port);
+ port_ = buffer;
+}
+
+MainWnd::~MainWnd() {
+ RTC_DCHECK(!IsWindow());
+}
+
+bool MainWnd::Create() {
+ RTC_DCHECK(wnd_ == NULL);
+ if (!RegisterWindowClass())
+ return false;
+
+ ui_thread_id_ = ::GetCurrentThreadId();
+ wnd_ =
+ ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC",
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), this);
+
+ ::SendMessage(wnd_, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()),
+ TRUE);
+
+ CreateChildWindows();
+ SwitchToConnectUI();
+
+ return wnd_ != NULL;
+}
+
+bool MainWnd::Destroy() {
+ BOOL ret = FALSE;
+ if (IsWindow()) {
+ ret = ::DestroyWindow(wnd_);
+ }
+
+ return ret != FALSE;
+}
+
+void MainWnd::RegisterObserver(MainWndCallback* callback) {
+ callback_ = callback;
+}
+
+bool MainWnd::IsWindow() {
+ return wnd_ && ::IsWindow(wnd_) != FALSE;
+}
+
+bool MainWnd::PreTranslateMessage(MSG* msg) {
+ bool ret = false;
+ if (msg->message == WM_CHAR) {
+ if (msg->wParam == VK_TAB) {
+ HandleTabbing();
+ ret = true;
+ } else if (msg->wParam == VK_RETURN) {
+ OnDefaultAction();
+ ret = true;
+ } else if (msg->wParam == VK_ESCAPE) {
+ if (callback_) {
+ if (ui_ == STREAMING) {
+ callback_->DisconnectFromCurrentPeer();
+ } else {
+ callback_->DisconnectFromServer();
+ }
+ }
+ }
+ } else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) {
+ callback_->UIThreadCallback(static_cast<int>(msg->wParam),
+ reinterpret_cast<void*>(msg->lParam));
+ ret = true;
+ }
+ return ret;
+}
+
+void MainWnd::SwitchToConnectUI() {
+ RTC_DCHECK(IsWindow());
+ LayoutPeerListUI(false);
+ ui_ = CONNECT_TO_SERVER;
+ LayoutConnectUI(true);
+ ::SetFocus(edit1_);
+
+ if (auto_connect_)
+ ::PostMessage(button_, BM_CLICK, 0, 0);
+}
+
+void MainWnd::SwitchToPeerList(const Peers& peers) {
+ LayoutConnectUI(false);
+
+ ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0);
+
+ AddListBoxItem(listbox_, "List of currently connected peers:", -1);
+ Peers::const_iterator i = peers.begin();
+ for (; i != peers.end(); ++i)
+ AddListBoxItem(listbox_, i->second.c_str(), i->first);
+
+ ui_ = LIST_PEERS;
+ LayoutPeerListUI(true);
+ ::SetFocus(listbox_);
+
+ if (auto_call_ && peers.begin() != peers.end()) {
+ // Get the number of items in the list
+ LRESULT count = ::SendMessage(listbox_, LB_GETCOUNT, 0, 0);
+ if (count != LB_ERR) {
+ // Select the last item in the list
+ LRESULT selection = ::SendMessage(listbox_, LB_SETCURSEL, count - 1, 0);
+ if (selection != LB_ERR)
+ ::PostMessage(wnd_, WM_COMMAND,
+ MAKEWPARAM(GetDlgCtrlID(listbox_), LBN_DBLCLK),
+ reinterpret_cast<LPARAM>(listbox_));
+ }
+ }
+}
+
+void MainWnd::SwitchToStreamingUI() {
+ LayoutConnectUI(false);
+ LayoutPeerListUI(false);
+ ui_ = STREAMING;
+}
+
+void MainWnd::MessageBox(const char* caption, const char* text, bool is_error) {
+ DWORD flags = MB_OK;
+ if (is_error)
+ flags |= MB_ICONERROR;
+
+ ::MessageBoxA(handle(), text, caption, flags);
+}
+
+void MainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) {
+ local_renderer_.reset(new VideoRenderer(handle(), 1, 1, local_video));
+}
+
+void MainWnd::StopLocalRenderer() {
+ local_renderer_.reset();
+}
+
+void MainWnd::StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) {
+ remote_renderer_.reset(new VideoRenderer(handle(), 1, 1, remote_video));
+}
+
+void MainWnd::StopRemoteRenderer() {
+ remote_renderer_.reset();
+}
+
+void MainWnd::QueueUIThreadCallback(int msg_id, void* data) {
+ ::PostThreadMessage(ui_thread_id_, UI_THREAD_CALLBACK,
+ static_cast<WPARAM>(msg_id),
+ reinterpret_cast<LPARAM>(data));
+}
+
+void MainWnd::OnPaint() {
+ PAINTSTRUCT ps;
+ ::BeginPaint(handle(), &ps);
+
+ RECT rc;
+ ::GetClientRect(handle(), &rc);
+
+ VideoRenderer* local_renderer = local_renderer_.get();
+ VideoRenderer* remote_renderer = remote_renderer_.get();
+ if (ui_ == STREAMING && remote_renderer && local_renderer) {
+ AutoLock<VideoRenderer> local_lock(local_renderer);
+ AutoLock<VideoRenderer> remote_lock(remote_renderer);
+
+ const BITMAPINFO& bmi = remote_renderer->bmi();
+ int height = abs(bmi.bmiHeader.biHeight);
+ int width = bmi.bmiHeader.biWidth;
+
+ const uint8_t* image = remote_renderer->image();
+ if (image != NULL) {
+ HDC dc_mem = ::CreateCompatibleDC(ps.hdc);
+ ::SetStretchBltMode(dc_mem, HALFTONE);
+
+ // Set the map mode so that the ratio will be maintained for us.
+ HDC all_dc[] = {ps.hdc, dc_mem};
+ for (size_t i = 0; i < arraysize(all_dc); ++i) {
+ SetMapMode(all_dc[i], MM_ISOTROPIC);
+ SetWindowExtEx(all_dc[i], width, height, NULL);
+ SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL);
+ }
+
+ HBITMAP bmp_mem = ::CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom);
+ HGDIOBJ bmp_old = ::SelectObject(dc_mem, bmp_mem);
+
+ POINT logical_area = {rc.right, rc.bottom};
+ DPtoLP(ps.hdc, &logical_area, 1);
+
+ HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
+ RECT logical_rect = {0, 0, logical_area.x, logical_area.y};
+ ::FillRect(dc_mem, &logical_rect, brush);
+ ::DeleteObject(brush);
+
+ int x = (logical_area.x / 2) - (width / 2);
+ int y = (logical_area.y / 2) - (height / 2);
+
+ StretchDIBits(dc_mem, x, y, width, height, 0, 0, width, height, image,
+ &bmi, DIB_RGB_COLORS, SRCCOPY);
+
+ if ((rc.right - rc.left) > 200 && (rc.bottom - rc.top) > 200) {
+ const BITMAPINFO& bmi = local_renderer->bmi();
+ image = local_renderer->image();
+ int thumb_width = bmi.bmiHeader.biWidth / 4;
+ int thumb_height = abs(bmi.bmiHeader.biHeight) / 4;
+ StretchDIBits(dc_mem, logical_area.x - thumb_width - 10,
+ logical_area.y - thumb_height - 10, thumb_width,
+ thumb_height, 0, 0, bmi.bmiHeader.biWidth,
+ -bmi.bmiHeader.biHeight, image, &bmi, DIB_RGB_COLORS,
+ SRCCOPY);
+ }
+
+ BitBlt(ps.hdc, 0, 0, logical_area.x, logical_area.y, dc_mem, 0, 0,
+ SRCCOPY);
+
+ // Cleanup.
+ ::SelectObject(dc_mem, bmp_old);
+ ::DeleteObject(bmp_mem);
+ ::DeleteDC(dc_mem);
+ } else {
+ // We're still waiting for the video stream to be initialized.
+ HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
+ ::FillRect(ps.hdc, &rc, brush);
+ ::DeleteObject(brush);
+
+ HGDIOBJ old_font = ::SelectObject(ps.hdc, GetDefaultFont());
+ ::SetTextColor(ps.hdc, RGB(0xff, 0xff, 0xff));
+ ::SetBkMode(ps.hdc, TRANSPARENT);
+
+ std::string text(kConnecting);
+ if (!local_renderer->image()) {
+ text += kNoVideoStreams;
+ } else {
+ text += kNoIncomingStream;
+ }
+ ::DrawTextA(ps.hdc, text.c_str(), -1, &rc,
+ DT_SINGLELINE | DT_CENTER | DT_VCENTER);
+ ::SelectObject(ps.hdc, old_font);
+ }
+ } else {
+ HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
+ ::FillRect(ps.hdc, &rc, brush);
+ ::DeleteObject(brush);
+ }
+
+ ::EndPaint(handle(), &ps);
+}
+
+void MainWnd::OnDestroyed() {
+ PostQuitMessage(0);
+}
+
+void MainWnd::OnDefaultAction() {
+ if (!callback_)
+ return;
+ if (ui_ == CONNECT_TO_SERVER) {
+ std::string server(GetWindowText(edit1_));
+ std::string port_str(GetWindowText(edit2_));
+ int port = port_str.length() ? atoi(port_str.c_str()) : 0;
+ callback_->StartLogin(server, port);
+ } else if (ui_ == LIST_PEERS) {
+ LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0);
+ if (sel != LB_ERR) {
+ LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0);
+ if (peer_id != -1 && callback_) {
+ callback_->ConnectToPeer(peer_id);
+ }
+ }
+ } else {
+ ::MessageBoxA(wnd_, "OK!", "Yeah", MB_OK);
+ }
+}
+
+bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) {
+ switch (msg) {
+ case WM_ERASEBKGND:
+ *result = TRUE;
+ return true;
+
+ case WM_PAINT:
+ OnPaint();
+ return true;
+
+ case WM_SETFOCUS:
+ if (ui_ == CONNECT_TO_SERVER) {
+ SetFocus(edit1_);
+ } else if (ui_ == LIST_PEERS) {
+ SetFocus(listbox_);
+ }
+ return true;
+
+ case WM_SIZE:
+ if (ui_ == CONNECT_TO_SERVER) {
+ LayoutConnectUI(true);
+ } else if (ui_ == LIST_PEERS) {
+ LayoutPeerListUI(true);
+ }
+ break;
+
+ case WM_CTLCOLORSTATIC:
+ *result = reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_WINDOW));
+ return true;
+
+ case WM_COMMAND:
+ if (button_ == reinterpret_cast<HWND>(lp)) {
+ if (BN_CLICKED == HIWORD(wp))
+ OnDefaultAction();
+ } else if (listbox_ == reinterpret_cast<HWND>(lp)) {
+ if (LBN_DBLCLK == HIWORD(wp)) {
+ OnDefaultAction();
+ }
+ }
+ return true;
+
+ case WM_CLOSE:
+ if (callback_)
+ callback_->Close();
+ break;
+ }
+ return false;
+}
+
+// static
+LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
+ MainWnd* me =
+ reinterpret_cast<MainWnd*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA));
+ if (!me && WM_CREATE == msg) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lp);
+ me = reinterpret_cast<MainWnd*>(cs->lpCreateParams);
+ me->wnd_ = hwnd;
+ ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(me));
+ }
+
+ LRESULT result = 0;
+ if (me) {
+ void* prev_nested_msg = me->nested_msg_;
+ me->nested_msg_ = &msg;
+
+ bool handled = me->OnMessage(msg, wp, lp, &result);
+ if (WM_NCDESTROY == msg) {
+ me->destroyed_ = true;
+ } else if (!handled) {
+ result = ::DefWindowProc(hwnd, msg, wp, lp);
+ }
+
+ if (me->destroyed_ && prev_nested_msg == NULL) {
+ me->OnDestroyed();
+ me->wnd_ = NULL;
+ me->destroyed_ = false;
+ }
+
+ me->nested_msg_ = prev_nested_msg;
+ } else {
+ result = ::DefWindowProc(hwnd, msg, wp, lp);
+ }
+
+ return result;
+}
+
+// static
+bool MainWnd::RegisterWindowClass() {
+ if (wnd_class_)
+ return true;
+
+ WNDCLASSEXW wcex = {sizeof(WNDCLASSEX)};
+ wcex.style = CS_DBLCLKS;
+ wcex.hInstance = GetModuleHandle(NULL);
+ wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
+ wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
+ wcex.lpfnWndProc = &WndProc;
+ wcex.lpszClassName = kClassName;
+ wnd_class_ = ::RegisterClassExW(&wcex);
+ RTC_DCHECK(wnd_class_ != 0);
+ return wnd_class_ != 0;
+}
+
+void MainWnd::CreateChildWindow(HWND* wnd,
+ MainWnd::ChildWindowID id,
+ const wchar_t* class_name,
+ DWORD control_style,
+ DWORD ex_style) {
+ if (::IsWindow(*wnd))
+ return;
+
+ // Child windows are invisible at first, and shown after being resized.
+ DWORD style = WS_CHILD | control_style;
+ *wnd = ::CreateWindowExW(ex_style, class_name, L"", style, 100, 100, 100, 100,
+ wnd_, reinterpret_cast<HMENU>(id),
+ GetModuleHandle(NULL), NULL);
+ RTC_DCHECK(::IsWindow(*wnd) != FALSE);
+ ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()),
+ TRUE);
+}
+
+void MainWnd::CreateChildWindows() {
+ // Create the child windows in tab order.
+ CreateChildWindow(&label1_, LABEL1_ID, L"Static", ES_CENTER | ES_READONLY, 0);
+ CreateChildWindow(&edit1_, EDIT_ID, L"Edit",
+ ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE);
+ CreateChildWindow(&label2_, LABEL2_ID, L"Static", ES_CENTER | ES_READONLY, 0);
+ CreateChildWindow(&edit2_, EDIT_ID, L"Edit",
+ ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE);
+ CreateChildWindow(&button_, BUTTON_ID, L"Button", BS_CENTER | WS_TABSTOP, 0);
+
+ CreateChildWindow(&listbox_, LISTBOX_ID, L"ListBox",
+ LBS_HASSTRINGS | LBS_NOTIFY, WS_EX_CLIENTEDGE);
+
+ ::SetWindowTextA(edit1_, server_.c_str());
+ ::SetWindowTextA(edit2_, port_.c_str());
+}
+
+void MainWnd::LayoutConnectUI(bool show) {
+ struct Windows {
+ HWND wnd;
+ const wchar_t* text;
+ size_t width;
+ size_t height;
+ } windows[] = {
+ {label1_, L"Server"}, {edit1_, L"XXXyyyYYYgggXXXyyyYYYggg"},
+ {label2_, L":"}, {edit2_, L"XyXyX"},
+ {button_, L"Connect"},
+ };
+
+ if (show) {
+ const size_t kSeparator = 5;
+ size_t total_width = (ARRAYSIZE(windows) - 1) * kSeparator;
+
+ for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+ CalculateWindowSizeForText(windows[i].wnd, windows[i].text,
+ &windows[i].width, &windows[i].height);
+ total_width += windows[i].width;
+ }
+
+ RECT rc;
+ ::GetClientRect(wnd_, &rc);
+ size_t x = (rc.right / 2) - (total_width / 2);
+ size_t y = rc.bottom / 2;
+ for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+ size_t top = y - (windows[i].height / 2);
+ ::MoveWindow(windows[i].wnd, static_cast<int>(x), static_cast<int>(top),
+ static_cast<int>(windows[i].width),
+ static_cast<int>(windows[i].height), TRUE);
+ x += kSeparator + windows[i].width;
+ if (windows[i].text[0] != 'X')
+ ::SetWindowTextW(windows[i].wnd, windows[i].text);
+ ::ShowWindow(windows[i].wnd, SW_SHOWNA);
+ }
+ } else {
+ for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+ ::ShowWindow(windows[i].wnd, SW_HIDE);
+ }
+ }
+}
+
+void MainWnd::LayoutPeerListUI(bool show) {
+ if (show) {
+ RECT rc;
+ ::GetClientRect(wnd_, &rc);
+ ::MoveWindow(listbox_, 0, 0, rc.right, rc.bottom, TRUE);
+ ::ShowWindow(listbox_, SW_SHOWNA);
+ } else {
+ ::ShowWindow(listbox_, SW_HIDE);
+ InvalidateRect(wnd_, NULL, TRUE);
+ }
+}
+
+void MainWnd::HandleTabbing() {
+ bool shift = ((::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
+ UINT next_cmd = shift ? GW_HWNDPREV : GW_HWNDNEXT;
+ UINT loop_around_cmd = shift ? GW_HWNDLAST : GW_HWNDFIRST;
+ HWND focus = GetFocus(), next;
+ do {
+ next = ::GetWindow(focus, next_cmd);
+ if (IsWindowVisible(next) &&
+ (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) {
+ break;
+ }
+
+ if (!next) {
+ next = ::GetWindow(focus, loop_around_cmd);
+ if (IsWindowVisible(next) &&
+ (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) {
+ break;
+ }
+ }
+ focus = next;
+ } while (true);
+ ::SetFocus(next);
+}
+
+//
+// MainWnd::VideoRenderer
+//
+
+MainWnd::VideoRenderer::VideoRenderer(
+ HWND wnd,
+ int width,
+ int height,
+ webrtc::VideoTrackInterface* track_to_render)
+ : wnd_(wnd), rendered_track_(track_to_render) {
+ ::InitializeCriticalSection(&buffer_lock_);
+ ZeroMemory(&bmi_, sizeof(bmi_));
+ bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmi_.bmiHeader.biPlanes = 1;
+ bmi_.bmiHeader.biBitCount = 32;
+ bmi_.bmiHeader.biCompression = BI_RGB;
+ bmi_.bmiHeader.biWidth = width;
+ bmi_.bmiHeader.biHeight = -height;
+ bmi_.bmiHeader.biSizeImage =
+ width * height * (bmi_.bmiHeader.biBitCount >> 3);
+ rendered_track_->AddOrUpdateSink(this, rtc::VideoSinkWants());
+}
+
+MainWnd::VideoRenderer::~VideoRenderer() {
+ rendered_track_->RemoveSink(this);
+ ::DeleteCriticalSection(&buffer_lock_);
+}
+
+void MainWnd::VideoRenderer::SetSize(int width, int height) {
+ AutoLock<VideoRenderer> lock(this);
+
+ if (width == bmi_.bmiHeader.biWidth && height == bmi_.bmiHeader.biHeight) {
+ return;
+ }
+
+ bmi_.bmiHeader.biWidth = width;
+ bmi_.bmiHeader.biHeight = -height;
+ bmi_.bmiHeader.biSizeImage =
+ width * height * (bmi_.bmiHeader.biBitCount >> 3);
+ image_.reset(new uint8_t[bmi_.bmiHeader.biSizeImage]);
+}
+
+void MainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) {
+ {
+ AutoLock<VideoRenderer> lock(this);
+
+ rtc::scoped_refptr<webrtc::I420BufferInterface> buffer(
+ video_frame.video_frame_buffer()->ToI420());
+ if (video_frame.rotation() != webrtc::kVideoRotation_0) {
+ buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation());
+ }
+
+ SetSize(buffer->width(), buffer->height());
+
+ RTC_DCHECK(image_.get() != NULL);
+ libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(),
+ buffer->StrideU(), buffer->DataV(), buffer->StrideV(),
+ image_.get(),
+ bmi_.bmiHeader.biWidth * bmi_.bmiHeader.biBitCount / 8,
+ buffer->width(), buffer->height());
+ }
+ InvalidateRect(wnd_, NULL, TRUE);
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/client/main_wnd.h b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.h
new file mode 100644
index 0000000000..898fea9d92
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_
+#define EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "api/media_stream_interface.h"
+#include "api/video/video_frame.h"
+#include "examples/peerconnection/client/peer_connection_client.h"
+#include "media/base/media_channel.h"
+#include "media/base/video_common.h"
+#if defined(WEBRTC_WIN)
+#include "rtc_base/win32.h"
+#endif // WEBRTC_WIN
+
+class MainWndCallback {
+ public:
+ virtual void StartLogin(const std::string& server, int port) = 0;
+ virtual void DisconnectFromServer() = 0;
+ virtual void ConnectToPeer(int peer_id) = 0;
+ virtual void DisconnectFromCurrentPeer() = 0;
+ virtual void UIThreadCallback(int msg_id, void* data) = 0;
+ virtual void Close() = 0;
+
+ protected:
+ virtual ~MainWndCallback() {}
+};
+
+// Pure virtual interface for the main window.
+class MainWindow {
+ public:
+ virtual ~MainWindow() {}
+
+ enum UI {
+ CONNECT_TO_SERVER,
+ LIST_PEERS,
+ STREAMING,
+ };
+
+ virtual void RegisterObserver(MainWndCallback* callback) = 0;
+
+ virtual bool IsWindow() = 0;
+ virtual void MessageBox(const char* caption,
+ const char* text,
+ bool is_error) = 0;
+
+ virtual UI current_ui() = 0;
+
+ virtual void SwitchToConnectUI() = 0;
+ virtual void SwitchToPeerList(const Peers& peers) = 0;
+ virtual void SwitchToStreamingUI() = 0;
+
+ virtual void StartLocalRenderer(webrtc::VideoTrackInterface* local_video) = 0;
+ virtual void StopLocalRenderer() = 0;
+ virtual void StartRemoteRenderer(
+ webrtc::VideoTrackInterface* remote_video) = 0;
+ virtual void StopRemoteRenderer() = 0;
+
+ virtual void QueueUIThreadCallback(int msg_id, void* data) = 0;
+};
+
+#ifdef WIN32
+
+class MainWnd : public MainWindow {
+ public:
+ static const wchar_t kClassName[];
+
+ enum WindowMessages {
+ UI_THREAD_CALLBACK = WM_APP + 1,
+ };
+
+ MainWnd(const char* server, int port, bool auto_connect, bool auto_call);
+ ~MainWnd();
+
+ bool Create();
+ bool Destroy();
+ bool PreTranslateMessage(MSG* msg);
+
+ virtual void RegisterObserver(MainWndCallback* callback);
+ virtual bool IsWindow();
+ virtual void SwitchToConnectUI();
+ virtual void SwitchToPeerList(const Peers& peers);
+ virtual void SwitchToStreamingUI();
+ virtual void MessageBox(const char* caption, const char* text, bool is_error);
+ virtual UI current_ui() { return ui_; }
+
+ virtual void StartLocalRenderer(webrtc::VideoTrackInterface* local_video);
+ virtual void StopLocalRenderer();
+ virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video);
+ virtual void StopRemoteRenderer();
+
+ virtual void QueueUIThreadCallback(int msg_id, void* data);
+
+ HWND handle() const { return wnd_; }
+
+ class VideoRenderer : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
+ public:
+ VideoRenderer(HWND wnd,
+ int width,
+ int height,
+ webrtc::VideoTrackInterface* track_to_render);
+ virtual ~VideoRenderer();
+
+ void Lock() { ::EnterCriticalSection(&buffer_lock_); }
+
+ void Unlock() { ::LeaveCriticalSection(&buffer_lock_); }
+
+ // VideoSinkInterface implementation
+ void OnFrame(const webrtc::VideoFrame& frame) override;
+
+ const BITMAPINFO& bmi() const { return bmi_; }
+ const uint8_t* image() const { return image_.get(); }
+
+ protected:
+ void SetSize(int width, int height);
+
+ enum {
+ SET_SIZE,
+ RENDER_FRAME,
+ };
+
+ HWND wnd_;
+ BITMAPINFO bmi_;
+ std::unique_ptr<uint8_t[]> image_;
+ CRITICAL_SECTION buffer_lock_;
+ rtc::scoped_refptr<webrtc::VideoTrackInterface> rendered_track_;
+ };
+
+ // A little helper class to make sure we always to proper locking and
+ // unlocking when working with VideoRenderer buffers.
+ template <typename T>
+ class AutoLock {
+ public:
+ explicit AutoLock(T* obj) : obj_(obj) { obj_->Lock(); }
+ ~AutoLock() { obj_->Unlock(); }
+
+ protected:
+ T* obj_;
+ };
+
+ protected:
+ enum ChildWindowID {
+ EDIT_ID = 1,
+ BUTTON_ID,
+ LABEL1_ID,
+ LABEL2_ID,
+ LISTBOX_ID,
+ };
+
+ void OnPaint();
+ void OnDestroyed();
+
+ void OnDefaultAction();
+
+ bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result);
+
+ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
+ static bool RegisterWindowClass();
+
+ void CreateChildWindow(HWND* wnd,
+ ChildWindowID id,
+ const wchar_t* class_name,
+ DWORD control_style,
+ DWORD ex_style);
+ void CreateChildWindows();
+
+ void LayoutConnectUI(bool show);
+ void LayoutPeerListUI(bool show);
+
+ void HandleTabbing();
+
+ private:
+ std::unique_ptr<VideoRenderer> local_renderer_;
+ std::unique_ptr<VideoRenderer> remote_renderer_;
+ UI ui_;
+ HWND wnd_;
+ DWORD ui_thread_id_;
+ HWND edit1_;
+ HWND edit2_;
+ HWND label1_;
+ HWND label2_;
+ HWND button_;
+ HWND listbox_;
+ bool destroyed_;
+ void* nested_msg_;
+ MainWndCallback* callback_;
+ static ATOM wnd_class_;
+ std::string server_;
+ std::string port_;
+ bool auto_connect_;
+ bool auto_call_;
+};
+#endif // WIN32
+
+#endif // EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.cc b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.cc
new file mode 100644
index 0000000000..2746752d80
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.cc
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/client/peer_connection_client.h"
+
+#include "api/units/time_delta.h"
+#include "examples/peerconnection/client/defaults.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/net_helpers.h"
+
+namespace {
+
+// This is our magical hangup signal.
+constexpr char kByeMessage[] = "BYE";
+// Delay between server connection retries, in milliseconds
+constexpr webrtc::TimeDelta kReconnectDelay = webrtc::TimeDelta::Seconds(2);
+
+rtc::Socket* CreateClientSocket(int family) {
+ rtc::Thread* thread = rtc::Thread::Current();
+ RTC_DCHECK(thread != NULL);
+ return thread->socketserver()->CreateSocket(family, SOCK_STREAM);
+}
+
+} // namespace
+
+PeerConnectionClient::PeerConnectionClient()
+ : callback_(NULL), resolver_(NULL), state_(NOT_CONNECTED), my_id_(-1) {}
+
+PeerConnectionClient::~PeerConnectionClient() = default;
+
+void PeerConnectionClient::InitSocketSignals() {
+ RTC_DCHECK(control_socket_.get() != NULL);
+ RTC_DCHECK(hanging_get_.get() != NULL);
+ control_socket_->SignalCloseEvent.connect(this,
+ &PeerConnectionClient::OnClose);
+ hanging_get_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose);
+ control_socket_->SignalConnectEvent.connect(this,
+ &PeerConnectionClient::OnConnect);
+ hanging_get_->SignalConnectEvent.connect(
+ this, &PeerConnectionClient::OnHangingGetConnect);
+ control_socket_->SignalReadEvent.connect(this, &PeerConnectionClient::OnRead);
+ hanging_get_->SignalReadEvent.connect(
+ this, &PeerConnectionClient::OnHangingGetRead);
+}
+
+int PeerConnectionClient::id() const {
+ return my_id_;
+}
+
+bool PeerConnectionClient::is_connected() const {
+ return my_id_ != -1;
+}
+
+const Peers& PeerConnectionClient::peers() const {
+ return peers_;
+}
+
+void PeerConnectionClient::RegisterObserver(
+ PeerConnectionClientObserver* callback) {
+ RTC_DCHECK(!callback_);
+ callback_ = callback;
+}
+
+void PeerConnectionClient::Connect(const std::string& server,
+ int port,
+ const std::string& client_name) {
+ RTC_DCHECK(!server.empty());
+ RTC_DCHECK(!client_name.empty());
+
+ if (state_ != NOT_CONNECTED) {
+ RTC_LOG(LS_WARNING)
+ << "The client must not be connected before you can call Connect()";
+ callback_->OnServerConnectionFailure();
+ return;
+ }
+
+ if (server.empty() || client_name.empty()) {
+ callback_->OnServerConnectionFailure();
+ return;
+ }
+
+ if (port <= 0)
+ port = kDefaultServerPort;
+
+ server_address_.SetIP(server);
+ server_address_.SetPort(port);
+ client_name_ = client_name;
+
+ if (server_address_.IsUnresolvedIP()) {
+ state_ = RESOLVING;
+ resolver_ = new rtc::AsyncResolver();
+ resolver_->SignalDone.connect(this, &PeerConnectionClient::OnResolveResult);
+ resolver_->Start(server_address_);
+ } else {
+ DoConnect();
+ }
+}
+
+void PeerConnectionClient::OnResolveResult(
+ rtc::AsyncResolverInterface* resolver) {
+ if (resolver_->GetError() != 0) {
+ callback_->OnServerConnectionFailure();
+ resolver_->Destroy(false);
+ resolver_ = NULL;
+ state_ = NOT_CONNECTED;
+ } else {
+ server_address_ = resolver_->address();
+ DoConnect();
+ }
+}
+
+void PeerConnectionClient::DoConnect() {
+ control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));
+ hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));
+ InitSocketSignals();
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer), "GET /sign_in?%s HTTP/1.0\r\n\r\n",
+ client_name_.c_str());
+ onconnect_data_ = buffer;
+
+ bool ret = ConnectControlSocket();
+ if (ret)
+ state_ = SIGNING_IN;
+ if (!ret) {
+ callback_->OnServerConnectionFailure();
+ }
+}
+
+bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) {
+ if (state_ != CONNECTED)
+ return false;
+
+ RTC_DCHECK(is_connected());
+ RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
+ if (!is_connected() || peer_id == -1)
+ return false;
+
+ char headers[1024];
+ snprintf(headers, sizeof(headers),
+ "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
+ "Content-Length: %zu\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n",
+ my_id_, peer_id, message.length());
+ onconnect_data_ = headers;
+ onconnect_data_ += message;
+ return ConnectControlSocket();
+}
+
+bool PeerConnectionClient::SendHangUp(int peer_id) {
+ return SendToPeer(peer_id, kByeMessage);
+}
+
+bool PeerConnectionClient::IsSendingMessage() {
+ return state_ == CONNECTED &&
+ control_socket_->GetState() != rtc::Socket::CS_CLOSED;
+}
+
+bool PeerConnectionClient::SignOut() {
+ if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
+ return true;
+
+ if (hanging_get_->GetState() != rtc::Socket::CS_CLOSED)
+ hanging_get_->Close();
+
+ if (control_socket_->GetState() == rtc::Socket::CS_CLOSED) {
+ state_ = SIGNING_OUT;
+
+ if (my_id_ != -1) {
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer),
+ "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
+ onconnect_data_ = buffer;
+ return ConnectControlSocket();
+ } else {
+ // Can occur if the app is closed before we finish connecting.
+ return true;
+ }
+ } else {
+ state_ = SIGNING_OUT_WAITING;
+ }
+
+ return true;
+}
+
+void PeerConnectionClient::Close() {
+ control_socket_->Close();
+ hanging_get_->Close();
+ onconnect_data_.clear();
+ peers_.clear();
+ if (resolver_ != NULL) {
+ resolver_->Destroy(false);
+ resolver_ = NULL;
+ }
+ my_id_ = -1;
+ state_ = NOT_CONNECTED;
+}
+
+bool PeerConnectionClient::ConnectControlSocket() {
+ RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
+ int err = control_socket_->Connect(server_address_);
+ if (err == SOCKET_ERROR) {
+ Close();
+ return false;
+ }
+ return true;
+}
+
+void PeerConnectionClient::OnConnect(rtc::Socket* socket) {
+ RTC_DCHECK(!onconnect_data_.empty());
+ size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
+ RTC_DCHECK(sent == onconnect_data_.length());
+ onconnect_data_.clear();
+}
+
+void PeerConnectionClient::OnHangingGetConnect(rtc::Socket* socket) {
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer), "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n",
+ my_id_);
+ int len = static_cast<int>(strlen(buffer));
+ int sent = socket->Send(buffer, len);
+ RTC_DCHECK(sent == len);
+}
+
+void PeerConnectionClient::OnMessageFromPeer(int peer_id,
+ const std::string& message) {
+ if (message.length() == (sizeof(kByeMessage) - 1) &&
+ message.compare(kByeMessage) == 0) {
+ callback_->OnPeerDisconnected(peer_id);
+ } else {
+ callback_->OnMessageFromPeer(peer_id, message);
+ }
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data,
+ size_t eoh,
+ const char* header_pattern,
+ size_t* value) {
+ RTC_DCHECK(value != NULL);
+ size_t found = data.find(header_pattern);
+ if (found != std::string::npos && found < eoh) {
+ *value = atoi(&data[found + strlen(header_pattern)]);
+ return true;
+ }
+ return false;
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data,
+ size_t eoh,
+ const char* header_pattern,
+ std::string* value) {
+ RTC_DCHECK(value != NULL);
+ size_t found = data.find(header_pattern);
+ if (found != std::string::npos && found < eoh) {
+ size_t begin = found + strlen(header_pattern);
+ size_t end = data.find("\r\n", begin);
+ if (end == std::string::npos)
+ end = eoh;
+ value->assign(data.substr(begin, end - begin));
+ return true;
+ }
+ return false;
+}
+
+bool PeerConnectionClient::ReadIntoBuffer(rtc::Socket* socket,
+ std::string* data,
+ size_t* content_length) {
+ char buffer[0xffff];
+ do {
+ int bytes = socket->Recv(buffer, sizeof(buffer), nullptr);
+ if (bytes <= 0)
+ break;
+ data->append(buffer, bytes);
+ } while (true);
+
+ bool ret = false;
+ size_t i = data->find("\r\n\r\n");
+ if (i != std::string::npos) {
+ RTC_LOG(LS_INFO) << "Headers received";
+ if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
+ size_t total_response_size = (i + 4) + *content_length;
+ if (data->length() >= total_response_size) {
+ ret = true;
+ std::string should_close;
+ const char kConnection[] = "\r\nConnection: ";
+ if (GetHeaderValue(*data, i, kConnection, &should_close) &&
+ should_close.compare("close") == 0) {
+ socket->Close();
+ // Since we closed the socket, there was no notification delivered
+ // to us. Compensate by letting ourselves know.
+ OnClose(socket, 0);
+ }
+ } else {
+ // We haven't received everything. Just continue to accept data.
+ }
+ } else {
+ RTC_LOG(LS_ERROR) << "No content length field specified by the server.";
+ }
+ }
+ return ret;
+}
+
+void PeerConnectionClient::OnRead(rtc::Socket* socket) {
+ size_t content_length = 0;
+ if (ReadIntoBuffer(socket, &control_data_, &content_length)) {
+ size_t peer_id = 0, eoh = 0;
+ bool ok =
+ ParseServerResponse(control_data_, content_length, &peer_id, &eoh);
+ if (ok) {
+ if (my_id_ == -1) {
+ // First response. Let's store our server assigned ID.
+ RTC_DCHECK(state_ == SIGNING_IN);
+ my_id_ = static_cast<int>(peer_id);
+ RTC_DCHECK(my_id_ != -1);
+
+ // The body of the response will be a list of already connected peers.
+ if (content_length) {
+ size_t pos = eoh + 4;
+ while (pos < control_data_.size()) {
+ size_t eol = control_data_.find('\n', pos);
+ if (eol == std::string::npos)
+ break;
+ int id = 0;
+ std::string name;
+ bool connected;
+ if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
+ &connected) &&
+ id != my_id_) {
+ peers_[id] = name;
+ callback_->OnPeerConnected(id, name);
+ }
+ pos = eol + 1;
+ }
+ }
+ RTC_DCHECK(is_connected());
+ callback_->OnSignedIn();
+ } else if (state_ == SIGNING_OUT) {
+ Close();
+ callback_->OnDisconnected();
+ } else if (state_ == SIGNING_OUT_WAITING) {
+ SignOut();
+ }
+ }
+
+ control_data_.clear();
+
+ if (state_ == SIGNING_IN) {
+ RTC_DCHECK(hanging_get_->GetState() == rtc::Socket::CS_CLOSED);
+ state_ = CONNECTED;
+ hanging_get_->Connect(server_address_);
+ }
+ }
+}
+
+void PeerConnectionClient::OnHangingGetRead(rtc::Socket* socket) {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+ size_t content_length = 0;
+ if (ReadIntoBuffer(socket, &notification_data_, &content_length)) {
+ size_t peer_id = 0, eoh = 0;
+ bool ok =
+ ParseServerResponse(notification_data_, content_length, &peer_id, &eoh);
+
+ if (ok) {
+ // Store the position where the body begins.
+ size_t pos = eoh + 4;
+
+ if (my_id_ == static_cast<int>(peer_id)) {
+ // A notification about a new member or a member that just
+ // disconnected.
+ int id = 0;
+ std::string name;
+ bool connected = false;
+ if (ParseEntry(notification_data_.substr(pos), &name, &id,
+ &connected)) {
+ if (connected) {
+ peers_[id] = name;
+ callback_->OnPeerConnected(id, name);
+ } else {
+ peers_.erase(id);
+ callback_->OnPeerDisconnected(id);
+ }
+ }
+ } else {
+ OnMessageFromPeer(static_cast<int>(peer_id),
+ notification_data_.substr(pos));
+ }
+ }
+
+ notification_data_.clear();
+ }
+
+ if (hanging_get_->GetState() == rtc::Socket::CS_CLOSED &&
+ state_ == CONNECTED) {
+ hanging_get_->Connect(server_address_);
+ }
+}
+
+bool PeerConnectionClient::ParseEntry(const std::string& entry,
+ std::string* name,
+ int* id,
+ bool* connected) {
+ RTC_DCHECK(name != NULL);
+ RTC_DCHECK(id != NULL);
+ RTC_DCHECK(connected != NULL);
+ RTC_DCHECK(!entry.empty());
+
+ *connected = false;
+ size_t separator = entry.find(',');
+ if (separator != std::string::npos) {
+ *id = atoi(&entry[separator + 1]);
+ name->assign(entry.substr(0, separator));
+ separator = entry.find(',', separator + 1);
+ if (separator != std::string::npos) {
+ *connected = atoi(&entry[separator + 1]) ? true : false;
+ }
+ }
+ return !name->empty();
+}
+
+int PeerConnectionClient::GetResponseStatus(const std::string& response) {
+ int status = -1;
+ size_t pos = response.find(' ');
+ if (pos != std::string::npos)
+ status = atoi(&response[pos + 1]);
+ return status;
+}
+
+bool PeerConnectionClient::ParseServerResponse(const std::string& response,
+ size_t content_length,
+ size_t* peer_id,
+ size_t* eoh) {
+ int status = GetResponseStatus(response.c_str());
+ if (status != 200) {
+ RTC_LOG(LS_ERROR) << "Received error from server";
+ Close();
+ callback_->OnDisconnected();
+ return false;
+ }
+
+ *eoh = response.find("\r\n\r\n");
+ RTC_DCHECK(*eoh != std::string::npos);
+ if (*eoh == std::string::npos)
+ return false;
+
+ *peer_id = -1;
+
+ // See comment in peer_channel.cc for why we use the Pragma header.
+ GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
+
+ return true;
+}
+
+void PeerConnectionClient::OnClose(rtc::Socket* socket, int err) {
+ RTC_LOG(LS_INFO) << __FUNCTION__;
+
+ socket->Close();
+
+#ifdef WIN32
+ if (err != WSAECONNREFUSED) {
+#else
+ if (err != ECONNREFUSED) {
+#endif
+ if (socket == hanging_get_.get()) {
+ if (state_ == CONNECTED) {
+ hanging_get_->Close();
+ hanging_get_->Connect(server_address_);
+ }
+ } else {
+ callback_->OnMessageSent(err);
+ }
+ } else {
+ if (socket == control_socket_.get()) {
+ RTC_LOG(LS_WARNING) << "Connection refused; retrying in 2 seconds";
+ rtc::Thread::Current()->PostDelayedTask(
+ SafeTask(safety_.flag(), [this] { DoConnect(); }), kReconnectDelay);
+ } else {
+ Close();
+ callback_->OnDisconnected();
+ }
+ }
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.h b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.h
new file mode 100644
index 0000000000..8f9c5b6a75
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_
+#define EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "api/task_queue/pending_task_safety_flag.h"
+#include "rtc_base/net_helpers.h"
+#include "rtc_base/physical_socket_server.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+
+typedef std::map<int, std::string> Peers;
+
+struct PeerConnectionClientObserver {
+ virtual void OnSignedIn() = 0; // Called when we're logged on.
+ virtual void OnDisconnected() = 0;
+ virtual void OnPeerConnected(int id, const std::string& name) = 0;
+ virtual void OnPeerDisconnected(int peer_id) = 0;
+ virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0;
+ virtual void OnMessageSent(int err) = 0;
+ virtual void OnServerConnectionFailure() = 0;
+
+ protected:
+ virtual ~PeerConnectionClientObserver() {}
+};
+
+class PeerConnectionClient : public sigslot::has_slots<> {
+ public:
+ enum State {
+ NOT_CONNECTED,
+ RESOLVING,
+ SIGNING_IN,
+ CONNECTED,
+ SIGNING_OUT_WAITING,
+ SIGNING_OUT,
+ };
+
+ PeerConnectionClient();
+ ~PeerConnectionClient();
+
+ int id() const;
+ bool is_connected() const;
+ const Peers& peers() const;
+
+ void RegisterObserver(PeerConnectionClientObserver* callback);
+
+ void Connect(const std::string& server,
+ int port,
+ const std::string& client_name);
+
+ bool SendToPeer(int peer_id, const std::string& message);
+ bool SendHangUp(int peer_id);
+ bool IsSendingMessage();
+
+ bool SignOut();
+
+ protected:
+ void DoConnect();
+ void Close();
+ void InitSocketSignals();
+ bool ConnectControlSocket();
+ void OnConnect(rtc::Socket* socket);
+ void OnHangingGetConnect(rtc::Socket* socket);
+ void OnMessageFromPeer(int peer_id, const std::string& message);
+
+ // Quick and dirty support for parsing HTTP header values.
+ bool GetHeaderValue(const std::string& data,
+ size_t eoh,
+ const char* header_pattern,
+ size_t* value);
+
+ bool GetHeaderValue(const std::string& data,
+ size_t eoh,
+ const char* header_pattern,
+ std::string* value);
+
+ // Returns true if the whole response has been read.
+ bool ReadIntoBuffer(rtc::Socket* socket,
+ std::string* data,
+ size_t* content_length);
+
+ void OnRead(rtc::Socket* socket);
+
+ void OnHangingGetRead(rtc::Socket* socket);
+
+ // Parses a single line entry in the form "<name>,<id>,<connected>"
+ bool ParseEntry(const std::string& entry,
+ std::string* name,
+ int* id,
+ bool* connected);
+
+ int GetResponseStatus(const std::string& response);
+
+ bool ParseServerResponse(const std::string& response,
+ size_t content_length,
+ size_t* peer_id,
+ size_t* eoh);
+
+ void OnClose(rtc::Socket* socket, int err);
+
+ void OnResolveResult(rtc::AsyncResolverInterface* resolver);
+
+ PeerConnectionClientObserver* callback_;
+ rtc::SocketAddress server_address_;
+ rtc::AsyncResolver* resolver_;
+ std::unique_ptr<rtc::Socket> control_socket_;
+ std::unique_ptr<rtc::Socket> hanging_get_;
+ std::string onconnect_data_;
+ std::string control_data_;
+ std::string notification_data_;
+ std::string client_name_;
+ Peers peers_;
+ State state_;
+ int my_id_;
+ webrtc::ScopedTaskSafety safety_;
+};
+
+#endif // EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc
new file mode 100644
index 0000000000..855ebd8c0c
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/server/data_socket.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(WEBRTC_POSIX)
+#include <unistd.h>
+#endif
+
+#include "examples/peerconnection/server/utils.h"
+#include "rtc_base/checks.h"
+
+static const char kHeaderTerminator[] = "\r\n\r\n";
+static const int kHeaderTerminatorLength = sizeof(kHeaderTerminator) - 1;
+
+// static
+const char DataSocket::kCrossOriginAllowHeaders[] =
+ "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Credentials: true\r\n"
+ "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
+ "Access-Control-Allow-Headers: Content-Type, "
+ "Content-Length, Connection, Cache-Control\r\n"
+ "Access-Control-Expose-Headers: Content-Length\r\n";
+
+#if defined(WIN32)
+class WinsockInitializer {
+ static WinsockInitializer singleton;
+
+ WinsockInitializer() {
+ WSADATA data;
+ WSAStartup(MAKEWORD(1, 0), &data);
+ }
+
+ public:
+ ~WinsockInitializer() { WSACleanup(); }
+};
+WinsockInitializer WinsockInitializer::singleton;
+#endif
+
+//
+// SocketBase
+//
+
+bool SocketBase::Create() {
+ RTC_DCHECK(!valid());
+ socket_ = ::socket(AF_INET, SOCK_STREAM, 0);
+ return valid();
+}
+
+void SocketBase::Close() {
+ if (socket_ != INVALID_SOCKET) {
+ closesocket(socket_);
+ socket_ = INVALID_SOCKET;
+ }
+}
+
+//
+// DataSocket
+//
+
+std::string DataSocket::request_arguments() const {
+ size_t args = request_path_.find('?');
+ if (args != std::string::npos)
+ return request_path_.substr(args + 1);
+ return "";
+}
+
+bool DataSocket::PathEquals(const char* path) const {
+ RTC_DCHECK(path);
+ size_t args = request_path_.find('?');
+ if (args != std::string::npos)
+ return request_path_.substr(0, args).compare(path) == 0;
+ return request_path_.compare(path) == 0;
+}
+
+bool DataSocket::OnDataAvailable(bool* close_socket) {
+ RTC_DCHECK(valid());
+ char buffer[0xfff] = {0};
+ int bytes = recv(socket_, buffer, sizeof(buffer), 0);
+ if (bytes == SOCKET_ERROR || bytes == 0) {
+ *close_socket = true;
+ return false;
+ }
+
+ *close_socket = false;
+
+ bool ret = true;
+ if (headers_received()) {
+ if (method_ != POST) {
+ // unexpectedly received data.
+ ret = false;
+ } else {
+ data_.append(buffer, bytes);
+ }
+ } else {
+ request_headers_.append(buffer, bytes);
+ size_t found = request_headers_.find(kHeaderTerminator);
+ if (found != std::string::npos) {
+ data_ = request_headers_.substr(found + kHeaderTerminatorLength);
+ request_headers_.resize(found + kHeaderTerminatorLength);
+ ret = ParseHeaders();
+ }
+ }
+ return ret;
+}
+
+bool DataSocket::Send(const std::string& data) const {
+ return send(socket_, data.data(), static_cast<int>(data.length()), 0) !=
+ SOCKET_ERROR;
+}
+
+bool DataSocket::Send(const std::string& status,
+ bool connection_close,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) const {
+ RTC_DCHECK(valid());
+ RTC_DCHECK(!status.empty());
+ std::string buffer("HTTP/1.1 " + status + "\r\n");
+
+ buffer +=
+ "Server: PeerConnectionTestServer/0.1\r\n"
+ "Cache-Control: no-cache\r\n";
+
+ if (connection_close)
+ buffer += "Connection: close\r\n";
+
+ if (!content_type.empty())
+ buffer += "Content-Type: " + content_type + "\r\n";
+
+ buffer +=
+ "Content-Length: " + int2str(static_cast<int>(data.size())) + "\r\n";
+
+ if (!extra_headers.empty()) {
+ buffer += extra_headers;
+ // Extra headers are assumed to have a separator per header.
+ }
+
+ buffer += kCrossOriginAllowHeaders;
+
+ buffer += "\r\n";
+ buffer += data;
+
+ return Send(buffer);
+}
+
+void DataSocket::Clear() {
+ method_ = INVALID;
+ content_length_ = 0;
+ content_type_.clear();
+ request_path_.clear();
+ request_headers_.clear();
+ data_.clear();
+}
+
+bool DataSocket::ParseHeaders() {
+ RTC_DCHECK(!request_headers_.empty());
+ RTC_DCHECK_EQ(method_, INVALID);
+ size_t i = request_headers_.find("\r\n");
+ if (i == std::string::npos)
+ return false;
+
+ if (!ParseMethodAndPath(request_headers_.data(), i))
+ return false;
+
+ RTC_DCHECK_NE(method_, INVALID);
+ RTC_DCHECK(!request_path_.empty());
+
+ if (method_ == POST) {
+ const char* headers = request_headers_.data() + i + 2;
+ size_t len = request_headers_.length() - i - 2;
+ if (!ParseContentLengthAndType(headers, len))
+ return false;
+ }
+
+ return true;
+}
+
+bool DataSocket::ParseMethodAndPath(const char* begin, size_t len) {
+ struct {
+ const char* method_name;
+ size_t method_name_len;
+ RequestMethod id;
+ } supported_methods[] = {
+ {"GET", 3, GET},
+ {"POST", 4, POST},
+ {"OPTIONS", 7, OPTIONS},
+ };
+
+ const char* path = NULL;
+ for (size_t i = 0; i < ARRAYSIZE(supported_methods); ++i) {
+ if (len > supported_methods[i].method_name_len &&
+ isspace(begin[supported_methods[i].method_name_len]) &&
+ strncmp(begin, supported_methods[i].method_name,
+ supported_methods[i].method_name_len) == 0) {
+ method_ = supported_methods[i].id;
+ path = begin + supported_methods[i].method_name_len;
+ break;
+ }
+ }
+
+ const char* end = begin + len;
+ if (!path || path >= end)
+ return false;
+
+ ++path;
+ begin = path;
+ while (!isspace(*path) && path < end)
+ ++path;
+
+ request_path_.assign(begin, path - begin);
+
+ return true;
+}
+
+bool DataSocket::ParseContentLengthAndType(const char* headers, size_t length) {
+ RTC_DCHECK_EQ(content_length_, 0);
+ RTC_DCHECK(content_type_.empty());
+
+ const char* end = headers + length;
+ while (headers && headers < end) {
+ if (!isspace(headers[0])) {
+ static const char kContentLength[] = "Content-Length:";
+ static const char kContentType[] = "Content-Type:";
+ if ((headers + ARRAYSIZE(kContentLength)) < end &&
+ strncmp(headers, kContentLength, ARRAYSIZE(kContentLength) - 1) ==
+ 0) {
+ headers += ARRAYSIZE(kContentLength) - 1;
+ while (headers[0] == ' ')
+ ++headers;
+ content_length_ = atoi(headers);
+ } else if ((headers + ARRAYSIZE(kContentType)) < end &&
+ strncmp(headers, kContentType, ARRAYSIZE(kContentType) - 1) ==
+ 0) {
+ headers += ARRAYSIZE(kContentType) - 1;
+ while (headers[0] == ' ')
+ ++headers;
+ const char* type_end = strstr(headers, "\r\n");
+ if (type_end == NULL)
+ type_end = end;
+ content_type_.assign(headers, type_end);
+ }
+ } else {
+ ++headers;
+ }
+ headers = strstr(headers, "\r\n");
+ if (headers)
+ headers += 2;
+ }
+
+ return !content_type_.empty() && content_length_ != 0;
+}
+
+//
+// ListeningSocket
+//
+
+bool ListeningSocket::Listen(unsigned short port) {
+ RTC_DCHECK(valid());
+ int enabled = 1;
+ if (setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<const char*>(&enabled),
+ sizeof(enabled)) != 0) {
+ printf("setsockopt failed\n");
+ return false;
+ }
+ struct sockaddr_in addr = {0};
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(port);
+ if (bind(socket_, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) ==
+ SOCKET_ERROR) {
+ printf("bind failed\n");
+ return false;
+ }
+ return listen(socket_, 5) != SOCKET_ERROR;
+}
+
+DataSocket* ListeningSocket::Accept() const {
+ RTC_DCHECK(valid());
+ struct sockaddr_in addr = {0};
+ socklen_t size = sizeof(addr);
+ NativeSocket client =
+ accept(socket_, reinterpret_cast<sockaddr*>(&addr), &size);
+ if (client == INVALID_SOCKET)
+ return NULL;
+
+ return new DataSocket(client);
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.h b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h
new file mode 100644
index 0000000000..57ad5b9aee
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+#define EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+
+#ifdef WIN32
+#include <winsock2.h>
+typedef int socklen_t;
+typedef SOCKET NativeSocket;
+#else
+#include <netinet/in.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#define closesocket close
+typedef int NativeSocket;
+
+#ifndef SOCKET_ERROR
+#define SOCKET_ERROR (-1)
+#endif
+
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET static_cast<NativeSocket>(-1)
+#endif
+#endif
+
+#include <string>
+
+class SocketBase {
+ public:
+ SocketBase() : socket_(INVALID_SOCKET) {}
+ explicit SocketBase(NativeSocket socket) : socket_(socket) {}
+ SocketBase(SocketBase& other) = delete;
+ SocketBase& operator=(const SocketBase& other) = delete;
+ ~SocketBase() { Close(); }
+
+ NativeSocket socket() const { return socket_; }
+ bool valid() const { return socket_ != INVALID_SOCKET; }
+
+ bool Create();
+ void Close();
+
+ protected:
+ NativeSocket socket_;
+};
+
+// Represents an HTTP server socket.
+class DataSocket : public SocketBase {
+ public:
+ enum RequestMethod {
+ INVALID,
+ GET,
+ POST,
+ OPTIONS,
+ };
+
+ explicit DataSocket(NativeSocket socket)
+ : SocketBase(socket), method_(INVALID), content_length_(0) {}
+
+ ~DataSocket() {}
+
+ static const char kCrossOriginAllowHeaders[];
+
+ bool headers_received() const { return method_ != INVALID; }
+
+ RequestMethod method() const { return method_; }
+
+ const std::string& request_path() const { return request_path_; }
+ std::string request_arguments() const;
+
+ const std::string& data() const { return data_; }
+
+ const std::string& content_type() const { return content_type_; }
+
+ size_t content_length() const { return content_length_; }
+
+ bool request_received() const {
+ return headers_received() && (method_ != POST || data_received());
+ }
+
+ bool data_received() const {
+ return method_ != POST || data_.length() >= content_length_;
+ }
+
+ // Checks if the request path (minus arguments) matches a given path.
+ bool PathEquals(const char* path) const;
+
+ // Called when we have received some data from clients.
+ // Returns false if an error occurred.
+ bool OnDataAvailable(bool* close_socket);
+
+ // Send a raw buffer of bytes.
+ bool Send(const std::string& data) const;
+
+ // Send an HTTP response. The `status` should start with a valid HTTP
+ // response code, followed by a string. E.g. "200 OK".
+ // If `connection_close` is set to true, an extra "Connection: close" HTTP
+ // header will be included. `content_type` is the mime content type, not
+ // including the "Content-Type: " string.
+ // `extra_headers` should be either empty or a list of headers where each
+ // header terminates with "\r\n".
+ // `data` is the body of the message. It's length will be specified via
+ // a "Content-Length" header.
+ bool Send(const std::string& status,
+ bool connection_close,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) const;
+
+ // Clears all held state and prepares the socket for receiving a new request.
+ void Clear();
+
+ protected:
+ // A fairly relaxed HTTP header parser. Parses the method, path and
+ // content length (POST only) of a request.
+ // Returns true if a valid request was received and no errors occurred.
+ bool ParseHeaders();
+
+ // Figures out whether the request is a GET or POST and what path is
+ // being requested.
+ bool ParseMethodAndPath(const char* begin, size_t len);
+
+ // Determines the length of the body and it's mime type.
+ bool ParseContentLengthAndType(const char* headers, size_t length);
+
+ protected:
+ RequestMethod method_;
+ size_t content_length_;
+ std::string content_type_;
+ std::string request_path_;
+ std::string request_headers_;
+ std::string data_;
+};
+
+// The server socket. Accepts connections and generates DataSocket instances
+// for each new connection.
+class ListeningSocket : public SocketBase {
+ public:
+ ListeningSocket() {}
+
+ bool Listen(unsigned short port);
+ DataSocket* Accept() const;
+};
+
+#endif // EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/server/main.cc b/third_party/libwebrtc/examples/peerconnection/server/main.cc
new file mode 100644
index 0000000000..50b8c23401
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/main.cc
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(WEBRTC_POSIX)
+#include <sys/select.h>
+#endif
+#include <time.h>
+
+#include <string>
+#include <vector>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/flags/usage.h"
+#include "examples/peerconnection/server/data_socket.h"
+#include "examples/peerconnection/server/peer_channel.h"
+#include "rtc_base/checks.h"
+#include "system_wrappers/include/field_trial.h"
+#include "test/field_trial.h"
+
+ABSL_FLAG(
+ std::string,
+ force_fieldtrials,
+ "",
+ "Field trials control experimental features. This flag specifies the field "
+ "trials in effect. E.g. running with "
+ "--force_fieldtrials=WebRTC-FooFeature/Enabled/ "
+ "will assign the group Enabled to field trial WebRTC-FooFeature. Multiple "
+ "trials are separated by \"/\"");
+ABSL_FLAG(int, port, 8888, "default: 8888");
+
+static const size_t kMaxConnections = (FD_SETSIZE - 2);
+
+void HandleBrowserRequest(DataSocket* ds, bool* quit) {
+ RTC_DCHECK(ds && ds->valid());
+ RTC_DCHECK(quit);
+
+ const std::string& path = ds->request_path();
+
+ *quit = (path.compare("/quit") == 0);
+
+ if (*quit) {
+ ds->Send("200 OK", true, "text/html", "",
+ "<html><body>Quitting...</body></html>");
+ } else if (ds->method() == DataSocket::OPTIONS) {
+ // We'll get this when a browsers do cross-resource-sharing requests.
+ // The headers to allow cross-origin script support will be set inside
+ // Send.
+ ds->Send("200 OK", true, "", "", "");
+ } else {
+ // Here we could write some useful output back to the browser depending on
+ // the path.
+ printf("Received an invalid request: %s\n", ds->request_path().c_str());
+ ds->Send("500 Sorry", true, "text/html", "",
+ "<html><body>Sorry, not yet implemented</body></html>");
+ }
+}
+
+int main(int argc, char* argv[]) {
+ absl::SetProgramUsageMessage(
+ "Example usage: ./peerconnection_server --port=8888\n");
+ absl::ParseCommandLine(argc, argv);
+
+ // InitFieldTrialsFromString stores the char*, so the char array must outlive
+ // the application.
+ const std::string force_field_trials = absl::GetFlag(FLAGS_force_fieldtrials);
+ webrtc::field_trial::InitFieldTrialsFromString(force_field_trials.c_str());
+
+ int port = absl::GetFlag(FLAGS_port);
+
+ // Abort if the user specifies a port that is outside the allowed
+ // range [1, 65535].
+ if ((port < 1) || (port > 65535)) {
+ printf("Error: %i is not a valid port.\n", port);
+ return -1;
+ }
+
+ ListeningSocket listener;
+ if (!listener.Create()) {
+ printf("Failed to create server socket\n");
+ return -1;
+ } else if (!listener.Listen(port)) {
+ printf("Failed to listen on server socket\n");
+ return -1;
+ }
+
+ printf("Server listening on port %i\n", port);
+
+ PeerChannel clients;
+ typedef std::vector<DataSocket*> SocketArray;
+ SocketArray sockets;
+ bool quit = false;
+ while (!quit) {
+ fd_set socket_set;
+ FD_ZERO(&socket_set);
+ if (listener.valid())
+ FD_SET(listener.socket(), &socket_set);
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+ FD_SET((*i)->socket(), &socket_set);
+
+ struct timeval timeout = {10, 0};
+ if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) {
+ printf("select failed\n");
+ break;
+ }
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {
+ DataSocket* s = *i;
+ bool socket_done = true;
+ if (FD_ISSET(s->socket(), &socket_set)) {
+ if (s->OnDataAvailable(&socket_done) && s->request_received()) {
+ ChannelMember* member = clients.Lookup(s);
+ if (member || PeerChannel::IsPeerConnection(s)) {
+ if (!member) {
+ if (s->PathEquals("/sign_in")) {
+ clients.AddMember(s);
+ } else {
+ printf("No member found for: %s\n", s->request_path().c_str());
+ s->Send("500 Error", true, "text/plain", "",
+ "Peer most likely gone.");
+ }
+ } else if (member->is_wait_request(s)) {
+ // no need to do anything.
+ socket_done = false;
+ } else {
+ ChannelMember* target = clients.IsTargetedRequest(s);
+ if (target) {
+ member->ForwardRequestToPeer(s, target);
+ } else if (s->PathEquals("/sign_out")) {
+ s->Send("200 OK", true, "text/plain", "", "");
+ } else {
+ printf("Couldn't find target for request: %s\n",
+ s->request_path().c_str());
+ s->Send("500 Error", true, "text/plain", "",
+ "Peer most likely gone.");
+ }
+ }
+ } else {
+ HandleBrowserRequest(s, &quit);
+ if (quit) {
+ printf("Quitting...\n");
+ FD_CLR(listener.socket(), &socket_set);
+ listener.Close();
+ clients.CloseAll();
+ }
+ }
+ }
+ } else {
+ socket_done = false;
+ }
+
+ if (socket_done) {
+ printf("Disconnecting socket\n");
+ clients.OnClosing(s);
+ RTC_DCHECK(s->valid()); // Close must not have been called yet.
+ FD_CLR(s->socket(), &socket_set);
+ delete (*i);
+ i = sockets.erase(i);
+ if (i == sockets.end())
+ break;
+ }
+ }
+
+ clients.CheckForTimeout();
+
+ if (FD_ISSET(listener.socket(), &socket_set)) {
+ DataSocket* s = listener.Accept();
+ if (sockets.size() >= kMaxConnections) {
+ delete s; // sorry, that's all we can take.
+ printf("Connection limit reached\n");
+ } else {
+ sockets.push_back(s);
+ printf("New connection...\n");
+ }
+ }
+ }
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+ delete (*i);
+ sockets.clear();
+
+ return 0;
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc
new file mode 100644
index 0000000000..f53820cc60
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/server/peer_channel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+
+#include "examples/peerconnection/server/data_socket.h"
+#include "examples/peerconnection/server/utils.h"
+#include "rtc_base/checks.h"
+
+// Set to the peer id of the originator when messages are being
+// exchanged between peers, but set to the id of the receiving peer
+// itself when notifications are sent from the server about the state
+// of other peers.
+//
+// WORKAROUND: Since support for CORS varies greatly from one browser to the
+// next, we don't use a custom name for our peer-id header (originally it was
+// "X-Peer-Id: "). Instead, we use a "simple header", "Pragma" which should
+// always be exposed to CORS requests. There is a special CORS header devoted
+// to exposing proprietary headers (Access-Control-Expose-Headers), however
+// at this point it is not working correctly in some popular browsers.
+static const char kPeerIdHeader[] = "Pragma: ";
+
+static const char* kRequestPaths[] = {
+ "/wait",
+ "/sign_out",
+ "/message",
+};
+
+enum RequestPathIndex {
+ kWait,
+ kSignOut,
+ kMessage,
+};
+
+const size_t kMaxNameLength = 512;
+
+//
+// ChannelMember
+//
+
+int ChannelMember::s_member_id_ = 0;
+
+ChannelMember::ChannelMember(DataSocket* socket)
+ : waiting_socket_(NULL),
+ id_(++s_member_id_),
+ connected_(true),
+ timestamp_(time(NULL)) {
+ RTC_DCHECK(socket);
+ RTC_DCHECK_EQ(socket->method(), DataSocket::GET);
+ RTC_DCHECK(socket->PathEquals("/sign_in"));
+ name_ = socket->request_arguments();
+ if (name_.empty())
+ name_ = "peer_" + int2str(id_);
+ else if (name_.length() > kMaxNameLength)
+ name_.resize(kMaxNameLength);
+
+ std::replace(name_.begin(), name_.end(), ',', '_');
+}
+
+ChannelMember::~ChannelMember() {}
+
+bool ChannelMember::is_wait_request(DataSocket* ds) const {
+ return ds && ds->PathEquals(kRequestPaths[kWait]);
+}
+
+bool ChannelMember::TimedOut() {
+ return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30;
+}
+
+std::string ChannelMember::GetPeerIdHeader() const {
+ std::string ret(kPeerIdHeader + int2str(id_) + "\r\n");
+ return ret;
+}
+
+bool ChannelMember::NotifyOfOtherMember(const ChannelMember& other) {
+ RTC_DCHECK_NE(&other, this);
+ QueueResponse("200 OK", "text/plain", GetPeerIdHeader(), other.GetEntry());
+ return true;
+}
+
+// Returns a string in the form "name,id,connected\n".
+std::string ChannelMember::GetEntry() const {
+ RTC_DCHECK(name_.length() <= kMaxNameLength);
+
+ // name, 11-digit int, 1-digit bool, newline, null
+ char entry[kMaxNameLength + 15];
+ snprintf(entry, sizeof(entry), "%s,%d,%d\n",
+ name_.substr(0, kMaxNameLength).c_str(), id_, connected_);
+ return entry;
+}
+
+void ChannelMember::ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer) {
+ RTC_DCHECK(peer);
+ RTC_DCHECK(ds);
+
+ std::string extra_headers(GetPeerIdHeader());
+
+ if (peer == this) {
+ ds->Send("200 OK", true, ds->content_type(), extra_headers, ds->data());
+ } else {
+ printf("Client %s sending to %s\n", name_.c_str(), peer->name().c_str());
+ peer->QueueResponse("200 OK", ds->content_type(), extra_headers,
+ ds->data());
+ ds->Send("200 OK", true, "text/plain", "", "");
+ }
+}
+
+void ChannelMember::OnClosing(DataSocket* ds) {
+ if (ds == waiting_socket_) {
+ waiting_socket_ = NULL;
+ timestamp_ = time(NULL);
+ }
+}
+
+void ChannelMember::QueueResponse(const std::string& status,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) {
+ if (waiting_socket_) {
+ RTC_DCHECK(queue_.empty());
+ RTC_DCHECK_EQ(waiting_socket_->method(), DataSocket::GET);
+ bool ok =
+ waiting_socket_->Send(status, true, content_type, extra_headers, data);
+ if (!ok) {
+ printf("Failed to deliver data to waiting socket\n");
+ }
+ waiting_socket_ = NULL;
+ timestamp_ = time(NULL);
+ } else {
+ QueuedResponse qr;
+ qr.status = status;
+ qr.content_type = content_type;
+ qr.extra_headers = extra_headers;
+ qr.data = data;
+ queue_.push(qr);
+ }
+}
+
+void ChannelMember::SetWaitingSocket(DataSocket* ds) {
+ RTC_DCHECK_EQ(ds->method(), DataSocket::GET);
+ if (ds && !queue_.empty()) {
+ RTC_DCHECK(!waiting_socket_);
+ const QueuedResponse& response = queue_.front();
+ ds->Send(response.status, true, response.content_type,
+ response.extra_headers, response.data);
+ queue_.pop();
+ } else {
+ waiting_socket_ = ds;
+ }
+}
+
+//
+// PeerChannel
+//
+
+// static
+bool PeerChannel::IsPeerConnection(const DataSocket* ds) {
+ RTC_DCHECK(ds);
+ return (ds->method() == DataSocket::POST && ds->content_length() > 0) ||
+ (ds->method() == DataSocket::GET && ds->PathEquals("/sign_in"));
+}
+
+ChannelMember* PeerChannel::Lookup(DataSocket* ds) const {
+ RTC_DCHECK(ds);
+
+ if (ds->method() != DataSocket::GET && ds->method() != DataSocket::POST)
+ return NULL;
+
+ size_t i = 0;
+ for (; i < ARRAYSIZE(kRequestPaths); ++i) {
+ if (ds->PathEquals(kRequestPaths[i]))
+ break;
+ }
+
+ if (i == ARRAYSIZE(kRequestPaths))
+ return NULL;
+
+ std::string args(ds->request_arguments());
+ static const char kPeerId[] = "peer_id=";
+ size_t found = args.find(kPeerId);
+ if (found == std::string::npos)
+ return NULL;
+
+ int id = atoi(&args[found + ARRAYSIZE(kPeerId) - 1]);
+ Members::const_iterator iter = members_.begin();
+ for (; iter != members_.end(); ++iter) {
+ if (id == (*iter)->id()) {
+ if (i == kWait)
+ (*iter)->SetWaitingSocket(ds);
+ if (i == kSignOut)
+ (*iter)->set_disconnected();
+ return *iter;
+ }
+ }
+
+ return NULL;
+}
+
+ChannelMember* PeerChannel::IsTargetedRequest(const DataSocket* ds) const {
+ RTC_DCHECK(ds);
+ // Regardless of GET or POST, we look for the peer_id parameter
+ // only in the request_path.
+ const std::string& path = ds->request_path();
+ size_t args = path.find('?');
+ if (args == std::string::npos)
+ return NULL;
+ size_t found;
+ const char kTargetPeerIdParam[] = "to=";
+ do {
+ found = path.find(kTargetPeerIdParam, args);
+ if (found == std::string::npos)
+ return NULL;
+ if (found == (args + 1) || path[found - 1] == '&') {
+ found += ARRAYSIZE(kTargetPeerIdParam) - 1;
+ break;
+ }
+ args = found + ARRAYSIZE(kTargetPeerIdParam) - 1;
+ } while (true);
+ int id = atoi(&path[found]);
+ Members::const_iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ if ((*i)->id() == id) {
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+bool PeerChannel::AddMember(DataSocket* ds) {
+ RTC_DCHECK(IsPeerConnection(ds));
+ ChannelMember* new_guy = new ChannelMember(ds);
+ Members failures;
+ BroadcastChangedState(*new_guy, &failures);
+ HandleDeliveryFailures(&failures);
+ members_.push_back(new_guy);
+
+ printf("New member added (total=%s): %s\n",
+ size_t2str(members_.size()).c_str(), new_guy->name().c_str());
+
+ // Let the newly connected peer know about other members of the channel.
+ std::string content_type;
+ std::string response = BuildResponseForNewMember(*new_guy, &content_type);
+ ds->Send("200 Added", true, content_type, new_guy->GetPeerIdHeader(),
+ response);
+ return true;
+}
+
+void PeerChannel::CloseAll() {
+ Members::const_iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ (*i)->QueueResponse("200 OK", "text/plain", "", "Server shutting down");
+ }
+ DeleteAll();
+}
+
+void PeerChannel::OnClosing(DataSocket* ds) {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ ChannelMember* m = (*i);
+ m->OnClosing(ds);
+ if (!m->connected()) {
+ i = members_.erase(i);
+ Members failures;
+ BroadcastChangedState(*m, &failures);
+ HandleDeliveryFailures(&failures);
+ delete m;
+ if (i == members_.end())
+ break;
+ }
+ }
+ printf("Total connected: %s\n", size_t2str(members_.size()).c_str());
+}
+
+void PeerChannel::CheckForTimeout() {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ ChannelMember* m = (*i);
+ if (m->TimedOut()) {
+ printf("Timeout: %s\n", m->name().c_str());
+ m->set_disconnected();
+ i = members_.erase(i);
+ Members failures;
+ BroadcastChangedState(*m, &failures);
+ HandleDeliveryFailures(&failures);
+ delete m;
+ if (i == members_.end())
+ break;
+ }
+ }
+}
+
+void PeerChannel::DeleteAll() {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i)
+ delete (*i);
+ members_.clear();
+}
+
+void PeerChannel::BroadcastChangedState(const ChannelMember& member,
+ Members* delivery_failures) {
+ // This function should be called prior to DataSocket::Close().
+ RTC_DCHECK(delivery_failures);
+
+ if (!member.connected()) {
+ printf("Member disconnected: %s\n", member.name().c_str());
+ }
+
+ Members::iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ if (&member != (*i)) {
+ if (!(*i)->NotifyOfOtherMember(member)) {
+ (*i)->set_disconnected();
+ delivery_failures->push_back(*i);
+ i = members_.erase(i);
+ if (i == members_.end())
+ break;
+ }
+ }
+ }
+}
+
+void PeerChannel::HandleDeliveryFailures(Members* failures) {
+ RTC_DCHECK(failures);
+
+ while (!failures->empty()) {
+ Members::iterator i = failures->begin();
+ ChannelMember* member = *i;
+ RTC_DCHECK(!member->connected());
+ failures->erase(i);
+ BroadcastChangedState(*member, failures);
+ delete member;
+ }
+}
+
+// Builds a simple list of "name,id\n" entries for each member.
+std::string PeerChannel::BuildResponseForNewMember(const ChannelMember& member,
+ std::string* content_type) {
+ RTC_DCHECK(content_type);
+
+ *content_type = "text/plain";
+ // The peer itself will always be the first entry.
+ std::string response(member.GetEntry());
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ if (member.id() != (*i)->id()) {
+ RTC_DCHECK((*i)->connected());
+ response += (*i)->GetEntry();
+ }
+ }
+
+ return response;
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h
new file mode 100644
index 0000000000..c3624908ac
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+#define EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+
+#include <time.h>
+
+#include <queue>
+#include <string>
+#include <vector>
+
+class DataSocket;
+
+// Represents a single peer connected to the server.
+class ChannelMember {
+ public:
+ explicit ChannelMember(DataSocket* socket);
+ ~ChannelMember();
+
+ bool connected() const { return connected_; }
+ int id() const { return id_; }
+ void set_disconnected() { connected_ = false; }
+ bool is_wait_request(DataSocket* ds) const;
+ const std::string& name() const { return name_; }
+
+ bool TimedOut();
+
+ std::string GetPeerIdHeader() const;
+
+ bool NotifyOfOtherMember(const ChannelMember& other);
+
+ // Returns a string in the form "name,id\n".
+ std::string GetEntry() const;
+
+ void ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer);
+
+ void OnClosing(DataSocket* ds);
+
+ void QueueResponse(const std::string& status,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data);
+
+ void SetWaitingSocket(DataSocket* ds);
+
+ protected:
+ struct QueuedResponse {
+ std::string status, content_type, extra_headers, data;
+ };
+
+ DataSocket* waiting_socket_;
+ int id_;
+ bool connected_;
+ time_t timestamp_;
+ std::string name_;
+ std::queue<QueuedResponse> queue_;
+ static int s_member_id_;
+};
+
+// Manages all currently connected peers.
+class PeerChannel {
+ public:
+ typedef std::vector<ChannelMember*> Members;
+
+ PeerChannel() {}
+
+ ~PeerChannel() { DeleteAll(); }
+
+ const Members& members() const { return members_; }
+
+ // Returns true if the request should be treated as a new ChannelMember
+ // request. Otherwise the request is not peerconnection related.
+ static bool IsPeerConnection(const DataSocket* ds);
+
+ // Finds a connected peer that's associated with the `ds` socket.
+ ChannelMember* Lookup(DataSocket* ds) const;
+
+ // Checks if the request has a "peer_id" parameter and if so, looks up the
+ // peer for which the request is targeted at.
+ ChannelMember* IsTargetedRequest(const DataSocket* ds) const;
+
+ // Adds a new ChannelMember instance to the list of connected peers and
+ // associates it with the socket.
+ bool AddMember(DataSocket* ds);
+
+ // Closes all connections and sends a "shutting down" message to all
+ // connected peers.
+ void CloseAll();
+
+ // Called when a socket was determined to be closing by the peer (or if the
+ // connection went dead).
+ void OnClosing(DataSocket* ds);
+
+ void CheckForTimeout();
+
+ protected:
+ void DeleteAll();
+ void BroadcastChangedState(const ChannelMember& member,
+ Members* delivery_failures);
+ void HandleDeliveryFailures(Members* failures);
+
+ // Builds a simple list of "name,id\n" entries for each member.
+ std::string BuildResponseForNewMember(const ChannelMember& member,
+ std::string* content_type);
+
+ protected:
+ Members members_;
+};
+
+#endif // EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/server/server_test.html b/third_party/libwebrtc/examples/peerconnection/server/server_test.html
new file mode 100644
index 0000000000..0a165f19d5
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/server_test.html
@@ -0,0 +1,237 @@
+<html>
+<head>
+<title>PeerConnection server test page</title>
+
+<script>
+var request = null;
+var hangingGet = null;
+var localName;
+var server;
+var my_id = -1;
+var other_peers = {};
+var message_counter = 0;
+
+function trace(txt) {
+ var elem = document.getElementById("debug");
+ elem.innerHTML += txt + "<br>";
+}
+
+function handleServerNotification(data) {
+ trace("Server notification: " + data);
+ var parsed = data.split(',');
+ if (parseInt(parsed[2]) != 0)
+ other_peers[parseInt(parsed[1])] = parsed[0];
+}
+
+function handlePeerMessage(peer_id, data) {
+ ++message_counter;
+ var str = "Message from '" + other_peers[peer_id] + "'&nbsp;";
+ str += "<span id='toggle_" + message_counter + "' onclick='toggleMe(this);' ";
+ str += "style='cursor: pointer'>+</span><br>";
+ str += "<blockquote id='msg_" + message_counter + "' style='display:none'>";
+ str += data + "</blockquote>";
+ trace(str);
+ if (document.getElementById("loopback").checked) {
+ if (data.search("offer") != -1) {
+ // In loopback mode, if DTLS is enabled, notify the client to disable it.
+ // Otherwise replace the offer with an answer.
+ if (data.search("fingerprint") != -1)
+ data = data.replace("offer", "offer-loopback");
+ else
+ data = data.replace("offer", "answer");
+ }
+ sendToPeer(peer_id, data);
+ }
+}
+
+function GetIntHeader(r, name) {
+ var val = r.getResponseHeader(name);
+ return val != null && val.length ? parseInt(val) : -1;
+}
+
+function hangingGetCallback() {
+ try {
+ if (hangingGet.readyState != 4)
+ return;
+ if (hangingGet.status != 200) {
+ trace("server error: " + hangingGet.statusText);
+ disconnect();
+ } else {
+ var peer_id = GetIntHeader(hangingGet, "Pragma");
+ if (peer_id == my_id) {
+ handleServerNotification(hangingGet.responseText);
+ } else {
+ handlePeerMessage(peer_id, hangingGet.responseText);
+ }
+ }
+
+ if (hangingGet) {
+ hangingGet.abort();
+ hangingGet = null;
+ }
+
+ if (my_id != -1)
+ window.setTimeout(startHangingGet, 0);
+ } catch (e) {
+ trace("Hanging get error: " + e.description);
+ }
+}
+
+function startHangingGet() {
+ try {
+ hangingGet = new XMLHttpRequest();
+ hangingGet.onreadystatechange = hangingGetCallback;
+ hangingGet.ontimeout = onHangingGetTimeout;
+ hangingGet.open("GET", server + "/wait?peer_id=" + my_id, true);
+ hangingGet.send();
+ } catch (e) {
+ trace("error" + e.description);
+ }
+}
+
+function onHangingGetTimeout() {
+ trace("hanging get timeout. issuing again.");
+ hangingGet.abort();
+ hangingGet = null;
+ if (my_id != -1)
+ window.setTimeout(startHangingGet, 0);
+}
+
+function signInCallback() {
+ try {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ var peers = request.responseText.split("\n");
+ my_id = parseInt(peers[0].split(',')[1]);
+ trace("My id: " + my_id);
+ for (var i = 1; i < peers.length; ++i) {
+ if (peers[i].length > 0) {
+ trace("Peer " + i + ": " + peers[i]);
+ var parsed = peers[i].split(',');
+ other_peers[parseInt(parsed[1])] = parsed[0];
+ }
+ }
+ startHangingGet();
+ request = null;
+ }
+ }
+ } catch (e) {
+ trace("error: " + e.description);
+ }
+}
+
+function signIn() {
+ try {
+ request = new XMLHttpRequest();
+ request.onreadystatechange = signInCallback;
+ request.open("GET", server + "/sign_in?" + localName, true);
+ request.send();
+ } catch (e) {
+ trace("error: " + e.description);
+ }
+}
+
+function sendToPeer(peer_id, data) {
+ if (my_id == -1) {
+ alert("Not connected");
+ return;
+ }
+ if (peer_id == my_id) {
+ alert("Can't send a message to oneself :)");
+ return;
+ }
+ var r = new XMLHttpRequest();
+ r.open("POST", server + "/message?peer_id=" + my_id + "&to=" + peer_id,
+ false);
+ r.setRequestHeader("Content-Type", "text/plain");
+ r.send(data);
+ r = null;
+}
+
+function connect() {
+ localName = document.getElementById("local").value.toLowerCase();
+ server = document.getElementById("server").value.toLowerCase();
+ if (localName.length == 0) {
+ alert("I need a name please.");
+ document.getElementById("local").focus();
+ } else {
+ document.getElementById("connect").disabled = true;
+ document.getElementById("disconnect").disabled = false;
+ document.getElementById("send").disabled = false;
+ signIn();
+ }
+}
+
+function disconnect() {
+ if (request) {
+ request.abort();
+ request = null;
+ }
+
+ if (hangingGet) {
+ hangingGet.abort();
+ hangingGet = null;
+ }
+
+ if (my_id != -1) {
+ request = new XMLHttpRequest();
+ request.open("GET", server + "/sign_out?peer_id=" + my_id, false);
+ request.send();
+ request = null;
+ my_id = -1;
+ }
+
+ document.getElementById("connect").disabled = false;
+ document.getElementById("disconnect").disabled = true;
+ document.getElementById("send").disabled = true;
+}
+
+window.onbeforeunload = disconnect;
+
+function send() {
+ var text = document.getElementById("message").value;
+ var peer_id = parseInt(document.getElementById("peer_id").value);
+ if (!text.length || peer_id == 0) {
+ alert("No text supplied or invalid peer id");
+ } else {
+ sendToPeer(peer_id, text);
+ }
+}
+
+function toggleMe(obj) {
+ var id = obj.id.replace("toggle", "msg");
+ var t = document.getElementById(id);
+ if (obj.innerText == "+") {
+ obj.innerText = "-";
+ t.style.display = "block";
+ } else {
+ obj.innerText = "+";
+ t.style.display = "none";
+ }
+}
+
+</script>
+
+</head>
+<body>
+Server: <input type="text" id="server" value="http://localhost:8888" /><br>
+<input type="checkbox" id="loopback" checked="checked"/> Loopback (just send
+received messages right back)<br>
+Your name: <input type="text" id="local" value="my_name"/>
+<button id="connect" onclick="connect();">Connect</button>
+<button disabled="true" id="disconnect"
+ onclick="disconnect();">Disconnect</button>
+<br>
+<table><tr><td>
+Target peer id: <input type="text" id="peer_id" size="3"/></td><td>
+Message: <input type="text" id="message"/></td><td>
+<button disabled="true" id="send" onclick="send();">Send</button>
+</td></tr></table>
+<button onclick="document.getElementById('debug').innerHTML='';">
+Clear log</button>
+
+<pre id="debug">
+</pre>
+<br><hr>
+</body>
+</html>
diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.cc b/third_party/libwebrtc/examples/peerconnection/server/utils.cc
new file mode 100644
index 0000000000..5e61e601d9
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/utils.cc
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/server/utils.h"
+
+#include <stdio.h>
+
+#include "rtc_base/string_encode.h"
+
+using rtc::ToString;
+
+std::string int2str(int i) {
+ return ToString(i);
+}
+
+std::string size_t2str(size_t i) {
+ return ToString(i);
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.h b/third_party/libwebrtc/examples/peerconnection/server/utils.h
new file mode 100644
index 0000000000..85c04a40e9
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/utils.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+#define EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
+
+std::string int2str(int i);
+std::string size_t2str(size_t i);
+
+#endif // EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_