This commit is contained in:
启星
2025-08-08 10:49:36 +08:00
parent 6400cf78bb
commit b5ce3d580a
8780 changed files with 978183 additions and 0 deletions

View File

@@ -0,0 +1,368 @@
//
// TUICallKitImpl.swift
// TUICallKit
//
// Created by vincepzhang on 2023/1/4.
//
import Foundation
import TUICore
import UIKit
import TUICallEngine
#if canImport(TXLiteAVSDK_TRTC)
import TXLiteAVSDK_TRTC
#elseif canImport(TXLiteAVSDK_Professional)
import TXLiteAVSDK_Professional
#endif
class TUICallKitImpl: TUICallKit {
static let instance = TUICallKitImpl()
let selfUserCallStatusObserver = Observer()
let callingVibratorFeature = CallingVibratorFeature()
let callingBellFeature = CallingBellFeature()
override init() {
super.init()
registerNotifications()
registerObserveState()
}
deinit {
CallEngineManager.instance.removeObserver(TUICallState.instance)
NotificationCenter.default.removeObserver(self)
TUICallState.instance.selfUser.value.callStatus.removeObserver(selfUserCallStatusObserver)
}
// MARK: Implementation of external interface for TUICallKit
override func setSelfInfo(nickname: String, avatar: String, succ: @escaping TUICallSucc, fail: @escaping TUICallFail) {
CallEngineManager.instance.setSelfInfo(nickname: nickname, avatar: avatar) {
succ()
} fail: { code, message in
fail(code,message)
}
}
override func call(userId: String, callMediaType: TUICallMediaType) {
call(userId: userId, callMediaType: callMediaType, params: getCallParams()) {
} fail: { errCode, errMessage in
}
}
override func call(userId: String, callMediaType: TUICallMediaType, params: TUICallParams,
succ: @escaping TUICallSucc, fail: @escaping TUICallFail) {
if TUILogin.getUserID() == nil {
fail(ERROR_INIT_FAIL, "call failed, please login")
return
}
if userId.count <= 0 || userId == TUILogin.getUserID() {
fail(ERROR_PARAM_INVALID, "call failed, invalid params 'userId'")
return
}
if WindowManager.instance.isFloating {
fail(ERROR_PARAM_INVALID, "call failed, Unable to restart the call")
TUITool.makeToast(TUICallKitLocalize(key: "TUICallKit.UnableToRestartTheCall"))
return
}
if callMediaType == .unknown {
fail(ERROR_PARAM_INVALID, "call failed, callMediaType is Unknown")
return
}
if TUICallKitCommon.checkAuthorizationStatusIsDenied(mediaType: callMediaType) {
showAuthorizationAlert(mediaType: callMediaType)
fail(ERROR_PARAM_INVALID, "call failed, authorization status is denied")
return
}
CallEngineManager.instance.call(userId: userId, callMediaType: callMediaType, params: params) {
succ()
} fail: { [weak self] code, message in
guard let self = self else { return }
self.handleAbilityFailErrorMessage(code: code, message: message)
fail(code, message)
}
}
override func groupCall(groupId: String, userIdList: [String], callMediaType: TUICallMediaType) {
groupCall(groupId: groupId, userIdList: userIdList, callMediaType: callMediaType, params: getCallParams()) {
} fail: { code, message in
}
}
override func groupCall(groupId: String, userIdList: [String], callMediaType: TUICallMediaType, params: TUICallParams,
succ: @escaping TUICallSucc, fail: @escaping TUICallFail) {
if TUILogin.getUserID() == nil {
fail(ERROR_INIT_FAIL, "call failed, please login")
return
}
let userIdList = userIdList.filter { $0 != TUILogin.getUserID() }
if userIdList.isEmpty {
fail(ERROR_PARAM_INVALID, "call failed, invalid params 'userIdList'")
return
}
if userIdList.count >= MAX_USER {
fail(ERROR_PARAM_INVALID, "groupCall failed, currently supports call with up to 9 people")
TUITool.makeToast(TUICallKitLocalize(key: "TUICallKit.User.Exceed.Limit"))
return
}
if WindowManager.instance.isFloating {
fail(ERROR_PARAM_INVALID, "call failed, Unable to restart the call")
TUITool.makeToast(TUICallKitLocalize(key: "TUICallKit.UnableToRestartTheCall"))
return
}
if callMediaType == .unknown {
fail(ERROR_PARAM_INVALID, "call failed, callMediaType is Unknown")
return
}
if TUICallKitCommon.checkAuthorizationStatusIsDenied(mediaType: callMediaType) {
showAuthorizationAlert(mediaType: callMediaType)
fail(ERROR_PARAM_INVALID, "call failed, authorization status is denied")
return
}
CallEngineManager.instance.groupCall(groupId: groupId, userIdList: userIdList, callMediaType: callMediaType, params: params) {
succ()
} fail: { [weak self] code, message in
guard let self = self else { return }
self.handleAbilityFailErrorMessage(code: code, message: message)
fail(code,message)
}
}
override func joinInGroupCall(roomId: TUIRoomId, groupId: String, callMediaType: TUICallMediaType) {
if TUICallKitCommon.checkAuthorizationStatusIsDenied(mediaType: callMediaType) {
showAuthorizationAlert(mediaType: callMediaType)
return
}
CallEngineManager.instance.joinInGroupCall(roomId: roomId, groupId: groupId, callMediaType: callMediaType) {
} fail: { [weak self] code, message in
guard let self = self else { return }
self.handleAbilityFailErrorMessage(code: code, message: message)
}
}
override func setCallingBell(filePath: String) {
if filePath.hasPrefix("http") {
let session = URLSession.shared
guard let url = URL(string: filePath) else { return }
let downloadTask = session.downloadTask(with: url) { location, response, error in
if error != nil {
return
}
if location != nil {
if let oldBellFilePath = UserDefaults.standard.object(forKey: TUI_CALLING_BELL_KEY) as? String {
do {
try FileManager.default.removeItem(atPath: oldBellFilePath)
} catch let error {
debugPrint("FileManager Error: \(error)")
}
}
guard let location = location else { return }
guard let dstDocPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last else { return }
let dstPath = dstDocPath + "/" + location.lastPathComponent
do {
try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath: dstPath))
} catch let error {
debugPrint("FileManager Error: \(error)")
}
UserDefaults.standard.set(dstPath, forKey: TUI_CALLING_BELL_KEY)
UserDefaults.standard.synchronize()
}
}
downloadTask.resume()
} else {
UserDefaults.standard.set(filePath, forKey: TUI_CALLING_BELL_KEY)
UserDefaults.standard.synchronize()
}
}
override func enableMuteMode(enable: Bool) {
UserDefaults.standard.set(enable, forKey: ENABLE_MUTEMODE_USERDEFAULT)
TUICallState.instance.enableMuteMode = enable
}
override func enableFloatWindow(enable: Bool) {
TUICallState.instance.enableFloatWindow = enable
}
override func enableCustomViewRoute(enable: Bool) {
}
override func getCallViewController() -> UIViewController {
if let callWindowVC = WindowManager.instance.callWindow.rootViewController {
return callWindowVC
}
if let floatingWindowVC = WindowManager.instance.floatWindow.rootViewController {
return floatingWindowVC
}
return UIViewController()
}
override func enableVirtualBackground (enable: Bool) {
CallEngineManager.instance.reportOnlineLog(enable)
TUICallState.instance.showVirtualBackgroundButton = enable
}
override func enableIncomingBanner (enable: Bool) {
TUICallState.instance.enableIncomingBanner = enable
}
}
// MARK: Internal interface for TUICallKit
private extension TUICallKitImpl {
func registerNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(loginSuccessNotification),
name: NSNotification.Name.TUILoginSuccess,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(logoutSuccessNotification),
name: NSNotification.Name.TUILogoutSuccess,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(showViewControllerNotification),
name: NSNotification.Name(Constants.EVENT_SHOW_TUICALLKIT_VIEWCONTROLLER),
object: nil)
}
@objc func loginSuccessNotification(noti: Notification) {
initEngine()
initState()
CallEngineManager.instance.addObserver(TUICallState.instance)
CallEngineManager.instance.setFramework()
CallEngineManager.instance.setExcludeFromHistoryMessage()
}
@objc func logoutSuccessNotification(noti: Notification) {
CallEngineManager.instance.hangup()
TUICallEngine.destroyInstance()
TUICallState.instance.cleanState()
}
@objc func showViewControllerNotification(noti: Notification) {
CallEngineManager.instance.setAudioPlaybackDevice(device: TUICallState.instance.audioDevice.value)
WindowManager.instance.showCallWindow(false)
}
func initEngine() {
CallEngineManager.instance.initEngine(sdkAppId:TUILogin.getSdkAppID(),
userId: TUILogin.getUserID() ?? "",
userSig: TUILogin.getUserSig() ?? "") {} fail: { Int32errCode, errMessage in }
let videoEncoderParams = TUIVideoEncoderParams()
videoEncoderParams.resolution = ._640_360
videoEncoderParams.resolutionMode = .portrait
CallEngineManager.instance.setVideoEncoderParams(params: videoEncoderParams) {} fail: { Int32errCode, errMessage in }
let videoRenderParams = TUIVideoRenderParams()
videoRenderParams.fillMode = .fill
videoRenderParams.rotation = ._0
CallEngineManager.instance.setVideoRenderParams(userId: TUILogin.getUserID() ?? "",
params: videoRenderParams) {} fail: { Int32errCode, errMessage in }
let beauty = CallEngineManager.instance.getTRTCCloudInstance().getBeautyManager()
beauty.setBeautyStyle(.nature)
beauty.setBeautyLevel(6.0)
}
func initState() {
CallEngineManager.instance.addObserver(TUICallState.instance)
User.getSelfUserInfo(response: { selfUser in
TUICallState.instance.selfUser.value.id.value = selfUser.id.value
TUICallState.instance.selfUser.value.nickname.value = selfUser.nickname.value
TUICallState.instance.selfUser.value.avatar.value = selfUser.avatar.value
})
}
func registerObserveState() {
TUICallState.instance.selfUser.value.callStatus.addObserver(selfUserCallStatusObserver, closure: { newValue, _ in
if TUICallState.instance.selfUser.value.callRole.value != TUICallRole.none {
if TUICallState.instance.selfUser.value.callStatus.value == TUICallStatus.waiting {
TUICallState.instance.audioDevice.value = TUIAudioPlaybackDevice.earpiece
CallEngineManager.instance.setAudioPlaybackDevice(device: TUIAudioPlaybackDevice.earpiece)
WindowManager.instance.showCallWindow(TUICallState.instance.enableIncomingBanner)
} else if TUICallState.instance.selfUser.value.callStatus.value == TUICallStatus.accept && !WindowManager.instance.isFloating {
WindowManager.instance.showCallWindow(false)
}
} else {
if TUICallState.instance.selfUser.value.callStatus.value == TUICallStatus.none {
WindowManager.instance.closeCallWindow()
WindowManager.instance.closeFloatWindow()
}
}
})
}
func showAuthorizationAlert(mediaType: TUICallMediaType) {
let statusVideo: AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
var deniedType: AuthorizationDeniedType = AuthorizationDeniedType.audio
if mediaType == .video && statusVideo == .denied {
deniedType = .video
}
TUICallKitCommon.showAuthorizationAlert(deniedType: deniedType) {
CallEngineManager.instance.hangup()
} cancelHandler: {
CallEngineManager.instance.hangup()
}
}
func getCallParams() -> TUICallParams {
let offlinePushInfo = OfflinePushInfoConfig.createOfflinePushInfo()
let callParams = TUICallParams()
callParams.offlinePushInfo = offlinePushInfo
callParams.timeout = TUI_CALLKIT_SIGNALING_MAX_TIME
return callParams
}
func convertCallKitError(code: Int32, message: String?) -> String {
var errorMessage: String? = message
if code == ERROR_PACKAGE_NOT_PURCHASED {
errorMessage = TUICallKitLocalize(key: "TUICallKit.purchased")
} else if code == ERROR_PACKAGE_NOT_SUPPORTED {
errorMessage = TUICallKitLocalize(key: "TUICallKit.support")
} else if code == ERR_SVR_MSG_IN_PEER_BLACKLIST.rawValue {
errorMessage = TUICallKitLocalize(key: "TUICallKit.ErrorInPeerBlacklist")
} else if code == ERROR_INIT_FAIL {
errorMessage = TUICallKitLocalize(key: "TUICallKit.ErrorInvalidLogin")
} else if code == ERROR_PARAM_INVALID {
errorMessage = TUICallKitLocalize(key: "TUICallKit.ErrorParameterInvalid")
} else if code == ERROR_REQUEST_REFUSED {
errorMessage = TUICallKitLocalize(key: "TUICallKit.ErrorRequestRefused")
} else if code == ERROR_REQUEST_REPEATED {
errorMessage = TUICallKitLocalize(key: "TUICallKit.ErrorRequestRepeated")
} else if code == ERROR_SCENE_NOT_SUPPORTED {
errorMessage = TUICallKitLocalize(key: "TUICallKit.ErrorSceneNotSupport")
}
return errorMessage ?? ""
}
func handleAbilityFailErrorMessage(code: Int32, message: String?) {
let errorMessage = TUITool.convertIMError(Int(code), msg: convertCallKitError(code: code, message: message))
TUITool.makeToast(errorMessage ?? "", duration: 4)
}
}