// // CallingBellFeature.swift // TUICallKit // // Created by vincepzhang on 2022/12/30. // import Foundation import AVFAudio import TUICallEngine #if canImport(TXLiteAVSDK_TRTC) import TXLiteAVSDK_TRTC #elseif canImport(TXLiteAVSDK_Professional) import TXLiteAVSDK_Professional #endif let CALLKIT_AUDIO_DIAL_ID: Int32 = 48 class CallingBellFeature: NSObject, AVAudioPlayerDelegate { enum CallingBellType { case CallingBellTypeHangup case CallingBellTypeCalled case CallingBellTypeDial } var player: AVAudioPlayer? var loop: Bool = true private var needPlayRingtone: Bool = false let selfUserCallStatusObserver = Observer() override init() { super.init() registerNotifications() registerObserveState() } deinit { NotificationCenter.default.removeObserver(self) TUICallState.instance.selfUser.value.callStatus.removeObserver(selfUserCallStatusObserver) } func registerObserveState() { TUICallState.instance.selfUser.value.callStatus.addObserver(selfUserCallStatusObserver, closure: { newValue, _ in let callStatus = TUICallState.instance.selfUser.value.callStatus.value let callRole = TUICallState.instance.selfUser.value.callRole.value if callStatus == TUICallStatus.waiting { if callRole == TUICallRole.called { self.startMusicBasedOnAppState(type: .CallingBellTypeCalled) } else if callRole == TUICallRole.call { self.startPlayMusic(type: .CallingBellTypeDial) } } else { self.stopPlayMusic() } }) } func startMusicBasedOnAppState(type: CallingBellType) { if UIApplication.shared.applicationState != .background { self.setAudioSessionWith(category: .soloAmbient) self.startPlayMusic(type: type) } else { self.needPlayRingtone = true; } } func registerNotifications() { if #available(iOS 13.0, *) { NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIScene.didActivateNotification, object: nil) } else { NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) } } @objc func appDidBecomeActive() { if needPlayRingtone { let _ = startPlayMusic(type: .CallingBellTypeCalled) needPlayRingtone = false; } } func startPlayMusic(type: CallingBellType) { guard let bundle = TUICallKitCommon.getTUICallKitBundle() else { return } switch type { case .CallingBellTypeHangup: let path = bundle.bundlePath + "/AudioFile" + "/phone_hangup.mp3" let url = URL(fileURLWithPath: path) return startPlayMusicBySystemPlayer(url: url, loop: false) case .CallingBellTypeCalled: if TUICallState.instance.enableMuteMode { return } var path = bundle.bundlePath + "/AudioFile" + "/phone_ringing.mp3" if let value = UserDefaults.standard.object(forKey: TUI_CALLING_BELL_KEY) as? String { path = value } let url = URL(fileURLWithPath: path) return startPlayMusicBySystemPlayer(url: url) case .CallingBellTypeDial: let path = bundle.bundlePath + "/AudioFile" + "/phone_dialing.m4a" startPlayMusicByTRTCPlayer(path: path, id: CALLKIT_AUDIO_DIAL_ID) return } } func stopPlayMusic() { setAudioSessionWith(category: .playAndRecord) if TUICallState.instance.selfUser.value.callRole.value == .call { stopPlayMusicByTRTCPlayer(id: CALLKIT_AUDIO_DIAL_ID) return } stopPlayMusicBySystemPlayer() needPlayRingtone = false; } // MARK: TRTC Audio Player private func startPlayMusicByTRTCPlayer(path: String, id: Int32) { let audioDevice: TUIAudioPlaybackDevice = TUICallState.instance.mediaType.value == .audio ? .earpiece : .speakerphone CallEngineManager.instance.setAudioPlaybackDevice(device: audioDevice) let param = TXAudioMusicParam() param.id = id param.isShortFile = true param.path = path let audioEffectManager = TUICallEngine.createInstance().getTRTCCloudInstance().getAudioEffectManager() audioEffectManager.startPlayMusic(param, onStart: nil, onProgress: nil) audioEffectManager.setMusicPlayoutVolume(id, volume: 100) } private func stopPlayMusicByTRTCPlayer(id: Int32) { let audioEffectManager = TUICallEngine.createInstance().getTRTCCloudInstance().getAudioEffectManager() audioEffectManager.stopPlayMusic(id) } // MARK: System AVAudio Player private func startPlayMusicBySystemPlayer(url: URL, loop: Bool = true) { self.loop = loop if player != nil { stopPlayMusicBySystemPlayer() } do { player = try AVAudioPlayer(contentsOf: url) } catch let error as NSError { print("Error: \(error.localizedDescription)") return } guard let prepare = player?.prepareToPlay(), prepare else { return } player?.delegate = self player?.play() } private func stopPlayMusicBySystemPlayer() { player?.stop() player = nil } // MARK: AVAudioPlayerDelegate func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { if loop { player.play() } else { stopPlayMusicBySystemPlayer() } } func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) { if error != nil { stopPlayMusicBySystemPlayer() } } private func setAudioSessionWith(category: AVAudioSession.Category) { let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(category, options: [.allowBluetooth, .allowBluetoothA2DP, .mixWithOthers]) try audioSession.setActive(true) } catch { print("Error setting up audio session: \(error)") } } }