diff --git a/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m b/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m index 86d18d1333..8fb1841e39 100644 --- a/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m +++ b/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m @@ -330,6 +330,7 @@ static BOOL const kARDAppClientEnableTracing = NO; [_messageQueue insertObject:message atIndex:0]; break; case kARDSignalingMessageTypeCandidate: + case kARDSignalingMessageTypeCandidateRemoval: [_messageQueue addObject:message]; break; case kARDSignalingMessageTypeBye: @@ -409,6 +410,16 @@ static BOOL const kARDAppClientEnableTracing = NO; }); } +- (void)peerConnection:(RTCPeerConnection *)peerConnection + didRemoveIceCandidates:(NSArray *)candidates { + dispatch_async(dispatch_get_main_queue(), ^{ + ARDICECandidateRemovalMessage *message = + [[ARDICECandidateRemovalMessage alloc] + initWithRemovedCandidates:candidates]; + [self sendSignalingMessage:message]; + }); +} + - (void)peerConnection:(RTCPeerConnection *)peerConnection didOpenDataChannel:(RTCDataChannel *)dataChannel { } @@ -573,6 +584,12 @@ static BOOL const kARDAppClientEnableTracing = NO; [_peerConnection addIceCandidate:candidateMessage.candidate]; break; } + case kARDSignalingMessageTypeCandidateRemoval: { + ARDICECandidateRemovalMessage *candidateMessage = + (ARDICECandidateRemovalMessage *)message; + [_peerConnection removeIceCandidates:candidateMessage.candidates]; + break; + } case kARDSignalingMessageTypeBye: // Other client disconnected. // TODO(tkchin): support waiting in room for next client. For now just diff --git a/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.h b/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.h index 18eafe2673..e60517282b 100644 --- a/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.h +++ b/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.h @@ -15,6 +15,7 @@ typedef enum { kARDSignalingMessageTypeCandidate, + kARDSignalingMessageTypeCandidateRemoval, kARDSignalingMessageTypeOffer, kARDSignalingMessageTypeAnswer, kARDSignalingMessageTypeBye, @@ -37,6 +38,15 @@ typedef enum { @end +@interface ARDICECandidateRemovalMessage : ARDSignalingMessage + +@property(nonatomic, readonly) NSArray *candidates; + +- (instancetype)initWithRemovedCandidates: + (NSArray *)candidates; + +@end + @interface ARDSessionDescriptionMessage : ARDSignalingMessage @property(nonatomic, readonly) RTCSessionDescription *sessionDescription; diff --git a/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.m b/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.m index a5ca29576a..3fab185848 100644 --- a/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.m +++ b/webrtc/examples/objc/AppRTCDemo/ARDSignalingMessage.m @@ -16,7 +16,8 @@ #import "RTCIceCandidate+JSON.h" #import "RTCSessionDescription+JSON.h" -static NSString const *kARDSignalingMessageTypeKey = @"type"; +static NSString * const kARDSignalingMessageTypeKey = @"type"; +static NSString * const kARDTypeValueRemoveCandidates = @"remove-candidates"; @implementation ARDSignalingMessage @@ -47,6 +48,12 @@ static NSString const *kARDSignalingMessageTypeKey = @"type"; RTCIceCandidate *candidate = [RTCIceCandidate candidateFromJSONDictionary:values]; message = [[ARDICECandidateMessage alloc] initWithCandidate:candidate]; + } else if ([typeString isEqualToString:kARDTypeValueRemoveCandidates]) { + RTCLogInfo(@"Received remove-candidates message"); + NSArray *candidates = + [RTCIceCandidate candidatesFromJSONDictionary:values]; + message = [[ARDICECandidateRemovalMessage alloc] + initWithRemovedCandidates:candidates]; } else if ([typeString isEqualToString:@"offer"] || [typeString isEqualToString:@"answer"]) { RTCSessionDescription *description = @@ -84,6 +91,27 @@ static NSString const *kARDSignalingMessageTypeKey = @"type"; @end +@implementation ARDICECandidateRemovalMessage + +@synthesize candidates = _candidates; + +- (instancetype)initWithRemovedCandidates:( + NSArray *)candidates { + NSParameterAssert(candidates.count); + if (self = [super initWithType:kARDSignalingMessageTypeCandidateRemoval]) { + _candidates = candidates; + } + return self; +} + +- (NSData *)JSONData { + return + [RTCIceCandidate JSONDataForIceCandidates:_candidates + withType:kARDTypeValueRemoveCandidates]; +} + +@end + @implementation ARDSessionDescriptionMessage @synthesize sessionDescription = _sessionDescription; diff --git a/webrtc/examples/objc/AppRTCDemo/ARDWebSocketChannel.m b/webrtc/examples/objc/AppRTCDemo/ARDWebSocketChannel.m index 87a690ebeb..6f60380bff 100644 --- a/webrtc/examples/objc/AppRTCDemo/ARDWebSocketChannel.m +++ b/webrtc/examples/objc/AppRTCDemo/ARDWebSocketChannel.m @@ -233,6 +233,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; // Should not receive answer in loopback scenario. break; case kARDSignalingMessageTypeCandidate: + case kARDSignalingMessageTypeCandidateRemoval: // Send back to server. [self sendMessage:message]; break; diff --git a/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.h b/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.h index 1051f8ee48..d2e5e33b5b 100644 --- a/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.h +++ b/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.h @@ -13,6 +13,10 @@ @interface RTCIceCandidate (JSON) + (RTCIceCandidate *)candidateFromJSONDictionary:(NSDictionary *)dictionary; ++ (NSArray *)candidatesFromJSONDictionary: + (NSDictionary *)dictionary; ++ (NSData *)JSONDataForIceCandidates:(NSArray *)candidates + withType:(NSString *)typeValue; - (NSData *)JSONData; @end diff --git a/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m b/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m index f20c490682..b1be7fb7e3 100644 --- a/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m +++ b/webrtc/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m @@ -17,6 +17,8 @@ static NSString const *kRTCICECandidateTypeValue = @"candidate"; static NSString const *kRTCICECandidateMidKey = @"id"; static NSString const *kRTCICECandidateMLineIndexKey = @"label"; static NSString const *kRTCICECandidateSdpKey = @"candidate"; +static NSString const *kRTCICECandidatesTypeKey = @"candidates"; + @implementation RTCIceCandidate (JSON) @@ -30,6 +32,43 @@ static NSString const *kRTCICECandidateSdpKey = @"candidate"; sdpMid:mid]; } ++ (NSData *)JSONDataForIceCandidates:(NSArray *)candidates + withType:(NSString *)typeValue { + NSMutableArray *jsonCandidates = + [NSMutableArray arrayWithCapacity:candidates.count]; + for (RTCIceCandidate *candidate in candidates) { + NSDictionary *jsonCandidate = [candidate JSONDictionary]; + [jsonCandidates addObject:jsonCandidate]; + } + NSDictionary *json = @{ + kRTCICECandidateTypeKey : typeValue, + kRTCICECandidatesTypeKey : jsonCandidates + }; + NSError *error = nil; + NSData *data = + [NSJSONSerialization dataWithJSONObject:json + options:NSJSONWritingPrettyPrinted + error:&error]; + if (error) { + RTCLogError(@"Error serializing JSON: %@", error); + return nil; + } + return data; +} + ++ (NSArray *)candidatesFromJSONDictionary: + (NSDictionary *)dictionary { + NSArray *jsonCandidates = dictionary[kRTCICECandidatesTypeKey]; + NSMutableArray *candidates = + [NSMutableArray arrayWithCapacity:jsonCandidates.count]; + for (NSDictionary *jsonCandidate in jsonCandidates) { + RTCIceCandidate *candidate = + [RTCIceCandidate candidateFromJSONDictionary:jsonCandidate]; + [candidates addObject:candidate]; + } + return candidates; +} + - (NSData *)JSONData { NSDictionary *json = @{ kRTCICECandidateTypeKey : kRTCICECandidateTypeValue, @@ -49,4 +88,13 @@ static NSString const *kRTCICECandidateSdpKey = @"candidate"; return data; } +- (NSDictionary *)JSONDictionary{ + NSDictionary *json = @{ + kRTCICECandidateMLineIndexKey : @(self.sdpMLineIndex), + kRTCICECandidateMidKey : self.sdpMid, + kRTCICECandidateSdpKey : self.sdp + }; + return json; +} + @end diff --git a/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection+Private.h b/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection+Private.h index cbae3607cf..92bdfa3bf5 100644 --- a/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection+Private.h +++ b/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection+Private.h @@ -45,6 +45,9 @@ class PeerConnectionDelegateAdapter : public PeerConnectionObserver { void OnIceCandidate(const IceCandidateInterface *candidate) override; + void OnIceCandidatesRemoved( + const std::vector& candidates) override; + private: __weak RTCPeerConnection *peer_connection_; }; diff --git a/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection.mm b/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection.mm index 9a488fdf49..3fcc652e69 100644 --- a/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection.mm +++ b/webrtc/sdk/objc/Framework/Classes/RTCPeerConnection.mm @@ -25,6 +25,7 @@ #include +#include "webrtc/api/jsepicecandidate.h" #include "webrtc/base/checks.h" NSString * const kRTCPeerConnectionErrorDomain = @@ -182,6 +183,23 @@ void PeerConnectionDelegateAdapter::OnIceCandidate( [peer_connection.delegate peerConnection:peer_connection didGenerateIceCandidate:iceCandidate]; } + +void PeerConnectionDelegateAdapter::OnIceCandidatesRemoved( + const std::vector& candidates) { + NSMutableArray* ice_candidates = + [NSMutableArray arrayWithCapacity:candidates.size()]; + for (const auto& candidate : candidates) { + std::unique_ptr candidate_wrapper( + new JsepIceCandidate(candidate.transport_name(), -1, candidate)); + RTCIceCandidate* ice_candidate = [[RTCIceCandidate alloc] + initWithNativeCandidate:candidate_wrapper.get()]; + [ice_candidates addObject:ice_candidate]; + } + RTCPeerConnection* peer_connection = peer_connection_; + [peer_connection.delegate peerConnection:peer_connection + didRemoveIceCandidates:ice_candidates]; +} + } // namespace webrtc @@ -273,6 +291,22 @@ void PeerConnectionDelegateAdapter::OnIceCandidate( _peerConnection->AddIceCandidate(iceCandidate.get()); } +- (void)removeIceCandidates:(NSArray *)iceCandidates { + std::vector candidates; + for (RTCIceCandidate *iceCandidate in iceCandidates) { + std::unique_ptr candidate( + iceCandidate.nativeCandidate); + if (candidate) { + candidates.push_back(candidate->candidate()); + // Need to fill the transport name from the sdp_mid. + candidates.back().set_transport_name(candidate->sdp_mid()); + } + } + if (!candidates.empty()) { + _peerConnection->RemoveIceCandidates(candidates); + } +} + - (void)addStream:(RTCMediaStream *)stream { if (!_peerConnection->AddStream(stream.nativeMediaStream)) { RTCLogError(@"Failed to add stream: %@", stream); diff --git a/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCPeerConnection.h b/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCPeerConnection.h index 2ba8661d13..922ccfa0ee 100644 --- a/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCPeerConnection.h +++ b/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCPeerConnection.h @@ -98,6 +98,10 @@ RTC_EXPORT - (void)peerConnection:(RTCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate; +/** Called when a group of local Ice candidates have been removed. */ +- (void)peerConnection:(RTCPeerConnection *)peerConnection + didRemoveIceCandidates:(NSArray *)candidates; + /** New data channel has been opened. */ - (void)peerConnection:(RTCPeerConnection *)peerConnection didOpenDataChannel:(RTCDataChannel *)dataChannel; @@ -148,6 +152,9 @@ RTC_EXPORT /** Provide a remote candidate to the ICE Agent. */ - (void)addIceCandidate:(RTCIceCandidate *)candidate; +/** Remove a group of remote candidates from the ICE Agent. */ +- (void)removeIceCandidates:(NSArray *)candidates; + /** Add a new media stream to be sent on this peer connection. */ - (void)addStream:(RTCMediaStream *)stream;