增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

View File

@@ -0,0 +1,15 @@
//
// NSObject+Extension.h
// TUIVideoSeat
//
// Created by WesleyLei on 2022/9/23.
// Copyright © 2022 Tencent. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (Extension)
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,18 @@
//
// NSObject+Extension.m
// TUIVideoSeat
//
// Created by WesleyLei on 2022/9/23.
// Copyright © 2022 Tencent. All rights reserved.
//
#import "NSObject+Extension.h"
@implementation NSObject (Extension)
+ (void)load {
#pragma GCC diagnostic ignored "-Wundeclared-selector"
if ([self respondsToSelector:@selector(swiftLoad)]) {
[self performSelector:@selector(swiftLoad)];
}
}
@end

View File

@@ -0,0 +1,42 @@
//
// SwiftLoad.swift
// TUICallKit
//
// Created by WesleyLei on 2022/9/23.
// Copyright © 2022 Tencent. All rights reserved.
//
import Foundation
import TUICore
import TUICallEngine
extension NSObject {
@objc class func swiftLoad() {
let _ = TUICallKit.createInstance()
let _ = TUICallEngine.createInstance()
TUICore.registerService(TUICore_TUICallingService, object: TUICallKitService.instance)
TUICore.registerService(TUICore_TUIAudioMessageRecordService, object: TUIAudioMessageRecordService.instance)
TUICore.registerExtension(TUICore_TUIChatExtension_NavigationMoreItem_MinimalistExtensionID,
object: TUICallKitExtension.instance)
TUICore.registerExtension(TUICore_TUIChatExtension_InputViewMoreItem_ClassicExtensionID,
object: TUICallKitExtension.instance)
TUICore.registerExtension(TUICore_TUIContactExtension_FriendProfileActionMenu_ClassicExtensionID,
object: TUICallKitExtension.instance)
TUICore.registerExtension(TUICore_TUIContactExtension_FriendProfileActionMenu_MinimalistExtensionID,
object: TUICallKitExtension.instance)
TUICore.registerExtension(TUICore_TUIContactExtension_GroupInfoCardActionMenu_MinimalistExtensionID,
object: TUICallKitExtension.instance)
TUICore.registerExtension(TUICore_TUIChatExtension_ChatViewTopArea_ClassicExtensionID,
object: TUICallKitExtension.instance)
TUICore.registerExtension(TUICore_TUIChatExtension_ChatViewTopArea_MinimalistExtensionID,
object: TUICallKitExtension.instance)
TUICore.registerObjectFactory(TUICore_TUICallingObjectFactory, objectFactory: TUICallKitObjectFactory.instance)
TUIThemeManager.share().registerThemeResourcePath(TUICoreDefineConvert.getTUICallKitThemePath(), for: .calling)
}
}

View File

