git-svn-id: http://webrtc.googlecode.com/svn/trunk@5 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/peerconnection/samples/client/conductor.cc b/peerconnection/samples/client/conductor.cc
new file mode 100644
index 0000000..698ba48
--- /dev/null
+++ b/peerconnection/samples/client/conductor.cc
@@ -0,0 +1,321 @@
+/*
+ *  Copyright (c) 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 "peerconnection/samples/client/conductor.h"
+
+#include "peerconnection/samples/client/defaults.h"
+#include "talk/base/logging.h"
+
+Conductor::Conductor(PeerConnectionClient* client, MainWnd* main_wnd)
+  : handshake_(NONE),
+    waiting_for_audio_(false),
+    waiting_for_video_(false),
+    peer_id_(-1),
+    video_channel_(-1),
+    audio_channel_(-1),
+    client_(client),
+    main_wnd_(main_wnd) {
+  // Create a window for posting notifications back to from other threads.
+  bool ok = Create(HWND_MESSAGE, L"Conductor", 0, 0, 0, 0, 0, 0);
+  ASSERT(ok);
+  client_->RegisterObserver(this);
+  main_wnd->RegisterObserver(this);
+}
+
+Conductor::~Conductor() {
+  ASSERT(peer_connection_.get() == NULL);
+  Destroy();
+  DeletePeerConnection();
+}
+
+bool Conductor::has_video() const {
+  return video_channel_ != -1;
+}
+
+bool Conductor::has_audio() const {
+  return audio_channel_ != -1;
+}
+
+bool Conductor::connection_active() const {
+  return peer_connection_.get() != NULL;
+}
+
+void Conductor::Close() {
+  if (peer_connection_.get()) {
+    peer_connection_->Close();
+  } else {
+    client_->SignOut();
+  }
+}
+
+bool Conductor::InitializePeerConnection() {
+  ASSERT(peer_connection_.get() == NULL);
+  peer_connection_.reset(new webrtc::PeerConnection(GetPeerConnectionString()));
+  peer_connection_->RegisterObserver(this);
+  if (!peer_connection_->Init()) {
+    DeletePeerConnection();
+  } else {
+    bool audio = peer_connection_->SetAudioDevice("", "", 0);
+    LOG(INFO) << "SetAudioDevice " << (audio ? "succeeded." : "failed.");
+  }
+  return peer_connection_.get() != NULL;
+}
+
+void Conductor::DeletePeerConnection() {
+  peer_connection_.reset();
+  handshake_ = NONE;
+}
+
+void Conductor::StartCaptureDevice() {
+  ASSERT(peer_connection_.get());
+  if (main_wnd_->IsWindow()) {
+    main_wnd_->SwitchToStreamingUI();
+
+    if (peer_connection_->SetVideoCapture("")) {
+      peer_connection_->SetVideoRenderer(-1, main_wnd_->handle(), 0,
+                                         0.7f, 0.7f, 0.95f, 0.95f);
+    } else {
+      ASSERT(false);
+    }
+  }
+}
+
+//
+// PeerConnectionObserver implementation.
+//
+
+void Conductor::OnError() {
+  LOG(INFO) << __FUNCTION__;
+  ASSERT(false);
+}
+
+void Conductor::OnSignalingMessage(const std::string& msg) {
+  LOG(INFO) << __FUNCTION__;
+
+  bool shutting_down = (video_channel_ == -1 && audio_channel_ == -1);
+
+  if (handshake_ == OFFER_RECEIVED && !shutting_down)
+    StartCaptureDevice();
+
+  // Send our answer/offer/shutting down message.
+  // If we're the initiator, this will be our offer.  If we just received
+  // an offer, this will be an answer.  If PeerConnection::Close has been
+  // called, then this is our signal to the other end that we're shutting
+  // down.
+  if (handshake_ != QUIT_SENT) {
+    SendMessage(handle(), SEND_MESSAGE_TO_PEER, 0,
+                reinterpret_cast<LPARAM>(&msg));
+  }
+
+  if (shutting_down) {
+    handshake_ = QUIT_SENT;
+    PostMessage(handle(), PEER_CONNECTION_CLOSED, 0, 0);
+  }
+}
+
+// Called when a remote stream is added
+void Conductor::OnAddStream(const std::string& stream_id, int channel_id,
+                            bool video) {
+  LOG(INFO) << __FUNCTION__ << " " << stream_id;
+  bool send_notification = (waiting_for_video_ || waiting_for_audio_);
+  if (video) {
+    ASSERT(video_channel_ == -1);
+    video_channel_ = channel_id;
+    waiting_for_video_ = false;
+    LOG(INFO) << "Setting video renderer for channel: " << channel_id;
+    bool ok = peer_connection_->SetVideoRenderer(channel_id,
+        main_wnd_->handle(), 1, 0.0f, 0.0f, 1.0f, 1.0f);
+    ASSERT(ok);
+  } else {
+    ASSERT(audio_channel_ == -1);
+    audio_channel_ = channel_id;
+    waiting_for_audio_ = false;
+  }
+
+  if (send_notification && !waiting_for_audio_ && !waiting_for_video_)
+    PostMessage(handle(), MEDIA_CHANNELS_INITIALIZED, 0, 0);
+}
+
+void Conductor::OnRemoveStream(const std::string& stream_id, int channel_id,
+                               bool video) {
+  LOG(INFO) << __FUNCTION__;
+  if (video) {
+    ASSERT(channel_id == video_channel_);
+    video_channel_ = -1;
+  } else {
+    ASSERT(channel_id == audio_channel_);
+    audio_channel_ = -1;
+  }
+}
+
+//
+// PeerConnectionClientObserver implementation.
+//
+
+void Conductor::OnSignedIn() {
+  LOG(INFO) << __FUNCTION__;
+  main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnDisconnected() {
+  LOG(INFO) << __FUNCTION__;
+  if (peer_connection_.get()) {
+    peer_connection_->Close();
+  } else if (main_wnd_->IsWindow()) {
+    main_wnd_->SwitchToConnectUI();
+  }
+}
+
+void Conductor::OnPeerConnected(int id, const std::string& name) {
+  LOG(INFO) << __FUNCTION__;
+  // Refresh the list if we're showing it.
+  if (main_wnd_->current_ui() == MainWnd::LIST_PEERS)
+    main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnPeerDisconnected(int id, const std::string& name) {
+  LOG(INFO) << __FUNCTION__;
+  if (id == peer_id_) {
+    LOG(INFO) << "Our peer disconnected";
+    peer_id_ = -1;
+    if (peer_connection_.get())
+      peer_connection_->Close();
+  }
+
+  // Refresh the list if we're showing it.
+  if (main_wnd_->current_ui() == MainWnd::LIST_PEERS)
+    main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) {
+  ASSERT(peer_id_ == peer_id || peer_id_ == -1);
+
+  if (handshake_ == NONE) {
+    handshake_ = OFFER_RECEIVED;
+    peer_id_ = peer_id;
+    if (!peer_connection_.get()) {
+      // Got an offer.  Give it to the PeerConnection instance.
+      // Once processed, we will get a callback to OnSignalingMessage with
+      // our 'answer' which we'll send to the peer.
+      LOG(INFO) << "Got an offer from our peer: " << peer_id;
+      if (!InitializePeerConnection()) {
+        LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";
+        client_->SignOut();
+        return;
+      }
+    }
+  } else if (handshake_ == INITIATOR) {
+    LOG(INFO) << "Remote peer sent us an answer";
+    handshake_ = ANSWER_RECEIVED;
+  } else {
+    LOG(INFO) << "Remote peer is disconnecting";
+    handshake_ = QUIT_SENT;
+  }
+
+  peer_connection_->SignalingMessage(message);
+
+  if (handshake_ == QUIT_SENT) {
+    DisconnectFromCurrentPeer();
+  }
+}
+
+//
+// MainWndCallback implementation.
+//
+
+void Conductor::StartLogin(const std::string& server, int port) {
+  ASSERT(!client_->is_connected());
+  if (!client_->Connect(server, port, GetPeerName())) {
+    MessageBoxA(main_wnd_->handle(),
+        ("Failed to connect to " + server).c_str(),
+        "Error", MB_OK | MB_ICONERROR);
+  }
+}
+
+void Conductor::DisconnectFromServer() {
+  if (!client_->is_connected())
+    return;
+  client_->SignOut();
+}
+
+void Conductor::ConnectToPeer(int peer_id) {
+  ASSERT(peer_id_ == -1);
+  ASSERT(peer_id != -1);
+  ASSERT(handshake_ == NONE);
+
+  if (handshake_ != NONE)
+    return;
+
+  if (InitializePeerConnection()) {
+    peer_id_ = peer_id;
+    waiting_for_video_ = peer_connection_->AddStream(kVideoLabel, true);
+    waiting_for_audio_ = peer_connection_->AddStream(kAudioLabel, false);
+    if (waiting_for_video_ || waiting_for_audio_)
+      handshake_ = INITIATOR;
+    ASSERT(waiting_for_video_ || waiting_for_audio_);
+  }
+
+  if (handshake_ == NONE) {
+    ::MessageBoxA(main_wnd_->handle(), "Failed to initialize PeerConnection",
+                  "Error", MB_OK | MB_ICONERROR);
+  }
+}
+
+void Conductor::DisconnectFromCurrentPeer() {
+  if (peer_connection_.get())
+    peer_connection_->Close();
+}
+
+//
+// Win32Window implementation.
+//
+
+bool Conductor::OnMessage(UINT msg, WPARAM wp, LPARAM lp,
+                          LRESULT& result) {  // NOLINT
+  bool ret = true;
+  if (msg == MEDIA_CHANNELS_INITIALIZED) {
+      ASSERT(handshake_ == INITIATOR);
+      bool ok = peer_connection_->Connect();
+      ASSERT(ok);
+      StartCaptureDevice();
+      // When we get an OnSignalingMessage notification, we'll send our
+      // json encoded signaling message to the peer, which is the first step
+      // of establishing a connection.
+  } else if (msg == PEER_CONNECTION_CLOSED) {
+    LOG(INFO) << "PEER_CONNECTION_CLOSED";
+    DeletePeerConnection();
+    ::InvalidateRect(main_wnd_->handle(), NULL, TRUE);
+    waiting_for_audio_ = false;
+    waiting_for_video_ = false;
+    peer_id_ = -1;
+    ASSERT(video_channel_ == -1);
+    ASSERT(audio_channel_ == -1);
+    if (main_wnd_->IsWindow()) {
+      if (client_->is_connected()) {
+        main_wnd_->SwitchToPeerList(client_->peers());
+      } else {
+        main_wnd_->SwitchToConnectUI();
+      }
+    } else {
+      DisconnectFromServer();
+    }
+  } else if (msg == SEND_MESSAGE_TO_PEER) {
+    bool ok = client_->SendToPeer(peer_id_,
+                                  *reinterpret_cast<std::string*>(lp));
+    if (!ok) {
+      LOG(LS_ERROR) << "SendToPeer failed";
+      DisconnectFromServer();
+    }
+  } else {
+    ret = false;
+  }
+
+  return ret;
+}
diff --git a/peerconnection/samples/client/conductor.h b/peerconnection/samples/client/conductor.h
new file mode 100644
index 0000000..ce968d7
--- /dev/null
+++ b/peerconnection/samples/client/conductor.h
@@ -0,0 +1,117 @@
+/*
+ *  Copyright (c) 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 PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
+#pragma once
+
+#include <string>
+
+#include "peerconnection/samples/client/main_wnd.h"
+#include "peerconnection/samples/client/peer_connection_client.h"
+#include "talk/app/peerconnection.h"
+#include "talk/base/scoped_ptr.h"
+
+
+class Conductor
+  : public webrtc::PeerConnectionObserver,
+    public PeerConnectionClientObserver,
+    public MainWndCallback,
+    public talk_base::Win32Window {
+ public:
+  enum WindowMessages {
+    MEDIA_CHANNELS_INITIALIZED = WM_APP + 1,
+    PEER_CONNECTION_CLOSED,
+    SEND_MESSAGE_TO_PEER,
+  };
+
+  enum HandshakeState {
+    NONE,
+    INITIATOR,
+    ANSWER_RECEIVED,
+    OFFER_RECEIVED,
+    QUIT_SENT,
+  };
+
+  Conductor(PeerConnectionClient* client, MainWnd* main_wnd);
+  ~Conductor();
+
+  bool has_video() const;
+  bool has_audio() const;
+  bool connection_active() const;
+
+  void Close();
+
+ protected:
+  bool InitializePeerConnection();
+  void DeletePeerConnection();
+  void StartCaptureDevice();
+
+  //
+  // PeerConnectionObserver implementation.
+  //
+
+  virtual void OnError();
+  virtual void OnSignalingMessage(const std::string& msg);
+
+  // Called when a remote stream is added
+  virtual void OnAddStream(const std::string& stream_id, int channel_id,
+                           bool video);
+
+  virtual void OnRemoveStream(const std::string& stream_id,
+                              int channel_id,
+                              bool video);
+
+  //
+  // PeerConnectionClientObserver implementation.
+  //
+
+  virtual void OnSignedIn();
+
+  virtual void OnDisconnected();
+
+  virtual void OnPeerConnected(int id, const std::string& name);
+
+  virtual void OnPeerDisconnected(int id, const std::string& name);
+
+  virtual void OnMessageFromPeer(int peer_id, const std::string& message);
+
+  //
+  // MainWndCallback implementation.
+  //
+
+  virtual void StartLogin(const std::string& server, int port);
+
+  virtual void DisconnectFromServer();
+
+  virtual void ConnectToPeer(int peer_id);
+
+  virtual void DisconnectFromCurrentPeer();
+
+  //
+  // Win32Window implementation.
+  //
+
+  virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp,
+                         LRESULT& result); // NOLINT
+
+ protected:
+  HandshakeState handshake_;
+  bool waiting_for_audio_;
+  bool waiting_for_video_;
+  int peer_id_;
+  talk_base::scoped_ptr<webrtc::PeerConnection> peer_connection_;
+  PeerConnectionClient* client_;
+  MainWnd* main_wnd_;
+  int video_channel_;
+  int audio_channel_;
+};
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
diff --git a/peerconnection/samples/client/defaults.cc b/peerconnection/samples/client/defaults.cc
new file mode 100644
index 0000000..c8e8527
--- /dev/null
+++ b/peerconnection/samples/client/defaults.cc
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 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 "peerconnection/samples/client/defaults.h"
+
+const char kAudioLabel[] = "audio_label";
+const char kVideoLabel[] = "video_label";
+const uint16 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[MAX_PATH] = {0}, user_name[MAX_PATH] = {0};
+  DWORD size = ARRAYSIZE(computer_name);
+  ::GetComputerNameA(computer_name, &size);
+  size = ARRAYSIZE(user_name);
+  ::GetUserNameA(user_name, &size);
+  std::string ret(user_name);
+  ret += '@';
+  ret += computer_name;
+  return ret;
+}
diff --git a/peerconnection/samples/client/defaults.h b/peerconnection/samples/client/defaults.h
new file mode 100644
index 0000000..466a965
--- /dev/null
+++ b/peerconnection/samples/client/defaults.h
@@ -0,0 +1,30 @@
+/*
+ *  Copyright (c) 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 PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
+#pragma once
+
+#include <windows.h>
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+extern const char kAudioLabel[];
+extern const char kVideoLabel[];
+extern const uint16 kDefaultServerPort;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+                               const char* default_value);
+std::string GetPeerConnectionString();
+std::string GetDefaultServerName();
+std::string GetPeerName();
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
diff --git a/peerconnection/samples/client/main.cc b/peerconnection/samples/client/main.cc
new file mode 100644
index 0000000..21054b1
--- /dev/null
+++ b/peerconnection/samples/client/main.cc
@@ -0,0 +1,57 @@
+/*
+ *  Copyright (c) 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 <windows.h>
+
+#include "peerconnection/samples/client/conductor.h"
+#include "peerconnection/samples/client/main_wnd.h"
+#include "peerconnection/samples/client/peer_connection_client.h"
+#include "system_wrappers/source/trace_impl.h"
+#include "talk/base/win32socketinit.h"
+
+
+int PASCAL wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
+                    wchar_t* cmd_line, int cmd_show) {
+  talk_base::EnsureWinsockInit();
+
+  webrtc::Trace::CreateTrace();
+  webrtc::Trace::SetTraceFile("peerconnection_client.log");
+  webrtc::Trace::SetLevelFilter(webrtc::kTraceWarning);
+
+  MainWnd wnd;
+  if (!wnd.Create()) {
+    ASSERT(false);
+    return -1;
+  }
+
+  PeerConnectionClient client;
+  Conductor conductor(&client, &wnd);
+
+  // Main loop.
+  MSG msg;
+  BOOL gm;
+  while ((gm = ::GetMessage(&msg, NULL, 0, 0)) && gm != -1) {
+    if (!wnd.PreTranslateMessage(&msg)) {
+      ::TranslateMessage(&msg);
+      ::DispatchMessage(&msg);
+    }
+  }
+
+  if (conductor.connection_active() || client.is_connected()) {
+    conductor.Close();
+    while ((conductor.connection_active() || client.is_connected()) &&
+           (gm = ::GetMessage(&msg, NULL, 0, 0)) && gm != -1) {
+      ::TranslateMessage(&msg);
+      ::DispatchMessage(&msg);
+    }
+  }
+
+  return 0;
+}
diff --git a/peerconnection/samples/client/main_wnd.cc b/peerconnection/samples/client/main_wnd.cc
new file mode 100644
index 0000000..e75133f
--- /dev/null
+++ b/peerconnection/samples/client/main_wnd.cc
@@ -0,0 +1,396 @@
+/*
+ *  Copyright (c) 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 "peerconnection/samples/client/main_wnd.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+ATOM MainWnd::wnd_class_ = 0;
+const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd";
+
+// TODO(tommi): declare in header:
+std::string GetDefaultServerName();
+
+namespace {
+void CalculateWindowSizeForText(HWND wnd, const wchar_t* text,
+                                size_t* width, size_t* height) {
+  HDC dc = ::GetDC(wnd);
+  RECT text_rc = {0};
+  ::DrawText(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()
+  : ui_(CONNECT_TO_SERVER), wnd_(NULL), edit1_(NULL), edit2_(NULL),
+    label1_(NULL), label2_(NULL), button_(NULL), listbox_(NULL),
+    destroyed_(false), callback_(NULL), nested_msg_(NULL) {
+}
+
+MainWnd::~MainWnd() {
+  ASSERT(!IsWindow());
+}
+
+bool MainWnd::Create() {
+  ASSERT(wnd_ == NULL);
+  if (!RegisterWindowClass())
+    return false;
+
+  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() const {
+  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();
+        }
+      }
+    }
+  }
+  return ret;
+}
+
+void MainWnd::SwitchToConnectUI() {
+  ASSERT(IsWindow());
+  LayoutPeerListUI(false);
+  ui_ = CONNECT_TO_SERVER;
+  LayoutConnectUI(true);
+  ::SetFocus(edit1_);
+}
+
+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);
+}
+
+void MainWnd::SwitchToStreamingUI() {
+  LayoutConnectUI(false);
+  LayoutPeerListUI(false);
+  ui_ = STREAMING;
+}
+
+void MainWnd::OnPaint() {
+  PAINTSTRUCT ps;
+  ::BeginPaint(handle(), &ps);
+
+  RECT rc;
+  ::GetClientRect(handle(), &rc);
+  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_);
+      }
+      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;
+  }
+  return false;
+}
+
+// static
+LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
+  MainWnd* me = reinterpret_cast<MainWnd*>(
+      ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+  if (!me && WM_CREATE == msg) {
+    CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lp);
+    me = reinterpret_cast<MainWnd*>(cs->lpCreateParams);
+    me->wnd_ = hwnd;
+    ::SetWindowLongPtr(hwnd, GWL_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;
+
+  WNDCLASSEX 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_ = ::RegisterClassEx(&wcex);
+  ASSERT(wnd_class_);
+  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 = ::CreateWindowEx(ex_style, class_name, L"", style,
+                          100, 100, 100, 100, wnd_,
+                          reinterpret_cast<HMENU>(id),
+                          GetModuleHandle(NULL), NULL);
+  ASSERT(::IsWindow(*wnd));
+  ::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_, GetDefaultServerName().c_str());
+  ::SetWindowTextA(edit2_, "8888");
+}
+
+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, x, top, windows[i].width, windows[i].height,
+                   TRUE);
+      x += kSeparator + windows[i].width;
+      if (windows[i].text[0] != 'X')
+        ::SetWindowText(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);
+  }
+}
+
+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);
+}
diff --git a/peerconnection/samples/client/main_wnd.h b/peerconnection/samples/client/main_wnd.h
new file mode 100644
index 0000000..0fadfd8
--- /dev/null
+++ b/peerconnection/samples/client/main_wnd.h
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) 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 PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "peerconnection/samples/client/peer_connection_client.h"
+#include "talk/base/win32.h"
+
+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;
+ protected:
+  virtual ~MainWndCallback() {}
+};
+
+class MainWnd {
+ public:
+  static const wchar_t kClassName[];
+
+  enum UI {
+    CONNECT_TO_SERVER,
+    LIST_PEERS,
+    STREAMING,
+  };
+
+  MainWnd();
+  ~MainWnd();
+
+  bool Create();
+  bool Destroy();
+  bool IsWindow() const;
+
+  void RegisterObserver(MainWndCallback* callback);
+
+  bool PreTranslateMessage(MSG* msg);
+
+  void SwitchToConnectUI();
+  void SwitchToPeerList(const Peers& peers);
+  void SwitchToStreamingUI();
+
+  HWND handle() const { return wnd_; }
+  UI current_ui() const { return ui_; }
+
+ 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:
+  UI ui_;
+  HWND wnd_;
+  HWND edit1_;
+  HWND edit2_;
+  HWND label1_;
+  HWND label2_;
+  HWND button_;
+  HWND listbox_;
+  bool destroyed_;
+  void* nested_msg_;
+  MainWndCallback* callback_;
+  static ATOM wnd_class_;
+};
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
diff --git a/peerconnection/samples/client/peer_connection_client.cc b/peerconnection/samples/client/peer_connection_client.cc
new file mode 100644
index 0000000..be5fd90
--- /dev/null
+++ b/peerconnection/samples/client/peer_connection_client.cc
@@ -0,0 +1,406 @@
+/*
+ *  Copyright (c) 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 "peerconnection/samples/client/peer_connection_client.h"
+
+#include "peerconnection/samples/client/defaults.h"
+#include "talk/base/logging.h"
+
+PeerConnectionClient::PeerConnectionClient()
+  : callback_(NULL), my_id_(-1), state_(NOT_CONNECTED) {
+  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);
+}
+
+PeerConnectionClient::~PeerConnectionClient() {
+}
+
+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) {
+  ASSERT(!callback_);
+  callback_ = callback;
+}
+
+bool PeerConnectionClient::Connect(const std::string& server, int port,
+                                   const std::string& client_name) {
+  ASSERT(!server.empty());
+  ASSERT(!client_name.empty());
+  ASSERT(state_ == NOT_CONNECTED);
+
+  if (server.empty() || client_name.empty())
+    return false;
+
+  if (port <= 0)
+    port = kDefaultServerPort;
+
+  server_address_.SetIP(server);
+  server_address_.SetPort(port);
+
+  if (server_address_.IsUnresolved()) {
+    hostent* h = gethostbyname(server_address_.IPAsString().c_str());
+    if (!h) {
+      LOG(LS_ERROR) << "Failed to resolve host name: "
+                    << server_address_.IPAsString();
+      return false;
+    } else {
+      server_address_.SetResolvedIP(
+          ntohl(*reinterpret_cast<uint32*>(h->h_addr_list[0])));
+    }
+  }
+
+  char buffer[1024];
+  wsprintfA(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;
+
+  return ret;
+}
+
+bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) {
+  if (state_ != CONNECTED)
+    return false;
+
+  ASSERT(is_connected());
+  ASSERT(control_socket_.GetState() == talk_base::Socket::CS_CLOSED);
+  if (!is_connected() || peer_id == -1)
+    return false;
+
+  char headers[1024];
+  wsprintfA(headers, "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
+                     "Content-Length: %i\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::SignOut() {
+  if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
+    return true;
+
+  if (hanging_get_.GetState() != talk_base::Socket::CS_CLOSED)
+    hanging_get_.Close();
+
+  if (control_socket_.GetState() == talk_base::Socket::CS_CLOSED) {
+    ASSERT(my_id_ != -1);
+    state_ = SIGNING_OUT;
+
+    char buffer[1024];
+    wsprintfA(buffer, "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
+    onconnect_data_ = buffer;
+    return ConnectControlSocket();
+  } else {
+    state_ = SIGNING_OUT_WAITING;
+  }
+
+  return true;
+}
+
+void PeerConnectionClient::Close() {
+  control_socket_.Close();
+  hanging_get_.Close();
+  onconnect_data_.clear();
+  peers_.clear();
+  my_id_ = -1;
+  state_ = NOT_CONNECTED;
+}
+
+bool PeerConnectionClient::ConnectControlSocket() {
+  ASSERT(control_socket_.GetState() == talk_base::Socket::CS_CLOSED);
+  int err = control_socket_.Connect(server_address_);
+  if (err == SOCKET_ERROR) {
+    Close();
+    return false;
+  }
+  return true;
+}
+
+void PeerConnectionClient::OnConnect(talk_base::AsyncSocket* socket) {
+  ASSERT(!onconnect_data_.empty());
+  int sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
+  ASSERT(sent == onconnect_data_.length());
+  onconnect_data_.clear();
+}
+
+void PeerConnectionClient::OnHangingGetConnect(talk_base::AsyncSocket* socket) {
+  char buffer[1024];
+  wsprintfA(buffer, "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
+  int len = lstrlenA(buffer);
+  int sent = socket->Send(buffer, len);
+  ASSERT(sent == len);
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data,
+                                          size_t eoh,
+                                          const char* header_pattern,
+                                          size_t* value) {
+  ASSERT(value);
+  size_t found = data.find(header_pattern);
+  if (found != std::string::npos && found < eoh) {
+    *value = atoi(&data[found + lstrlenA(header_pattern)]);
+    return true;
+  }
+  return false;
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh,
+                                          const char* header_pattern,
+                                          std::string* value) {
+  ASSERT(value);
+  size_t found = data.find(header_pattern);
+  if (found != std::string::npos && found < eoh) {
+    size_t begin = found + lstrlenA(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(talk_base::AsyncSocket* socket,
+                                          std::string* data,
+                                          size_t* content_length) {
+  LOG(INFO) << __FUNCTION__;
+
+  char buffer[0xffff];
+  do {
+    int bytes = socket->Recv(buffer, sizeof(buffer));
+    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) {
+    LOG(INFO) << "Headers received";
+    const char kContentLengthHeader[] = "\r\nContent-Length: ";
+    if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
+      LOG(INFO) << "Expecting " << *content_length << " bytes.";
+      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();
+        }
+      } else {
+        // We haven't received everything.  Just continue to accept data.
+      }
+    } else {
+      LOG(LS_ERROR) << "No content length field specified by the server.";
+    }
+  }
+  return ret;
+}
+
+void PeerConnectionClient::OnRead(talk_base::AsyncSocket* socket) {
+  LOG(INFO) << __FUNCTION__;
+  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.
+        ASSERT(state_ == SIGNING_IN);
+        my_id_ = peer_id;
+        ASSERT(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;
+          }
+        }
+        ASSERT(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) {
+      ASSERT(hanging_get_.GetState() == talk_base::Socket::CS_CLOSED);
+      state_ = CONNECTED;
+      hanging_get_.Connect(server_address_);
+    }
+  }
+}
+
+void PeerConnectionClient::OnHangingGetRead(talk_base::AsyncSocket* socket) {
+  LOG(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_ == 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, name);
+          }
+        }
+      } else {
+        callback_->OnMessageFromPeer(peer_id,
+                                     notification_data_.substr(pos));
+      }
+    }
+
+    notification_data_.clear();
+  }
+
+  if (hanging_get_.GetState() == talk_base::Socket::CS_CLOSED &&
+      state_ == CONNECTED) {
+    hanging_get_.Connect(server_address_);
+  }
+}
+
+bool PeerConnectionClient::ParseEntry(const std::string& entry,
+                                      std::string* name,
+                                      int* id,
+                                      bool* connected) {
+  ASSERT(name);
+  ASSERT(id);
+  ASSERT(connected);
+  ASSERT(entry.length());
+
+  *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) {
+  LOG(INFO) << response;
+
+  int status = GetResponseStatus(response.c_str());
+  if (status != 200) {
+    LOG(LS_ERROR) << "Received error from server";
+    Close();
+    callback_->OnDisconnected();
+    return false;
+  }
+
+  *eoh = response.find("\r\n\r\n");
+  ASSERT(*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 and
+  // not e.g. "X-Peer-Id".
+  GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
+
+  return true;
+}
+
+void PeerConnectionClient::OnClose(talk_base::AsyncSocket* socket, int err) {
+  LOG(INFO) << __FUNCTION__;
+
+  socket->Close();
+
+  if (err != WSAECONNREFUSED) {
+    if (socket == &hanging_get_) {
+      if (state_ == CONNECTED) {
+        LOG(INFO) << "Issuing  a new hanging get";
+        hanging_get_.Close();
+        hanging_get_.Connect(server_address_);
+      }
+    }
+  } else {
+    // Failed to connect to the server.
+    Close();
+    callback_->OnDisconnected();
+  }
+}
diff --git a/peerconnection/samples/client/peer_connection_client.h b/peerconnection/samples/client/peer_connection_client.h
new file mode 100644
index 0000000..d40179e
--- /dev/null
+++ b/peerconnection/samples/client/peer_connection_client.h
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (c) 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 PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/win32socketserver.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 id, const std::string& name) = 0;
+  virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0;
+ protected:
+  virtual ~PeerConnectionClientObserver() {}
+};
+
+class PeerConnectionClient : public sigslot::has_slots<> {
+ public:
+  enum State {
+    NOT_CONNECTED,
+    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);
+
+  bool Connect(const std::string& server, int port,
+               const std::string& client_name);
+
+  bool SendToPeer(int peer_id, const std::string& message);
+
+  bool SignOut();
+
+ protected:
+  void Close();
+  bool ConnectControlSocket();
+  void OnConnect(talk_base::AsyncSocket* socket);
+  void OnHangingGetConnect(talk_base::AsyncSocket* socket);
+
+  // 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(talk_base::AsyncSocket* socket, std::string* data,
+                      size_t* content_length);
+
+  void OnRead(talk_base::AsyncSocket* socket);
+
+  void OnHangingGetRead(talk_base::AsyncSocket* 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(talk_base::AsyncSocket* socket, int err);
+
+  PeerConnectionClientObserver* callback_;
+  talk_base::SocketAddress server_address_;
+  talk_base::Win32Socket control_socket_;
+  talk_base::Win32Socket hanging_get_;
+  std::string onconnect_data_;
+  std::string control_data_;
+  std::string notification_data_;
+  Peers peers_;
+  State state_;
+  int my_id_;
+};
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_