/// <summary> /// Get a snapshot of all transceivers associated with this PeerConnection. /// This PeerConnection owns the returned transceivers. /// </summary> /// <remarks>This method is thread safe</remarks> /// <returns></returns> public IReadOnlyList <RtpTransceiver> GetTransceivers() { SafetyCheck(); PeerConnectionInterop.GetTransceivers(_handle, out var nativeTransceiverArray, out var nativeTransceiverArraySize); // When there is no transceivers, // The unmanaged code returns size = 0 and nativeTransceiverArray will be IntPtr.Zero, // therefore we ignore that. if (nativeTransceiverArraySize > 0) { try { for (var i = 0; i < nativeTransceiverArraySize; i++) { var nativeTransceiverIntPtr = Marshal.ReadIntPtr(nativeTransceiverArray, i * IntPtr.Size); if (false == _knownTransceivers.ContainsKey(nativeTransceiverIntPtr)) { _knownTransceivers[nativeTransceiverIntPtr] = new RtpTransceiver( nativeTransceiverIntPtr, _signallingThread); } } } finally { PeerConnectionInterop.FreeGetTransceiversResult(_handle, nativeTransceiverArray); } } // Return transceivers as a copy return(_knownTransceivers.Values.ToList()); }
/// <summary> /// Call this immediately after CreateAnswer() /// </summary> /// <exception cref="Errors.SetSessionDescriptionFailedException"></exception> public void SetLocalSessionDescription(string type, string sdp, Callback observer) { if (type is null) { throw new ArgumentNullException(nameof(type)); } if (sdp is null) { throw new ArgumentNullException(nameof(sdp)); } if (observer is null) { throw new ArgumentNullException(nameof(observer)); } SafetyCheck(); RequireCallbackNotSet(_setLocalSessionDescCallback); _setLocalSessionDescCallback = new PeerConnectionInterop.SetSessionDescriptionCallback( (userData, sucess, errorMessage) => { _signallingThread.EnsureCurrentThread(); _setLocalSessionDescCallback = null; Complete(observer, sucess, errorMessage); }); _pendingCallbacks.Add(observer); PeerConnectionInterop.SetLocalSessionDescription(_handle, type, sdp, _setLocalSessionDescCallback, IntPtr.Zero); }
/// <summary> /// Generate an SDP offer. /// </summary> /// <remarks>Can be called from any thread</remarks> /// <returns></returns> public void CreateOffer(Callback <RTCSessionDescription> callback) { SafetyCheck(); RequireCallbackNotSet(_createOfferCallback); _createOfferCallback = new PeerConnectionInterop.CreateSdpResultCallback((userData, result) => { _signallingThread.EnsureCurrentThread(); _createOfferCallback = null; Complete(callback, result); }); _pendingCallbacks.Add(callback); PeerConnectionInterop.CreateOffer(_handle, _createOfferCallback, IntPtr.Zero); }
/// <summary> /// Only call this after SetRemoteSessionDescriptionAsync completes /// </summary> /// <exception cref="Errors.AddIceCandidateFailedException" public void AddIceCandidate(RTCIceCandidate iceCandidate) { SafetyCheck(); Require.NotEmpty(iceCandidate); if (false == PeerConnectionInterop.AddIceCandidate( _handle, iceCandidate.SdpMid, iceCandidate.SdpMLineIndex, iceCandidate.Candidate)) { throw new Errors.AddIceCandidateFailedException("Add ICE candidate failed, check RTC logs"); } }
/// <summary> /// Close the native PeerConnection, /// note that pending async operations such as the CreateAnswer() /// method may still continue execute after the PeerConnection is closed. /// </summary> public void Close() { SafetyCheck(); if (_isClosed) { throw new InvalidOperationException("Already closed"); } _isClosed = true; _pendingCallbacks.ToList().ForEach(l => l.Error("Cancelled")); _pendingCallbacks.Clear(); PeerConnectionInterop.Close(_handle); }
/// <summary> /// No longer used but still keep here for integration tests /// </summary> /// <param name="track"></param> /// <param name="streamId">Id of the stream, doesn't have to exist</param> /// <exception cref="AddTrackFailedException"></exception> /// <remarks>Can be called from anythread, the libWebRTC will proxy to the correct thread</remarks> /// <returns>RtpSender, this PeerConnection takes ownership</returns> internal RtpSender AddTrack(MediaStreamTrack track, Guid streamId) { SafetyCheck(); Require.NotNull(track); Require.NotEmpty(streamId); var rtpSenderPtr = PeerConnectionInterop.AddTrack(_handle, track.Handle, streamId.ToString()); if (rtpSenderPtr == IntPtr.Zero) { throw new AddTrackFailedException(); } var rtpSender = new RtpSender(rtpSenderPtr, _signallingThread); return(rtpSender); }
/// <summary> /// Generate an answer after the remote sdp is set /// </summary> /// <returns></returns> /// <remarks>Can be called from any thread</remarks> /// <exception cref="Errors.CreateAnswerFailedException" /> public void CreateAnswer(Callback <RTCSessionDescription> callback) { if (callback is null) { throw new ArgumentNullException(nameof(callback)); } SafetyCheck(); RequireCallbackNotSet(_createAnswerCallback); _createAnswerCallback = new PeerConnectionInterop.CreateSdpResultCallback((userData, result) => { _signallingThread.EnsureCurrentThread(); _createAnswerCallback = null; Complete(callback, result); }); _pendingCallbacks.Add(callback); PeerConnectionInterop.CreateAnswer(_handle, _createAnswerCallback, IntPtr.Zero); }
public RtpTransceiver AddTransceiver(MediaKind kind, RtpTransceiverDirection direction) { SafetyCheck(); if (kind == MediaKind.Data) { throw new NotSupportedException(); } var transceiverPtr = PeerConnectionInterop.AddTransceiver(_handle, kind == MediaKind.Audio, direction); if (transceiverPtr == IntPtr.Zero) { throw new AddTransceiverFailedException(); } var tmp = new RtpTransceiver(transceiverPtr, _signallingThread); _knownTransceivers.Add(transceiverPtr, tmp); return(tmp); }
protected override void ReleaseHandle(IntPtr handle) => PeerConnectionInterop.Destroy(handle);
/// <summary> /// No longer used but still keep it here for tests /// </summary> /// <param name="rtpSender">The track, represented by its RtpSender</param> /// <remarks>Can be called from any thread, will be proxied to signalling thread by the lib.</remarks> internal void RemoveTrack(RtpSender rtpSender) { SafetyCheck(); PeerConnectionInterop.RemoveTrack(_handle, rtpSender.Handle); }