@@ -0,0 +1,275 @@
//
// TUIAudioMessageRecordService.swift
// TUICallKit
//
// Created by vincepzhang on 2023/8/16.
//
import Foundation
import AVFAudio
import TUICore
import TUICallEngine
#if canImport(TXLiteAVSDK_TRTC)
import TXLiteAVSDK_TRTC
#elseif canImport(TXLiteAVSDK_Professional)
import TXLiteAVSDK_Professional
#endif
class TUIAudioRecordInfo {
var path: String = ""
var sdkAppId: Int = 0
var signature: String = ""
}
class TUIAudioMessageRecordService: NSObject, TUIServiceProtocol, TUINotificationProtocol, TRTCCloudDelegate, TUICallObserver {
static let instance = TUIAudioMessageRecordService()
var audioRecordInfo: TUIAudioRecordInfo?
var category: AVAudioSession.Category?
var categoryOptions: AVAudioSession.CategoryOptions?
var callback: TUICallServiceResultCallback?
override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(loginSuccessNotification),
name: NSNotification.Name.TUILoginSuccess, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc
func loginSuccessNotification() {
TUICallEngine.createInstance().addObserver(self)
}
}
// MARK: TUIServiceProtocol
extension TUIAudioMessageRecordService {
func onCall(_ method: String, param: [AnyHashable : Any]?) -> Any? {
return nil
}
func onCall(_ method: String, param: [AnyHashable : Any]?, resultCallback: @escaping TUICallServiceResultCallback) -> Any? {
callback = resultCallback
if method == TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod {
guard let param = param else {
notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey,
errorCode: Int(TUICore_RecordAudioMessageNotifyError_InvalidParam), path: "")
return nil
}
if TUICallState.instance.selfUser.value.callStatus.value != TUICallStatus.none {
notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey,
errorCode: Int(TUICore_RecordAudioMessageNotifyError_StatusInCall), path: "")
return nil
}
if audioRecordInfo != nil {
notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey,
errorCode: Int(TUICore_RecordAudioMessageNotifyError_StatusIsAudioRecording), path: "")
return nil
}
requestRecordAuthorization { [weak self] granted in
guard let self = self else { return }
if granted {
if !self.requestAudioFocus() {
self.notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey,
errorCode: Int(TUICore_RecordAudioMessageNotifyError_RequestAudioFocusFailed), path: "")
} else {
self.audioRecordInfo = TUIAudioRecordInfo()
let pathKey = TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_PathKey
self.audioRecordInfo?.path = param[pathKey] as? String ?? ""
let sdkAppId = param[TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_SdkappidKey] as? NSNumber
if let sdkAppId = sdkAppId {
self.audioRecordInfo?.sdkAppId = sdkAppId.intValue
}
let signatureKey = TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_SignatureKey
self.audioRecordInfo?.signature = param[signatureKey] as? String ?? ""
TRTCCloud.sharedInstance().delegate = self
let audioVolumeEvaluateParams = TRTCAudioVolumeEvaluateParams()
audioVolumeEvaluateParams.interval = 500
TRTCCloud.sharedInstance().enableAudioVolumeEvaluation(true, with: audioVolumeEvaluateParams)
self.startRecordAudioMessage()
}
} else {
self.notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey,
errorCode: Int(TUICore_RecordAudioMessageNotifyError_MicPermissionRefused), path: "")
}
}
return nil
} else if method == TUICore_TUIAudioMessageRecordService_StopRecordAudioMessageMethod {
stopRecordAudioMessage()
}
return nil
}
func requestRecordAuthorization(response: @escaping (_ granted: Bool) -> Void) {
let session = AVAudioSession.sharedInstance()
let permission = session.recordPermission
if permission == .undetermined {
session.requestRecordPermission(response)
} else if permission == .granted {
response(true)
} else if permission == .denied {
response(false)
}
}
func startRecordAudioMessage() {
guard let audioRecordInfo = audioRecordInfo else { return }
let sdkAppIdKey = TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_SdkappidKey
let pathKey = TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_PathKey
let jsonParams: [String: Any] = ["api": "startRecordAudioMessage",
"params": ["key": audioRecordInfo.signature,
sdkAppIdKey: audioRecordInfo.sdkAppId,
pathKey: audioRecordInfo.path,] as [String : Any],]
guard let data = try? JSONSerialization.data(withJSONObject: jsonParams,
options: JSONSerialization.WritingOptions(rawValue: 0)) else { return }
guard let paramsString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as? String else { return }
TRTCCloud.sharedInstance().callExperimentalAPI(paramsString)
}
func stopRecordAudioMessage() {
guard let _ = audioRecordInfo else { return }
let jsonParams: [String: Any] = ["api": "stopRecordAudioMessage",
"params": [:] as [String : Any],]
guard let data = try? JSONSerialization.data(withJSONObject: jsonParams,
options: JSONSerialization.WritingOptions(rawValue: 0)) else { return }
guard let paramsString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as? String else { return }
TRTCCloud.sharedInstance().callExperimentalAPI(paramsString)
TRTCCloud.sharedInstance().enableAudioVolumeEvaluation(false, with: TRTCAudioVolumeEvaluateParams())
TRTCCloud.sharedInstance().stopLocalAudio()
audioRecordInfo = nil
let _ = abandonAudioFocus()
}
}
// MARK: TUICallObserver
extension TUIAudioMessageRecordService {
func onCallReceived(callerId: String, calleeIdList: [String], groupId: String?, callMediaType: TUICallMediaType, userData: String?) {
stopRecordAudioMessage()
}
}
// MARK: TRTCCloudDelegate
extension TUIAudioMessageRecordService {
func onError(_ errCode: TXLiteAVError, errMsg: String?, extInfo: [AnyHashable : Any]?) {
if errCode.rawValue == TUICore_RecordAudioMessageNotifyError_MicStartFail ||
errCode.rawValue == TUICore_RecordAudioMessageNotifyError_MicNotAuthorized ||
errCode.rawValue == TUICore_RecordAudioMessageNotifyError_MicSetParamFail ||
errCode.rawValue == TUICore_RecordAudioMessageNotifyError_MicOccupy {
notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey,
errorCode: Int(errCode.rawValue) , path: "")
}
}
func onLocalRecordBegin(_ errCode: Int, storagePath: String) {
if errCode == TUICore_RecordAudioMessageNotifyError_None {
TRTCCloud.sharedInstance().startLocalAudio(TRTCAudioQuality.speech)
}
let tempCode = convertErrorCode("onLocalRecordBegin", errorCode: errCode)
notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey,
errorCode: Int(tempCode), path: storagePath)
}
func onLocalRecordComplete(_ errCode: Int, storagePath: String) {
let tempCode = convertErrorCode("onLocalRecordComplete", errorCode: errCode)
notifyAudioMessageRecordEvent(method: TUICore_RecordAudioMessageNotify_StopRecordAudioMessageSubKey,
errorCode: Int(tempCode), path: storagePath)
}
func onUserVoiceVolume(_ userVolumes: [TRTCVolumeInfo], totalVolume: Int) {
for volumeInfo in userVolumes {
if volumeInfo.userId == nil || volumeInfo.userId?.isEmpty == true {
let param = [TUICore_RecordAudioMessageNotify_RecordAudioVoiceVolumeSubKey_VolumeKey: volumeInfo.volume,]
TUICore.notifyEvent(TUICore_RecordAudioMessageNotify,
subKey: TUICore_RecordAudioMessageNotify_RecordAudioVoiceVolumeSubKey,
object: nil, param: param)
break
}
}
}
}
// MARK: Private
extension TUIAudioMessageRecordService {
func requestAudioFocus() -> Bool {
let session = AVAudioSession.sharedInstance()
category = session.category
categoryOptions = session.categoryOptions
do {
try session.setCategory(.playAndRecord, options: .allowBluetooth)
try session.setActive(true)
return true
} catch {
return false
}
}
func abandonAudioFocus() -> Bool {
guard let category = category else { return false }
guard let categoryOptions = categoryOptions else { return false }
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(category, options: categoryOptions)
try session.setActive(false, options: .notifyOthersOnDeactivation)
return true
} catch {
return false
}
}
func notifyAudioMessageRecordEvent(method: String, errorCode: Int, path: String) {
let params: [String: Any] = ["method": method,
"errorCode": errorCode,
"path": path,]
guard let callback = callback else { return }
callback(errorCode, "", params)
}
func convertErrorCode(_ method: String?, errorCode: Int) -> Int32 {
var targetCode: Int32
switch errorCode {
case -1:
if let method = method, method == "onLocalRecordBegin" {
targetCode = TUICore_RecordAudioMessageNotifyError_RecordInitFailed
} else {
targetCode = TUICore_RecordAudioMessageNotifyError_RecordFailed
}
case -2:
targetCode = TUICore_RecordAudioMessageNotifyError_PathFormatNotSupport
case -3:
targetCode = TUICore_RecordAudioMessageNotifyError_NoMessageToRecord
case -4:
targetCode = TUICore_RecordAudioMessageNotifyError_SignatureError
case -5:
targetCode = TUICore_RecordAudioMessageNotifyError_SignatureExpired
default:
targetCode = TUICore_RecordAudioMessageNotifyError_None
}
return targetCode
}
}

