git-svn-id: http://webrtc.googlecode.com/svn/trunk@5 4adac7df-926f-26a2-2b94-8c16560cd09d

This commit is contained in:
niklase@google.com 2011-05-30 11:39:02 +00:00
parent 77ae29bc81
commit 278733b2d9
9 changed files with 1580 additions and 0 deletions

View File

@ -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;
}

View File

@ -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_

View File

@ -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;
}

View File

@ -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_

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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_

View File

@ -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();
}
}

View File

@ -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_