diff --git a/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.h b/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.h index 766410231e..e84fcd180f 100644 --- a/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.h +++ b/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.h @@ -12,6 +12,8 @@ #import "sdk/objc/base/RTCMacros.h" +typedef NS_ENUM(NSInteger, RTCRtpTransceiverDirection); + NS_ASSUME_NONNULL_BEGIN RTC_OBJC_EXPORT @@ -26,6 +28,9 @@ RTC_OBJC_EXPORT /** Whether the header extension is encrypted or not. */ @property(nonatomic, readonly, getter=isPreferredEncrypted) BOOL preferredEncrypted; +/** Direction of the header extension. */ +@property(nonatomic) RTCRtpTransceiverDirection direction; + - (instancetype)init NS_UNAVAILABLE; @end diff --git a/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.mm b/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.mm index 22e53384b1..9fe4a67b1e 100644 --- a/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.mm +++ b/sdk/objc/api/peerconnection/RTCRtpHeaderExtensionCapability.mm @@ -9,6 +9,7 @@ */ #import "RTCRtpHeaderExtensionCapability+Private.h" +#import "RTCRtpTransceiver+Private.h" #import "helpers/NSString+StdString.h" @@ -17,6 +18,7 @@ @synthesize uri = _uri; @synthesize preferredId = _preferredId; @synthesize preferredEncrypted = _preferredEncrypted; +@synthesize direction = _direction; - (instancetype)init { webrtc::RtpHeaderExtensionCapability nativeRtpHeaderExtensionCapability; @@ -32,6 +34,8 @@ _preferredId = [NSNumber numberWithInt:*nativeRtpHeaderExtensionCapability.preferred_id]; } _preferredEncrypted = nativeRtpHeaderExtensionCapability.preferred_encrypt; + _direction = [RTC_OBJC_TYPE(RTCRtpTransceiver) + rtpTransceiverDirectionFromNativeDirection:nativeRtpHeaderExtensionCapability.direction]; } return self; } @@ -51,6 +55,8 @@ rtpHeaderExtensionCapability.preferred_id = std::optional(_preferredId.intValue); } rtpHeaderExtensionCapability.preferred_encrypt = _preferredEncrypted; + rtpHeaderExtensionCapability.direction = + [RTC_OBJC_TYPE(RTCRtpTransceiver) nativeRtpTransceiverDirectionFromDirection:_direction]; return rtpHeaderExtensionCapability; } diff --git a/sdk/objc/api/peerconnection/RTCRtpTransceiver.h b/sdk/objc/api/peerconnection/RTCRtpTransceiver.h index 8099e1898e..f5923ed6bb 100644 --- a/sdk/objc/api/peerconnection/RTCRtpTransceiver.h +++ b/sdk/objc/api/peerconnection/RTCRtpTransceiver.h @@ -47,6 +47,7 @@ RTC_OBJC_EXPORT @class RTC_OBJC_TYPE(RTCRtpTransceiver); @class RTC_OBJC_TYPE(RTCRtpCodecCapability); +@class RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability); /** The RTCRtpTransceiver maps to the RTCRtpTransceiver defined by the * WebRTC specification. A transceiver represents a combination of an RTCRtpSender @@ -105,6 +106,17 @@ RTC_OBJC_EXPORT */ @property(nonatomic, readonly) RTCRtpTransceiverDirection direction; +/** It will contain all the RTP header extensions that are supported. + * The direction attribute for all extensions that are mandatory to use MUST be initialized to an + * appropriate value other than RTCRtpTransceiverDirectionStopped. The direction attribute for + * extensions that will not be offered by default in an initial offer MUST be initialized to + * RTCRtpTransceiverDirectionStopped. + */ +@property(nonatomic, readonly, copy) + NSArray *headerExtensionsToNegotiate; +@property(nonatomic, readonly, copy) + NSArray *negotiatedHeaderExtensions; + /** The currentDirection attribute indicates the current direction negotiated * for this transceiver. If this transceiver has never been represented in an * offer/answer exchange, or if the transceiver is stopped, the value is not @@ -130,6 +142,14 @@ RTC_OBJC_EXPORT - (void)setCodecPreferences:(NSArray *_Nullable)codecs RTC_OBJC_DEPRECATED("Use setCodecPreferences:error: instead."); +/** The setHeaderExtensionsToNegotiate method overrides the default header extensions used + * by WebRTC for this transceiver. + * https://w3c.github.io/webrtc-extensions/#ref-for-dom-rtcrtptransceiver-setheaderextensionstonegotiate + */ +- (BOOL)setHeaderExtensionsToNegotiate: + (NSArray *)extensions + error:(NSError **)error; + /** An update of directionality does not take effect immediately. Instead, * future calls to createOffer and createAnswer mark the corresponding media * descriptions as sendrecv, sendonly, recvonly, or inactive. diff --git a/sdk/objc/api/peerconnection/RTCRtpTransceiver.mm b/sdk/objc/api/peerconnection/RTCRtpTransceiver.mm index bde4d08b0f..f696a634f8 100644 --- a/sdk/objc/api/peerconnection/RTCRtpTransceiver.mm +++ b/sdk/objc/api/peerconnection/RTCRtpTransceiver.mm @@ -12,6 +12,7 @@ #import "RTCRtpCodecCapability+Private.h" #import "RTCRtpEncodingParameters+Private.h" +#import "RTCRtpHeaderExtensionCapability+Private.h" #import "RTCRtpParameters+Private.h" #import "RTCRtpReceiver+Private.h" #import "RTCRtpSender+Private.h" @@ -81,17 +82,44 @@ NSString *const kRTCRtpTransceiverErrorDomain = @"org.webrtc.RTCRtpTranceiver"; rtpTransceiverDirectionFromNativeDirection:_nativeRtpTransceiver->direction()]; } +- (NSArray *)headerExtensionsToNegotiate { + std::vector nativeHeaderExtensions( + _nativeRtpTransceiver->GetHeaderExtensionsToNegotiate()); + + NSMutableArray *headerExtensions = + [NSMutableArray arrayWithCapacity:nativeHeaderExtensions.size()]; + for (const auto &headerExtension : nativeHeaderExtensions) { + [headerExtensions addObject:[[RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability) alloc] + initWithNativeRtpHeaderExtensionCapability:headerExtension]]; + } + return headerExtensions; +} + +- (NSArray *)negotiatedHeaderExtensions { + std::vector nativeHeaderExtensions( + _nativeRtpTransceiver->GetNegotiatedHeaderExtensions()); + + NSMutableArray *headerExtensions = + [NSMutableArray arrayWithCapacity:nativeHeaderExtensions.size()]; + for (const auto &headerExtension : nativeHeaderExtensions) { + [headerExtensions addObject:[[RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability) alloc] + initWithNativeRtpHeaderExtensionCapability:headerExtension]]; + } + return headerExtensions; +} + - (void)setDirection:(RTCRtpTransceiverDirection)direction error:(NSError **)error { webrtc::RTCError nativeError = _nativeRtpTransceiver->SetDirectionWithError( [RTC_OBJC_TYPE(RTCRtpTransceiver) nativeRtpTransceiverDirectionFromDirection:direction]); if (!nativeError.ok() && error) { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey : [NSString stringWithCString:nativeError.message() + encoding:NSUTF8StringEncoding] + }; *error = [NSError errorWithDomain:kRTCRtpTransceiverErrorDomain code:static_cast(nativeError.type()) - userInfo:@{ - @"message" : [NSString stringWithCString:nativeError.message() - encoding:NSUTF8StringEncoding] - }]; + userInfo:userInfo]; } } @@ -131,6 +159,28 @@ NSString *const kRTCRtpTransceiverErrorDomain = @"org.webrtc.RTCRtpTranceiver"; [self setCodecPreferences:codecs error:nil]; } +- (BOOL)setHeaderExtensionsToNegotiate: + (NSArray *)extensions + error:(NSError **)error { + std::vector headerExtensionCapabilities; + for (RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability) * extension in extensions) { + headerExtensionCapabilities.push_back(extension.nativeRtpHeaderExtensionCapability); + } + webrtc::RTCError nativeError = + _nativeRtpTransceiver->SetHeaderExtensionsToNegotiate(headerExtensionCapabilities); + BOOL ok = nativeError.ok(); + if (!ok && error) { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey : [NSString stringWithCString:nativeError.message() + encoding:NSUTF8StringEncoding] + }; + *error = [NSError errorWithDomain:kRTCRtpTransceiverErrorDomain + code:static_cast(nativeError.type()) + userInfo:userInfo]; + } + return ok; +} + - (NSString *)description { return [NSString stringWithFormat:@"RTC_OBJC_TYPE(RTCRtpTransceiver) {\n sender: %@\n receiver: %@\n}", diff --git a/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m b/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m index e1598b60a1..448f502911 100644 --- a/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m +++ b/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m @@ -21,6 +21,7 @@ #import "api/peerconnection/RTCPeerConnectionFactory.h" #import "api/peerconnection/RTCRtpCapabilities.h" #import "api/peerconnection/RTCRtpCodecCapability.h" +#import "api/peerconnection/RTCRtpHeaderExtensionCapability.h" #import "api/peerconnection/RTCRtpReceiver.h" #import "api/peerconnection/RTCRtpSender.h" #import "api/peerconnection/RTCRtpTransceiver.h" @@ -510,6 +511,131 @@ } } +- (void)testSetHeaderExtensionsToNegotiate { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCRtpTransceiverInit) *init = + [[RTC_OBJC_TYPE(RTCRtpTransceiverInit) alloc] init]; + + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; + RTC_OBJC_TYPE(RTCRtpTransceiver) * tranceiver; + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + + peerConnection = [factory peerConnectionWithConfiguration:config + constraints:constraints + delegate:nil]; + tranceiver = [peerConnection addTransceiverOfType:RTCRtpMediaTypeVideo init:init]; + XCTAssertNotNil(tranceiver); + + NSArray *headerExtensionsToNegotiate = + tranceiver.headerExtensionsToNegotiate; + + __block RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability) *targetExtension = nil; + [headerExtensionsToNegotiate + enumerateObjectsUsingBlock:^(RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability) * extension, + NSUInteger idx, + BOOL * stop) { + if ([extension.uri isEqualToString:@"urn:ietf:params:rtp-hdrext:sdes:mid"]) { + targetExtension = extension; + } else { + extension.direction = RTCRtpTransceiverDirectionStopped; + } + }]; + + NSError *error = nil; + BOOL isOK = [tranceiver setHeaderExtensionsToNegotiate:headerExtensionsToNegotiate + error:&error]; + XCTAssertNil(error); + XCTAssertTrue(isOK); + + @autoreleasepool { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + __block BOOL completed = NO; + [peerConnection offerForConstraints:constraints + completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) *_Nullable sdp, + NSError *_Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(sdp); + + NSArray *extMaps = [self extMapsFromSDP:sdp.sdp]; + XCTAssertEqual(1, extMaps.count); + + XCTAssertNotNil(targetExtension); + XCTAssertNotNil(targetExtension.preferredId); + + NSString *expected = + [NSString stringWithFormat:@"a=extmap:%i %@", + targetExtension.preferredId.intValue, + targetExtension.uri]; + + XCTAssertTrue([expected isEqualToString:extMaps[0]]); + + completed = YES; + dispatch_semaphore_signal(semaphore); + }]; + + [peerConnection close]; + peerConnection = nil; + factory = nil; + tranceiver = nil; + + dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC)); + XCTAssertTrue(completed); + } + } +} + +- (void)testSetHeaderExtensionsToNegotiateError { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCRtpTransceiverInit) *init = + [[RTC_OBJC_TYPE(RTCRtpTransceiverInit) alloc] init]; + + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; + RTC_OBJC_TYPE(RTCRtpTransceiver) * tranceiver; + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + + peerConnection = [factory peerConnectionWithConfiguration:config + constraints:constraints + delegate:nil]; + tranceiver = [peerConnection addTransceiverOfType:RTCRtpMediaTypeVideo init:init]; + XCTAssertNotNil(tranceiver); + + NSArray *headerExtensionsToNegotiate = + tranceiver.headerExtensionsToNegotiate; + + [headerExtensionsToNegotiate + enumerateObjectsUsingBlock:^(RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability) * extension, + NSUInteger idx, + BOOL * stop) { + if ([extension.uri isEqualToString:@"urn:ietf:params:rtp-hdrext:sdes:mid"]) { + extension.direction = RTCRtpTransceiverDirectionStopped; + } + }]; + + // Stopping a mandatory extension should yield an error + NSError *error = nil; + BOOL isOK = [tranceiver setHeaderExtensionsToNegotiate:headerExtensionsToNegotiate + error:&error]; + XCTAssertNotNil(error); + XCTAssertFalse(isOK); + + [peerConnection close]; + peerConnection = nil; + factory = nil; + tranceiver = nil; + } +} + - (bool)negotiatePeerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)pc1 withPeerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)pc2 negotiationTimeout:(NSTimeInterval)timeout { @@ -574,4 +700,16 @@ return rtpMaps; } +- (NSArray *)extMapsFromSDP:(NSString *)sdp { + NSMutableArray *extMaps = [NSMutableArray new]; + NSArray *sdpLines = + [sdp componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + for (NSString *line in sdpLines) { + if ([line hasPrefix:@"a=extmap:"]) { + [extMaps addObject:line]; + } + } + return extMaps; +} + @end