View File

@@ -0,0 +1,307 @@
//
// TUICallKitExtension.swift
// TUICallKit
//
// Created by vincepzhang on 2023/8/14.
//
import Foundation
import TUICore
import TUICallEngine
class TUICallKitExtension: NSObject, TUIExtensionProtocol {
static let instance = TUICallKitExtension()
var joinGroupCallViewModel = JoinInGroupCallViewModel()
func launchCall(type: TUICallMediaType, groupID: String, pushVC: UINavigationController, isClassic: Bool) {
if !groupID.isEmpty {
var requestParam: [String: Any] = [:]
requestParam[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_GroupID] = groupID
requestParam[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Name] =
TUIGlobalization.getLocalizedString(forKey: "Make-a-call", bundle: TUIKitLocalizableBundle)
let viewControllerKey = isClassic ? TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Classic :
TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Minimalist
pushVC.push(viewControllerKey, param: requestParam) { [weak self] responseData in
guard let self = self else { return }
guard let modelList = responseData[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_ResultUserList]
as? [AnyObject] else { return }
let userIDs: [String] = modelList.compactMap { $0.userId }
self.startCall(groupID: groupID, userIDs: userIDs, callingType: type)
}
}
}
func startCall(groupID: String, userIDs: [String], callingType: TUICallMediaType) {
let selector = NSSelectorFromString("setOnlineUserOnly")
if TUICallEngine.createInstance().responds(to: selector) {
TUICallEngine.createInstance().perform(selector, with: 0)
}
if groupID.isEmpty {
guard let userID = userIDs.first else { return }
TUICallKit.createInstance().call(userId: userID, callMediaType: callingType)
} else {
TUICallKit.createInstance().groupCall(groupId: groupID, userIdList: userIDs, callMediaType: callingType)
}
}
func doResponseInputViewExtension(param: [String: Any], type: TUICallMediaType, isClassic: Bool) {
let userID = param[TUICore_TUIChatExtension_InputViewMoreItem_UserID] as? String ?? ""
let groupId = param[TUICore_TUIChatExtension_InputViewMoreItem_GroupID] as? String ?? ""
let pushVC = param[TUICore_TUIChatExtension_InputViewMoreItem_PushVC] as? UINavigationController
if !userID.isEmpty {
startCall(groupID: "", userIDs: [userID], callingType: type)
} else if !groupId.isEmpty {
guard let pushVC = pushVC else { return }
launchCall(type: type, groupID: groupId, pushVC: pushVC, isClassic: isClassic)
}
}
// MARK: TUIExtensionProtocol
func onRaiseExtension(_ extensionID: String, parentView: UIView, param: [AnyHashable : Any]?) -> Bool {
if extensionID.isEmpty {
return false
}
guard let groupId = param?[TUICore_TUIChatExtension_ChatViewTopArea_ChatID] as? String,
let isGroup = param?[TUICore_TUIChatExtension_ChatViewTopArea_IsGroup], isGroup as! String == "1" else {
return false
}
if extensionID == TUICore_TUIChatExtension_ChatViewTopArea_ClassicExtensionID ||
extensionID == TUICore_TUIChatExtension_ChatViewTopArea_MinimalistExtensionID {
let joinGroupCallView = JoinInGroupCallView()
joinGroupCallViewModel.setJoinGroupCallView(joinGroupCallView)
joinGroupCallViewModel.getGroupAttributes(groupId)
parentView.subviews.forEach {
if $0 is JoinInGroupCallView {
$0.removeFromSuperview()
}
}
parentView.addSubview(joinGroupCallView)
return true
}
return false
}
func onGetExtension(_ extensionID: String, param: [AnyHashable : Any]?) -> [TUIExtensionInfo]? {
if extensionID.isEmpty {
return nil
}
if extensionID == TUICore_TUIChatExtension_NavigationMoreItem_MinimalistExtensionID {
return getNavigationMoreItemExtensionForMinimalistChat(param: param)
} else if extensionID == TUICore_TUIChatExtension_InputViewMoreItem_ClassicExtensionID {
return getInputViewMoreItemExtensionForClassicChat(param: param)
} else if extensionID == TUICore_TUIContactExtension_FriendProfileActionMenu_ClassicExtensionID {
return getFriendProfileActionMenuExtensionForClassicContact(param: param)
} else if extensionID == TUICore_TUIContactExtension_FriendProfileActionMenu_MinimalistExtensionID {
return getFriendProfileActionMenuExtensionForMinimalistContact(param: param)
} else if extensionID == TUICore_TUIContactExtension_GroupInfoCardActionMenu_MinimalistExtensionID {
return getGroupInfoCardActionMenuExtensionForMinimalistGroup(param: param)
} else {
return nil
}
}
func getNavigationMoreItemExtensionForMinimalistChat(param: [AnyHashable : Any]?) -> [TUIExtensionInfo]? {
guard let param = param else { return nil }
let onClick: ([AnyHashable : Any]?, TUICallMediaType) -> Void = { [weak self] param, type in
guard let self = self else { return }
guard let param = param else { return }
let userID = param[TUICore_TUIChatExtension_NavigationMoreItem_UserID] as? String ?? ""
let groupID = param[TUICore_TUIChatExtension_NavigationMoreItem_GroupID] as? String ?? ""
let pushVC = param[TUICore_TUIChatExtension_NavigationMoreItem_PushVC] as? UINavigationController
if !userID.isEmpty {
self.startCall(groupID: "", userIDs: [userID], callingType: type)
} else if !groupID.isEmpty {
guard let pushVC = pushVC else { return }
self.launchCall(type: type, groupID: groupID, pushVC: pushVC, isClassic: false)
}
}
var result: [TUIExtensionInfo] = []
guard let filterVideoCall = param[TUICore_TUIChatExtension_NavigationMoreItem_FilterVideoCall] as? Bool else { return nil }
if !filterVideoCall {
let videoInfo = TUIExtensionInfo()
videoInfo.weight = 200
videoInfo.icon = TUICallKitCommon.getBundleImage(name: "video_call") ?? UIImage()
videoInfo.onClicked = { param in
onClick(param, .video)
}
result.append(videoInfo)
}
guard let filterAudioCall = param[TUICore_TUIChatExtension_NavigationMoreItem_FilterAudioCall] as? Bool else { return nil }
if !filterAudioCall {
let audioInfo = TUIExtensionInfo()
audioInfo.weight = 100
audioInfo.icon = TUICallKitCommon.getBundleImage(name: "audio_call") ?? UIImage()
audioInfo.onClicked = { param in
onClick(param, .audio)
}
result.append(audioInfo)
}
return result
}
func getInputViewMoreItemExtensionForClassicChat(param: [AnyHashable : Any]?) -> [TUIExtensionInfo]? {
guard let param = param else { return nil }
var result: [TUIExtensionInfo] = []
guard let filterVideoCall = param[TUICore_TUIChatExtension_InputViewMoreItem_FilterVideoCall] as? Bool else { return nil }
if !filterVideoCall {
let videoInfo = TUIExtensionInfo()
videoInfo.weight = 600
videoInfo.text = TUICoreDefineConvert.getTUIKitLocalizableString(key: "TUIKitMoreVideoCall")
videoInfo.icon = TUICoreDefineConvert.getTUICoreBundleThemeImage(imageKey: "service_more_video_call_img",
defaultImageName: "more_video_call")
videoInfo.onClicked = { param in
guard let param = param as? [String: Any] else { return }
self.doResponseInputViewExtension(param: param, type: .video, isClassic: true)
}
result.append(videoInfo)
}
guard let filterAudioCall = param[TUICore_TUIChatExtension_InputViewMoreItem_FilterAudioCall] as? Bool else { return nil }
if !filterAudioCall {
let audioInfo = TUIExtensionInfo()
audioInfo.weight = 500
audioInfo.text = TUICoreDefineConvert.getTUIKitLocalizableString(key: "TUIKitMoreVoiceCall")
audioInfo.icon = TUICoreDefineConvert.getTUICoreBundleThemeImage(imageKey: "service_more_voice_call_img",
defaultImageName: "more_voice_call")
audioInfo.onClicked = { param in
guard let param = param as? [String: Any] else { return }
self.doResponseInputViewExtension(param: param, type: .audio, isClassic: true)
}
result.append(audioInfo)
}
return result
}
func getFriendProfileActionMenuExtensionForClassicContact(param: [AnyHashable : Any]?) -> [TUIExtensionInfo]? {
guard let param = param else { return nil }
var result: [TUIExtensionInfo] = []
guard let filterVideoCall = param[TUICore_TUIContactExtension_FriendProfileActionMenu_FilterVideoCall] as? Bool else { return nil }
if !filterVideoCall {
let videoInfo = TUIExtensionInfo()
videoInfo.weight = 200
videoInfo.text = TUICoreDefineConvert.getTUIKitLocalizableString(key: "TUIKitMoreVideoCall")
videoInfo.onClicked = {[weak self] param in
guard let self = self else { return }
guard let userID = param[TUICore_TUIContactExtension_FriendProfileActionMenu_UserID] as? String else { return }
if !userID.isEmpty {
self.startCall(groupID: "", userIDs: [userID], callingType: .video)
}
}
result.append(videoInfo)
}
guard let filterAudioCall = param[TUICore_TUIContactExtension_FriendProfileActionMenu_FilterAudioCall] as? Bool else { return nil }
if !filterAudioCall {
let audioInfo = TUIExtensionInfo()
audioInfo.weight = 100
audioInfo.text = TUICoreDefineConvert.getTUIKitLocalizableString(key: "TUIKitMoreVoiceCall")
audioInfo.onClicked = {[weak self] param in
guard let self = self else { return }
guard let userID = param[TUICore_TUIContactExtension_FriendProfileActionMenu_UserID] as? String else { return }
if !userID.isEmpty {
self.startCall(groupID: "", userIDs: [userID], callingType: .audio)
}
}
result.append(audioInfo)
}
return result
}
func getFriendProfileActionMenuExtensionForMinimalistContact(param: [AnyHashable : Any]?) -> [TUIExtensionInfo]? {
guard let param = param else { return nil }
var result: [TUIExtensionInfo] = []
guard let filterVideoCall = param[TUICore_TUIContactExtension_FriendProfileActionMenu_FilterVideoCall] as? Bool else { return nil }
if !filterVideoCall {
let videoInfo = TUIExtensionInfo()
videoInfo.weight = 100
videoInfo.icon = TUICoreDefineConvert.getTUIDynamicImage(imageKey: "", module: TUIThemeModule.contact_Minimalist,
defaultImage: UIImage(named: TUICoreDefineConvert.getTUIContactImagePathMinimalist(imageName: "contact_info_video")) ?? UIImage())
videoInfo.text = TUICoreDefineConvert.getTIMCommonLocalizableString(key: "TUIKitVideo")
videoInfo.onClicked = {[weak self] param in
guard let self = self else { return }
guard let userID = param[TUICore_TUIContactExtension_FriendProfileActionMenu_UserID] as? String else { return }
if !userID.isEmpty {
self.startCall(groupID: "", userIDs: [userID], callingType: .video)
}
}
result.append(videoInfo)
}
guard let filterAudioCall = param[TUICore_TUIContactExtension_FriendProfileActionMenu_FilterAudioCall] as? Bool else { return nil }
if !filterAudioCall {
let audioInfo = TUIExtensionInfo()
audioInfo.weight = 200
audioInfo.icon = TUICoreDefineConvert.getTUIDynamicImage(imageKey: "", module: TUIThemeModule.contact_Minimalist,
defaultImage: UIImage(named: TUICoreDefineConvert.getTUIContactImagePathMinimalist(imageName: "contact_info_audio")) ?? UIImage())
audioInfo.text = TUICoreDefineConvert.getTIMCommonLocalizableString(key: "TUIKitAudio")
audioInfo.onClicked = {[weak self] param in
guard let self = self else { return }
guard let userID = param[TUICore_TUIContactExtension_FriendProfileActionMenu_UserID] as? String else { return }
if !userID.isEmpty {
self.startCall(groupID: "", userIDs: [userID], callingType: .audio)
}
}
result.append(audioInfo)
}
return result
}
func getGroupInfoCardActionMenuExtensionForMinimalistGroup(param: [AnyHashable : Any]?) -> [TUIExtensionInfo]? {
guard let param = param else { return nil }
var result: [TUIExtensionInfo] = []
guard let filterVideoCall = param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_FilterVideoCall] as? Bool else { return nil }
if !filterVideoCall {
let videoInfo = TUIExtensionInfo()
videoInfo.weight = 100
videoInfo.icon = TUICoreDefineConvert.getTUIDynamicImage(imageKey: "", module: TUIThemeModule.contact_Minimalist,
defaultImage: UIImage(named: TUICoreDefineConvert.getTUIContactImagePathMinimalist(imageName: "contact_info_video")) ?? UIImage())
videoInfo.text = TUICoreDefineConvert.getTIMCommonLocalizableString(key: "TUIKitVideo")
videoInfo.onClicked = {[weak self] param in
guard let self = self else { return }
guard let pushVC = param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_PushVC] as? UINavigationController else { return }
guard let groupID = param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_GroupID] as? String else { return }
if !groupID.isEmpty {
self.launchCall(type: .video, groupID: groupID, pushVC: pushVC, isClassic: false)
}
}
result.append(videoInfo)
}
guard let filterAudioCall = param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_FilterAudioCall] as? Bool else { return nil }
if !filterAudioCall {
let audioInfo = TUIExtensionInfo()
audioInfo.weight = 200
audioInfo.icon = TUICoreDefineConvert.getTUIDynamicImage(imageKey: "", module: TUIThemeModule.contact_Minimalist,
defaultImage: UIImage(named: TUICoreDefineConvert.getTUIContactImagePathMinimalist(imageName: "contact_info_audio")) ?? UIImage())
audioInfo.text = TUICoreDefineConvert.getTIMCommonLocalizableString(key: "TUIKitAudio")
audioInfo.onClicked = {[weak self] param in
guard let self = self else { return }
guard let pushVC = param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_PushVC] as? UINavigationController else { return }
guard let groupID = param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_GroupID] as? String else { return }
if !groupID.isEmpty {
self.launchCall(type: .audio, groupID: groupID, pushVC: pushVC, isClassic: false)
}
}
result.append(audioInfo)
}
return result
}
}

