diff --git a/.bazelrc b/.bazelrc index 1ce9174344..5b12464e70 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,32 +1,34 @@ build --action_env=ZERO_AR_DATE=1 -build --apple_platform_type=ios build --enable_platform_specific_config -build --apple_crosstool_top=@local_config_apple_cc//:toolchain -build --crosstool_top=@local_config_apple_cc//:toolchain -build --host_crosstool_top=@local_config_apple_cc//:toolchain -build --per_file_copt=".*\.m$","@-fno-objc-msgsend-selector-stubs" -build --per_file_copt=".*\.mm$","@-fno-objc-msgsend-selector-stubs" - -build --features=debug_prefix_map_pwd_is_dot -build --features=swift.cacheable_swiftmodules -build --features=swift.debug_prefix_map -build --features=swift.enable_vfsoverlays +# macOS-specific settings (auto-applied on macOS via --enable_platform_specific_config) +build:macos --apple_platform_type=ios +build:macos --apple_crosstool_top=@local_config_apple_cc//:toolchain +build:macos --crosstool_top=@local_config_apple_cc//:toolchain +build:macos --host_crosstool_top=@local_config_apple_cc//:toolchain +build:macos --per_file_copt=".*\.m$","@-fno-objc-msgsend-selector-stubs" +build:macos --per_file_copt=".*\.mm$","@-fno-objc-msgsend-selector-stubs" +build:macos --features=debug_prefix_map_pwd_is_dot +build:macos --features=swift.cacheable_swiftmodules +build:macos --features=swift.debug_prefix_map +build:macos --features=swift.enable_vfsoverlays build:dbg --features=swift.emit_swiftsourceinfo +# Linux-specific settings (auto-applied on Linux via --enable_platform_specific_config) +build:linux --action_env=CC +build:linux --action_env=CXX + build --strategy=Genrule=standalone build --spawn_strategy=standalone -build --strategy=SwiftCompile=worker +build:macos --strategy=SwiftCompile=worker -#common --registry=https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/ - -# SourceKit BSP: Swift indexing features -common --features=swift.index_while_building -common --features=swift.use_global_index_store -common --features=swift.use_global_module_cache -common --features=oso_prefix_is_pwd +# SourceKit BSP: Swift indexing features (macOS only) +common:macos --features=swift.index_while_building +common:macos --features=swift.use_global_index_store +common:macos --features=swift.use_global_module_cache +common:macos --features=oso_prefix_is_pwd # SourceKit BSP: Index build config (used for background indexing) common:index_build --experimental_convenience_symlinks=ignore diff --git a/MODULE.bazel b/MODULE.bazel index 99b68b09ef..06eeac82fb 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,6 +3,25 @@ http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_ bazel_dep(name = "bazel_features", version = "1.33.0") bazel_dep(name = "bazel_skylib", version = "1.8.1") bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_go", version = "0.60.0", repo_name = "io_bazel_rules_go") + +go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk") +go_sdk.download(version = "1.24.2") + +bazel_dep(name = "gazelle", version = "0.43.0", repo_name = "bazel_gazelle") + +go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps") +go_deps.from_file(go_mod = "//submodules/TgVoipWebrtc/tgcalls/tools/go_sfu:go.mod") +use_repo( + go_deps, + "com_github_pion_datachannel", + "com_github_pion_dtls_v3", + "com_github_pion_ice_v4", + "com_github_pion_logging", + "com_github_pion_rtcp", + "com_github_pion_sctp", + "com_github_pion_srtp_v3", +) bazel_dep(name = "rules_xcodeproj") local_path_override( @@ -30,26 +49,26 @@ local_path_override( http_file( name = "cmake_tar_gz", - urls = ["https://github.com/Kitware/CMake/releases/download/v4.1.2/cmake-4.1.2-macos-universal.tar.gz"], sha256 = "3be85f5b999e327b1ac7d804cbc9acd767059e9f603c42ec2765f6ab68fbd367", + urls = ["https://github.com/Kitware/CMake/releases/download/v4.1.2/cmake-4.1.2-macos-universal.tar.gz"], ) http_file( name = "meson_tar_gz", - urls = ["https://github.com/mesonbuild/meson/releases/download/1.6.0/meson-1.6.0.tar.gz"], sha256 = "999b65f21c03541cf11365489c1fad22e2418bb0c3d50ca61139f2eec09d5496", + urls = ["https://github.com/mesonbuild/meson/releases/download/1.6.0/meson-1.6.0.tar.gz"], ) http_file( name = "ninja-mac_zip", - urls = ["https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip"], sha256 = "89a287444b5b3e98f88a945afa50ce937b8ffd1dcc59c555ad9b1baf855298c9", + urls = ["https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip"], ) http_file( name = "flatbuffers_zip", - urls = ["https://github.com/google/flatbuffers/archive/refs/tags/v24.12.23.zip"], sha256 = "c5cd6a605ff20350c7faa19d8eeb599df6117ea4aabd16ac58a7eb5ba82df4e7", + urls = ["https://github.com/google/flatbuffers/archive/refs/tags/v24.12.23.zip"], ) provisioning_profile_repository = use_extension("@build_bazel_rules_apple//apple:apple.bzl", "provisioning_profile_repository_extension") diff --git a/build-system/BUILD b/build-system/BUILD index 5fb4a881f4..43d4c19eab 100644 --- a/build-system/BUILD +++ b/build-system/BUILD @@ -4,6 +4,22 @@ config_setting( values = {"cpu": "ios_sim_arm64"}, ) +config_setting( + name = "linux_arm64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +) + +config_setting( + name = "linux_x86_64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +) + exports_files([ "GenerateStrings/GenerateStrings.py", ]) diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index 85a1aaf83b..bc84de9157 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -189,7 +189,10 @@ public final class BrowserBookmarksScreen: ViewController { }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) let tagMask: MessageTags = .webPage diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift index 355be9c09f..a1a87ca53f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift @@ -31,6 +31,7 @@ func pollCloudMediaToInputMedia(_ media: Media) -> Api.InputMedia? { public enum RequestMessageSelectPollOptionError { case generic + case restrictedToSubscribers } func _internal_requestMessageSelectPollOption(account: Account, messageId: MessageId, opaqueIdentifiers: [Data]) -> Signal { @@ -40,7 +41,10 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa |> mapToSignal { peer in if let inputPeer = apiInputPeer(peer) { return account.network.request(Api.functions.messages.sendVote(peer: inputPeer, msgId: messageId.id, options: opaqueIdentifiers.map { Buffer(data: $0) })) - |> mapError { _ -> RequestMessageSelectPollOptionError in + |> mapError { error -> RequestMessageSelectPollOptionError in + if error.errorDescription == "POLL_MEMBER_RESTRICTED" { + return .restrictedToSubscribers + } return .generic } |> mapToSignal { result -> Signal in @@ -266,7 +270,7 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager pollMediaFlags |= 1 << 1 } - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil, countriesIso2: poll.countries, hash: 0)), correctAnswers: correctAnswersIndices, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil)) + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: poll.deadlineDate, countriesIso2: poll.countries, hash: 0)), correctAnswers: correctAnswersIndices, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index e4a227b0e4..1be7762dff 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -2918,49 +2918,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { if case .public = poll.publicity { item.controllerInteraction.openMessagePollResults(item.message.id, option.opaqueIdentifier) } else if isRestricted { - let text: String - - let peerName = item.message.peers[item.message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" - if !poll.countries.isEmpty { - let locale = localeWithStrings(item.presentationData.strings) - let countryNames = poll.countries.map { id in - if id == "FT" { - return "Fragment" - } else if let countryName = locale.localizedString(forRegionCode: id) { - return countryName - } else { - return id - } - } - var countries: String = "" - if countryNames.count == 1, let country = countryNames.first { - countries = "**\(country)**" - } else { - for i in 0 ..< countryNames.count { - countries.append("**\(countryNames[i])**") - if i == countryNames.count - 2 { - countries.append(item.presentationData.strings.Chat_Poll_Restriction_Country_CountriesLastDelimiter) - } else if i < countryNames.count - 2 { - countries.append(item.presentationData.strings.Chat_Poll_Restriction_Country_CountriesDelimiter) - } - } - } - if poll.restrictToSubscribers { - text = item.presentationData.strings.Chat_Poll_Restriction_SubscribersCountry(peerName, countries).string - } else { - text = item.presentationData.strings.Chat_Poll_Restriction_Country(countries).string - } - } else { - text = item.presentationData.strings.Chat_Poll_Restriction_Subscribers(peerName).string - } - let controller = UndoOverlayController( - presentationData: item.context.sharedContext.currentPresentationData.with { $0 }, - content: .banned(text: text), - elevatedLayout: true, - position: .bottom, - action: { _ in return true } - ) - item.controllerInteraction.presentController(controller, nil) + item.controllerInteraction.displayPollRestrictedToast(item.message.id) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 90557d5de4..e1408ee47d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -666,7 +666,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode)) self.controllerInteraction = controllerInteraction diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index ad1a8f0e1e..f5069b7768 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -513,7 +513,10 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode)) let associatedData = ChatMessageItemAssociatedData( diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 8f69b09ca1..168605725f 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -303,6 +303,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openStarsPurchase: (Int64?) -> Void public let openRankInfo: (EnginePeer, ChatRankInfoScreenRole, String) -> Void public let openSetPeerAvatar: () -> Void + public let displayPollRestrictedToast: (EngineMessage.Id) -> Void public var canPlayMedia: Bool = false public var hiddenMedia: [MessageId: [Media]] = [:] @@ -480,6 +481,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openStarsPurchase: @escaping (Int64?) -> Void, openRankInfo: @escaping (EnginePeer, ChatRankInfoScreenRole, String) -> Void, openSetPeerAvatar: @escaping () -> Void, + displayPollRestrictedToast: @escaping (EngineMessage.Id) -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings, @@ -610,6 +612,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.openStarsPurchase = openStarsPurchase self.openRankInfo = openRankInfo self.openSetPeerAvatar = openSetPeerAvatar + self.displayPollRestrictedToast = displayPollRestrictedToast self.automaticMediaDownloadSettings = automaticMediaDownloadSettings diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index a0b4bb2858..6c61401182 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -1284,7 +1284,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift index 754f6996c2..8e37932d93 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift @@ -8,6 +8,7 @@ import Display import UndoUI import AccountContext import ChatControllerInteraction +import TelegramStringFormatting extension ChatControllerImpl { func displaySendReactionRestrictedToast() { @@ -20,6 +21,71 @@ extension ChatControllerImpl { action: { _ in return true } ), nil) } + + func displayPollRestrictedToast(messageId: EngineMessage.Id) { + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Messages.Message(id: messageId), + TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] message, peer in + guard let self, let message, let peer else { + return + } + let peerName = peer.compactDisplayTitle + guard let poll = message.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll else { + return + } + + var accountCountry = "" + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let country = data["phone_country_iso2"] as? String { + accountCountry = (country) + } + var text = "" + if !poll.countries.isEmpty && !poll.countries.contains(accountCountry) { + let locale = localeWithStrings(self.presentationData.strings) + let countryNames = poll.countries.map { id in + if id == "FT" { + return "Fragment" + } else if let countryName = locale.localizedString(forRegionCode: id) { + return countryName + } else { + return id + } + } + var countries: String = "" + if countryNames.count == 1, let country = countryNames.first { + countries = "**\(country)**" + } else { + for i in 0 ..< countryNames.count { + countries.append("**\(countryNames[i])**") + if i == countryNames.count - 2 { + countries.append(self.presentationData.strings.Chat_Poll_Restriction_Country_CountriesLastDelimiter) + } else if i < countryNames.count - 2 { + countries.append(self.presentationData.strings.Chat_Poll_Restriction_Country_CountriesDelimiter) + } + } + } + if poll.restrictToSubscribers { + text = self.presentationData.strings.Chat_Poll_Restriction_SubscribersCountry(peerName, countries).string + } else { + text = self.presentationData.strings.Chat_Poll_Restriction_Country(countries).string + } + } else { + if case let .channel(channel) = peer, case .member = channel.participationStatus { + text = self.presentationData.strings.Chat_Poll_Restriction_Subscribers_TimeLimit + } else { + text = self.presentationData.strings.Chat_Poll_Restriction_Subscribers(peerName).string + } + } + let controller = UndoOverlayController( + presentationData: self.presentationData, + content: .banned(text: text), + position: .bottom, + action: { _ in return true } + ) + self.controllerInteraction?.presentControllerInCurrent(controller, nil) + }) + } func displayPostedScheduledMessagesToast(ids: [EngineMessage.Id]) { let timestamp = CFAbsoluteTimeGetCurrent() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 6bf848312a..68457c5b26 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3896,20 +3896,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ) strongSelf.present(controller, in: .current) } - }, error: { _ in + }, error: { error in guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else { return } if controllerInteraction.pollActionState.pollMessageIdsInProgress.removeValue(forKey: id) != nil { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) } + + switch error { + case .restrictedToSubscribers: + strongSelf.displayPollRestrictedToast(messageId: id) + default: + break + } }, completed: { guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else { return } if controllerInteraction.pollActionState.pollMessageIdsInProgress.removeValue(forKey: id) != nil { Queue.mainQueue().after(1.0, { - strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) }) } @@ -5664,6 +5670,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.interfaceInteraction?.openSetPeerAvatar() + }, displayPollRestrictedToast: { [weak self] messageId in + guard let self else { + return + } + self.displayPollRestrictedToast(messageId: messageId) }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode)) controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 7fecf934f3..9fa74bbce6 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -258,7 +258,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 8fabe4977e..223c0af0a8 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2581,7 +2581,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { openStarsPurchase: { _ in }, openRankInfo: { _, _, _ in - }, openSetPeerAvatar: { + }, + openSetPeerAvatar: { + }, + displayPollRestrictedToast: { _ in }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), diff --git a/submodules/TgVoipWebrtc/BUILD b/submodules/TgVoipWebrtc/BUILD index 14cc452523..c5e0acf5d0 100644 --- a/submodules/TgVoipWebrtc/BUILD +++ b/submodules/TgVoipWebrtc/BUILD @@ -7,6 +7,11 @@ config_setting( }, ) +config_setting( + name = "is_macos", + constraint_values = ["@platforms//os:macos"], +) + optimization_flags = select({ ":debug_build": [ ], @@ -107,6 +112,7 @@ sources = glob([ "tgcalls/tgcalls/group/AudioStreamingPartPersistentDecoder.cpp", "tgcalls/tgcalls/group/AVIOContextImpl.cpp", "tgcalls/tgcalls/group/GroupInstanceCustomImpl.cpp", + "tgcalls/tgcalls/group/GroupInstanceReferenceImpl.cpp", "tgcalls/tgcalls/group/GroupJoinPayloadInternal.cpp", "tgcalls/tgcalls/group/GroupNetworkManager.cpp", "tgcalls/tgcalls/group/StreamingMediaContext.cpp", @@ -116,6 +122,7 @@ sources = glob([ "tgcalls/tgcalls/v2/DirectNetworkingImpl.cpp", "tgcalls/tgcalls/v2/ExternalSignalingConnection.cpp", "tgcalls/tgcalls/v2/InstanceV2Impl.cpp", + "tgcalls/tgcalls/v2/InstanceV2CompatImpl.cpp", "tgcalls/tgcalls/v2/InstanceV2ReferenceImpl.cpp", "tgcalls/tgcalls/v2/NativeNetworkingImpl.cpp", "tgcalls/tgcalls/v2/RawTcpSocket.cpp", @@ -125,6 +132,7 @@ sources = glob([ "tgcalls/tgcalls/v2/SignalingConnection.cpp", "tgcalls/tgcalls/v2/SignalingEncryption.cpp", "tgcalls/tgcalls/v2/SignalingSctpConnection.cpp", + "tgcalls/tgcalls/v2/CustomDcSctpSocket.cpp", ] objc_library( @@ -189,3 +197,105 @@ objc_library( "//visibility:public", ], ) + +# Pure C++ core library for non-iOS targets (e.g. CLI tools). +# Uses FakeInterface instead of darwin platform files. +cc_library( + name = "tgcalls_core", + srcs = glob([ + "tgcalls/tgcalls/**/*.h", + "tgcalls/tgcalls/**/*.hpp", + "tgcalls/tgcalls/platform/fake/**/*.cpp", + "tgcalls/tgcalls/third-party/**/*.cpp", + "tgcalls/tgcalls/utils/**/*.cpp", + ], allow_empty=True, exclude = [ + "tgcalls/tgcalls/legacy/**", + "tgcalls/tgcalls/platform/tdesktop/**", + "tgcalls/tgcalls/platform/android/**", + "tgcalls/tgcalls/platform/windows/**", + "tgcalls/tgcalls/platform/uwp/**", + "tgcalls/tgcalls/platform/darwin/**", + "tgcalls/tgcalls/desktop_capturer/**", + ]) + [ + "tgcalls/tgcalls/Manager.cpp", + "tgcalls/tgcalls/MediaManager.cpp", + "tgcalls/tgcalls/AudioDeviceHelper.cpp", + "tgcalls/tgcalls/ChannelManager.cpp", + "tgcalls/tgcalls/CodecSelectHelper.cpp", + "tgcalls/tgcalls/CryptoHelper.cpp", + "tgcalls/tgcalls/EncryptedConnection.cpp", + "tgcalls/tgcalls/FakeAudioDeviceModule.cpp", + "tgcalls/tgcalls/FakeVideoTrackSource.cpp", + "tgcalls/tgcalls/FieldTrialsConfig.cpp", + "tgcalls/tgcalls/Instance.cpp", + "tgcalls/tgcalls/InstanceImpl.cpp", + "tgcalls/tgcalls/LogSinkImpl.cpp", + "tgcalls/tgcalls/Message.cpp", + "tgcalls/tgcalls/NetworkManager.cpp", + "tgcalls/tgcalls/SctpDataChannelProviderInterfaceImpl.cpp", + "tgcalls/tgcalls/StaticThreads.cpp", + "tgcalls/tgcalls/ThreadLocalObject.cpp", + "tgcalls/tgcalls/TurnCustomizerImpl.cpp", + "tgcalls/tgcalls/VideoCaptureInterface.cpp", + "tgcalls/tgcalls/VideoCaptureInterfaceImpl.cpp", + + "tgcalls/tgcalls/group/AudioStreamingPart.cpp", + "tgcalls/tgcalls/group/AudioStreamingPartInternal.cpp", + "tgcalls/tgcalls/group/AudioStreamingPartPersistentDecoder.cpp", + "tgcalls/tgcalls/group/AVIOContextImpl.cpp", + "tgcalls/tgcalls/group/GroupInstanceCustomImpl.cpp", + "tgcalls/tgcalls/group/GroupInstanceReferenceImpl.cpp", + "tgcalls/tgcalls/group/GroupJoinPayloadInternal.cpp", + "tgcalls/tgcalls/group/GroupNetworkManager.cpp", + "tgcalls/tgcalls/group/StreamingMediaContext.cpp", + "tgcalls/tgcalls/group/VideoStreamingPart.cpp", + + "tgcalls/tgcalls/v2/ContentNegotiation.cpp", + "tgcalls/tgcalls/v2/DirectNetworkingImpl.cpp", + "tgcalls/tgcalls/v2/ExternalSignalingConnection.cpp", + "tgcalls/tgcalls/v2/InstanceV2Impl.cpp", + "tgcalls/tgcalls/v2/InstanceV2CompatImpl.cpp", + "tgcalls/tgcalls/v2/InstanceV2ReferenceImpl.cpp", + "tgcalls/tgcalls/v2/NativeNetworkingImpl.cpp", + "tgcalls/tgcalls/v2/RawTcpSocket.cpp", + "tgcalls/tgcalls/v2/ReflectorPort.cpp", + "tgcalls/tgcalls/v2/ReflectorRelayPortFactory.cpp", + "tgcalls/tgcalls/v2/Signaling.cpp", + "tgcalls/tgcalls/v2/SignalingConnection.cpp", + "tgcalls/tgcalls/v2/SignalingEncryption.cpp", + "tgcalls/tgcalls/v2/SignalingSctpConnection.cpp", + "tgcalls/tgcalls/v2/SignalingTranslator.cpp", + "tgcalls/tgcalls/v2/CustomDcSctpSocket.cpp", + ], + copts = [ + "-I{}/tgcalls/tgcalls".format(package_name()), + "-Ithird-party/webrtc/webrtc", + "-Ithird-party/webrtc/dependencies", + "-Ithird-party/webrtc/absl", + "-Ithird-party/libyuv", + "-DRTC_ENABLE_VP9", + "-DTGVOIP_NAMESPACE=tgvoip_webrtc", + "-std=c++17", + "-w", + ] + select({ + "@platforms//os:linux": ["-DWEBRTC_LINUX", "-DWEBRTC_POSIX"], + "//conditions:default": ["-DWEBRTC_MAC", "-DWEBRTC_POSIX"], + }) + optimization_flags, + deps = [ + "//third-party/webrtc", + "//third-party/boringssl:crypto", + "//third-party/boringssl:ssl", + "//third-party/ogg:ogg", + "//third-party/opusfile:opusfile", + "//submodules/ffmpeg:ffmpeg", + "//third-party/rnnoise:rnnoise", + "//third-party/libyuv", + ] + select({ + ":is_macos": ["//third-party/webrtc:webrtc_platform_helpers"], + "//conditions:default": [], + }), + linkopts = ["-lz"], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TgVoipWebrtc/CLAUDE.md b/submodules/TgVoipWebrtc/CLAUDE.md new file mode 100644 index 0000000000..7766eb1d60 --- /dev/null +++ b/submodules/TgVoipWebrtc/CLAUDE.md @@ -0,0 +1,292 @@ +# tgcalls Library + +The tgcalls VoIP library source. See the root `CLAUDE.md` for build instructions and the project overview. + +## macOS Build Support + +This repo has been patched to support native macOS arm64 builds (`darwin_arm64` CPU) in addition to the original iOS targets. Changes made: +- `third-party/webrtc/BUILD` — added `@platforms//os:linux` to `arch_specific_cflags` select (fixes macOS getting Linux flags via `//conditions:default`); moved `cocoa_threading.mm` from `cc_library` to `webrtc_platform_helpers` `objc_library` (Bazel 8 rejects `.mm` in `cc_library`); replaced UIKit with AppKit for macOS +- `third-party/openh264/BUILD` — added `//conditions:default` to `select()` statements +- `third-party/webrtc/absl/absl/base/attributes.h` — disabled `ABSL_ATTRIBUTE_LIFETIME_BOUND` (newer Xcode clang rejects it on void-returning functions) +- 8 third-party BUILD files + 8 build shell scripts — added `darwin_arm64 -> macos_arm64` architecture support (opus, libvpx, ffmpeg, dav1d, mozjpeg, webp, libjxl, td) + +## Linux Build Support + +The repo supports native Linux arm64 and x86_64 builds. Key changes from the iOS/macOS-only baseline: +- `.bazelrc` — Apple toolchain settings under `build:macos`, Linux uses default CC toolchain via `build:linux` (auto-selected by `--enable_platform_specific_config`) +- `build-system/BUILD` — `linux_arm64` and `linux_x86_64` config_settings +- `objc_library` → `cc_library` conversions for pure C/C++ targets (ogg, opusfile, rnnoise, opus, libvpx, dav1d, ffmpeg wrappers, WebRTC main target) +- WebRTC BUILD — platform flags via `select()` (`-DWEBRTC_LINUX` vs `-DWEBRTC_MAC`), stdlib task queue instead of GCD on Linux, macOS-only sources excluded +- Third-party genrule build scripts — Linux architecture cases added (libvpx, dav1d, ffmpeg), system cmake/meson/ninja used instead of downloaded macOS binaries +- BoringSSL — `_Generic` C11 guarded for C++ mode (GCC compatibility) +- tgcalls headers — `#include ` added for GCC 15 strictness + +## SCTP Signaling + +### Writable Gate (role-based handshake ordering) + +tgcalls uses a custom SCTP association (via dc-sctp) over the signaling channel for reliable message delivery. `SignalingSctpConnection` wraps `DcSctpTransport` with a `SignalingPacketTransport` shim. + +The SCTP handshake is ordered using DcSctpTransport's writable gate (`MaybeConnectSocket()`), mirroring how WebRTC PeerConnection uses DTLS writable state to control SCTP connection timing: + +- **Caller** (`isOutgoing=true`): `SignalingPacketTransport` starts writable → `Connect()` fires immediately → sends INIT +- **Callee** (`isOutgoing=false`): starts not-writable → `Connect()` deferred → on first `receiveExternal()`, `setWritable(true)` fires `SignalWritableState` → `MaybeConnectSocket()` → `Connect()` + +The callee's `Connect()` and processing of the caller's INIT happen synchronously within the same `BlockingCall` on the network thread (RFC 4960 §5.2.1 simultaneous-open). + +Key files: +- `SignalingSctpConnection.cpp` — `SignalingPacketTransport` writable state, `setWritable()`, constructor takes `isInitiator` +- `InstanceV2Impl.cpp` / `InstanceV2ReferenceImpl.cpp` — pass `_encryptionKey.isOutgoing` as `isInitiator` +- `third-party/webrtc/webrtc/media/sctp/dcsctp_transport.cc:662-667` — `MaybeConnectSocket()` gate (unmodified) + +### Timer Tuning (CustomDcSctpSocket) + +WebRTC's stock `DcSctpSocket` has a bug: `max_timer_backoff_duration` is wired to the T3-rtx (data retransmission) timer but **not** to the t1_init and t1_cookie (handshake) timers. The handshake timers use unlimited exponential backoff (1000, 2000, 4000, 8000ms...), causing the SCTP handshake to stall for 20+ seconds under packet loss with simultaneous-open (both sides call `Connect()`). + +Fix: `CustomDcSctpSocket` (in `tgcalls/v2/`) is a copy of `DcSctpSocket` with the 6-line fix that passes `max_timer_backoff_duration` to the t1_init and t1_cookie timer constructors. A `CustomDcSctpSocketFactory` in `SignalingSctpConnection.cpp` creates it instead of the stock socket, with configurable timer overrides. WebRTC source is **untouched**. + +Default signaling SCTP timer values (set in `SignalingSctpConnection::Options`): + +| Setting | WebRTC Default | Signaling Override | +|---|---|---| +| `t1_init_timeout` | 1000ms | 400ms | +| `t1_cookie_timeout` | 1000ms | 400ms | +| `max_timer_backoff_duration` | 3000ms | 750ms | +| `max_init_retransmits` | 8 | unlimited (from `DcSctpTransport::Start`) | + +Retry pattern: 400ms, 750ms, 750ms, 750ms... (~18 attempts in 15s). At 30% loss, 100% success rate over 5000 runs. + +These values are configurable via JSON custom parameters (passed to `InstanceV2Impl` via `config.customParameters`): +- `network_sctp_t1_init_ms` — T1-init timeout (0 = use default 400ms) +- `network_sctp_t1_cookie_ms` — T1-cookie timeout (0 = use default 400ms) +- `network_sctp_max_backoff_ms` — max timer backoff cap (0 = use default 750ms) + +Key files: +- `tgcalls/v2/CustomDcSctpSocket.h/.cpp` — patched `DcSctpSocket` copy +- `tgcalls/v2/SignalingSctpConnection.cpp` — `CustomDcSctpSocketFactory`, timer option plumbing +- `tgcalls/v2/InstanceV2Impl.cpp` — reads JSON params, passes `Options` to `SignalingSctpConnection` + +## InstanceV2CompatImpl (version 14.0.0) + +A cross-version interop implementation that uses WebRTC PeerConnection internally (like InstanceV2ReferenceImpl) but speaks V2Impl's signaling protocol (`InitialSetupMessage`, `NegotiateChannelsMessage`, `CandidatesMessage`). This enables bidirectional calls between PeerConnection-based clients and V2Impl-based clients (versions 7.0.0–13.0.0). + +### Architecture + +``` +PeerConnection <-> SignalingTranslator <-> EncryptedConnection <-> SignalingSctpConnection +``` + +- **SignalingTranslator** (`tgcalls/v2/SignalingTranslator.h/.cpp`): Converts between `cricket::SessionDescription` (PeerConnection's internal format) and V2Impl signaling messages. Uses `JsepSessionDescription` programmatic API — no SDP string round-trips. +- **Outbound**: PeerConnection generates offer/answer → SignalingTranslator extracts `InitialSetupMessage` (transport params) + `NegotiateChannelsMessage` (media contents) +- **Inbound**: Buffers both messages until complete → builds `cricket::SessionDescription` → wraps in `JsepSessionDescription` → `SetRemoteDescription` + +### Key Design Decisions + +- **No data channel with V2Impl peers**: WebRTC data channel requires PeerConnection on both sides. V2Impl uses NativeNetworkingImpl (no PeerConnection). When paired with V2Impl, the data channel m-line is padded as `rejected` in the remote answer so PeerConnection accepts it. For CompatImpl↔CompatImpl calls, the data channel works normally. +- **Caller-only renegotiation**: Only the outgoing side triggers offers from `onRenegotiationNeeded` to prevent unsolicited offer storms. +- **MediaState via signaling**: `MediaStateMessage` sent over the SCTP signaling channel (not data channel), ensuring it works with both V2Impl and CompatImpl peers. +- **Sequential content IDs**: Uses "0", "1", ... as m-line mids, matching PeerConnection's default scheme. +- **Shared conversion functions**: `convertContentInfoToSignalingContent()` and `convertSignalingContentToContentInfo()` extracted to `Signaling.h/.cpp` for use by both `ContentNegotiationContext` (V2Impl) and `SignalingTranslator` (CompatImpl). + +### Cross-Version Testing + +```bash +# CompatImpl caller → V2Impl callee +./bazel-bin/tools/tgcalls_cli/tgcalls_cli --mode p2p --version 14.0.0 --version2 13.0.0 --duration 10 --quiet + +# V2Impl caller → CompatImpl callee +./bazel-bin/tools/tgcalls_cli/tgcalls_cli --mode p2p --version 13.0.0 --version2 14.0.0 --duration 10 --quiet + +# With lossy signaling +./bazel-bin/tools/tgcalls_cli/tgcalls_cli --mode p2p --version 14.0.0 --version2 13.0.0 --duration 15 --drop-rate 0.3 --delay 50-200 --quiet +``` + +100% success rate at 30% loss in both directions (tested with 50 sequential + 20 parallel runs each direction). + +Key files: +- `tgcalls/v2/InstanceV2CompatImpl.h/.cpp` — main implementation +- `tgcalls/v2/SignalingTranslator.h/.cpp` — cricket↔signaling conversion +- `tgcalls/v2/Signaling.h/.cpp` — shared conversion functions (`convertContentInfoToSignalingContent`, `convertSignalingContentToContentInfo`) + +## GroupInstanceCustomImpl (Group Calls) + +The group call implementation in `tgcalls/group/GroupInstanceCustomImpl.cpp` (~4700 lines). Uses a client-server model with an SFU, unlike 1:1 calls which are peer-to-peer. + +### Protocol Stack +- **Join signaling**: JSON over application layer (`emitJoinPayload` → app sends to SFU → `setJoinResponsePayload`) +- **Transport**: ICE + DTLS-SRTP over UDP (standard WebRTC transport, NOT PeerConnection) +- **Media**: RTP/RTCP with Opus audio (48kHz, 2ch, 32kbps), optional VP8/H264/VP9 video +- **Control**: SCTP data channel over DTLS for Colibri protocol (video constraints, debug messages) + +### Join Flow +1. Client calls `emitJoinPayload()` → generates JSON with audio SSRC, ICE ufrag/pwd, DTLS fingerprint +2. Application sends JSON to SFU server +3. Server responds with its ICE candidates, DTLS fingerprint, video codec info +4. Client calls `setJoinResponsePayload(json)` → ICE/DTLS negotiation begins +5. On connection: `networkStateUpdated` callback fires + +### Participant Discovery +- Unknown SSRC arrives in RTP → `receiveUnknownSsrcPacket()` → `maybeRequestUnknownSsrc(ssrc)` +- App's `requestMediaChannelDescriptions` callback queries server for SSRC→participant mapping +- `addIncomingAudioChannel(ssrc, userId)` creates decoder channel + +### Colibri Data Channel Messages +```json +// SFU → Client +{"colibriClass": "SenderVideoConstraints", "videoConstraints": {"idealHeight": 360}} + +// Client → SFU +{"colibriClass": "ReceiverVideoConstraints", "defaultConstraints": {"maxHeight": 0}, + "constraints": {"endpoint1": {"minHeight": 720, "maxHeight": 720}}} +``` + +### Key Files +- `tgcalls/group/GroupInstanceCustomImpl.h/.cpp` — main implementation +- `tgcalls/group/GroupNetworkManager.h/.cpp` — ICE/DTLS/SRTP transport +- `tgcalls/group/GroupJoinPayloadInternal.h/.cpp` — join JSON serialization + +## GroupInstanceReferenceImpl (PeerConnection-based Group Calls) + +An alternative group call implementation that uses standard WebRTC PeerConnection instead of the manual ICE/DTLS/SRTP management in `GroupInstanceCustomImpl`. Supports both audio and video (H264 simulcast). Implements the same `GroupInstanceInterface`. + +### Architecture + +``` +GroupInstanceReferenceImpl + └── PeerConnection (single, to SFU) + ├── sendrecv audio transceiver (outgoing audio) + ├── sendonly video transceiver (outgoing H264 simulcast, SDP-munged SSRCs) + ├── recvonly audio transceivers (one per remote SSRC, added dynamically) + ├── recvonly video transceivers (one per remote endpoint, added dynamically) + └── data channel ("data", for ActiveAudioSsrcs / ActiveVideoSsrcs) +``` + +### How It Differs from CustomImpl + +| Aspect | CustomImpl | ReferenceImpl | +|--------|-----------|---------------| +| Transport | Manual ICE/DTLS/SRTP via GroupNetworkManager | WebRTC PeerConnection | +| SDP | None (custom JSON protocol) | Local SDP construction, translates to/from JSON | +| SSRC discovery | `unknownSsrcPacketReceived` on raw RTP | `ActiveAudioSsrcs`/`ActiveVideoSsrcs` data channel messages from SFU | +| Audio channels | Manual `IncomingAudioChannel` per SSRC | PeerConnection recvonly transceivers | +| Audio levels | RTP header extension parsing | Synthetic levels based on known SSRCs | +| Video outgoing | Manual `cricket::VideoChannel` with direct SSRC control | PeerConnection sendonly transceiver + SDP munging for simulcast SSRCs | +| Video incoming | Manual `IncomingVideoChannel` per endpoint | PeerConnection recvonly transceivers with SSRCs in answer | +| Video decode | Manual decoder lifecycle | PeerConnection handles internally | +| Code size | ~4700 lines | ~1500 lines | + +### Join Flow (SDP Translation) + +1. Create PeerConnection with Opus audio transceiver, sendonly video transceiver (no track), and data channel +2. `createOffer` → munge video SSRCs (replace PeerConnection's auto-generated SSRCs with pre-allocated simulcast SSRCs) → `SetLocalDescription` → extract ICE/DTLS params from local SDP +3. Serialize as JSON (same format as CustomImpl): `{ssrc, ufrag, pwd, fingerprints, ssrc-groups}` +4. Parse SFU response JSON → construct `JsepSessionDescription("answer")` programmatically via `cricket::SessionDescription` API (no SDP string parsing) +5. `SetRemoteDescription` → ICE/DTLS connects via PeerConnection internals +6. Add remote ICE candidates via `AddIceCandidate` after `SetRemoteDescription` +7. Activate outgoing video: attach `FakeVideoTrackSource` track to the existing sendonly transceiver via `sender()->SetTrack()` — no renegotiation needed + +### Dynamic Participant Handling + +**Audio:** +1. SFU sends `{"colibriClass":"ActiveAudioSsrcs","ssrcs":[54321,98765]}` over data channel +2. Client diffs against known SSRCs +3. New SSRCs: add recvonly audio transceiver → renegotiate (new offer + constructed answer mirroring offer mids) +4. Removed SSRCs: clean up from tracking map + +**Video:** +1. SFU sends `ActiveVideoSsrcs` over data channel → forwarded to app via `dataChannelMessageReceived` +2. App calls `setRequestedVideoChannels()` → adds recvonly video transceivers, sends `ReceiverVideoConstraints` over data channel +3. Renegotiate: new offer → munge outgoing video SSRCs → `SetLocalDescription` → build answer with incoming video SSRCs → `SetRemoteDescription` +4. `wirePendingVideoSinks()`: attach `FakeVideoSink` to the recvonly transceiver's receiver track after `SetRemoteDescription` completes +5. Renegotiations are serialized (`_isRenegotiating` / `_pendingRenegotiation` flags) to prevent overlapping offer/answer cycles + +### Outgoing Video: SDP Munging for Simulcast + +PeerConnection's API doesn't support SSRC-based simulcast directly (only RID-based, which doesn't put SSRCs in the SDP). The workaround: + +1. Pre-allocate 6 random video SSRCs at construction: 3 layers × (primary + RTX) +2. Add a sendonly video transceiver in `start()` with no track +3. Before `SetLocalDescription`, `mungeVideoSsrcsInOffer()` replaces the video m-line's auto-generated `StreamParams` with our pre-allocated SSRCs + SIM + FID groups +4. `UpdateLocalStreams_w()` in WebRTC's `channel.cc` sees SSRCs already present and skips generation +5. Later, `setVideoSource()` just calls `sender()->SetTrack()` — no renegotiation + +### Incoming Video: SSRC-Based Demux + +The answer for incoming video m-lines includes remote SSRCs from `VideoChannelDescription.ssrcGroups`. This is required because CustomImpl sets the `WebRTC-Video-DiscardPacketsWithUnknownSsrc` field trial process-wide, which disables unsignaled stream creation. Without explicit SSRCs, PeerConnection drops incoming video packets in mixed groups. + +### Key Implementation Details + +- **ICE roles**: PeerConnection uses standard ICE (full agent, controlling when remote is ICE-lite). The SFU uses `Accept` for PeerConnection clients vs `Dial` for CustomImpl clients. +- **Loopback**: `PeerConnectionFactory::Options::network_ignore_mask = 0` enables loopback interface gathering for localhost SFU +- **MID exclusion**: The `buildRemoteAnswer()` excludes the `urn:ietf:params:rtp-hdrext:sdes:mid` RTP header extension from ALL m-lines (audio and video). The SFU forwards raw RTP with the sender's MID value, which would cause the BUNDLE demuxer to route packets to the wrong channel. Without MID, PeerConnection falls back to SSRC/PT-based routing. +- **RTP header extensions**: Copied from the local offer per m-line (minus MID), ensuring BUNDLE-safe IDs. Hardcoding IDs risks collisions across the BUNDLE group. +- **SDP mid matching**: During renegotiation, the constructed remote answer mirrors the local offer's m-line structure and mids exactly. Mismatched mids cause `SetRemoteDescription` to fail. +- **Audio level reporting**: Uses synthetic levels (0.1) for all known remote SSRCs, since the SFU forwards RTP with extension IDs that may not match PeerConnection's negotiated mapping +- **Video sink wiring**: `OnTrack` doesn't fire for locally-created recvonly transceivers. Sinks are wired explicitly in `wirePendingVideoSinks()` after `SetRemoteDescription` completes, and also in `addIncomingVideoOutput()` if the track already exists. +- **H264 codec in answer**: PT 104 (primary) + PT 105 (RTX, apt=104), matching WebRTC's `assignPayloadTypes` order. RTCP feedback: nack, nack pli, ccm fir, goog-remb, transport-cc. +- **Renegotiation serialization**: Only one offer/answer cycle runs at a time. Deferred renegotiations only fire if there are unnegotiated transceivers (no mid assigned yet), avoiding redundant cycles. + +### Key Files +- `tgcalls/group/GroupInstanceReferenceImpl.h/.cpp` — implementation +- `tgcalls/group/GroupInstanceImpl.h` — shared `GroupInstanceInterface` + +## Video Support Pitfalls + +Critical findings from implementing video in the test SFU — relevant for anyone working on group video: + +### H264 Decoder Requires Two Build Flags +The WebRTC BUILD needs BOTH `-DWEBRTC_USE_H264` (encoder, OpenH264) AND `-DWEBRTC_USE_H264_DECODER` (decoder, FFmpeg). Without the decoder flag, `H264Decoder::Create()` returns nullptr and WebRTC silently falls back to `NullVideoDecoder` which accepts frames but never decodes them — no error logged. The encoder works fine without the decoder flag, making this easy to miss. + +### FFmpeg 7+ Removed `reordered_opaque` +`h264_decoder_impl.cc` uses `AVCodecContext::reordered_opaque` and `AVFrame::reordered_opaque` for passing timestamps through the decode pipeline. FFmpeg 7+ removed this field. The fix uses `AVPacket::pts` instead. IMPORTANT: `AVCodecContext::opaque` is already used to store the `H264DecoderImpl*` pointer (line 74 of `AVGetBuffer2`) — do NOT use it for timestamps. + +### Outgoing Video Channel Steals Incoming RTP +`GroupInstanceCustomImpl` creates separate `cricket::VideoChannel` objects for outgoing and incoming video, all sharing the same `RtpTransport`. The outgoing channel's `WebRtcVideoReceiveChannel` has an "unsignalled SSRC" handler that creates default receive streams for unknown SSRCs. When video RTP from other participants arrives before `IncomingVideoChannel` registers its SSRCs, the outgoing channel intercepts the packets permanently. Fix: enable the `WebRTC-Video-DiscardPacketsWithUnknownSsrc` field trial in the field trial string. + +### Video Channel Setup Is Reactive, Not Pre-Registered +Video channels are set up reactively when `ActiveVideoSsrcs` arrives via the data channel — same as the real Telegram app. The `dataChannelMessageReceived` callback in `GroupInstanceDescriptor` forwards Colibri messages to the app, which calls `setRequestedVideoChannels`. The `DiscardPacketsWithUnknownSsrc` field trial prevents the outgoing channel from stealing RTP packets for SSRCs not yet registered. The SFU sends proactive PLI after constraints arrive, ensuring keyframes are produced after the incoming channel is ready. + +### SFU Must Send Proactive PLI +WebRTC's `VideoReceiveStream2` doesn't immediately request a keyframe when a new receive stream is created — it waits until it detects missing packets or a timeout. The SFU must proactively send PLI to the sender when a receiver first requests video via `ReceiverVideoConstraints`. Without this, the decoder waits indefinitely for a keyframe. + +### RTP/RTCP Demux: Marker Bit False Positives +RFC 5761 demux by second byte: RTCP types are 200-211. But RTP with Marker=1 and dynamic PT ≥ 96 gives byte[1] ≥ 224. Using `byte[1] >= 200` falsely classifies H264 RTP (PT=104, M=1 → byte[1]=232) as RTCP. Correct range: `byte[1] >= 200 && byte[1] < 224`. + +### SRTCP Requires Separate Contexts from SRTP +Pion's `SessionSRTP` and `SessionSRTCP` can't share the same `net.Conn` (both start read loops that fight for packets). The solution: demux RTCP at the transport level (in `PacketDemux`), create separate `srtp.Context` instances for SRTCP decrypt/encrypt using the same DTLS-extracted keys, and handle RTCP manually without `SessionSRTCP`. + +### PeerConnection Simulcast SSRCs Require SDP Munging +PeerConnection's API doesn't support SSRC-based simulcast (only RID-based). With RID-based simulcast, SSRCs are NOT in the `createOffer` SDP — they're generated internally during `SetLocalDescription` and not accessible via `sender->GetParameters()` (only primary SSRCs, not RTX). The workaround: add a single-encoding transceiver (no RIDs), then replace the auto-generated `StreamParams` in the offer with pre-allocated SSRCs + SIM + FID groups before calling `SetLocalDescription`. `UpdateLocalStreams_w()` skips generation when SSRCs already exist. IMPORTANT: `transceiver->mid()` is `nullopt` before `SetLocalDescription` — match by content direction, not mid. + +### MID RTP Header Extension Causes Wrong Channel Routing in SFU +The SFU forwards raw RTP packets including all header extensions. If the sender's video RTP includes a MID extension (e.g., MID="1"), the receiver's PeerConnection BUNDLE demuxer routes the packet to its own mid=1 channel — which is the outgoing video, not the incoming video transceiver. Fix: exclude `urn:ietf:params:rtp-hdrext:sdes:mid` from ALL m-lines in `buildRemoteAnswer()`. Without MID negotiated, PeerConnection falls back to SSRC/PT-based routing. This must be done for ALL m-lines (including audio) because the BUNDLE transport shares the extension map across all channels. + +### `DiscardPacketsWithUnknownSsrc` Is Process-Wide +CustomImpl calls `field_trial::InitFieldTrialsFromString(...)` which sets `WebRTC-Video-DiscardPacketsWithUnknownSsrc/Enabled/` globally for the process. In mixed groups, this prevents ReferenceImpl's PeerConnection from creating unsignaled receive streams for incoming video. Fix: include explicit remote video SSRCs in the `buildRemoteAnswer()` for incoming video m-lines, so PeerConnection registers SSRC-based demux entries instead of relying on unsignaled stream handling. + +### `OnTrack` Doesn't Fire for Locally-Created Recvonly Transceivers +When you call `AddTransceiver(MEDIA_TYPE_VIDEO, {direction=recvonly})`, PeerConnection creates the transceiver and its receiver track immediately. `OnTrack` only fires when a REMOTE-initiated track is added. For locally-created recvonly transceivers, you must wire sinks explicitly after `SetRemoteDescription` completes — don't wait for `OnTrack`. + +### SSRC Parsing: json11 int_value() Overflows for uint32 > INT_MAX +`GoSfu_QueryVideoSsrcs` returns SSRCs as `uint32` in JSON. For values > 2^31, json11's `int_value()` (which returns `int`) overflows to `INT_MAX` (2147483647). Fix: use `number_value()` (returns `double`) and cast via `int64_t` to `uint32_t`. + +### Join Payload JSON Field Name: `"sources"` Not `"ssrcs"` +tgcalls serializes video SSRC groups in `GroupJoinInternalPayload::serialize()` using the key `"sources"` (not `"ssrcs"`). The Go SFU's JSON struct tags must match: `Sources []int32 \`json:"sources"\``. + +### Simulcast Max Layers Depends on Source Resolution, Not Bitrate +WebRTC's `kSimulcastFormats` table in `video/config/simulcast.cc` hardcodes `max_layers` per resolution: 640x360 → 2 layers, 960x540 → 3 layers, 1280x720 → 3 layers. The `SimulcastEncoderAdapter` uses this to cap the number of encoders regardless of available bitrate. If you need 3 simulcast layers, the source must be at least 960x540. The `FakeVideoTrackSource` uses 1280x720 for this reason. With 1280x720 and scale factors /4, /2, /1, the layers are 320x180, 640x360, 1280x720. + +### SFU Must Rewrite SSRCs When Switching Simulcast Layers +CustomImpl's `IncomingVideoChannel` calls `SetSink(_mainVideoSsrc, ...)` where `_mainVideoSsrc` is the first SSRC in the SIM group (layer 0). The video sink only receives decoded frames from that specific SSRC's receive stream. When the SFU forwards a higher layer's packets, it must rewrite bytes 8-11 of the RTP header to the primary (layer 0) SSRC. RTX packets must similarly be rewritten to the layer 0 FID SSRC. Without this, higher-layer packets are delivered to the wrong receive stream and produce zero decoded frames. This is standard SFU behavior for simulcast — Jitsi and mediasoup do the same. + +### Sender BWE Start Bitrate Determines Initial Layer Count +`adjustBitratePreferences` sets `start_bitrate_bps = max(min_bitrate_bps, 400k)`. At 400kbps start, the `BitrateAllocator` gives L0 (60k) + L1 (110k) = 170k, leaving only 230k for L2 which needs min 300k. Layer 2 is disabled until the GCC ramps up. The SFU's transport-cc feedback enables this ramp-up. The `UpdateAllocationLimits` log shows `total_requested_max_bitrate` — if this is below the sum of all layers' min bitrates, some layers are excluded. + +### `assignPayloadTypes` Codec Ordering +WebRTC's `assignPayloadTypes` assigns dynamic PTs starting at 100 in order: VP8 (100/101), VP9 (102/103), H264 (104/105). Both sender and receiver call this independently with the same codec list, so PTs match. The SFU's join response codec PTs (100 for H264 in our case) are used by `configureVideoParams` to SELECT which codec to use, but the actual PT assignment comes from `assignPayloadTypes`. + +## Known Issues +- `ThreadLocalObject::~ThreadLocalObject()` posts fire-and-forget cleanup tasks to the tgcalls media thread. If the process does orderly static destruction, the static thread pool may be torn down while these tasks are still executing, causing "pure virtual function called". The CLI tool uses `_exit()` to avoid this. This is not a problem in the real Telegram app. +- `SignalingSctpConnection::OnReadyToSend()` had a missing `break` after the first send failure in its pending-data flush loop. This could cause application-level message reordering (though the application handles it gracefully via `_pendingIceCandidates` buffering). Fixed in our fork. +- `InstanceV2ReferenceImpl::writeStateLogRecords()` had a use-after-free: it captured a raw `Call*` pointer on the media thread and posted it to the worker thread. If `stop()` called `_peerConnection->Close()` (which destroys `Call`) between the post and worker thread execution, the worker thread would dereference a dangling pointer. The `call_ptr_` field in WebRTC's `PeerConnection` is `Call* const` and is never nulled, so the existing null check didn't catch this. Fixed with an `_isStopped` atomic flag checked in the worker thread lambda before accessing `call`. Manifested as ~2% segfault rate under 250-process parallel load; 100% pass rate after fix (5000/5000). +- WebRTC's `RTC_LOG` writes to stdout, not stderr. There is no way to separate it from application output within a single process. The local mass test harness (`run-local-test.sh`) works around this by using separate processes and checking exit codes rather than parsing output. diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index edf115e500..acebade7a2 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -291,8 +291,10 @@ final class WebAppWebView: WKWebView { } func sendEvent(name: String, data: String?) { - guard let trustedOrigin = self.trustedOrigin, self.origin == trustedOrigin else { - return + if self.useSecuredEventProxy { + guard let trustedOrigin = self.trustedOrigin, self.origin == trustedOrigin else { + return + } } let script = "window.TelegramGameProxy && window.TelegramGameProxy.receiveEvent && window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))" self.evaluateJavaScript(script, completionHandler: { _, _ in diff --git a/submodules/ffmpeg/BUILD b/submodules/ffmpeg/BUILD index 2c1758dee2..9dad938d82 100644 --- a/submodules/ffmpeg/BUILD +++ b/submodules/ffmpeg/BUILD @@ -271,6 +271,13 @@ genrule( elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then BUILD_ARCH="sim_arm64" VARIANT="debug" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" + VARIANT="debug" + elif [ "$(TARGET_CPU)" == "k8" ] || [ "$(TARGET_CPU)" == "x86_64" ]; then + BUILD_ARCH="linux_x86_64" + elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then + BUILD_ARCH="linux_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -301,24 +308,21 @@ cc_library( ] ) -objc_library( +cc_library( name = "ffmpeg", - module_name = "ffmpeg", - enable_modules = True, hdrs = ["Public/third_party/ffmpeg/" + x for x in ffmpeg_header_paths] + ["Public/" + x for x in ffmpeg_header_paths], includes = [ "Public", ], - sdk_dylibs = [ - "libbz2", - "libiconv", - "z", - ], - sdk_frameworks = [ - "AudioToolbox", - "CoreAudio", - "VideoToolbox" - ], + linkopts = select({ + "@platforms//os:linux": ["-lbz2", "-lz"], + "//conditions:default": [ + "-lbz2", "-liconv", "-lz", + "-framework AudioToolbox", + "-framework CoreAudio", + "-framework VideoToolbox", + ], + }), deps = [ ":ffmpeg_lib", "//third-party/libvpx:vpx", @@ -327,5 +331,5 @@ objc_library( ], visibility = [ "//visibility:public", - ] + ], ) diff --git a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh index a816a5ac02..5015e7489a 100755 --- a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh +++ b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -x @@ -7,7 +7,7 @@ ARCHS="" for RAW_ARCH in $RAW_ARCHS; do ARCH_NAME="$RAW_ARCH" - if [ "$ARCH_NAME" = "i386" -o "$ARCH_NAME" = "x86_64" -o "$ARCH_NAME" = "arm64" -o "$ARCH_NAME" = "armv7" -o "$ARCH_NAME" = "sim_arm64" ] + if [ "$ARCH_NAME" = "i386" -o "$ARCH_NAME" = "x86_64" -o "$ARCH_NAME" = "arm64" -o "$ARCH_NAME" = "armv7" -o "$ARCH_NAME" = "sim_arm64" -o "$ARCH_NAME" = "macos_arm64" -o "$ARCH_NAME" = "linux_arm64" -o "$ARCH_NAME" = "linux_x86_64" ] then ARCHS="$ARCHS $ARCH_NAME" else @@ -38,24 +38,21 @@ LIB_NAMES="libavcodec libavformat libavutil libswresample" set -e CONFIGURE_FLAGS="--enable-cross-compile --disable-programs \ - --disable-armv5te --disable-armv6 --disable-armv6t2 \ + --disable-armv5te --disable-armv6 --disable-armv6t2 \ --disable-doc --enable-pic --disable-all --disable-everything \ --enable-avcodec \ --enable-swresample \ --enable-avformat \ --disable-xlib \ --enable-libopus \ - --enable-libvpx \ - --enable-libdav1d \ - --enable-audiotoolbox \ + --enable-libvpx \ + --enable-libdav1d \ --enable-bsf=aac_adtstoasc,vp9_superframe,h264_mp4toannexb \ - --enable-decoder=h264,libvpx_vp9,hevc,libopus,flac,alac_at,pcm_s16le,pcm_s24le,pcm_f32le,gsm_ms_at,libdav1d,av1,mp3,aac_at \ - --enable-encoder=libvpx_vp9,aac_at \ + --enable-decoder=h264,libvpx_vp9,hevc,libopus,flac,pcm_s16le,pcm_s24le,pcm_f32le,libdav1d,av1,mp3 \ --enable-demuxer=aac,mov,m4v,mp3,ogg,libopus,flac,wav,aiff,matroska,mpegts, \ --enable-parser=aac,h264,mp3,libopus \ --enable-protocol=file \ --enable-muxer=mp4,matroska,ogg,mpegts \ - --enable-hwaccel=h264_videotoolbox,hevc_videotoolbox,av1_videotoolbox \ " #vorbis @@ -84,7 +81,7 @@ do do LIB="$THIN/$ARCH/lib/$LIB_NAME.a" if [ -f "$LIB" ]; then - LIB_DATE=`crc32 "$LIB"` + LIB_DATE=$(crc32 "$LIB" 2>/dev/null || cksum "$LIB" 2>/dev/null | cut -d' ' -f1 || echo "unknown") LIBS_HASH="$LIBS_HASH $ARCH/$LIB:$LIB_DATE" fi done @@ -99,7 +96,11 @@ then echo "PATH=$PATH" echo "pkg-config=$(which pkg-config)" fi - if [ ! `which "$GAS_PREPROCESSOR_PATH"` ]; then + IS_LINUX=false + for A in $ARCHS; do + case "$A" in linux_*) IS_LINUX=true ;; esac + done + if [ "$IS_LINUX" = "false" ] && [ ! `which "$GAS_PREPROCESSOR_PATH"` ]; then echo '$GAS_PREPROCESSOR_PATH not found.' exit 1 fi @@ -114,6 +115,8 @@ then ARCH="$RAW_ARCH" if [ "$RAW_ARCH" == "sim_arm64" ]; then ARCH="arm64" + elif [ "$RAW_ARCH" == "macos_arm64" ]; then + ARCH="arm64" fi echo "building $RAW_ARCH..." @@ -124,10 +127,26 @@ then LIBVPX_PATH="$SOURCE_DIR/libvpx" LIBDAV1D_PATH="$SOURCE_DIR/libdav1d" + if [ "$RAW_ARCH" = "linux_arm64" ]; then + ARCH="aarch64" + CFLAGS="$EXTRA_CFLAGS -fPIC" + CC="gcc" + AS="gcc" + PLATFORM="linux" + elif [ "$RAW_ARCH" = "linux_x86_64" ]; then + ARCH="x86_64" + CFLAGS="$EXTRA_CFLAGS -fPIC" + CC="gcc" + AS="nasm" + PLATFORM="linux" + else CFLAGS="$EXTRA_CFLAGS -arch $ARCH" if [ "$RAW_ARCH" = "sim_arm64" ]; then PLATFORM="iPhoneSimulator" CFLAGS="$CFLAGS -mios-simulator-version-min=$DEPLOYMENT_TARGET --target=arm64-apple-ios$DEPLOYMENT_TARGET-simulator" + elif [ "$RAW_ARCH" = "macos_arm64" ]; then + PLATFORM="MacOSX" + CFLAGS="$CFLAGS -mmacosx-version-min=14.0 --target=arm64-apple-macosx14.0" else PLATFORM="iPhoneOS" CFLAGS="$CFLAGS -mios-version-min=$DEPLOYMENT_TARGET" @@ -136,15 +155,18 @@ then EXPORT="GASPP_FIX_XCODE5=1" fi fi + fi - XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'` - CC="xcrun -sdk $XCRUN_SDK clang" + if [ "$PLATFORM" != "linux" ]; then + XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'` + CC="xcrun -sdk $XCRUN_SDK clang" - if [ "$RAW_ARCH" = "arm64" ] || [ "$RAW_ARCH" = "sim_arm64" ] - then - AS="$GAS_PREPROCESSOR_PATH -arch aarch64 -- $CC" - else - AS="$GAS_PREPROCESSOR_PATH -- $CC" + if [ "$RAW_ARCH" = "arm64" ] || [ "$RAW_ARCH" = "sim_arm64" ] || [ "$RAW_ARCH" = "macos_arm64" ] + then + AS="$GAS_PREPROCESSOR_PATH -arch aarch64 -- $CC" + else + AS="$GAS_PREPROCESSOR_PATH -- $CC" + fi fi CXXFLAGS="$CFLAGS" @@ -161,22 +183,42 @@ then echo "1" >/dev/null else mkdir -p "$THIN/$RAW_ARCH" - TMPDIR=${TMPDIR/%\/} "$SOURCE/configure" \ - --target-os=darwin \ - --arch=$ARCH \ - --cc="$CC" \ - --as="$AS" \ - $CONFIGURE_FLAGS \ - --extra-cflags="$CFLAGS" \ - --extra-ldflags="$LDFLAGS" \ - --prefix="$THIN/$RAW_ARCH" \ - --pkg-config="$PKG_CONFIG" \ - --pkg-config-flags="--libopus_path $LIBOPUS_PATH --libvpx_path $LIBVPX_PATH --libdav1d_path $LIBDAV1D_PATH" \ - || exit 1 + if [ "$PLATFORM" = "linux" ]; then + TMPDIR=${TMPDIR/%\/} "$SOURCE/configure" \ + --target-os=linux \ + --arch=$ARCH \ + --cc="$CC" \ + --as="$AS" \ + $CONFIGURE_FLAGS \ + --enable-encoder=libvpx_vp9 \ + --extra-cflags="$CFLAGS" \ + --extra-ldflags="$LDFLAGS" \ + --prefix="$THIN/$RAW_ARCH" \ + --pkg-config="$PKG_CONFIG" \ + --pkg-config-flags="--libopus_path $LIBOPUS_PATH --libvpx_path $LIBVPX_PATH --libdav1d_path $LIBDAV1D_PATH" \ + || exit 1 + else + TMPDIR=${TMPDIR/%\/} "$SOURCE/configure" \ + --target-os=darwin \ + --arch=$ARCH \ + --cc="$CC" \ + --as="$AS" \ + $CONFIGURE_FLAGS \ + --enable-audiotoolbox \ + --enable-decoder=alac_at,gsm_ms_at,aac_at \ + --enable-encoder=libvpx_vp9,aac_at \ + --enable-hwaccel=h264_videotoolbox,hevc_videotoolbox,av1_videotoolbox \ + --extra-cflags="$CFLAGS" \ + --extra-ldflags="$LDFLAGS" \ + --prefix="$THIN/$RAW_ARCH" \ + --pkg-config="$PKG_CONFIG" \ + --pkg-config-flags="--libopus_path $LIBOPUS_PATH --libvpx_path $LIBVPX_PATH --libdav1d_path $LIBDAV1D_PATH" \ + || exit 1 + fi echo "$CONFIGURE_FLAGS" > "$CONFIGURED_MARKER" fi - CORE_COUNT=`PATH="$PATH:/usr/sbin" sysctl -n hw.logicalcpu` + CORE_COUNT=$(nproc 2>/dev/null || PATH="$PATH:/usr/sbin" sysctl -n hw.logicalcpu 2>/dev/null || echo 4) make -j$CORE_COUNT install $EXPORT || exit 1 popd @@ -190,7 +232,7 @@ do do LIB="$THIN/$ARCH/lib/$LIB_NAME.a" if [ -f "$LIB" ]; then - LIB_DATE=`crc32 "$LIB"` + LIB_DATE=$(crc32 "$LIB" 2>/dev/null || cksum "$LIB" 2>/dev/null | cut -d' ' -f1 || echo "unknown") UPDATED_LIBS_HASH="$UPDATED_LIBS_HASH $ARCH/$LIB:$LIB_DATE" fi done @@ -215,7 +257,11 @@ then LIB_NAME="$(basename $LIB)" echo "LIPO_INPUT command find \"$THIN\" -name \"$LIB_NAME\"" LIPO_INPUT=`find "$THIN" -name "$LIB_NAME"` - lipo -create $LIPO_INPUT -output "$FAT/lib/$LIB_NAME" || exit 1 + if command -v lipo >/dev/null 2>&1; then + lipo -create $LIPO_INPUT -output "$FAT/lib/$LIB_NAME" || exit 1 + else + cp $LIPO_INPUT "$FAT/lib/$LIB_NAME" || exit 1 + fi done cp -rf "$THIN/$1/include" "$FAT" diff --git a/submodules/ffmpeg/Sources/FFMpeg/pkg-config b/submodules/ffmpeg/Sources/FFMpeg/pkg-config index 48e6014229..7a40f50389 100755 --- a/submodules/ffmpeg/Sources/FFMpeg/pkg-config +++ b/submodules/ffmpeg/Sources/FFMpeg/pkg-config @@ -1,4 +1,10 @@ -#!/bin/sh +#!/bin/bash + +# Strip version qualifiers (e.g. "dav1d >= 0.5.0" -> "dav1d") +# FFmpeg's configure passes these to pkg-config +strip_version() { + echo "$@" | sed 's/ *[><=!].*//' +} if [ "$1" == "--version" ]; then echo "0.29.2" @@ -7,8 +13,10 @@ elif [ "$1" == "--exists" ]; then NAME="$2" PRINT_ERRORS="0" if [ "$NAME" == "--print-errors" ]; then - NAME="$3" + NAME=$(strip_version "$3") PRINT_ERRORS="1" + else + NAME=$(strip_version "$2") fi if [ "$NAME" == "zlib" ]; then exit 0 @@ -87,17 +95,53 @@ elif [ "$1" == "--libs" ]; then echo "-lz" exit 0 elif [ "$NAME" == "opus" ]; then - echo "-L$LIBOPUS_PATH/lib -lopus" + echo "-L$LIBOPUS_PATH/lib -lopus -lm" exit 0 elif [ "$NAME" == "vpx" ]; then - echo "-L$LIBVPX_PATH/lib -lVPX" + echo "-L$LIBVPX_PATH/lib -lVPX -lm -lpthread" exit 0 elif [ "$NAME" == "dav1d" ]; then - echo "-L$LIBDAV1D_PATH/lib -ldav1d" + echo "-L$LIBDAV1D_PATH/lib -ldav1d -lm -lpthread -ldl" exit 0 else exit 1 fi +elif [[ "$1" == --variable=* ]]; then + # Handle --variable=includedir etc. + LIBOPUS_PATH="" + LIBVPX_PATH="" + LIBDAV1D_PATH="" + # Parse the library path flags to find NAME (last arg) + ARGS=("$@") + NAME="${ARGS[-1]}" + for ((i=1; i<${#ARGS[@]}-1; i++)); do + if [ "${ARGS[$i]}" == "--libopus_path" ]; then + LIBOPUS_PATH="${ARGS[$((i+1))]}" + elif [ "${ARGS[$i]}" == "--libvpx_path" ]; then + LIBVPX_PATH="${ARGS[$((i+1))]}" + elif [ "${ARGS[$i]}" == "--libdav1d_path" ]; then + LIBDAV1D_PATH="${ARGS[$((i+1))]}" + fi + done + VAR="${1#--variable=}" + if [ "$VAR" == "includedir" ]; then + if [ "$NAME" == "opus" ]; then + echo "$LIBOPUS_PATH/include" + exit 0 + elif [ "$NAME" == "vpx" ]; then + echo "$LIBVPX_PATH/include" + exit 0 + elif [ "$NAME" == "dav1d" ]; then + echo "$LIBDAV1D_PATH/include" + exit 0 + elif [ "$NAME" == "zlib" ]; then + echo "/usr/include" + exit 0 + fi + fi + # Return empty string for unhandled variables (non-fatal) + echo "" + exit 0 else exit 1 fi diff --git a/third-party/boringssl/src/crypto/internal.h b/third-party/boringssl/src/crypto/internal.h index 8e7fc86de1..d1f0b3ed2b 100644 --- a/third-party/boringssl/src/crypto/internal.h +++ b/third-party/boringssl/src/crypto/internal.h @@ -1170,7 +1170,8 @@ static inline uint64_t CRYPTO_rotr_u64(uint64_t value, int shift) { // CRYPTO_addc_* returns |x + y + carry|, and sets |*out_carry| to the carry // bit. |carry| must be zero or one. -#if OPENSSL_HAS_BUILTIN(__builtin_addc) +// _Generic is C11-only and not available in C++ mode with GCC. +#if OPENSSL_HAS_BUILTIN(__builtin_addc) && !defined(__cplusplus) #define CRYPTO_GENERIC_ADDC(x, y, carry, out_carry) \ (_Generic((x), \ @@ -1222,7 +1223,7 @@ static inline uint64_t CRYPTO_addc_u64(uint64_t x, uint64_t y, uint64_t carry, // CRYPTO_subc_* returns |x - y - borrow|, and sets |*out_borrow| to the borrow // bit. |borrow| must be zero or one. -#if OPENSSL_HAS_BUILTIN(__builtin_subc) +#if OPENSSL_HAS_BUILTIN(__builtin_subc) && !defined(__cplusplus) #define CRYPTO_GENERIC_SUBC(x, y, borrow, out_borrow) \ (_Generic((x), \ diff --git a/third-party/dav1d/BUILD b/third-party/dav1d/BUILD index ba022d0420..c62fdeea97 100644 --- a/third-party/dav1d/BUILD +++ b/third-party/dav1d/BUILD @@ -49,6 +49,12 @@ genrule( BUILD_ARCH="arm64" elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then BUILD_ARCH="sim_arm64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" + elif [ "$(TARGET_CPU)" == "k8" ] || [ "$(TARGET_CPU)" == "x86_64" ]; then + BUILD_ARCH="linux_x86_64" + elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then + BUILD_ARCH="linux_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -57,15 +63,20 @@ genrule( rm -rf "$$BUILD_DIR" mkdir -p "$$BUILD_DIR" - MESON_DIR="$$(pwd)/$$BUILD_DIR/meson" - rm -rf "$$MESON_DIR" - mkdir -p "$$MESON_DIR" - tar -xzf "$(location @meson_tar_gz//file)" -C "$$MESON_DIR" + if [ "$${BUILD_ARCH}" = "linux_x86_64" ] || [ "$${BUILD_ARCH}" = "linux_arm64" ]; then + EXTRA_PATH="" + else + MESON_DIR="$$(pwd)/$$BUILD_DIR/meson" + rm -rf "$$MESON_DIR" + mkdir -p "$$MESON_DIR" + tar -xzf "$(location @meson_tar_gz//file)" -C "$$MESON_DIR" - NINJA_DIR="$$(pwd)/$$BUILD_DIR/ninja" - rm -rf "$$NINJA_DIR" - mkdir -p "$$NINJA_DIR" - unzip "$(location @ninja-mac_zip//file)" -d "$$NINJA_DIR" + NINJA_DIR="$$(pwd)/$$BUILD_DIR/ninja" + rm -rf "$$NINJA_DIR" + mkdir -p "$$NINJA_DIR" + unzip "$(location @ninja-mac_zip//file)" -d "$$NINJA_DIR" + EXTRA_PATH="$$MESON_DIR/meson-1.6.0:$$NINJA_DIR" + fi cp $(location :build-dav1d-bazel.sh) "$$BUILD_DIR/" cp $(location :arm64-iPhoneSimulator.meson) "$$BUILD_DIR/" @@ -78,7 +89,11 @@ genrule( mkdir -p "$$BUILD_DIR/Public/compat" mkdir -p "$$BUILD_DIR/Public/common" - PATH="$$PATH:$$MESON_DIR/meson-1.6.0:$$NINJA_DIR" sh $$BUILD_DIR/build-dav1d-bazel.sh $$BUILD_ARCH "$$BUILD_DIR" + if [ -n "$$EXTRA_PATH" ]; then + PATH="$$PATH:$$EXTRA_PATH" bash $$BUILD_DIR/build-dav1d-bazel.sh $$BUILD_ARCH "$$BUILD_DIR" + else + bash $$BUILD_DIR/build-dav1d-bazel.sh $$BUILD_ARCH "$$BUILD_DIR" + fi """ + "\n".join([ "cp -f \"$$BUILD_DIR/dav1d/build/include/{}\" \"$(location Public/{})\"".format(header, header) for header in generated_headers @@ -104,10 +119,8 @@ cc_library( srcs = [":Public/dav1d/lib/lib" + x + ".a" for x in libs] ) -objc_library( +cc_library( name = "dav1d", - module_name = "dav1d", - enable_modules = True, hdrs = [":Public/" + x for x in generated_headers], includes = [ "Public", diff --git a/third-party/dav1d/build-dav1d-bazel.sh b/third-party/dav1d/build-dav1d-bazel.sh index 6df4bd50fa..7bdb31cb5a 100644 --- a/third-party/dav1d/build-dav1d-bazel.sh +++ b/third-party/dav1d/build-dav1d-bazel.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -e @@ -18,6 +18,43 @@ elif [ "$ARCH" = "sim_arm64" ]; then custom_xcode_path="$(xcode-select -p)/" sed -i '' "s|/Applications/Xcode.app/Contents/Developer/|$custom_xcode_path|g" "$TARGET_CROSSFILE" CROSSFILE="../package/crossfiles/arm64-iPhoneSimulator-custom.meson" +elif [ "$ARCH" = "macos_arm64" ]; then + TARGET_CROSSFILE="$BUILD_DIR/dav1d/package/crossfiles/arm64-MacOSX-custom.meson" + custom_xcode_path="$(xcode-select -p)" + MACOS_SYSROOT="$custom_xcode_path/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + cat > "$TARGET_CROSSFILE" << MESONEOF +[binaries] +c = ['clang', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +cpp = ['clang++', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +objc = ['clang', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +objcpp = ['clang++', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +ar = 'ar' +strip = 'strip' + +[built-in options] +c_args = ['-mmacosx-version-min=14.0'] +cpp_args = ['-mmacosx-version-min=14.0'] +c_link_args = ['-mmacosx-version-min=14.0'] +cpp_link_args = ['-mmacosx-version-min=14.0'] +objc_args = ['-mmacosx-version-min=14.0'] +objcpp_args = ['-mmacosx-version-min=14.0'] + +[properties] +root = '$custom_xcode_path/Platforms/MacOSX.platform/Developer' +needs_exe_wrapper = false + +[host_machine] +system = 'darwin' +subsystem = 'macos' +kernel = 'xnu' +cpu_family = 'aarch64' +cpu = 'arm64' +endian = 'little' +MESONEOF + CROSSFILE="../package/crossfiles/arm64-MacOSX-custom.meson" +elif [ "$ARCH" = "linux_arm64" ] || [ "$ARCH" = "linux_x86_64" ]; then + # Native Linux build - no cross file needed + CROSSFILE="" else echo "Unsupported architecture $ARCH" exit 1 @@ -28,7 +65,13 @@ rm -rf build mkdir build pushd build -meson.py setup .. --cross-file="$CROSSFILE" $MESON_OPTIONS +MESON_CMD=$(command -v meson.py 2>/dev/null || command -v meson 2>/dev/null || echo meson.py) + +if [ -n "$CROSSFILE" ]; then + $MESON_CMD setup .. --cross-file="$CROSSFILE" $MESON_OPTIONS +else + $MESON_CMD setup .. $MESON_OPTIONS +fi ninja popd diff --git a/third-party/libjxl/BUILD b/third-party/libjxl/BUILD index 58ab6bebee..d1c7975ef7 100644 --- a/third-party/libjxl/BUILD +++ b/third-party/libjxl/BUILD @@ -57,6 +57,8 @@ genrule( BUILD_ARCH="sim_arm64" elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/libjxl/build-libjxl-bazel.sh b/third-party/libjxl/build-libjxl-bazel.sh index d63765f2af..5e35ba1aca 100755 --- a/third-party/libjxl/build-libjxl-bazel.sh +++ b/third-party/libjxl/build-libjxl-bazel.sh @@ -33,7 +33,24 @@ elif [ "$ARCH" = "sim_arm64" ]; then IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk) export CFLAGS="-Wall -arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0 -funwind-tables" export CXXFLAGS="$CFLAGS" - + + cd "$BUILD_DIR" + mkdir build + cd build + + touch toolchain.cmake + echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake + echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake + echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + + cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $CMAKE_OPTIONS ../libjxl + make +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-Wall -arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -funwind-tables" + export CXXFLAGS="$CFLAGS" + cd "$BUILD_DIR" mkdir build cd build diff --git a/third-party/libvpx/BUILD b/third-party/libvpx/BUILD index 9040681907..5c34bd2ad9 100644 --- a/third-party/libvpx/BUILD +++ b/third-party/libvpx/BUILD @@ -49,6 +49,15 @@ genrule( elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" PLATFORM_HEADER_DIR="x86_64-iphonesimulator-gcc" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" + PLATFORM_HEADER_DIR="arm64-darwin22-gcc" + elif [ "$(TARGET_CPU)" == "k8" ] || [ "$(TARGET_CPU)" == "x86_64" ]; then + BUILD_ARCH="linux_x86_64" + PLATFORM_HEADER_DIR="x86_64-linux-gcc" + elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then + BUILD_ARCH="linux_arm64" + PLATFORM_HEADER_DIR="arm64-linux-gcc" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -76,7 +85,7 @@ genrule( mkdir -p "$$BUILD_DIR/Public/libvpx" - PATH="$$PATH:$$ABS_YASM_DIR" sh $$BUILD_DIR/build-libvpx-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/libvpx" "$$BUILD_DIR" + PATH="$$PATH:$$ABS_YASM_DIR" bash $$BUILD_DIR/build-libvpx-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/libvpx" "$$BUILD_DIR" """ + "\n".join([ "cp -f \"$$BUILD_DIR/VPX.framework/Headers/vpx/{}\" \"$(location Public/vpx/{})\"".format(header, header) for header in headers @@ -102,10 +111,8 @@ cc_library( srcs = [":Public/vpx/lib" + x + ".a" for x in libs], ) -objc_library( +cc_library( name = "vpx", - module_name = "vpx", - enable_modules = True, hdrs = [":Public/vpx/" + x for x in headers], includes = [ "Public", diff --git a/third-party/libvpx/build-libvpx-bazel.sh b/third-party/libvpx/build-libvpx-bazel.sh old mode 100755 new mode 100644 index 991aab1293..775886ba71 --- a/third-party/libvpx/build-libvpx-bazel.sh +++ b/third-party/libvpx/build-libvpx-bazel.sh @@ -1,4 +1,4 @@ -#! /bin/sh +#!/bin/bash set -e set -x @@ -33,18 +33,34 @@ SCRIPT_DIR="$SOURCE_DIR" LIBVPX_SOURCE_DIR="$SOURCE_DIR" -LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) - ORIG_PWD="$(pwd)" +EXTRA_CONFIGURE_ARGS="" + if [ "$ARCH" = "armv7" ]; then TARGETS="armv7-darwin-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) elif [ "$ARCH" = "arm64" ]; then TARGETS="arm64-darwin-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) elif [ "$ARCH" = "sim_arm64" ]; then TARGETS="arm64-iphonesimulator-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) +elif [ "$ARCH" = "macos_arm64" ]; then + TARGETS="arm64-darwin22-gcc" + LIPO=$(xcrun -sdk macosx -find lipo) + export SDKROOT=$(xcrun --sdk macosx --show-sdk-path) +elif [ "$ARCH" = "linux_arm64" ]; then + TARGETS="arm64-linux-gcc" + LIPO="cp" + EXTRA_CONFIGURE_ARGS="--enable-pic" +elif [ "$ARCH" = "linux_x86_64" ]; then + TARGETS="x86_64-linux-gcc" + LIPO="cp" + EXTRA_CONFIGURE_ARGS="--enable-pic" elif [ "$ARCH" = "x86_64" ]; then TARGETS="x86_64-iphonesimulator-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) else echo "Unsupported architecture $ARCH" exit 1 @@ -167,7 +183,11 @@ build_framework() { cp -p "${target_dist_dir}"/include/vpx/* "${HEADER_DIR}" # Build the fat library. - ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/VPX + if [ "$LIPO" = "cp" ]; then + cp ${lib_list} ${FRAMEWORK_DIR}/VPX + else + ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/VPX + fi # Create the vpx_config.h shim that allows usage of vpx_config.h from # within VPX.framework. diff --git a/third-party/libyuv/BUILD b/third-party/libyuv/BUILD index 14e2255c5e..dbabb4e81f 100644 --- a/third-party/libyuv/BUILD +++ b/third-party/libyuv/BUILD @@ -25,7 +25,9 @@ arch_specific_cflags = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags, "//build-system:ios_sim_arm64": common_flags + arm64_specific_flags, "@build_bazel_rules_apple//apple:ios_x86_64": common_flags + x86_64_specific_flags, - "//conditions:default": common_flags, + "//build-system:linux_arm64": common_flags + arm64_specific_flags, + "//build-system:linux_x86_64": common_flags + x86_64_specific_flags, + "//conditions:default": common_flags + arm64_specific_flags, }) cc_library( diff --git a/third-party/mozjpeg/BUILD b/third-party/mozjpeg/BUILD index b74836738d..2c31552b90 100644 --- a/third-party/mozjpeg/BUILD +++ b/third-party/mozjpeg/BUILD @@ -36,6 +36,8 @@ genrule( BUILD_ARCH="sim_arm64" elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/mozjpeg/build-mozjpeg-bazel.sh b/third-party/mozjpeg/build-mozjpeg-bazel.sh index c115e26c97..1ebdf75e3f 100755 --- a/third-party/mozjpeg/build-mozjpeg-bazel.sh +++ b/third-party/mozjpeg/build-mozjpeg-bazel.sh @@ -37,6 +37,22 @@ elif [ "$ARCH" = "sim_arm64" ]; then echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} -DPNG_SUPPORTED=FALSE -DENABLE_SHARED=FALSE -DWITH_JPEG8=1 -DBUILD=10000 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../mozjpeg + make +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-Wall -arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -funwind-tables" + + cd "$BUILD_DIR" + mkdir build + cd build + + touch toolchain.cmake + echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake + echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake + echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} -DPNG_SUPPORTED=FALSE -DENABLE_SHARED=FALSE -DWITH_JPEG8=1 -DBUILD=10000 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../mozjpeg make else diff --git a/third-party/ogg/BUILD b/third-party/ogg/BUILD index 1da3eaa02d..5c11a1409b 100644 --- a/third-party/ogg/BUILD +++ b/third-party/ogg/BUILD @@ -1,8 +1,6 @@ - -objc_library( + +cc_library( name = "ogg", - enable_modules = True, - module_name = "ogg", srcs = glob([ "Sources/*.c", "Sources/*.h", diff --git a/third-party/ogg/include/ogg/config_types.h b/third-party/ogg/include/ogg/config_types.h new file mode 100644 index 0000000000..de228d380f --- /dev/null +++ b/third-party/ogg/include/ogg/config_types.h @@ -0,0 +1,15 @@ +#ifndef __CONFIG_TYPES_H__ +#define __CONFIG_TYPES_H__ + +/* Generated for Linux builds */ + +#include + +typedef int16_t ogg_int16_t; +typedef uint16_t ogg_uint16_t; +typedef int32_t ogg_int32_t; +typedef uint32_t ogg_uint32_t; +typedef int64_t ogg_int64_t; +typedef uint64_t ogg_uint64_t; + +#endif diff --git a/third-party/openh264/BUILD b/third-party/openh264/BUILD index cb340b6fb7..11f3b912e1 100644 --- a/third-party/openh264/BUILD +++ b/third-party/openh264/BUILD @@ -57,18 +57,21 @@ arch_specific_sources = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_sources, "//build-system:ios_sim_arm64": arm64_specific_sources, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": arm64_specific_sources, }) arch_specific_copts = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_copts, "//build-system:ios_sim_arm64": arm64_specific_copts, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": arm64_specific_copts, }) arch_specific_textual_hdrs = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_textual_hdrs, "//build-system:ios_sim_arm64": arm64_specific_textual_hdrs, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": arm64_specific_textual_hdrs, }) all_sources = arch_specific_sources + [ diff --git a/third-party/opus/BUILD b/third-party/opus/BUILD index 873cdda53d..d80e343284 100644 --- a/third-party/opus/BUILD +++ b/third-party/opus/BUILD @@ -33,6 +33,8 @@ genrule( BUILD_ARCH="linux_x86_64" elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then BUILD_ARCH="linux_arm64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -81,10 +83,8 @@ cc_library( ], ) -objc_library( +cc_library( name = "opus", - module_name = "opus", - enable_modules = True, hdrs = [":Public/opus/" + x for x in headers], includes = [ "Public", diff --git a/third-party/opus/build-opus-bazel.sh b/third-party/opus/build-opus-bazel.sh index bb79c9efc6..74e6692762 100755 --- a/third-party/opus/build-opus-bazel.sh +++ b/third-party/opus/build-opus-bazel.sh @@ -54,6 +54,10 @@ elif [ "${ARCH}" == "sim_arm64" ]; then PLATFORM="iphonesimulator" EXTRA_CFLAGS="-arch arm64 --target=arm64-apple-ios$MINIOSVERSION-simulator" EXTRA_CONFIG="--host=arm-apple-darwin20" +elif [ "${ARCH}" == "macos_arm64" ]; then + PLATFORM="macosx" + EXTRA_CFLAGS="-arch arm64 --target=arm64-apple-macosx14.0" + EXTRA_CONFIG="--host=arm-apple-darwin20" else PLATFORM="iphoneos" EXTRA_CFLAGS="-arch ${ARCH}" diff --git a/third-party/opusfile/BUILD b/third-party/opusfile/BUILD index 0e62d353df..f28b1d6e46 100644 --- a/third-party/opusfile/BUILD +++ b/third-party/opusfile/BUILD @@ -1,8 +1,6 @@ - -objc_library( + +cc_library( name = "opusfile", - enable_modules = True, - module_name = "opusfile", srcs = glob([ "Sources/*.c", "Sources/*.h", diff --git a/third-party/rnnoise/BUILD b/third-party/rnnoise/BUILD index e1066c862b..d260114645 100644 --- a/third-party/rnnoise/BUILD +++ b/third-party/rnnoise/BUILD @@ -24,11 +24,9 @@ replace_symbol_list = [ "pitch_search", "remove_doubling", ] - -objc_library( + +cc_library( name = "rnnoise", - enable_modules = True, - module_name = "rnnoise", srcs = glob([ "Sources/*.c", "Sources/*.h", diff --git a/third-party/td/BUILD b/third-party/td/BUILD index 365f56ee82..f84f29c97b 100644 --- a/third-party/td/BUILD +++ b/third-party/td/BUILD @@ -38,6 +38,8 @@ genrule( BUILD_ARCH="arm64" elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then BUILD_ARCH="sim_arm64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/td/build-td-bazel.sh b/third-party/td/build-td-bazel.sh index 6ccc056e74..fc6f43b156 100755 --- a/third-party/td/build-td-bazel.sh +++ b/third-party/td/build-td-bazel.sh @@ -34,6 +34,10 @@ elif [ "$ARCH" = "sim_arm64" ]; then IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform" IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk) export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0 -w" +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -w" else echo "Unsupported architecture $ARCH" exit 1 diff --git a/third-party/webp/BUILD b/third-party/webp/BUILD index 82b7278c8e..2541873587 100644 --- a/third-party/webp/BUILD +++ b/third-party/webp/BUILD @@ -35,6 +35,8 @@ genrule( BUILD_ARCH="sim_arm64" elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/webp/build-webp-bazel.sh b/third-party/webp/build-webp-bazel.sh index 7a3d123187..dcbb665938 100755 --- a/third-party/webp/build-webp-bazel.sh +++ b/third-party/webp/build-webp-bazel.sh @@ -41,6 +41,22 @@ elif [ "$ARCH" = "sim_arm64" ]; then echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $COMMON_ARGS ../libwebp + make +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-Wall -arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -funwind-tables" + + cd "$BUILD_DIR" + mkdir build + cd build + + touch toolchain.cmake + echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake + echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake + echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $COMMON_ARGS ../libwebp make elif [ "$ARCH" = "x86_64" ]; then diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index 37837b1606..954e853949 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -15,7 +15,6 @@ optimization_flags = select({ }) webrtc_objcpp_sources = [ - "rtc_base/system/cocoa_threading.mm", ] webrtc_headers = [ @@ -2767,46 +2766,58 @@ arm64_specific_sources = ["webrtc/" + path for path in [ arch_specific_sources = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_arm_specific_sources + arm64_specific_sources, "//build-system:ios_sim_arm64": common_arm_specific_sources + arm64_specific_sources, + "//conditions:default": common_arm_specific_sources + arm64_specific_sources, }) -common_flags = [ - "-DWEBRTC_IOS", - "-DWEBRTC_MAC", +platform_shared_flags = [ "-DWEBRTC_POSIX", "-DHAVE_WEBRTC_VIDEO", "-DRTC_ENABLE_VP9", "-DRTC_ENABLE_H265", "-DWEBRTC_USE_H264", + "-DWEBRTC_USE_H264_DECODER", "-DHAVE_SCTP", "-DWEBRTC_HAVE_DCSCTP", "-DWEBRTC_HAVE_SCTP", "-DWEBRTC_NS_FLOAT", "-DRTC_DISABLE_TRACE_EVENTS", - #"-DWEBRTC_OPUS_SUPPORT_120MS_PTIME=1", "-DWEBRTC_APM_DEBUG_DUMP=0", "-DBWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0", "-DABSL_ALLOCATOR_NOTHROW=1", "-DDYNAMIC_ANNOTATIONS_ENABLED=0", - #"-DNS_BLOCK_ASSERTIONS=1", "-DWEBRTC_ENABLE_PROTOBUF=0", - "-DWEBRTC_ENABLE_AVX2", "-DWEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0", - "-Wno-shorten-64-to-32", - "-Wno-macro-redefined", - "-D__APPLE__", "-DWEBRTC_OPUS_USE_CODEC_PLC", "-DWEBRTC_OPUS_SUPPORT_DRED", ] +apple_specific_flags = [ + "-DWEBRTC_IOS", + "-DWEBRTC_MAC", + "-D__APPLE__", + "-D__Userspace_os_Darwin", + "-DWEBRTC_ENABLE_AVX2", + "-Wno-shorten-64-to-32", + "-Wno-macro-redefined", +] + +linux_specific_flags = [ + "-DWEBRTC_LINUX", + "-D__Userspace_os_Linux", +] + arm64_specific_flags = [ "-DWEBRTC_ARCH_ARM64", "-DWEBRTC_HAS_NEON", "-DLIBYUV_NEON", ] +# Flatten platform + arch flags into a single select to avoid nested selects arch_specific_cflags = select({ - "@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags, - "//build-system:ios_sim_arm64": common_flags + arm64_specific_flags, + "@build_bazel_rules_apple//apple:ios_arm64": platform_shared_flags + apple_specific_flags + arm64_specific_flags, + "//build-system:ios_sim_arm64": platform_shared_flags + apple_specific_flags + arm64_specific_flags, + "@platforms//os:linux": platform_shared_flags + linux_specific_flags + arm64_specific_flags, + "//conditions:default": platform_shared_flags + apple_specific_flags + arm64_specific_flags, }) dcsctp_sources = [ "webrtc/net/dcsctp/" + path for path in [ @@ -2981,11 +2992,44 @@ fft4g_sources = [ "fft4g/fft4g.cc", ] -raw_combined_sources = webrtc_headers + webrtc_sources + webrtc_objcpp_sources -combined_sources = [ - "webrtc/" + path for path in raw_combined_sources +raw_combined_cpp_sources = webrtc_headers + webrtc_sources +raw_combined_objcpp_sources = webrtc_objcpp_sources + +# Platform-specific source files: GCD task queue on Apple, stdlib on Linux +apple_only_cpp_sources = [ + "webrtc/rtc_base/task_queue_gcd.cc", + "webrtc/rtc_base/task_queue_gcd.h", + "webrtc/api/task_queue/default_task_queue_factory_gcd.cc", + "webrtc/rtc_base/mac_ifaddrs_converter.cc", +] + +linux_only_cpp_sources = [ + "webrtc/rtc_base/task_queue_stdlib.cc", + "webrtc/rtc_base/task_queue_stdlib.h", + "webrtc/api/task_queue/default_task_queue_factory_stdlib.cc", +] + +# Files excluded from common sources because they are platform-specific +platform_specific_excludes = [ + "rtc_base/task_queue_gcd.cc", "rtc_base/task_queue_gcd.h", + "rtc_base/task_queue_stdlib.cc", "rtc_base/task_queue_stdlib.h", + "rtc_base/mac_ifaddrs_converter.cc", + "api/task_queue/default_task_queue_factory_gcd.cc", + "api/task_queue/default_task_queue_factory_stdlib.cc", + "api/task_queue/default_task_queue_factory_libevent.cc", + "api/task_queue/default_task_queue_factory_win.cc", + "api/task_queue/default_task_queue_factory_stdlib_or_libevent_experiment.cc", +] + +combined_cpp_sources = [ + "webrtc/" + path for path in raw_combined_cpp_sources + if path not in platform_specific_excludes ] + arch_specific_sources + fft4g_sources + dcsctp_sources +combined_objcpp_sources = [ + "webrtc/" + path for path in raw_combined_objcpp_sources +] + genrule( name = "generate_field_trials_header", srcs = [ @@ -3118,13 +3162,15 @@ objc_library( visibility = ["//visibility:public"], ) -objc_library( +cc_library( name = "webrtc", - enable_modules = True, - module_name = "webrtc", - srcs = combined_sources, + srcs = combined_cpp_sources + select({ + "@platforms//os:linux": linux_only_cpp_sources, + "//conditions:default": combined_objcpp_sources + apple_only_cpp_sources, + }), copts = [ "-w", + "-include", "stdint.h", "-Ithird-party/webrtc/libsrtp/third_party/libsrtp/include", "-Ithird-party/webrtc/libsrtp/third_party/libsrtp/crypto/include", "-Ithird-party/webrtc/libsrtp", @@ -3140,7 +3186,6 @@ objc_library( "-DSCTP_SIMPLE_ALLOCATOR", "-DSCTP_PROCESS_LEVEL_LOCKS", "-D__Userspace__", - "-D__Userspace_os_Darwin", "-DPACKAGE_VERSION=\\\"\\\"", "-DHAVE_SCTP", "-DWEBRTC_HAVE_DCSCTP", @@ -3168,20 +3213,49 @@ objc_library( "//third-party/webrtc/pffft", "//third-party/webrtc/libsrtp", "//third-party/webrtc/absl", + ] + select({ + "@platforms//os:linux": [], + "//conditions:default": [":webrtc_platform_helpers"], + }), + linkopts = select({ + "@platforms//os:linux": ["-lpthread", "-lm"], + "//conditions:default": [ + "-framework AVFoundation", + "-framework AudioToolbox", + "-framework VideoToolbox", + "-framework CoreMedia", + "-framework CoreVideo", + "-framework CoreGraphics", + "-framework QuartzCore", + "-framework AppKit", + "-weak_framework Network", + "-weak_framework Metal", + ], + }), + visibility = ["//visibility:public"], +) + +# Minimal helpers needed by tgcalls C++ code on non-iOS platforms. +# Separate from webrtc_objc to avoid pulling in iOS SDK dependencies. +objc_library( + name = "webrtc_platform_helpers", + srcs = [ + "webrtc/rtc_base/system/gcd_helpers.m", + "webrtc/rtc_base/system/cocoa_threading.mm", ], - sdk_frameworks = [ - "AVFoundation", - "AudioToolbox", - "VideoToolbox", - "UIKit", - "CoreMedia", - "CoreVideo", - "CoreGraphics", - "QuartzCore", + hdrs = [ + "webrtc/rtc_base/system/gcd_helpers.h", + "webrtc/rtc_base/system/cocoa_threading.h", ], - weak_sdk_frameworks = [ - "Network", - "Metal", + copts = [ + "-w", + "-Ithird-party/webrtc/webrtc/", + "-Ithird-party/webrtc/absl", + "-DWEBRTC_MAC", + "-DWEBRTC_IOS", + ], + deps = [ + "//third-party/webrtc/absl", ], visibility = ["//visibility:public"], ) diff --git a/third-party/webrtc/absl/absl/base/attributes.h b/third-party/webrtc/absl/absl/base/attributes.h index 38086a82e4..6f61871041 100644 --- a/third-party/webrtc/absl/absl/base/attributes.h +++ b/third-party/webrtc/absl/absl/base/attributes.h @@ -808,13 +808,8 @@ // // See also the upstream documentation: // https://clang.llvm.org/docs/AttributeReference.html#lifetimebound -#if ABSL_HAVE_CPP_ATTRIBUTE(clang::lifetimebound) -#define ABSL_ATTRIBUTE_LIFETIME_BOUND [[clang::lifetimebound]] -#elif ABSL_HAVE_ATTRIBUTE(lifetimebound) -#define ABSL_ATTRIBUTE_LIFETIME_BOUND __attribute__((lifetimebound)) -#else +// Disabled: newer clang rejects lifetimebound on void-returning functions #define ABSL_ATTRIBUTE_LIFETIME_BOUND -#endif // ABSL_ATTRIBUTE_TRIVIAL_ABI // Indicates that a type is "trivially relocatable" -- meaning it can be diff --git a/third-party/yasm/BUILD b/third-party/yasm/BUILD index ecfef261b0..1d0e523fba 100644 --- a/third-party/yasm/BUILD +++ b/third-party/yasm/BUILD @@ -16,7 +16,7 @@ genrule( cmd_bash = """ set -x - core_count=`PATH="$$PATH:/usr/sbin" sysctl -n hw.logicalcpu` + core_count=$$(nproc 2>/dev/null || PATH="$$PATH:/usr/sbin" sysctl -n hw.logicalcpu 2>/dev/null || echo 4) BUILD_DIR="$(RULEDIR)/build" rm -rf "$$BUILD_DIR" mkdir -p "$$BUILD_DIR" @@ -25,14 +25,24 @@ set -x rm -rf "$$CMAKE_DIR" mkdir -p "$$CMAKE_DIR" tar -xf "$(location @cmake_tar_gz//file)" -C "$$CMAKE_DIR" - + + # Find cmake: try system cmake first, then downloaded macOS cmake + if command -v cmake >/dev/null 2>&1; then + CMAKE_BIN="cmake" + elif [ -d "$$CMAKE_DIR/cmake-4.1.2-macos-universal/CMake.app/Contents/bin" ]; then + CMAKE_BIN="$$CMAKE_DIR/cmake-4.1.2-macos-universal/CMake.app/Contents/bin/cmake" + else + echo "cmake not found" + exit 1 + fi + SOURCE_PATH="third-party/yasm/yasm-1.3.0" cp -R "$$SOURCE_PATH" "$$BUILD_DIR/" pushd "$$BUILD_DIR/yasm-1.3.0" mkdir build cd build - PATH="$$CMAKE_DIR/cmake-4.1.2-macos-universal/CMake.app/Contents/bin:$$PATH" cmake .. -DYASM_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DPYTHON_EXECUTABLE="$$(which python3)" + "$$CMAKE_BIN" .. -DYASM_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DPYTHON_EXECUTABLE="$$(which python3)" make -j $$core_count popd