// // AudioPlayTool.swift // LegendTeam // // Created by dong on 2022/1/2. // import Foundation import UIKit import AVFAudio enum VoicePlayViewState { case normal case loading case playing } class AudioPlayTool: NSObject { public var loadCompleteBlock: ((_ url: URL?) -> Void)? public var playFinishBlock: ((_ url: URL?) -> Void)? public var playProgressBlock: ((_ secconds: Int?) -> Void)? private(set) var audioPlayer: AVAudioPlayer? private(set) var playingUrl: URL? private(set) var state: VoicePlayViewState = .normal private var timer: Timer? private var allowPlay: Bool = false override init() { super.init() NotificationCenter.default.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) } deinit { if let isPlaying = audioPlayer?.isPlaying, isPlaying == true { audioPlayer?.stop() audioPlayer = nil } NotificationCenter.default.removeObserver(self) print("♻️ dealloc audio player") } public class func audioChannelFreeToUse() -> Bool{ // guard PhoneManager.isInPhoneChannel() == false && ChatRoomRoute.isChatRoomOpen() == false && PhoneManager.shared().isPlayingRing == false else{ // UIWindow.key?.makeToast("Currently using other voice communication services, please try again later") // return false // } return true } // 网络或者在线 public func load(url: URL?, complete: ((_ url: URL?) -> Void)?) { stop() allowPlay = true loadCompleteBlock = complete guard let audioUrl = url else { return } playingUrl = audioUrl state = .loading DispatchQueue.global().async { [weak self] in do { let data = try Data(contentsOf: url!) if let allow = self?.allowPlay, allow == true { self?.audioPlayer = try AVAudioPlayer(data: data) // try audioPlayer = AVAudioPlayer(contentsOf: audioUrl) self?.audioPlayer?.delegate = self self?.audioPlayer!.prepareToPlay() if self?.loadCompleteBlock != nil { DispatchQueue.main.async { [weak self] in self?.loadCompleteBlock!(url) } } } else { // eg: 加载过程中,停止,应该直接返回 self?.stop() } } catch {} } } // 支持 base64 字符串初始化音频 public func load(base64String: String?, complete: ((_ url: URL?) -> Void)?) { stop() allowPlay = true loadCompleteBlock = complete guard let base64Str = base64String, !base64Str.isEmpty else { complete?(nil) return } state = .loading DispatchQueue.global().async { [weak self] in do { guard let data = Data(base64Encoded: base64Str) else { DispatchQueue.main.async { [weak self] in self?.loadCompleteBlock?(nil) } return } if let allow = self?.allowPlay, allow == true { self?.audioPlayer = try AVAudioPlayer(data: data) self?.audioPlayer?.delegate = self self?.audioPlayer!.prepareToPlay() if self?.loadCompleteBlock != nil { DispatchQueue.main.async { [weak self] in self?.loadCompleteBlock?(nil) // base64 没有 URL,传 nil } } } else { // eg: 加载过程中,停止,应该直接返回 self?.stop() } } catch { DispatchQueue.main.async { [weak self] in self?.loadCompleteBlock?(nil) } } } } func isPlaying() -> Bool { audioPlayer?.isPlaying ?? false } public func play() { let session = AVAudioSession.sharedInstance() try? session.setCategory(AVAudioSession.Category.playAndRecord) try? session.overrideOutputAudioPort(.speaker) try? session.setActive(true) if playProgressBlock != nil { startTimer() } audioPlayer?.play() state = .playing } public func stop() { state = .normal if let isPlaying = audioPlayer?.isPlaying, isPlaying == true { audioPlayer?.stop() playFinishBlock?(playingUrl) clearTimer() } allowPlay = false } public func setupListenPlayProgress(block: ((_ secconds: Int?) -> Void)?) { playProgressBlock = block } // MARK: - helper private func clearTimer() { if timer != nil { timer?.invalidate() timer = nil } } private func startTimer() { clearTimer() let myTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(timerDidChanged), userInfo: nil, repeats: true) RunLoop.current.add(myTimer, forMode: .common) myTimer.fire() timer = myTimer } // MARK: - action @objc private func timerDidChanged() { if let audiotime = audioPlayer?.currentTime, let duration = audioPlayer?.duration { let seconds = Int(ceil(duration - audiotime)) // dlog("player's current time: \(String(describing: audioPlayer?.currentTime)) seconds: \(seconds)") playProgressBlock?(seconds) } } // MARK: - noti @objc private func enterBackground() { stop() } } // MARK: - AVAudioPlayerDelegate extension AudioPlayTool: AVAudioPlayerDelegate { func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { state = .normal clearTimer() DispatchQueue.main.async { [weak self] in self?.playFinishBlock?(self?.playingUrl) } } func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {} func audioPlayerBeginInterruption(_ player: AVAudioPlayer) {} func audioPlayerEndInterruption(_ player: AVAudioPlayer, withOptions flags: Int) {} }