View File

@@ -0,0 +1,35 @@
//
// TUICallKitObjectFactory.swift
// TUICallKit
//
// Created by vincepzhang on 2023/8/15.
//
import Foundation
import TUICore
class TUICallKitObjectFactory: NSObject, TUIObjectProtocol {
static let instance = TUICallKitObjectFactory()
func createRecordCallsVC(uiStyle: String) -> UIViewController {
var recordCallsUIStyle: TUICallKitRecordCallsUIStyle = .minimalist
if uiStyle == TUICore_TUICallingObjectFactory_RecordCallsVC_UIStyle_Classic {
recordCallsUIStyle = .classic
}
let vc = TUICallRecordCallsViewController(recordCallsUIStyle: recordCallsUIStyle)
return vc
}
// MARK: TUIObjectProtocol
func onCreateObject(_ method: String, param: [AnyHashable : Any]?) -> Any? {
if method == TUICore_TUICallingObjectFactory_RecordCallsVC {
guard let uiStyle = param?[TUICore_TUICallingObjectFactory_RecordCallsVC_UIStyle] as? String else { return nil }
return createRecordCallsVC(uiStyle: uiStyle)
}
return nil
}
}

View File

@@ -0,0 +1,93 @@
//
// TUICallKitService.swift
// TUICallKit
//
// Created by vincepzhang on 2023/4/20.
//
import Foundation
import TUICore
import TUICallEngine
import UIKit
class TUICallKitService: NSObject, TUIServiceProtocol {
static let instance = TUICallKitService()
func startCall(groupID: String, userIDs: [String], callingType: TUICallMediaType) {
let selector = NSSelectorFromString("setOnlineUserOnly")
if TUICallEngine.createInstance().responds(to: selector) {
TUICallEngine.createInstance().perform(selector, with: 0)
}
if groupID.isEmpty {
guard let userID = userIDs.first else {
return
}
TUICallKit.createInstance().call(userId: userID, callMediaType: callingType)
} else {
TUICallKit.createInstance().groupCall(groupId: groupID, userIdList: userIDs, callMediaType: callingType)
}
}
}
// MARK: TUIServiceProtocol
extension TUICallKitService {
func onCall(_ method: String, param: [AnyHashable : Any]?) -> Any? {
guard let param = param else {
return nil
}
if method == TUICore_TUICallingService_EnableFloatWindowMethod {
guard let enableFloatWindow = param[TUICore_TUICallingService_EnableFloatWindowMethod_EnableFloatWindow] as? Bool else {
return nil
}
TUICallKit.createInstance().enableFloatWindow(enable: enableFloatWindow)
} else if method == TUICore_TUICallingService_EnableIncomingBannerMethod {
guard let enableIncomingBanner = param[TUICore_TUICallingService_EnableIncomingBannerMethod_EnableIncomingBanner] as? Bool else {
return nil
}
TUICallKit.createInstance().enableIncomingBanner(enable: enableIncomingBanner)
} else if method == TUICore_TUICallingService_EnableVirtualBackgroundForCallMethod {
guard let enableVirtualBackground = param[TUICore_TUICallingService_EnableVirtualBackgroundForCallMethod_EnableVirtualBackgroundForCall] as? Bool else {
return nil
}
TUICallKit.createInstance().enableVirtualBackground(enable: enableVirtualBackground)
} else if method == TUICore_TUICallingService_ShowCallingViewMethod {
guard let userIDs = param[TUICore_TUICallingService_ShowCallingViewMethod_UserIDsKey] as? [ String],
let mediaTypeIndex = param[TUICore_TUICallingService_ShowCallingViewMethod_CallTypeKey] as? String else {
return nil
}
var mediaType: TUICallMediaType = .unknown
if mediaTypeIndex == "0" {
mediaType = .audio
} else if mediaTypeIndex == "1" {
mediaType = .video
}
let groupId = param[TUICore_TUICallingService_ShowCallingViewMethod_GroupIDKey] as? String ?? ""
startCall(groupID: groupId, userIDs: userIDs, callingType: mediaType)
} else if method == TUICore_TUICallingService_ReceivePushCallingMethod {
guard let signalingInfo = param[TUICore_TUICallingService_ShowCallingViewMethod_SignalingInfo] as? V2TIMSignalingInfo else {
return nil
}
let selector = NSSelectorFromString("onReceiveGroupCallAPNs:")
if TUICallEngine.createInstance().responds(to: selector) {
TUICallEngine.createInstance().perform(selector, with: signalingInfo)
}
} else if method == TUICore_TUICallingService_EnableMultiDeviceAbilityMethod {
let key = TUICore_TUICallingService_EnableMultiDeviceAbilityMethod_EnableMultiDeviceAbility
guard let enableMultiDeviceAbility = param[key] as? Bool else {
return nil
}
TUICallEngine.createInstance().enableMultiDeviceAbility(enable: enableMultiDeviceAbility) {
} fail: { code, message in
}
} else {
CallEngineManager.instance.voipDataSyncHandler.onCall(method, param: param)
}
return nil
}
}

