diff --git a/Telegram/BUILD b/Telegram/BUILD index 6086da8b83..e519409f89 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -251,9 +251,9 @@ official_bundle_ids = [ "org.telegram.Telegram-iOS", ] -apple_pay_merchants = []#official_apple_pay_merchants if telegram_bundle_id == "ph.telegra.Telegraph" else "" +apple_pay_merchants = official_apple_pay_merchants if telegram_bundle_id == "ph.telegra.Telegraph" else [] -apple_pay_merchants_fragment = "" if apple_pay_merchants == "" else """ +apple_pay_merchants_fragment = "" if apple_pay_merchants == [] else """ com.apple.developer.in-app-payments """ + "\n".join([ @@ -1528,13 +1528,13 @@ ios_application( ":AppStringResources", ], extensions = [ - #":ShareExtension", - #":NotificationContentExtension", - #":NotificationServiceExtension", - #":IntentsExtension", - #":WidgetExtension", + ":ShareExtension", + ":NotificationContentExtension", + ":NotificationServiceExtension", + ":IntentsExtension", + ":WidgetExtension", ], - #watch_application = ":TelegramWatchApp", + watch_application = ":TelegramWatchApp", deps = [ ":Main", ":Lib", diff --git a/build-system/xcode_version b/build-system/xcode_version index b700dc1d47..40e6bd96a6 100644 --- a/build-system/xcode_version +++ b/build-system/xcode_version @@ -1 +1 @@ -12.0.1 +12.1 diff --git a/submodules/TelegramCallsUI/Sources/GroupCallController.swift b/submodules/TelegramCallsUI/Sources/GroupCallController.swift index 59d1d7399d..fdf85d8b22 100644 --- a/submodules/TelegramCallsUI/Sources/GroupCallController.swift +++ b/submodules/TelegramCallsUI/Sources/GroupCallController.swift @@ -21,6 +21,9 @@ public final class GroupCallController: ViewController { private var isMutedDisposable: Disposable? private let audioSessionActive = Promise(false) + private var incomingVideoStreamList: [String] = [] + private var incomingVideoStreamListDisposable: Disposable? + private var memberCount: Int = 0 private let memberCountNode: ImmediateTextNode @@ -28,6 +31,8 @@ public final class GroupCallController: ViewController { private let isMutedNode: ImmediateTextNode private let muteButton: HighlightableButtonNode + private var videoViews: [OngoingCallContextPresentationCallVideoView] = [] + private var validLayout: ContainerViewLayout? init(context: AccountContext) { @@ -77,6 +82,34 @@ public final class GroupCallController: ViewController { } }) + self.incomingVideoStreamListDisposable = (callContext.videoStreamList + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + var addedStreamIds: [String] = [] + for id in value { + if !strongSelf.incomingVideoStreamList.contains(id) { + addedStreamIds.append(id) + } + } + strongSelf.incomingVideoStreamList = value + + for id in addedStreamIds { + callContext.makeIncomingVideoView(id: id, completion: { videoView in + guard let strongSelf = self, let videoView = videoView else { + return + } + strongSelf.videoViews.append(videoView) + videoView.view.backgroundColor = .black + strongSelf.view.addSubview(videoView.view) + if let layout = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, transition: .immediate) + } + }) + } + }) + self.isMutedDisposable = (callContext.isMuted |> deliverOnMainQueue).start(next: { [weak self] value in guard let strongSelf = self else { @@ -94,6 +127,7 @@ public final class GroupCallController: ViewController { deinit { self.callDisposable?.dispose() self.memberCountDisposable?.dispose() + self.incomingVideoStreamListDisposable?.dispose() } @objc private func muteButtonPressed() { @@ -116,6 +150,17 @@ public final class GroupCallController: ViewController { let isMutedFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - isMutedSize.width) / 2.0), y: textFrame.maxY + 12.0), size: isMutedSize) transition.updateFrame(node: self.muteButton, frame: isMutedFrame) self.isMutedNode.frame = CGRect(origin: CGPoint(), size: isMutedFrame.size) + + let videoSize = CGSize(width: 200.0, height: 360.0) + var nextVideoOrigin = CGPoint() + for videoView in self.videoViews { + videoView.view.frame = CGRect(origin: nextVideoOrigin, size: videoSize) + nextVideoOrigin.x += videoSize.width + if nextVideoOrigin.x + videoSize.width > layout.size.width { + nextVideoOrigin.x = 0.0 + nextVideoOrigin.y += videoSize.height + } + } } } diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index ed915a8cc6..9ce189cfcc 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -501,9 +501,15 @@ private extension ConferenceDescription.Content.Channel.PayloadType { [ "type": "transport-cc" ] as [String: Any], - /*[ + [ + "type": "ccm fir" + ] as [String: Any], + [ "type": "nack" - ] as [String: Any]*/ + ] as [String: Any], + [ + "type": "nack pli" + ] as [String: Any], ] as [Any] if let parameters = self.parameters { result["parameters"] = parameters @@ -782,7 +788,7 @@ private extension ConferenceDescription { appendSdp("a=ssrc:\(stream.audioSsrc) label:audio\(stream.audioSsrc)") } - appendSdp("m=video \(stream.isMain ? "1" : "0") RTP/SAVPF 100") + appendSdp("m=video 0 RTP/SAVPF 100") appendSdp("a=mid:video\(stream.videoSsrc)") if stream.isRemoved { appendSdp("a=inactive") @@ -809,6 +815,11 @@ private extension ConferenceDescription { appendSdp("a=rtcp-fb:100 nack") appendSdp("a=rtcp-fb:100 nack pli") + if stream.isMain { + appendSdp("a=sendrecv") + } else { + appendSdp("a=sendonly") + } appendSdp("a=bundle-only") appendSdp("a=ssrc-group:FID \(stream.videoSsrc)") @@ -1026,9 +1037,6 @@ private extension ConferenceDescription { [ "type": "transport-cc" ] as [String: Any], - /*[ - "type": "nack" - ] as [String: Any]*/ ] as [Any] ] ), @@ -1075,9 +1083,15 @@ private extension ConferenceDescription { [ "type": "transport-cc" ] as [String: Any], + [ + "type": "ccm fir" + ] as [String: Any], [ "type": "nack" - ] as [String: Any] + ] as [String: Any], + [ + "type": "nack pli" + ] as [String: Any], ] as [Any] ] ) @@ -1263,6 +1277,7 @@ public final class GroupCallContext { private var localTransport: ConferenceDescription.Transport? let memberCount = ValuePromise(0, ignoreRepeated: true) + let videoStreamList = ValuePromise<[String]>([], ignoreRepeated: true) private var isMutedValue: Bool = false let isMuted = ValuePromise(false, ignoreRepeated: true) @@ -1271,14 +1286,20 @@ public final class GroupCallContext { self.queue = queue self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max)) - self.colibriHost = "192.168.8.118" + self.colibriHost = "192.168.93.24" var relaySdpAnswerImpl: ((String) -> Void)? + let videoStreamList = self.videoStreamList + self.context = GroupCallThreadLocalContext(queue: ContextQueueImpl(queue: queue), relaySdpAnswer: { sdpAnswer in queue.async { relaySdpAnswerImpl?(sdpAnswer) } + }, incomingVideoStreamListUpdated: { streamList in + queue.async { + videoStreamList.set(streamList) + } }, videoCapturer: video?.impl) relaySdpAnswerImpl = { [weak self] sdpAnswer in @@ -1595,6 +1616,45 @@ public final class GroupCallContext { self.isMuted.set(self.isMutedValue) self.context.setIsMuted(self.isMutedValue) } + + func makeIncomingVideoView(id: String, completion: @escaping (OngoingCallContextPresentationCallVideoView?) -> Void) { + self.context.makeIncomingVideoView(withStreamId: id, completion: { view in + if let view = view { + completion(OngoingCallContextPresentationCallVideoView( + view: view, + setOnFirstFrameReceived: { [weak view] f in + view?.setOnFirstFrameReceived(f) + }, + getOrientation: { [weak view] in + if let view = view { + return OngoingCallVideoOrientation(view.orientation) + } else { + return .rotation0 + } + }, + getAspect: { [weak view] in + if let view = view { + return view.aspect + } else { + return 0.0 + } + }, + setOnOrientationUpdated: { [weak view] f in + view?.setOnOrientationUpdated { value, aspect in + f?(OngoingCallVideoOrientation(value), aspect) + } + }, + setOnIsMirroredUpdated: { [weak view] f in + view?.setOnIsMirroredUpdated { value in + f?(value) + } + } + )) + } else { + completion(nil) + } + }) + } } private let queue = Queue() @@ -1619,6 +1679,18 @@ public final class GroupCallContext { } } + public var videoStreamList: Signal<[String], NoError> { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.videoStreamList.get().start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + public var isMuted: Signal { return Signal { subscriber in let disposable = MetaDisposable() @@ -1636,5 +1708,11 @@ public final class GroupCallContext { impl.toggleIsMuted() } } + + public func makeIncomingVideoView(id: String, completion: @escaping (OngoingCallContextPresentationCallVideoView?) -> Void) { + self.impl.with { impl in + impl.makeIncomingVideoView(id: id, completion: completion) + } + } } diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index 564317977c..606beeb2d9 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -472,7 +472,7 @@ public enum OngoingCallVideoOrientation { case rotation270 } -private extension OngoingCallVideoOrientation { +extension OngoingCallVideoOrientation { init(_ orientation: OngoingCallVideoOrientationWebrtc) { switch orientation { case .orientation0: diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 5462ff7bc2..dd5ea3b001 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -154,11 +154,12 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { @interface GroupCallThreadLocalContext : NSObject -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer incomingVideoStreamListUpdated:(void (^ _Nonnull)(NSArray * _Nonnull))incomingVideoStreamListUpdated videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; - (void)emitOffer; - (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial; - (void)setIsMuted:(bool)isMuted; +- (void)makeIncomingVideoViewWithStreamId:(NSString * _Nonnull)streamId completion:(void (^_Nonnull)(UIView * _Nullable))completion; @end diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index f8c96b3322..55a55c498f 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -806,7 +806,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; @implementation GroupCallThreadLocalContext -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer { +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer incomingVideoStreamListUpdated:(void (^ _Nonnull)(NSArray * _Nonnull))incomingVideoStreamListUpdated videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer { self = [super init]; if (self != nil) { _queue = queue; @@ -825,6 +825,19 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; relaySdpAnswer(string); }]; }, + .incomingVideoStreamListUpdated = [weakSelf, queue, incomingVideoStreamListUpdated](std::vector const &incomingVideoStreamList) { + NSMutableArray *mappedList = [[NSMutableArray alloc] init]; + for (auto &it : incomingVideoStreamList) { + [mappedList addObject:[NSString stringWithUTF8String:it.c_str()]]; + } + [queue dispatch:^{ + __strong GroupCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + incomingVideoStreamListUpdated(mappedList); + }]; + }, .videoCapture = [_videoCapturer getInterface] })); } @@ -849,5 +862,43 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; } } +- (void)makeIncomingVideoViewWithStreamId:(NSString * _Nonnull)streamId completion:(void (^_Nonnull)(UIView * _Nullable))completion { + if (_instance) { + __weak GroupCallThreadLocalContext *weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([VideoMetalView isSupported]) { + VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero]; +#if TARGET_OS_IPHONE + remoteRenderer.videoContentMode = UIViewContentModeScaleToFill; +#else + remoteRenderer.videoContentMode = UIViewContentModeScaleAspect; +#endif + + std::shared_ptr> sink = [remoteRenderer getSink]; + __strong GroupCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + //[remoteRenderer setOrientation:strongSelf->_remoteVideoOrientation]; + //strongSelf->_currentRemoteVideoRenderer = remoteRenderer; + strongSelf->_instance->setIncomingVideoOutput([streamId UTF8String], sink); + } + + completion(remoteRenderer); + } else { + GLVideoView *remoteRenderer = [[GLVideoView alloc] initWithFrame:CGRectZero]; + + std::shared_ptr> sink = [remoteRenderer getSink]; + __strong GroupCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + //[remoteRenderer setOrientation:strongSelf->_remoteVideoOrientation]; + //strongSelf->_currentRemoteVideoRenderer = remoteRenderer; + strongSelf->_instance->setIncomingVideoOutput([streamId UTF8String], sink); + } + + completion(remoteRenderer); + } + }); + } +} + @end