fix
杨锴
2025-05-06 fdb1d18a0b4b941b986d55f66c589e29836494eb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//
//  VoicePlayer.swift
//  DolphinEnglishLearnStudent
//
//  Created by 无故事王国 on 2024/6/4.
//
 
import Foundation
import AVFAudio
 
 
protocol VoicePlayerDelegate{
                func playComplete()
                func playing()
}
 
/// 音频播放器
class VoicePlayer:NSObject{
                /// 缓存地址
                private let voiceCacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("voices")
                private static var _sharedInstance: VoicePlayer?
                private var player:AVAudioPlayer?
                private var tempPlayer:AVAudioPlayer?
 
                var delegate:VoicePlayerDelegate?
 
                //是否正在播放
                var isPlaying:Bool{return player?.isPlaying ?? false}
                //播放完成回调
                private var playComplete:(()->Void)?
 
 
                /// 单例
                class func share() -> VoicePlayer {
                                guard let instance = _sharedInstance else {
                                                _sharedInstance = VoicePlayer()
                                                try? FileManager.default.createDirectory(at: _sharedInstance!.voiceCacheDirectory, withIntermediateDirectories: true)
                                                return _sharedInstance!
                                }
                                return instance
                }
 
                private override init() {} // 私有化init方法
 
                ///销毁单例对象
                class func destroy() {
                                _sharedInstance = nil
                }
 
                /// 播放语音
                /// - Parameter url: Https的语音地址
                func playerAt(url:String?){
                                guard let u = url else {return}
                                if player?.isPlaying ?? false{
//                                                player?.stop()
                                                return
                                }
                                                //文件存在:直接播放缓存路径的语音
                                                let fileURL = self.voiceCacheDirectory.appendingPathComponent(URL(fileURLWithPath: u).lastPathComponent).droppedScheme()
                                                if FileManager.default.fileExists(atPath: fileURL!.absoluteString){
                                                                self.player = try? AVAudioPlayer(contentsOf: fileURL!)
                                                                self.player?.delegate = self
                                                                self.player?.play()
                                                                DispatchQueue.main.async {
                                                                                self.delegate?.playing()
                                                                }
                                                }else{
                                                                //文件不存在:执行下载
                                                                let downloadTask = URLSession.shared.downloadTask(with: URL(string: u)!) { tempLocalUrl, response, error in
                                                                                if let tempLocalUrl = tempLocalUrl, error == nil {
                                                                                                do {
                                                                                                                let finalCacheUrl = self.voiceCacheDirectory.appendingPathComponent(URL(fileURLWithPath: u).lastPathComponent)
                                                                                                                try FileManager.default.moveItem(at: tempLocalUrl, to: finalCacheUrl)
                                                                                                                self.player = try? AVAudioPlayer(contentsOf: finalCacheUrl.droppedScheme()!)
                                                                                                                self.player?.delegate = self
                                                                                                                self.player?.play()
                                                                                                                DispatchQueue.main.async {
                                                                                                                                self.delegate?.playing()
                                                                                                                }
                                                                                                } catch {
                                                                                                                print("视频缓存失败:catch")
                                                                                                }
                                                                                } else {
                                                                                                print("视频缓存失败:\(error?.localizedDescription ?? "")")
                                                                                }
                                                                }
                                                                downloadTask.resume()
                                                }
                }
 
                func playerEnd(){
                                player?.stop()
                                playComplete?()
                }
 
                //打断播放未完全播放
                func playerInterrupt(){
                                player?.stop()
                }
 
                func playEnd(course:@escaping ()->Void){
                                self.playComplete = course
                }
 
                func playSuccessVoice(){
                                let list = try? FileManager.default.contentsOfDirectory(atPath: voiceCacheDirectory.droppedScheme()!.absoluteString)
                                var promoteName:String?
                                for v in list ?? []{
                                                if v.contains("SuccessPromote"){promoteName = v;break}
                                }
                                guard let url = promoteName else { return }
                            let promote    = self.voiceCacheDirectory.appendingPathComponent(url)
                                tempPlayer = try? AVAudioPlayer(contentsOf: promote)
                                tempPlayer?.play()
                }
 
