From 99239d5a41fa81bd5f8d5aa06ffe6c27da2d0243 Mon Sep 17 00:00:00 2001 From: "perkj@webrtc.org" Date: Mon, 3 Oct 2011 15:59:40 +0000 Subject: [PATCH] First compiling version of peerconnection_client_dev using the new Peerconnection API. Links but does not work since the new peerconnection is under development. I would like to commit a version with as few changes as possible to the old peerconnection_client but using the new PeerConnection API. BUG= TEST= Review URL: http://webrtc-codereview.appspot.com/183003 git-svn-id: http://webrtc.googlecode.com/svn/trunk@677 4adac7df-926f-26a2-2b94-8c16560cd09d --- peerconnection/samples/client/conductor.cc | 6 +- peerconnection/samples/client/conductor.h | 2 +- third_party_mods/libjingle/libjingle.gyp | 59 +- .../peerconnection_client/conductor.cc | 353 +++++++++++ .../peerconnection_client/conductor.h | 109 ++++ .../peerconnection_client/defaults.cc | 58 ++ .../examples/peerconnection_client/defaults.h | 30 + .../peerconnection_client/linux/main.cc | 85 +++ .../peerconnection_client/linux/main_wnd.cc | 458 ++++++++++++++ .../peerconnection_client/linux/main_wnd.h | 114 ++++ .../examples/peerconnection_client/main.cc | 58 ++ .../peerconnection_client/main_wnd.cc | 582 ++++++++++++++++++ .../examples/peerconnection_client/main_wnd.h | 190 ++++++ .../peer_connection_client.cc | 479 ++++++++++++++ .../peer_connection_client.h | 109 ++++ .../peerconnection_client.gyp | 55 ++ 16 files changed, 2703 insertions(+), 44 deletions(-) create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.cc create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.h create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.cc create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.h create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main.cc create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.cc create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.h create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/main.cc create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.cc create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.h create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.cc create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.h create mode 100644 third_party_mods/libjingle/source/talk/examples/peerconnection_client/peerconnection_client.gyp diff --git a/peerconnection/samples/client/conductor.cc b/peerconnection/samples/client/conductor.cc index 3dae5e62c..e8cf73f5a 100644 --- a/peerconnection/samples/client/conductor.cc +++ b/peerconnection/samples/client/conductor.cc @@ -102,7 +102,7 @@ void Conductor::DeletePeerConnection() { peer_id_ = -1; } -void Conductor::StartCaptureDevice() { +void Conductor::SwitchToStreamingUi() { ASSERT(peer_connection_.get() != NULL); if (main_wnd_->IsWindow()) { if (main_wnd_->current_ui() != MainWindow::STREAMING) @@ -249,7 +249,7 @@ void Conductor::ConnectToPeer(int peer_id) { if (InitializePeerConnection()) { peer_id_ = peer_id; main_wnd_->SwitchToStreamingUI(); - StartCaptureDevice(); + SwitchToStreamingUi(); AddStreams(); } else { main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true); @@ -371,7 +371,7 @@ void Conductor::UIThreadCallback(int msg_id, void* data) { // TODO(tommi): For the initiator, we shouldn't have to make this call // here (which is actually the second time this is called for the // initiator). Look into why this is needed. - StartCaptureDevice(); + SwitchToStreamingUi(); } // If we haven't shared any streams with this peer (we're the receiver) diff --git a/peerconnection/samples/client/conductor.h b/peerconnection/samples/client/conductor.h index 83fd9e313..c11177833 100644 --- a/peerconnection/samples/client/conductor.h +++ b/peerconnection/samples/client/conductor.h @@ -51,7 +51,7 @@ class Conductor protected: bool InitializePeerConnection(); void DeletePeerConnection(); - void StartCaptureDevice(); + void SwitchToStreamingUi(); bool AddStream(const std::string& id, bool video); void AddStreams(); diff --git a/third_party_mods/libjingle/libjingle.gyp b/third_party_mods/libjingle/libjingle.gyp index 84750e219..f59b5a060 100644 --- a/third_party_mods/libjingle/libjingle.gyp +++ b/third_party_mods/libjingle/libjingle.gyp @@ -712,41 +712,19 @@ }], # peer_connection_dev ], # conditions }, - { - 'target_name': 'peerconnection_client_dev', - 'conditions': [ - ['peer_connection_dev==1 and OS=="linux"', { - 'type': 'executable', - 'sources': [ - '<(libjingle_mods)/source/talk/app/peer_connection_dev/peerconnection_client_dev.cc', - ], - 'libraries': [ - '-lXext', - '-lX11', - ], - }, { - 'type': 'none', - } ], # peer_connection_dev - ['inside_chromium_build==1', { - 'dependencies': [ - 'libjingle_app', - ], - }, { - 'dependencies': [ - 'libjingle_app', - ], - } ], # inside_chromium_build - ], # conditions - }, { 'target_name': 'peerconnection_unittests', - 'dependencies': [ - 'libjingle_app', - '../../testing/gtest.gyp:gtest', - '../../testing/gtest.gyp:gtest_main', - ], 'conditions': [ - ['peer_connection_dev==1', { + ['peer_connection_dev==1', { + 'dependencies': [ + 'libjingle_app', + '../../testing/gtest.gyp:gtest', + '../../testing/gtest.gyp:gtest_main', + # TODO(perkj): Temporary build the client app here to make sure + # nothing is broken. + 'source/talk/examples/peerconnection_client/' + 'peerconnection_client.gyp:peerconnection_client_dev', + ], 'type': 'executable', 'conditions': [ ['inside_chromium_build==1', { @@ -760,6 +738,12 @@ '../../src/system_wrappers/source/system_wrappers.gyp:system_wrappers', ], }], + ['OS=="linux"', { + 'libraries': [ + '-lXext', + '-lX11', + ], + }], ], #conditions 'sources': [ '<(libjingle_mods)/source/talk/app/webrtc_dev/mediastreamimpl_unittest.cc', @@ -770,15 +754,10 @@ '<(libjingle_mods)/source/talk/app/webrtc_dev/peerconnectionsignaling_unittest.cc', #'<(libjingle_mods)/source/talk/app/webrtc_dev/webrtcsession_unittest.cc', ], - }, { # peer_connection_dev != 1 + } , { 'type': 'none', - } ], # peer_connection_dev - ['peer_connection_dev==1 and OS=="linux"', { - 'libraries': [ - '-lXext', - '-lX11', - ], - } ], + } + ], # peer_connection_dev ], # conditions }, ], diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.cc b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.cc new file mode 100644 index 000000000..34959c573 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.cc @@ -0,0 +1,353 @@ +/* + * 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 "talk/examples/peerconnection_client/conductor.h" + +#include + +#include "talk/examples/peerconnection_client/defaults.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/session/phone/videorendererfactory.h" + +Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd) + : peer_id_(-1), + client_(client), + main_wnd_(main_wnd) { + client_->RegisterObserver(this); + main_wnd->RegisterObserver(this); +} + +Conductor::~Conductor() { + ASSERT(peer_connection_.get() == NULL); +} + +bool Conductor::connection_active() const { + return peer_connection_.get() != NULL; +} + +void Conductor::Close() { + client_->SignOut(); + DeletePeerConnection(); +} + +bool Conductor::InitializePeerConnection() { + ASSERT(peer_connection_factory_.get() == NULL); + ASSERT(peer_connection_.get() == NULL); + + peer_connection_factory_ = webrtc::PeerConnectionManager::Create(); + + if (!peer_connection_factory_.get()) { + main_wnd_->MessageBox("Error", + "Failed to initialize PeerConnectionFactory", true); + DeletePeerConnection(); + return false; + } + + peer_connection_ = peer_connection_factory_->CreatePeerConnection( + GetPeerConnectionString(), this); + + if (!peer_connection_.get()) { + main_wnd_->MessageBox("Error", + "CreatePeerConnection failed", true); + DeletePeerConnection(); + } + return peer_connection_.get() != NULL; +} + +void Conductor::DeletePeerConnection() { + peer_connection_.release(); + active_streams_.clear(); + peer_connection_factory_.release(); + peer_id_ = -1; +} + +void Conductor::EnsureStreamingUI() { + ASSERT(peer_connection_.get() != NULL); + if (main_wnd_->IsWindow()) { + if (main_wnd_->current_ui() != MainWindow::STREAMING) + main_wnd_->SwitchToStreamingUI(); + } +} + +// +// PeerConnectionObserver implementation. +// + +void Conductor::OnError() { + LOG(LS_ERROR) << __FUNCTION__; + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_ERROR, NULL); +} + +void Conductor::OnSignalingMessage(const std::string& msg) { + LOG(INFO) << __FUNCTION__; + + std::string* msg_copy = new std::string(msg); + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg_copy); +} + +// Called when a remote stream is added +void Conductor::OnAddStream(webrtc::MediaStream* stream) { + LOG(INFO) << __FUNCTION__ << " " << stream->label(); + + stream->AddRef(); + main_wnd_->QueueUIThreadCallback(NEW_STREAM_ADDED, + stream); +} + +void Conductor::OnRemoveStream(webrtc::MediaStream* stream) { + LOG(INFO) << __FUNCTION__ << " " << stream->label(); + stream->AddRef(); + main_wnd_->QueueUIThreadCallback(STREAM_REMOVED, + stream); +} + +// +// PeerConnectionClientObserver implementation. +// + +void Conductor::OnSignedIn() { + LOG(INFO) << __FUNCTION__; + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnDisconnected() { + LOG(INFO) << __FUNCTION__; + + 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() == MainWindow::LIST_PEERS) + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnPeerDisconnected(int id) { + LOG(INFO) << __FUNCTION__; + if (id == peer_id_) { + LOG(INFO) << "Our peer disconnected"; + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL); + } else { + // Refresh the list if we're showing it. + if (main_wnd_->current_ui() == MainWindow::LIST_PEERS) + main_wnd_->SwitchToPeerList(client_->peers()); + } +} + +void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) { + ASSERT(peer_id_ == peer_id || peer_id_ == -1); + ASSERT(!message.empty()); + + if (!peer_connection_.get()) { + ASSERT(peer_id_ == -1); + peer_id_ = peer_id; + + // 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 (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_->ProcessSignalingMessage(message); +} + +void Conductor::OnMessageSent(int err) { + // Process the next pending message if any. + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL); +} + +// +// MainWndCallback implementation. +// + +bool Conductor::StartLogin(const std::string& server, int port) { + if (client_->is_connected()) + return false; + + if (!client_->Connect(server, port, GetPeerName())) { + main_wnd_->MessageBox("Error", ("Failed to connect to " + server).c_str(), + true); + return false; + } + + return true; +} + +void Conductor::DisconnectFromServer() { + if (client_->is_connected()) + client_->SignOut(); +} + +void Conductor::ConnectToPeer(int peer_id) { + ASSERT(peer_id_ == -1); + ASSERT(peer_id != -1); + + if (peer_connection_.get()) { + main_wnd_->MessageBox("Error", + "We only support connecting to one peer at a time", true); + return; + } + + if (InitializePeerConnection()) { + peer_id_ = peer_id; + main_wnd_->SwitchToStreamingUI(); + EnsureStreamingUI(); + AddStreams(); + } else { + main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true); + } +} + +void Conductor::AddStreams() { + if (active_streams_.find(kStreamLabel) != active_streams_.end()) + return; // Already added. + + scoped_refptr audio_track( + webrtc::CreateLocalAudioTrack(kAudioLabel, NULL)); + + scoped_refptr video_track( + webrtc::CreateLocalVideoTrack(kVideoLabel, NULL)); + + scoped_refptr renderer(webrtc::CreateVideoRenderer( + main_wnd_->local_renderer())); + video_track->SetRenderer(renderer); + + scoped_refptr stream = + webrtc::CreateLocalMediaStream(kStreamLabel); + + stream->AddTrack(audio_track); + stream->AddTrack(video_track); + peer_connection_->AddStream(stream); + peer_connection_->CommitStreamChanges(); + typedef std::pair > + MediaStreamPair; + active_streams_.insert(MediaStreamPair(stream->label(), stream)); +} + +void Conductor::DisconnectFromCurrentPeer() { + LOG(INFO) << __FUNCTION__; + if (peer_connection_.get()) { + client_->SendHangUp(peer_id_); + DeletePeerConnection(); + } + + if (main_wnd_->IsWindow()) + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::UIThreadCallback(int msg_id, void* data) { + switch (msg_id) { + case PEER_CONNECTION_CLOSED: + LOG(INFO) << "PEER_CONNECTION_CLOSED"; + DeletePeerConnection(); + + ASSERT(active_streams_.empty()); + + if (main_wnd_->IsWindow()) { + if (client_->is_connected()) { + main_wnd_->SwitchToPeerList(client_->peers()); + } else { + main_wnd_->SwitchToConnectUI(); + } + } else { + DisconnectFromServer(); + } + break; + + case SEND_MESSAGE_TO_PEER: { + 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; + } + + if (!peer_connection_.get()) + peer_id_ = -1; + } + break; + } + + case PEER_CONNECTION_ADDSTREAMS: + AddStreams(); + break; + + case PEER_CONNECTION_ERROR: + main_wnd_->MessageBox("Error", "an unknown error occurred", true); + break; + + case NEW_STREAM_ADDED: { + webrtc::MediaStream* stream = reinterpret_cast( + data); + scoped_refptr tracks = + stream->tracks(); + for (size_t i = 0; i < tracks->count(); ++i) { + if (tracks->at(i)->kind().compare(webrtc::kVideoTrackKind) == 0) { + webrtc::VideoTrack* track = + reinterpret_cast(tracks->at(i).get()); + LOG(INFO) << "Setting video renderer for track: " << track->label(); + scoped_refptr renderer( + webrtc::CreateVideoRenderer(main_wnd_->remote_renderer())); + track->SetRenderer(renderer); + } + } + // If we haven't shared any streams with this peer (we're the receiver) + // then do so now. + if (active_streams_.empty()) + AddStreams(); + stream->Release(); + break; + } + + case STREAM_REMOVED: { + webrtc::MediaStream* stream = reinterpret_cast( + data); + active_streams_.erase(stream->label()); + stream->Release(); + if (active_streams_.empty()) { + LOG(INFO) << "All streams have been closed."; + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL); + } + break; + } + + default: + ASSERT(false); + break; + } +} diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.h b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.h new file mode 100644 index 000000000..ed0fe2131 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/conductor.h @@ -0,0 +1,109 @@ +/* + * 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 +#include +#include +#include + +#include "talk/examples/peerconnection_client/main_wnd.h" +#include "talk/examples/peerconnection_client/peer_connection_client.h" +#include "talk/app/webrtc_dev/mediastream.h" +#include "talk/app/webrtc_dev/peerconnection.h" +#include "talk/base/scoped_ptr.h" + +namespace cricket { +class VideoRenderer; +} // namespace cricket + +class Conductor + : public webrtc::PeerConnectionObserver, + public PeerConnectionClientObserver, + public MainWndCallback { + public: + enum CallbackID { + MEDIA_CHANNELS_INITIALIZED = 1, + PEER_CONNECTION_CLOSED, + SEND_MESSAGE_TO_PEER, + PEER_CONNECTION_ADDSTREAMS, + PEER_CONNECTION_ERROR, + NEW_STREAM_ADDED, + STREAM_REMOVED, + }; + + Conductor(PeerConnectionClient* client, MainWindow* main_wnd); + ~Conductor(); + + bool connection_active() const; + + virtual void Close(); + + protected: + bool InitializePeerConnection(); + void DeletePeerConnection(); + void EnsureStreamingUI(); + void AddStreams(); + + // + // PeerConnectionObserver implementation. + // + virtual void OnError(); + virtual void OnMessage(const std::string& msg) {} + virtual void OnSignalingMessage(const std::string& msg); + virtual void OnStateChange(Readiness state) {} + virtual void OnAddStream(webrtc::MediaStream* stream); + virtual void OnRemoveStream(webrtc::MediaStream* stream); + + + // + // PeerConnectionClientObserver implementation. + // + + virtual void OnSignedIn(); + + virtual void OnDisconnected(); + + virtual void OnPeerConnected(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 bool StartLogin(const std::string& server, int port); + + virtual void DisconnectFromServer(); + + virtual void ConnectToPeer(int peer_id); + + virtual void DisconnectFromCurrentPeer(); + + virtual void UIThreadCallback(int msg_id, void* data); + + protected: + int peer_id_; + scoped_refptr peer_connection_; + scoped_refptr peer_connection_factory_; + PeerConnectionClient* client_; + MainWindow* main_wnd_; + std::deque pending_messages_; + std::map > active_streams_; +}; + +#endif // PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_ diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.cc b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.cc new file mode 100644 index 000000000..38b6d9d4a --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.cc @@ -0,0 +1,58 @@ +/* + * 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 "talk/examples/peerconnection_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 char kStreamLabel[] = "stream_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[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/third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.h b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.h new file mode 100644 index 000000000..bd05980c6 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/defaults.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_ +#define PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_ +#pragma once + +#include + +#include "talk/base/basictypes.h" + +extern const char kAudioLabel[]; +extern const char kVideoLabel[]; +extern const char kStreamLabel[]; +extern const uint16 kDefaultServerPort; + +std::string GetEnvVarOrDefault(const char* env_var_name, + const char* default_value); +std::string GetPeerConnectionString(); +std::string GetDefaultServerName(); +std::string GetPeerName(); + +#endif // PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_ diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main.cc b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main.cc new file mode 100644 index 000000000..87590fbe5 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main.cc @@ -0,0 +1,85 @@ +/* + * 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 + +#include "talk/examples/peerconnection_client/conductor.h" +#include "talk/examples/peerconnection_client/linux/main_wnd.h" +#include "talk/examples/peerconnection_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(); + + 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(); + //} + + return 0; +} + diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.cc b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.cc new file mode 100644 index 000000000..9abc762be --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.cc @@ -0,0 +1,458 @@ +/* + * 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 "talk/examples/peerconnection_client/linux/main_wnd.h" + +#include +#include +#include + +#include "talk/examples/peerconnection_client/defaults.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/stringutils.h" + +using talk_base::sprintfn; + +namespace { + +// +// Simple static functions that simply forward the callback to the +// GtkMainWnd instance. +// + +gboolean OnDestroyedCallback(GtkWidget* widget, GdkEvent* event, + gpointer data) { + reinterpret_cast(data)->OnDestroyed(widget, event); + return FALSE; +} + +void OnClickedCallback(GtkWidget* widget, gpointer data) { + reinterpret_cast(data)->OnClicked(widget); +} + +gboolean OnKeyPressCallback(GtkWidget* widget, GdkEventKey* key, + gpointer data) { + reinterpret_cast(data)->OnKeyPress(widget, key); + return false; +} + +void OnRowActivatedCallback(GtkTreeView* tree_view, GtkTreePath* path, + GtkTreeViewColumn* column, gpointer data) { + reinterpret_cast(data)->OnRowActivated(tree_view, path, column); +} + +// Creates a tree view, that we use to display the list of peers. +void InitializeList(GtkWidget* list) { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( + "List Items", renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(list), column); + GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); + gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store)); + g_object_unref(store); +} + +// Adds an entry to a tree view. +void AddToList(GtkWidget* list, const gchar* str, int value) { + GtkListStore* store = GTK_LIST_STORE( + gtk_tree_view_get_model(GTK_TREE_VIEW(list))); + + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, str, 1, value, -1); +} + +struct UIThreadCallbackData { + explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d) + : callback(cb), msg_id(id), data(d) {} + MainWndCallback* callback; + int msg_id; + void* data; +}; + +gboolean HandleUIThreadCallback(gpointer data) { + UIThreadCallbackData* cb_data = reinterpret_cast(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 + +// +// GtkMainWnd implementation. +// + +GtkMainWnd::GtkMainWnd() + : 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); + + window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (window_) { + gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER); + gtk_window_set_default_size(GTK_WINDOW(window_), 640, 480); + gtk_window_set_title(GTK_WINDOW(window_), "PeerConnection client"); + g_signal_connect(G_OBJECT(window_), "delete-event", + G_CALLBACK(&OnDestroyedCallback), this); + g_signal_connect(window_, "key-press-event", G_CALLBACK(OnKeyPressCallback), + this); + + SwitchToConnectUI(); + } + + return window_ != NULL; +} + +bool GtkMainWnd::Destroy() { + if (!IsWindow()) + return false; + + gtk_widget_destroy(window_); + window_ = NULL; + + return true; +} + +void GtkMainWnd::SwitchToConnectUI() { + LOG(INFO) << __FUNCTION__; + + ASSERT(IsWindow()); + ASSERT(vbox_ == NULL); + + gtk_container_set_border_width(GTK_CONTAINER(window_), 10); + + if (peer_list_) { + gtk_widget_destroy(peer_list_); + peer_list_ = NULL; + } + + vbox_ = gtk_vbox_new(FALSE, 5); + GtkWidget* valign = gtk_alignment_new(0, 1, 0, 0); + gtk_container_add(GTK_CONTAINER(vbox_), valign); + gtk_container_add(GTK_CONTAINER(window_), vbox_); + + GtkWidget* hbox = gtk_hbox_new(FALSE, 5); + + GtkWidget* label = gtk_label_new("Server"); + gtk_container_add(GTK_CONTAINER(hbox), label); + + server_edit_ = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str()); + gtk_widget_set_size_request(server_edit_, 400, 30); + gtk_container_add(GTK_CONTAINER(hbox), server_edit_); + + port_edit_ = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str()); + gtk_widget_set_size_request(port_edit_, 70, 30); + gtk_container_add(GTK_CONTAINER(hbox), port_edit_); + + GtkWidget* button = gtk_button_new_with_label("Connect"); + gtk_widget_set_size_request(button, 70, 30); + g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this); + gtk_container_add(GTK_CONTAINER(hbox), button); + + GtkWidget* halign = gtk_alignment_new(1, 0, 0, 0); + gtk_container_add(GTK_CONTAINER(halign), hbox); + gtk_box_pack_start(GTK_BOX(vbox_), halign, FALSE, FALSE, 0); + + gtk_widget_show_all(window_); +} + +void GtkMainWnd::SwitchToPeerList(const Peers& peers) { + LOG(INFO) << __FUNCTION__; + + // Clean up buffers from a potential previous session. + local_renderer_.reset(); + remote_renderer_.reset(); + + if (!peer_list_) { + gtk_container_set_border_width(GTK_CONTAINER(window_), 0); + if (vbox_) { + gtk_widget_destroy(vbox_); + vbox_ = NULL; + server_edit_ = NULL; + port_edit_ = NULL; + } else if (draw_area_) { + gtk_widget_destroy(draw_area_); + draw_area_ = NULL; + draw_buffer_.reset(); + } + + peer_list_ = gtk_tree_view_new(); + g_signal_connect(peer_list_, "row-activated", + G_CALLBACK(OnRowActivatedCallback), this); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE); + InitializeList(peer_list_); + gtk_container_add(GTK_CONTAINER(window_), peer_list_); + gtk_widget_show_all(window_); + } else { + GtkListStore* store = + GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_))); + gtk_list_store_clear(store); + } + + AddToList(peer_list_, "List of currently connected peers:", -1); + for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i) + AddToList(peer_list_, i->second.c_str(), i->first); +} + +void GtkMainWnd::SwitchToStreamingUI() { + LOG(INFO) << __FUNCTION__; + + ASSERT(draw_area_ == NULL); + + gtk_container_set_border_width(GTK_CONTAINER(window_), 0); + if (peer_list_) { + gtk_widget_destroy(peer_list_); + peer_list_ = NULL; + } + + draw_area_ = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(window_), draw_area_); + + gtk_widget_show_all(window_); +} + +void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) { + callback_->Close(); + window_ = NULL; + draw_area_ = NULL; + vbox_ = NULL; + server_edit_ = NULL; + port_edit_ = NULL; + peer_list_ = NULL; +} + +void GtkMainWnd::OnClicked(GtkWidget* widget) { + server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_)); + port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_)); + int port = port_.length() ? atoi(port_.c_str()) : 0; + callback_->StartLogin(server_, port); +} + +void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) { + if (key->type == GDK_KEY_PRESS) { + switch (key->keyval) { + case GDK_Escape: + if (draw_area_) { + callback_->DisconnectFromCurrentPeer(); + } else if (peer_list_) { + callback_->DisconnectFromServer(); + } + break; + + case GDK_KP_Enter: + case GDK_Return: + if (vbox_) { + OnClicked(NULL); + } else if (peer_list_) { + // OnRowActivated will be called automatically when the user + // presses enter. + } + break; + + default: + break; + } + } +} + +void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path, + GtkTreeViewColumn* column) { + ASSERT(peer_list_ != NULL); + GtkTreeIter iter; + GtkTreeModel* model; + GtkTreeSelection* selection = + gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + char* text; + int id = -1; + gtk_tree_model_get(model, &iter, 0, &text, 1, &id, -1); + if (id != -1) + callback_->ConnectToPeer(id); + g_free(text); + } +} + +void GtkMainWnd::OnRedraw() { + gdk_threads_enter(); + + 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; + } + + if (local_renderer_.get() && local_renderer_->image()) { + 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/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.h b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.h new file mode 100644 index 000000000..64a3eb2b8 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/linux/main_wnd.h @@ -0,0 +1,114 @@ +/* + * 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_LINUX_MAIN_WND_H_ +#define PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_ + +#include "talk/examples/peerconnection_client/main_wnd.h" +#include "talk/examples/peerconnection_client/peer_connection_client.h" + +// Forward declarations. +typedef struct _GtkWidget GtkWidget; +typedef union _GdkEvent GdkEvent; +typedef struct _GdkEventKey GdkEventKey; +typedef struct _GtkTreeView GtkTreeView; +typedef struct _GtkTreePath GtkTreePath; +typedef struct _GtkTreeViewColumn GtkTreeViewColumn; + +// Implements the main UI of the peer connection client. +// This is functionally equivalent to the MainWnd class in the Windows +// implementation. +class GtkMainWnd : public MainWindow { + public: + GtkMainWnd(); + ~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(); + + // Destroys the window. When the window is destroyed, it ends the + // main message loop. + bool Destroy(); + + // Callback for when the main window is destroyed. + void OnDestroyed(GtkWidget* widget, GdkEvent* event); + + // Callback for when the user clicks the "Connect" button. + void OnClicked(GtkWidget* widget); + + // Callback for keystrokes. Used to capture Esc and Return. + void OnKeyPress(GtkWidget* widget, GdkEventKey* key); + + // Callback when the user double clicks a peer in order to initiate a + // connection. + void OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path, + GtkTreeViewColumn* column); + + void OnRedraw(); + + protected: + class VideoRenderer : public cricket::VideoRenderer { + public: + VideoRenderer(GtkMainWnd* main_wnd); + virtual ~VideoRenderer(); + + virtual bool SetSize(int width, int height, int reserved); + + 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/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main.cc b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main.cc new file mode 100644 index 000000000..27d81e0bf --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main.cc @@ -0,0 +1,58 @@ +/* + * 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 + +#include "talk/examples/peerconnection_client/conductor.h" +#include "talk/examples/peerconnection_client/main_wnd.h" +#include "talk/examples/peerconnection_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()) { + while ((conductor.connection_active() || client.is_connected()) && + (gm = ::GetMessage(&msg, NULL, 0, 0)) && gm != -1) { + if (!wnd.PreTranslateMessage(&msg)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + } + + return 0; +} diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.cc b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.cc new file mode 100644 index 000000000..c237331e2 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.cc @@ -0,0 +1,582 @@ +/* + * 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 "talk/examples/peerconnection_client/main_wnd.h" + +#include + +#include "talk/base/common.h" +#include "talk/base/logging.h" + +ATOM MainWnd::wnd_class_ = 0; +const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd"; + +namespace { + +const char kConnecting[] = "Connecting... "; +const char kNoVideoStreams[] = "(no video streams either way)"; +const char kNoIncomingStream[] = "(no incoming video)"; + +void CalculateWindowSizeForText(HWND wnd, const wchar_t* text, + size_t* width, size_t* height) { + HDC dc = ::GetDC(wnd); + RECT text_rc = {0}; + ::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(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(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; + + ui_thread_id_ = ::GetCurrentThreadId(); + wnd_ = ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC", + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, GetModuleHandle(NULL), this); + + ::SendMessage(wnd_, WM_SETFONT, reinterpret_cast(GetDefaultFont()), + TRUE); + + CreateChildWindows(); + SwitchToConnectUI(); + + return wnd_ != NULL; +} + +bool MainWnd::Destroy() { + BOOL ret = FALSE; + if (IsWindow()) { + ret = ::DestroyWindow(wnd_); + } + + return ret != FALSE; +} + +void MainWnd::RegisterObserver(MainWndCallback* callback) { + callback_ = callback; +} + +bool MainWnd::IsWindow() { + return wnd_ && ::IsWindow(wnd_) != FALSE; +} + +bool MainWnd::PreTranslateMessage(MSG* msg) { + bool ret = false; + if (msg->message == WM_CHAR) { + if (msg->wParam == VK_TAB) { + HandleTabbing(); + ret = true; + } else if (msg->wParam == VK_RETURN) { + OnDefaultAction(); + ret = true; + } else if (msg->wParam == VK_ESCAPE) { + if (callback_) { + if (ui_ == STREAMING) { + callback_->DisconnectFromCurrentPeer(); + } else { + callback_->DisconnectFromServer(); + } + } + } + } else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) { + callback_->UIThreadCallback(static_cast(msg->wParam), + reinterpret_cast(msg->lParam)); + ret = true; + } + return ret; +} + +void MainWnd::SwitchToConnectUI() { + ASSERT(IsWindow()); + LayoutPeerListUI(false); + ui_ = CONNECT_TO_SERVER; + LayoutConnectUI(true); + ::SetFocus(edit1_); +} + +void MainWnd::SwitchToPeerList(const Peers& peers) { + remote_video_.reset(); + local_video_.reset(); + + LayoutConnectUI(false); + + ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0); + + AddListBoxItem(listbox_, "List of currently connected peers:", -1); + Peers::const_iterator i = peers.begin(); + for (; i != peers.end(); ++i) + AddListBoxItem(listbox_, i->second.c_str(), i->first); + + ui_ = LIST_PEERS; + LayoutPeerListUI(true); + ::SetFocus(listbox_); +} + +void MainWnd::SwitchToStreamingUI() { + LayoutConnectUI(false); + LayoutPeerListUI(false); + ui_ = STREAMING; +} + +void MainWnd::MessageBox(const char* caption, const char* text, bool is_error) { + DWORD flags = MB_OK; + if (is_error) + flags |= MB_ICONERROR; + + ::MessageBoxA(handle(), text, caption, flags); +} + +cricket::VideoRenderer* MainWnd::local_renderer() { + if (!local_video_.get()) + local_video_.reset(new VideoRenderer(handle(), 1, 1)); + return local_video_.get(); +} + +cricket::VideoRenderer* MainWnd::remote_renderer() { + if (!remote_video_.get()) + remote_video_.reset(new VideoRenderer(handle(), 1, 1)); + return remote_video_.get(); +} + +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); + + RECT rc; + ::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(); + int height = abs(bmi.bmiHeader.biHeight); + int width = bmi.bmiHeader.biWidth; + + const uint8* image = remote_video_->image(); + if (image != NULL) { + HDC dc_mem = ::CreateCompatibleDC(ps.hdc); + ::SetStretchBltMode(dc_mem, HALFTONE); + + // Set the map mode so that the ratio will be maintained for us. + HDC all_dc[] = { ps.hdc, dc_mem }; + for (int i = 0; i < ARRAY_SIZE(all_dc); ++i) { + SetMapMode(all_dc[i], MM_ISOTROPIC); + SetWindowExtEx(all_dc[i], width, height, NULL); + SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL); + } + + HBITMAP bmp_mem = ::CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom); + HGDIOBJ bmp_old = ::SelectObject(dc_mem, bmp_mem); + + POINT logical_area = { rc.right, rc.bottom }; + DPtoLP(ps.hdc, &logical_area, 1); + + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + RECT logical_rect = {0, 0, logical_area.x, logical_area.y }; + ::FillRect(dc_mem, &logical_rect, brush); + ::DeleteObject(brush); + + int max_unit = std::max(width, height); + int x = (logical_area.x / 2) - (width / 2); + int y = (logical_area.y / 2) - (height / 2); + + StretchDIBits(dc_mem, x, y, width, height, + 0, 0, width, height, image, &bmi, DIB_RGB_COLORS, SRCCOPY); + + if ((rc.right - rc.left) > 200 && (rc.bottom - rc.top) > 200) { + const BITMAPINFO& bmi = local_video_->bmi(); + image = local_video_->image(); + int thumb_width = bmi.bmiHeader.biWidth / 4; + int thumb_height = abs(bmi.bmiHeader.biHeight) / 4; + StretchDIBits(dc_mem, + logical_area.x - thumb_width - 10, + logical_area.y - thumb_height - 10, + thumb_width, thumb_height, + 0, 0, bmi.bmiHeader.biWidth, -bmi.bmiHeader.biHeight, + image, &bmi, DIB_RGB_COLORS, SRCCOPY); + } + + BitBlt(ps.hdc, 0, 0, logical_area.x, logical_area.y, + dc_mem, 0, 0, SRCCOPY); + + // Cleanup. + ::SelectObject(dc_mem, bmp_old); + ::DeleteObject(bmp_mem); + ::DeleteDC(dc_mem); + } else { + // We're still waiting for the video stream to be initialized. + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + ::FillRect(ps.hdc, &rc, brush); + ::DeleteObject(brush); + + HGDIOBJ old_font = ::SelectObject(ps.hdc, GetDefaultFont()); + ::SetTextColor(ps.hdc, RGB(0xff, 0xff, 0xff)); + ::SetBkMode(ps.hdc, TRANSPARENT); + + std::string text(kConnecting); + if (!local_video_->image()) { + text += kNoVideoStreams; + } else { + text += kNoIncomingStream; + } + ::DrawTextA(ps.hdc, text.c_str(), -1, &rc, + DT_SINGLELINE | DT_CENTER | DT_VCENTER); + ::SelectObject(ps.hdc, old_font); + } + } else { + HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW)); + ::FillRect(ps.hdc, &rc, brush); + ::DeleteObject(brush); + } + + ::EndPaint(handle(), &ps); +} + +void MainWnd::OnDestroyed() { + PostQuitMessage(0); +} + +void MainWnd::OnDefaultAction() { + if (!callback_) + return; + if (ui_ == CONNECT_TO_SERVER) { + std::string server(GetWindowText(edit1_)); + std::string port_str(GetWindowText(edit2_)); + int port = port_str.length() ? atoi(port_str.c_str()) : 0; + callback_->StartLogin(server, port); + } else if (ui_ == LIST_PEERS) { + LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0); + if (sel != LB_ERR) { + LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0); + if (peer_id != -1 && callback_) { + callback_->ConnectToPeer(peer_id); + } + } + } else { + MessageBoxA(wnd_, "OK!", "Yeah", MB_OK); + } +} + +bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { + switch (msg) { + case WM_ERASEBKGND: + *result = TRUE; + return true; + + case WM_PAINT: + OnPaint(); + return true; + + case WM_SETFOCUS: + if (ui_ == CONNECT_TO_SERVER) { + SetFocus(edit1_); + } else if (ui_ == LIST_PEERS) { + SetFocus(listbox_); + } + return true; + + case WM_SIZE: + if (ui_ == CONNECT_TO_SERVER) { + LayoutConnectUI(true); + } else if (ui_ == LIST_PEERS) { + LayoutPeerListUI(true); + } + break; + + case WM_CTLCOLORSTATIC: + *result = reinterpret_cast(GetSysColorBrush(COLOR_WINDOW)); + return true; + + case WM_COMMAND: + if (button_ == reinterpret_cast(lp)) { + if (BN_CLICKED == HIWORD(wp)) + OnDefaultAction(); + } else if (listbox_ == reinterpret_cast(lp)) { + if (LBN_DBLCLK == HIWORD(wp)) { + OnDefaultAction(); + } + } + return true; + + case WM_CLOSE: + if (callback_) + callback_->Close(); + break; + } + return false; +} + +// static +LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + MainWnd* me = reinterpret_cast( + ::GetWindowLongPtr(hwnd, GWL_USERDATA)); + if (!me && WM_CREATE == msg) { + CREATESTRUCT* cs = reinterpret_cast(lp); + me = reinterpret_cast(cs->lpCreateParams); + me->wnd_ = hwnd; + ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast(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(COLOR_WINDOW + 1); + wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW); + wcex.lpfnWndProc = &WndProc; + wcex.lpszClassName = kClassName; + wnd_class_ = ::RegisterClassEx(&wcex); + ASSERT(wnd_class_ != 0); + return wnd_class_ != 0; +} + +void MainWnd::CreateChildWindow(HWND* wnd, MainWnd::ChildWindowID id, + const wchar_t* class_name, DWORD control_style, + DWORD ex_style) { + if (::IsWindow(*wnd)) + return; + + // Child windows are invisible at first, and shown after being resized. + DWORD style = WS_CHILD | control_style; + *wnd = ::CreateWindowEx(ex_style, class_name, L"", style, + 100, 100, 100, 100, wnd_, + reinterpret_cast(id), + GetModuleHandle(NULL), NULL); + ASSERT(::IsWindow(*wnd) != FALSE); + ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast(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); + InvalidateRect(wnd_, NULL, TRUE); + } +} + +void MainWnd::HandleTabbing() { + bool shift = ((::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0); + UINT next_cmd = shift ? GW_HWNDPREV : GW_HWNDNEXT; + UINT loop_around_cmd = shift ? GW_HWNDLAST : GW_HWNDFIRST; + HWND focus = GetFocus(), next; + do { + next = ::GetWindow(focus, next_cmd); + if (IsWindowVisible(next) && + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { + break; + } + + if (!next) { + next = ::GetWindow(focus, loop_around_cmd); + if (IsWindowVisible(next) && + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { + break; + } + } + focus = next; + } while (true); + ::SetFocus(next); +} + +// +// MainWnd::VideoRenderer +// + +MainWnd::VideoRenderer::VideoRenderer(HWND wnd, int width, int height) + : wnd_(wnd) { + ::InitializeCriticalSection(&buffer_lock_); + ZeroMemory(&bmi_, sizeof(bmi_)); + bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi_.bmiHeader.biPlanes = 1; + bmi_.bmiHeader.biBitCount = 32; + bmi_.bmiHeader.biCompression = BI_RGB; + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = width * height * + (bmi_.bmiHeader.biBitCount >> 3); +} + +MainWnd::VideoRenderer::~VideoRenderer() { + ::DeleteCriticalSection(&buffer_lock_); +} + +bool MainWnd::VideoRenderer::SetSize(int width, int height, int reserved) { + 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; +} + +bool MainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) { + if (!frame) + return false; + + { + 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; +} diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.h b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.h new file mode 100644 index 000000000..02be37160 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/main_wnd.h @@ -0,0 +1,190 @@ +/* + * 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 +#include + +#include "talk/examples/peerconnection_client/peer_connection_client.h" +#include "talk/base/win32.h" +#include "talk/session/phone/mediachannel.h" +#include "talk/session/phone/videocommon.h" +#include "talk/session/phone/videoframe.h" +#include "talk/session/phone/videorenderer.h" + +class MainWndCallback { + public: + 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() {} +}; + +// Pure virtual interface for the main window. +class MainWindow { + public: + virtual ~MainWindow() {} + + enum UI { + CONNECT_TO_SERVER, + LIST_PEERS, + STREAMING, + }; + + virtual void RegisterObserver(MainWndCallback* callback) = 0; + + virtual bool IsWindow() = 0; + virtual void MessageBox(const char* caption, const char* text, + bool is_error) = 0; + + virtual UI current_ui() = 0; + + virtual void SwitchToConnectUI() = 0; + virtual void SwitchToPeerList(const Peers& peers) = 0; + virtual void SwitchToStreamingUI() = 0; + + virtual 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 { + UI_THREAD_CALLBACK = WM_APP + 1, + }; + + MainWnd(); + ~MainWnd(); + + bool Create(); + bool Destroy(); + bool PreTranslateMessage(MSG* msg); + + virtual void RegisterObserver(MainWndCallback* callback); + virtual bool IsWindow(); + virtual void SwitchToConnectUI(); + virtual void SwitchToPeerList(const Peers& peers); + virtual void SwitchToStreamingUI(); + virtual void MessageBox(const char* caption, const char* text, + bool is_error); + virtual UI current_ui() { return ui_; } + + virtual cricket::VideoRenderer* local_renderer(); + virtual cricket::VideoRenderer* remote_renderer(); + + 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); + + const BITMAPINFO& bmi() const { return bmi_; } + const uint8* image() const { return image_.get(); } + + protected: + enum { + SET_SIZE, + RENDER_FRAME, + }; + + 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: + explicit AutoLock(T* obj) : obj_(obj) { obj_->Lock(); } + ~AutoLock() { obj_->Unlock(); } + protected: + T* obj_; + }; + + protected: + enum ChildWindowID { + EDIT_ID = 1, + BUTTON_ID, + LABEL1_ID, + LABEL2_ID, + LISTBOX_ID, + }; + + void OnPaint(); + void OnDestroyed(); + + void OnDefaultAction(); + + bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result); + + static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); + static bool RegisterWindowClass(); + + void CreateChildWindow(HWND* wnd, ChildWindowID id, const wchar_t* class_name, + DWORD control_style, DWORD ex_style); + void CreateChildWindows(); + + void LayoutConnectUI(bool show); + void LayoutPeerListUI(bool show); + + void HandleTabbing(); + + private: + talk_base::scoped_ptr remote_video_; + talk_base::scoped_ptr local_video_; + UI ui_; + HWND wnd_; + DWORD ui_thread_id_; + HWND edit1_; + HWND edit2_; + HWND label1_; + HWND label2_; + HWND button_; + HWND listbox_; + bool destroyed_; + void* nested_msg_; + MainWndCallback* callback_; + static ATOM wnd_class_; +}; +#endif // WIN32 + +#endif // PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_ diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.cc b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.cc new file mode 100644 index 000000000..07e3c7467 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.cc @@ -0,0 +1,479 @@ +/* + * 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 "talk/examples/peerconnection_client/peer_connection_client.h" + +#include "talk/examples/peerconnection_client/defaults.h" +#include "talk/base/common.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), + control_socket_(CreateClientSocket()), + hanging_get_(CreateClientSocket()), + state_(NOT_CONNECTED), + my_id_(-1) { + 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()); + + 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; + + if (port <= 0) + port = kDefaultServerPort; + + server_address_.SetIP(server); + server_address_.SetPort(port); + + if (server_address_.IsUnresolved()) { + 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(); + return false; + } else { + server_address_.SetResolvedIP( + ntohl(*reinterpret_cast(h->h_addr_list[0]))); + talk_base::FreeHostEnt(h); + } + } + + char buffer[1024]; + sprintfn(buffer, sizeof(buffer), + "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name.c_str()); + onconnect_data_ = buffer; + + bool ret = ConnectControlSocket(); + if (ret) + state_ = SIGNING_IN; + + 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]; + sprintfn(headers, sizeof(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::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 (control_socket_->GetState() == talk_base::Socket::CS_CLOSED) { + state_ = SIGNING_OUT; + + 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; + } + + 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()); + size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length()); + ASSERT(sent == onconnect_data_.length()); + UNUSED(sent); + onconnect_data_.clear(); +} + +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 = strlen(buffer); + int sent = socket->Send(buffer, len); + ASSERT(sent == len); + UNUSED2(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 != NULL); + size_t found = data.find(header_pattern); + if (found != std::string::npos && found < eoh) { + *value = atoi(&data[found + strlen(header_pattern)]); + return true; + } + return false; +} + +bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh, + const char* header_pattern, + std::string* value) { + ASSERT(value != NULL); + size_t found = data.find(header_pattern); + if (found != std::string::npos && found < eoh) { + size_t begin = found + strlen(header_pattern); + size_t end = data.find("\r\n", begin); + if (end == std::string::npos) + end = eoh; + value->assign(data.substr(begin, end - begin)); + return true; + } + return false; +} + +bool PeerConnectionClient::ReadIntoBuffer(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"; + if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) { + LOG(INFO) << "Expecting " << *content_length << " bytes."; + size_t total_response_size = (i + 4) + *content_length; + if (data->length() >= total_response_size) { + ret = true; + std::string should_close; + const char kConnection[] = "\r\nConnection: "; + if (GetHeaderValue(*data, i, kConnection, &should_close) && + should_close.compare("close") == 0) { + socket->Close(); + } + } else { + // We haven't received everything. Just continue to accept data. + } + } else { + LOG(LS_ERROR) << "No content length field specified by the server."; + } + } + return ret; +} + +void PeerConnectionClient::OnRead(talk_base::AsyncSocket* socket) { + LOG(INFO) << __FUNCTION__; + size_t content_length = 0; + if (ReadIntoBuffer(socket, &control_data_, &content_length)) { + size_t peer_id = 0, eoh = 0; + bool ok = ParseServerResponse(control_data_, content_length, &peer_id, + &eoh); + if (ok) { + if (my_id_ == -1) { + // First response. Let's store our server assigned ID. + ASSERT(state_ == SIGNING_IN); + my_id_ = peer_id; + ASSERT(my_id_ != -1); + + // The body of the response will be a list of already connected peers. + if (content_length) { + size_t pos = eoh + 4; + while (pos < control_data_.size()) { + size_t eol = control_data_.find('\n', pos); + if (eol == std::string::npos) + break; + int id = 0; + std::string name; + bool connected; + if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id, + &connected) && id != my_id_) { + peers_[id] = name; + callback_->OnPeerConnected(id, name); + } + pos = eol + 1; + } + } + ASSERT(is_connected()); + callback_->OnSignedIn(); + } else if (state_ == SIGNING_OUT) { + Close(); + callback_->OnDisconnected(); + } else if (state_ == SIGNING_OUT_WAITING) { + SignOut(); + } + } + + control_data_.clear(); + + if (state_ == SIGNING_IN) { + ASSERT(hanging_get_->GetState() == talk_base::Socket::CS_CLOSED); + state_ = CONNECTED; + hanging_get_->Connect(server_address_); + } + } +} + +void PeerConnectionClient::OnHangingGetRead(talk_base::AsyncSocket* socket) { + LOG(INFO) << __FUNCTION__; + size_t content_length = 0; + if (ReadIntoBuffer(socket, ¬ification_data_, &content_length)) { + size_t peer_id = 0, eoh = 0; + bool ok = ParseServerResponse(notification_data_, content_length, + &peer_id, &eoh); + + if (ok) { + // Store the position where the body begins. + size_t pos = eoh + 4; + + if (my_id_ == static_cast(peer_id)) { + // A notification about a new member or a member that just + // disconnected. + int id = 0; + std::string name; + bool connected = false; + if (ParseEntry(notification_data_.substr(pos), &name, &id, + &connected)) { + if (connected) { + peers_[id] = name; + callback_->OnPeerConnected(id, name); + } else { + peers_.erase(id); + callback_->OnPeerDisconnected(id); + } + } + } else { + OnMessageFromPeer(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 != NULL); + ASSERT(id != NULL); + ASSERT(connected != NULL); + ASSERT(!entry.empty()); + + *connected = false; + size_t separator = entry.find(','); + if (separator != std::string::npos) { + *id = atoi(&entry[separator + 1]); + name->assign(entry.substr(0, separator)); + separator = entry.find(',', separator + 1); + if (separator != std::string::npos) { + *connected = atoi(&entry[separator + 1]) ? true : false; + } + } + return !name->empty(); +} + +int PeerConnectionClient::GetResponseStatus(const std::string& response) { + int status = -1; + size_t pos = response.find(' '); + if (pos != std::string::npos) + status = atoi(&response[pos + 1]); + return status; +} + +bool PeerConnectionClient::ParseServerResponse(const std::string& response, + size_t content_length, + size_t* peer_id, + size_t* eoh) { + 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(); + +#ifdef WIN32 + if (err != WSAECONNREFUSED) { +#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_); + } + } else { + callback_->OnMessageSent(err); + } + } else { + // Failed to connect to the server. + Close(); + callback_->OnDisconnected(); + } +} diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.h b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.h new file mode 100644 index 000000000..bc02bdd82 --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peer_connection_client.h @@ -0,0 +1,109 @@ +/* + * 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 +#include + +#include "talk/base/sigslot.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/scoped_ptr.h" + +typedef std::map Peers; + +struct PeerConnectionClientObserver { + virtual void OnSignedIn() = 0; // Called when we're logged on. + virtual void OnDisconnected() = 0; + virtual void OnPeerConnected(int id, const std::string& name) = 0; + virtual void OnPeerDisconnected(int peer_id) = 0; + virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0; + virtual void OnMessageSent(int err) = 0; + + 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 SendHangUp(int peer_id); + bool IsSendingMessage(); + + bool SignOut(); + + protected: + void Close(); + 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, + 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 ",," + 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::scoped_ptr control_socket_; + talk_base::scoped_ptr 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_ diff --git a/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peerconnection_client.gyp b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peerconnection_client.gyp new file mode 100644 index 000000000..27cce641e --- /dev/null +++ b/third_party_mods/libjingle/source/talk/examples/peerconnection_client/peerconnection_client.gyp @@ -0,0 +1,55 @@ +# 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. + +{ + 'conditions': [ + ['OS=="linux"', { + 'targets': [ + { + 'target_name': 'peerconnection_client_dev', + 'type': 'executable', + 'sources': [ + 'conductor.cc', + 'conductor.h', + 'defaults.cc', + 'defaults.h', + 'linux/main.cc', + 'linux/main_wnd.cc', + 'linux/main_wnd.h', + 'peer_connection_client.cc', + 'peer_connection_client.h', + ], + 'dependencies': [ + '../../../../libjingle.gyp:libjingle_app', + # TODO(tommi): Switch to this and remove specific gtk dependency + # sections below for cflags and link_settings. + # '<(DEPTH)/build/linux/system.gyp:gtk', + ], + 'include_dirs': [ + '../../../', + #TODO(perkj): Remove when this project is in the correct folder. + '../../../../../../third_party/libjingle/source/', + ], + 'cflags': [ + '