diff --git a/CONTEXT.md b/CONTEXT.md index f7036b6..3b9d002 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -8,12 +8,14 @@ It contains **no implementation details** — only term definitions. ## Terms ### Host -A peer that initiates a WebRTC session by opening a TCP listener and waiting for -an incoming connection before creating an SDP offer. +The API root object used as the non-browser equivalent of `Navigator`, including +access to `MediaDevices`. -### Guest -A peer that joins a WebRTC session by dialling the Host's IP address and port, -then responding to the Host's SDP offer with an SDP answer. +### Caller +A peer that initiates a WebRTC session by creating and sending the SDP offer. + +### Callee +A peer that receives the SDP offer and responds with an SDP answer. ### Signaling The out-of-band exchange of SDP offers/answers and ICE candidates between two @@ -32,3 +34,13 @@ a direct TCP signaling channel, targeting both .NET 10 and .NET Framework 4.8. A prototype `VideoRenderer` implementation backed by a WPF `WriteableBitmap`. Lives in `examples/BasicVideoChat` until a proper `WebRtcNet.Wpf` renderer assembly is created (see issue #36). + +## Relationships + +- **Host** exposes **MediaDevices** as the API entry point for capture access. +- A **Caller** sends an SDP offer and a **Callee** returns an SDP answer. + +## Flagged ambiguities + +- "Host" previously meant the signaling initiator peer; resolved to the API root + object. Signaling roles are now **Caller** and **Callee**. diff --git a/WebRtcInterop/Media/MediaDevices.cpp b/WebRtcInterop/Media/MediaDevices.cpp new file mode 100644 index 0000000..3a7ffd7 --- /dev/null +++ b/WebRtcInterop/Media/MediaDevices.cpp @@ -0,0 +1,26 @@ +#include "pch.h" + +#include "MediaDevices.h" + +using namespace System::Collections::Generic; +using namespace System::Threading::Tasks; + +namespace WebRtcInterop::Media +{ + Task^>^ MediaDevices::EnumerateDevices() + { + auto devices = gcnew List(); + return Task::FromResult^>(devices); + } + + WebRtcNet::Media::MediaTrackSupportedConstraints^ MediaDevices::GetSupportedConstraints() + { + return gcnew WebRtcNet::Media::MediaTrackSupportedConstraints(); + } + + Task^ MediaDevices::GetUserMedia(WebRtcNet::Media::MediaStreamConstraints^ constraints) + { + return Task::FromException( + gcnew WebRtcNet::Media::MediaStreamException("GetUserMedia is not currently implemented.")); + } +} diff --git a/WebRtcInterop/Media/MediaDevices.h b/WebRtcInterop/Media/MediaDevices.h new file mode 100644 index 0000000..7ee6a2f --- /dev/null +++ b/WebRtcInterop/Media/MediaDevices.h @@ -0,0 +1,25 @@ +#pragma once + +namespace WebRtcInterop::Media +{ + using namespace System; + using namespace System::Collections::Generic; + using namespace System::Threading::Tasks; + + public ref class MediaDevices : WebRtcNet::Media::MediaDevices + { + public: + virtual event EventHandler^ OnDeviceChange + { + void add(EventHandler^ value) override { on_device_change_ += value; } + void remove(EventHandler^ value) override { on_device_change_ -= value; } + } + + virtual Task^>^ EnumerateDevices() override; + virtual WebRtcNet::Media::MediaTrackSupportedConstraints^ GetSupportedConstraints() override; + virtual Task^ GetUserMedia(WebRtcNet::Media::MediaStreamConstraints^ constraints) override; + + private: + EventHandler^ on_device_change_; + }; +} diff --git a/WebRtcInterop/Media/MediaDevicesFactory.cpp b/WebRtcInterop/Media/MediaDevicesFactory.cpp new file mode 100644 index 0000000..ccf3613 --- /dev/null +++ b/WebRtcInterop/Media/MediaDevicesFactory.cpp @@ -0,0 +1,12 @@ +#include "pch.h" + +#include "MediaDevicesFactory.h" +#include "MediaDevices.h" + +namespace WebRtcInterop::Media +{ + WebRtcNet::Media::MediaDevices^ MediaDevicesFactory::CreateMediaDevices() + { + return gcnew MediaDevices(); + } +} diff --git a/WebRtcInterop/Media/MediaDevicesFactory.h b/WebRtcInterop/Media/MediaDevicesFactory.h new file mode 100644 index 0000000..431f98b --- /dev/null +++ b/WebRtcInterop/Media/MediaDevicesFactory.h @@ -0,0 +1,10 @@ +#pragma once + +namespace WebRtcInterop::Media +{ + public ref class MediaDevicesFactory sealed + { + public: + static WebRtcNet::Media::MediaDevices^ CreateMediaDevices(); + }; +} diff --git a/WebRtcInterop/Media/MediaStream.cpp b/WebRtcInterop/Media/MediaStream.cpp index 3f1c516..5e14b82 100644 --- a/WebRtcInterop/Media/MediaStream.cpp +++ b/WebRtcInterop/Media/MediaStream.cpp @@ -12,23 +12,6 @@ using namespace WebRtcNet; namespace WebRtcInterop::Media { - Task^>^ MediaDevices::EnumerateDevices() - { - auto devices = gcnew List(); - return Task::FromResult^>(devices); - } - - MediaTrackSupportedConstraints^ MediaDevices::GetSupportedConstraints() - { - return gcnew MediaTrackSupportedConstraints(); - } - - Task^ MediaDevices::GetUserMedia(MediaStreamConstraints^ constraints) - { - return Task::FromException( - gcnew MediaStreamException("GetUserMedia is not currently implemented.")); - } - MediaStream::MediaStream(WebRtcNet::Media::MediaStream^ stream) : _rpMediaStreamInterface(nullptr), on_active_(nullptr), diff --git a/WebRtcInterop/Media/MediaStream.h b/WebRtcInterop/Media/MediaStream.h index 639f317..c7b27cf 100644 --- a/WebRtcInterop/Media/MediaStream.h +++ b/WebRtcInterop/Media/MediaStream.h @@ -66,20 +66,4 @@ namespace WebRtcInterop::Media EventHandler^ on_remove_track_; }; - public ref class MediaDevices : WebRtcNet::Media::MediaDevices - { - public: - virtual event EventHandler^ OnDeviceChange - { - void add(EventHandler^ value) { on_device_change_ += value; } - void remove(EventHandler^ value) { on_device_change_ -= value; } - } - - virtual Task^>^ EnumerateDevices(); - virtual WebRtcNet::Media::MediaTrackSupportedConstraints^ GetSupportedConstraints(); - virtual Task^ GetUserMedia(WebRtcNet::Media::MediaStreamConstraints^ constraints); - - private: - EventHandler^ on_device_change_; - }; } diff --git a/WebRtcInterop/RtcPeerConnection.cpp b/WebRtcInterop/RtcPeerConnection.cpp index 4a68b09..87322ef 100644 --- a/WebRtcInterop/RtcPeerConnection.cpp +++ b/WebRtcInterop/RtcPeerConnection.cpp @@ -1,209 +1,153 @@ -#include "stdafx.h" - -#include +#include "pch.h" +#include "RtcPeerConnection.h" using namespace System; -using namespace Collections::Generic; -using namespace Threading::Tasks; -using namespace Runtime::InteropServices; - -using namespace WebRtcNet; - -#include "RtcPeerConnection.h" -#include "RtcPeerConnectionFactory.h" -#include "MediaStream.h" -#include "Observers/PeerConnectionObserver.h" -#include "Observers/CreateSessionDescriptionObserver.h" -#include "Marshaling/MarshalPeerConnection.h" -#include "Marshaling/MarshalRtcConfiguration.h" -#include "Marshaling/MarshalMediaConstraints.h" +using namespace System::Collections::Generic; +using namespace System::Threading::Tasks; namespace WebRtcInterop { RtcPeerConnection::RtcPeerConnection(RtcConfiguration^ configuration) - : observer_(new webrtc_observers::PeerConnectionObserver(this)) - , configuration_(configuration) + : configuration_(configuration), + is_closed_(false), + on_negotiation_needed_(nullptr), + on_ice_candidate_(nullptr), + on_ice_candidate_error_(nullptr), + on_signaling_state_change_(nullptr), + on_ice_connection_state_change_(nullptr), + on_gathering_state_change_(nullptr), + on_connection_state_change_(nullptr), + on_track_(nullptr), + on_data_channel_(nullptr) { - auto nativePeerConnectionFactory = RtcPeerConnectionFactory::Instance-> - GetNativePeerConnectionFactoryInterface(true); - - auto nativeConfig = marshal_as(configuration); - auto nativePeerConnection = nativePeerConnectionFactory->CreatePeerConnection( - nativeConfig, nullptr, nullptr, observer_); - if (nativePeerConnection == nullptr) throw gcnew - NotSupportedException("Failed to create native PeerConnection"); - - rp_peer_connection_ = new rtc::scoped_refptr(nativePeerConnection); + if (configuration == nullptr) + throw gcnew ArgumentNullException("configuration"); } - RtcPeerConnection::~RtcPeerConnection() + void RtcPeerConnection::ThrowShimNotImplemented(String^ memberName) { - this->!RtcPeerConnection(); + throw gcnew NotImplementedException(String::Format( + "{0} is a compile-only shim in WebRtcInterop and is not implemented yet.", + memberName)); } - RtcPeerConnection::!RtcPeerConnection() + IntPtr RtcPeerConnection::GetNativePeerConnectionHandle(bool throwOnDisposed) { - delete rp_peer_connection_; - rp_peer_connection_ = nullptr; - + if (throwOnDisposed && is_closed_) + throw gcnew ObjectDisposedException(NAMEOF(RtcPeerConnection)); - delete observer_; - observer_ = nullptr; + return IntPtr::Zero; } - webrtc::PeerConnectionInterface* RtcPeerConnection::GetNativePeerConnection(bool throwOnDisposed) + Nullable RtcPeerConnection::LocalDescription::get() { return Nullable(); } + Nullable RtcPeerConnection::CurrentLocalDescription::get() { return Nullable(); } + Nullable RtcPeerConnection::PendingLocalDescription::get() { return Nullable(); } + Nullable RtcPeerConnection::RemoteDescription::get() { return Nullable(); } + Nullable RtcPeerConnection::CurrentRemoteDescription::get() { return Nullable(); } + Nullable RtcPeerConnection::PendingRemoteDescription::get() { return Nullable(); } + RtcSignalingState RtcPeerConnection::SignalingState::get() { return is_closed_ ? RtcSignalingState::Closed : RtcSignalingState::Stable; } + RtcIceGatheringState RtcPeerConnection::IceGatheringState::get() { return is_closed_ ? RtcIceGatheringState::Complete : RtcIceGatheringState::New; } + RtcIceConnectionState RtcPeerConnection::IceConnectionState::get() { return is_closed_ ? RtcIceConnectionState::Closed : RtcIceConnectionState::New; } + RtcPeerConnectionState RtcPeerConnection::ConnectionState::get() { return is_closed_ ? RtcPeerConnectionState::Closed : RtcPeerConnectionState::New; } + Nullable RtcPeerConnection::CanTrickleIceCandidates::get() { return Nullable(); } + RtcConfiguration^ RtcPeerConnection::Configuration::get() { return configuration_; } + void RtcPeerConnection::Configuration::set(RtcConfiguration^ configuration) { - if (rp_peer_connection_ == nullptr || rp_peer_connection_->get() == nullptr) - { - if (throwOnDisposed) throw gcnew ObjectDisposedException("RtcPeerConnection"); - return nullptr; - } - - return rp_peer_connection_->get(); + if (configuration == nullptr) + throw gcnew ArgumentNullException("configuration"); + configuration_ = configuration; } - System::IntPtr RtcPeerConnection::GetNativePeerConnectionHandle(bool throwOnDisposed) - { - return System::IntPtr(GetNativePeerConnection(throwOnDisposed)); - } + RtcSctpTransport^ RtcPeerConnection::Sctp::get() { return nullptr; } Task^ RtcPeerConnection::CreateOffer(RtcOfferOptions^ options) { - auto pc = GetNativePeerConnection(true); - auto observer = new rtc::RefCountedObject(); - auto task = observer->CreateSessionTask(); - - if (options == nullptr) - { - pc->CreateOffer(observer, NULL); - } - else - { - webrtc::FakeConstraints constraints; - constraints.AddMandatory(webrtc::MediaConstraintsInterface::kVoiceActivityDetection, - static_cast(options->VoiceActivityDetection)); - constraints.AddMandatory(webrtc::MediaConstraintsInterface::kIceRestart, - static_cast(options->IceRestart)); - - pc->CreateOffer(observer, &constraints); - } - - return task; + ThrowShimNotImplemented("RtcPeerConnection.CreateOffer"); + return nullptr; } - - Task^ RtcPeerConnection::CreateAnswer() + Task^ RtcPeerConnection::CreateAnswer(RtcAnswerOptions^ options) { - auto pc = GetNativePeerConnection(true); - auto observer = new rtc::RefCountedObject(); - auto task = observer->CreateSessionTask(); - - pc->CreateAnswer(observer, NULL); - - return task; + ThrowShimNotImplemented("RtcPeerConnection.CreateAnswer"); + return nullptr; } Task^ RtcPeerConnection::SetLocalDescription(Nullable description) { - // TODO: Implement using two native overloads on PeerConnectionInterface: - // - description is null, or description.Value.Type is null: - // -> SetLocalDescription(observer) [native creates offer/answer from signaling state] - // - description.Value.Type has a value: - // -> SetLocalDescription(unique_ptr, observer) - // Add marshal_as(RtcLocalSessionDescriptionInit) in - // MarshalPeerConnection.h to support the second path. - throw gcnew NotImplementedException(); + ThrowShimNotImplemented("RtcPeerConnection.SetLocalDescription"); + return nullptr; } Task^ RtcPeerConnection::SetRemoteDescription(RtcSessionDescription description) { - throw gcnew NotImplementedException(); - // TODO: insert return statement here + ThrowShimNotImplemented("RtcPeerConnection.SetRemoteDescription"); + return nullptr; } - Task^ RtcPeerConnection::AddIceCandidate(RtcIceCandidate^ candidate) { - throw gcnew NotImplementedException(); - // TODO: insert return statement here - } - - RtcConfiguration^ RtcPeerConnection::Configuration::get() - { - return configuration_; + ThrowShimNotImplemented("RtcPeerConnection.AddIceCandidate"); + return nullptr; } - void RtcPeerConnection::Configuration::set(RtcConfiguration^ configuration) + void RtcPeerConnection::RestartIce() { - throw gcnew NotImplementedException(); + ThrowShimNotImplemented("RtcPeerConnection.RestartIce"); } - IEnumerable^ RtcPeerConnection::LocalStreams::get() - { - throw gcnew NotImplementedException(); - // TODO: insert return statement here - } - - - IEnumerable^ RtcPeerConnection::RemoteStreams::get() + void RtcPeerConnection::Close() { - throw gcnew NotImplementedException(); - // TODO: insert return statement here + is_closed_ = true; } - MediaStream^ RtcPeerConnection::GetStreamById(String^ streamId) + Task^ RtcPeerConnection::GetStats(WebRtcNet::Media::MediaStreamTrack^ selector) { - throw gcnew NotImplementedException(); - // TODO: insert return statement here + ThrowShimNotImplemented("RtcPeerConnection.GetStats"); + return nullptr; } - void RtcPeerConnection::AddStream(MediaStream^ stream) + IEnumerable^ RtcPeerConnection::GetSenders() { - auto nativePeerConnection = GetNativePeerConnection(true); - auto nativeStream = reinterpret_cast( - stream->GetNativeMediaStreamInterface(true).ToPointer()); - nativePeerConnection->AddStream(nativeStream); + return gcnew List(); } - void RtcPeerConnection::RemoveStream(MediaStream^ stream) + IEnumerable^ RtcPeerConnection::GetReceivers() { - auto nativePeerConnection = GetNativePeerConnection(true); - auto nativeStream = reinterpret_cast( - stream->GetNativeMediaStreamInterface(true).ToPointer()); - nativePeerConnection->RemoveStream(nativeStream); + return gcnew List(); } - void RtcPeerConnection::Close() + IEnumerable^ RtcPeerConnection::GetTransceivers() { - throw gcnew NotImplementedException(); + return gcnew List(); } - RtcDataChannel^ RtcPeerConnection::CreateDataChannel(String^ label, RtcDataChannelInit^ dataChannelInit) + RtcRtpSender^ RtcPeerConnection::AddTrack(WebRtcNet::Media::MediaStreamTrack^ track, ... array^ streams) { - throw gcnew NotImplementedException(); - // TODO: insert return statement here + ThrowShimNotImplemented("RtcPeerConnection.AddTrack"); + return nullptr; } - RtcDtmfSender^ RtcPeerConnection::CreateRtcDtmfSender(MediaStreamTrack^ track) + RtcRtpTransceiver^ RtcPeerConnection::AddTransceiver(WebRtcNet::Media::MediaStreamTrack^ track, RtcRtpTransceiverInit^ init) { - throw gcnew NotImplementedException(); - // TODO: insert return statement here + ThrowShimNotImplemented("RtcPeerConnection.AddTransceiver(track)"); + return nullptr; } - Task^ RtcPeerConnection::GetStats(MediaStreamTrack^ selector) + RtcRtpTransceiver^ RtcPeerConnection::AddTransceiver(WebRtcNet::Media::MediaStreamTrackKind kind, RtcRtpTransceiverInit^ init) { - throw gcnew NotImplementedException(); - // TODO: insert return statement here + ThrowShimNotImplemented("RtcPeerConnection.AddTransceiver(kind)"); + return nullptr; } - void RtcPeerConnection::SetIdentityProvider(String^ provider, String^ protocol, String^ username) + void RtcPeerConnection::RemoveTrack(RtcRtpSender^ sender) { - throw gcnew NotImplementedException(); + ThrowShimNotImplemented("RtcPeerConnection.RemoveTrack"); } - void RtcPeerConnection::GetIdentityAssertion() + RtcDataChannel^ RtcPeerConnection::CreateDataChannel(String^ label, RtcDataChannelInit^ dataChannelInit) { - throw gcnew NotImplementedException(); + ThrowShimNotImplemented("RtcPeerConnection.CreateDataChannel"); + return nullptr; } } diff --git a/WebRtcInterop/RtcPeerConnection.h b/WebRtcInterop/RtcPeerConnection.h index 3908c46..fdd9d14 100644 --- a/WebRtcInterop/RtcPeerConnection.h +++ b/WebRtcInterop/RtcPeerConnection.h @@ -1,43 +1,30 @@ #pragma once -namespace rtc -{ - template - class scoped_refptr; -} - -namespace webrtc -{ - class PeerConnectionInterface; -} - -WebRtcObservers_Start - class PeerConnectionObserver; -WebRtcObservers_End - namespace WebRtcInterop { + using namespace System; + using namespace System::Collections::Generic; + using namespace System::Threading::Tasks; + using namespace WebRtcNet; + public ref class RtcPeerConnection : WebRtcNet::RtcPeerConnection { public: RtcPeerConnection(RtcConfiguration^ configuration); - ~RtcPeerConnection(); - // Inherited via RtcPeerConnection virtual property Nullable LocalDescription { Nullable get() override; } virtual property Nullable CurrentLocalDescription { Nullable get() override; } virtual property Nullable PendingLocalDescription { Nullable get() override; } - virtual property Nullable RemoteDescription { Nullable get() override; } virtual property Nullable CurrentRemoteDescription { Nullable get() override; } virtual property Nullable PendingRemoteDescription { Nullable get() override; } - - virtual property RtcPeerConnectionState ConnectionState { RtcPeerConnectionState get() override; } virtual property RtcSignalingState SignalingState { RtcSignalingState get() override; } virtual property RtcIceGatheringState IceGatheringState { RtcIceGatheringState get() override; } virtual property RtcIceConnectionState IceConnectionState { RtcIceConnectionState get() override; } - virtual property bool CanTrickleIceCandidates { bool get() override; } + virtual property RtcPeerConnectionState ConnectionState { RtcPeerConnectionState get() override; } + virtual property Nullable CanTrickleIceCandidates { Nullable get() override; } virtual property RtcConfiguration^ Configuration { RtcConfiguration^ get() override; void set(RtcConfiguration^ configuration) override; } + virtual property RtcSctpTransport^ Sctp { RtcSctpTransport^ get() override; } virtual event EventHandler^ OnNegotiationNeeded { @@ -49,6 +36,11 @@ namespace WebRtcInterop void add(EventHandler^ value) override { on_ice_candidate_ += value; } void remove(EventHandler^ value) override { on_ice_candidate_ -= value; } } + virtual event EventHandler^ OnIceCandidateError + { + void add(EventHandler^ value) override { on_ice_candidate_error_ += value; } + void remove(EventHandler^ value) override { on_ice_candidate_error_ -= value; } + } virtual event EventHandler^ OnSignalingStateChange { void add(EventHandler^ value) override { on_signaling_state_change_ += value; } @@ -64,16 +56,6 @@ namespace WebRtcInterop void add(EventHandler^ value) override { on_gathering_state_change_ += value; } void remove(EventHandler^ value) override { on_gathering_state_change_ -= value; } } - virtual event EventHandler^ OnDataChannel - { - void add(EventHandler^ value) override { on_data_channel_ += value; } - void remove(EventHandler^ value) override { on_data_channel_ -= value; } - } - virtual event EventHandler^ OnIceCandidateError - { - void add(EventHandler^ value) override { on_ice_candidate_error_ += value; } - void remove(EventHandler^ value) override { on_ice_candidate_error_ -= value; } - } virtual event EventHandler^ OnConnectionStateChange { void add(EventHandler^ value) override { on_connection_state_change_ += value; } @@ -84,56 +66,47 @@ namespace WebRtcInterop void add(EventHandler^ value) override { on_track_ += value; } void remove(EventHandler^ value) override { on_track_ -= value; } } + virtual event EventHandler^ OnDataChannel + { + void add(EventHandler^ value) override { on_data_channel_ += value; } + void remove(EventHandler^ value) override { on_data_channel_ -= value; } + } virtual Task^ CreateOffer([System::Runtime::InteropServices::Optional] RtcOfferOptions^ options) override; virtual Task^ CreateAnswer([System::Runtime::InteropServices::Optional] RtcAnswerOptions^ options) override; - - virtual Task^ AddIceCandidate(RtcIceCandidate^ candidate) override; - virtual void RestartIce() override; - - virtual Task^ SetLocalDescription(Nullable description) override; + virtual Task^ SetLocalDescription([System::Runtime::InteropServices::Optional] Nullable description) override; virtual Task^ SetRemoteDescription(RtcSessionDescription description) override; - - virtual RtcRtpSender^ AddTrack(WebRtcNet::Media::MediaStreamTrack^ track, - ... array^ streams) override; - virtual void RemoveTrack(WebRtcNet::Media::MediaStreamTrack^ track) override; + virtual Task^ AddIceCandidate([System::Runtime::InteropServices::Optional] RtcIceCandidate^ candidate) override; + virtual void RestartIce() override; + virtual void Close() override; + virtual Task^ GetStats([System::Runtime::InteropServices::Optional] WebRtcNet::Media::MediaStreamTrack^ selector) override; virtual IEnumerable^ GetSenders() override; virtual IEnumerable^ GetReceivers() override; virtual IEnumerable^ GetTransceivers() override; + virtual RtcRtpSender^ AddTrack(WebRtcNet::Media::MediaStreamTrack^ track, ... array^ streams) override; + virtual RtcRtpTransceiver^ AddTransceiver(WebRtcNet::Media::MediaStreamTrack^ track, [System::Runtime::InteropServices::Optional] RtcRtpTransceiverInit^ init) override; + virtual RtcRtpTransceiver^ AddTransceiver(WebRtcNet::Media::MediaStreamTrackKind kind, [System::Runtime::InteropServices::Optional] RtcRtpTransceiverInit^ init) override; + virtual void RemoveTrack(RtcRtpSender^ sender) override; + virtual RtcDataChannel^ CreateDataChannel(String^ label, [System::Runtime::InteropServices::Optional] RtcDataChannelInit^ dataChannelInit) override; - virtual RtcDataChannel^ CreateDataChannel(String^ label, RtcDataChannelInit^ dataChannelInit) override; - - virtual void Close() override; - - virtual Task^ GetStats([System::Runtime::InteropServices::Optional] WebRtcNet::Media::MediaStreamTrack^ selector) override; - - internal: - !RtcPeerConnection(); - webrtc::PeerConnectionInterface* GetNativePeerConnection(bool throwOnDisposed); - virtual System::IntPtr GetNativePeerConnectionHandle(bool throwOnDisposed); - - //Event invocation - void FireOnSignalingStateChange(RtcSignalingState newState) { if (on_signaling_state_change_ != nullptr) on_signaling_state_change_(this, EventArgs::Empty); } - void FireOnDataChannel(RtcDataChannel^ channel) { if (on_data_channel_ != nullptr) on_data_channel_(this, gcnew RtcDataChannelEventArgs(channel)); } - void FireOnNegotiationNeeded() { if (on_negotiation_needed_ != nullptr) on_negotiation_needed_(this, EventArgs::Empty); } - void FireOnIceConnectionStateChange(RtcIceConnectionState newState) { if (on_ice_connection_state_change_ != nullptr) on_ice_connection_state_change_(this, EventArgs::Empty); } - void FireOnGatheringStateChange(RtcIceGatheringState newState) { if (on_gathering_state_change_ != nullptr) on_gathering_state_change_(this, EventArgs::Empty); } - void FireOnConnectionStateChange() { if (on_connection_state_change_ != nullptr) on_connection_state_change_(this, EventArgs::Empty); } - void FireOnIceCandidate(RtcIceCandidate^ candidate) { if (on_ice_candidate_ != nullptr) on_ice_candidate_(this, gcnew RtcIceCandidateEventArgs(candidate)); } + protected: + virtual IntPtr GetNativePeerConnectionHandle(bool throwOnDisposed) override; private: - rtc::scoped_refptr* rp_peer_connection_; - webrtc_observers::PeerConnectionObserver* observer_; + void ThrowShimNotImplemented(String^ memberName); + RtcConfiguration^ configuration_; + bool is_closed_; + EventHandler^ on_negotiation_needed_; EventHandler^ on_ice_candidate_; + EventHandler^ on_ice_candidate_error_; EventHandler^ on_signaling_state_change_; EventHandler^ on_ice_connection_state_change_; EventHandler^ on_gathering_state_change_; - EventHandler^ on_data_channel_; - EventHandler^ on_ice_candidate_error_; EventHandler^ on_connection_state_change_; EventHandler^ on_track_; + EventHandler^ on_data_channel_; }; } diff --git a/WebRtcInterop/RtcPeerConnectionFactory.cpp b/WebRtcInterop/RtcPeerConnectionFactory.cpp index e46fecb..61329f9 100644 --- a/WebRtcInterop/RtcPeerConnectionFactory.cpp +++ b/WebRtcInterop/RtcPeerConnectionFactory.cpp @@ -1,107 +1,128 @@ -#include "stdafx.h" +#include "pch.h" + #include "RtcPeerConnectionFactory.h" +#include "RtcPeerConnection.h" -#include -#include +#include #include - -#include -#include -#include -#include -#include +#include using namespace System; -using namespace System::Threading; -using namespace System::Threading::Tasks; -using namespace System::Runtime::InteropServices; -RtcPeerConnectionFactory::RtcPeerConnectionFactory() - : _main_thread(&_ss) +namespace WebRtcInterop { - if (_signalThread == nullptr) + namespace { - _signalThread = rtc::Thread::CreateWithSocketServer(); - _signalThread->SetName("WebRtc Signal Thread", NULL); - _signalThread->Start(); + std::unique_ptr network_thread_; + std::unique_ptr worker_thread_; + std::unique_ptr signaling_thread_; } - auto action = gcnew Action(this, &RtcPeerConnectionFactory::CreateNativePeerConnectionFactory); - _signalThread->Invoke(rtc::Bind((void (*)())Marshal::GetFunctionPointerForDelegate(action).ToPointer())); -} + RtcPeerConnectionFactory::RtcPeerConnectionFactory() + : _rpPeerConnectionFactory(nullptr) + { + if (network_thread_ == nullptr) + { + network_thread_ = webrtc::Thread::CreateWithSocketServer(); + network_thread_->SetName("WebRtc Network Thread", nullptr); + network_thread_->Start(); + } + + if (worker_thread_ == nullptr) + { + worker_thread_ = webrtc::Thread::Create(); + worker_thread_->SetName("WebRtc Worker Thread", nullptr); + worker_thread_->Start(); + } + + if (signaling_thread_ == nullptr) + { + signaling_thread_ = webrtc::Thread::Create(); + signaling_thread_->SetName("WebRtc Signaling Thread", nullptr); + signaling_thread_->Start(); + } + + CreateNativePeerConnectionFactory(); + } + RtcPeerConnectionFactory::~RtcPeerConnectionFactory() + { + this->!RtcPeerConnectionFactory(); + } -void RtcPeerConnectionFactory::CreateNativePeerConnectionFactory() -{ - rtc::InitializeSSL(); - - auto nativePeerConnectionFactory = webrtc::CreatePeerConnectionFactory( - nullptr /* network_thread */, - nullptr /* worker_thread */, - _signalThread.get(), - nullptr /* default_adm */, - webrtc::CreateBuiltinAudioEncoderFactory(), - webrtc::CreateBuiltinAudioDecoderFactory(), - webrtc::CreateBuiltinVideoEncoderFactory(), - webrtc::CreateBuiltinVideoDecoderFactory(), - nullptr /* audio_mixer */, - nullptr /* audio_processing */); - - if (nativePeerConnectionFactory == nullptr) throw gcnew System::NotSupportedException("Failed to create native PeerConnectionFactory"); - _rpPeerConnectionFactory = new rtc::scoped_refptr(nativePeerConnectionFactory); -} + RtcPeerConnectionFactory::!RtcPeerConnectionFactory() + { + DestroyNativePeerConnectionFactory(); + } -void RtcPeerConnectionFactory::DestroyNativePeerConnectionFactory() -{ - delete _rpPeerConnectionFactory; - _rpPeerConnectionFactory = nullptr; + WebRtcNet::RtcPeerConnection^ RtcPeerConnectionFactory::CreatePeerConnection(RtcConfiguration^ configuration) + { + if (configuration == nullptr) + throw gcnew ArgumentNullException("configuration"); + return gcnew RtcPeerConnection(configuration); + } - rtc::CleanupSSL(); -} + void RtcPeerConnectionFactory::CreateNativePeerConnectionFactory() + { + webrtc::PeerConnectionFactoryDependencies dependencies; + dependencies.network_thread = network_thread_.get(); + dependencies.worker_thread = worker_thread_.get(); + dependencies.signaling_thread = signaling_thread_.get(); + auto nativeFactory = webrtc::CreateModularPeerConnectionFactory(std::move(dependencies)); + if (nativeFactory == nullptr) + throw gcnew NotSupportedException("Failed to create native PeerConnectionFactory"); -RtcPeerConnectionFactory::~RtcPeerConnectionFactory() -{ - this->!RtcPeerConnectionFactory(); -} - -RtcPeerConnectionFactory::!RtcPeerConnectionFactory() -{ - auto action = gcnew Action(this, &RtcPeerConnectionFactory::DestroyNativePeerConnectionFactory); - _signalThread->Invoke(rtc::Bind((void(*)())Marshal::GetFunctionPointerForDelegate(action).ToPointer())); -} + _rpPeerConnectionFactory = + new webrtc::scoped_refptr(nativeFactory); + } -webrtc::PeerConnectionFactoryInterface* RtcPeerConnectionFactory::GetNativePeerConnectionFactoryInterface(bool throwOnDisposed) -{ - if (_rpPeerConnectionFactory == nullptr || _rpPeerConnectionFactory->get() == nullptr) + void RtcPeerConnectionFactory::DestroyNativePeerConnectionFactory() { - if (throwOnDisposed) throw gcnew ObjectDisposedException("RtcPeerConnection"); - return nullptr; + delete _rpPeerConnectionFactory; + _rpPeerConnectionFactory = nullptr; } - return _rpPeerConnectionFactory->get(); -} + webrtc::PeerConnectionFactoryInterface* RtcPeerConnectionFactory::GetNativePeerConnectionFactoryInterface(bool throwOnDisposed) + { + if (_rpPeerConnectionFactory == nullptr || _rpPeerConnectionFactory->get() == nullptr) + { + if (throwOnDisposed) + throw gcnew ObjectDisposedException("RtcPeerConnectionFactory"); + return nullptr; + } + + return _rpPeerConnectionFactory->get(); + } -RtcPeerConnectionFactory ^ RtcPeerConnectionFactory::Instance::get() -{ - if (_instance == nullptr) + RtcPeerConnectionFactory^ RtcPeerConnectionFactory::Instance::get() { - InitializeInstance(); + if (_instance == nullptr) + InitializeInstance(); + return _instance; } - return _instance; -} + void RtcPeerConnectionFactory::InitializeInstance() + { + if (_instance != nullptr) + return; + _instance = gcnew RtcPeerConnectionFactory(); + } -void RtcPeerConnectionFactory::InitializeInstance() -{ - if (_instance != nullptr) return; - _instance = gcnew RtcPeerConnectionFactory(); + void RtcPeerConnectionFactory::DestroyInstance() + { + delete _instance; + _instance = nullptr; + + if (signaling_thread_ != nullptr) + signaling_thread_->Stop(); + if (worker_thread_ != nullptr) + worker_thread_->Stop(); + if (network_thread_ != nullptr) + network_thread_->Stop(); + + signaling_thread_.reset(); + worker_thread_.reset(); + network_thread_.reset(); + } } - -void RtcPeerConnectionFactory::DestroyInstance() -{ - delete _instance; - _instance = nullptr; - delete _signalThread; - _signalThread = nullptr; -} \ No newline at end of file diff --git a/WebRtcInterop/RtcPeerConnectionFactory.h b/WebRtcInterop/RtcPeerConnectionFactory.h index 0564b49..10d8e39 100644 --- a/WebRtcInterop/RtcPeerConnectionFactory.h +++ b/WebRtcInterop/RtcPeerConnectionFactory.h @@ -1,51 +1,41 @@ #pragma once -namespace rtc -{ - template class scoped_refptr; - class Thread; -} - namespace webrtc { + template + class scoped_refptr; + class Thread; class PeerConnectionFactoryInterface; } - -private ref class RtcPeerConnectionFactory +namespace WebRtcInterop { -public: - /// - /// Get the singleton RtcPeerConnectionFactory. Calls InitializeInstance if necessary. - /// - static property RtcPeerConnectionFactory ^ Instance{ RtcPeerConnectionFactory ^ get(); } + public ref class RtcPeerConnectionFactory + { + public: + static WebRtcNet::RtcPeerConnection^ CreatePeerConnection(WebRtcNet::RtcConfiguration^ configuration); -internal: - RtcPeerConnectionFactory(); - ~RtcPeerConnectionFactory(); - !RtcPeerConnectionFactory(); + static property RtcPeerConnectionFactory^ Instance + { + RtcPeerConnectionFactory^ get(); + } - static void InitializeInstance(); - static void DestroyInstance(); - - webrtc::PeerConnectionFactoryInterface* GetNativePeerConnectionFactoryInterface(bool throwOnDisposed); + internal: + RtcPeerConnectionFactory(); + ~RtcPeerConnectionFactory(); + !RtcPeerConnectionFactory(); -private: - void CreateNativePeerConnectionFactory(); - void DestroyNativePeerConnectionFactory(); + static void InitializeInstance(); + static void DestroyInstance(); - rtc::scoped_refptr* _rpPeerConnectionFactory; + webrtc::PeerConnectionFactoryInterface* GetNativePeerConnectionFactoryInterface(bool throwOnDisposed); - static RtcPeerConnectionFactory ^ _instance; + private: + void CreateNativePeerConnectionFactory(); + void DestroyNativePeerConnectionFactory(); - static std::unique_ptr _signalThread; + webrtc::scoped_refptr* _rpPeerConnectionFactory; - rtc::WinsockInitializer _winsock_init; - rtc::PhysicalSocketServer _ss; - rtc::AutoSocketServerThread _main_thread; -}; - -/// -/// Thrown when unable to create the native RtcPeerConnetionFactory. -/// -public ref class CreatePeerConnectionFactoryException : System::Exception{}; + static RtcPeerConnectionFactory^ _instance = nullptr; + }; +} diff --git a/WebRtcInterop/WebRtcInterop.Shared.vcxitems b/WebRtcInterop/WebRtcInterop.Shared.vcxitems index 6755a7d..09eeb78 100644 --- a/WebRtcInterop/WebRtcInterop.Shared.vcxitems +++ b/WebRtcInterop/WebRtcInterop.Shared.vcxitems @@ -134,6 +134,8 @@ ninja -C "$(WebRtcOutRoot)\$(Configuration)\$(Platform)" + + @@ -141,13 +143,19 @@ ninja -C "$(WebRtcOutRoot)\$(Configuration)\$(Platform)" + + + + + + diff --git a/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters b/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters index d012c90..e955fac 100644 --- a/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters +++ b/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters @@ -28,6 +28,10 @@ + + + + @@ -63,5 +67,9 @@ + + + + \ No newline at end of file diff --git a/WebRtcNet.Api/Media/MediaDevices.cs b/WebRtcNet.Api/Media/MediaDevices.cs index 531b894..9c784d7 100644 --- a/WebRtcNet.Api/Media/MediaDevices.cs +++ b/WebRtcNet.Api/Media/MediaDevices.cs @@ -5,18 +5,25 @@ namespace WebRtcNet.Media; /// -/// A .NET implementation of the MediaDevices interface. +/// Represents access to media input/output devices. /// /// -public interface MediaDevices +public abstract class MediaDevices { + /// + /// Initializes the media devices wrapper. + /// + protected MediaDevices() + { + } + /// /// Raised when the set of available media devices changes — for example, when a camera or /// microphone is connected or disconnected. The event args carry the updated device list and /// a hint about which devices were recently inserted by the user. /// /// - event EventHandler OnDeviceChange; + public abstract event EventHandler OnDeviceChange; /// /// Collects information about the available media input and output devices. @@ -26,7 +33,7 @@ public interface MediaDevices /// representing each available device. /// /// - Task> EnumerateDevices(); + public abstract Task> EnumerateDevices(); #region 10.2 MediaDevices Interface Extensions @@ -40,7 +47,7 @@ public interface MediaDevices /// constrainable properties are supported. /// /// - MediaTrackSupportedConstraints GetSupportedConstraints(); + public abstract MediaTrackSupportedConstraints GetSupportedConstraints(); /// /// Requests access to media input devices and returns a whose @@ -54,7 +61,7 @@ public interface MediaDevices /// to . /// /// - Task GetUserMedia(MediaStreamConstraints constraints); + public abstract Task GetUserMedia(MediaStreamConstraints constraints); #endregion //10.2 MediaDevices Interface Extensions } \ No newline at end of file diff --git a/WebRtcNet.Api/RtcPeerConnection.cs b/WebRtcNet.Api/RtcPeerConnection.cs index 396db44..b4ff3b0 100644 --- a/WebRtcNet.Api/RtcPeerConnection.cs +++ b/WebRtcNet.Api/RtcPeerConnection.cs @@ -183,7 +183,7 @@ protected RtcPeerConnection() /// Returns the native peer connection interface used by WebRtcInterop. /// /// True to throw when the peer connection has already been disposed. - internal abstract IntPtr GetNativePeerConnectionHandle(bool throwOnDisposed); + protected internal abstract IntPtr GetNativePeerConnectionHandle(bool throwOnDisposed); /// /// The local RTCSessionDescription that was successfully set using diff --git a/WebRtcNet/Host.cs b/WebRtcNet/Host.cs new file mode 100644 index 0000000..ec6ac7c --- /dev/null +++ b/WebRtcNet/Host.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics.Contracts; +using System.IO; +using System.Threading; +using WebRtcNet.Media; + +namespace WebRtcNet; + +/// +/// Non-browser host root for acquiring WebRTC API entry points. +/// +public static class Host +{ + private static readonly Lazy media_devices_ = + new(CreateMediaDevices, LazyThreadSafetyMode.ExecutionAndPublication); + + /// + /// Gets the process-wide media devices entry point. + /// + public static MediaDevices MediaDevices => media_devices_.Value; + + /// + /// Creates a peer connection using the active native backend. + /// + /// Peer connection configuration. + /// A new peer connection instance. + public static RtcPeerConnection CreatePeerConnection(RtcConfiguration configuration) + { + Contract.Requires(configuration != null, nameof(configuration)); + return CreateInteropInstance( + () => WebRtcInterop.RtcPeerConnectionFactory.CreatePeerConnection(configuration)); + } + + private static MediaDevices CreateMediaDevices() => + CreateInteropInstance(WebRtcInterop.Media.MediaDevicesFactory.CreateMediaDevices); + + private static T CreateInteropInstance(Func activator) + { + try + { + return activator(); + } + catch (Exception ex) when ( + ex is DllNotFoundException || + ex is FileLoadException || + ex is FileNotFoundException || + ex is TypeLoadException || + ex is MissingMethodException || + ex is BadImageFormatException) + { + throw new InvalidOperationException( + $"Failed to initialize native WebRTC backend type '{typeof(T).FullName}'. Ensure WebRtcInterop assemblies and native dependencies are present for this target.", + ex); + } + } +} diff --git a/docs/adr/0001-host-entry-point-for-api-access.md b/docs/adr/0001-host-entry-point-for-api-access.md new file mode 100644 index 0000000..48acdd7 --- /dev/null +++ b/docs/adr/0001-host-entry-point-for-api-access.md @@ -0,0 +1,3 @@ +# Host entry point for API access + +WebRtcNet will expose a static `WebRtcNet.Host` as the non-browser equivalent of `Navigator`, with `Host.MediaDevices` and `Host.CreatePeerConnection(...)` as the public entry points for client code. We chose this to remove direct `WebRtcInterop` usage from applications while preserving the existing abstract managed/native layering in `WebRtcNet.Api`. We rejected static injection on API model types because type-initialization order is implicit and brittle compared to an explicit root surface. diff --git a/documents/crosswalk/webrtcnet-api-to-spec.md b/documents/crosswalk/webrtcnet-api-to-spec.md index 15daabc..22862b3 100644 --- a/documents/crosswalk/webrtcnet-api-to-spec.md +++ b/documents/crosswalk/webrtcnet-api-to-spec.md @@ -8,24 +8,24 @@ This crosswalk maps high-value `WebRtcNet.Api` symbols to current W3C specificat | API Symbol | Spec Section | Anchor URL | Status | Adaptation Notes | | --- | --- | --- | --- | --- | -| `IRtcPeerConnection` | RTCPeerConnection interface | | partial | JS promise/event model maps to `Task` and .NET events; partial interop implementation remains in some members. | +| `RtcPeerConnection` | RTCPeerConnection interface | | partial | JS promise/event model maps to `Task` and .NET events; partial interop implementation remains in some members. | | `RtcConfiguration` | RTCConfiguration dictionary | | partial | Dictionary members map to C# properties; enum/value coercion rules need explicit validation behavior in managed API. | | `RtcIceCandidate` | RTCIceCandidate interface | | implemented | Constructor/dictionary parsing maps well; candidate parsing behavior should preserve spec terminology in XML docs. | | `RtcIceCandidateErrorEventArgs` | RTCPeerConnectionIceErrorEvent interface | | implemented | Payload exposes `address`/`port` as nullable and `url`/`errorCode`/`errorText` as required fields aligned to the spec event payload. | -| `IRtcRtpSender` | RTCRtpSender interface | | partial | Promise-returning operations map to `Task`; parameter mutation semantics require interop parity checks. | -| `IRtcRtpReceiver` | RTCRtpReceiver interface | | partial | Contract includes nullable `jitterBufferTarget` and receiver parameter/stat APIs; parity remains partial while interop coverage is expanded. | -| `IRtcRtpTransceiver` | RTCRtpTransceiver interface | | partial | Direction/currentDirection state alignment requires nullability-safe representation in .NET. | +| `RtcRtpSender` | RTCRtpSender interface | | partial | Promise-returning operations map to `Task`; parameter mutation semantics require interop parity checks. | +| `RtcRtpReceiver` | RTCRtpReceiver interface | | partial | Contract includes nullable `jitterBufferTarget` and receiver parameter/stat APIs; parity remains partial while interop coverage is expanded. | +| `RtcRtpTransceiver` | RTCRtpTransceiver interface | | partial | Direction/currentDirection state alignment requires nullability-safe representation in .NET. | | `RtcRtpParameters` | RTP interfaces and dictionaries | | partial | Includes RID modeling via `RtcRtpCodingParameters`; continue tracking dictionary evolution for forward-compatible defaults. | -| `IRtcDataChannel` | RTCDataChannel interface | | implemented | Event handler attributes become C# events/delegates; binary type semantics require explicit managed mapping. | -| `IRtcPeerConnection.GetStats` | `getStats` operation | | partial | Return contract maps to managed report interfaces; detailed dictionary parity delegated to webrtc-stats mapping. | +| `RtcDataChannel` | RTCDataChannel interface | | implemented | Event handler attributes become C# events/delegates; binary type semantics require explicit managed mapping. | +| `RtcPeerConnection.GetStats` | `getStats` operation | | partial | Return contract maps to managed report interfaces; detailed dictionary parity delegated to webrtc-stats mapping. | ## Media Capture and Streams | API Symbol | Spec Section | Anchor URL | Status | Adaptation Notes | | --- | --- | --- | --- | --- | -| `IMediaDevices` | MediaDevices interface | | partial | JS permission/device selection model maps to host-dependent .NET platform behavior. | -| `IMediaStream` | MediaStream interface | | implemented | ID/events map to managed interfaces; source/track ownership lifetimes must stay explicit in wrapper docs. | -| `IMediaStreamTrack` | MediaStreamTrack interface | | partial | `applyConstraints()` maps to nullable optional argument in C# (`MediaTrackConstraints?`). | +| `MediaDevices` (`Host.MediaDevices`) | MediaDevices interface and `Navigator.mediaDevices` access pattern | | partial | `Navigator` is adapted to static `WebRtcNet.Host`; `Host.MediaDevices` provides same-object access while permission/device selection remains host-platform dependent. | +| `MediaStream` | MediaStream interface | | implemented | ID/events map to managed interfaces; source/track ownership lifetimes must stay explicit in wrapper docs. | +| `MediaStreamTrack` | MediaStreamTrack interface | | partial | `applyConstraints()` maps to nullable optional argument in C# (`MediaTrackConstraints?`). | | `MediaStreamConstraints` | MediaStreamConstraints dictionary | | partial | JS union bool/object semantics represented with typed C# properties and constraint wrappers; local-IDL parity assertions cover bool/object member mapping in `MediaStreamConstraintsIdlParityTests`. | | `MediaTrackConstraints` | MediaTrackConstraints dictionary | | partial | Constrain* exact/ideal model implemented via dedicated nested constraint types, including ordered `Advanced` constraint-set entries; continue parity pass for remaining edge members. | | `MediaTrackCapabilities` | MediaTrackCapabilities dictionary | | partial | Sequence-valued capability members map to CLR collections (`IReadOnlyList`) with no legacy scalar compatibility adapters; scalar spec members remain scalar. | diff --git a/examples/BasicVideoChat/MainWindow.xaml b/examples/BasicVideoChat/MainWindow.xaml index 5d80e69..1c2aaf6 100644 --- a/examples/BasicVideoChat/MainWindow.xaml +++ b/examples/BasicVideoChat/MainWindow.xaml @@ -11,12 +11,12 @@ - - + + Checked="CalleeRadio_Checked"/> diff --git a/examples/BasicVideoChat/MainWindow.xaml.cs b/examples/BasicVideoChat/MainWindow.xaml.cs index 2be3373..82bcf43 100644 --- a/examples/BasicVideoChat/MainWindow.xaml.cs +++ b/examples/BasicVideoChat/MainWindow.xaml.cs @@ -32,12 +32,12 @@ public MainWindow() InitializeComponent(); } - private bool IsHost => HostRadio.IsChecked == true; + private bool IsCaller => CallerRadio.IsChecked == true; - private void HostRadio_Checked(object sender, RoutedEventArgs e) => + private void CallerRadio_Checked(object sender, RoutedEventArgs e) => IpBox.IsEnabled = false; - private void GuestRadio_Checked(object sender, RoutedEventArgs e) => + private void CalleeRadio_Checked(object sender, RoutedEventArgs e) => IpBox.IsEnabled = true; private async void ConnectBtn_Click(object sender, RoutedEventArgs e) @@ -76,8 +76,7 @@ private async Task StartCallAsync() { SetStatus("Acquiring media..."); - var mediaDevices = CreateInteropInstance("WebRtcInterop.Media.MediaDevices"); - _localStream = await mediaDevices.GetUserMedia(new MediaStreamConstraints(true, true)); + _localStream = await Host.MediaDevices.GetUserMedia(new MediaStreamConstraints(true, true)); _audioTrack = _localStream.GetAudioTracks().FirstOrDefault(); _videoTrack = _localStream.GetVideoTracks().FirstOrDefault(); @@ -89,7 +88,7 @@ private async Task StartCallAsync() // NOTE: Many WebRtcInterop methods currently throw NotImplementedException — this example // will not run end-to-end until they are implemented. SetLocalDescription and // AddIceCandidate are the minimum required for a basic call flow. - _peerConnection = CreateInteropInstance("WebRtcInterop.RtcPeerConnection", DefaultConfiguration); + _peerConnection = Host.CreatePeerConnection(DefaultConfiguration); _peerConnection.OnIceCandidate += OnIceCandidate; _peerConnection.OnTrack += OnTrack; _peerConnection.OnConnectionStateChange += OnConnectionStateChange; @@ -101,12 +100,12 @@ private async Task StartCallAsync() _signaling.MessageHandler = OnSignalingMessageAsync; _signaling.Disconnected += () => Dispatcher.BeginInvoke(async () => await HangUpAsync()); - if (IsHost) + if (IsCaller) { var port = int.TryParse(PortBox.Text, out var p) ? p : DefaultPort; SetStatus($"Listening on port {port}..."); await _signaling.ListenAsync(port); - SetStatus("Guest connected. Creating offer..."); + SetStatus("Callee connected. Creating offer..."); // Drive the offer explicitly here rather than relying on OnNegotiationNeeded, // since we control when the signaling channel is ready. @@ -117,10 +116,10 @@ private async Task StartCallAsync() } else { - var host = IpBox.Text.Trim(); + var callerIp = IpBox.Text.Trim(); var port = int.TryParse(PortBox.Text, out var p) ? p : DefaultPort; - SetStatus($"Connecting to {host}:{port}..."); - await _signaling.ConnectAsync(host, port); + SetStatus($"Connecting to {callerIp}:{port}..."); + await _signaling.ConnectAsync(callerIp, port); SetStatus("Connected. Waiting for offer..."); } } @@ -223,19 +222,4 @@ private async Task HangUpAsync() private void SetStatus(string message) => Dispatcher.Invoke(() => StatusText.Text = message); - - private static T CreateInteropInstance(string fullTypeName, params object[] args) where T : class - { - var type = - Type.GetType($"{fullTypeName}, WebRtcInterop.Core") ?? - Type.GetType($"{fullTypeName}, WebRtcInterop.Framework") ?? - Type.GetType($"{fullTypeName}, WebRtcInterop"); - if (type == null) - throw new NotSupportedException($"{fullTypeName} is not available. Ensure WebRtcInterop is built for the active target framework."); - - if (Activator.CreateInstance(type, args) is T instance) - return instance; - - throw new InvalidOperationException($"{fullTypeName} does not implement {typeof(T).FullName}."); - } }