diff --git a/peerconnection/samples/client/conductor.cc b/peerconnection/samples/client/conductor.cc index f2d8d1abb3..1d3ceffdec 100644 --- a/peerconnection/samples/client/conductor.cc +++ b/peerconnection/samples/client/conductor.cc @@ -15,34 +15,18 @@ #include "talk/p2p/client/basicportallocator.h" #include "talk/session/phone/videorendererfactory.h" -Conductor::Conductor(PeerConnectionClient* client, MainWnd* main_wnd) - : handshake_(NONE), - waiting_for_audio_(false), +Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd) + : waiting_for_audio_(false), waiting_for_video_(false), peer_id_(-1), - video_channel_(""), - audio_channel_(""), 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_.empty(); -} - -bool Conductor::has_audio() const { - return !audio_channel_.empty(); } bool Conductor::connection_active() const { @@ -50,40 +34,52 @@ bool Conductor::connection_active() const { } void Conductor::Close() { - if (peer_connection_.get()) { - peer_connection_->Close(); - video_channel_ = ""; - audio_channel_ = ""; - } else { - client_->SignOut(); - } + client_->SignOut(); + DeletePeerConnection(); } bool Conductor::InitializePeerConnection() { + ASSERT(peer_connection_factory_.get() == NULL); ASSERT(peer_connection_.get() == NULL); - ASSERT(port_allocator_.get() == NULL); ASSERT(worker_thread_.get() == NULL); - port_allocator_.reset(new cricket::BasicPortAllocator( - new talk_base::BasicNetworkManager(), - talk_base::SocketAddress("stun.l.google.com", 19302), - talk_base::SocketAddress(), - talk_base::SocketAddress(), talk_base::SocketAddress())); - worker_thread_.reset(new talk_base::Thread()); - if (!worker_thread_->SetName("workder thread", this) || + if (!worker_thread_->SetName("ConductorWT", this) || !worker_thread_->Start()) { - LOG(WARNING) << "Failed to start libjingle workder thread"; + LOG(LS_ERROR) << "Failed to start libjingle worker thread"; + worker_thread_.reset(); + return false; } - peer_connection_.reset( - webrtc::PeerConnection::Create(GetPeerConnectionString(), - port_allocator_.get(), - worker_thread_.get())); - peer_connection_->RegisterObserver(this); - if (!peer_connection_->Init()) { + cricket::PortAllocator* port_allocator = + new cricket::BasicPortAllocator( + new talk_base::BasicNetworkManager(), + talk_base::SocketAddress("stun.l.google.com", 19302), + talk_base::SocketAddress(), + talk_base::SocketAddress(), + talk_base::SocketAddress()); + + peer_connection_factory_.reset( + new webrtc::PeerConnectionFactory(GetPeerConnectionString(), + port_allocator, + worker_thread_.get())); + if (!peer_connection_factory_->Initialize()) { + main_wnd_->MessageBox("Error", + "Failed to initialize PeerConnectionFactory", true); + DeletePeerConnection(); + return false; + } + + // Since we only ever use a single PeerConnection instance, we share + // the worker thread between the factory and the PC instance. + peer_connection_.reset(peer_connection_factory_->CreatePeerConnection( + worker_thread_.get())); + if (!peer_connection_.get()) { + main_wnd_->MessageBox("Error", + "CreatePeerConnection failed", true); DeletePeerConnection(); } else { + peer_connection_->RegisterObserver(this); bool audio = peer_connection_->SetAudioDevice("", "", 0); LOG(INFO) << "SetAudioDevice " << (audio ? "succeeded." : "failed."); } @@ -92,13 +88,20 @@ bool Conductor::InitializePeerConnection() { void Conductor::DeletePeerConnection() { peer_connection_.reset(); - handshake_ = NONE; + worker_thread_.reset(); + video_channel_.clear(); + audio_channel_.clear(); + peer_connection_factory_.reset(); + waiting_for_audio_ = false; + waiting_for_video_ = false; + peer_id_ = -1; } void Conductor::StartCaptureDevice() { - ASSERT(peer_connection_.get()); + ASSERT(peer_connection_.get() != NULL); if (main_wnd_->IsWindow()) { - main_wnd_->SwitchToStreamingUI(); + if (main_wnd_->current_ui() != MainWindow::STREAMING) + main_wnd_->SwitchToStreamingUI(); if (peer_connection_->SetVideoCapture("")) { peer_connection_->SetLocalVideoRenderer(main_wnd_->local_renderer()); @@ -113,41 +116,24 @@ void Conductor::StartCaptureDevice() { // void Conductor::OnInitialized() { - PostMessage(handle(), PEER_CONNECTION_ADDSTREAMS, 0, 0); + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_ADDSTREAMS, NULL); } void Conductor::OnError() { - LOG(INFO) << __FUNCTION__; - ASSERT(false); + LOG(LS_ERROR) << __FUNCTION__; + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_ERROR, NULL); } void Conductor::OnSignalingMessage(const std::string& msg) { LOG(INFO) << __FUNCTION__; - bool shutting_down = (video_channel_.empty() && audio_channel_.empty()); - - 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(&msg)); - } - - if (shutting_down) { - handshake_ = QUIT_SENT; - PostMessage(handle(), PEER_CONNECTION_CLOSED, 0, 0); - } + std::string* msg_copy = new std::string(msg); + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg_copy); } // Called when a local stream is added and initialized void Conductor::OnLocalStreamInitialized(const std::string& stream_id, - bool video) { + bool video) { LOG(INFO) << __FUNCTION__ << " " << stream_id; bool send_notification = (waiting_for_video_ || waiting_for_audio_); if (video) { @@ -165,11 +151,10 @@ void Conductor::OnLocalStreamInitialized(const std::string& stream_id, } if (send_notification && !waiting_for_audio_ && !waiting_for_video_) - PostMessage(handle(), MEDIA_CHANNELS_INITIALIZED, 0, 0); + main_wnd_->QueueUIThreadCallback(MEDIA_CHANNELS_INITIALIZED, NULL); - if (!waiting_for_audio_ && !waiting_for_video_) { - PostMessage(handle(), PEER_CONNECTION_CONNECT, 0, 0); - } + if (!waiting_for_audio_ && !waiting_for_video_) + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CONNECT, NULL); } // Called when a remote stream is added @@ -177,35 +162,42 @@ void Conductor::OnAddStream(const std::string& stream_id, bool video) { LOG(INFO) << __FUNCTION__ << " " << stream_id; bool send_notification = (waiting_for_video_ || waiting_for_audio_); if (video) { - ASSERT(video_channel_.empty()); + // ASSERT(video_channel_.empty()); video_channel_ = stream_id; waiting_for_video_ = false; + LOG(INFO) << "Setting video renderer for stream: " << stream_id; bool ok = peer_connection_->SetVideoRenderer(stream_id, main_wnd_->remote_renderer()); - ASSERT(ok); + if (!ok) + LOG(LS_ERROR) << "SetVideoRenderer failed for : " << stream_id; } else { - ASSERT(audio_channel_.empty()); + // ASSERT(audio_channel_.empty()); audio_channel_ = stream_id; waiting_for_audio_ = false; } if (send_notification && !waiting_for_audio_ && !waiting_for_video_) - PostMessage(handle(), MEDIA_CHANNELS_INITIALIZED, 0, 0); + main_wnd_->QueueUIThreadCallback(MEDIA_CHANNELS_INITIALIZED, NULL); - if (!waiting_for_audio_ && !waiting_for_video_) { - PostMessage(handle(), PEER_CONNECTION_CONNECT, 0, 0); - } + if (!waiting_for_audio_ && !waiting_for_video_) + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CONNECT, NULL); } void Conductor::OnRemoveStream(const std::string& stream_id, bool video) { - LOG(INFO) << __FUNCTION__; + LOG(INFO) << __FUNCTION__ << (video ? " video: " : " audio: ") << stream_id; if (video) { - ASSERT(video_channel_.compare(stream_id) == 0); - video_channel_ = ""; + video_channel_.clear(); } else { - ASSERT(audio_channel_.compare(stream_id) == 0); - audio_channel_ = ""; + audio_channel_.clear(); + } + + if (video_channel_.empty() && audio_channel_.empty()) { + LOG(INFO) << "All streams have been closed."; + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL); + } else { + LOG(INFO) << "Remaining streams: '" << video_channel_ << "', '" + << audio_channel_ << "'"; } } @@ -220,137 +212,144 @@ void Conductor::OnSignedIn() { void Conductor::OnDisconnected() { LOG(INFO) << __FUNCTION__; - if (peer_connection_.get()) { - peer_connection_->Close(); - } else if (main_wnd_->IsWindow()) { + + DeletePeerConnection(); + + 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) + if (main_wnd_->current_ui() == MainWindow::LIST_PEERS) main_wnd_->SwitchToPeerList(client_->peers()); } -void Conductor::OnPeerDisconnected(int id, const std::string& name) { +void Conductor::OnPeerDisconnected(int id) { LOG(INFO) << __FUNCTION__; if (id == peer_id_) { LOG(INFO) << "Our peer disconnected"; - peer_id_ = -1; - if (peer_connection_.get()) - peer_connection_->Close(); + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL); + } else { + // Refresh the list if we're showing it. + if (main_wnd_->current_ui() == MainWindow::LIST_PEERS) + main_wnd_->SwitchToPeerList(client_->peers()); } - - // 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); + ASSERT(!message.empty()); - if (handshake_ == NONE) { - handshake_ = OFFER_RECEIVED; + if (!peer_connection_.get()) { + ASSERT(peer_id_ == -1); 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; - } + + // 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 { + StartCaptureDevice(); } - } else if (handshake_ == INITIATOR) { - LOG(INFO) << "Remote peer sent us an answer"; - handshake_ = ANSWER_RECEIVED; + } else if (peer_id != peer_id_) { + ASSERT(peer_id_ != -1); + LOG(WARNING) << "Received an offer from a peer while already in a " + "conversation with a different peer."; + return; } peer_connection_->SignalingMessage(message); +} - if (handshake_ == QUIT_SENT) { - DisconnectFromCurrentPeer(); - } +void Conductor::OnMessageSent(int err) { + // Process the next pending message if any. + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL); } // // MainWndCallback implementation. // -void Conductor::StartLogin(const std::string& server, int port) { - ASSERT(!client_->is_connected()); +bool Conductor::StartLogin(const std::string& server, int port) { + if (client_->is_connected()) + return false; + if (!client_->Connect(server, port, GetPeerName())) { - MessageBoxA(main_wnd_->handle(), - ("Failed to connect to " + server).c_str(), - "Error", MB_OK | MB_ICONERROR); + main_wnd_->MessageBox("Error", ("Failed to connect to " + server).c_str(), + true); + return false; } + + return true; } void Conductor::DisconnectFromServer() { - if (!client_->is_connected()) - return; - client_->SignOut(); + if (client_->is_connected()) + client_->SignOut(); } void Conductor::ConnectToPeer(int peer_id) { ASSERT(peer_id_ == -1); ASSERT(peer_id != -1); - ASSERT(handshake_ == NONE); - if (handshake_ != NONE) + if (peer_connection_.get()) { + main_wnd_->MessageBox("Error", + "We only support connecting to one peer at a time", true); return; + } if (InitializePeerConnection()) { peer_id_ = peer_id; + main_wnd_->SwitchToStreamingUI(); + OnInitialized(); // TODO(tommi): Figure out why we don't get this callback. } else { - ::MessageBoxA(main_wnd_->handle(), "Failed to initialize PeerConnection", - "Error", MB_OK | MB_ICONERROR); + main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true); } } void Conductor::AddStreams() { - 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_); -} + ASSERT(!waiting_for_video_); + ASSERT(!waiting_for_audio_); -void Conductor::PeerConnectionConnect() { - peer_connection_->Connect(); + waiting_for_video_ = true; + waiting_for_audio_ = true; + + if (!peer_connection_->AddStream(kVideoLabel, true)) + waiting_for_video_ = false; + + if (!peer_connection_->AddStream(kAudioLabel, false)) + waiting_for_audio_ = false; } void Conductor::DisconnectFromCurrentPeer() { + LOG(INFO) << __FUNCTION__; if (peer_connection_.get()) - peer_connection_->Close(); + DeletePeerConnection(); + + if (main_wnd_->IsWindow()) + main_wnd_->SwitchToPeerList(client_->peers()); } -// -// 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) { +void Conductor::UIThreadCallback(int msg_id, void* data) { + if (msg_id == MEDIA_CHANNELS_INITIALIZED) { + 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_id == 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_.empty()); ASSERT(audio_channel_.empty()); if (main_wnd_->IsWindow()) { @@ -362,20 +361,34 @@ bool Conductor::OnMessage(UINT msg, WPARAM wp, LPARAM lp, } else { DisconnectFromServer(); } - } else if (msg == SEND_MESSAGE_TO_PEER) { - bool ok = client_->SendToPeer(peer_id_, - *reinterpret_cast(lp)); - if (!ok) { - LOG(LS_ERROR) << "SendToPeer failed"; - DisconnectFromServer(); - } - } else if (msg == PEER_CONNECTION_ADDSTREAMS) { - AddStreams(); - } else if (msg == PEER_CONNECTION_CONNECT) { - PeerConnectionConnect(); - } else { - ret = false; - } + } else if (msg_id == SEND_MESSAGE_TO_PEER) { + LOG(INFO) << "SEND_MESSAGE_TO_PEER"; + std::string* msg = reinterpret_cast(data); + if (client_->IsSendingMessage()) { + ASSERT(msg != NULL); + pending_messages_.push_back(msg); + } else { + if (!msg && !pending_messages_.empty()) { + msg = pending_messages_.front(); + pending_messages_.pop_front(); + } + if (msg) { + bool ok = client_->SendToPeer(peer_id_, *msg); + if (!ok && peer_id_ != -1) { + LOG(LS_ERROR) << "SendToPeer failed"; + DisconnectFromServer(); + } + delete msg; + } - return ret; + if (!peer_connection_.get()) + peer_id_ = -1; + } + } else if (msg_id == PEER_CONNECTION_ADDSTREAMS) { + AddStreams(); + } else if (msg_id == PEER_CONNECTION_CONNECT) { + peer_connection_->Connect(); + } else if (msg_id == PEER_CONNECTION_ERROR) { + main_wnd_->MessageBox("Error", "an unknown error occurred", true); + } } diff --git a/peerconnection/samples/client/conductor.h b/peerconnection/samples/client/conductor.h index 01a36b49f0..54547e0b2b 100644 --- a/peerconnection/samples/client/conductor.h +++ b/peerconnection/samples/client/conductor.h @@ -12,11 +12,13 @@ #define PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_ #pragma once +#include #include #include "peerconnection/samples/client/main_wnd.h" #include "peerconnection/samples/client/peer_connection_client.h" #include "talk/app/webrtc/peerconnection.h" +#include "talk/app/webrtc/peerconnectionfactory.h" #include "talk/base/scoped_ptr.h" namespace cricket { @@ -26,40 +28,29 @@ class VideoRenderer; class Conductor : public webrtc::PeerConnectionObserver, public PeerConnectionClientObserver, - public MainWndCallback, - public talk_base::Win32Window { + public MainWndCallback { public: - enum WindowMessages { - MEDIA_CHANNELS_INITIALIZED = WM_APP + 1, + enum CallbackID { + MEDIA_CHANNELS_INITIALIZED = 1, PEER_CONNECTION_CLOSED, SEND_MESSAGE_TO_PEER, PEER_CONNECTION_ADDSTREAMS, PEER_CONNECTION_CONNECT, + PEER_CONNECTION_ERROR, }; - enum HandshakeState { - NONE, - INITIATOR, - ANSWER_RECEIVED, - OFFER_RECEIVED, - QUIT_SENT, - }; - - Conductor(PeerConnectionClient* client, MainWnd* main_wnd); + Conductor(PeerConnectionClient* client, MainWindow* main_wnd); ~Conductor(); - bool has_video() const; - bool has_audio() const; bool connection_active() const; - void Close(); + virtual void Close(); protected: bool InitializePeerConnection(); void DeletePeerConnection(); void StartCaptureDevice(); void AddStreams(); - void PeerConnectionConnect(); // // PeerConnectionObserver implementation. @@ -71,7 +62,7 @@ class Conductor // Called when a local stream is added and initialized virtual void OnLocalStreamInitialized(const std::string& stream_id, - bool video); + bool video); // Called when a remote stream is added virtual void OnAddStream(const std::string& stream_id, bool video); @@ -89,15 +80,17 @@ class Conductor virtual void OnPeerConnected(int id, const std::string& name); - virtual void OnPeerDisconnected(int id, const std::string& name); + virtual void OnPeerDisconnected(int id); virtual void OnMessageFromPeer(int peer_id, const std::string& message); + virtual void OnMessageSent(int err); + // // MainWndCallback implementation. // - virtual void StartLogin(const std::string& server, int port); + virtual bool StartLogin(const std::string& server, int port); virtual void DisconnectFromServer(); @@ -105,25 +98,20 @@ class Conductor virtual void DisconnectFromCurrentPeer(); - // - // Win32Window implementation. - // - - virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, - LRESULT& result); // NOLINT + virtual void UIThreadCallback(int msg_id, void* data); protected: - HandshakeState handshake_; bool waiting_for_audio_; bool waiting_for_video_; int peer_id_; talk_base::scoped_ptr peer_connection_; - talk_base::scoped_ptr port_allocator_; + talk_base::scoped_ptr peer_connection_factory_; talk_base::scoped_ptr worker_thread_; PeerConnectionClient* client_; - MainWnd* main_wnd_; + MainWindow* main_wnd_; std::string video_channel_; std::string audio_channel_; + std::deque pending_messages_; }; #endif // PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_ diff --git a/peerconnection/samples/client/defaults.cc b/peerconnection/samples/client/defaults.cc index c8e8527578..af4dbb9490 100644 --- a/peerconnection/samples/client/defaults.cc +++ b/peerconnection/samples/client/defaults.cc @@ -10,6 +10,17 @@ #include "peerconnection/samples/client/defaults.h" +#include +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +#include "talk/base/common.h" + const char kAudioLabel[] = "audio_label"; const char kVideoLabel[] = "video_label"; const uint16 kDefaultServerPort = 8888; @@ -36,12 +47,10 @@ std::string GetDefaultServerName() { } 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); + char computer_name[256]; + if (gethostname(computer_name, ARRAY_SIZE(computer_name)) != 0) + strcpy(computer_name, "host"); + std::string ret(GetEnvVarOrDefault("USERNAME", "user")); ret += '@'; ret += computer_name; return ret; diff --git a/peerconnection/samples/client/defaults.h b/peerconnection/samples/client/defaults.h index 466a965de6..195b4d867f 100644 --- a/peerconnection/samples/client/defaults.h +++ b/peerconnection/samples/client/defaults.h @@ -12,7 +12,6 @@ #define PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_ #pragma once -#include #include #include "talk/base/basictypes.h" diff --git a/peerconnection/samples/client/linux/main.cc b/peerconnection/samples/client/linux/main.cc index 557274a25b..c8d5a2a6ea 100644 --- a/peerconnection/samples/client/linux/main.cc +++ b/peerconnection/samples/client/linux/main.cc @@ -10,17 +10,71 @@ #include +#include "peerconnection/samples/client/conductor.h" #include "peerconnection/samples/client/linux/main_wnd.h" +#include "peerconnection/samples/client/peer_connection_client.h" + +#include "talk/base/thread.h" + +class CustomSocketServer : public talk_base::PhysicalSocketServer { + public: + CustomSocketServer(talk_base::Thread* thread, GtkMainWnd* wnd) + : thread_(thread), wnd_(wnd), conductor_(NULL), client_(NULL) {} + virtual ~CustomSocketServer() {} + + void set_client(PeerConnectionClient* client) { client_ = client; } + void set_conductor(Conductor* conductor) { conductor_ = conductor; } + + // Override so that we can also pump the GTK message loop. + virtual bool Wait(int cms, bool process_io) { + // Pump GTK events. + // TODO(tommi): We really should move either the socket server or UI to a + // different thread. Alternatively we could look at merging the two loops + // by implementing a dispatcher for the socket server and/or use + // g_main_context_set_poll_func. + while (gtk_events_pending()) + gtk_main_iteration(); + + if (!wnd_->IsWindow() && !conductor_->connection_active() && + client_ != NULL && !client_->is_connected()) { + thread_->Quit(); + } + return talk_base::PhysicalSocketServer::Wait(0/*cms == -1 ? 1 : cms*/, + process_io); + } + + protected: + talk_base::Thread* thread_; + GtkMainWnd* wnd_; + Conductor* conductor_; + PeerConnectionClient* client_; +}; int main(int argc, char* argv[]) { gtk_init(&argc, &argv); g_type_init(); + g_thread_init(NULL); GtkMainWnd wnd; wnd.Create(); - gtk_main(); + + talk_base::AutoThread auto_thread; + talk_base::Thread* thread = talk_base::Thread::Current(); + CustomSocketServer socket_server(thread, &wnd); + thread->set_socketserver(&socket_server); + + // Must be constructed after we set the socketserver. + PeerConnectionClient client; + Conductor conductor(&client, &wnd); + socket_server.set_client(&client); + socket_server.set_conductor(&conductor); + + thread->Run(); + + // gtk_main(); wnd.Destroy(); + thread->set_socketserver(NULL); // TODO(tommi): Run the Gtk main loop to tear down the connection. //while (gtk_events_pending()) { // gtk_main_iteration(); diff --git a/peerconnection/samples/client/linux/main_wnd.cc b/peerconnection/samples/client/linux/main_wnd.cc index 400a2bc66f..1187ed2219 100644 --- a/peerconnection/samples/client/linux/main_wnd.cc +++ b/peerconnection/samples/client/linux/main_wnd.cc @@ -15,8 +15,12 @@ #include #include +#include "peerconnection/samples/client/defaults.h" #include "talk/base/common.h" #include "talk/base/logging.h" +#include "talk/base/stringutils.h" + +using talk_base::sprintfn; namespace { @@ -67,6 +71,27 @@ void AddToList(GtkWidget* list, const gchar* str, int value) { gtk_list_store_set(store, &iter, 0, str, 1, value, -1); } +struct UIThreadCallbackData { + explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d) + : callback(cb), msg_id(id), data(d) {} + MainWndCallback* callback; + int msg_id; + void* data; +}; + +gboolean HandleUIThreadCallback(gpointer data) { + UIThreadCallbackData* cb_data = reinterpret_cast(data); + cb_data->callback->UIThreadCallback(cb_data->msg_id, cb_data->data); + delete cb_data; + return false; +} + +gboolean Redraw(gpointer data) { + GtkMainWnd* wnd = reinterpret_cast(data); + wnd->OnRedraw(); + return false; +} + } // end anonymous // @@ -74,17 +99,64 @@ void AddToList(GtkWidget* list, const gchar* str, int value) { // GtkMainWnd::GtkMainWnd() - : window_(NULL), draw_area_(NULL), vbox_(NULL), peer_list_(NULL) { + : window_(NULL), draw_area_(NULL), vbox_(NULL), server_edit_(NULL), + port_edit_(NULL), peer_list_(NULL), callback_(NULL), + server_("localhost") { + char buffer[10]; + sprintfn(buffer, sizeof(buffer), "%i", kDefaultServerPort); + port_ = buffer; } GtkMainWnd::~GtkMainWnd() { ASSERT(!IsWindow()); } +void GtkMainWnd::RegisterObserver(MainWndCallback* callback) { + callback_ = callback; +} + bool GtkMainWnd::IsWindow() { return window_ != NULL && GTK_IS_WINDOW(window_); } +void GtkMainWnd::MessageBox(const char* caption, const char* text, + bool is_error) { + GtkWidget* dialog = gtk_message_dialog_new(GTK_WINDOW(window_), + GTK_DIALOG_DESTROY_WITH_PARENT, + is_error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, "%s", text); + gtk_window_set_title(GTK_WINDOW(dialog), caption); + gtk_dialog_run(GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +MainWindow::UI GtkMainWnd::current_ui() { + if (vbox_) + return CONNECT_TO_SERVER; + + if (peer_list_) + return LIST_PEERS; + + return STREAMING; +} + +cricket::VideoRenderer* GtkMainWnd::local_renderer() { + if (!local_renderer_.get()) + local_renderer_.reset(new VideoRenderer(this)); + return local_renderer_.get(); +} + +cricket::VideoRenderer* GtkMainWnd::remote_renderer() { + if (!remote_renderer_.get()) + remote_renderer_.reset(new VideoRenderer(this)); + return remote_renderer_.get(); +} + +void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) { + g_idle_add(HandleUIThreadCallback, + new UIThreadCallbackData(callback_, msg_id, data)); +} + bool GtkMainWnd::Create() { ASSERT(window_ == NULL); @@ -115,6 +187,8 @@ bool GtkMainWnd::Destroy() { } void GtkMainWnd::SwitchToConnectUI() { + LOG(INFO) << __FUNCTION__; + ASSERT(IsWindow()); ASSERT(vbox_ == NULL); @@ -130,11 +204,20 @@ void GtkMainWnd::SwitchToConnectUI() { gtk_container_add(GTK_CONTAINER(vbox_), valign); gtk_container_add(GTK_CONTAINER(window_), vbox_); - GtkWidget* hbox = gtk_hbox_new(FALSE, 3); + GtkWidget* hbox = gtk_hbox_new(FALSE, 5); - GtkWidget* edit = gtk_entry_new(); - gtk_widget_set_size_request(edit, 400, 30); - gtk_container_add(GTK_CONTAINER(hbox), edit); + GtkWidget* label = gtk_label_new("Server"); + gtk_container_add(GTK_CONTAINER(hbox), label); + + server_edit_ = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str()); + gtk_widget_set_size_request(server_edit_, 400, 30); + gtk_container_add(GTK_CONTAINER(hbox), server_edit_); + + port_edit_ = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str()); + gtk_widget_set_size_request(port_edit_, 70, 30); + gtk_container_add(GTK_CONTAINER(hbox), port_edit_); GtkWidget* button = gtk_button_new_with_label("Connect"); gtk_widget_set_size_request(button, 70, 30); @@ -148,30 +231,43 @@ void GtkMainWnd::SwitchToConnectUI() { gtk_widget_show_all(window_); } -void GtkMainWnd::SwitchToPeerList(/*const Peers& peers*/) { - gtk_container_set_border_width(GTK_CONTAINER(window_), 0); - if (vbox_) { - gtk_widget_destroy(vbox_); - vbox_ = NULL; - } else if (draw_area_) { - gtk_widget_destroy(draw_area_); - draw_area_ = NULL; +void GtkMainWnd::SwitchToPeerList(const Peers& peers) { + LOG(INFO) << __FUNCTION__; + + if (!peer_list_) { + gtk_container_set_border_width(GTK_CONTAINER(window_), 0); + if (vbox_) { + gtk_widget_destroy(vbox_); + vbox_ = NULL; + server_edit_ = NULL; + port_edit_ = NULL; + } else if (draw_area_) { + gtk_widget_destroy(draw_area_); + draw_area_ = NULL; + draw_buffer_.reset(); + } + + peer_list_ = gtk_tree_view_new(); + g_signal_connect(peer_list_, "row-activated", + G_CALLBACK(OnRowActivatedCallback), this); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE); + InitializeList(peer_list_); + gtk_container_add(GTK_CONTAINER(window_), peer_list_); + gtk_widget_show_all(window_); + } else { + GtkListStore* store = + GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_))); + gtk_list_store_clear(store); } - peer_list_ = gtk_tree_view_new(); - g_signal_connect(peer_list_, "row-activated", - G_CALLBACK(OnRowActivatedCallback), this); - gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE); - InitializeList(peer_list_); - AddToList(peer_list_, "item 1", 1); - AddToList(peer_list_, "item 2", 2); - AddToList(peer_list_, "item 3", 3); - gtk_container_add(GTK_CONTAINER(window_), peer_list_); - - gtk_widget_show_all(window_); + AddToList(peer_list_, "List of currently connected peers:", -1); + for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i) + AddToList(peer_list_, i->second.c_str(), i->first); } void GtkMainWnd::SwitchToStreamingUI() { + LOG(INFO) << __FUNCTION__; + ASSERT(draw_area_ == NULL); gtk_container_set_border_width(GTK_CONTAINER(window_), 0); @@ -187,36 +283,40 @@ void GtkMainWnd::SwitchToStreamingUI() { } void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) { - gtk_main_quit(); + callback_->Close(); + window_ = NULL; + draw_area_ = NULL; + vbox_ = NULL; + server_edit_ = NULL; + port_edit_ = NULL; + peer_list_ = NULL; } void GtkMainWnd::OnClicked(GtkWidget* widget) { - g_print("Clicked!\n"); - SwitchToPeerList(); + server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_)); + port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_)); + int port = port_.length() ? atoi(port_.c_str()) : 0; + callback_->StartLogin(server_, port); } void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) { - g_print("KeyPress!\n"); if (key->type == GDK_KEY_PRESS) { - g_print("0x%08X\n", key->keyval); switch (key->keyval) { - case GDK_Escape : + case GDK_Escape: if (draw_area_) { - SwitchToPeerList(); + callback_->DisconnectFromCurrentPeer(); } else if (peer_list_) { - SwitchToConnectUI(); - } else { - gtk_main_quit(); + callback_->DisconnectFromServer(); } break; case GDK_KP_Enter: case GDK_Return: - // TODO(tommi): Use g_idle_add() if we need to switch asynchronously. if (vbox_) { - SwitchToPeerList(); + OnClicked(NULL); } else if (peer_list_) { - SwitchToStreamingUI(); + // OnRowActivated will be called automatically when the user + // presses enter. } break; @@ -235,9 +335,119 @@ void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path, gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); if (gtk_tree_selection_get_selected(selection, &model, &iter)) { char* text; - int id = 0; + int id = -1; gtk_tree_model_get(model, &iter, 0, &text, 1, &id, -1); - g_print("%s - %i\n", text, id); + if (id != -1) + callback_->ConnectToPeer(id); g_free(text); } } + +void GtkMainWnd::OnRedraw() { + gdk_threads_enter(); + + if (remote_renderer_.get() && remote_renderer_->image() != NULL && + draw_area_ != NULL) { + int width = remote_renderer_->width(); + int height = remote_renderer_->height(); + + if (!draw_buffer_.get()) { + draw_buffer_size_ = (width * height * 4) * 4; + draw_buffer_.reset(new uint8[draw_buffer_size_]); + gtk_widget_set_size_request(draw_area_, width * 2, height * 2); + } + + const uint32* image = reinterpret_cast( + remote_renderer_->image()); + uint32* scaled = reinterpret_cast(draw_buffer_.get()); + for (int r = 0; r < height; ++r) { + for (int c = 0; c < width; ++c) { + int x = c * 2; + scaled[x] = scaled[x + 1] = image[c]; + } + + uint32* prev_line = scaled; + scaled += width * 2; + memcpy(scaled, prev_line, (width * 2) * 4); + + image += width; + scaled += width * 2; + } + + image = reinterpret_cast(local_renderer_->image()); + scaled = reinterpret_cast(draw_buffer_.get()); + // Position the local preview on the right side. + scaled += (width * 2) - (local_renderer_->width() / 2); + // right margin... + scaled -= 10; + // ... towards the bottom. + scaled += (height * width * 4) - + ((local_renderer_->height() / 2) * + (local_renderer_->width() / 2) * 4); + // bottom margin... + scaled -= (width * 2) * 5; + for (int r = 0; r < local_renderer_->height(); r += 2) { + for (int c = 0; c < local_renderer_->width(); c += 2) { + scaled[c / 2] = image[c + r * local_renderer_->width()]; + } + scaled += width * 2; + } + + gdk_draw_rgb_32_image(draw_area_->window, + draw_area_->style->fg_gc[GTK_STATE_NORMAL], + 0, + 0, + width * 2, + height * 2, + GDK_RGB_DITHER_MAX, + draw_buffer_.get(), + (width * 2) * 4); + } + + gdk_threads_leave(); +} + +GtkMainWnd::VideoRenderer::VideoRenderer(GtkMainWnd* main_wnd) + : width_(0), height_(0), main_wnd_(main_wnd) { +} + +GtkMainWnd::VideoRenderer::~VideoRenderer() { +} + +bool GtkMainWnd::VideoRenderer::SetSize(int width, int height, int reserved) { + gdk_threads_enter(); + width_ = width; + height_ = height; + image_.reset(new uint8[width * height * 4]); + gdk_threads_leave(); + return true; +} + +bool GtkMainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) { + gdk_threads_enter(); + + int size = width_ * height_ * 4; + frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, + image_.get(), + size, + width_ * 4); + // Convert the B,G,R,A frame to R,G,B,A, which is accepted by GTK. + // The 'A' is just padding for GTK, so we can use it as temp. + uint8* pix = image_.get(); + uint8* end = image_.get() + size; + while (pix < end) { + pix[3] = pix[0]; // Save B to A. + pix[0] = pix[2]; // Set Red. + pix[2] = pix[3]; // Set Blue. + pix[3] = 0xFF; // Fixed Alpha. + pix += 4; + } + + gdk_threads_leave(); + + g_idle_add(Redraw, main_wnd_); + + return true; +} + + diff --git a/peerconnection/samples/client/linux/main_wnd.h b/peerconnection/samples/client/linux/main_wnd.h index eae452e907..7cd1d4e65c 100644 --- a/peerconnection/samples/client/linux/main_wnd.h +++ b/peerconnection/samples/client/linux/main_wnd.h @@ -12,6 +12,9 @@ #ifndef PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_ #define PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_ +#include "peerconnection/samples/client/main_wnd.h" +#include "peerconnection/samples/client/peer_connection_client.h" + // Forward declarations. typedef struct _GtkWidget GtkWidget; typedef union _GdkEvent GdkEvent; @@ -23,11 +26,23 @@ typedef struct _GtkTreeViewColumn GtkTreeViewColumn; // Implements the main UI of the peer connection client. // This is functionally equivalent to the MainWnd class in the Windows // implementation. -class GtkMainWnd { +class GtkMainWnd : public MainWindow { public: GtkMainWnd(); ~GtkMainWnd(); + virtual void RegisterObserver(MainWndCallback* callback); + virtual bool IsWindow(); + virtual void SwitchToConnectUI(); + virtual void SwitchToPeerList(const Peers& peers); + virtual void SwitchToStreamingUI(); + virtual void MessageBox(const char* caption, const char* text, + bool is_error); + virtual MainWindow::UI current_ui(); + virtual cricket::VideoRenderer* local_renderer(); + virtual cricket::VideoRenderer* remote_renderer(); + virtual void QueueUIThreadCallback(int msg_id, void* data); + // Creates and shows the main window with the |Connect UI| enabled. bool Create(); @@ -35,9 +50,6 @@ class GtkMainWnd { // main message loop. bool Destroy(); - // Returns true iff the main window exists. - bool IsWindow(); - // Callback for when the main window is destroyed. void OnDestroyed(GtkWidget* widget, GdkEvent* event); @@ -51,22 +63,52 @@ class GtkMainWnd { // connection. void OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path, GtkTreeViewColumn* column); + + void OnRedraw(); protected: - // Switches to the Connect UI. The Connect UI must not already be active. - void SwitchToConnectUI(); + class VideoRenderer : public cricket::VideoRenderer { + public: + VideoRenderer(GtkMainWnd* main_wnd); + virtual ~VideoRenderer(); - // Switches to a list view that shows a list of currently connected peers. - // TODO(tommi): Support providing a peer list. - void SwitchToPeerList(/*const Peers& peers*/); + virtual bool SetSize(int width, int height, int reserved); - // Switches to the video streaming UI. - void SwitchToStreamingUI(); + virtual bool RenderFrame(const cricket::VideoFrame* frame); + const uint8* image() const { + return image_.get(); + } + + int width() const { + return width_; + } + + int height() const { + return height_; + } + + protected: + talk_base::scoped_array image_; + int width_; + int height_; + GtkMainWnd* main_wnd_; + }; + + protected: GtkWidget* window_; // Our main window. GtkWidget* draw_area_; // The drawing surface for rendering video streams. GtkWidget* vbox_; // Container for the Connect UI. + GtkWidget* server_edit_; + GtkWidget* port_edit_; GtkWidget* peer_list_; // The list of peers. + MainWndCallback* callback_; + std::string server_; + std::string port_; + talk_base::scoped_ptr local_renderer_; + talk_base::scoped_ptr remote_renderer_; + talk_base::scoped_ptr draw_buffer_; + int draw_buffer_size_; }; #endif // PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_ diff --git a/peerconnection/samples/client/main.cc b/peerconnection/samples/client/main.cc index 21054b193c..1ee279fe17 100644 --- a/peerconnection/samples/client/main.cc +++ b/peerconnection/samples/client/main.cc @@ -45,11 +45,12 @@ int PASCAL wWinMain(HINSTANCE instance, HINSTANCE prev_instance, } 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); + if (!wnd.PreTranslateMessage(&msg)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } } } diff --git a/peerconnection/samples/client/main_wnd.cc b/peerconnection/samples/client/main_wnd.cc index 416b6dbfd5..900548a97c 100644 --- a/peerconnection/samples/client/main_wnd.cc +++ b/peerconnection/samples/client/main_wnd.cc @@ -74,6 +74,7 @@ bool MainWnd::Create() { if (!RegisterWindowClass()) return false; + ui_thread_id_ = ::GetCurrentThreadId(); wnd_ = ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC", WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, @@ -101,7 +102,7 @@ void MainWnd::RegisterObserver(MainWndCallback* callback) { callback_ = callback; } -bool MainWnd::IsWindow() const { +bool MainWnd::IsWindow() { return wnd_ && ::IsWindow(wnd_) != FALSE; } @@ -123,6 +124,10 @@ bool MainWnd::PreTranslateMessage(MSG* msg) { } } } + } else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) { + callback_->UIThreadCallback(static_cast(msg->wParam), + reinterpret_cast(msg->lParam)); + ret = true; } return ret; } @@ -151,11 +156,27 @@ void MainWnd::SwitchToPeerList(const Peers& peers) { } void MainWnd::SwitchToStreamingUI() { + remote_video_.reset(new VideoRenderer(handle(), 1, 1)); + local_video_.reset(new VideoRenderer(handle(), 1, 1)); + LayoutConnectUI(false); LayoutPeerListUI(false); ui_ = STREAMING; } +void MainWnd::MessageBox(const char* caption, const char* text, bool is_error) { + DWORD flags = MB_OK; + if (is_error) + flags |= MB_ICONERROR; + + ::MessageBoxA(handle(), text, caption, flags); +} + +void MainWnd::QueueUIThreadCallback(int msg_id, void* data) { + ::PostThreadMessage(ui_thread_id_, UI_THREAD_CALLBACK, + static_cast(msg_id), reinterpret_cast(data)); +} + void MainWnd::OnPaint() { PAINTSTRUCT ps; ::BeginPaint(handle(), &ps); @@ -164,6 +185,9 @@ void MainWnd::OnPaint() { ::GetClientRect(handle(), &rc); if (ui_ == STREAMING && remote_video_.get() && local_video_.get()) { + AutoLock local_lock(local_video_.get()); + AutoLock remote_lock(remote_video_.get()); + const BITMAPINFO& bmi = remote_video_->bmi(); long height = abs(bmi.bmiHeader.biHeight); long width = bmi.bmiHeader.biWidth; @@ -266,17 +290,14 @@ void MainWnd::OnDefaultAction() { bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { switch (msg) { - case WM_CREATE: - remote_video_.reset(new VideoRenderer(handle(), 1, 1)); - local_video_.reset(new VideoRenderer(handle(), 1, 1)); - break; - case WM_ERASEBKGND: *result = TRUE; return true; + case WM_PAINT: OnPaint(); return true; + case WM_SETFOCUS: if (ui_ == CONNECT_TO_SERVER) { SetFocus(edit1_); @@ -284,6 +305,7 @@ bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { SetFocus(listbox_); } return true; + case WM_SIZE: if (ui_ == CONNECT_TO_SERVER) { LayoutConnectUI(true); @@ -291,9 +313,11 @@ bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { LayoutPeerListUI(true); } break; + case WM_CTLCOLORSTATIC: *result = reinterpret_cast(GetSysColorBrush(COLOR_WINDOW)); return true; + case WM_COMMAND: if (button_ == reinterpret_cast(lp)) { if (BN_CLICKED == HIWORD(wp)) @@ -304,12 +328,11 @@ bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { } } return true; - case VIDEO_RENDERER_MESSAGE: { - VideoRenderer* renderer = reinterpret_cast(lp); - const MSG* msg_ptr = reinterpret_cast(wp); - renderer->OnMessage(*msg_ptr); - return true; - } + + case WM_CLOSE: + if (callback_) + callback_->Close(); + break; } return false; } @@ -364,7 +387,7 @@ bool MainWnd::RegisterWindowClass() { wcex.lpfnWndProc = &WndProc; wcex.lpszClassName = kClassName; wnd_class_ = ::RegisterClassEx(&wcex); - ASSERT(wnd_class_); + ASSERT(wnd_class_ != 0); return wnd_class_ != 0; } @@ -380,7 +403,7 @@ void MainWnd::CreateChildWindow(HWND* wnd, MainWnd::ChildWindowID id, 100, 100, 100, 100, wnd_, reinterpret_cast(id), GetModuleHandle(NULL), NULL); - ASSERT(::IsWindow(*wnd)); + ASSERT(::IsWindow(*wnd) != FALSE); ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast(GetDefaultFont()), TRUE); } @@ -454,6 +477,7 @@ void MainWnd::LayoutPeerListUI(bool show) { ::ShowWindow(listbox_, SW_SHOWNA); } else { ::ShowWindow(listbox_, SW_HIDE); + InvalidateRect(wnd_, NULL, TRUE); } } @@ -487,6 +511,7 @@ void MainWnd::HandleTabbing() { MainWnd::VideoRenderer::VideoRenderer(HWND wnd, int width, int height) : wnd_(wnd) { + ::InitializeCriticalSection(&buffer_lock_); ZeroMemory(&bmi_, sizeof(bmi_)); bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi_.bmiHeader.biPlanes = 1; @@ -499,22 +524,18 @@ MainWnd::VideoRenderer::VideoRenderer(HWND wnd, int width, int height) } MainWnd::VideoRenderer::~VideoRenderer() { + ::DeleteCriticalSection(&buffer_lock_); } bool MainWnd::VideoRenderer::SetSize(int width, int height, int reserved) { - if (width != bmi_.bmiHeader.biWidth || - height != -bmi_.bmiHeader.biHeight) { - // Update the bitmap info and image buffer. - // To avoid touching buffers from different threads, we always - // marshal messages through the main window's thread. - MSG msg = {0}; - msg.message = WM_SIZE; - msg.lParam = width; - msg.wParam = height; - ::SendMessage(wnd_, VIDEO_RENDERER_MESSAGE, - reinterpret_cast(&msg), - reinterpret_cast(this)); - } + AutoLock lock(this); + + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = width * height * + (bmi_.bmiHeader.biBitCount >> 3); + image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]); + return true; } @@ -522,36 +543,17 @@ bool MainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) { if (!frame) return false; - MSG msg = {0}; - msg.message = WM_PAINT; - msg.lParam = reinterpret_cast(frame); - ::SendMessage(wnd_, VIDEO_RENDERER_MESSAGE, - reinterpret_cast(&msg), - reinterpret_cast(this)); + { + AutoLock lock(this); + + ASSERT(image_.get() != NULL); + frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(), + bmi_.bmiHeader.biSizeImage, + bmi_.bmiHeader.biWidth * + (bmi_.bmiHeader.biBitCount >> 3)); + } + + InvalidateRect(wnd_, NULL, TRUE); + return true; } - -void MainWnd::VideoRenderer::OnMessage(const MSG& msg) { - switch (msg.message) { - case WM_SIZE: - bmi_.bmiHeader.biWidth = static_cast(msg.lParam); - bmi_.bmiHeader.biHeight = -static_cast(msg.wParam); - bmi_.bmiHeader.biSizeImage = bmi_.bmiHeader.biWidth * - static_cast(msg.wParam) * - (bmi_.bmiHeader.biBitCount >> 3); - image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]); - break; - - case WM_PAINT: { - ASSERT(image_.get() != NULL); - const cricket::VideoFrame* frame = - reinterpret_cast(msg.lParam); - frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(), - bmi_.bmiHeader.biSizeImage, - bmi_.bmiHeader.biWidth * - (bmi_.bmiHeader.biBitCount >> 3)); - InvalidateRect(wnd_, 0, 0); - break; - } - } -} diff --git a/peerconnection/samples/client/main_wnd.h b/peerconnection/samples/client/main_wnd.h index d72792cf53..31bb5aaccb 100644 --- a/peerconnection/samples/client/main_wnd.h +++ b/peerconnection/samples/client/main_wnd.h @@ -22,26 +22,51 @@ class MainWndCallback { public: - virtual void StartLogin(const std::string& server, int port) = 0; + virtual bool StartLogin(const std::string& server, int port) = 0; virtual void DisconnectFromServer() = 0; virtual void ConnectToPeer(int peer_id) = 0; virtual void DisconnectFromCurrentPeer() = 0; + virtual void UIThreadCallback(int msg_id, void* data) = 0; + virtual void Close() = 0; protected: virtual ~MainWndCallback() {} }; -class MainWnd { +// Pure virtual interface for the main window. +class MainWindow { public: - static const wchar_t kClassName[]; - enum UI { CONNECT_TO_SERVER, LIST_PEERS, STREAMING, }; + virtual void RegisterObserver(MainWndCallback* callback) = 0; + + virtual bool IsWindow() = 0; + virtual void MessageBox(const char* caption, const char* text, + bool is_error) = 0; + + virtual UI current_ui() = 0; + + virtual void SwitchToConnectUI() = 0; + virtual void SwitchToPeerList(const Peers& peers) = 0; + virtual void SwitchToStreamingUI() = 0; + + virtual cricket::VideoRenderer* local_renderer() = 0; + virtual cricket::VideoRenderer* remote_renderer() = 0; + + virtual void QueueUIThreadCallback(int msg_id, void* data) = 0; +}; + +#ifdef WIN32 + +class MainWnd : public MainWindow { + public: + static const wchar_t kClassName[]; + enum WindowMessages { - VIDEO_RENDERER_MESSAGE = WM_APP + 1, + UI_THREAD_CALLBACK = WM_APP + 1, }; MainWnd(); @@ -49,39 +74,47 @@ class 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(); + virtual void RegisterObserver(MainWndCallback* callback); + virtual bool IsWindow(); + virtual void SwitchToConnectUI(); + virtual void SwitchToPeerList(const Peers& peers); + virtual void SwitchToStreamingUI(); + virtual void MessageBox(const char* caption, const char* text, + bool is_error); + virtual UI current_ui() { return ui_; } - HWND handle() const { return wnd_; } - UI current_ui() const { return ui_; } - - cricket::VideoRenderer* local_renderer() const { + virtual cricket::VideoRenderer* local_renderer() { return local_video_.get(); } - cricket::VideoRenderer* remote_renderer() const { + virtual cricket::VideoRenderer* remote_renderer() { return remote_video_.get(); } + virtual void QueueUIThreadCallback(int msg_id, void* data); + + HWND handle() const { return wnd_; } + class VideoRenderer : public cricket::VideoRenderer { public: VideoRenderer(HWND wnd, int width, int height); virtual ~VideoRenderer(); + void Lock() { + ::EnterCriticalSection(&buffer_lock_); + } + + void Unlock() { + ::LeaveCriticalSection(&buffer_lock_); + } + virtual bool SetSize(int width, int height, int reserved); // Called when a new frame is available for display. virtual bool RenderFrame(const cricket::VideoFrame* frame); - void OnMessage(const MSG& msg); - const BITMAPINFO& bmi() const { return bmi_; } const uint8* image() const { return image_.get(); } @@ -94,6 +127,18 @@ class MainWnd { HWND wnd_; BITMAPINFO bmi_; talk_base::scoped_array image_; + CRITICAL_SECTION buffer_lock_; + }; + + // A little helper class to make sure we always to proper locking and + // unlocking when working with VideoRenderer buffers. + template + class AutoLock { + public: + AutoLock(T* obj) : obj_(obj) { obj_->Lock(); } + ~AutoLock() { obj_->Unlock(); } + protected: + T* obj_; }; protected: @@ -129,6 +174,7 @@ class MainWnd { talk_base::scoped_ptr local_video_; UI ui_; HWND wnd_; + DWORD ui_thread_id_; HWND edit1_; HWND edit2_; HWND label1_; @@ -140,5 +186,6 @@ class MainWnd { MainWndCallback* callback_; static ATOM wnd_class_; }; +#endif // WIN32 #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 index 255cdc904a..32410c8d6b 100644 --- a/peerconnection/samples/client/peer_connection_client.cc +++ b/peerconnection/samples/client/peer_connection_client.cc @@ -11,24 +11,52 @@ #include "peerconnection/samples/client/peer_connection_client.h" #include "peerconnection/samples/client/defaults.h" +#include "talk/base/nethelpers.h" #include "talk/base/logging.h" #include "talk/base/stringutils.h" +#ifdef WIN32 +#include "talk/base/win32socketserver.h" +#endif + using talk_base::sprintfn; +namespace { + +// This is our magical hangup signal. +const char kByeMessage[] = "BYE"; + +talk_base::AsyncSocket* CreateClientSocket() { +#ifdef WIN32 + return new talk_base::Win32Socket(); +#elif defined(POSIX) + talk_base::Thread* thread = talk_base::Thread::Current(); + ASSERT(thread != NULL); + return thread->socketserver()->CreateAsyncSocket(SOCK_STREAM); +#else +#error Platform not supported. +#endif +} + +} + PeerConnectionClient::PeerConnectionClient() - : callback_(NULL), my_id_(-1), state_(NOT_CONNECTED) { - control_socket_.SignalCloseEvent.connect(this, + : callback_(NULL), + control_socket_(CreateClientSocket()), + hanging_get_(CreateClientSocket()), + state_(NOT_CONNECTED), + my_id_(-1) { + control_socket_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose); - hanging_get_.SignalCloseEvent.connect(this, + hanging_get_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose); - control_socket_.SignalConnectEvent.connect(this, + control_socket_->SignalConnectEvent.connect(this, &PeerConnectionClient::OnConnect); - hanging_get_.SignalConnectEvent.connect(this, + hanging_get_->SignalConnectEvent.connect(this, &PeerConnectionClient::OnHangingGetConnect); - control_socket_.SignalReadEvent.connect(this, + control_socket_->SignalReadEvent.connect(this, &PeerConnectionClient::OnRead); - hanging_get_.SignalReadEvent.connect(this, + hanging_get_->SignalReadEvent.connect(this, &PeerConnectionClient::OnHangingGetRead); } @@ -57,7 +85,12 @@ 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 (state_ != NOT_CONNECTED) { + LOG(WARNING) + << "The client must not be connected before you can call Connect()"; + return false; + } if (server.empty() || client_name.empty()) return false; @@ -69,7 +102,9 @@ bool PeerConnectionClient::Connect(const std::string& server, int port, server_address_.SetPort(port); if (server_address_.IsUnresolved()) { - hostent* h = gethostbyname(server_address_.IPAsString().c_str()); + int errcode = 0; + hostent* h = talk_base::SafeGetHostByName( + server_address_.IPAsString().c_str(), &errcode); if (!h) { LOG(LS_ERROR) << "Failed to resolve host name: " << server_address_.IPAsString(); @@ -77,6 +112,7 @@ bool PeerConnectionClient::Connect(const std::string& server, int port, } else { server_address_.SetResolvedIP( ntohl(*reinterpret_cast(h->h_addr_list[0]))); + talk_base::FreeHostEnt(h); } } @@ -97,7 +133,7 @@ bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) { return false; ASSERT(is_connected()); - ASSERT(control_socket_.GetState() == talk_base::Socket::CS_CLOSED); + ASSERT(control_socket_->GetState() == talk_base::Socket::CS_CLOSED); if (!is_connected() || peer_id == -1) return false; @@ -113,22 +149,35 @@ bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) { return ConnectControlSocket(); } +bool PeerConnectionClient::SendHangUp(int peer_id) { + return SendToPeer(peer_id, kByeMessage); +} + +bool PeerConnectionClient::IsSendingMessage() { + return state_ == CONNECTED && + control_socket_->GetState() != talk_base::Socket::CS_CLOSED; +} + 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 (hanging_get_->GetState() != talk_base::Socket::CS_CLOSED) + hanging_get_->Close(); - if (control_socket_.GetState() == talk_base::Socket::CS_CLOSED) { - ASSERT(my_id_ != -1); + if (control_socket_->GetState() == talk_base::Socket::CS_CLOSED) { state_ = SIGNING_OUT; - char buffer[1024]; - sprintfn(buffer, sizeof(buffer), - "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_); - onconnect_data_ = buffer; - return ConnectControlSocket(); + if (my_id_ != -1) { + char buffer[1024]; + sprintfn(buffer, sizeof(buffer), + "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_); + onconnect_data_ = buffer; + return ConnectControlSocket(); + } else { + // Can occur if the app is closed before we finish connecting. + return true; + } } else { state_ = SIGNING_OUT_WAITING; } @@ -137,8 +186,8 @@ bool PeerConnectionClient::SignOut() { } void PeerConnectionClient::Close() { - control_socket_.Close(); - hanging_get_.Close(); + control_socket_->Close(); + hanging_get_->Close(); onconnect_data_.clear(); peers_.clear(); my_id_ = -1; @@ -146,8 +195,8 @@ void PeerConnectionClient::Close() { } bool PeerConnectionClient::ConnectControlSocket() { - ASSERT(control_socket_.GetState() == talk_base::Socket::CS_CLOSED); - int err = control_socket_.Connect(server_address_); + ASSERT(control_socket_->GetState() == talk_base::Socket::CS_CLOSED); + int err = control_socket_->Connect(server_address_); if (err == SOCKET_ERROR) { Close(); return false; @@ -157,7 +206,7 @@ bool PeerConnectionClient::ConnectControlSocket() { void PeerConnectionClient::OnConnect(talk_base::AsyncSocket* socket) { ASSERT(!onconnect_data_.empty()); - int sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length()); + size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length()); ASSERT(sent == onconnect_data_.length()); onconnect_data_.clear(); } @@ -166,19 +215,29 @@ void PeerConnectionClient::OnHangingGetConnect(talk_base::AsyncSocket* socket) { char buffer[1024]; sprintfn(buffer, sizeof(buffer), "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_); - int len = lstrlenA(buffer); + int len = strlen(buffer); int sent = socket->Send(buffer, len); ASSERT(sent == len); } +void PeerConnectionClient::OnMessageFromPeer(int peer_id, + const std::string& message) { + if (message.length() == (sizeof(kByeMessage) - 1) && + message.compare(kByeMessage) == 0) { + callback_->OnPeerDisconnected(peer_id); + } else { + callback_->OnMessageFromPeer(peer_id, message); + } +} + bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh, const char* header_pattern, size_t* value) { - ASSERT(value); + ASSERT(value != NULL); size_t found = data.find(header_pattern); if (found != std::string::npos && found < eoh) { - *value = atoi(&data[found + lstrlenA(header_pattern)]); + *value = atoi(&data[found + strlen(header_pattern)]); return true; } return false; @@ -187,10 +246,10 @@ bool PeerConnectionClient::GetHeaderValue(const std::string& data, bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh, const char* header_pattern, std::string* value) { - ASSERT(value); + ASSERT(value != NULL); size_t found = data.find(header_pattern); if (found != std::string::npos && found < eoh) { - size_t begin = found + lstrlenA(header_pattern); + size_t begin = found + strlen(header_pattern); size_t end = data.find("\r\n", begin); if (end == std::string::npos) end = eoh; @@ -217,7 +276,6 @@ bool PeerConnectionClient::ReadIntoBuffer(talk_base::AsyncSocket* socket, 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; @@ -284,9 +342,9 @@ void PeerConnectionClient::OnRead(talk_base::AsyncSocket* socket) { control_data_.clear(); if (state_ == SIGNING_IN) { - ASSERT(hanging_get_.GetState() == talk_base::Socket::CS_CLOSED); + ASSERT(hanging_get_->GetState() == talk_base::Socket::CS_CLOSED); state_ = CONNECTED; - hanging_get_.Connect(server_address_); + hanging_get_->Connect(server_address_); } } } @@ -303,7 +361,7 @@ void PeerConnectionClient::OnHangingGetRead(talk_base::AsyncSocket* socket) { // Store the position where the body begins. size_t pos = eoh + 4; - if (my_id_ == peer_id) { + if (my_id_ == static_cast(peer_id)) { // A notification about a new member or a member that just // disconnected. int id = 0; @@ -316,21 +374,20 @@ void PeerConnectionClient::OnHangingGetRead(talk_base::AsyncSocket* socket) { callback_->OnPeerConnected(id, name); } else { peers_.erase(id); - callback_->OnPeerDisconnected(id, name); + callback_->OnPeerDisconnected(id); } } } else { - callback_->OnMessageFromPeer(peer_id, - notification_data_.substr(pos)); + OnMessageFromPeer(peer_id, notification_data_.substr(pos)); } } notification_data_.clear(); } - if (hanging_get_.GetState() == talk_base::Socket::CS_CLOSED && + if (hanging_get_->GetState() == talk_base::Socket::CS_CLOSED && state_ == CONNECTED) { - hanging_get_.Connect(server_address_); + hanging_get_->Connect(server_address_); } } @@ -338,10 +395,10 @@ bool PeerConnectionClient::ParseEntry(const std::string& entry, std::string* name, int* id, bool* connected) { - ASSERT(name); - ASSERT(id); - ASSERT(connected); - ASSERT(entry.length()); + ASSERT(name != NULL); + ASSERT(id != NULL); + ASSERT(connected != NULL); + ASSERT(!entry.empty()); *connected = false; size_t separator = entry.find(','); @@ -397,13 +454,19 @@ void PeerConnectionClient::OnClose(talk_base::AsyncSocket* socket, int err) { socket->Close(); +#ifdef WIN32 if (err != WSAECONNREFUSED) { - if (socket == &hanging_get_) { +#else + if (err != ECONNREFUSED) { +#endif + if (socket == hanging_get_.get()) { if (state_ == CONNECTED) { LOG(INFO) << "Issuing a new hanging get"; - hanging_get_.Close(); - hanging_get_.Connect(server_address_); + hanging_get_->Close(); + hanging_get_->Connect(server_address_); } + } else { + callback_->OnMessageSent(err); } } else { // Failed to connect to the server. diff --git a/peerconnection/samples/client/peer_connection_client.h b/peerconnection/samples/client/peer_connection_client.h index d40179e763..bc02bdd823 100644 --- a/peerconnection/samples/client/peer_connection_client.h +++ b/peerconnection/samples/client/peer_connection_client.h @@ -16,7 +16,8 @@ #include #include "talk/base/sigslot.h" -#include "talk/base/win32socketserver.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/scoped_ptr.h" typedef std::map Peers; @@ -24,8 +25,10 @@ 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 OnPeerDisconnected(int peer_id) = 0; virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0; + virtual void OnMessageSent(int err) = 0; + protected: virtual ~PeerConnectionClientObserver() {} }; @@ -53,6 +56,8 @@ class PeerConnectionClient : public sigslot::has_slots<> { const std::string& client_name); bool SendToPeer(int peer_id, const std::string& message); + bool SendHangUp(int peer_id); + bool IsSendingMessage(); bool SignOut(); @@ -61,6 +66,7 @@ class PeerConnectionClient : public sigslot::has_slots<> { bool ConnectControlSocket(); void OnConnect(talk_base::AsyncSocket* socket); void OnHangingGetConnect(talk_base::AsyncSocket* socket); + void OnMessageFromPeer(int peer_id, const std::string& message); // Quick and dirty support for parsing HTTP header values. bool GetHeaderValue(const std::string& data, size_t eoh, @@ -90,8 +96,8 @@ class PeerConnectionClient : public sigslot::has_slots<> { PeerConnectionClientObserver* callback_; talk_base::SocketAddress server_address_; - talk_base::Win32Socket control_socket_; - talk_base::Win32Socket hanging_get_; + talk_base::scoped_ptr control_socket_; + talk_base::scoped_ptr hanging_get_; std::string onconnect_data_; std::string control_data_; std::string notification_data_; diff --git a/webrtc.gyp b/webrtc.gyp index 801e1adf52..df0e22b95b 100644 --- a/webrtc.gyp +++ b/webrtc.gyp @@ -68,8 +68,8 @@ 'third_party_mods/libjingle/libjingle.gyp:libjingle_app', ], 'include_dirs': [ - 'third_party_mods/libjingle/source', 'third_party/libjingle/source', + 'third_party_mods/libjingle/source', ], }, ], # targets @@ -80,9 +80,15 @@ 'target_name': 'peerconnection_client', 'type': 'executable', 'sources': [ + 'peerconnection/samples/client/conductor.cc', + 'peerconnection/samples/client/conductor.h', + 'peerconnection/samples/client/defaults.cc', + 'peerconnection/samples/client/defaults.h', 'peerconnection/samples/client/linux/main.cc', 'peerconnection/samples/client/linux/main_wnd.cc', 'peerconnection/samples/client/linux/main_wnd.h', + 'peerconnection/samples/client/peer_connection_client.cc', + 'peerconnection/samples/client/peer_connection_client.h', ], 'dependencies': [ 'third_party_mods/libjingle/libjingle.gyp:libjingle_app', @@ -92,6 +98,7 @@ ], 'include_dirs': [ 'third_party/libjingle/source', + 'third_party_mods/libjingle/source', ], 'cflags': [ '