NCPeerConnection.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "NCPeerConnection.h"
  6. #import <WebRTC/RTCConfiguration.h>
  7. #import <WebRTC/RTCDataChannelConfiguration.h>
  8. #import <WebRTC/RTCIceServer.h>
  9. #import <WebRTC/RTCPeerConnectionFactory.h>
  10. #import <WebRTC/RTCMediaConstraints.h>
  11. #import <WebRTC/RTCMediaStream.h>
  12. #import <WebRTC/RTCDefaultVideoEncoderFactory.h>
  13. #import <WebRTC/RTCDefaultVideoDecoderFactory.h>
  14. #import "ARDSDPUtils.h"
  15. #import "NCSignalingMessage.h"
  16. #import "NextcloudTalk-Swift.h"
  17. @interface NCPeerConnection () <RTCPeerConnectionDelegate, RTCDataChannelDelegate>
  18. @property (nonatomic, strong) NSMutableArray *queuedRemoteCandidates;
  19. @property (nonatomic, strong) RTCPeerConnection *peerConnection;
  20. @property (nonatomic, strong) RTCDataChannel *localDataChannel;
  21. @property (nonatomic, strong) RTCDataChannel *remoteDataChannel;
  22. @property (nonatomic, strong) RTCMediaStream *remoteStream;
  23. @end
  24. @implementation NCPeerConnection
  25. - (instancetype)initWithSessionId:(NSString *)sessionId sid:(NSString *)sid andICEServers:(NSArray *)iceServers forAudioOnlyCall:(BOOL)audioOnly
  26. {
  27. self = [super init];
  28. if (self) {
  29. [[WebRTCCommon shared] assertQueue];
  30. RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
  31. initWithMandatoryConstraints:nil
  32. optionalConstraints:nil];
  33. RTCConfiguration *config = [[RTCConfiguration alloc] init];
  34. [config setIceServers:iceServers];
  35. [config setSdpSemantics:RTCSdpSemanticsUnifiedPlan];
  36. RTCPeerConnectionFactory *peerConnectionFactory = [WebRTCCommon shared].peerConnectionFactory;
  37. RTCPeerConnection *peerConnection = [peerConnectionFactory peerConnectionWithConfiguration:config
  38. constraints:constraints
  39. delegate:self];
  40. _peerConnection = peerConnection;
  41. _peerId = sessionId;
  42. NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970] * 1000;
  43. _sid = sid ? sid : [NSString stringWithFormat:@"%.0f", timeStamp];
  44. _isAudioOnly = audioOnly;
  45. }
  46. return self;
  47. }
  48. - (instancetype)initForPublisherWithSessionId:(NSString *)sessionId andICEServers:(NSArray *)iceServers forAudioOnlyCall:(BOOL)audioOnly
  49. {
  50. self = [self initWithSessionId:sessionId sid:nil andICEServers:iceServers forAudioOnlyCall:audioOnly];
  51. if (self) {
  52. _isMCUPublisherPeer = YES;
  53. }
  54. return self;
  55. }
  56. #pragma mark - NSObject
  57. - (BOOL)isEqual:(id)object
  58. {
  59. if ([object isKindOfClass:[NCPeerConnection class]]) {
  60. NCPeerConnection *otherConnection = (NCPeerConnection *)object;
  61. return [otherConnection.peerConnection isEqual:self.peerConnection];
  62. }
  63. return NO;
  64. }
  65. - (void)dealloc {
  66. // [self close];
  67. NSLog(@"NCPeerConnection dealloc");
  68. }
  69. #pragma mark - Public
  70. - (NSString *)peerIdentifier
  71. {
  72. if (_sid != nil) {
  73. return [NSString stringWithFormat:@"%@-%@", _peerId, _sid];
  74. }
  75. return _peerId;
  76. }
  77. - (void)addICECandidate:(RTCIceCandidate *)candidate
  78. {
  79. [[WebRTCCommon shared] assertQueue];
  80. if (!_peerConnection.remoteDescription) {
  81. if (!self.queuedRemoteCandidates) {
  82. self.queuedRemoteCandidates = [NSMutableArray array];
  83. }
  84. NSLog(@"Queued a remote ICE candidate for later.");
  85. [self.queuedRemoteCandidates addObject:candidate];
  86. } else {
  87. NSLog(@"Adding a remote ICE candidate.");
  88. [self.peerConnection addIceCandidate:candidate completionHandler:^(NSError * _Nullable error) {
  89. if (error) {
  90. NSLog(@"Error while adding a remote ICE candidate.");
  91. }
  92. }];
  93. }
  94. }
  95. - (void)drainRemoteCandidates
  96. {
  97. [[WebRTCCommon shared] assertQueue];
  98. NSLog(@"Drain %lu remote ICE candidates.", (unsigned long)[self.queuedRemoteCandidates count]);
  99. for (RTCIceCandidate *candidate in self.queuedRemoteCandidates) {
  100. [self.peerConnection addIceCandidate:candidate completionHandler:^(NSError * _Nullable error) {
  101. if (error) {
  102. NSLog(@"Error while adding a remote ICE candidate.");
  103. }
  104. }];
  105. }
  106. self.queuedRemoteCandidates = nil;
  107. }
  108. - (void)setRemoteDescription:(RTCSessionDescription *)sessionDescription
  109. {
  110. [[WebRTCCommon shared] assertQueue];
  111. __weak NCPeerConnection *weakSelf = self;
  112. RTCSessionDescription *sdpPreferringCodec = [ARDSDPUtils descriptionForDescription:sessionDescription preferredVideoCodec:@"H264"];
  113. [_peerConnection setRemoteDescription:sdpPreferringCodec completionHandler:^(NSError *error) {
  114. [[WebRTCCommon shared] dispatch:^{
  115. NCPeerConnection *strongSelf = weakSelf;
  116. if (strongSelf) {
  117. [strongSelf peerConnectionDidSetRemoteSessionDescription:sdpPreferringCodec error:error];
  118. }
  119. }];
  120. }];
  121. }
  122. - (void)sendOffer
  123. {
  124. [self sendOfferWithConstraints:[self defaultOfferConstraints]];
  125. }
  126. - (void)sendPublisherOffer
  127. {
  128. [self sendOfferWithConstraints:[self publisherOfferConstraints]];
  129. }
  130. - (void)sendOfferWithConstraints:(RTCMediaConstraints *)constraints
  131. {
  132. [[WebRTCCommon shared] assertQueue];
  133. //Create data channel before creating the offer to enable data channels
  134. RTCDataChannelConfiguration* config = [[RTCDataChannelConfiguration alloc] init];
  135. config.isNegotiated = NO;
  136. _localDataChannel = [_peerConnection dataChannelForLabel:@"status" configuration:config];
  137. _localDataChannel.delegate = self;
  138. // Create offer
  139. __weak NCPeerConnection *weakSelf = self;
  140. [_peerConnection offerForConstraints:constraints completionHandler:^(RTCSessionDescription *sdp, NSError *error) {
  141. [[WebRTCCommon shared] dispatch:^{
  142. NCPeerConnection *strongSelf = weakSelf;
  143. if (strongSelf) {
  144. [strongSelf peerConnectionDidCreateLocalSessionDescription:sdp error:error];
  145. }
  146. }];
  147. }];
  148. }
  149. - (void)setStatusForDataChannelMessageType:(NSString *)type withPayload:(id)payload
  150. {
  151. [[WebRTCCommon shared] assertQueue];
  152. if ([type isEqualToString:@"nickChanged"]) {
  153. NSString *nick = @"";
  154. if ([payload isKindOfClass:[NSString class]]) {
  155. nick = payload;
  156. } else if ([payload isKindOfClass:[NSDictionary class]]) {
  157. nick = [payload objectForKey:@"name"];
  158. }
  159. _peerName = nick;
  160. [self.delegate peerConnection:self didReceivePeerNick:nick];
  161. } else {
  162. // Check remote audio/video status
  163. if ([type isEqualToString:@"audioOn"]) {
  164. _isRemoteAudioDisabled = NO;
  165. } else if ([type isEqualToString:@"audioOff"]) {
  166. _isRemoteAudioDisabled = YES;
  167. } else if ([type isEqualToString:@"videoOn"]) {
  168. _isRemoteVideoDisabled = NO;
  169. } else if ([type isEqualToString:@"videoOff"]) {
  170. _isRemoteVideoDisabled = YES;
  171. } else if ([type isEqualToString:@"speaking"]) {
  172. _isPeerSpeaking = YES;
  173. } else if ([type isEqualToString:@"stoppedSpeaking"]) {
  174. _isPeerSpeaking = NO;
  175. } else if ([type isEqualToString:@"raiseHand"]) {
  176. _isHandRaised = [payload boolValue];
  177. }
  178. [self.delegate peerConnection:self didReceiveStatusDataChannelMessage:type];
  179. }
  180. }
  181. - (void)close
  182. {
  183. [[WebRTCCommon shared] assertQueue];
  184. RTCMediaStream *localStream = [self.peerConnection.localStreams firstObject];
  185. if (localStream) {
  186. [self.peerConnection removeStream:localStream];
  187. }
  188. [self.peerConnection close];
  189. self.remoteStream = nil;
  190. self.localDataChannel = nil;
  191. self.remoteDataChannel = nil;
  192. self.peerConnection = nil;
  193. }
  194. #pragma mark - Public RTC getters
  195. - (RTCPeerConnection *)getPeerConnection {
  196. [[WebRTCCommon shared] assertQueue];
  197. return self.peerConnection;
  198. }
  199. - (RTCDataChannel *)getLocalDataChannel {
  200. [[WebRTCCommon shared] assertQueue];
  201. return self.localDataChannel;
  202. }
  203. - (RTCDataChannel *)getRemoteDataChannel {
  204. [[WebRTCCommon shared] assertQueue];
  205. return self.remoteDataChannel;
  206. }
  207. - (RTCMediaStream *)getRemoteStream {
  208. [[WebRTCCommon shared] assertQueue];
  209. return self.remoteStream;
  210. }
  211. - (BOOL)hasRemoteStream {
  212. return (self.remoteStream != nil);
  213. }
  214. #pragma mark - RTCPeerConnectionDelegate
  215. // Delegates from RTCPeerConnection are called on the "signaling_thread" of WebRTC
  216. - (void)peerConnection:(RTCPeerConnection *)peerConnection didChangeSignalingState:(RTCSignalingState)stateChanged
  217. {
  218. NSLog(@"Signaling state with '%@' changed to: %@", self.peerId, [self stringForSignalingState:stateChanged]);
  219. }
  220. - (void)peerConnection:(RTCPeerConnection *)peerConnection didAddStream:(RTCMediaStream *)stream
  221. {
  222. [[WebRTCCommon shared] dispatch:^{
  223. NSLog(@"Received %lu video tracks and %lu audio tracks from %@",
  224. (unsigned long)stream.videoTracks.count,
  225. (unsigned long)stream.audioTracks.count,
  226. self.peerId);
  227. self.remoteStream = stream;
  228. if ([stream.videoTracks count] == 0) {
  229. self.isRemoteVideoDisabled = YES;
  230. }
  231. [self.delegate peerConnection:self didAddStream:stream];
  232. }];
  233. }
  234. - (void)peerConnection:(RTCPeerConnection *)peerConnection didRemoveStream:(RTCMediaStream *)stream
  235. {
  236. [[WebRTCCommon shared] dispatch:^{
  237. NSLog(@"Stream was removed from %@", self.peerId);
  238. self.remoteStream = nil;
  239. [self.delegate peerConnection:self didRemoveStream:stream];
  240. }];
  241. }
  242. - (void)peerConnection:(RTCPeerConnection *)peerConnection didAddReceiver:(RTCRtpReceiver *)rtpReceiver streams:(NSArray<RTCMediaStream *> *)mediaStreams
  243. {
  244. [[WebRTCCommon shared] dispatch:^{
  245. RTCMediaStream *stream = mediaStreams[0];
  246. if (!stream) {
  247. return;
  248. }
  249. NSLog(@"Received %lu video tracks and %lu audio tracks from %@",
  250. (unsigned long)stream.videoTracks.count,
  251. (unsigned long)stream.audioTracks.count,
  252. self.peerId);
  253. self.remoteStream = stream;
  254. [self.delegate peerConnection:self didAddStream:stream];
  255. }];
  256. }
  257. - (void)peerConnection:(RTCPeerConnection *)peerConnection didRemoveReceiver:(RTCRtpReceiver *)rtpReceiver
  258. {
  259. [[WebRTCCommon shared] dispatch:^{
  260. NSLog(@"Receiver was removed from %@", self.peerId);
  261. self.remoteStream = nil;
  262. [self.delegate peerConnection:self didRemoveStream:nil];
  263. }];
  264. }
  265. - (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection
  266. {
  267. NSLog(@"WARNING: Renegotiation needed but unimplemented.");
  268. }
  269. - (void)peerConnection:(RTCPeerConnection *)peerConnection didChangeIceConnectionState:(RTCIceConnectionState)newState
  270. {
  271. [[WebRTCCommon shared] dispatch:^{
  272. NSLog(@"ICE state with '%@' changed to: %@", self.peerId, [self stringForConnectionState:newState]);
  273. [self.delegate peerConnection:self didChangeIceConnectionState:newState];
  274. }];
  275. }
  276. - (void)peerConnection:(RTCPeerConnection *)peerConnection didChangeIceGatheringState:(RTCIceGatheringState)newState
  277. {
  278. NSLog(@"ICE gathering state with '%@' changed to : %@", self.peerId, [self stringForGatheringState:newState]);
  279. }
  280. - (void)peerConnection:(RTCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate
  281. {
  282. [[WebRTCCommon shared] dispatch:^{
  283. NSLog(@"Peer '%@' did generate Ice Candidate: %@", self.peerId, candidate);
  284. [self.delegate peerConnection:self didGenerateIceCandidate:candidate];
  285. }];
  286. }
  287. - (void)peerConnection:(RTCPeerConnection *)peerConnection didRemoveIceCandidates:(NSArray<RTCIceCandidate *> *)candidates
  288. {
  289. NSLog(@"PeerConnection didRemoveIceCandidates delegate has been called.");
  290. }
  291. - (void)peerConnection:(RTCPeerConnection *)peerConnection didOpenDataChannel:(RTCDataChannel *)dataChannel
  292. {
  293. [[WebRTCCommon shared] dispatch:^{
  294. if (self->_remoteDataChannel) {
  295. NSLog(@"Remote data channel with label '%@' exists, but received open event for data channel with label '%@'", self->_remoteDataChannel.label, dataChannel.label);
  296. }
  297. self->_remoteDataChannel = dataChannel;
  298. self->_remoteDataChannel.delegate = self;
  299. NSLog(@"Remote data channel '%@' was opened.", dataChannel.label);
  300. }];
  301. }
  302. #pragma mark - RTCDataChannelDelegate
  303. // Delegates from RTCDataChannel are called on the "signaling_thread" of WebRTC
  304. - (void)dataChannelDidChangeState:(RTCDataChannel *)dataChannel
  305. {
  306. [[WebRTCCommon shared] dispatch:^{
  307. NSLog(@"Data channel '%@' did change state: %ld", dataChannel.label, (long)dataChannel.readyState);
  308. // if (dataChannel.readyState == RTCDataChannelStateOpen && [dataChannel.label isEqualToString:@"status"]) {
  309. // [self.delegate peerConnectionDidOpenStatusDataChannel:self];
  310. // }
  311. }];
  312. }
  313. - (void)dataChannel:(RTCDataChannel *)dataChannel didReceiveMessageWithBuffer:(RTCDataBuffer *)buffer
  314. {
  315. [[WebRTCCommon shared] dispatch:^{
  316. NSDictionary *message = [self getDataChannelMessageFromJSONData:buffer.data];
  317. NSString *messageType = [message objectForKey:@"type"];
  318. id messagePayload = [message objectForKey:@"payload"];
  319. [self setStatusForDataChannelMessageType:messageType withPayload:messagePayload];
  320. }];
  321. }
  322. - (NSDictionary *)getDataChannelMessageFromJSONData:(NSData *)jsonData
  323. {
  324. NSError *error;
  325. NSDictionary* messageDict = [NSJSONSerialization JSONObjectWithData:jsonData
  326. options:kNilOptions
  327. error:&error];
  328. if (!messageDict) {
  329. NSLog(@"Error parsing data channel message: %@", error);
  330. }
  331. return messageDict;
  332. }
  333. - (NSData *)createDataChannelMessage:(NSDictionary *)message
  334. {
  335. NSError *error;
  336. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:message
  337. options:0
  338. error:&error];
  339. if (!jsonData) {
  340. NSLog(@"Error creating data channel message: %@", error);
  341. }
  342. return jsonData;
  343. }
  344. - (void)sendDataChannelMessageOfType:(NSString *)type withPayload:(id)payload
  345. {
  346. [[WebRTCCommon shared] assertQueue];
  347. NSDictionary *message = @{@"type": type};
  348. if (payload) {
  349. message = @{@"type": type,
  350. @"payload": payload};
  351. }
  352. NSData *jsonMessage = [self createDataChannelMessage:message];
  353. RTCDataBuffer *dataBuffer = [[RTCDataBuffer alloc] initWithData:jsonMessage isBinary:NO];
  354. if (_localDataChannel) {
  355. [_localDataChannel sendData:dataBuffer];
  356. } else if (_remoteDataChannel) {
  357. [_remoteDataChannel sendData:dataBuffer];
  358. } else {
  359. NSLog(@"No data channel opened");
  360. }
  361. }
  362. #pragma mark - RTCSessionDescriptionDelegate
  363. // Delegates from RTCSessionDescription are already dispatched to the webrtc client thread
  364. - (void)peerConnectionDidCreateLocalSessionDescription:(RTCSessionDescription *)sdp error:(NSError *)error
  365. {
  366. if (error) {
  367. NSLog(@"Failed to create local session description for peer %@. Error: %@", _peerId, error);
  368. return;
  369. }
  370. [[WebRTCCommon shared] assertQueue];
  371. // Set H264 as preferred codec.
  372. RTCSessionDescription *sdpPreferringCodec = [ARDSDPUtils descriptionForDescription:sdp preferredVideoCodec:@"H264"];
  373. __weak NCPeerConnection *weakSelf = self;
  374. [self->_peerConnection setLocalDescription:sdpPreferringCodec completionHandler:^(NSError *error) {
  375. if (error) {
  376. NSLog(@"Failed to set local session description: %@", error);
  377. return;
  378. }
  379. [[WebRTCCommon shared] dispatch:^{
  380. NCPeerConnection *strongSelf = weakSelf;
  381. if (strongSelf) {
  382. [strongSelf.delegate peerConnection:strongSelf needsToSendSessionDescription:sdpPreferringCodec];
  383. }
  384. }];
  385. }];
  386. }
  387. - (void)peerConnectionDidSetRemoteSessionDescription:(RTCSessionDescription *)sessionDescription error:(NSError *)error
  388. {
  389. if (error) {
  390. NSLog(@"Failed to set remote session description for peer %@. Error: %@", _peerId, error);
  391. return;
  392. }
  393. [[WebRTCCommon shared] assertQueue];
  394. // If we just set a remote offer we need to create an answer and set it as local description.
  395. if (self->_peerConnection.signalingState == RTCSignalingStateHaveRemoteOffer) {
  396. // Create data channel before sending answer
  397. RTCDataChannelConfiguration* config = [[RTCDataChannelConfiguration alloc] init];
  398. config.isNegotiated = NO;
  399. self->_localDataChannel = [self->_peerConnection dataChannelForLabel:@"status" configuration:config];
  400. self->_localDataChannel.delegate = self;
  401. // Stop video transceiver in audio only peer connections
  402. // Constraints are no longer supported when creating answers (with Unified Plan semantics)
  403. if (_isAudioOnly) {
  404. for (RTCRtpTransceiver *transceiver in self->_peerConnection.transceivers) {
  405. if (transceiver.mediaType == RTCRtpMediaTypeVideo) {
  406. [transceiver stopInternal];
  407. NSLog(@"Stop video transceiver in audio only peer connections.");
  408. }
  409. }
  410. }
  411. // Create answer
  412. RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
  413. __weak NCPeerConnection *weakSelf = self;
  414. [self->_peerConnection answerForConstraints:constraints completionHandler:^(RTCSessionDescription *sdp, NSError *error) {
  415. [[WebRTCCommon shared] dispatch:^{
  416. NCPeerConnection *strongSelf = weakSelf;
  417. if (strongSelf) {
  418. [strongSelf peerConnectionDidCreateLocalSessionDescription:sdp error:error];
  419. }
  420. }];
  421. }];
  422. }
  423. if (self->_peerConnection.remoteDescription) {
  424. [self drainRemoteCandidates];
  425. }
  426. }
  427. #pragma mark - Utils
  428. - (RTCMediaConstraints *)defaultAnswerConstraints
  429. {
  430. return [self defaultOfferConstraints];
  431. }
  432. - (RTCMediaConstraints *)defaultOfferConstraints
  433. {
  434. NSDictionary *mandatoryConstraints = @{
  435. @"OfferToReceiveAudio" : @"true",
  436. @"OfferToReceiveVideo" : @"true"
  437. };
  438. if (_isAudioOnly) {
  439. mandatoryConstraints = @{
  440. @"OfferToReceiveAudio" : @"true",
  441. @"OfferToReceiveVideo" : @"false"
  442. };
  443. }
  444. NSDictionary *optionalConstraints = @{
  445. @"internalSctpDataChannels": @"true",
  446. @"DtlsSrtpKeyAgreement": @"true"
  447. };
  448. RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
  449. initWithMandatoryConstraints:mandatoryConstraints
  450. optionalConstraints:optionalConstraints];
  451. return constraints;
  452. }
  453. - (RTCMediaConstraints *)publisherOfferConstraints
  454. {
  455. NSDictionary *mandatoryConstraints = @{
  456. @"OfferToReceiveAudio" : @"false",
  457. @"OfferToReceiveVideo" : @"false"
  458. };
  459. NSDictionary *optionalConstraints = @{
  460. @"internalSctpDataChannels": @"true",
  461. @"DtlsSrtpKeyAgreement": @"true"
  462. };
  463. RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
  464. initWithMandatoryConstraints:mandatoryConstraints
  465. optionalConstraints:optionalConstraints];
  466. return constraints;
  467. }
  468. - (NSString *)stringForSignalingState:(RTCSignalingState)state
  469. {
  470. switch (state) {
  471. case RTCSignalingStateStable:
  472. return @"Stable";
  473. break;
  474. case RTCSignalingStateHaveLocalOffer:
  475. return @"Have Local Offer";
  476. break;
  477. case RTCSignalingStateHaveRemoteOffer:
  478. return @"Have Remote Offer";
  479. break;
  480. case RTCSignalingStateClosed:
  481. return @"Closed";
  482. break;
  483. default:
  484. return @"Other state";
  485. break;
  486. }
  487. }
  488. - (NSString *)stringForConnectionState:(RTCIceConnectionState)state
  489. {
  490. switch (state) {
  491. case RTCIceConnectionStateNew:
  492. return @"New";
  493. break;
  494. case RTCIceConnectionStateChecking:
  495. return @"Checking";
  496. break;
  497. case RTCIceConnectionStateConnected:
  498. return @"Connected";
  499. break;
  500. case RTCIceConnectionStateCompleted:
  501. return @"Completed";
  502. break;
  503. case RTCIceConnectionStateFailed:
  504. return @"Failed";
  505. break;
  506. case RTCIceConnectionStateDisconnected:
  507. return @"Disconnected";
  508. break;
  509. case RTCIceConnectionStateClosed:
  510. return @"Closed";
  511. break;
  512. default:
  513. return @"Other state";
  514. break;
  515. }
  516. }
  517. - (NSString *)stringForGatheringState:(RTCIceGatheringState)state
  518. {
  519. switch (state) {
  520. case RTCIceGatheringStateNew:
  521. return @"New";
  522. break;
  523. case RTCIceGatheringStateGathering:
  524. return @"Gathering";
  525. break;
  526. case RTCIceGatheringStateComplete:
  527. return @"Complete";
  528. break;
  529. default:
  530. return @"Other state";
  531. break;
  532. }
  533. }
  534. @end