123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- //
- // TSBusinessAudioPlayer.swift
- // AIRingtone
- //
- // Created by 100Years on 2025/3/7.
- //
- import AVFoundation
- class TSBusinessAudioPlayer {
-
- static let shared = TSBusinessAudioPlayer()
-
- enum PlayerState:Equatable {
- case play
- case pause
- case stop
- case loading(Float)
- case volume(Float)
- case currentTime(Double)
- }
-
- private var audioPlayer: TSAudioPlayer?
-
- var stateChangedHandle:((PlayerState) -> Void)?
- var currentTimeChangedHandle:((Double,Double) -> Void)?
-
- var currentPlayerState:PlayerState = .stop
- var duration:Double{
- if let audioPlayer = audioPlayer {
- return audioPlayer.duration
- }
- return 0.0
- }
- var isPlaying:Bool{
- if let audioPlayer = audioPlayer {
- return audioPlayer.isPlaying
- }
- return false
- }
-
- var isLoading:Bool{
- switch currentPlayerState {
- case .loading(let float):
- return float < 1.0 ? true : false
- default:
- return false
- }
- }
-
- var currentTime:Double{
- if let audioPlayer = audioPlayer {
- return audioPlayer.currentTime
- }
- return 0.0
- }
-
- /// 跳转到指定时间
- /// - Parameter time: 目标时间(秒)
- func seek(to time: Double) {
- audioPlayer?.seek(to: time)
- }
-
- var playProgress:Double{
- let playProgress = currentTime / duration
- // dePrint("TSAudioPlayer playProgress = \(playProgress)")
- return playProgress
- }
-
- //播放器是否可用
- var playerUsable:Bool {
- if let audioPlayer = audioPlayer {
- return audioPlayer.playerUsable
- }
- return false
- }
- var currentURLString:String = ""
- var currentLocalURL:URL? = nil
- var currentIndexPath:IndexPath? = nil
-
- //加载音乐可能 2-3 秒有结果,停止加载后播放.
- private var isStopPlayingAfterLoading:Bool = false
- func isPlayURLString(string:String,localURL:URL? = nil,indexPath:IndexPath? = nil) -> Bool {
- if currentURLString == string {
-
- if let currentIndexPath = currentIndexPath,
- let indexPath = indexPath,
- indexPath != currentIndexPath
- {
- return false
- }else if let currentLocalURL = currentLocalURL,
- let localURL = localURL,
- currentLocalURL != localURL
- {
- return false
- }else{
- return true
- }
- }
- return false
- }
-
- func playUrlString(_ urlString:String?,localURL:URL? = nil,loop:Bool = false,indexPath:IndexPath? = nil) {
- self.stop()
- if let urlString = urlString {
-
- // if self.currentURLString == urlStrin {
- // self.play()
- // return
- // }
-
- self.currentURLString = urlString
- self.currentLocalURL = localURL
- self.currentIndexPath = indexPath
- let palyFile:(URL)->Void = { [weak self] url in
- guard let self = self else { return }
- debugPrint("TSAudioPlayer 正在播放url:\(currentURLString)")
- debugPrint("TSAudioPlayer 正在播放path:\(url)")
- self.audioPlayer = TSAudioPlayer(url: url)
- self.audioPlayer?.setLoop(loop)
-
- if self.audioPlayer?.volume == 0 {
- setVolume(volume: 1.0)
- }
-
- self.audioPlayer?.currentTimeChanged = { [weak self] currentTime,duration in
- guard let self = self else { return }
- currentTimeChangedHandle?(currentTime,duration)
- changePlayerState(.currentTime(currentTime))
- }
-
- self.play()
- dePrint(self.audioPlayer?.duration)
-
- self.audioPlayer?.audioPlayerDidFinishHandle = { [weak self] flag in
- guard let self = self else { return }
- if flag == true, self.audioPlayer?.isLooping == false{
- stop()
- }
- }
- }
-
- isStopPlayingAfterLoading = false
-
- if let path = self.currentLocalURL,TSFileManagerTool.fileExists(at: path){
- palyFile(path) //播放
-
- }else if let path = TSDownloadManager.getRingLocalURL(urlString: urlString) {
- // if let path = TSCommonTool.getCachedURLString(from: urlString,missingEx: "mp3") {
- palyFile(path) //播放
- }else{
- self.changePlayerState(.loading(0.0))
-
- _ = TSDownloadManager.downloadFile(urlString: urlString,missingEx: "mp3") {[weak self] url, error in
- guard let self = self else { return }
-
- self.changePlayerState(.loading(1.0))
-
- if isStopPlayingAfterLoading == true || currentURLString != urlString{
- isStopPlayingAfterLoading = false
- return
- }
-
- if let url = url {
- palyFile(url) //播放
- }else{
- //暂停
- self.stop()
- }
- }
-
- // TSCommonTool.downloadAndCacheFile(from: urlString,missingEx: "mp3") { [weak self] path, error in
- // guard let self = self else { return }
- // self.changePlayerState(.loading(1.0))
- //
- // if isStopPlayingAfterLoading == true || currentURLString != urlString{
- // isStopPlayingAfterLoading = false
- // return
- // }
- //
- // if let path = path {
- // palyFile(URL(fileURLWithPath: path)) //播放
- // }else{
- // //暂停
- // self.stop()
- // }
- // }
- }
- }
- }
-
- func play() {
- self.audioPlayer?.play()
- changePlayerState(.play)
- }
-
- func stop() {
- self.audioPlayer?.currentTimeChanged = nil
- isStopPlayingAfterLoading = true
- currentURLString = ""
- self.audioPlayer?.stop()
- changePlayerState(.stop)
- }
-
- func pause() {
- isStopPlayingAfterLoading = true
- self.audioPlayer?.pause()
- changePlayerState(.pause)
- }
-
- func setVolume(volume:Float){
- self.audioPlayer?.volume = volume
- // self.audioPlayer?.setVolume(volume)
- changePlayerState(.volume(volume))
- }
-
- func changeAudioSwitch()->Float {
- let volume:Float = self.audioPlayer?.volume == 0.0 ? 1.0 : 0.0
- setVolume(volume: volume)
- return volume
- }
-
- func changePlayerState(_ state:PlayerState){
- if case .currentTime(let time) = state {} else {
- debugPrint("TSAudioPlayer changePlayerState=\(state)")
- }
- currentPlayerState = state
- kExecuteOnMainThread{
- self.stateChangedHandle?(state)
- // NotificationCenter.default.post(name: .kBusinessAudioStateChange, object: nil, userInfo: ["PlayerState": state])
- }
- }
-
- deinit {
- dePrint("TSAudioPlayer TSBusinessAudioPlayer deinit")
- }
- }
- extension TSBusinessAudioPlayer{
- struct AudioFileInfo {
- let sizeInBytes: UInt64? // 文件大小(字节)
- let durationInSeconds: Double? // 音频时长(秒)
-
- // // 计算属性:格式化显示
- // var formattedSize: String {
- // guard let size = sizeInBytes else { return "未知大小" }
- // let formatter = ByteCountFormatter()
- // formatter.allowedUnits = [.useBytes, .useKB, .useMB, .useGB]
- // return formatter.string(fromByteCount: Int64(size))
- // }
- //
- // var formattedDuration: String {
- // guard let duration = durationInSeconds else { return "未知时长" }
- // let formatter = DateComponentsFormatter()
- // formatter.unitsStyle = .positional
- // formatter.allowedUnits = [.hour, .minute, .second]
- // formatter.zeroFormattingBehavior = .pad
- // return formatter.string(from: duration) ?? "00:00"
- // }
- }
- static func getAudioFileInfo(path: String) -> AudioFileInfo? {
- // 1. 检查URL有效性
- guard let url = URL(string: path) else {
- print("无效的URL字符串")
- return nil
- }
-
- // 2. 检查文件是否存在(仅限本地文件)
- guard FileManager.default.fileExists(atPath: url.path) else {
- print("文件不存在或不是本地路径")
- return nil
- }
-
- // 3. 获取文件大小
- let fileSize: UInt64? = {
- do {
- let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
- return attributes[.size] as? UInt64
- } catch {
- print("获取文件大小失败: \(error.localizedDescription)")
- return nil
- }
- }()
-
- // 4. 获取音频时长
- let duration: Double? = {
-
- return getAudioDurationWithAudioFile(url: url)
- // let asset = AVURLAsset(url: url)
- // let seconds = Double(CMTimeGetSeconds(asset.duration))
- // return seconds.isNaN ? nil : seconds
- }()
-
- return AudioFileInfo(sizeInBytes: fileSize, durationInSeconds: duration)
- }
-
- /// 同步获取音频时长(可能阻塞线程!)
- /// 使用 AudioFile 同步获取音频时长
- static func getAudioDurationWithAudioFile(url: URL) -> TimeInterval? {
- var audioFile: AudioFileID?
- let status = AudioFileOpenURL(url as CFURL, .readPermission, 0, &audioFile)
-
- guard status == noErr, let file = audioFile else {
- print("⚠️ 打开音频文件失败: \(status)")
- return nil
- }
-
- // 获取音频时长(单位:秒)
- var duration: Float64 = 0
- var propertySize = UInt32(MemoryLayout.size(ofValue: duration))
- let durationStatus = AudioFileGetProperty(
- file,
- kAudioFilePropertyEstimatedDuration,
- &propertySize,
- &duration
- )
-
- AudioFileClose(file)
-
- return durationStatus == noErr ? duration : nil
- }
- }
|