                func playFailVoice(){
                                let list = try? FileManager.default.contentsOfDirectory(atPath: voiceCacheDirectory.droppedScheme()!.absoluteString)
                                var promoteName:String?
                                for v in list ?? []{
                                                if v.contains("FailPromote"){promoteName = v;break}
                                }
                                guard let url = promoteName else { return }
                                let promote    = self.voiceCacheDirectory.appendingPathComponent(url)
                                tempPlayer = try? AVAudioPlayer(contentsOf: promote)
                                tempPlayer?.play()
                }
 
            static    func hasPromoteVoice()->Bool{
                            let list = try? FileManager.default.contentsOfDirectory(atPath: VoicePlayer.share().voiceCacheDirectory.droppedScheme()!.absoluteString)
                                return list?.contains(["SuccessPromote","FailPromote"]) ?? false
                }
 
                func donwloadPromoteVoice(successVoice:String,failVoice:String,updateTime:String){
                                print("-->\(VoicePlayer.share().voiceCacheDirectory)")
                                let group = DispatchGroup()
                                let promoteQueue = DispatchQueue(label: "promoteVoice")
                                promoteQueue.async(group: group){
                                                //文件不存在:执行下载
                                                let downloadTask = URLSession.shared.downloadTask(with: URL(string: successVoice)!) { tempLocalUrl, response, error in
                                                                if let tempLocalUrl = tempLocalUrl, error == nil {
                                                                                do {
                                                                                                let fileType = URL(fileURLWithPath: successVoice).lastPathComponent.components(separatedBy: ".").last ?? "mp3"
                                                                                                let finalCacheUrl = self.voiceCacheDirectory.appendingPathComponent("SuccessPromote.\(fileType)")
                                                                                                if FileManager.default.fileExists(atPath: finalCacheUrl.droppedScheme()!.absoluteString){
                                                                                                                try? FileManager.default.removeItem(at: finalCacheUrl)
                                                                                                }
                                                                                                try FileManager.default.moveItem(at: tempLocalUrl, to: finalCacheUrl)
                                                                                } catch {
                                                                                                print("视频缓存失败:catch")
                                                                                }
                                                                } else {
                                                                                print("视频缓存失败:\(error?.localizedDescription ?? "")")
                                                                }
                                                }
                                                downloadTask.resume()
                                }
 
                                promoteQueue.async(group: group){
                                                //文件不存在:执行下载
                                                let downloadTask = URLSession.shared.downloadTask(with: URL(string: failVoice)!) { tempLocalUrl, response, error in
                                                                if let tempLocalUrl = tempLocalUrl, error == nil {
                                                                                do {
                                                                                                let fileType = URL(fileURLWithPath: successVoice).lastPathComponent.components(separatedBy: ".").last ?? "mp3"
                                                                                                let finalCacheUrl = self.voiceCacheDirectory.appendingPathComponent("FailPromote.\(fileType)")
                                                                                                if FileManager.default.fileExists(atPath: finalCacheUrl.droppedScheme()!.absoluteString){
                                                                                                                try? FileManager.default.removeItem(at: finalCacheUrl)
                                                                                                }
                                                                                                try FileManager.default.moveItem(at: tempLocalUrl, to: finalCacheUrl)
                                                                                } catch {
                                                                                                print("视频缓存失败:catch")
                                                                                }
                                                                } else {
                                                                                print("视频缓存失败:\(error?.localizedDescription ?? "")")
                                                                }
                                                }
                                                downloadTask.resume()
                                }
 
                                group.notify(queue: .main){
                                                UserDefaults.standard.setValue(updateTime, forKey: "promptVoiceDate")
                                                UserDefaults.standard.synchronize()
                                }
                }
}
 
extension VoicePlayer:AVAudioPlayerDelegate{
                func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
                                DispatchQueue.main.async {
                                                VoicePlayer.share().playComplete?()
                                                self.delegate?.playComplete()
                                }
                }
 
                func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: (any Error)?) {
                                print("播放错误")
                }
}