diff --git a/WebRtcInterop/RtcDataChannel.cpp b/WebRtcInterop/RtcDataChannel.cpp index 1d88aff..58cd3ca 100644 --- a/WebRtcInterop/RtcDataChannel.cpp +++ b/WebRtcInterop/RtcDataChannel.cpp @@ -67,14 +67,14 @@ namespace WebRtcInterop return GetNativeDataChannelInterface(true)->ordered(); } - Nullable RtcDataChannel::MaxPacketLifeTime::get() + Nullable RtcDataChannel::MaxPacketLifeTime::get() { - return marshal_as(GetNativeDataChannelInterface(true)->maxPacketLifeTime()); + return marshal_as(GetNativeDataChannelInterface(true)->maxPacketLifeTime()); } - Nullable RtcDataChannel::MaxRetransmits::get() + Nullable RtcDataChannel::MaxRetransmits::get() { - return marshal_as(GetNativeDataChannelInterface(true)->maxRetransmitsOpt()); + return marshal_as(GetNativeDataChannelInterface(true)->maxRetransmitsOpt()); } String^ RtcDataChannel::Protocol::get() diff --git a/WebRtcInterop/RtcDataChannel.h b/WebRtcInterop/RtcDataChannel.h index 023698e..617053e 100644 --- a/WebRtcInterop/RtcDataChannel.h +++ b/WebRtcInterop/RtcDataChannel.h @@ -20,8 +20,8 @@ namespace WebRtcInterop // Inherited via RtcDataChannel virtual property String^ Label { String^ get() override; } virtual property bool Ordered { bool get() override; } - virtual property Nullable MaxPacketLifeTime { Nullable get() override; } - virtual property Nullable MaxRetransmits { Nullable get() override; } + virtual property Nullable MaxPacketLifeTime { Nullable get() override; } + virtual property Nullable MaxRetransmits { Nullable get() override; } virtual property String^ Protocol { String^ get() override; } virtual property bool Negotiated { bool get() override; } virtual property Nullable Id { Nullable get() override; } diff --git a/WebRtcInterop/RtcPeerConnection.cpp b/WebRtcInterop/RtcPeerConnection.cpp index a976bd2..4a68b09 100644 --- a/WebRtcInterop/RtcPeerConnection.cpp +++ b/WebRtcInterop/RtcPeerConnection.cpp @@ -104,10 +104,16 @@ namespace WebRtcInterop return task; } - Task^ RtcPeerConnection::SetLocalDescription(RtcSessionDescription description) - { + 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(); - // TODO: insert return statement here } Task^ RtcPeerConnection::SetRemoteDescription(RtcSessionDescription description) diff --git a/WebRtcInterop/RtcPeerConnection.h b/WebRtcInterop/RtcPeerConnection.h index 2ce3477..3908c46 100644 --- a/WebRtcInterop/RtcPeerConnection.h +++ b/WebRtcInterop/RtcPeerConnection.h @@ -91,7 +91,7 @@ namespace WebRtcInterop virtual Task^ AddIceCandidate(RtcIceCandidate^ candidate) override; virtual void RestartIce() override; - virtual Task^ SetLocalDescription(RtcSessionDescription description) override; + virtual Task^ SetLocalDescription(Nullable description) override; virtual Task^ SetRemoteDescription(RtcSessionDescription description) override; virtual RtcRtpSender^ AddTrack(WebRtcNet::Media::MediaStreamTrack^ track, diff --git a/WebRtcNet.Api.UnitTests/RtcPeerConnectionContractTests.cs b/WebRtcNet.Api.UnitTests/RtcPeerConnectionContractTests.cs index 16c43c4..dc00ac7 100644 --- a/WebRtcNet.Api.UnitTests/RtcPeerConnectionContractTests.cs +++ b/WebRtcNet.Api.UnitTests/RtcPeerConnectionContractTests.cs @@ -79,15 +79,21 @@ public void RtcPeerConnection_Exposes_Nullable_Optional_SetLocalDescription_And_ { var setLocalDescription = typeof(RtcPeerConnection).GetMethod( nameof(RtcPeerConnection.SetLocalDescription), - new[] { typeof(RtcSessionDescription?) }); + new[] { typeof(RtcLocalSessionDescriptionInit?) }); var addIceCandidate = typeof(RtcPeerConnection).GetMethod( nameof(RtcPeerConnection.AddIceCandidate), new[] { typeof(RtcIceCandidate) }); - Assert.That(setLocalDescription, Is.Not.Null); + Assert.That(setLocalDescription, Is.Not.Null, + "SetLocalDescription must accept RtcLocalSessionDescriptionInit? (spec: RTCLocalSessionDescriptionInit, type optional)"); Assert.That(setLocalDescription!.GetParameters()[0].IsOptional, Is.True); Assert.That(setLocalDescription.GetParameters()[0].DefaultValue, Is.Null); + // Verify the parameter is NOT the old RtcSessionDescription? type — type distinction is spec-required. + Assert.That(setLocalDescription.GetParameters()[0].ParameterType, + Is.Not.EqualTo(typeof(RtcSessionDescription?)), + "SetLocalDescription must use RtcLocalSessionDescriptionInit (RTCLocalSessionDescriptionInit), not RtcSessionDescription (RTCSessionDescriptionInit)"); + Assert.That(addIceCandidate, Is.Not.Null); Assert.That(addIceCandidate!.GetParameters()[0].IsOptional, Is.True); Assert.That(addIceCandidate.GetParameters()[0].DefaultValue, Is.Null); diff --git a/WebRtcNet.Api/RtcPeerConnection.cs b/WebRtcNet.Api/RtcPeerConnection.cs index f05c2b1..396db44 100644 --- a/WebRtcNet.Api/RtcPeerConnection.cs +++ b/WebRtcNet.Api/RtcPeerConnection.cs @@ -334,15 +334,16 @@ public static Task GenerateCertificate(object keygenAlgorithm) /// /// The SetLocalDescription() method instructs the RtcPeerConnection to apply the supplied - /// RtcSessionDescription as the + /// description as the /// local description. /// /// - /// A session description containing the SDP describing the local session, or null to let the implementation - /// infer and apply the next local description. + /// A local session description init whose type may be omitted to let the implementation + /// infer it from the current signaling state, or to use the implementation's + /// default (equivalent to passing an empty RTCLocalSessionDescriptionInit). /// /// - public abstract Task SetLocalDescription(RtcSessionDescription? description = null); + public abstract Task SetLocalDescription(RtcLocalSessionDescriptionInit? description = null); /// /// The SetRemoteDescription() method instructs the RTCPeerConnection to apply the supplied diff --git a/WebRtcNet.Api/RtcSessionDescription.cs b/WebRtcNet.Api/RtcSessionDescription.cs index ff13214..8301909 100644 --- a/WebRtcNet.Api/RtcSessionDescription.cs +++ b/WebRtcNet.Api/RtcSessionDescription.cs @@ -1,3 +1,5 @@ +using System; + namespace WebRtcNet; /// @@ -32,10 +34,12 @@ public enum RtcSdpType } /// -/// Represents an SDP session description used by operations. +/// Represents an SDP session description produced by or +/// , or received from a remote peer via signaling. +/// Models RTCSessionDescriptionInit where type is required. /// -/// -public struct RtcSessionDescription +/// +public record struct RtcSessionDescription { /// /// Initializes a session description with a type and SDP payload. @@ -51,10 +55,72 @@ public RtcSessionDescription(RtcSdpType type, string sdp) /// /// Gets the SDP description type. /// - public readonly RtcSdpType Type; + /// + public RtcSdpType Type { get; init; } + + /// + /// Gets the SDP payload string. + /// + /// + public string Sdp { get; init; } = string.Empty; + + /// + /// Implicitly converts an to an + /// . Throws if + /// is , as is required. + /// + /// + /// Thrown when is . + /// + public static implicit operator RtcSessionDescription(RtcLocalSessionDescriptionInit description) => + new(description.Type ?? throw new InvalidOperationException( + "Cannot convert RtcLocalSessionDescriptionInit to RtcSessionDescription: Type is null."), + description.Sdp); +} + +/// +/// The description passed to . +/// Models RTCLocalSessionDescriptionInit where type is optional — the implementation +/// may infer the type when it is omitted. +/// +/// +/// Implicitly converts from so that the value returned by +/// or +/// can be passed directly to without a cast. +/// +/// +public record struct RtcLocalSessionDescriptionInit +{ + /// + /// Initializes a local session description init with an optional type and SDP payload. + /// + /// + /// The SDP description type, or to let the implementation infer the type + /// from the current signaling state. + /// + /// The SDP payload text. Defaults to empty string. + public RtcLocalSessionDescriptionInit(RtcSdpType? type = null, string sdp = "") + { + Type = type; + Sdp = sdp; + } + + /// + /// Gets the SDP description type, or if the implementation should infer it. + /// + /// + public RtcSdpType? Type { get; init; } /// /// Gets the SDP payload string. /// - public readonly string Sdp; -} \ No newline at end of file + /// + public string Sdp { get; init; } = string.Empty; + + /// + /// Implicitly converts an (required type) to an + /// (optional type). + /// + public static implicit operator RtcLocalSessionDescriptionInit(RtcSessionDescription description) => + new(description.Type, description.Sdp); +}