# Conflicts:
#	submodules/TgVoipWebrtc/tgcalls
This commit is contained in:
isaac 2026-04-30 22:24:28 +02:00
commit ebe43c72c6
44 changed files with 1049 additions and 212 deletions

View file

@ -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

View file

@ -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")

View file

@ -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",
])

View file

@ -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

View file

@ -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<TelegramMediaPoll?, RequestMessageSelectPollOptionError> {
@ -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<TelegramMediaPoll?, RequestMessageSelectPollOptionError> 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<Api.Updates?, NoError> in
return .single(nil)

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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 {

View file

@ -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()

View file

@ -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

View file

@ -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)

View file

@ -2581,7 +2581,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
openStarsPurchase: { _ in
},
openRankInfo: { _, _, _ in
}, openSetPeerAvatar: {
},
openSetPeerAvatar: {
},
displayPollRestrictedToast: { _ in
},
automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(),

View file

@ -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",
],
)

View file

@ -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 <cstdint>` 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.013.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.

View file

@ -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

View file

@ -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",
]
],
)

View file

@ -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"

View file

@ -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

View file

@ -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), \

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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",

28
third-party/libvpx/build-libvpx-bazel.sh vendored Executable file → Normal file
View file

@ -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.

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -1,8 +1,6 @@
objc_library(
cc_library(
name = "ogg",
enable_modules = True,
module_name = "ogg",
srcs = glob([
"Sources/*.c",
"Sources/*.h",

View file

@ -0,0 +1,15 @@
#ifndef __CONFIG_TYPES_H__
#define __CONFIG_TYPES_H__
/* Generated for Linux builds */
#include <stdint.h>
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

View file

@ -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 + [

View file

@ -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",

View file

@ -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}"

View file

@ -1,8 +1,6 @@
objc_library(
cc_library(
name = "opusfile",
enable_modules = True,
module_name = "opusfile",
srcs = glob([
"Sources/*.c",
"Sources/*.h",

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"],
)

View file

@ -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

View file

@ -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