diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/examples/peerconnection | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/examples/peerconnection')
22 files changed, 4689 insertions, 0 deletions
diff --git a/third_party/libwebrtc/examples/peerconnection/OWNERS b/third_party/libwebrtc/examples/peerconnection/OWNERS new file mode 100644 index 0000000000..0fba125734 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/OWNERS @@ -0,0 +1 @@ +tommi@webrtc.org diff --git a/third_party/libwebrtc/examples/peerconnection/client/conductor.cc b/third_party/libwebrtc/examples/peerconnection/client/conductor.cc new file mode 100644 index 0000000000..f94a981a75 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/conductor.cc @@ -0,0 +1,614 @@ +/* + * Copyright 2012 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 "examples/peerconnection/client/conductor.h" + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "api/audio/audio_mixer.h" +#include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/audio_encoder_factory.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/audio_options.h" +#include "api/create_peerconnection_factory.h" +#include "api/rtp_sender_interface.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_decoder_factory_template.h" +#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "api/video_codecs/video_encoder_factory_template.h" +#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h" +#include "examples/peerconnection/client/defaults.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "modules/video_capture/video_capture.h" +#include "modules/video_capture/video_capture_factory.h" +#include "p2p/base/port_allocator.h" +#include "pc/video_track_source.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/rtc_certificate_generator.h" +#include "rtc_base/strings/json.h" +#include "test/vcm_capturer.h" + +namespace { +// Names used for a IceCandidate JSON object. +const char kCandidateSdpMidName[] = "sdpMid"; +const char kCandidateSdpMlineIndexName[] = "sdpMLineIndex"; +const char kCandidateSdpName[] = "candidate"; + +// Names used for a SessionDescription JSON object. +const char kSessionDescriptionTypeName[] = "type"; +const char kSessionDescriptionSdpName[] = "sdp"; + +class DummySetSessionDescriptionObserver + : public webrtc::SetSessionDescriptionObserver { + public: + static rtc::scoped_refptr<DummySetSessionDescriptionObserver> Create() { + return rtc::make_ref_counted<DummySetSessionDescriptionObserver>(); + } + virtual void OnSuccess() { RTC_LOG(LS_INFO) << __FUNCTION__; } + virtual void OnFailure(webrtc::RTCError error) { + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << ToString(error.type()) << ": " + << error.message(); + } +}; + +class CapturerTrackSource : public webrtc::VideoTrackSource { + public: + static rtc::scoped_refptr<CapturerTrackSource> Create() { + const size_t kWidth = 640; + const size_t kHeight = 480; + const size_t kFps = 30; + std::unique_ptr<webrtc::test::VcmCapturer> capturer; + std::unique_ptr<webrtc::VideoCaptureModule::DeviceInfo> info( + webrtc::VideoCaptureFactory::CreateDeviceInfo()); + if (!info) { + return nullptr; + } + int num_devices = info->NumberOfDevices(); + for (int i = 0; i < num_devices; ++i) { + capturer = absl::WrapUnique( + webrtc::test::VcmCapturer::Create(kWidth, kHeight, kFps, i)); + if (capturer) { + return rtc::make_ref_counted<CapturerTrackSource>(std::move(capturer)); + } + } + + return nullptr; + } + + protected: + explicit CapturerTrackSource( + std::unique_ptr<webrtc::test::VcmCapturer> capturer) + : VideoTrackSource(/*remote=*/false), capturer_(std::move(capturer)) {} + + private: + rtc::VideoSourceInterface<webrtc::VideoFrame>* source() override { + return capturer_.get(); + } + std::unique_ptr<webrtc::test::VcmCapturer> capturer_; +}; + +} // namespace + +Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd) + : peer_id_(-1), loopback_(false), client_(client), main_wnd_(main_wnd) { + client_->RegisterObserver(this); + main_wnd->RegisterObserver(this); +} + +Conductor::~Conductor() { + RTC_DCHECK(!peer_connection_); +} + +bool Conductor::connection_active() const { + return peer_connection_ != nullptr; +} + +void Conductor::Close() { + client_->SignOut(); + DeletePeerConnection(); +} + +bool Conductor::InitializePeerConnection() { + RTC_DCHECK(!peer_connection_factory_); + RTC_DCHECK(!peer_connection_); + + if (!signaling_thread_.get()) { + signaling_thread_ = rtc::Thread::CreateWithSocketServer(); + signaling_thread_->Start(); + } + peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( + nullptr /* network_thread */, nullptr /* worker_thread */, + signaling_thread_.get(), nullptr /* default_adm */, + webrtc::CreateBuiltinAudioEncoderFactory(), + webrtc::CreateBuiltinAudioDecoderFactory(), + std::make_unique<webrtc::VideoEncoderFactoryTemplate< + webrtc::LibvpxVp8EncoderTemplateAdapter, + webrtc::LibvpxVp9EncoderTemplateAdapter, + webrtc::OpenH264EncoderTemplateAdapter, + webrtc::LibaomAv1EncoderTemplateAdapter>>(), + std::make_unique<webrtc::VideoDecoderFactoryTemplate< + webrtc::LibvpxVp8DecoderTemplateAdapter, + webrtc::LibvpxVp9DecoderTemplateAdapter, + webrtc::OpenH264DecoderTemplateAdapter, + webrtc::Dav1dDecoderTemplateAdapter>>(), + nullptr /* audio_mixer */, nullptr /* audio_processing */); + + if (!peer_connection_factory_) { + main_wnd_->MessageBox("Error", "Failed to initialize PeerConnectionFactory", + true); + DeletePeerConnection(); + return false; + } + + if (!CreatePeerConnection()) { + main_wnd_->MessageBox("Error", "CreatePeerConnection failed", true); + DeletePeerConnection(); + } + + AddTracks(); + + return peer_connection_ != nullptr; +} + +bool Conductor::ReinitializePeerConnectionForLoopback() { + loopback_ = true; + std::vector<rtc::scoped_refptr<webrtc::RtpSenderInterface>> senders = + peer_connection_->GetSenders(); + peer_connection_ = nullptr; + // Loopback is only possible if encryption is disabled. + webrtc::PeerConnectionFactoryInterface::Options options; + options.disable_encryption = true; + peer_connection_factory_->SetOptions(options); + if (CreatePeerConnection()) { + for (const auto& sender : senders) { + peer_connection_->AddTrack(sender->track(), sender->stream_ids()); + } + peer_connection_->CreateOffer( + this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); + } + options.disable_encryption = false; + peer_connection_factory_->SetOptions(options); + return peer_connection_ != nullptr; +} + +bool Conductor::CreatePeerConnection() { + RTC_DCHECK(peer_connection_factory_); + RTC_DCHECK(!peer_connection_); + + webrtc::PeerConnectionInterface::RTCConfiguration config; + config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; + webrtc::PeerConnectionInterface::IceServer server; + server.uri = GetPeerConnectionString(); + config.servers.push_back(server); + + webrtc::PeerConnectionDependencies pc_dependencies(this); + auto error_or_peer_connection = + peer_connection_factory_->CreatePeerConnectionOrError( + config, std::move(pc_dependencies)); + if (error_or_peer_connection.ok()) { + peer_connection_ = std::move(error_or_peer_connection.value()); + } + return peer_connection_ != nullptr; +} + +void Conductor::DeletePeerConnection() { + main_wnd_->StopLocalRenderer(); + main_wnd_->StopRemoteRenderer(); + peer_connection_ = nullptr; + peer_connection_factory_ = nullptr; + peer_id_ = -1; + loopback_ = false; +} + +void Conductor::EnsureStreamingUI() { + RTC_DCHECK(peer_connection_); + if (main_wnd_->IsWindow()) { + if (main_wnd_->current_ui() != MainWindow::STREAMING) + main_wnd_->SwitchToStreamingUI(); + } +} + +// +// PeerConnectionObserver implementation. +// + +void Conductor::OnAddTrack( + rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, + const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>& + streams) { + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id(); + main_wnd_->QueueUIThreadCallback(NEW_TRACK_ADDED, + receiver->track().release()); +} + +void Conductor::OnRemoveTrack( + rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) { + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id(); + main_wnd_->QueueUIThreadCallback(TRACK_REMOVED, receiver->track().release()); +} + +void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) { + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index(); + // For loopback test. To save some connecting delay. + if (loopback_) { + if (!peer_connection_->AddIceCandidate(candidate)) { + RTC_LOG(LS_WARNING) << "Failed to apply the received candidate"; + } + return; + } + + Json::Value jmessage; + jmessage[kCandidateSdpMidName] = candidate->sdp_mid(); + jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index(); + std::string sdp; + if (!candidate->ToString(&sdp)) { + RTC_LOG(LS_ERROR) << "Failed to serialize candidate"; + return; + } + jmessage[kCandidateSdpName] = sdp; + + Json::StreamWriterBuilder factory; + SendMessage(Json::writeString(factory, jmessage)); +} + +// +// PeerConnectionClientObserver implementation. +// + +void Conductor::OnSignedIn() { + RTC_LOG(LS_INFO) << __FUNCTION__; + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnDisconnected() { + RTC_LOG(LS_INFO) << __FUNCTION__; + + DeletePeerConnection(); + + if (main_wnd_->IsWindow()) + main_wnd_->SwitchToConnectUI(); +} + +void Conductor::OnPeerConnected(int id, const std::string& name) { + RTC_LOG(LS_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) { + RTC_LOG(LS_INFO) << __FUNCTION__; + if (id == peer_id_) { + RTC_LOG(LS_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) { + RTC_DCHECK(peer_id_ == peer_id || peer_id_ == -1); + RTC_DCHECK(!message.empty()); + + if (!peer_connection_.get()) { + RTC_DCHECK(peer_id_ == -1); + peer_id_ = peer_id; + + if (!InitializePeerConnection()) { + RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance"; + client_->SignOut(); + return; + } + } else if (peer_id != peer_id_) { + RTC_DCHECK(peer_id_ != -1); + RTC_LOG(LS_WARNING) + << "Received a message from unknown peer while already in a " + "conversation with a different peer."; + return; + } + + Json::CharReaderBuilder factory; + std::unique_ptr<Json::CharReader> reader = + absl::WrapUnique(factory.newCharReader()); + Json::Value jmessage; + if (!reader->parse(message.data(), message.data() + message.length(), + &jmessage, nullptr)) { + RTC_LOG(LS_WARNING) << "Received unknown message. " << message; + return; + } + std::string type_str; + std::string json_object; + + rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName, + &type_str); + if (!type_str.empty()) { + if (type_str == "offer-loopback") { + // This is a loopback call. + // Recreate the peerconnection with DTLS disabled. + if (!ReinitializePeerConnectionForLoopback()) { + RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance"; + DeletePeerConnection(); + client_->SignOut(); + } + return; + } + absl::optional<webrtc::SdpType> type_maybe = + webrtc::SdpTypeFromString(type_str); + if (!type_maybe) { + RTC_LOG(LS_ERROR) << "Unknown SDP type: " << type_str; + return; + } + webrtc::SdpType type = *type_maybe; + std::string sdp; + if (!rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName, + &sdp)) { + RTC_LOG(LS_WARNING) + << "Can't parse received session description message."; + return; + } + webrtc::SdpParseError error; + std::unique_ptr<webrtc::SessionDescriptionInterface> session_description = + webrtc::CreateSessionDescription(type, sdp, &error); + if (!session_description) { + RTC_LOG(LS_WARNING) + << "Can't parse received session description message. " + "SdpParseError was: " + << error.description; + return; + } + RTC_LOG(LS_INFO) << " Received session description :" << message; + peer_connection_->SetRemoteDescription( + DummySetSessionDescriptionObserver::Create().get(), + session_description.release()); + if (type == webrtc::SdpType::kOffer) { + peer_connection_->CreateAnswer( + this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); + } + } else { + std::string sdp_mid; + int sdp_mlineindex = 0; + std::string sdp; + if (!rtc::GetStringFromJsonObject(jmessage, kCandidateSdpMidName, + &sdp_mid) || + !rtc::GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName, + &sdp_mlineindex) || + !rtc::GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) { + RTC_LOG(LS_WARNING) << "Can't parse received message."; + return; + } + webrtc::SdpParseError error; + std::unique_ptr<webrtc::IceCandidateInterface> candidate( + webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error)); + if (!candidate.get()) { + RTC_LOG(LS_WARNING) << "Can't parse received candidate message. " + "SdpParseError was: " + << error.description; + return; + } + if (!peer_connection_->AddIceCandidate(candidate.get())) { + RTC_LOG(LS_WARNING) << "Failed to apply the received candidate"; + return; + } + RTC_LOG(LS_INFO) << " Received candidate :" << message; + } +} + +void Conductor::OnMessageSent(int err) { + // Process the next pending message if any. + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL); +} + +void Conductor::OnServerConnectionFailure() { + main_wnd_->MessageBox("Error", ("Failed to connect to " + server_).c_str(), + true); +} + +// +// MainWndCallback implementation. +// + +void Conductor::StartLogin(const std::string& server, int port) { + if (client_->is_connected()) + return; + server_ = server; + client_->Connect(server, port, GetPeerName()); +} + +void Conductor::DisconnectFromServer() { + if (client_->is_connected()) + client_->SignOut(); +} + +void Conductor::ConnectToPeer(int peer_id) { + RTC_DCHECK(peer_id_ == -1); + RTC_DCHECK(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; + peer_connection_->CreateOffer( + this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); + } else { + main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true); + } +} + +void Conductor::AddTracks() { + if (!peer_connection_->GetSenders().empty()) { + return; // Already added tracks. + } + + rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track( + peer_connection_factory_->CreateAudioTrack( + kAudioLabel, + peer_connection_factory_->CreateAudioSource(cricket::AudioOptions()) + .get())); + auto result_or_error = peer_connection_->AddTrack(audio_track, {kStreamId}); + if (!result_or_error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add audio track to PeerConnection: " + << result_or_error.error().message(); + } + + rtc::scoped_refptr<CapturerTrackSource> video_device = + CapturerTrackSource::Create(); + if (video_device) { + rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track_( + peer_connection_factory_->CreateVideoTrack(video_device, kVideoLabel)); + main_wnd_->StartLocalRenderer(video_track_.get()); + + result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId}); + if (!result_or_error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add video track to PeerConnection: " + << result_or_error.error().message(); + } + } else { + RTC_LOG(LS_ERROR) << "OpenVideoCaptureDevice failed"; + } + + main_wnd_->SwitchToStreamingUI(); +} + +void Conductor::DisconnectFromCurrentPeer() { + RTC_LOG(LS_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: + RTC_LOG(LS_INFO) << "PEER_CONNECTION_CLOSED"; + DeletePeerConnection(); + + 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: { + RTC_LOG(LS_INFO) << "SEND_MESSAGE_TO_PEER"; + std::string* msg = reinterpret_cast<std::string*>(data); + if (msg) { + // For convenience, we always run the message through the queue. + // This way we can be sure that messages are sent to the server + // in the same order they were signaled without much hassle. + pending_messages_.push_back(msg); + } + + if (!pending_messages_.empty() && !client_->IsSendingMessage()) { + msg = pending_messages_.front(); + pending_messages_.pop_front(); + + if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) { + RTC_LOG(LS_ERROR) << "SendToPeer failed"; + DisconnectFromServer(); + } + delete msg; + } + + if (!peer_connection_.get()) + peer_id_ = -1; + + break; + } + + case NEW_TRACK_ADDED: { + auto* track = reinterpret_cast<webrtc::MediaStreamTrackInterface*>(data); + if (track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) { + auto* video_track = static_cast<webrtc::VideoTrackInterface*>(track); + main_wnd_->StartRemoteRenderer(video_track); + } + track->Release(); + break; + } + + case TRACK_REMOVED: { + // Remote peer stopped sending a track. + auto* track = reinterpret_cast<webrtc::MediaStreamTrackInterface*>(data); + track->Release(); + break; + } + + default: + RTC_DCHECK_NOTREACHED(); + break; + } +} + +void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) { + peer_connection_->SetLocalDescription( + DummySetSessionDescriptionObserver::Create().get(), desc); + + std::string sdp; + desc->ToString(&sdp); + + // For loopback test. To save some connecting delay. + if (loopback_) { + // Replace message type from "offer" to "answer" + std::unique_ptr<webrtc::SessionDescriptionInterface> session_description = + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp); + peer_connection_->SetRemoteDescription( + DummySetSessionDescriptionObserver::Create().get(), + session_description.release()); + return; + } + + Json::Value jmessage; + jmessage[kSessionDescriptionTypeName] = + webrtc::SdpTypeToString(desc->GetType()); + jmessage[kSessionDescriptionSdpName] = sdp; + + Json::StreamWriterBuilder factory; + SendMessage(Json::writeString(factory, jmessage)); +} + +void Conductor::OnFailure(webrtc::RTCError error) { + RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message(); +} + +void Conductor::SendMessage(const std::string& json_object) { + std::string* msg = new std::string(json_object); + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg); +} diff --git a/third_party/libwebrtc/examples/peerconnection/client/conductor.h b/third_party/libwebrtc/examples/peerconnection/client/conductor.h new file mode 100644 index 0000000000..80617d3cf4 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/conductor.h @@ -0,0 +1,136 @@ +/* + * Copyright 2012 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 EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_ + +#include <deque> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "api/media_stream_interface.h" +#include "api/peer_connection_interface.h" +#include "examples/peerconnection/client/main_wnd.h" +#include "examples/peerconnection/client/peer_connection_client.h" +#include "rtc_base/thread.h" + +namespace webrtc { +class VideoCaptureModule; +} // namespace webrtc + +namespace cricket { +class VideoRenderer; +} // namespace cricket + +class Conductor : public webrtc::PeerConnectionObserver, + public webrtc::CreateSessionDescriptionObserver, + public PeerConnectionClientObserver, + public MainWndCallback { + public: + enum CallbackID { + MEDIA_CHANNELS_INITIALIZED = 1, + PEER_CONNECTION_CLOSED, + SEND_MESSAGE_TO_PEER, + NEW_TRACK_ADDED, + TRACK_REMOVED, + }; + + Conductor(PeerConnectionClient* client, MainWindow* main_wnd); + + bool connection_active() const; + + void Close() override; + + protected: + ~Conductor(); + bool InitializePeerConnection(); + bool ReinitializePeerConnectionForLoopback(); + bool CreatePeerConnection(); + void DeletePeerConnection(); + void EnsureStreamingUI(); + void AddTracks(); + + // + // PeerConnectionObserver implementation. + // + + void OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) override {} + void OnAddTrack( + rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, + const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>& + streams) override; + void OnRemoveTrack( + rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override; + void OnDataChannel( + rtc::scoped_refptr<webrtc::DataChannelInterface> channel) override {} + void OnRenegotiationNeeded() override {} + void OnIceConnectionChange( + webrtc::PeerConnectionInterface::IceConnectionState new_state) override {} + void OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) override {} + void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override; + void OnIceConnectionReceivingChange(bool receiving) override {} + + // + // PeerConnectionClientObserver implementation. + // + + void OnSignedIn() override; + + void OnDisconnected() override; + + void OnPeerConnected(int id, const std::string& name) override; + + void OnPeerDisconnected(int id) override; + + void OnMessageFromPeer(int peer_id, const std::string& message) override; + + void OnMessageSent(int err) override; + + void OnServerConnectionFailure() override; + + // + // MainWndCallback implementation. + // + + void StartLogin(const std::string& server, int port) override; + + void DisconnectFromServer() override; + + void ConnectToPeer(int peer_id) override; + + void DisconnectFromCurrentPeer() override; + + void UIThreadCallback(int msg_id, void* data) override; + + // CreateSessionDescriptionObserver implementation. + void OnSuccess(webrtc::SessionDescriptionInterface* desc) override; + void OnFailure(webrtc::RTCError error) override; + + protected: + // Send a message to the remote peer. + void SendMessage(const std::string& json_object); + + int peer_id_; + bool loopback_; + std::unique_ptr<rtc::Thread> signaling_thread_; + rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_; + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> + peer_connection_factory_; + PeerConnectionClient* client_; + MainWindow* main_wnd_; + std::deque<std::string*> pending_messages_; + std::string server_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/client/defaults.cc b/third_party/libwebrtc/examples/peerconnection/client/defaults.cc new file mode 100644 index 0000000000..ee3a9e1f0a --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/defaults.cc @@ -0,0 +1,59 @@ +/* + * Copyright 2012 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 "examples/peerconnection/client/defaults.h" + +#include <stdlib.h> + +#ifdef WIN32 +#include <winsock2.h> +#else +#include <unistd.h> +#endif + +#include "rtc_base/arraysize.h" + +const char kAudioLabel[] = "audio_label"; +const char kVideoLabel[] = "video_label"; +const char kStreamId[] = "stream_id"; +const uint16_t 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]; + std::string ret(GetEnvVarOrDefault("USERNAME", "user")); + ret += '@'; + if (gethostname(computer_name, arraysize(computer_name)) == 0) { + ret += computer_name; + } else { + ret += "host"; + } + return ret; +} diff --git a/third_party/libwebrtc/examples/peerconnection/client/defaults.h b/third_party/libwebrtc/examples/peerconnection/client/defaults.h new file mode 100644 index 0000000000..30936fd9d4 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/defaults.h @@ -0,0 +1,29 @@ +/* + * Copyright 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 EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_ + +#include <stdint.h> + +#include <string> + +extern const char kAudioLabel[]; +extern const char kVideoLabel[]; +extern const char kStreamId[]; +extern const uint16_t kDefaultServerPort; + +std::string GetEnvVarOrDefault(const char* env_var_name, + const char* default_value); +std::string GetPeerConnectionString(); +std::string GetDefaultServerName(); +std::string GetPeerName(); + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/client/flag_defs.h b/third_party/libwebrtc/examples/peerconnection/client/flag_defs.h new file mode 100644 index 0000000000..986daf64ce --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/flag_defs.h @@ -0,0 +1,52 @@ +/* + * Copyright 2012 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 EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_ + +#include <string> + +#include "absl/flags/flag.h" + +extern const uint16_t kDefaultServerPort; // From defaults.[h|cc] + +// Define flags for the peerconnect_client testing tool, in a separate +// header file so that they can be shared across the different main.cc's +// for each platform. + +ABSL_FLAG(bool, + autoconnect, + false, + "Connect to the server without user " + "intervention."); +ABSL_FLAG(std::string, server, "localhost", "The server to connect to."); +ABSL_FLAG(int, + port, + kDefaultServerPort, + "The port on which the server is listening."); +ABSL_FLAG( + bool, + autocall, + false, + "Call the first available other client on " + "the server without user intervention. Note: this flag should only be set " + "to true on one of the two clients."); + +ABSL_FLAG( + std::string, + force_fieldtrials, + "", + "Field trials control experimental features. This flag specifies the field " + "trials in effect. E.g. running with " + "--force_fieldtrials=WebRTC-FooFeature/Enabled/ " + "will assign the group Enabled to field trial WebRTC-FooFeature. Multiple " + "trials are separated by \"/\""); + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/client/linux/main.cc b/third_party/libwebrtc/examples/peerconnection/client/linux/main.cc new file mode 100644 index 0000000000..ad3d671073 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/linux/main.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2012 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 <glib.h> +#include <gtk/gtk.h> +#include <stdio.h> + +#include "absl/flags/parse.h" +#include "api/scoped_refptr.h" +#include "examples/peerconnection/client/conductor.h" +#include "examples/peerconnection/client/flag_defs.h" +#include "examples/peerconnection/client/linux/main_wnd.h" +#include "examples/peerconnection/client/peer_connection_client.h" +#include "rtc_base/physical_socket_server.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/thread.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" + +class CustomSocketServer : public rtc::PhysicalSocketServer { + public: + explicit CustomSocketServer(GtkMainWnd* wnd) + : wnd_(wnd), conductor_(NULL), client_(NULL) {} + virtual ~CustomSocketServer() {} + + void SetMessageQueue(rtc::Thread* queue) override { message_queue_ = queue; } + + 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. + // This function never waits. + bool Wait(webrtc::TimeDelta max_wait_duration, bool process_io) override { + // Pump GTK events. + // TODO(henrike): 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()) { + message_queue_->Quit(); + } + return rtc::PhysicalSocketServer::Wait(webrtc::TimeDelta::Zero(), + process_io); + } + + protected: + rtc::Thread* message_queue_; + GtkMainWnd* wnd_; + Conductor* conductor_; + PeerConnectionClient* client_; +}; + +int main(int argc, char* argv[]) { + gtk_init(&argc, &argv); +// g_type_init API is deprecated (and does nothing) since glib 2.35.0, see: +// https://mail.gnome.org/archives/commits-list/2012-November/msg07809.html +#if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); +#endif +// g_thread_init API is deprecated since glib 2.31.0, see release note: +// http://mail.gnome.org/archives/gnome-announce-list/2011-October/msg00041.html +#if !GLIB_CHECK_VERSION(2, 31, 0) + g_thread_init(NULL); +#endif + + absl::ParseCommandLine(argc, argv); + + // InitFieldTrialsFromString stores the char*, so the char array must outlive + // the application. + const std::string forced_field_trials = + absl::GetFlag(FLAGS_force_fieldtrials); + webrtc::field_trial::InitFieldTrialsFromString(forced_field_trials.c_str()); + + // Abort if the user specifies a port that is outside the allowed + // range [1, 65535]. + if ((absl::GetFlag(FLAGS_port) < 1) || (absl::GetFlag(FLAGS_port) > 65535)) { + printf("Error: %i is not a valid port.\n", absl::GetFlag(FLAGS_port)); + return -1; + } + + const std::string server = absl::GetFlag(FLAGS_server); + GtkMainWnd wnd(server.c_str(), absl::GetFlag(FLAGS_port), + absl::GetFlag(FLAGS_autoconnect), + absl::GetFlag(FLAGS_autocall)); + wnd.Create(); + + CustomSocketServer socket_server(&wnd); + rtc::AutoSocketServerThread thread(&socket_server); + + rtc::InitializeSSL(); + // Must be constructed after we set the socketserver. + PeerConnectionClient client; + auto conductor = rtc::make_ref_counted<Conductor>(&client, &wnd); + socket_server.set_client(&client); + socket_server.set_conductor(conductor.get()); + + thread.Run(); + + // gtk_main(); + wnd.Destroy(); + + // TODO(henrike): Run the Gtk main loop to tear down the connection. + /* + while (gtk_events_pending()) { + gtk_main_iteration(); + } + */ + rtc::CleanupSSL(); + return 0; +} diff --git a/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.cc b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.cc new file mode 100644 index 0000000000..2be75d8f8d --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.cc @@ -0,0 +1,545 @@ +/* + * Copyright 2012 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 "examples/peerconnection/client/linux/main_wnd.h" + +#include <cairo.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <glib-object.h> +#include <glib.h> +#include <gobject/gclosure.h> +#include <gtk/gtk.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <cstdint> +#include <map> +#include <utility> + +#include "api/video/i420_buffer.h" +#include "api/video/video_frame_buffer.h" +#include "api/video/video_rotation.h" +#include "api/video/video_source_interface.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "third_party/libyuv/include/libyuv/convert.h" +#include "third_party/libyuv/include/libyuv/convert_from.h" + +namespace { + +// +// Simple static functions that simply forward the callback to the +// GtkMainWnd instance. +// + +gboolean OnDestroyedCallback(GtkWidget* widget, + GdkEvent* event, + gpointer data) { + reinterpret_cast<GtkMainWnd*>(data)->OnDestroyed(widget, event); + return FALSE; +} + +void OnClickedCallback(GtkWidget* widget, gpointer data) { + reinterpret_cast<GtkMainWnd*>(data)->OnClicked(widget); +} + +gboolean SimulateButtonClick(gpointer button) { + g_signal_emit_by_name(button, "clicked"); + return false; +} + +gboolean OnKeyPressCallback(GtkWidget* widget, + GdkEventKey* key, + gpointer data) { + reinterpret_cast<GtkMainWnd*>(data)->OnKeyPress(widget, key); + return false; +} + +void OnRowActivatedCallback(GtkTreeView* tree_view, + GtkTreePath* path, + GtkTreeViewColumn* column, + gpointer data) { + reinterpret_cast<GtkMainWnd*>(data)->OnRowActivated(tree_view, path, column); +} + +gboolean SimulateLastRowActivated(gpointer data) { + GtkTreeView* tree_view = reinterpret_cast<GtkTreeView*>(data); + GtkTreeModel* model = gtk_tree_view_get_model(tree_view); + + // "if iter is NULL, then the number of toplevel nodes is returned." + int rows = gtk_tree_model_iter_n_children(model, NULL); + GtkTreePath* lastpath = gtk_tree_path_new_from_indices(rows - 1, -1); + + // Select the last item in the list + GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view); + gtk_tree_selection_select_path(selection, lastpath); + + // Our TreeView only has one column, so it is column 0. + GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view, 0); + + gtk_tree_view_row_activated(tree_view, lastpath, column); + + gtk_tree_path_free(lastpath); + return false; +} + +// 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<UIThreadCallbackData*>(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<GtkMainWnd*>(data); + wnd->OnRedraw(); + return false; +} + +gboolean Draw(GtkWidget* widget, cairo_t* cr, gpointer data) { + GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data); + wnd->Draw(widget, cr); + return false; +} + +} // namespace + +// +// GtkMainWnd implementation. +// + +GtkMainWnd::GtkMainWnd(const char* server, + int port, + bool autoconnect, + bool autocall) + : window_(NULL), + draw_area_(NULL), + vbox_(NULL), + server_edit_(NULL), + port_edit_(NULL), + peer_list_(NULL), + callback_(NULL), + server_(server), + autoconnect_(autoconnect), + autocall_(autocall) { + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%i", port); + port_ = buffer; +} + +GtkMainWnd::~GtkMainWnd() { + RTC_DCHECK(!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; +} + +void GtkMainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) { + local_renderer_.reset(new VideoRenderer(this, local_video)); +} + +void GtkMainWnd::StopLocalRenderer() { + local_renderer_.reset(); +} + +void GtkMainWnd::StartRemoteRenderer( + webrtc::VideoTrackInterface* remote_video) { + remote_renderer_.reset(new VideoRenderer(this, remote_video)); +} + +void GtkMainWnd::StopRemoteRenderer() { + remote_renderer_.reset(); +} + +void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) { + g_idle_add(HandleUIThreadCallback, + new UIThreadCallbackData(callback_, msg_id, data)); +} + +bool GtkMainWnd::Create() { + RTC_DCHECK(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() { + RTC_LOG(LS_INFO) << __FUNCTION__; + + RTC_DCHECK(IsWindow()); + RTC_DCHECK(vbox_ == NULL); + + gtk_container_set_border_width(GTK_CONTAINER(window_), 10); + + if (peer_list_) { + gtk_widget_destroy(peer_list_); + peer_list_ = NULL; + } + + vbox_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 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_box_new(GTK_ORIENTATION_HORIZONTAL, 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_); + + if (autoconnect_) + g_idle_add(SimulateButtonClick, button); +} + +void GtkMainWnd::SwitchToPeerList(const Peers& peers) { + RTC_LOG(LS_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); + } + + 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); + + if (autocall_ && peers.begin() != peers.end()) + g_idle_add(SimulateLastRowActivated, peer_list_); +} + +void GtkMainWnd::SwitchToStreamingUI() { + RTC_LOG(LS_INFO) << __FUNCTION__; + + RTC_DCHECK(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_); + g_signal_connect(G_OBJECT(draw_area_), "draw", G_CALLBACK(&::Draw), this); + + 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) { + // Make the connect button insensitive, so that it cannot be clicked more than + // once. Now that the connection includes auto-retry, it should not be + // necessary to click it more than once. + gtk_widget_set_sensitive(widget, false); + 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_KEY_Escape: + if (draw_area_) { + callback_->DisconnectFromCurrentPeer(); + } else if (peer_list_) { + callback_->DisconnectFromServer(); + } + break; + + case GDK_KEY_KP_Enter: + case GDK_KEY_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) { + RTC_DCHECK(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(); + + VideoRenderer* remote_renderer = remote_renderer_.get(); + if (remote_renderer && remote_renderer->image() != NULL && + draw_area_ != NULL) { + width_ = remote_renderer->width(); + height_ = remote_renderer->height(); + + if (!draw_buffer_.get()) { + draw_buffer_size_ = (width_ * height_ * 4) * 4; + draw_buffer_.reset(new uint8_t[draw_buffer_size_]); + gtk_widget_set_size_request(draw_area_, width_ * 2, height_ * 2); + } + + const uint32_t* image = + reinterpret_cast<const uint32_t*>(remote_renderer->image()); + uint32_t* scaled = reinterpret_cast<uint32_t*>(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_t* prev_line = scaled; + scaled += width_ * 2; + memcpy(scaled, prev_line, (width_ * 2) * 4); + + image += width_; + scaled += width_ * 2; + } + + VideoRenderer* local_renderer = local_renderer_.get(); + if (local_renderer && local_renderer->image()) { + image = reinterpret_cast<const uint32_t*>(local_renderer->image()); + scaled = reinterpret_cast<uint32_t*>(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; + } + } + + gtk_widget_queue_draw(draw_area_); + } + + gdk_threads_leave(); +} + +void GtkMainWnd::Draw(GtkWidget* widget, cairo_t* cr) { + cairo_format_t format = CAIRO_FORMAT_ARGB32; + cairo_surface_t* surface = cairo_image_surface_create_for_data( + draw_buffer_.get(), format, width_ * 2, height_ * 2, + cairo_format_stride_for_width(format, width_ * 2)); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_rectangle(cr, 0, 0, width_ * 2, height_ * 2); + cairo_fill(cr); + cairo_surface_destroy(surface); +} + +GtkMainWnd::VideoRenderer::VideoRenderer( + GtkMainWnd* main_wnd, + webrtc::VideoTrackInterface* track_to_render) + : width_(0), + height_(0), + main_wnd_(main_wnd), + rendered_track_(track_to_render) { + rendered_track_->AddOrUpdateSink(this, rtc::VideoSinkWants()); +} + +GtkMainWnd::VideoRenderer::~VideoRenderer() { + rendered_track_->RemoveSink(this); +} + +void GtkMainWnd::VideoRenderer::SetSize(int width, int height) { + gdk_threads_enter(); + + if (width_ == width && height_ == height) { + return; + } + + width_ = width; + height_ = height; + image_.reset(new uint8_t[width * height * 4]); + gdk_threads_leave(); +} + +void GtkMainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) { + gdk_threads_enter(); + + rtc::scoped_refptr<webrtc::I420BufferInterface> buffer( + video_frame.video_frame_buffer()->ToI420()); + if (video_frame.rotation() != webrtc::kVideoRotation_0) { + buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation()); + } + SetSize(buffer->width(), buffer->height()); + + // TODO(bugs.webrtc.org/6857): This conversion is correct for little-endian + // only. Cairo ARGB32 treats pixels as 32-bit values in *native* byte order, + // with B in the least significant byte of the 32-bit value. Which on + // little-endian means that memory layout is BGRA, with the B byte stored at + // lowest address. Libyuv's ARGB format (surprisingly?) uses the same + // little-endian format, with B in the first byte in memory, regardless of + // native endianness. + libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(), + buffer->StrideU(), buffer->DataV(), buffer->StrideV(), + image_.get(), width_ * 4, buffer->width(), + buffer->height()); + + gdk_threads_leave(); + + g_idle_add(Redraw, main_wnd_); +} diff --git a/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.h b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.h new file mode 100644 index 0000000000..3b31e1be3b --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/linux/main_wnd.h @@ -0,0 +1,128 @@ +/* + * Copyright 2012 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 EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_ + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "api/media_stream_interface.h" +#include "api/scoped_refptr.h" +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" +#include "examples/peerconnection/client/main_wnd.h" +#include "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; +typedef struct _cairo cairo_t; + +// 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(const char* server, int port, bool autoconnect, bool autocall); + ~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 void StartLocalRenderer(webrtc::VideoTrackInterface* local_video); + virtual void StopLocalRenderer(); + virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video); + virtual void StopRemoteRenderer(); + + 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(); + + void Draw(GtkWidget* widget, cairo_t* cr); + + protected: + class VideoRenderer : public rtc::VideoSinkInterface<webrtc::VideoFrame> { + public: + VideoRenderer(GtkMainWnd* main_wnd, + webrtc::VideoTrackInterface* track_to_render); + virtual ~VideoRenderer(); + + // VideoSinkInterface implementation + void OnFrame(const webrtc::VideoFrame& frame) override; + + const uint8_t* image() const { return image_.get(); } + + int width() const { return width_; } + + int height() const { return height_; } + + protected: + void SetSize(int width, int height); + std::unique_ptr<uint8_t[]> image_; + int width_; + int height_; + GtkMainWnd* main_wnd_; + rtc::scoped_refptr<webrtc::VideoTrackInterface> rendered_track_; + }; + + 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_; + bool autoconnect_; + bool autocall_; + std::unique_ptr<VideoRenderer> local_renderer_; + std::unique_ptr<VideoRenderer> remote_renderer_; + int width_; + int height_; + std::unique_ptr<uint8_t[]> draw_buffer_; + int draw_buffer_size_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/client/main.cc b/third_party/libwebrtc/examples/peerconnection/client/main.cc new file mode 100644 index 0000000000..32bc52bda4 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/main.cc @@ -0,0 +1,133 @@ +/* + * Copyright 2012 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. + */ + +// clang-format off +// clang formating would change include order. +#include <windows.h> +#include <shellapi.h> // must come after windows.h +// clang-format on + +#include <string> +#include <vector> + +#include "absl/flags/parse.h" +#include "examples/peerconnection/client/conductor.h" +#include "examples/peerconnection/client/flag_defs.h" +#include "examples/peerconnection/client/main_wnd.h" +#include "examples/peerconnection/client/peer_connection_client.h" +#include "rtc_base/checks.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/string_utils.h" // For ToUtf8 +#include "rtc_base/win32_socket_init.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" + +namespace { +// A helper class to translate Windows command line arguments into UTF8, +// which then allows us to just pass them to the flags system. +// This encapsulates all the work of getting the command line and translating +// it to an array of 8-bit strings; all you have to do is create one of these, +// and then call argc() and argv(). +class WindowsCommandLineArguments { + public: + WindowsCommandLineArguments(); + + WindowsCommandLineArguments(const WindowsCommandLineArguments&) = delete; + WindowsCommandLineArguments& operator=(WindowsCommandLineArguments&) = delete; + + int argc() { return argv_.size(); } + char** argv() { return argv_.data(); } + + private: + // Owned argument strings. + std::vector<std::string> args_; + // Pointers, to get layout compatible with char** argv. + std::vector<char*> argv_; +}; + +WindowsCommandLineArguments::WindowsCommandLineArguments() { + // start by getting the command line. + LPCWSTR command_line = ::GetCommandLineW(); + // now, convert it to a list of wide char strings. + int argc; + LPWSTR* wide_argv = ::CommandLineToArgvW(command_line, &argc); + + // iterate over the returned wide strings; + for (int i = 0; i < argc; ++i) { + args_.push_back(rtc::ToUtf8(wide_argv[i], wcslen(wide_argv[i]))); + // make sure the argv array points to the string data. + argv_.push_back(const_cast<char*>(args_.back().c_str())); + } + LocalFree(wide_argv); +} + +} // namespace +int PASCAL wWinMain(HINSTANCE instance, + HINSTANCE prev_instance, + wchar_t* cmd_line, + int cmd_show) { + rtc::WinsockInitializer winsock_init; + rtc::PhysicalSocketServer ss; + rtc::AutoSocketServerThread main_thread(&ss); + + WindowsCommandLineArguments win_args; + int argc = win_args.argc(); + char** argv = win_args.argv(); + + absl::ParseCommandLine(argc, argv); + + // InitFieldTrialsFromString stores the char*, so the char array must outlive + // the application. + const std::string forced_field_trials = + absl::GetFlag(FLAGS_force_fieldtrials); + webrtc::field_trial::InitFieldTrialsFromString(forced_field_trials.c_str()); + + // Abort if the user specifies a port that is outside the allowed + // range [1, 65535]. + if ((absl::GetFlag(FLAGS_port) < 1) || (absl::GetFlag(FLAGS_port) > 65535)) { + printf("Error: %i is not a valid port.\n", absl::GetFlag(FLAGS_port)); + return -1; + } + + const std::string server = absl::GetFlag(FLAGS_server); + MainWnd wnd(server.c_str(), absl::GetFlag(FLAGS_port), + absl::GetFlag(FLAGS_autoconnect), absl::GetFlag(FLAGS_autocall)); + if (!wnd.Create()) { + RTC_DCHECK_NOTREACHED(); + return -1; + } + + rtc::InitializeSSL(); + PeerConnectionClient client; + auto conductor = rtc::make_ref_counted<Conductor>(&client, &wnd); + + // Main loop. + MSG msg; + BOOL gm; + while ((gm = ::GetMessage(&msg, NULL, 0, 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)) != 0 && gm != -1) { + if (!wnd.PreTranslateMessage(&msg)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + } + + rtc::CleanupSSL(); + return 0; +} diff --git a/third_party/libwebrtc/examples/peerconnection/client/main_wnd.cc b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.cc new file mode 100644 index 0000000000..afafa621b3 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.cc @@ -0,0 +1,633 @@ +/* + * Copyright 2012 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 "examples/peerconnection/client/main_wnd.h" + +#include <math.h> + +#include "api/video/i420_buffer.h" +#include "examples/peerconnection/client/defaults.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "third_party/libyuv/include/libyuv/convert_argb.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}; + ::DrawTextW(dc, text, -1, &text_rc, DT_CALCRECT | DT_SINGLELINE); + ::ReleaseDC(wnd, dc); + RECT client, window; + ::GetClientRect(wnd, &client); + ::GetWindowRect(wnd, &window); + + *width = text_rc.right - text_rc.left; + *width += (window.right - window.left) - (client.right - client.left); + *height = text_rc.bottom - text_rc.top; + *height += (window.bottom - window.top) - (client.bottom - client.top); +} + +HFONT GetDefaultFont() { + static HFONT font = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT)); + return font; +} + +std::string GetWindowText(HWND wnd) { + char text[MAX_PATH] = {0}; + ::GetWindowTextA(wnd, &text[0], ARRAYSIZE(text)); + return text; +} + +void AddListBoxItem(HWND listbox, const std::string& str, LPARAM item_data) { + LRESULT index = ::SendMessageA(listbox, LB_ADDSTRING, 0, + reinterpret_cast<LPARAM>(str.c_str())); + ::SendMessageA(listbox, LB_SETITEMDATA, index, item_data); +} + +} // namespace + +MainWnd::MainWnd(const char* server, + int port, + bool auto_connect, + bool auto_call) + : ui_(CONNECT_TO_SERVER), + wnd_(NULL), + edit1_(NULL), + edit2_(NULL), + label1_(NULL), + label2_(NULL), + button_(NULL), + listbox_(NULL), + destroyed_(false), + nested_msg_(NULL), + callback_(NULL), + server_(server), + auto_connect_(auto_connect), + auto_call_(auto_call) { + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%i", port); + port_ = buffer; +} + +MainWnd::~MainWnd() { + RTC_DCHECK(!IsWindow()); +} + +bool MainWnd::Create() { + RTC_DCHECK(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<WPARAM>(GetDefaultFont()), + TRUE); + + CreateChildWindows(); + SwitchToConnectUI(); + + return wnd_ != NULL; +} + +bool MainWnd::Destroy() { + BOOL ret = FALSE; + if (IsWindow()) { + ret = ::DestroyWindow(wnd_); + } + + return ret != FALSE; +} + +void MainWnd::RegisterObserver(MainWndCallback* callback) { + callback_ = callback; +} + +bool MainWnd::IsWindow() { + 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<int>(msg->wParam), + reinterpret_cast<void*>(msg->lParam)); + ret = true; + } + return ret; +} + +void MainWnd::SwitchToConnectUI() { + RTC_DCHECK(IsWindow()); + LayoutPeerListUI(false); + ui_ = CONNECT_TO_SERVER; + LayoutConnectUI(true); + ::SetFocus(edit1_); + + if (auto_connect_) + ::PostMessage(button_, BM_CLICK, 0, 0); +} + +void MainWnd::SwitchToPeerList(const Peers& peers) { + LayoutConnectUI(false); + + ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0); + + AddListBoxItem(listbox_, "List of currently connected peers:", -1); + Peers::const_iterator i = peers.begin(); + for (; i != peers.end(); ++i) + AddListBoxItem(listbox_, i->second.c_str(), i->first); + + ui_ = LIST_PEERS; + LayoutPeerListUI(true); + ::SetFocus(listbox_); + + if (auto_call_ && peers.begin() != peers.end()) { + // Get the number of items in the list + LRESULT count = ::SendMessage(listbox_, LB_GETCOUNT, 0, 0); + if (count != LB_ERR) { + // Select the last item in the list + LRESULT selection = ::SendMessage(listbox_, LB_SETCURSEL, count - 1, 0); + if (selection != LB_ERR) + ::PostMessage(wnd_, WM_COMMAND, + MAKEWPARAM(GetDlgCtrlID(listbox_), LBN_DBLCLK), + reinterpret_cast<LPARAM>(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); +} + +void MainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) { + local_renderer_.reset(new VideoRenderer(handle(), 1, 1, local_video)); +} + +void MainWnd::StopLocalRenderer() { + local_renderer_.reset(); +} + +void MainWnd::StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) { + remote_renderer_.reset(new VideoRenderer(handle(), 1, 1, remote_video)); +} + +void MainWnd::StopRemoteRenderer() { + remote_renderer_.reset(); +} + +void MainWnd::QueueUIThreadCallback(int msg_id, void* data) { + ::PostThreadMessage(ui_thread_id_, UI_THREAD_CALLBACK, + static_cast<WPARAM>(msg_id), + reinterpret_cast<LPARAM>(data)); +} + +void MainWnd::OnPaint() { + PAINTSTRUCT ps; + ::BeginPaint(handle(), &ps); + + RECT rc; + ::GetClientRect(handle(), &rc); + + VideoRenderer* local_renderer = local_renderer_.get(); + VideoRenderer* remote_renderer = remote_renderer_.get(); + if (ui_ == STREAMING && remote_renderer && local_renderer) { + AutoLock<VideoRenderer> local_lock(local_renderer); + AutoLock<VideoRenderer> remote_lock(remote_renderer); + + const BITMAPINFO& bmi = remote_renderer->bmi(); + int height = abs(bmi.bmiHeader.biHeight); + int width = bmi.bmiHeader.biWidth; + + const uint8_t* image = remote_renderer->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 (size_t i = 0; i < arraysize(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 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_renderer->bmi(); + image = local_renderer->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_renderer->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<LRESULT>(GetSysColorBrush(COLOR_WINDOW)); + return true; + + case WM_COMMAND: + if (button_ == reinterpret_cast<HWND>(lp)) { + if (BN_CLICKED == HIWORD(wp)) + OnDefaultAction(); + } else if (listbox_ == reinterpret_cast<HWND>(lp)) { + if (LBN_DBLCLK == HIWORD(wp)) { + OnDefaultAction(); + } + } + return true; + + 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<MainWnd*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (!me && WM_CREATE == msg) { + CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lp); + me = reinterpret_cast<MainWnd*>(cs->lpCreateParams); + me->wnd_ = hwnd; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(me)); + } + + LRESULT result = 0; + if (me) { + void* prev_nested_msg = me->nested_msg_; + me->nested_msg_ = &msg; + + bool handled = me->OnMessage(msg, wp, lp, &result); + if (WM_NCDESTROY == msg) { + me->destroyed_ = true; + } else if (!handled) { + result = ::DefWindowProc(hwnd, msg, wp, lp); + } + + if (me->destroyed_ && prev_nested_msg == NULL) { + me->OnDestroyed(); + me->wnd_ = NULL; + me->destroyed_ = false; + } + + me->nested_msg_ = prev_nested_msg; + } else { + result = ::DefWindowProc(hwnd, msg, wp, lp); + } + + return result; +} + +// static +bool MainWnd::RegisterWindowClass() { + if (wnd_class_) + return true; + + WNDCLASSEXW wcex = {sizeof(WNDCLASSEX)}; + wcex.style = CS_DBLCLKS; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1); + wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW); + wcex.lpfnWndProc = &WndProc; + wcex.lpszClassName = kClassName; + wnd_class_ = ::RegisterClassExW(&wcex); + RTC_DCHECK(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 = ::CreateWindowExW(ex_style, class_name, L"", style, 100, 100, 100, 100, + wnd_, reinterpret_cast<HMENU>(id), + GetModuleHandle(NULL), NULL); + RTC_DCHECK(::IsWindow(*wnd) != FALSE); + ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()), + TRUE); +} + +void MainWnd::CreateChildWindows() { + // Create the child windows in tab order. + CreateChildWindow(&label1_, LABEL1_ID, L"Static", ES_CENTER | ES_READONLY, 0); + CreateChildWindow(&edit1_, EDIT_ID, L"Edit", + ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); + CreateChildWindow(&label2_, LABEL2_ID, L"Static", ES_CENTER | ES_READONLY, 0); + CreateChildWindow(&edit2_, EDIT_ID, L"Edit", + ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); + CreateChildWindow(&button_, BUTTON_ID, L"Button", BS_CENTER | WS_TABSTOP, 0); + + CreateChildWindow(&listbox_, LISTBOX_ID, L"ListBox", + LBS_HASSTRINGS | LBS_NOTIFY, WS_EX_CLIENTEDGE); + + ::SetWindowTextA(edit1_, server_.c_str()); + ::SetWindowTextA(edit2_, port_.c_str()); +} + +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, static_cast<int>(x), static_cast<int>(top), + static_cast<int>(windows[i].width), + static_cast<int>(windows[i].height), TRUE); + x += kSeparator + windows[i].width; + if (windows[i].text[0] != 'X') + ::SetWindowTextW(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, + webrtc::VideoTrackInterface* track_to_render) + : wnd_(wnd), rendered_track_(track_to_render) { + ::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); + rendered_track_->AddOrUpdateSink(this, rtc::VideoSinkWants()); +} + +MainWnd::VideoRenderer::~VideoRenderer() { + rendered_track_->RemoveSink(this); + ::DeleteCriticalSection(&buffer_lock_); +} + +void MainWnd::VideoRenderer::SetSize(int width, int height) { + AutoLock<VideoRenderer> lock(this); + + if (width == bmi_.bmiHeader.biWidth && height == bmi_.bmiHeader.biHeight) { + return; + } + + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = + width * height * (bmi_.bmiHeader.biBitCount >> 3); + image_.reset(new uint8_t[bmi_.bmiHeader.biSizeImage]); +} + +void MainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) { + { + AutoLock<VideoRenderer> lock(this); + + rtc::scoped_refptr<webrtc::I420BufferInterface> buffer( + video_frame.video_frame_buffer()->ToI420()); + if (video_frame.rotation() != webrtc::kVideoRotation_0) { + buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation()); + } + + SetSize(buffer->width(), buffer->height()); + + RTC_DCHECK(image_.get() != NULL); + libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(), + buffer->StrideU(), buffer->DataV(), buffer->StrideV(), + image_.get(), + bmi_.bmiHeader.biWidth * bmi_.bmiHeader.biBitCount / 8, + buffer->width(), buffer->height()); + } + InvalidateRect(wnd_, NULL, TRUE); +} diff --git a/third_party/libwebrtc/examples/peerconnection/client/main_wnd.h b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.h new file mode 100644 index 0000000000..898fea9d92 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/main_wnd.h @@ -0,0 +1,206 @@ +/* + * Copyright 2012 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 EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_ + +#include <map> +#include <memory> +#include <string> + +#include "api/media_stream_interface.h" +#include "api/video/video_frame.h" +#include "examples/peerconnection/client/peer_connection_client.h" +#include "media/base/media_channel.h" +#include "media/base/video_common.h" +#if defined(WEBRTC_WIN) +#include "rtc_base/win32.h" +#endif // WEBRTC_WIN + +class MainWndCallback { + public: + virtual void StartLogin(const std::string& server, int port) = 0; + virtual void DisconnectFromServer() = 0; + virtual void ConnectToPeer(int peer_id) = 0; + virtual void DisconnectFromCurrentPeer() = 0; + 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 void StartLocalRenderer(webrtc::VideoTrackInterface* local_video) = 0; + virtual void StopLocalRenderer() = 0; + virtual void StartRemoteRenderer( + webrtc::VideoTrackInterface* remote_video) = 0; + virtual void StopRemoteRenderer() = 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(const char* server, int port, bool auto_connect, bool auto_call); + ~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 void StartLocalRenderer(webrtc::VideoTrackInterface* local_video); + virtual void StopLocalRenderer(); + virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video); + virtual void StopRemoteRenderer(); + + virtual void QueueUIThreadCallback(int msg_id, void* data); + + HWND handle() const { return wnd_; } + + class VideoRenderer : public rtc::VideoSinkInterface<webrtc::VideoFrame> { + public: + VideoRenderer(HWND wnd, + int width, + int height, + webrtc::VideoTrackInterface* track_to_render); + virtual ~VideoRenderer(); + + void Lock() { ::EnterCriticalSection(&buffer_lock_); } + + void Unlock() { ::LeaveCriticalSection(&buffer_lock_); } + + // VideoSinkInterface implementation + void OnFrame(const webrtc::VideoFrame& frame) override; + + const BITMAPINFO& bmi() const { return bmi_; } + const uint8_t* image() const { return image_.get(); } + + protected: + void SetSize(int width, int height); + + enum { + SET_SIZE, + RENDER_FRAME, + }; + + HWND wnd_; + BITMAPINFO bmi_; + std::unique_ptr<uint8_t[]> image_; + CRITICAL_SECTION buffer_lock_; + rtc::scoped_refptr<webrtc::VideoTrackInterface> rendered_track_; + }; + + // A little helper class to make sure we always to proper locking and + // unlocking when working with VideoRenderer buffers. + template <typename T> + 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: + std::unique_ptr<VideoRenderer> local_renderer_; + std::unique_ptr<VideoRenderer> remote_renderer_; + 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_; + std::string server_; + std::string port_; + bool auto_connect_; + bool auto_call_; +}; +#endif // WIN32 + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.cc b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.cc new file mode 100644 index 0000000000..48d5bb6545 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.cc @@ -0,0 +1,493 @@ +/* + * Copyright 2012 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 "examples/peerconnection/client/peer_connection_client.h" + +#include "api/units/time_delta.h" +#include "examples/peerconnection/client/defaults.h" +#include "rtc_base/async_dns_resolver.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/net_helpers.h" + +namespace { + +// This is our magical hangup signal. +constexpr char kByeMessage[] = "BYE"; +// Delay between server connection retries, in milliseconds +constexpr webrtc::TimeDelta kReconnectDelay = webrtc::TimeDelta::Seconds(2); + +rtc::Socket* CreateClientSocket(int family) { + rtc::Thread* thread = rtc::Thread::Current(); + RTC_DCHECK(thread != NULL); + return thread->socketserver()->CreateSocket(family, SOCK_STREAM); +} + +} // namespace + +PeerConnectionClient::PeerConnectionClient() + : callback_(NULL), resolver_(nullptr), state_(NOT_CONNECTED), my_id_(-1) {} + +PeerConnectionClient::~PeerConnectionClient() = default; + +void PeerConnectionClient::InitSocketSignals() { + RTC_DCHECK(control_socket_.get() != NULL); + RTC_DCHECK(hanging_get_.get() != NULL); + 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); +} + +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) { + RTC_DCHECK(!callback_); + callback_ = callback; +} + +void PeerConnectionClient::Connect(const std::string& server, + int port, + const std::string& client_name) { + RTC_DCHECK(!server.empty()); + RTC_DCHECK(!client_name.empty()); + + if (state_ != NOT_CONNECTED) { + RTC_LOG(LS_WARNING) + << "The client must not be connected before you can call Connect()"; + callback_->OnServerConnectionFailure(); + return; + } + + if (server.empty() || client_name.empty()) { + callback_->OnServerConnectionFailure(); + return; + } + + if (port <= 0) + port = kDefaultServerPort; + + server_address_.SetIP(server); + server_address_.SetPort(port); + client_name_ = client_name; + + if (server_address_.IsUnresolvedIP()) { + RTC_DCHECK_NE(state_, RESOLVING); + RTC_DCHECK(!resolver_); + state_ = RESOLVING; + resolver_ = std::make_unique<webrtc::AsyncDnsResolver>(); + resolver_->Start(server_address_, + [this] { OnResolveResult(resolver_->result()); }); + } else { + DoConnect(); + } +} + +void PeerConnectionClient::OnResolveResult( + const webrtc::AsyncDnsResolverResult& result) { + if (result.GetError() != 0) { + callback_->OnServerConnectionFailure(); + resolver_.reset(); + state_ = NOT_CONNECTED; + return; + } + if (!result.GetResolvedAddress(AF_INET, &server_address_)) { + callback_->OnServerConnectionFailure(); + resolver_.reset(); + state_ = NOT_CONNECTED; + return; + } + DoConnect(); +} + +void PeerConnectionClient::DoConnect() { + control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family())); + hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family())); + InitSocketSignals(); + char buffer[1024]; + snprintf(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; + if (!ret) { + callback_->OnServerConnectionFailure(); + } +} + +bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) { + if (state_ != CONNECTED) + return false; + + RTC_DCHECK(is_connected()); + RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED); + if (!is_connected() || peer_id == -1) + return false; + + char headers[1024]; + snprintf(headers, sizeof(headers), + "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n" + "Content-Length: %zu\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() != rtc::Socket::CS_CLOSED; +} + +bool PeerConnectionClient::SignOut() { + if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT) + return true; + + if (hanging_get_->GetState() != rtc::Socket::CS_CLOSED) + hanging_get_->Close(); + + if (control_socket_->GetState() == rtc::Socket::CS_CLOSED) { + state_ = SIGNING_OUT; + + if (my_id_ != -1) { + char buffer[1024]; + snprintf(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(); + resolver_.reset(); + my_id_ = -1; + state_ = NOT_CONNECTED; +} + +bool PeerConnectionClient::ConnectControlSocket() { + RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED); + int err = control_socket_->Connect(server_address_); + if (err == SOCKET_ERROR) { + Close(); + return false; + } + return true; +} + +void PeerConnectionClient::OnConnect(rtc::Socket* socket) { + RTC_DCHECK(!onconnect_data_.empty()); + size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length()); + RTC_DCHECK(sent == onconnect_data_.length()); + onconnect_data_.clear(); +} + +void PeerConnectionClient::OnHangingGetConnect(rtc::Socket* socket) { + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", + my_id_); + int len = static_cast<int>(strlen(buffer)); + int sent = socket->Send(buffer, len); + RTC_DCHECK(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) { + RTC_DCHECK(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) { + RTC_DCHECK(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(rtc::Socket* socket, + std::string* data, + size_t* content_length) { + char buffer[0xffff]; + do { + int bytes = socket->Recv(buffer, sizeof(buffer), nullptr); + 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) { + RTC_LOG(LS_INFO) << "Headers received"; + if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) { + 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(); + // Since we closed the socket, there was no notification delivered + // to us. Compensate by letting ourselves know. + OnClose(socket, 0); + } + } else { + // We haven't received everything. Just continue to accept data. + } + } else { + RTC_LOG(LS_ERROR) << "No content length field specified by the server."; + } + } + return ret; +} + +void PeerConnectionClient::OnRead(rtc::Socket* socket) { + 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. + RTC_DCHECK(state_ == SIGNING_IN); + my_id_ = static_cast<int>(peer_id); + RTC_DCHECK(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; + } + } + RTC_DCHECK(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) { + RTC_DCHECK(hanging_get_->GetState() == rtc::Socket::CS_CLOSED); + state_ = CONNECTED; + hanging_get_->Connect(server_address_); + } + } +} + +void PeerConnectionClient::OnHangingGetRead(rtc::Socket* socket) { + RTC_LOG(LS_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<int>(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(static_cast<int>(peer_id), + notification_data_.substr(pos)); + } + } + + notification_data_.clear(); + } + + if (hanging_get_->GetState() == rtc::Socket::CS_CLOSED && + state_ == CONNECTED) { + hanging_get_->Connect(server_address_); + } +} + +bool PeerConnectionClient::ParseEntry(const std::string& entry, + std::string* name, + int* id, + bool* connected) { + RTC_DCHECK(name != NULL); + RTC_DCHECK(id != NULL); + RTC_DCHECK(connected != NULL); + RTC_DCHECK(!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) { + int status = GetResponseStatus(response.c_str()); + if (status != 200) { + RTC_LOG(LS_ERROR) << "Received error from server"; + Close(); + callback_->OnDisconnected(); + return false; + } + + *eoh = response.find("\r\n\r\n"); + RTC_DCHECK(*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. + GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id); + + return true; +} + +void PeerConnectionClient::OnClose(rtc::Socket* socket, int err) { + RTC_LOG(LS_INFO) << __FUNCTION__; + + socket->Close(); + +#ifdef WIN32 + if (err != WSAECONNREFUSED) { +#else + if (err != ECONNREFUSED) { +#endif + if (socket == hanging_get_.get()) { + if (state_ == CONNECTED) { + hanging_get_->Close(); + hanging_get_->Connect(server_address_); + } + } else { + callback_->OnMessageSent(err); + } + } else { + if (socket == control_socket_.get()) { + RTC_LOG(LS_WARNING) << "Connection refused; retrying in 2 seconds"; + rtc::Thread::Current()->PostDelayedTask( + SafeTask(safety_.flag(), [this] { DoConnect(); }), kReconnectDelay); + } else { + Close(); + callback_->OnDisconnected(); + } + } +} diff --git a/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.h b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.h new file mode 100644 index 0000000000..d56752a7fa --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/client/peer_connection_client.h @@ -0,0 +1,130 @@ +/* + * Copyright 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 EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_ + +#include <map> +#include <memory> +#include <string> + +#include "api/async_dns_resolver.h" +#include "api/task_queue/pending_task_safety_flag.h" +#include "rtc_base/net_helpers.h" +#include "rtc_base/physical_socket_server.h" +#include "rtc_base/third_party/sigslot/sigslot.h" + +typedef std::map<int, std::string> Peers; + +struct PeerConnectionClientObserver { + virtual void OnSignedIn() = 0; // Called when we're logged on. + virtual void OnDisconnected() = 0; + virtual void OnPeerConnected(int id, const std::string& name) = 0; + virtual void OnPeerDisconnected(int peer_id) = 0; + virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0; + virtual void OnMessageSent(int err) = 0; + virtual void OnServerConnectionFailure() = 0; + + protected: + virtual ~PeerConnectionClientObserver() {} +}; + +class PeerConnectionClient : public sigslot::has_slots<> { + public: + enum State { + NOT_CONNECTED, + RESOLVING, + 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); + + void 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 DoConnect(); + void Close(); + void InitSocketSignals(); + bool ConnectControlSocket(); + void OnConnect(rtc::Socket* socket); + void OnHangingGetConnect(rtc::Socket* 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(rtc::Socket* socket, + std::string* data, + size_t* content_length); + + void OnRead(rtc::Socket* socket); + + void OnHangingGetRead(rtc::Socket* socket); + + // Parses a single line entry in the form "<name>,<id>,<connected>" + bool ParseEntry(const std::string& entry, + std::string* name, + int* id, + bool* connected); + + int GetResponseStatus(const std::string& response); + + bool ParseServerResponse(const std::string& response, + size_t content_length, + size_t* peer_id, + size_t* eoh); + + void OnClose(rtc::Socket* socket, int err); + + void OnResolveResult(const webrtc::AsyncDnsResolverResult& result); + + PeerConnectionClientObserver* callback_; + rtc::SocketAddress server_address_; + std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver_; + std::unique_ptr<rtc::Socket> control_socket_; + std::unique_ptr<rtc::Socket> hanging_get_; + std::string onconnect_data_; + std::string control_data_; + std::string notification_data_; + std::string client_name_; + Peers peers_; + State state_; + int my_id_; + webrtc::ScopedTaskSafety safety_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc new file mode 100644 index 0000000000..855ebd8c0c --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc @@ -0,0 +1,299 @@ +/* + * Copyright 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 "examples/peerconnection/server/data_socket.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if defined(WEBRTC_POSIX) +#include <unistd.h> +#endif + +#include "examples/peerconnection/server/utils.h" +#include "rtc_base/checks.h" + +static const char kHeaderTerminator[] = "\r\n\r\n"; +static const int kHeaderTerminatorLength = sizeof(kHeaderTerminator) - 1; + +// static +const char DataSocket::kCrossOriginAllowHeaders[] = + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Credentials: true\r\n" + "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" + "Access-Control-Allow-Headers: Content-Type, " + "Content-Length, Connection, Cache-Control\r\n" + "Access-Control-Expose-Headers: Content-Length\r\n"; + +#if defined(WIN32) +class WinsockInitializer { + static WinsockInitializer singleton; + + WinsockInitializer() { + WSADATA data; + WSAStartup(MAKEWORD(1, 0), &data); + } + + public: + ~WinsockInitializer() { WSACleanup(); } +}; +WinsockInitializer WinsockInitializer::singleton; +#endif + +// +// SocketBase +// + +bool SocketBase::Create() { + RTC_DCHECK(!valid()); + socket_ = ::socket(AF_INET, SOCK_STREAM, 0); + return valid(); +} + +void SocketBase::Close() { + if (socket_ != INVALID_SOCKET) { + closesocket(socket_); + socket_ = INVALID_SOCKET; + } +} + +// +// DataSocket +// + +std::string DataSocket::request_arguments() const { + size_t args = request_path_.find('?'); + if (args != std::string::npos) + return request_path_.substr(args + 1); + return ""; +} + +bool DataSocket::PathEquals(const char* path) const { + RTC_DCHECK(path); + size_t args = request_path_.find('?'); + if (args != std::string::npos) + return request_path_.substr(0, args).compare(path) == 0; + return request_path_.compare(path) == 0; +} + +bool DataSocket::OnDataAvailable(bool* close_socket) { + RTC_DCHECK(valid()); + char buffer[0xfff] = {0}; + int bytes = recv(socket_, buffer, sizeof(buffer), 0); + if (bytes == SOCKET_ERROR || bytes == 0) { + *close_socket = true; + return false; + } + + *close_socket = false; + + bool ret = true; + if (headers_received()) { + if (method_ != POST) { + // unexpectedly received data. + ret = false; + } else { + data_.append(buffer, bytes); + } + } else { + request_headers_.append(buffer, bytes); + size_t found = request_headers_.find(kHeaderTerminator); + if (found != std::string::npos) { + data_ = request_headers_.substr(found + kHeaderTerminatorLength); + request_headers_.resize(found + kHeaderTerminatorLength); + ret = ParseHeaders(); + } + } + return ret; +} + +bool DataSocket::Send(const std::string& data) const { + return send(socket_, data.data(), static_cast<int>(data.length()), 0) != + SOCKET_ERROR; +} + +bool DataSocket::Send(const std::string& status, + bool connection_close, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data) const { + RTC_DCHECK(valid()); + RTC_DCHECK(!status.empty()); + std::string buffer("HTTP/1.1 " + status + "\r\n"); + + buffer += + "Server: PeerConnectionTestServer/0.1\r\n" + "Cache-Control: no-cache\r\n"; + + if (connection_close) + buffer += "Connection: close\r\n"; + + if (!content_type.empty()) + buffer += "Content-Type: " + content_type + "\r\n"; + + buffer += + "Content-Length: " + int2str(static_cast<int>(data.size())) + "\r\n"; + + if (!extra_headers.empty()) { + buffer += extra_headers; + // Extra headers are assumed to have a separator per header. + } + + buffer += kCrossOriginAllowHeaders; + + buffer += "\r\n"; + buffer += data; + + return Send(buffer); +} + +void DataSocket::Clear() { + method_ = INVALID; + content_length_ = 0; + content_type_.clear(); + request_path_.clear(); + request_headers_.clear(); + data_.clear(); +} + +bool DataSocket::ParseHeaders() { + RTC_DCHECK(!request_headers_.empty()); + RTC_DCHECK_EQ(method_, INVALID); + size_t i = request_headers_.find("\r\n"); + if (i == std::string::npos) + return false; + + if (!ParseMethodAndPath(request_headers_.data(), i)) + return false; + + RTC_DCHECK_NE(method_, INVALID); + RTC_DCHECK(!request_path_.empty()); + + if (method_ == POST) { + const char* headers = request_headers_.data() + i + 2; + size_t len = request_headers_.length() - i - 2; + if (!ParseContentLengthAndType(headers, len)) + return false; + } + + return true; +} + +bool DataSocket::ParseMethodAndPath(const char* begin, size_t len) { + struct { + const char* method_name; + size_t method_name_len; + RequestMethod id; + } supported_methods[] = { + {"GET", 3, GET}, + {"POST", 4, POST}, + {"OPTIONS", 7, OPTIONS}, + }; + + const char* path = NULL; + for (size_t i = 0; i < ARRAYSIZE(supported_methods); ++i) { + if (len > supported_methods[i].method_name_len && + isspace(begin[supported_methods[i].method_name_len]) && + strncmp(begin, supported_methods[i].method_name, + supported_methods[i].method_name_len) == 0) { + method_ = supported_methods[i].id; + path = begin + supported_methods[i].method_name_len; + break; + } + } + + const char* end = begin + len; + if (!path || path >= end) + return false; + + ++path; + begin = path; + while (!isspace(*path) && path < end) + ++path; + + request_path_.assign(begin, path - begin); + + return true; +} + +bool DataSocket::ParseContentLengthAndType(const char* headers, size_t length) { + RTC_DCHECK_EQ(content_length_, 0); + RTC_DCHECK(content_type_.empty()); + + const char* end = headers + length; + while (headers && headers < end) { + if (!isspace(headers[0])) { + static const char kContentLength[] = "Content-Length:"; + static const char kContentType[] = "Content-Type:"; + if ((headers + ARRAYSIZE(kContentLength)) < end && + strncmp(headers, kContentLength, ARRAYSIZE(kContentLength) - 1) == + 0) { + headers += ARRAYSIZE(kContentLength) - 1; + while (headers[0] == ' ') + ++headers; + content_length_ = atoi(headers); + } else if ((headers + ARRAYSIZE(kContentType)) < end && + strncmp(headers, kContentType, ARRAYSIZE(kContentType) - 1) == + 0) { + headers += ARRAYSIZE(kContentType) - 1; + while (headers[0] == ' ') + ++headers; + const char* type_end = strstr(headers, "\r\n"); + if (type_end == NULL) + type_end = end; + content_type_.assign(headers, type_end); + } + } else { + ++headers; + } + headers = strstr(headers, "\r\n"); + if (headers) + headers += 2; + } + + return !content_type_.empty() && content_length_ != 0; +} + +// +// ListeningSocket +// + +bool ListeningSocket::Listen(unsigned short port) { + RTC_DCHECK(valid()); + int enabled = 1; + if (setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast<const char*>(&enabled), + sizeof(enabled)) != 0) { + printf("setsockopt failed\n"); + return false; + } + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + if (bind(socket_, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == + SOCKET_ERROR) { + printf("bind failed\n"); + return false; + } + return listen(socket_, 5) != SOCKET_ERROR; +} + +DataSocket* ListeningSocket::Accept() const { + RTC_DCHECK(valid()); + struct sockaddr_in addr = {0}; + socklen_t size = sizeof(addr); + NativeSocket client = + accept(socket_, reinterpret_cast<sockaddr*>(&addr), &size); + if (client == INVALID_SOCKET) + return NULL; + + return new DataSocket(client); +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.h b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h new file mode 100644 index 0000000000..57ad5b9aee --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h @@ -0,0 +1,152 @@ +/* + * Copyright 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 EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ + +#ifdef WIN32 +#include <winsock2.h> +typedef int socklen_t; +typedef SOCKET NativeSocket; +#else +#include <netinet/in.h> +#include <sys/select.h> +#include <sys/socket.h> +#define closesocket close +typedef int NativeSocket; + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR (-1) +#endif + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET static_cast<NativeSocket>(-1) +#endif +#endif + +#include <string> + +class SocketBase { + public: + SocketBase() : socket_(INVALID_SOCKET) {} + explicit SocketBase(NativeSocket socket) : socket_(socket) {} + SocketBase(SocketBase& other) = delete; + SocketBase& operator=(const SocketBase& other) = delete; + ~SocketBase() { Close(); } + + NativeSocket socket() const { return socket_; } + bool valid() const { return socket_ != INVALID_SOCKET; } + + bool Create(); + void Close(); + + protected: + NativeSocket socket_; +}; + +// Represents an HTTP server socket. +class DataSocket : public SocketBase { + public: + enum RequestMethod { + INVALID, + GET, + POST, + OPTIONS, + }; + + explicit DataSocket(NativeSocket socket) + : SocketBase(socket), method_(INVALID), content_length_(0) {} + + ~DataSocket() {} + + static const char kCrossOriginAllowHeaders[]; + + bool headers_received() const { return method_ != INVALID; } + + RequestMethod method() const { return method_; } + + const std::string& request_path() const { return request_path_; } + std::string request_arguments() const; + + const std::string& data() const { return data_; } + + const std::string& content_type() const { return content_type_; } + + size_t content_length() const { return content_length_; } + + bool request_received() const { + return headers_received() && (method_ != POST || data_received()); + } + + bool data_received() const { + return method_ != POST || data_.length() >= content_length_; + } + + // Checks if the request path (minus arguments) matches a given path. + bool PathEquals(const char* path) const; + + // Called when we have received some data from clients. + // Returns false if an error occurred. + bool OnDataAvailable(bool* close_socket); + + // Send a raw buffer of bytes. + bool Send(const std::string& data) const; + + // Send an HTTP response. The `status` should start with a valid HTTP + // response code, followed by a string. E.g. "200 OK". + // If `connection_close` is set to true, an extra "Connection: close" HTTP + // header will be included. `content_type` is the mime content type, not + // including the "Content-Type: " string. + // `extra_headers` should be either empty or a list of headers where each + // header terminates with "\r\n". + // `data` is the body of the message. It's length will be specified via + // a "Content-Length" header. + bool Send(const std::string& status, + bool connection_close, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data) const; + + // Clears all held state and prepares the socket for receiving a new request. + void Clear(); + + protected: + // A fairly relaxed HTTP header parser. Parses the method, path and + // content length (POST only) of a request. + // Returns true if a valid request was received and no errors occurred. + bool ParseHeaders(); + + // Figures out whether the request is a GET or POST and what path is + // being requested. + bool ParseMethodAndPath(const char* begin, size_t len); + + // Determines the length of the body and it's mime type. + bool ParseContentLengthAndType(const char* headers, size_t length); + + protected: + RequestMethod method_; + size_t content_length_; + std::string content_type_; + std::string request_path_; + std::string request_headers_; + std::string data_; +}; + +// The server socket. Accepts connections and generates DataSocket instances +// for each new connection. +class ListeningSocket : public SocketBase { + public: + ListeningSocket() {} + + bool Listen(unsigned short port); + DataSocket* Accept() const; +}; + +#endif // EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/server/main.cc b/third_party/libwebrtc/examples/peerconnection/server/main.cc new file mode 100644 index 0000000000..50b8c23401 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/main.cc @@ -0,0 +1,193 @@ +/* + * Copyright 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 <stdio.h> +#include <stdlib.h> +#if defined(WEBRTC_POSIX) +#include <sys/select.h> +#endif +#include <time.h> + +#include <string> +#include <vector> + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/flags/usage.h" +#include "examples/peerconnection/server/data_socket.h" +#include "examples/peerconnection/server/peer_channel.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" + +ABSL_FLAG( + std::string, + force_fieldtrials, + "", + "Field trials control experimental features. This flag specifies the field " + "trials in effect. E.g. running with " + "--force_fieldtrials=WebRTC-FooFeature/Enabled/ " + "will assign the group Enabled to field trial WebRTC-FooFeature. Multiple " + "trials are separated by \"/\""); +ABSL_FLAG(int, port, 8888, "default: 8888"); + +static const size_t kMaxConnections = (FD_SETSIZE - 2); + +void HandleBrowserRequest(DataSocket* ds, bool* quit) { + RTC_DCHECK(ds && ds->valid()); + RTC_DCHECK(quit); + + const std::string& path = ds->request_path(); + + *quit = (path.compare("/quit") == 0); + + if (*quit) { + ds->Send("200 OK", true, "text/html", "", + "<html><body>Quitting...</body></html>"); + } else if (ds->method() == DataSocket::OPTIONS) { + // We'll get this when a browsers do cross-resource-sharing requests. + // The headers to allow cross-origin script support will be set inside + // Send. + ds->Send("200 OK", true, "", "", ""); + } else { + // Here we could write some useful output back to the browser depending on + // the path. + printf("Received an invalid request: %s\n", ds->request_path().c_str()); + ds->Send("500 Sorry", true, "text/html", "", + "<html><body>Sorry, not yet implemented</body></html>"); + } +} + +int main(int argc, char* argv[]) { + absl::SetProgramUsageMessage( + "Example usage: ./peerconnection_server --port=8888\n"); + absl::ParseCommandLine(argc, argv); + + // InitFieldTrialsFromString stores the char*, so the char array must outlive + // the application. + const std::string force_field_trials = absl::GetFlag(FLAGS_force_fieldtrials); + webrtc::field_trial::InitFieldTrialsFromString(force_field_trials.c_str()); + + int port = absl::GetFlag(FLAGS_port); + + // Abort if the user specifies a port that is outside the allowed + // range [1, 65535]. + if ((port < 1) || (port > 65535)) { + printf("Error: %i is not a valid port.\n", port); + return -1; + } + + ListeningSocket listener; + if (!listener.Create()) { + printf("Failed to create server socket\n"); + return -1; + } else if (!listener.Listen(port)) { + printf("Failed to listen on server socket\n"); + return -1; + } + + printf("Server listening on port %i\n", port); + + PeerChannel clients; + typedef std::vector<DataSocket*> SocketArray; + SocketArray sockets; + bool quit = false; + while (!quit) { + fd_set socket_set; + FD_ZERO(&socket_set); + if (listener.valid()) + FD_SET(listener.socket(), &socket_set); + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) + FD_SET((*i)->socket(), &socket_set); + + struct timeval timeout = {10, 0}; + if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) { + printf("select failed\n"); + break; + } + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) { + DataSocket* s = *i; + bool socket_done = true; + if (FD_ISSET(s->socket(), &socket_set)) { + if (s->OnDataAvailable(&socket_done) && s->request_received()) { + ChannelMember* member = clients.Lookup(s); + if (member || PeerChannel::IsPeerConnection(s)) { + if (!member) { + if (s->PathEquals("/sign_in")) { + clients.AddMember(s); + } else { + printf("No member found for: %s\n", s->request_path().c_str()); + s->Send("500 Error", true, "text/plain", "", + "Peer most likely gone."); + } + } else if (member->is_wait_request(s)) { + // no need to do anything. + socket_done = false; + } else { + ChannelMember* target = clients.IsTargetedRequest(s); + if (target) { + member->ForwardRequestToPeer(s, target); + } else if (s->PathEquals("/sign_out")) { + s->Send("200 OK", true, "text/plain", "", ""); + } else { + printf("Couldn't find target for request: %s\n", + s->request_path().c_str()); + s->Send("500 Error", true, "text/plain", "", + "Peer most likely gone."); + } + } + } else { + HandleBrowserRequest(s, &quit); + if (quit) { + printf("Quitting...\n"); + FD_CLR(listener.socket(), &socket_set); + listener.Close(); + clients.CloseAll(); + } + } + } + } else { + socket_done = false; + } + + if (socket_done) { + printf("Disconnecting socket\n"); + clients.OnClosing(s); + RTC_DCHECK(s->valid()); // Close must not have been called yet. + FD_CLR(s->socket(), &socket_set); + delete (*i); + i = sockets.erase(i); + if (i == sockets.end()) + break; + } + } + + clients.CheckForTimeout(); + + if (FD_ISSET(listener.socket(), &socket_set)) { + DataSocket* s = listener.Accept(); + if (sockets.size() >= kMaxConnections) { + delete s; // sorry, that's all we can take. + printf("Connection limit reached\n"); + } else { + sockets.push_back(s); + printf("New connection...\n"); + } + } + } + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) + delete (*i); + sockets.clear(); + + return 0; +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc new file mode 100644 index 0000000000..f53820cc60 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc @@ -0,0 +1,360 @@ +/* + * Copyright 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 "examples/peerconnection/server/peer_channel.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <algorithm> + +#include "examples/peerconnection/server/data_socket.h" +#include "examples/peerconnection/server/utils.h" +#include "rtc_base/checks.h" + +// Set to the peer id of the originator when messages are being +// exchanged between peers, but set to the id of the receiving peer +// itself when notifications are sent from the server about the state +// of other peers. +// +// WORKAROUND: Since support for CORS varies greatly from one browser to the +// next, we don't use a custom name for our peer-id header (originally it was +// "X-Peer-Id: "). Instead, we use a "simple header", "Pragma" which should +// always be exposed to CORS requests. There is a special CORS header devoted +// to exposing proprietary headers (Access-Control-Expose-Headers), however +// at this point it is not working correctly in some popular browsers. +static const char kPeerIdHeader[] = "Pragma: "; + +static const char* kRequestPaths[] = { + "/wait", + "/sign_out", + "/message", +}; + +enum RequestPathIndex { + kWait, + kSignOut, + kMessage, +}; + +const size_t kMaxNameLength = 512; + +// +// ChannelMember +// + +int ChannelMember::s_member_id_ = 0; + +ChannelMember::ChannelMember(DataSocket* socket) + : waiting_socket_(NULL), + id_(++s_member_id_), + connected_(true), + timestamp_(time(NULL)) { + RTC_DCHECK(socket); + RTC_DCHECK_EQ(socket->method(), DataSocket::GET); + RTC_DCHECK(socket->PathEquals("/sign_in")); + name_ = socket->request_arguments(); + if (name_.empty()) + name_ = "peer_" + int2str(id_); + else if (name_.length() > kMaxNameLength) + name_.resize(kMaxNameLength); + + std::replace(name_.begin(), name_.end(), ',', '_'); +} + +ChannelMember::~ChannelMember() {} + +bool ChannelMember::is_wait_request(DataSocket* ds) const { + return ds && ds->PathEquals(kRequestPaths[kWait]); +} + +bool ChannelMember::TimedOut() { + return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30; +} + +std::string ChannelMember::GetPeerIdHeader() const { + std::string ret(kPeerIdHeader + int2str(id_) + "\r\n"); + return ret; +} + +bool ChannelMember::NotifyOfOtherMember(const ChannelMember& other) { + RTC_DCHECK_NE(&other, this); + QueueResponse("200 OK", "text/plain", GetPeerIdHeader(), other.GetEntry()); + return true; +} + +// Returns a string in the form "name,id,connected\n". +std::string ChannelMember::GetEntry() const { + RTC_DCHECK(name_.length() <= kMaxNameLength); + + // name, 11-digit int, 1-digit bool, newline, null + char entry[kMaxNameLength + 15]; + snprintf(entry, sizeof(entry), "%s,%d,%d\n", + name_.substr(0, kMaxNameLength).c_str(), id_, connected_); + return entry; +} + +void ChannelMember::ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer) { + RTC_DCHECK(peer); + RTC_DCHECK(ds); + + std::string extra_headers(GetPeerIdHeader()); + + if (peer == this) { + ds->Send("200 OK", true, ds->content_type(), extra_headers, ds->data()); + } else { + printf("Client %s sending to %s\n", name_.c_str(), peer->name().c_str()); + peer->QueueResponse("200 OK", ds->content_type(), extra_headers, + ds->data()); + ds->Send("200 OK", true, "text/plain", "", ""); + } +} + +void ChannelMember::OnClosing(DataSocket* ds) { + if (ds == waiting_socket_) { + waiting_socket_ = NULL; + timestamp_ = time(NULL); + } +} + +void ChannelMember::QueueResponse(const std::string& status, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data) { + if (waiting_socket_) { + RTC_DCHECK(queue_.empty()); + RTC_DCHECK_EQ(waiting_socket_->method(), DataSocket::GET); + bool ok = + waiting_socket_->Send(status, true, content_type, extra_headers, data); + if (!ok) { + printf("Failed to deliver data to waiting socket\n"); + } + waiting_socket_ = NULL; + timestamp_ = time(NULL); + } else { + QueuedResponse qr; + qr.status = status; + qr.content_type = content_type; + qr.extra_headers = extra_headers; + qr.data = data; + queue_.push(qr); + } +} + +void ChannelMember::SetWaitingSocket(DataSocket* ds) { + RTC_DCHECK_EQ(ds->method(), DataSocket::GET); + if (ds && !queue_.empty()) { + RTC_DCHECK(!waiting_socket_); + const QueuedResponse& response = queue_.front(); + ds->Send(response.status, true, response.content_type, + response.extra_headers, response.data); + queue_.pop(); + } else { + waiting_socket_ = ds; + } +} + +// +// PeerChannel +// + +// static +bool PeerChannel::IsPeerConnection(const DataSocket* ds) { + RTC_DCHECK(ds); + return (ds->method() == DataSocket::POST && ds->content_length() > 0) || + (ds->method() == DataSocket::GET && ds->PathEquals("/sign_in")); +} + +ChannelMember* PeerChannel::Lookup(DataSocket* ds) const { + RTC_DCHECK(ds); + + if (ds->method() != DataSocket::GET && ds->method() != DataSocket::POST) + return NULL; + + size_t i = 0; + for (; i < ARRAYSIZE(kRequestPaths); ++i) { + if (ds->PathEquals(kRequestPaths[i])) + break; + } + + if (i == ARRAYSIZE(kRequestPaths)) + return NULL; + + std::string args(ds->request_arguments()); + static const char kPeerId[] = "peer_id="; + size_t found = args.find(kPeerId); + if (found == std::string::npos) + return NULL; + + int id = atoi(&args[found + ARRAYSIZE(kPeerId) - 1]); + Members::const_iterator iter = members_.begin(); + for (; iter != members_.end(); ++iter) { + if (id == (*iter)->id()) { + if (i == kWait) + (*iter)->SetWaitingSocket(ds); + if (i == kSignOut) + (*iter)->set_disconnected(); + return *iter; + } + } + + return NULL; +} + +ChannelMember* PeerChannel::IsTargetedRequest(const DataSocket* ds) const { + RTC_DCHECK(ds); + // Regardless of GET or POST, we look for the peer_id parameter + // only in the request_path. + const std::string& path = ds->request_path(); + size_t args = path.find('?'); + if (args == std::string::npos) + return NULL; + size_t found; + const char kTargetPeerIdParam[] = "to="; + do { + found = path.find(kTargetPeerIdParam, args); + if (found == std::string::npos) + return NULL; + if (found == (args + 1) || path[found - 1] == '&') { + found += ARRAYSIZE(kTargetPeerIdParam) - 1; + break; + } + args = found + ARRAYSIZE(kTargetPeerIdParam) - 1; + } while (true); + int id = atoi(&path[found]); + Members::const_iterator i = members_.begin(); + for (; i != members_.end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + return NULL; +} + +bool PeerChannel::AddMember(DataSocket* ds) { + RTC_DCHECK(IsPeerConnection(ds)); + ChannelMember* new_guy = new ChannelMember(ds); + Members failures; + BroadcastChangedState(*new_guy, &failures); + HandleDeliveryFailures(&failures); + members_.push_back(new_guy); + + printf("New member added (total=%s): %s\n", + size_t2str(members_.size()).c_str(), new_guy->name().c_str()); + + // Let the newly connected peer know about other members of the channel. + std::string content_type; + std::string response = BuildResponseForNewMember(*new_guy, &content_type); + ds->Send("200 Added", true, content_type, new_guy->GetPeerIdHeader(), + response); + return true; +} + +void PeerChannel::CloseAll() { + Members::const_iterator i = members_.begin(); + for (; i != members_.end(); ++i) { + (*i)->QueueResponse("200 OK", "text/plain", "", "Server shutting down"); + } + DeleteAll(); +} + +void PeerChannel::OnClosing(DataSocket* ds) { + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) { + ChannelMember* m = (*i); + m->OnClosing(ds); + if (!m->connected()) { + i = members_.erase(i); + Members failures; + BroadcastChangedState(*m, &failures); + HandleDeliveryFailures(&failures); + delete m; + if (i == members_.end()) + break; + } + } + printf("Total connected: %s\n", size_t2str(members_.size()).c_str()); +} + +void PeerChannel::CheckForTimeout() { + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) { + ChannelMember* m = (*i); + if (m->TimedOut()) { + printf("Timeout: %s\n", m->name().c_str()); + m->set_disconnected(); + i = members_.erase(i); + Members failures; + BroadcastChangedState(*m, &failures); + HandleDeliveryFailures(&failures); + delete m; + if (i == members_.end()) + break; + } + } +} + +void PeerChannel::DeleteAll() { + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) + delete (*i); + members_.clear(); +} + +void PeerChannel::BroadcastChangedState(const ChannelMember& member, + Members* delivery_failures) { + // This function should be called prior to DataSocket::Close(). + RTC_DCHECK(delivery_failures); + + if (!member.connected()) { + printf("Member disconnected: %s\n", member.name().c_str()); + } + + Members::iterator i = members_.begin(); + for (; i != members_.end(); ++i) { + if (&member != (*i)) { + if (!(*i)->NotifyOfOtherMember(member)) { + (*i)->set_disconnected(); + delivery_failures->push_back(*i); + i = members_.erase(i); + if (i == members_.end()) + break; + } + } + } +} + +void PeerChannel::HandleDeliveryFailures(Members* failures) { + RTC_DCHECK(failures); + + while (!failures->empty()) { + Members::iterator i = failures->begin(); + ChannelMember* member = *i; + RTC_DCHECK(!member->connected()); + failures->erase(i); + BroadcastChangedState(*member, failures); + delete member; + } +} + +// Builds a simple list of "name,id\n" entries for each member. +std::string PeerChannel::BuildResponseForNewMember(const ChannelMember& member, + std::string* content_type) { + RTC_DCHECK(content_type); + + *content_type = "text/plain"; + // The peer itself will always be the first entry. + std::string response(member.GetEntry()); + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) { + if (member.id() != (*i)->id()) { + RTC_DCHECK((*i)->connected()); + response += (*i)->GetEntry(); + } + } + + return response; +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h new file mode 100644 index 0000000000..c3624908ac --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h @@ -0,0 +1,118 @@ +/* + * Copyright 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 EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ + +#include <time.h> + +#include <queue> +#include <string> +#include <vector> + +class DataSocket; + +// Represents a single peer connected to the server. +class ChannelMember { + public: + explicit ChannelMember(DataSocket* socket); + ~ChannelMember(); + + bool connected() const { return connected_; } + int id() const { return id_; } + void set_disconnected() { connected_ = false; } + bool is_wait_request(DataSocket* ds) const; + const std::string& name() const { return name_; } + + bool TimedOut(); + + std::string GetPeerIdHeader() const; + + bool NotifyOfOtherMember(const ChannelMember& other); + + // Returns a string in the form "name,id\n". + std::string GetEntry() const; + + void ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer); + + void OnClosing(DataSocket* ds); + + void QueueResponse(const std::string& status, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data); + + void SetWaitingSocket(DataSocket* ds); + + protected: + struct QueuedResponse { + std::string status, content_type, extra_headers, data; + }; + + DataSocket* waiting_socket_; + int id_; + bool connected_; + time_t timestamp_; + std::string name_; + std::queue<QueuedResponse> queue_; + static int s_member_id_; +}; + +// Manages all currently connected peers. +class PeerChannel { + public: + typedef std::vector<ChannelMember*> Members; + + PeerChannel() {} + + ~PeerChannel() { DeleteAll(); } + + const Members& members() const { return members_; } + + // Returns true if the request should be treated as a new ChannelMember + // request. Otherwise the request is not peerconnection related. + static bool IsPeerConnection(const DataSocket* ds); + + // Finds a connected peer that's associated with the `ds` socket. + ChannelMember* Lookup(DataSocket* ds) const; + + // Checks if the request has a "peer_id" parameter and if so, looks up the + // peer for which the request is targeted at. + ChannelMember* IsTargetedRequest(const DataSocket* ds) const; + + // Adds a new ChannelMember instance to the list of connected peers and + // associates it with the socket. + bool AddMember(DataSocket* ds); + + // Closes all connections and sends a "shutting down" message to all + // connected peers. + void CloseAll(); + + // Called when a socket was determined to be closing by the peer (or if the + // connection went dead). + void OnClosing(DataSocket* ds); + + void CheckForTimeout(); + + protected: + void DeleteAll(); + void BroadcastChangedState(const ChannelMember& member, + Members* delivery_failures); + void HandleDeliveryFailures(Members* failures); + + // Builds a simple list of "name,id\n" entries for each member. + std::string BuildResponseForNewMember(const ChannelMember& member, + std::string* content_type); + + protected: + Members members_; +}; + +#endif // EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/server/server_test.html b/third_party/libwebrtc/examples/peerconnection/server/server_test.html new file mode 100644 index 0000000000..0a165f19d5 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/server_test.html @@ -0,0 +1,237 @@ +<html> +<head> +<title>PeerConnection server test page</title> + +<script> +var request = null; +var hangingGet = null; +var localName; +var server; +var my_id = -1; +var other_peers = {}; +var message_counter = 0; + +function trace(txt) { + var elem = document.getElementById("debug"); + elem.innerHTML += txt + "<br>"; +} + +function handleServerNotification(data) { + trace("Server notification: " + data); + var parsed = data.split(','); + if (parseInt(parsed[2]) != 0) + other_peers[parseInt(parsed[1])] = parsed[0]; +} + +function handlePeerMessage(peer_id, data) { + ++message_counter; + var str = "Message from '" + other_peers[peer_id] + "' "; + str += "<span id='toggle_" + message_counter + "' onclick='toggleMe(this);' "; + str += "style='cursor: pointer'>+</span><br>"; + str += "<blockquote id='msg_" + message_counter + "' style='display:none'>"; + str += data + "</blockquote>"; + trace(str); + if (document.getElementById("loopback").checked) { + if (data.search("offer") != -1) { + // In loopback mode, if DTLS is enabled, notify the client to disable it. + // Otherwise replace the offer with an answer. + if (data.search("fingerprint") != -1) + data = data.replace("offer", "offer-loopback"); + else + data = data.replace("offer", "answer"); + } + sendToPeer(peer_id, data); + } +} + +function GetIntHeader(r, name) { + var val = r.getResponseHeader(name); + return val != null && val.length ? parseInt(val) : -1; +} + +function hangingGetCallback() { + try { + if (hangingGet.readyState != 4) + return; + if (hangingGet.status != 200) { + trace("server error: " + hangingGet.statusText); + disconnect(); + } else { + var peer_id = GetIntHeader(hangingGet, "Pragma"); + if (peer_id == my_id) { + handleServerNotification(hangingGet.responseText); + } else { + handlePeerMessage(peer_id, hangingGet.responseText); + } + } + + if (hangingGet) { + hangingGet.abort(); + hangingGet = null; + } + + if (my_id != -1) + window.setTimeout(startHangingGet, 0); + } catch (e) { + trace("Hanging get error: " + e.description); + } +} + +function startHangingGet() { + try { + hangingGet = new XMLHttpRequest(); + hangingGet.onreadystatechange = hangingGetCallback; + hangingGet.ontimeout = onHangingGetTimeout; + hangingGet.open("GET", server + "/wait?peer_id=" + my_id, true); + hangingGet.send(); + } catch (e) { + trace("error" + e.description); + } +} + +function onHangingGetTimeout() { + trace("hanging get timeout. issuing again."); + hangingGet.abort(); + hangingGet = null; + if (my_id != -1) + window.setTimeout(startHangingGet, 0); +} + +function signInCallback() { + try { + if (request.readyState == 4) { + if (request.status == 200) { + var peers = request.responseText.split("\n"); + my_id = parseInt(peers[0].split(',')[1]); + trace("My id: " + my_id); + for (var i = 1; i < peers.length; ++i) { + if (peers[i].length > 0) { + trace("Peer " + i + ": " + peers[i]); + var parsed = peers[i].split(','); + other_peers[parseInt(parsed[1])] = parsed[0]; + } + } + startHangingGet(); + request = null; + } + } + } catch (e) { + trace("error: " + e.description); + } +} + +function signIn() { + try { + request = new XMLHttpRequest(); + request.onreadystatechange = signInCallback; + request.open("GET", server + "/sign_in?" + localName, true); + request.send(); + } catch (e) { + trace("error: " + e.description); + } +} + +function sendToPeer(peer_id, data) { + if (my_id == -1) { + alert("Not connected"); + return; + } + if (peer_id == my_id) { + alert("Can't send a message to oneself :)"); + return; + } + var r = new XMLHttpRequest(); + r.open("POST", server + "/message?peer_id=" + my_id + "&to=" + peer_id, + false); + r.setRequestHeader("Content-Type", "text/plain"); + r.send(data); + r = null; +} + +function connect() { + localName = document.getElementById("local").value.toLowerCase(); + server = document.getElementById("server").value.toLowerCase(); + if (localName.length == 0) { + alert("I need a name please."); + document.getElementById("local").focus(); + } else { + document.getElementById("connect").disabled = true; + document.getElementById("disconnect").disabled = false; + document.getElementById("send").disabled = false; + signIn(); + } +} + +function disconnect() { + if (request) { + request.abort(); + request = null; + } + + if (hangingGet) { + hangingGet.abort(); + hangingGet = null; + } + + if (my_id != -1) { + request = new XMLHttpRequest(); + request.open("GET", server + "/sign_out?peer_id=" + my_id, false); + request.send(); + request = null; + my_id = -1; + } + + document.getElementById("connect").disabled = false; + document.getElementById("disconnect").disabled = true; + document.getElementById("send").disabled = true; +} + +window.onbeforeunload = disconnect; + +function send() { + var text = document.getElementById("message").value; + var peer_id = parseInt(document.getElementById("peer_id").value); + if (!text.length || peer_id == 0) { + alert("No text supplied or invalid peer id"); + } else { + sendToPeer(peer_id, text); + } +} + +function toggleMe(obj) { + var id = obj.id.replace("toggle", "msg"); + var t = document.getElementById(id); + if (obj.innerText == "+") { + obj.innerText = "-"; + t.style.display = "block"; + } else { + obj.innerText = "+"; + t.style.display = "none"; + } +} + +</script> + +</head> +<body> +Server: <input type="text" id="server" value="http://localhost:8888" /><br> +<input type="checkbox" id="loopback" checked="checked"/> Loopback (just send +received messages right back)<br> +Your name: <input type="text" id="local" value="my_name"/> +<button id="connect" onclick="connect();">Connect</button> +<button disabled="true" id="disconnect" + onclick="disconnect();">Disconnect</button> +<br> +<table><tr><td> +Target peer id: <input type="text" id="peer_id" size="3"/></td><td> +Message: <input type="text" id="message"/></td><td> +<button disabled="true" id="send" onclick="send();">Send</button> +</td></tr></table> +<button onclick="document.getElementById('debug').innerHTML='';"> +Clear log</button> + +<pre id="debug"> +</pre> +<br><hr> +</body> +</html> diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.cc b/third_party/libwebrtc/examples/peerconnection/server/utils.cc new file mode 100644 index 0000000000..5e61e601d9 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/utils.cc @@ -0,0 +1,25 @@ +/* + * Copyright 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 "examples/peerconnection/server/utils.h" + +#include <stdio.h> + +#include "rtc_base/string_encode.h" + +using rtc::ToString; + +std::string int2str(int i) { + return ToString(i); +} + +std::string size_t2str(size_t i) { + return ToString(i); +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.h b/third_party/libwebrtc/examples/peerconnection/server/utils.h new file mode 100644 index 0000000000..85c04a40e9 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/utils.h @@ -0,0 +1,25 @@ +/* + * Copyright 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 EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ + +#include <stddef.h> + +#include <string> + +#ifndef ARRAYSIZE +#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) +#endif + +std::string int2str(int i); +std::string size_t2str(size_t i); + +#endif // EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ |