|
@@ -5,489 +5,489 @@
|
|
|
// 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
|
|
|
-// logPrint("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 loadLoactionURL(url:URL){
|
|
|
- self.audioPlayer = TSAudioPlayer(url: url)
|
|
|
- }
|
|
|
-
|
|
|
- func playUrlString(_ urlString:String?,localURL:URL? = nil,loop:Bool = false,indexPath:IndexPath? = nil) {
|
|
|
- self.stop()
|
|
|
- if let urlString = urlString {
|
|
|
-
|
|
|
- self.currentURLString = urlString
|
|
|
- self.currentLocalURL = localURL
|
|
|
- self.currentIndexPath = indexPath
|
|
|
-
|
|
|
- let palyFile:(URL)->Void = { [weak self] url in
|
|
|
- guard let self = self else { return }
|
|
|
- logPrint("TSAudioPlayer 正在播放url:\(currentURLString)")
|
|
|
- logPrint("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()
|
|
|
- logPrint(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,ASFileManager.fileExists(at: path){
|
|
|
- palyFile(path) //播放
|
|
|
-
|
|
|
- }else{
|
|
|
- self.changePlayerState(.loading(0.0))
|
|
|
-
|
|
|
- _ = ASDownloadManager.getDownLoadRing(urlString: urlString, progressHandler: { progress in
|
|
|
- logPrint("ASDownloadManager.etDownLoadRing progress = \(progress)")
|
|
|
- }, complete: {[weak self] url, success 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()
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- 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
|
|
|
- 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 {
|
|
|
- logPrint("TSAudioPlayer changePlayerState=\(state)")
|
|
|
- }
|
|
|
- currentPlayerState = state
|
|
|
- kExecuteOnMainThread{
|
|
|
- self.stateChangedHandle?(state)
|
|
|
-// NotificationCenter.default.post(name: .kBusinessAudioStateChange, object: nil, userInfo: ["PlayerState": state])
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- deinit {
|
|
|
- logPrint("TSAudioPlayer TSBusinessAudioPlayer deinit")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-extension TSBusinessAudioPlayer{
|
|
|
- struct AudioFileInfo {
|
|
|
- let sizeInBytes: UInt64? // 文件大小(字节)
|
|
|
- let durationInSeconds: Double? // 音频时长(秒)
|
|
|
- let songName: String? // 音频时长(秒)
|
|
|
- }
|
|
|
-
|
|
|
- static func getAudioFileInfo(path: String) -> AudioFileInfo? {
|
|
|
- // 1. 检查URL有效性
|
|
|
- guard let url = URL(string: path) else {
|
|
|
- print("getAudioFileInfo 无效的URL字符串")
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 检查文件是否存在(仅限本地文件)
|
|
|
- guard FileManager.default.fileExists(atPath: url.path) else {
|
|
|
- print("getAudioFileInfo 文件不存在或不是本地路径")
|
|
|
- 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)
|
|
|
- }()
|
|
|
-
|
|
|
- // 5. 获取音频名称
|
|
|
- let songName: String? = {
|
|
|
- return getSongNameFromLocalFile(fileURL: url)
|
|
|
- }()
|
|
|
-
|
|
|
- return AudioFileInfo(sizeInBytes: fileSize, durationInSeconds: duration,songName: songName)
|
|
|
- }
|
|
|
-
|
|
|
- /// 同步获取音频时长(可能阻塞线程!)
|
|
|
- /// 使用 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
|
|
|
- }
|
|
|
-
|
|
|
- //获取音频的名称
|
|
|
- static func getSongNameFromLocalFile(fileURL: URL) -> String? {
|
|
|
- // 1. 检查文件是否存在
|
|
|
- guard FileManager.default.fileExists(atPath: fileURL.path) else {
|
|
|
- print("文件不存在")
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 创建 AVAsset 对象(代表音频文件)
|
|
|
- let asset = AVAsset(url: fileURL)
|
|
|
-
|
|
|
- // 3. 同步读取元数据(适用于本地文件)
|
|
|
- var songName: String? = nil
|
|
|
- let metadataFormats = asset.availableMetadataFormats
|
|
|
-
|
|
|
- for format in metadataFormats {
|
|
|
- let metadata = asset.metadata(forFormat: format)
|
|
|
-
|
|
|
- // 4. 遍历元数据项,查找歌曲标题
|
|
|
- for item in metadata {
|
|
|
- if item.commonKey == .commonKeyTitle, // 标准键:标题
|
|
|
- let value = item.value as? String { // 确保值是字符串
|
|
|
- songName = value
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- // 额外检查 ID3 标签(某些 MP3 文件可能用非标准键)
|
|
|
- if item.key as? String == "TIT2", // ID3v2 标题键
|
|
|
- let value = item.value as? String {
|
|
|
- songName = value
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if songName != nil { break }
|
|
|
- }
|
|
|
-
|
|
|
- // 5. 返回结果(若未找到,则尝试从文件名推断)
|
|
|
- return songName ?? fileURL.deletingPathExtension().lastPathComponent
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-class TSAudioPlayer: NSObject {
|
|
|
- private var player: AVPlayer?
|
|
|
- private var timeObserverToken: Any?
|
|
|
- var isLooping: Bool = false
|
|
|
-
|
|
|
- override init() {
|
|
|
- super.init()
|
|
|
- }
|
|
|
-
|
|
|
- var isPlaying: Bool {
|
|
|
-// return player?.rate != 0 && player?.error == nil
|
|
|
-
|
|
|
- if let player = player {
|
|
|
- if #available(iOS 10.0, *) {
|
|
|
- return player.timeControlStatus == .playing
|
|
|
- } else {
|
|
|
- return player.rate > 0
|
|
|
- }
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- var duration: Double {
|
|
|
- if let currentItem = player?.currentItem {
|
|
|
- return CMTimeGetSeconds(currentItem.asset.duration)
|
|
|
- }
|
|
|
- return 0.0
|
|
|
- }
|
|
|
-
|
|
|
- var volume: Float {
|
|
|
- get {
|
|
|
- return player?.volume ?? 0.0
|
|
|
- }
|
|
|
- set {
|
|
|
- player?.volume = max(0.0, min(newValue, 1.0))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- var currentTime: Double {
|
|
|
- if let currentItem = player?.currentItem {
|
|
|
- return CMTimeGetSeconds(currentItem.currentTime())
|
|
|
- }
|
|
|
- return 0.0
|
|
|
- }
|
|
|
-
|
|
|
- var playerUsable: Bool {
|
|
|
- return player != nil
|
|
|
- }
|
|
|
-
|
|
|
- var currentTimeChanged: ((Double,Double) -> Void)?
|
|
|
- var audioPlayerDidFinishHandle: ((Bool) -> Void)?
|
|
|
-
|
|
|
- /// 初始化播放器
|
|
|
- /// - Parameter url: 音频文件的 URL
|
|
|
- init?(url: URL) {
|
|
|
- super.init()
|
|
|
-
|
|
|
- let playerItem = AVPlayerItem(url: url)
|
|
|
- player = AVPlayer(playerItem: playerItem)
|
|
|
-
|
|
|
- // 监听播放完成事件
|
|
|
- NotificationCenter.default.addObserver(
|
|
|
- self,
|
|
|
- selector: #selector(playerDidFinishPlaying),
|
|
|
- name: .AVPlayerItemDidPlayToEndTime,
|
|
|
- object: playerItem
|
|
|
- )
|
|
|
-
|
|
|
- // 设置音频会话
|
|
|
- do {
|
|
|
- let audioSession = AVAudioSession.sharedInstance()
|
|
|
- try audioSession.setCategory(.playback)
|
|
|
- try audioSession.setActive(true)
|
|
|
- } catch {
|
|
|
- print("TSAudioPlayer 音频会话设置失败: \(error.localizedDescription)")
|
|
|
- return nil
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- deinit {
|
|
|
- stop()
|
|
|
- }
|
|
|
-
|
|
|
- /// 开始播放音频
|
|
|
- func play() {
|
|
|
- guard let player = player else { return }
|
|
|
-
|
|
|
- let outputVolume = AVAudioSession.sharedInstance().outputVolume
|
|
|
- print("TSAudioPlayer outputVolume: \(outputVolume)")
|
|
|
-
|
|
|
- if outputVolume == 0.0 {
|
|
|
- print("Please turn up the volume".localized)
|
|
|
- }
|
|
|
-
|
|
|
- player.play()
|
|
|
-
|
|
|
- // 监听播放进度
|
|
|
- addTimeObserver()
|
|
|
- }
|
|
|
-
|
|
|
- /// 暂停播放音频
|
|
|
- func pause() {
|
|
|
- player?.pause()
|
|
|
- }
|
|
|
-
|
|
|
- /// 停止并销毁播放器
|
|
|
- func stop() {
|
|
|
- player?.pause()
|
|
|
- player?.replaceCurrentItem(with: nil)
|
|
|
- player = nil
|
|
|
- removeTimeObserver()
|
|
|
- }
|
|
|
-
|
|
|
- /// 设置是否重复播放
|
|
|
- /// - Parameter loop: 是否循环播放
|
|
|
- func setLoop(_ loop: Bool) {
|
|
|
- isLooping = loop
|
|
|
- }
|
|
|
-
|
|
|
- /// 跳转到指定时间
|
|
|
- /// - Parameter time: 目标时间(秒)
|
|
|
- func seek(to time: Double) {
|
|
|
- if let player = player {
|
|
|
- let targetTime = CMTimeMakeWithSeconds(time, preferredTimescale: 600)
|
|
|
- player.seek(to: targetTime)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 监听播放完成
|
|
|
- @objc private func playerDidFinishPlaying() {
|
|
|
- if isLooping {
|
|
|
- seek(to: 0.0)
|
|
|
- play()
|
|
|
- }
|
|
|
- audioPlayerDidFinishHandle?(true)
|
|
|
- }
|
|
|
-
|
|
|
- /// 添加时间观察者
|
|
|
- private func addTimeObserver() {
|
|
|
- let interval = CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
|
|
- timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
|
|
|
- guard let self = self else { return }
|
|
|
- let currentTime = CMTimeGetSeconds(time)
|
|
|
- self.currentTimeChanged?(currentTime,duration)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 移除时间观察者
|
|
|
- private func removeTimeObserver() {
|
|
|
- if let token = timeObserverToken {
|
|
|
- player?.removeTimeObserver(token)
|
|
|
- timeObserverToken = nil
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+//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
|
|
|
+//// logPrint("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 loadLoactionURL(url:URL){
|
|
|
+// self.audioPlayer = TSAudioPlayer(url: url)
|
|
|
+// }
|
|
|
+//
|
|
|
+// func playUrlString(_ urlString:String?,localURL:URL? = nil,loop:Bool = false,indexPath:IndexPath? = nil) {
|
|
|
+// self.stop()
|
|
|
+// if let urlString = urlString {
|
|
|
+//
|
|
|
+// self.currentURLString = urlString
|
|
|
+// self.currentLocalURL = localURL
|
|
|
+// self.currentIndexPath = indexPath
|
|
|
+//
|
|
|
+// let palyFile:(URL)->Void = { [weak self] url in
|
|
|
+// guard let self = self else { return }
|
|
|
+// logPrint("TSAudioPlayer 正在播放url:\(currentURLString)")
|
|
|
+// logPrint("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()
|
|
|
+// logPrint(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,ASFileManager.fileExists(at: path){
|
|
|
+// palyFile(path) //播放
|
|
|
+//
|
|
|
+// }else{
|
|
|
+// self.changePlayerState(.loading(0.0))
|
|
|
+//
|
|
|
+// _ = ASDownloadManager.getDownLoadRing(urlString: urlString, progressHandler: { progress in
|
|
|
+// logPrint("ASDownloadManager.etDownLoadRing progress = \(progress)")
|
|
|
+// }, complete: {[weak self] url, success 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()
|
|
|
+// }
|
|
|
+// })
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+//
|
|
|
+// 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
|
|
|
+// 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 {
|
|
|
+// logPrint("TSAudioPlayer changePlayerState=\(state)")
|
|
|
+// }
|
|
|
+// currentPlayerState = state
|
|
|
+// kExecuteOnMainThread{
|
|
|
+// self.stateChangedHandle?(state)
|
|
|
+//// NotificationCenter.default.post(name: .kBusinessAudioStateChange, object: nil, userInfo: ["PlayerState": state])
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// deinit {
|
|
|
+// logPrint("TSAudioPlayer TSBusinessAudioPlayer deinit")
|
|
|
+// }
|
|
|
+//}
|
|
|
+//
|
|
|
+//
|
|
|
+//extension TSBusinessAudioPlayer{
|
|
|
+// struct AudioFileInfo {
|
|
|
+// let sizeInBytes: UInt64? // 文件大小(字节)
|
|
|
+// let durationInSeconds: Double? // 音频时长(秒)
|
|
|
+// let songName: String? // 音频时长(秒)
|
|
|
+// }
|
|
|
+//
|
|
|
+// static func getAudioFileInfo(path: String) -> AudioFileInfo? {
|
|
|
+// // 1. 检查URL有效性
|
|
|
+// guard let url = URL(string: path) else {
|
|
|
+// print("getAudioFileInfo 无效的URL字符串")
|
|
|
+// return nil
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 2. 检查文件是否存在(仅限本地文件)
|
|
|
+// guard FileManager.default.fileExists(atPath: url.path) else {
|
|
|
+// print("getAudioFileInfo 文件不存在或不是本地路径")
|
|
|
+// 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)
|
|
|
+// }()
|
|
|
+//
|
|
|
+// // 5. 获取音频名称
|
|
|
+// let songName: String? = {
|
|
|
+// return getSongNameFromLocalFile(fileURL: url)
|
|
|
+// }()
|
|
|
+//
|
|
|
+// return AudioFileInfo(sizeInBytes: fileSize, durationInSeconds: duration,songName: songName)
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 同步获取音频时长(可能阻塞线程!)
|
|
|
+// /// 使用 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
|
|
|
+// }
|
|
|
+//
|
|
|
+// //获取音频的名称
|
|
|
+// static func getSongNameFromLocalFile(fileURL: URL) -> String? {
|
|
|
+// // 1. 检查文件是否存在
|
|
|
+// guard FileManager.default.fileExists(atPath: fileURL.path) else {
|
|
|
+// print("文件不存在")
|
|
|
+// return nil
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 2. 创建 AVAsset 对象(代表音频文件)
|
|
|
+// let asset = AVAsset(url: fileURL)
|
|
|
+//
|
|
|
+// // 3. 同步读取元数据(适用于本地文件)
|
|
|
+// var songName: String? = nil
|
|
|
+// let metadataFormats = asset.availableMetadataFormats
|
|
|
+//
|
|
|
+// for format in metadataFormats {
|
|
|
+// let metadata = asset.metadata(forFormat: format)
|
|
|
+//
|
|
|
+// // 4. 遍历元数据项,查找歌曲标题
|
|
|
+// for item in metadata {
|
|
|
+// if item.commonKey == .commonKeyTitle, // 标准键:标题
|
|
|
+// let value = item.value as? String { // 确保值是字符串
|
|
|
+// songName = value
|
|
|
+// break
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 额外检查 ID3 标签(某些 MP3 文件可能用非标准键)
|
|
|
+// if item.key as? String == "TIT2", // ID3v2 标题键
|
|
|
+// let value = item.value as? String {
|
|
|
+// songName = value
|
|
|
+// break
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// if songName != nil { break }
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 5. 返回结果(若未找到,则尝试从文件名推断)
|
|
|
+// return songName ?? fileURL.deletingPathExtension().lastPathComponent
|
|
|
+// }
|
|
|
+//}
|
|
|
+//
|
|
|
+//class TSAudioPlayer: NSObject {
|
|
|
+// private var player: AVPlayer?
|
|
|
+// private var timeObserverToken: Any?
|
|
|
+// var isLooping: Bool = false
|
|
|
+//
|
|
|
+// override init() {
|
|
|
+// super.init()
|
|
|
+// }
|
|
|
+//
|
|
|
+// var isPlaying: Bool {
|
|
|
+//// return player?.rate != 0 && player?.error == nil
|
|
|
+//
|
|
|
+// if let player = player {
|
|
|
+// if #available(iOS 10.0, *) {
|
|
|
+// return player.timeControlStatus == .playing
|
|
|
+// } else {
|
|
|
+// return player.rate > 0
|
|
|
+// }
|
|
|
+// }
|
|
|
+// return false
|
|
|
+// }
|
|
|
+//
|
|
|
+// var duration: Double {
|
|
|
+// if let currentItem = player?.currentItem {
|
|
|
+// return CMTimeGetSeconds(currentItem.asset.duration)
|
|
|
+// }
|
|
|
+// return 0.0
|
|
|
+// }
|
|
|
+//
|
|
|
+// var volume: Float {
|
|
|
+// get {
|
|
|
+// return player?.volume ?? 0.0
|
|
|
+// }
|
|
|
+// set {
|
|
|
+// player?.volume = max(0.0, min(newValue, 1.0))
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// var currentTime: Double {
|
|
|
+// if let currentItem = player?.currentItem {
|
|
|
+// return CMTimeGetSeconds(currentItem.currentTime())
|
|
|
+// }
|
|
|
+// return 0.0
|
|
|
+// }
|
|
|
+//
|
|
|
+// var playerUsable: Bool {
|
|
|
+// return player != nil
|
|
|
+// }
|
|
|
+//
|
|
|
+// var currentTimeChanged: ((Double,Double) -> Void)?
|
|
|
+// var audioPlayerDidFinishHandle: ((Bool) -> Void)?
|
|
|
+//
|
|
|
+// /// 初始化播放器
|
|
|
+// /// - Parameter url: 音频文件的 URL
|
|
|
+// init?(url: URL) {
|
|
|
+// super.init()
|
|
|
+//
|
|
|
+// let playerItem = AVPlayerItem(url: url)
|
|
|
+// player = AVPlayer(playerItem: playerItem)
|
|
|
+//
|
|
|
+// // 监听播放完成事件
|
|
|
+// NotificationCenter.default.addObserver(
|
|
|
+// self,
|
|
|
+// selector: #selector(playerDidFinishPlaying),
|
|
|
+// name: .AVPlayerItemDidPlayToEndTime,
|
|
|
+// object: playerItem
|
|
|
+// )
|
|
|
+//
|
|
|
+// // 设置音频会话
|
|
|
+// do {
|
|
|
+// let audioSession = AVAudioSession.sharedInstance()
|
|
|
+// try audioSession.setCategory(.playback)
|
|
|
+// try audioSession.setActive(true)
|
|
|
+// } catch {
|
|
|
+// print("TSAudioPlayer 音频会话设置失败: \(error.localizedDescription)")
|
|
|
+// return nil
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// deinit {
|
|
|
+// stop()
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 开始播放音频
|
|
|
+// func play() {
|
|
|
+// guard let player = player else { return }
|
|
|
+//
|
|
|
+// let outputVolume = AVAudioSession.sharedInstance().outputVolume
|
|
|
+// print("TSAudioPlayer outputVolume: \(outputVolume)")
|
|
|
+//
|
|
|
+// if outputVolume == 0.0 {
|
|
|
+// print("Please turn up the volume".localized)
|
|
|
+// }
|
|
|
+//
|
|
|
+// player.play()
|
|
|
+//
|
|
|
+// // 监听播放进度
|
|
|
+// addTimeObserver()
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 暂停播放音频
|
|
|
+// func pause() {
|
|
|
+// player?.pause()
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 停止并销毁播放器
|
|
|
+// func stop() {
|
|
|
+// player?.pause()
|
|
|
+// player?.replaceCurrentItem(with: nil)
|
|
|
+// player = nil
|
|
|
+// removeTimeObserver()
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 设置是否重复播放
|
|
|
+// /// - Parameter loop: 是否循环播放
|
|
|
+// func setLoop(_ loop: Bool) {
|
|
|
+// isLooping = loop
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 跳转到指定时间
|
|
|
+// /// - Parameter time: 目标时间(秒)
|
|
|
+// func seek(to time: Double) {
|
|
|
+// if let player = player {
|
|
|
+// let targetTime = CMTimeMakeWithSeconds(time, preferredTimescale: 600)
|
|
|
+// player.seek(to: targetTime)
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 监听播放完成
|
|
|
+// @objc private func playerDidFinishPlaying() {
|
|
|
+// if isLooping {
|
|
|
+// seek(to: 0.0)
|
|
|
+// play()
|
|
|
+// }
|
|
|
+// audioPlayerDidFinishHandle?(true)
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 添加时间观察者
|
|
|
+// private func addTimeObserver() {
|
|
|
+// let interval = CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
|
|
+// timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
|
|
|
+// guard let self = self else { return }
|
|
|
+// let currentTime = CMTimeGetSeconds(time)
|
|
|
+// self.currentTimeChanged?(currentTime,duration)
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// /// 移除时间观察者
|
|
|
+// private func removeTimeObserver() {
|
|
|
+// if let token = timeObserverToken {
|
|
|
+// player?.removeTimeObserver(token)
|
|
|
+// timeObserverToken = nil
|
|
|
+// }
|
|
|
+// }
|
|
|
+//}
|