View File

@@ -0,0 +1,101 @@
//
// VoIPDataSyncHandler.swift
// Pods
//
// Created by vincepzhang on 2024/11/25.
//
import Foundation
import TUICore
import TUICallEngine
import UIKit
class VoIPDataSyncHandler {
func onCall(_ method: String, param: [AnyHashable : Any]?) {
guard let param = param else { return }
if method == TUICore_TUICallingService_SetAudioPlaybackDeviceMethod {
let key = TUICore_TUICallingService_SetAudioPlaybackDevice_AudioPlaybackDevice
guard let value = param[key] as? UInt else { return }
let audioPlaybackDevice: TUIAudioPlaybackDevice
switch value {
case TUIAudioPlaybackDevice.earpiece.rawValue:
audioPlaybackDevice = .earpiece
default:
audioPlaybackDevice = .speakerphone
}
Logger.info("VoIPDataSyncHandler - onCall - selectAudioPlaybackDevice. deivce:\(audioPlaybackDevice)")
CallEngineManager.instance.selectAudioPlaybackDevice(device: audioPlaybackDevice)
} else if method == TUICore_TUICallingService_SetIsMicMuteMethod {
guard let isMicMute = param[TUICore_TUICallingService_SetIsMicMuteMethod_IsMicMute] as? Bool else {
return
}
if isMicMute {
Logger.info("VoIPDataSyncHandler - onCall - closeMicrophone")
CallEngineManager.instance.closeMicrophone(false)
} else {
Logger.info("VoIPDataSyncHandler - onCall - openMicrophone")
CallEngineManager.instance.openMicrophone(false)
}
} else if method == TUICore_TUICallingService_HangupMethod {
if TUICallState.instance.selfUser.value.callStatus.value == .accept {
Logger.info("VoIPDataSyncHandler - onCall - hangup")
CallEngineManager.instance.hangup()
} else {
Logger.info("VoIPDataSyncHandler - onCall - reject")
CallEngineManager.instance.reject()
}
} else if method == TUICore_TUICallingService_AcceptMethod {
Logger.info("VoIPDataSyncHandler - onCall - accept")
CallEngineManager.instance.accept()
}
}
func setVoIPMuteForTUICallKitVoIPExtension(_ mute: Bool) {
TUICore.notifyEvent(TUICore_TUICallKitVoIPExtensionNotify,
subKey: mute ? TUICore_TUICore_TUICallKitVoIPExtensionNotify_CloseMicrophoneSubKey :
TUICore_TUICore_TUICallKitVoIPExtensionNotify_OpenMicrophoneSubKey,
object: nil,
param: nil)
}
func setVoIPMute(_ mute: Bool) {
Logger.info("VoIPDataSyncHandler - setVoIPMute. mute:\(mute)")
TUICore.notifyEvent(TUICore_TUIVoIPExtensionNotify,
subKey: TUICore_TUICore_TUIVoIPExtensionNotify_MuteSubKey,
object: nil,
param: [TUICore_TUICore_TUIVoIPExtensionNotify_MuteSubKey_IsMuteKey: mute])
}
func closeVoIP() {
Logger.info("VoIPDataSyncHandler - closeVoIP")
TUICore.notifyEvent(TUICore_TUIVoIPExtensionNotify,
subKey: TUICore_TUICore_TUIVoIPExtensionNotify_EndSubKey,
object: nil,
param: nil)
}
func callBegin() {
Logger.info("VoIPDataSyncHandler - callBegin")
TUICore.notifyEvent(TUICore_TUIVoIPExtensionNotify,
subKey: TUICore_TUICore_TUIVoIPExtensionNotify_ConnectedKey,
object: nil,
param: nil)
}
func updateVoIPInfo(callerId: String, calleeList: [String], groupId: String) {
Logger.info("VoIPDataSyncHandler - updateInfo")
TUICore.notifyEvent(TUICore_TUIVoIPExtensionNotify,
subKey: TUICore_TUICore_TUIVoIPExtensionNotify_UpdateInfoSubKey,
object: nil,
param: [TUICore_TUICore_TUIVoIPExtensionNotify_UpdateInfoSubKey_InviterIdKey: callerId,
TUICore_TUICore_TUIVoIPExtensionNotify_UpdateInfoSubKey_InviteeListKey: calleeList,
TUICore_TUICore_TUIVoIPExtensionNotify_UpdateInfoSubKey_GroupIDKey: groupId])
}
}