diff --git a/WebRtcInterop/CameraVideoSource.cpp b/WebRtcInterop/CameraVideoSource.cpp new file mode 100644 index 0000000..b660c53 --- /dev/null +++ b/WebRtcInterop/CameraVideoSource.cpp @@ -0,0 +1,165 @@ +#include "pch.h" + +#include "CameraVideoSource.h" + +#include +#include +#include +#include + +namespace webrtc +{ + VideoCaptureCapability CreateCapability(const int32_t width, const int32_t height, const int32_t fps) + { + VideoCaptureCapability capability; + capability.width = width; + capability.height = height; + capability.maxFPS = fps; + return capability; + } + + bool AreCapabilitiesEqual(const VideoCaptureCapability& left, const VideoCaptureCapability& right) + { + return left.width == right.width && + left.height == right.height && + left.maxFPS == right.maxFPS && + left.videoType == right.videoType && + left.interlaced == right.interlaced; + } + + void AddCapabilityIfMissing(std::vector& capabilities, const VideoCaptureCapability& candidate) + { + for (const auto& existing : capabilities) + { + if (AreCapabilitiesEqual(existing, candidate)) + return; + } + + capabilities.push_back(candidate); + } + + std::vector BuildCaptureCapabilityCandidates(const std::string& deviceId) + { + std::vector candidates; + std::vector requested = { + CreateCapability(1280, 720, 30), + CreateCapability(1280, 720, 15), + CreateCapability(960, 540, 30), + CreateCapability(640, 480, 30), + CreateCapability(640, 480, 15), + CreateCapability(320, 240, 30) + }; + + const std::unique_ptr deviceInfo( + VideoCaptureFactory::CreateDeviceInfo()); + if (!deviceInfo) + return requested; + + for (const auto& capability : requested) + { + VideoCaptureCapability matched; + if (deviceInfo->GetBestMatchedCapability(deviceId.c_str(), capability, matched) >= 0) + AddCapabilityIfMissing(candidates, matched); + } + + for (const auto& capability : requested) + AddCapabilityIfMissing(candidates, capability); + + return candidates; + } +} + +namespace WebRtcInterop +{ + CameraVideoSource::CameraVideoSource(const webrtc::scoped_refptr& captureModule) + : state_(static_cast(kInitializing)), + stopping_(false), + capture_started_(false), + capture_module_(captureModule) + { + } + + CameraVideoSource::~CameraVideoSource() + { + Stop(); + } + + webrtc::scoped_refptr CameraVideoSource::Create(const std::string& deviceId) + { + if (deviceId.empty()) + return nullptr; + + auto captureModule = webrtc::VideoCaptureFactory::Create(deviceId.c_str()); + if (!captureModule) + return nullptr; + + auto source = webrtc::make_ref_counted(captureModule); + if (!source->Start(deviceId)) + return nullptr; + + return source; + } + + webrtc::MediaSourceInterface::SourceState CameraVideoSource::state() const + { + return static_cast(state_.load()); + } + + void CameraVideoSource::AddOrUpdateSink(VideoSinkInterface* sink, + const webrtc::VideoSinkWants& wants) + { + broadcaster_.AddOrUpdateSink(sink, wants); + } + + void CameraVideoSource::RemoveSink(VideoSinkInterface* sink) + { + broadcaster_.RemoveSink(sink); + } + + void CameraVideoSource::OnFrame(const webrtc::VideoFrame& frame) + { + if (stopping_.load()) + return; + + broadcaster_.OnFrame(frame); + } + + bool CameraVideoSource::Start(const std::string& deviceId) + { + if (!capture_module_) + return false; + + capture_module_->RegisterCaptureDataCallback(this); + const auto capabilities = webrtc::BuildCaptureCapabilityCandidates(deviceId); + for (const auto& capability : capabilities) + { + if (capture_module_->StartCapture(capability) == 0) + { + capture_started_.store(true); + state_.store(kLive); + return true; + } + } + + capture_module_->DeRegisterCaptureDataCallback(); + state_.store(kEnded); + return false; + } + + void CameraVideoSource::Stop() + { + if (stopping_.exchange(true)) + return; + + if (capture_module_) + { + if (capture_started_.exchange(false)) + capture_module_->StopCapture(); + + capture_module_->DeRegisterCaptureDataCallback(); + capture_module_ = nullptr; + } + + state_.store(kEnded); + } +} diff --git a/WebRtcInterop/CameraVideoSource.h b/WebRtcInterop/CameraVideoSource.h new file mode 100644 index 0000000..6335ed5 --- /dev/null +++ b/WebRtcInterop/CameraVideoSource.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace WebRtcInterop +{ + class CameraVideoSource : public webrtc::Notifier, + public webrtc::VideoSinkInterface + { + public: + static webrtc::scoped_refptr Create(const std::string& deviceId); + explicit CameraVideoSource(const webrtc::scoped_refptr& captureModule); + ~CameraVideoSource() override; + + SourceState state() const override; + bool remote() const override { return false; } + bool is_screencast() const override { return false; } + std::optional needs_denoising() const override { return std::nullopt; } + bool GetStats(Stats* /* stats */) override { return false; } + + void AddOrUpdateSink(VideoSinkInterface* sink, + const webrtc::VideoSinkWants& wants) override; + void RemoveSink(VideoSinkInterface* sink) override; + + bool SupportsEncodedOutput() const override { return false; } + void GenerateKeyFrame() override {} + void AddEncodedSink( + VideoSinkInterface* /* sink */) override {} + void RemoveEncodedSink( + VideoSinkInterface* /* sink */) override {} + + void OnFrame(const webrtc::VideoFrame& frame) override; + + private: + bool Start(const std::string& deviceId); + void Stop(); + + std::atomic state_; + std::atomic stopping_; + std::atomic capture_started_; + webrtc::scoped_refptr capture_module_; + webrtc::VideoBroadcaster broadcaster_; + }; +} diff --git a/WebRtcInterop/InteropHResult.cpp b/WebRtcInterop/InteropHResult.cpp new file mode 100644 index 0000000..144959f --- /dev/null +++ b/WebRtcInterop/InteropHResult.cpp @@ -0,0 +1,20 @@ +#include "pch.h" + +#include "InteropHResult.h" + +using namespace System::ComponentModel; +using namespace System::Runtime::InteropServices; + +namespace WebRtcInterop +{ + void InteropHResult::ThrowIfFailed(HRESULT hr, System::String^ message) + { + if (SUCCEEDED(hr)) + return; + + if (HRESULT_FACILITY(hr) == FACILITY_WIN32) + throw gcnew Win32Exception(HRESULT_CODE(hr), message); + + throw gcnew COMException(message, hr); + } +} diff --git a/WebRtcInterop/InteropHResult.h b/WebRtcInterop/InteropHResult.h new file mode 100644 index 0000000..e557bf1 --- /dev/null +++ b/WebRtcInterop/InteropHResult.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace WebRtcInterop +{ + public ref class InteropHResult abstract sealed + { + public: + static void ThrowIfFailed(HRESULT hr, System::String^ message); + }; +} diff --git a/WebRtcInterop/Marshaling/MarshalMedia.h b/WebRtcInterop/Marshaling/MarshalMedia.h new file mode 100644 index 0000000..c7e8f4c --- /dev/null +++ b/WebRtcInterop/Marshaling/MarshalMedia.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include + +namespace msclr { namespace interop +{ + // Marshal ValueRange from native to managed + // ValueRange: handles uint?, double? + template<> + inline WebRtcNet::ValueRange^ marshal_as(const std::pair& from) + { + auto range = gcnew WebRtcNet::ValueRange(); + range->Min = from.first; + range->Max = from.second; + return range; + } + + template<> + inline WebRtcNet::ValueRange^ marshal_as(const std::pair& from) + { + auto range = gcnew WebRtcNet::ValueRange(); + range->Min = from.first; + range->Max = from.second; + return range; + } + + // Marshal MediaDeviceKind enum + template<> + inline WebRtcNet::Media::MediaDeviceKind marshal_as(webrtc::MediaDeviceInfo::Kind from) + { + switch (from) + { + case webrtc::MediaDeviceInfo::Kind::kAudioInput: + return WebRtcNet::Media::MediaDeviceKind::AudioInput; + case webrtc::MediaDeviceInfo::Kind::kAudioOutput: + return WebRtcNet::Media::MediaDeviceKind::AudioOutput; + case webrtc::MediaDeviceInfo::Kind::kVideoInput: + return WebRtcNet::Media::MediaDeviceKind::VideoInput; + default: + throw gcnew System::ArgumentException("Unknown device kind"); + } + } + + template<> + inline webrtc::MediaDeviceInfo::Kind marshal_as(WebRtcNet::Media::MediaDeviceKind from) + { + switch (from) + { + case WebRtcNet::Media::MediaDeviceKind::AudioInput: + return webrtc::MediaDeviceInfo::Kind::kAudioInput; + case WebRtcNet::Media::MediaDeviceKind::AudioOutput: + return webrtc::MediaDeviceInfo::Kind::kAudioOutput; + case WebRtcNet::Media::MediaDeviceKind::VideoInput: + return webrtc::MediaDeviceInfo::Kind::kVideoInput; + default: + throw gcnew System::ArgumentException("Unknown device kind"); + } + } + + // Marshal MediaDeviceInfo: native to managed (one-way) + template<> + inline WebRtcNet::Media::MediaDeviceInfo^ marshal_as(const webrtc::MediaDeviceInfo& from) + { + auto deviceId = marshal_as(from.device_id()); + auto kind = marshal_as(from.kind()); + auto label = marshal_as(from.label()); + auto groupId = marshal_as(from.group_id()); + + // Use reflection to call internal constructor + auto ctor = WebRtcNet::Media::MediaDeviceInfo::typeid->GetConstructor( + System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Instance, + nullptr, + gcnew array { + System::String::typeid, + WebRtcNet::Media::MediaDeviceKind::typeid, + System::String::typeid, + System::String::typeid + }, + nullptr); + + if (ctor == nullptr) + throw gcnew System::InvalidOperationException("Cannot find internal MediaDeviceInfo constructor"); + + return safe_cast( + ctor->Invoke(gcnew array { deviceId, kind, label, groupId })); + } + + // Marshal MediaStreamTrackState enum + template<> + inline WebRtcNet::Media::MediaStreamTrackState marshal_as(webrtc::MediaStreamTrackInterface::TrackState from) + { + switch (from) + { + case webrtc::MediaStreamTrackInterface::TrackState::kLive: + return WebRtcNet::Media::MediaStreamTrackState::Live; + case webrtc::MediaStreamTrackInterface::TrackState::kEnded: + return WebRtcNet::Media::MediaStreamTrackState::Ended; + default: + throw gcnew System::ArgumentException("Unknown track state"); + } + } + + template<> + inline webrtc::MediaStreamTrackInterface::TrackState marshal_as(WebRtcNet::Media::MediaStreamTrackState from) + { + switch (from) + { + case WebRtcNet::Media::MediaStreamTrackState::Live: + return webrtc::MediaStreamTrackInterface::TrackState::kLive; + case WebRtcNet::Media::MediaStreamTrackState::Ended: + return webrtc::MediaStreamTrackInterface::TrackState::kEnded; + default: + throw gcnew System::ArgumentException("Unknown track state"); + } + } +}} diff --git a/WebRtcInterop/Media/MediaDevices.cpp b/WebRtcInterop/Media/MediaDevices.cpp index 3a7ffd7..b63c7ef 100644 --- a/WebRtcInterop/Media/MediaDevices.cpp +++ b/WebRtcInterop/Media/MediaDevices.cpp @@ -1,26 +1,444 @@ #include "pch.h" +#include #include "MediaDevices.h" +#include "CameraVideoSource.h" +#include "InteropHResult.h" +#include "MediaStream.h" +#include "MediaStreamTrack.h" +#include "Marshaling/MarshalMedia.h" +#include "RtcPeerConnectionFactory.h" + +#include +#include +#include +#include + +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "mmdevapi.lib") using namespace System::Collections::Generic; using namespace System::Threading::Tasks; +using namespace System::Timers; namespace WebRtcInterop::Media { - Task^>^ MediaDevices::EnumerateDevices() + static List^ EnumerateAudioDevices(); + static List^ EnumerateVideoDevices(); + + namespace + { + String^ GetDeviceMapKey(MediaDeviceInfo^ device) + { + return String::Format("{0}|{1}", (int)device->Kind, device->DeviceId); + } + + String^ GetAudioDeviceLabel(IMMDevice* device, String^ fallbackLabel) + { + auto label = fallbackLabel; + IPropertyStore* propertyStore = nullptr; + if (FAILED(device->OpenPropertyStore(STGM_READ, &propertyStore)) || propertyStore == nullptr) + return label; + + PROPVARIANT friendlyName; + PropVariantInit(&friendlyName); + if (SUCCEEDED(propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName)) && + friendlyName.vt == VT_LPWSTR) + label = gcnew String(friendlyName.pwszVal); + + PropVariantClear(&friendlyName); + propertyStore->Release(); + return label; + } + + void EnumerateAudioEndpoints( + IMMDeviceEnumerator* enumerator, + const EDataFlow flow, + const MediaDeviceKind kind, + String^ fallbackLabel, + List^ devices) + { + IMMDeviceCollection* collection = nullptr; + auto hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATE_ACTIVE, &collection); + if (FAILED(hr) || collection == nullptr) + return; + + UINT count = 0; + hr = collection->GetCount(&count); + if (FAILED(hr)) + { + collection->Release(); + return; + } + + for (UINT i = 0; i < count; ++i) + { + IMMDevice* device = nullptr; + if (FAILED(collection->Item(i, &device)) || device == nullptr) + continue; + + LPWSTR deviceId = nullptr; + if (SUCCEEDED(device->GetId(&deviceId))) + { + auto id = gcnew String(deviceId); + auto label = GetAudioDeviceLabel(device, fallbackLabel); + devices->Add(MediaDeviceInfo::Create(id, kind, label, String::Empty)); + CoTaskMemFree(deviceId); + } + + device->Release(); + } + + collection->Release(); + } + + void ValidateGetUserMediaConstraints(MediaStreamConstraints^ constraints) + { + if (constraints == nullptr) + throw gcnew ArgumentNullException("constraints"); + + if (!constraints->Audio && !constraints->Video) + throw gcnew MediaStreamException("At least one of audio or video must be requested."); + } + + void ValidateRequestedAudioDevices(MediaStreamConstraints^ constraints) + { + if (!constraints->Audio) + return; + + auto audioDevices = EnumerateAudioDevices(); + if (audioDevices == nullptr || audioDevices->Count == 0) + throw gcnew MediaStreamException("No audio input devices are currently available."); + } + + List^ ValidateAndGetRequestedVideoDevices(MediaStreamConstraints^ constraints) + { + if (!constraints->Video) + return nullptr; + + auto videoDevices = EnumerateVideoDevices(); + if (videoDevices == nullptr || videoDevices->Count == 0) + throw gcnew MediaStreamException("No video input devices are currently available."); + + return videoDevices; + } + + std::string CreateGuidLabel(String^ prefix) + { + return marshal_as(prefix + "_" + Guid::NewGuid().ToString()); + } + + webrtc::scoped_refptr CreateNativeStream( + webrtc::PeerConnectionFactoryInterface* factory) + { + const auto streamId = marshal_as(Guid::NewGuid().ToString()); + auto nativeStream = factory->CreateLocalMediaStream(streamId); + if (!nativeStream) + throw gcnew InvalidOperationException("Failed to create media stream."); + + return nativeStream; + } + + void AddAudioTrack( + webrtc::PeerConnectionFactoryInterface* factory, + const webrtc::scoped_refptr& nativeStream) + { + const webrtc::AudioOptions audioOptions; + const auto nativeAudioSource = factory->CreateAudioSource(audioOptions); + if (!nativeAudioSource) + throw gcnew MediaStreamException("Failed to create an audio source for the requested track."); + + const auto nativeAudioTrack = factory->CreateAudioTrack( + CreateGuidLabel(gcnew String(L"audio")), + nativeAudioSource.get()); + if (!nativeAudioTrack) + throw gcnew MediaStreamException("Failed to create the requested audio track."); + + if (!nativeStream->AddTrack(nativeAudioTrack)) + throw gcnew MediaStreamException("Failed to add the audio track to the media stream."); + } + + webrtc::scoped_refptr CreateVideoSource( + List^ videoDevices) + { + for each (auto videoDevice in videoDevices) + { + if (videoDevice == nullptr || + String::IsNullOrEmpty(videoDevice->DeviceId) || + videoDevice->Kind != MediaDeviceKind::VideoInput) + continue; + + auto videoDeviceId = marshal_as(videoDevice->DeviceId); + if (auto candidateSource = CameraVideoSource::Create(videoDeviceId)) + return candidateSource; + } + + throw gcnew MediaStreamException("Failed to create a camera-backed video source for the requested track."); + } + + void AddVideoTrack( + webrtc::PeerConnectionFactoryInterface* factory, + const webrtc::scoped_refptr& nativeStream, + List^ videoDevices) + { + const auto videoSource = CreateVideoSource(videoDevices); + const auto nativeVideoTrack = factory->CreateVideoTrack( + videoSource, + CreateGuidLabel(gcnew String(L"video"))); + if (!nativeVideoTrack) + throw gcnew MediaStreamException("Failed to create the requested video track."); + + if (!nativeStream->AddTrack(nativeVideoTrack)) + throw gcnew MediaStreamException("Failed to add the video track to the media stream."); + } + } + + MediaDevices::MediaDevices() + : known_devices_(gcnew Dictionary()), + device_poll_timer_(gcnew Timer(2000.0)), + device_poll_gate_(gcnew Object()), + on_device_change_(nullptr) + { + RefreshKnownDevices(false); + device_poll_timer_->AutoReset = true; + device_poll_timer_->Elapsed += gcnew ElapsedEventHandler(this, &MediaDevices::OnDevicePoll); + device_poll_timer_->Start(); + } + + MediaDevices::~MediaDevices() + { + this->!MediaDevices(); + } + + MediaDevices::!MediaDevices() + { + StopDevicePolling(); + } + + void MediaDevices::StopDevicePolling() { - auto devices = gcnew List(); - return Task::FromResult^>(devices); + if (device_poll_timer_ == nullptr) + return; + + device_poll_timer_->Stop(); + device_poll_timer_->Close(); + device_poll_timer_ = nullptr; + } + + void MediaDevices::OnDevicePoll(Object^ sender, ElapsedEventArgs^ args) + { + RefreshKnownDevices(true); + } + + void MediaDevices::RefreshKnownDevices(const bool raiseEvent) + { + Threading::Monitor::Enter(device_poll_gate_); + try + { + auto enumerateTask = EnumerateDevices(); + if (enumerateTask == nullptr) + return; + + auto devices = enumerateTask->GetAwaiter().GetResult(); + auto current = gcnew Dictionary(); + auto inserted = gcnew List(); + auto all = gcnew List(); + + for each (auto device in devices) + { + auto key = GetDeviceMapKey(device); + current[key] = device; + all->Add(device); + + if (!known_devices_->ContainsKey(key)) + inserted->Add(device); + } + + bool changed = current->Count != known_devices_->Count; + if (!changed) + { + for each (auto key in current->Keys) + { + if (!known_devices_->ContainsKey(key)) + { + changed = true; + break; + } + } + } + + known_devices_->Clear(); + for each (auto kvp in current) + known_devices_->Add(kvp.Key, kvp.Value); + + if (raiseEvent && changed && on_device_change_ != nullptr) + on_device_change_(this, gcnew DeviceChangeEventArgs(all, inserted)); + } + catch (Exception^) + { + // Device polling must not crash the process. Failures are retried on next poll. + } + finally + { + Threading::Monitor::Exit(device_poll_gate_); + } + } + + // Helper to enumerate Windows audio devices + static List^ EnumerateAudioDevices() + { + auto devices = gcnew List(); + + try + { + // Initialize COM + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr) && hr != S_FALSE) + return devices; // COM already initialized or failed + + IMMDeviceEnumerator* enumerator = nullptr; + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); + + if (SUCCEEDED(hr) && enumerator != nullptr) + { + EnumerateAudioEndpoints( + enumerator, + eCapture, + MediaDeviceKind::AudioInput, + gcnew String(L"Audio Input Device"), + devices); + EnumerateAudioEndpoints( + enumerator, + eRender, + MediaDeviceKind::AudioOutput, + gcnew String(L"Audio Output Device"), + devices); + enumerator->Release(); + } + + CoUninitialize(); + } + catch (...) + { + // Silently ignore errors in device enumeration + } + + return devices; } - WebRtcNet::Media::MediaTrackSupportedConstraints^ MediaDevices::GetSupportedConstraints() + // Helper to enumerate Windows video devices using DirectShow + static List^ EnumerateVideoDevices() { - return gcnew WebRtcNet::Media::MediaTrackSupportedConstraints(); + auto devices = gcnew List(); + + try + { + const std::unique_ptr deviceInfo( + webrtc::VideoCaptureFactory::CreateDeviceInfo()); + if (!deviceInfo) + return devices; + + const uint32_t count = deviceInfo->NumberOfDevices(); + for (uint32_t i = 0; i < count; ++i) + { + char deviceName[512] = {}; + char deviceUniqueId[512] = {}; + char productUniqueId[512] = {}; + + const int32_t result = deviceInfo->GetDeviceName( + i, + deviceName, + sizeof(deviceName), + deviceUniqueId, + sizeof(deviceUniqueId), + productUniqueId, + sizeof(productUniqueId)); + + if (result != 0) + continue; + + auto id = marshal_as(std::string(deviceUniqueId)); + auto label = marshal_as(std::string(deviceName)); + String^ groupId = + productUniqueId[0] != '\0' + ? marshal_as(std::string(productUniqueId)) + : String::Empty; + + devices->Add(MediaDeviceInfo::Create( + id, + MediaDeviceKind::VideoInput, + label, + groupId)); + } + } + catch (...) + { + // Silently ignore errors + } + + return devices; } - Task^ MediaDevices::GetUserMedia(WebRtcNet::Media::MediaStreamConstraints^ constraints) + Task^>^ MediaDevices::EnumerateDevices() { - return Task::FromException( - gcnew WebRtcNet::Media::MediaStreamException("GetUserMedia is not currently implemented.")); + try + { + auto allDevices = gcnew List(); + + // Enumerate audio devices + auto audioDevices = EnumerateAudioDevices(); + if (audioDevices != nullptr) + allDevices->AddRange(audioDevices); + + // Enumerate video devices + auto videoDevices = EnumerateVideoDevices(); + if (videoDevices != nullptr) + allDevices->AddRange(videoDevices); + + return Task::FromResult^>(allDevices); + } + catch (Exception^ ex) + { + return Task::FromException^>(ex); + } + } + + MediaTrackSupportedConstraints^ MediaDevices::GetSupportedConstraints() + { + return gcnew MediaTrackSupportedConstraints(); + } + + Task^ MediaDevices::GetUserMedia(MediaStreamConstraints^ constraints) + { + try + { + ValidateGetUserMediaConstraints(constraints); + ValidateRequestedAudioDevices(constraints); + auto videoDevices = ValidateAndGetRequestedVideoDevices(constraints); + + // Get the native peer connection factory + const auto factory = RtcPeerConnectionFactory::Instance->GetNativePeerConnectionFactoryInterface(true); + if (factory == nullptr) + throw gcnew InvalidOperationException("PeerConnectionFactory not initialized."); + + const auto nativeStream = CreateNativeStream(factory); + + // Create audio track if requested + if (constraints->Audio) + AddAudioTrack(factory, nativeStream); + + // Create video track if requested + if (constraints->Video) + AddVideoTrack(factory, nativeStream, videoDevices); + + // Create managed MediaStream wrapper + auto managedStream = gcnew MediaStream(nativeStream); + return Task::FromResult(managedStream); + } + catch (Exception^ ex) + { + return Task::FromException(ex); + } } } diff --git a/WebRtcInterop/Media/MediaDevices.h b/WebRtcInterop/Media/MediaDevices.h index 7ee6a2f..4fdc2d1 100644 --- a/WebRtcInterop/Media/MediaDevices.h +++ b/WebRtcInterop/Media/MediaDevices.h @@ -9,6 +9,10 @@ namespace WebRtcInterop::Media public ref class MediaDevices : WebRtcNet::Media::MediaDevices { public: + MediaDevices(); + ~MediaDevices(); + !MediaDevices(); + virtual event EventHandler^ OnDeviceChange { void add(EventHandler^ value) override { on_device_change_ += value; } @@ -20,6 +24,13 @@ namespace WebRtcInterop::Media virtual Task^ GetUserMedia(WebRtcNet::Media::MediaStreamConstraints^ constraints) override; private: + void RefreshKnownDevices(bool raiseEvent); + void OnDevicePoll(Object^ sender, System::Timers::ElapsedEventArgs^ args); + void StopDevicePolling(); + + System::Collections::Generic::Dictionary^ known_devices_; + System::Timers::Timer^ device_poll_timer_; + Object^ device_poll_gate_; EventHandler^ on_device_change_; }; } diff --git a/WebRtcInterop/Media/MediaStreamTrack.cpp b/WebRtcInterop/Media/MediaStreamTrack.cpp index baf730b..d9c16c0 100644 --- a/WebRtcInterop/Media/MediaStreamTrack.cpp +++ b/WebRtcInterop/Media/MediaStreamTrack.cpp @@ -16,6 +16,7 @@ namespace WebRtcInterop::Media MediaStreamTrack::MediaStreamTrack() { _rpMediaStreamTrackInterface = nullptr; + applied_constraints_ = gcnew MediaTrackConstraints(); on_mute_ = nullptr; on_unmute_ = nullptr; on_ended_ = nullptr; @@ -24,6 +25,7 @@ namespace WebRtcInterop::Media MediaStreamTrack::MediaStreamTrack(webrtc::scoped_refptr track) { _rpMediaStreamTrackInterface = new webrtc::scoped_refptr(track); + applied_constraints_ = gcnew MediaTrackConstraints(); on_mute_ = nullptr; on_unmute_ = nullptr; on_ended_ = nullptr; @@ -131,7 +133,10 @@ namespace WebRtcInterop::Media MediaTrackConstraints^ MediaStreamTrack::GetConstraints() { - throw gcnew NotImplementedException(); + if (applied_constraints_ == nullptr) + applied_constraints_ = gcnew MediaTrackConstraints(); + + return applied_constraints_; } MediaTrackSettings^ MediaStreamTrack::GetSettings() @@ -141,6 +146,9 @@ namespace WebRtcInterop::Media void MediaStreamTrack::ApplyConstraints(MediaTrackConstraints^ constraints) { - throw gcnew NotImplementedException(); + if (constraints == nullptr) + applied_constraints_ = gcnew MediaTrackConstraints(); + else + applied_constraints_ = constraints; } } diff --git a/WebRtcInterop/Media/MediaStreamTrack.h b/WebRtcInterop/Media/MediaStreamTrack.h index 657d3b0..d1dd3ec 100644 --- a/WebRtcInterop/Media/MediaStreamTrack.h +++ b/WebRtcInterop/Media/MediaStreamTrack.h @@ -57,6 +57,7 @@ public ref class MediaStreamTrack : WebRtcNet::Media::MediaStreamTrack private: webrtc::scoped_refptr* _rpMediaStreamTrackInterface; + MediaTrackConstraints^ applied_constraints_; System::EventHandler^ on_mute_; System::EventHandler^ on_unmute_; System::EventHandler^ on_ended_; diff --git a/WebRtcInterop/WebRtcInterop.Shared.vcxitems b/WebRtcInterop/WebRtcInterop.Shared.vcxitems index 09eeb78..47dd28f 100644 --- a/WebRtcInterop/WebRtcInterop.Shared.vcxitems +++ b/WebRtcInterop/WebRtcInterop.Shared.vcxitems @@ -1,167 +1,171 @@  - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - {fac2e2bf-5a09-428f-a224-1ce7a160a2d1} - - - - cd "$(WebRtcSrcRoot)" + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {fac2e2bf-5a09-428f-a224-1ce7a160a2d1} + + + + cd "$(WebRtcSrcRoot)" echo Generating WebRtc Build Files cmd /c gn gen --ide=vs "$(WebRtcOutRoot)\$(Configuration)\$(Platform)" --filters=//:webrtc --args=" - " + " echo Compiling WebRtc Build Files ninja -C "$(WebRtcOutRoot)\$(Configuration)\$(Platform)" - use_custom_libcxx=false libcxx_is_shared=true use_lld=false is_component_build=false clang_use_chrome_plugins=false rtc_include_tests=false rtc_build_tools=false rtc_build_examples=false rtc_enable_symbol_export=true rtc_enable_protobuf=false enable_libaom=false - - - $(ProjectDir);$(IncludePath) - PreBuildEvent - - - - Use - pch.h - Level3 - stdcpp20 - stdc17 - - - - - - _DEBUG;%(PreprocessorDefinitions) - - - - - NDEBUG;%(PreprocessorDefinitions) - - - - - WIN32;%(PreprocessorDefinitions) - - - - - %(PreprocessorDefinitions) - - - - - - ARM64;%(PreprocessorDefinitions);_ITERATOR_DEBUG_LEVEL=0 - - - - - %(ExternalIncludePath);$(WebRtcSrcRoot);$(WebRtcSrcRoot)\system_wrappers;$(WebRtcSrcRoot)\third_party\abseil-cpp;$(WebRtcSrcRoot)\buildtools\third_party\libc++;$(WebRtcSrcRoot)\third_party\libyuv\include;$(WebRtcSrcRoot)\third_party\jsoncpp\source\include - TurnOffAllWarnings - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory);$(WebRtcSrcRoot);$(WebRtcSrcRoot)\system_wrappers;$(WebRtcSrcRoot)\third_party\abseil-cpp;$(WebRtcSrcRoot)\buildtools\third_party\libc++;$(WebRtcSrcRoot)\third_party\libyuv\include;$(WebRtcSrcRoot)\third_party\jsoncpp\source\include - %(PreprocessorDefinitions);_HAS_EXCEPTIONS=0;__STD_C;_CRT_RAND_S;_CRT_SECURE_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;_ATL_NO_OPENGL;_WINDOWS;CERT_CHAIN_PARA_HAS_EXTRA_FIELDS;PSAPI_VERSION=2;WIN32;_SECURE_ATL;__WRL_NO_DEFAULT_LIB__;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WIN10=_WIN32_WINNT_WIN10;WIN32_LEAN_AND_MEAN;NOMINMAX;_UNICODE;UNICODE;NTDDI_VERSION=NTDDI_WIN10_RS2;_WIN32_WINNT=0x0A00;WINVER=0x0A00;NVALGRIND;DYNAMIC_ANNOTATIONS_ENABLED=0;WEBRTC_ENABLE_PROTOBUF=0;WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE;RTC_ENABLE_VP9;HAVE_SCTP;WEBRTC_LIBRARY_IMPL;WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0;WEBRTC_WIN;ABSL_ALLOCATOR_NOTHROW=1;HAVE_SCTP - - - %(AdditionalLibraryDirectories);$(WebRtcOutRoot)\$(Configuration)\$(Platform) - webrtc.lib;Ws2_32.lib;iphlpapi.lib;Winmm.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - libcmtd;%(IgnoreSpecificDefaultLibraries) - - - - - $(WebRtcGnCommandPrefix)target_cpu="x64" is_debug=false enable_iterator_debugging=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) + use_custom_libcxx=false libcxx_is_shared=true use_lld=false is_component_build=false clang_use_chrome_plugins=false rtc_include_tests=false rtc_build_tools=false rtc_build_examples=false rtc_enable_symbol_export=true rtc_enable_protobuf=false enable_libaom=false + + + $(ProjectDir);$(IncludePath) + PreBuildEvent + + + + Use + pch.h + Level3 + stdcpp20 + stdc17 + + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + %(PreprocessorDefinitions) + + + + + + ARM64;%(PreprocessorDefinitions);_ITERATOR_DEBUG_LEVEL=0 + + + + + %(ExternalIncludePath);$(WebRtcSrcRoot);$(WebRtcSrcRoot)\system_wrappers;$(WebRtcSrcRoot)\third_party\abseil-cpp;$(WebRtcSrcRoot)\buildtools\third_party\libc++;$(WebRtcSrcRoot)\third_party\libyuv\include;$(WebRtcSrcRoot)\third_party\jsoncpp\source\include + TurnOffAllWarnings + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory);$(WebRtcSrcRoot);$(WebRtcSrcRoot)\system_wrappers;$(WebRtcSrcRoot)\third_party\abseil-cpp;$(WebRtcSrcRoot)\buildtools\third_party\libc++;$(WebRtcSrcRoot)\third_party\libyuv\include;$(WebRtcSrcRoot)\third_party\jsoncpp\source\include + %(PreprocessorDefinitions);_HAS_EXCEPTIONS=0;__STD_C;_CRT_RAND_S;_CRT_SECURE_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;_ATL_NO_OPENGL;_WINDOWS;CERT_CHAIN_PARA_HAS_EXTRA_FIELDS;PSAPI_VERSION=2;WIN32;_SECURE_ATL;__WRL_NO_DEFAULT_LIB__;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WIN10=_WIN32_WINNT_WIN10;WIN32_LEAN_AND_MEAN;NOMINMAX;_UNICODE;UNICODE;NTDDI_VERSION=NTDDI_WIN10_RS2;_WIN32_WINNT=0x0A00;WINVER=0x0A00;NVALGRIND;DYNAMIC_ANNOTATIONS_ENABLED=0;WEBRTC_ENABLE_PROTOBUF=0;WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE;RTC_ENABLE_VP9;HAVE_SCTP;WEBRTC_LIBRARY_IMPL;WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0;WEBRTC_WIN;ABSL_ALLOCATOR_NOTHROW=1;HAVE_SCTP + + + %(AdditionalLibraryDirectories);$(WebRtcOutRoot)\$(Configuration)\$(Platform) + webrtc.lib;Ws2_32.lib;iphlpapi.lib;Winmm.lib;strmiids.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + libcmtd;%(IgnoreSpecificDefaultLibraries) + + + + + $(WebRtcGnCommandPrefix)target_cpu="x64" is_debug=false enable_iterator_debugging=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) - Building Google WebRtc Library - $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib - - - - - $(WebRtcGnCommandPrefix)target_cpu="x64" is_debug=true enable_iterator_debugging=true $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) + Building Google WebRtc Library + $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib + + + + + $(WebRtcGnCommandPrefix)target_cpu="x64" is_debug=true enable_iterator_debugging=true $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) - Building Google WebRtc Library - $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib - - - - - $(WebRtcGnCommandPrefix)target_cpu="arm64" is_debug=false enable_iterator_debugging=false rtc_enable_win_wgc=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) + Building Google WebRtc Library + $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib + + + + + $(WebRtcGnCommandPrefix)target_cpu="arm64" is_debug=false enable_iterator_debugging=false rtc_enable_win_wgc=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) - Building Google WebRtc Library - $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib - - - - - $(WebRtcGnCommandPrefix)target_cpu="arm64" is_debug=true enable_iterator_debugging=false rtc_enable_win_wgc=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) + Building Google WebRtc Library + $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib + + + + + $(WebRtcGnCommandPrefix)target_cpu="arm64" is_debug=true enable_iterator_debugging=false rtc_enable_win_wgc=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) - Building Google WebRtc Library - $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib - - - - - $(WebRtcGnCommandPrefix)target_cpu="x86" is_debug=false enable_iterator_debugging=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) + Building Google WebRtc Library + $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib + + + + + $(WebRtcGnCommandPrefix)target_cpu="x86" is_debug=false enable_iterator_debugging=false $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) - Building Google WebRtc Library - $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib - - - - - $(WebRtcGnCommandPrefix)target_cpu="x86" is_debug=true enable_iterator_debugging=true $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) + Building Google WebRtc Library + $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib + + + + + $(WebRtcGnCommandPrefix)target_cpu="x86" is_debug=true enable_iterator_debugging=true $(WebRtcGnCommonArgs)$(WebRtcGnCommandSuffix) - Building Google WebRtc Library - $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib - - - - - - - - {98433217-c99b-4b08-a19b-3f97a1f1e633} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - + Building Google WebRtc Library + $(WebRtcOutRoot)\$(Configuration)\$(Platform)\webrtc.lib + + + + + + + + {98433217-c99b-4b08-a19b-3f97a1f1e633} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + \ No newline at end of file diff --git a/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters b/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters index e955fac..1e08676 100644 --- a/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters +++ b/WebRtcInterop/WebRtcInterop.Shared.vcxitems.filters @@ -16,6 +16,7 @@ + Media @@ -28,13 +29,19 @@ - - + + Media + + + Media + + + Marshaling @@ -67,9 +74,14 @@ - - + + Media + + + Media + + \ No newline at end of file diff --git a/WebRtcNet.Api/Media/MediaDeviceInfo.cs b/WebRtcNet.Api/Media/MediaDeviceInfo.cs index d20d9b7..ff619a7 100644 --- a/WebRtcNet.Api/Media/MediaDeviceInfo.cs +++ b/WebRtcNet.Api/Media/MediaDeviceInfo.cs @@ -1,3 +1,5 @@ +using System.ComponentModel; + namespace WebRtcNet.Media; /// @@ -64,4 +66,11 @@ internal MediaDeviceInfo(string deviceId, MediaDeviceKind kind, string label, st /// /// public string GroupId { get; } + + /// + /// Factory method for creating MediaDeviceInfo instances (for interop use only). + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static MediaDeviceInfo Create(string deviceId, MediaDeviceKind kind, string label, string groupId) + => new(deviceId, kind, label, groupId); } \ No newline at end of file diff --git a/WebRtcNet.sln.DotSettings b/WebRtcNet.sln.DotSettings index f705b35..9671773 100644 --- a/WebRtcNet.sln.DotSettings +++ b/WebRtcNet.sln.DotSettings @@ -13,6 +13,11 @@ Tab 2 False + <NamingElement Priority="7" Title="Local variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="6" Title="Parameters"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="function parameter" /><type Name="lambda parameter" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="18" Title="Namespaces"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="namespace" /><type Name="namespace alias" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="10" Title="Global functions"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="11" Title="Class and struct methods"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> True True True