|
22 | 22 | @import CallKit; |
23 | 23 | @import WebRTC; |
24 | 24 |
|
| 25 | +// Helper to wait for first local video frame before answering |
| 26 | +@interface MLFirstVideoFrameRenderer : NSObject<RTCVideoRenderer> |
| 27 | +@property (nonatomic, copy) void (^onFirstFrame)(void); |
| 28 | +@property (nonatomic, assign) BOOL fired; |
| 29 | +@end |
| 30 | + |
| 31 | +@implementation MLFirstVideoFrameRenderer |
| 32 | +- (void)setSize:(CGSize)size {} |
| 33 | +- (void)renderFrame:(RTCVideoFrame*)frame |
| 34 | +{ if(!self.fired && self.onFirstFrame) { self.fired = YES; self.onFirstFrame(); } } |
| 35 | +@end |
| 36 | + |
25 | 37 | //this is our private interface only shared with MLVoIPProcessor |
26 | 38 | @interface MLCall() <WebRTCClientDelegate> |
27 | 39 | { |
@@ -1676,47 +1688,7 @@ -(void) processIncomingSDP:(NSNotification*) notification |
1676 | 1688 | DDLogDebug(@"Successfully passed SDP to webRTCClient..."); |
1677 | 1689 | //only send a "session-accept" if the remote is the initiator (e.g. this is an incoming call) |
1678 | 1690 | if(self.direction == MLCallDirectionIncoming) |
1679 | | - { |
1680 | | - [self.webRTCClient answerWithCompletion:^(RTCSessionDescription* localSdp) { |
1681 | | - DDLogDebug(@"Sending SDP answer back..."); |
1682 | | - NSArray<MLXMLNode*>* children = [HelperTools sdp2xml:localSdp.sdp withInitiator:NO]; |
1683 | | - //we got a session-initiate jingle iq |
1684 | | - //--> self.encryptionState will NOT be MLCallEncryptionStateClear, if that iq contained an encrypted fingerprint, |
1685 | | - //--> self.encryptionState WILL be MLCallEncryptionStateClear, if it did not contain such an encrypted fingerprint |
1686 | | - //(in this case we just don't try to decrypt anything, the call will simply be unencrypted but continue) |
1687 | | - //we don't need to check self.remoteOmemoDeviceId, because self.encryptionState will only be different to |
1688 | | - //MLCallEncryptionStateClear if the deviceid is not nil |
1689 | | - if(self.encryptionState != MLCallEncryptionStateClear && ![self encryptFingerprintsInChildren:children]) |
1690 | | - { |
1691 | | - DDLogError(@"Could not encrypt local SDP response fingerprint with OMEMO!"); |
1692 | | - XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; |
1693 | | - [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ |
1694 | | - [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], |
1695 | | - [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not encrypt call with OMEMO!"], |
1696 | | - ] andData:nil]]; |
1697 | | - [self.account send:errorIq]; |
1698 | | - |
1699 | | - [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; |
1700 | | - return; |
1701 | | - } |
1702 | | - [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; |
1703 | | - |
1704 | | - XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; |
1705 | | - [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ |
1706 | | - @"action": @"session-accept", |
1707 | | - @"sid": self.jmiid, |
1708 | | - } andChildren:children andData:nil]]; |
1709 | | - [self.account send:sdpIQ]; |
1710 | | - |
1711 | | - @synchronized(self.candidateQueueLock) { |
1712 | | - self.localSDP = sdpIQ; |
1713 | | - |
1714 | | - DDLogDebug(@"Now handling queued incoming candidate iqs: %lu", (unsigned long)self.incomingCandidateQueue.count); |
1715 | | - for(XMPPIQ* candidateIq in self.incomingCandidateQueue) |
1716 | | - [self processRemoteICECandidate:candidateIq]; |
1717 | | - } |
1718 | | - }]; |
1719 | | - } |
| 1691 | + [self ml_startVideoAndAnswerWhenFirstFrame:iqNode]; |
1720 | 1692 | else |
1721 | 1693 | { |
1722 | 1694 | [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; |
@@ -1829,4 +1801,104 @@ -(BOOL) decryptFingerprintsInIqNode:(XMPPIQ*) iqNode |
1829 | 1801 | return retval; |
1830 | 1802 | } |
1831 | 1803 |
|
| 1804 | +- (void) ml_sendImmediateAnswerForIq:(XMPPIQ*) iqNode |
| 1805 | +{ |
| 1806 | + [self.webRTCClient answerWithCompletion:^(RTCSessionDescription* localSdp) { |
| 1807 | + DDLogDebug(@"Sending SDP answer back..."); |
| 1808 | + NSArray<MLXMLNode*>* children = [HelperTools sdp2xml:localSdp.sdp withInitiator:NO]; |
| 1809 | + //we got a session-initiate jingle iq |
| 1810 | + //--> self.encryptionState will NOT be MLCallEncryptionStateClear, if that iq contained an encrypted fingerprint, |
| 1811 | + //--> self.encryptionState WILL be MLCallEncryptionStateClear, if it did not contain such an encrypted fingerprint |
| 1812 | + //(in this case we just don't try to decrypt anything, the call will simply be unencrypted but continue) |
| 1813 | + //we don't need to check self.remoteOmemoDeviceId, because self.encryptionState will only be different to |
| 1814 | + //MLCallEncryptionStateClear if the deviceid is not nil |
| 1815 | + if(self.encryptionState != MLCallEncryptionStateClear && ![self encryptFingerprintsInChildren:children]) |
| 1816 | + { |
| 1817 | + DDLogError(@"Could not encrypt local SDP response fingerprint with OMEMO!"); |
| 1818 | + XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; |
| 1819 | + [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ |
| 1820 | + [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], |
| 1821 | + [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not encrypt call with OMEMO!"], |
| 1822 | + ] andData:nil]]; |
| 1823 | + [self.account send:errorIq]; |
| 1824 | + |
| 1825 | + [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; |
| 1826 | + return; |
| 1827 | + } |
| 1828 | + [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; |
| 1829 | + |
| 1830 | + XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; |
| 1831 | + [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ |
| 1832 | + @"action": @"session-accept", |
| 1833 | + @"sid": self.jmiid, |
| 1834 | + } andChildren:children andData:nil]]; |
| 1835 | + [self.account send:sdpIQ]; |
| 1836 | + |
| 1837 | + @synchronized(self.candidateQueueLock) { |
| 1838 | + self.localSDP = sdpIQ; |
| 1839 | + |
| 1840 | + DDLogDebug(@"Now handling queued incoming candidate iqs: %lu", (unsigned long)self.incomingCandidateQueue.count); |
| 1841 | + for(XMPPIQ* candidateIq in self.incomingCandidateQueue) |
| 1842 | + [self processRemoteICECandidate:candidateIq]; |
| 1843 | + } |
| 1844 | + }]; |
| 1845 | + /* |
| 1846 | + [self.webRTCClient answerWithCompletion:^(RTCSessionDescription* localSdp) { |
| 1847 | + DDLogDebug(@"Sending SDP answer back..."); |
| 1848 | + NSArray<MLXMLNode*>* children = [HelperTools sdp2xml:localSdp.sdp withInitiator:NO]; |
| 1849 | + if(self.encryptionState != MLCallEncryptionStateClear && ![self encryptFingerprintsInChildren:children]) |
| 1850 | + { |
| 1851 | + DDLogError(@"Could not encrypt local SDP response fingerprint with OMEMO!"); |
| 1852 | + XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; |
| 1853 | + [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ |
| 1854 | + [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], |
| 1855 | + [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not encrypt call with OMEMO!"], |
| 1856 | + ] andData:nil]]; |
| 1857 | + [self.account send:errorIq]; |
| 1858 | + [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; |
| 1859 | + return; |
| 1860 | + } |
| 1861 | + [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; |
| 1862 | + XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; |
| 1863 | + [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ |
| 1864 | + @"action": @"session-accept", |
| 1865 | + @"sid": self.jmiid, |
| 1866 | + } andChildren:children andData:nil]]; |
| 1867 | + [self.account send:sdpIQ]; |
| 1868 | + @synchronized(self.candidateQueueLock) { |
| 1869 | + self.localSDP = sdpIQ; |
| 1870 | + DDLogDebug(@"Now handling queued incoming candidate iqs: %lu", (unsigned long)self.incomingCandidateQueue.count); |
| 1871 | + for(XMPPIQ* candidateIq in self.incomingCandidateQueue) |
| 1872 | + [self processRemoteICECandidate:candidateIq]; |
| 1873 | + } |
| 1874 | + }]; |
| 1875 | + */ |
| 1876 | +} |
| 1877 | + |
| 1878 | +- (void) ml_startVideoAndAnswerWhenFirstFrame:(XMPPIQ*) iqNode |
| 1879 | +{ |
| 1880 | + if(self.callType != MLCallTypeVideo) |
| 1881 | + { |
| 1882 | + [self ml_sendImmediateAnswerForIq:iqNode]; |
| 1883 | + return; |
| 1884 | + } |
| 1885 | + MLFirstVideoFrameRenderer* latch = [MLFirstVideoFrameRenderer new]; |
| 1886 | + weakify(self); |
| 1887 | + __block monal_void_block_t cancelTimeout = createTimer(2.5, (^{ |
| 1888 | + strongify(self); |
| 1889 | + if(self == nil || self.isFinished) return; |
| 1890 | + DDLogInfo(@"%@: No local video frame within timeout — answering anyway.", [self short]); |
| 1891 | + cancelTimeout = nil; |
| 1892 | + [self ml_sendImmediateAnswerForIq:iqNode]; |
| 1893 | + })); |
| 1894 | + latch.onFirstFrame = ^{ |
| 1895 | + strongify(self); |
| 1896 | + if(self == nil || self.isFinished) return; |
| 1897 | + if(cancelTimeout) { cancelTimeout(); cancelTimeout = nil; } |
| 1898 | + DDLogInfo(@"%@: First local video frame observed — answering now.", [self short]); |
| 1899 | + [self ml_sendImmediateAnswerForIq:iqNode]; |
| 1900 | + }; |
| 1901 | + [self startCaptureLocalVideoWithRenderer:latch andCameraPosition:AVCaptureDevicePositionFront]; |
| 1902 | +} |
| 1903 | + |
1832 | 1904 | @end |
0 commit comments