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, ¬ification_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_