增加音乐播放
authorliangzhenying <867174124@qq.com>
Fri, 10 Oct 2025 07:17:02 +0000 (15:17 +0800)
committerliangzhenying <867174124@qq.com>
Fri, 10 Oct 2025 07:17:02 +0000 (15:17 +0800)
ShenYin/ShenYin/Api/HttpClient2.swift
ShenYin/ShenYin/Resource/大约在冬季.mp3 [new file with mode: 0644]
ShenYin/ShenYin/UI/Base/BaseVC.swift
ShenYin/ShenYin/UI/Sleep/AudioPlayerManager.swift
ShenYin/ShenYin/UI/Sleep/AudioPlayerVC.swift
ShenYin/ShenYin/UI/Sleep/MusicModel.swift
ShenYin/ShenYin/UI/Sleep/SleepVM.swift

index 49477c7..cb99eba 100644 (file)
@@ -95,7 +95,7 @@ extension CommomApi{
         case .musicCategoryList(let categoryId,let page):
             return "/res/sleep-music/list/\(page)?categoryId=\(categoryId)"
         case .musicUrl( let id):
-            return "/res/voice-file/play/\(id)"
+            return "/res/music-file/path/\(id)"
         }
     }
     
@@ -185,6 +185,41 @@ class HttpClient{
         }
     }
     
+    static func download(url:String,fileId:String,completionHandler:@escaping ((String?) -> Void)){
+        Logger.debug("dowload => ",url)
+        if let request = URLRequest(urlString:url) {
+            let task = HTTP(request, isDownload: true)
+            task.progress = { progress in
+                Logger.debug("下载进度:\(progress)") // 进度值在 0 到 1 之间
+            }
+            task.downloadHandler = { response, tempUrl in
+                if let destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(fileId).mp3") {
+                    if FileManager.default.fileExists(atPath: destinationUrl.path) {
+                        Logger.debug("文件已经存在,先移除 => ",url)
+                        do {
+                            try FileManager.default.removeItem(atPath: destinationUrl.path)
+                        }catch{
+                            Logger.debug("移除文件失败:\(error)")
+                            completionHandler(nil)
+                        }
+                    }
+                    do {
+                        try FileManager.default.moveItem(at: tempUrl, to: destinationUrl)
+                        Logger.debug("文件下载成功,保存路径:\(destinationUrl.path)")
+                        completionHandler(destinationUrl.path)
+                    } catch {
+                        Logger.debug("移动文件失败:\(error)")
+                        completionHandler(nil)
+                    }
+                }
+                else{
+                    completionHandler(nil)
+                }
+            }
+            task.run()
+        }
+    }
+    
 }
 
 extension HttpClient{
diff --git a/ShenYin/ShenYin/Resource/大约在冬季.mp3 b/ShenYin/ShenYin/Resource/大约在冬季.mp3
new file mode 100644 (file)
index 0000000..4209e87
Binary files /dev/null and "b/ShenYin/ShenYin/Resource/\345\244\247\347\272\246\345\234\250\345\206\254\345\255\243.mp3" differ
index ae612d9..814542b 100644 (file)
@@ -85,7 +85,7 @@ open class BaseVC:UIViewController {
     }
     
     func addLog(){
-        logBtn.frame = CGRect(x:  UIScreen.main.bounds.size.width - 80, y: 200, width:60, height: 60)
+        logBtn.frame = CGRect(x:  UIScreen.main.bounds.size.width - 80, y: kScreenHei - 200, width:60, height: 60)
         logBtn.backgroundColor = .blue
         logBtn.layer.cornerRadius = 30
         logBtn.layer.masksToBounds = true
index 74225cc..32995b0 100644 (file)
@@ -1,19 +1,50 @@
-//
-//  AudioPlayer.swift
-//  ShenYin
-//
-//  Created by liangshi on 2025/10/9.
-//
-
 import AVFoundation
 
 class AudioPlayerManager {
+    static let shared = AudioPlayerManager()
+    
     var player: AVPlayer?
     var playerItem: AVPlayerItem?
+    var timeObserverToken: Any?
     
-    func play(url: URL) {
+    typealias ProgressCallback = (_ current: Float, _ duration: Float) -> Void
+    var progressCallback: ProgressCallback?
+    
+    func play(filePath: String) {
+        Logger.debug("播放路径", filePath)
+        let url = URL(fileURLWithPath: filePath)
         playerItem = AVPlayerItem(url: url)
         player = AVPlayer(playerItem: playerItem)
+        setupProgressObserver()
+        setupPlayToEndObserver()
+    }
+    
+    func setupProgressObserver() {
+        guard let player = player else { return }
+        let interval = CMTimeMake(value: 1, timescale: 10) // 每 0.1 秒调用一次回调
+        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [weak self] time in
+            guard let self = self, let duration = self.playerItem?.duration else { return }
+            if CMTimeGetSeconds(duration) > 0 {
+                let currentProgress = Float(CMTimeGetSeconds(time))
+                self.progressCallback?(currentProgress, Float(CMTimeGetSeconds(duration)))
+            }
+        }
+    }
+    
+    func setupPlayToEndObserver() {
+        NotificationCenter.default.addObserver(forName: Notification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem, queue: nil) { [weak self] _ in
+            guard let self = self else { return }
+            self.restartPlayback()
+        }
+    }
+    
+    func restartPlayback() {
+        guard let player = player, let playerItem = playerItem else { return }
+        player.seek(to: CMTime.zero)
+        player.play()
+    }
+    
+    func start() {
         player?.play()
     }
     
@@ -21,9 +52,19 @@ class AudioPlayerManager {
         player?.pause()
     }
     
-    func setProgress(_ progress: Float) {
-        let duration = CMTimeGetSeconds((player?.currentItem?.duration)!)
-        let seekTime = CMTimeMakeWithSeconds(Float64(progress) * duration, preferredTimescale: 1)
+    func stop() {
+        player?.pause()
+    }
+    
+    func setDuration(_ duration: Float) {
+        let seekTime = CMTimeMakeWithSeconds(Float64(duration), preferredTimescale: 1)
         player?.seek(to: seekTime)
     }
+    
+    deinit {
+        if let token = timeObserverToken {
+            player?.removeTimeObserver(token)
+        }
+        NotificationCenter.default.removeObserver(self, name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
+    }
 }
index 257b629..245433d 100644 (file)
@@ -78,6 +78,14 @@ class AudioPlayerVC:BaseVC{
             v.setImage(UIImage(named: "music_pause"), for: .selected)
             v.addTarget(self, action: #selector(btnAction), for: .touchUpInside)
         }
+        AudioPlayerManager.shared.progressCallback = {[weak self] current,duration in
+            guard let self = self else {return}
+            Logger.debug(duration)
+            slider.maximumValue = duration
+            slider.setValue(current, animated: true)
+            titleLabel.text = YLToos.formatSeconds1(Int(current))
+            valueLabel.text = YLToos.formatSeconds1(Int(duration))
+        }
     }
     override func setupLayout() {
         bgView.snp.makeConstraints { make in
@@ -114,21 +122,27 @@ class AudioPlayerVC:BaseVC{
         }
     }
     override func setupData() {
-
         SleepVM.getMusicUrl(filedId: musicModel.fileId) { [weak self] model in
             guard let self = self else{return}
-            
+            if model.isSuccess{
+                startDownload(url: model.data,fileId: musicModel.fileId)
+            }
         }
     }
     
-    override func navRightViewTapped() {
-        
-    }
-    @objc func addAction(){
-        
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        AudioPlayerManager.shared.pause()
     }
+
     @objc func btnAction(){
         playBtn.isSelected = !playBtn.isSelected
+        if playBtn.isSelected {
+            AudioPlayerManager.shared.pause()
+        }
+        else{
+            AudioPlayerManager.shared.start()
+        }
     }
     @objc func sliderChanged(_ slider:UISlider){
         let value = YLToos.formatSeconds1(Int(slider.value))
@@ -136,6 +150,21 @@ class AudioPlayerVC:BaseVC{
         if let text = titleLabel.text, text != value {
             titleLabel.text = value
         }
+        
+        AudioPlayerManager.shared.setDuration(slider.value)
+    }
+    func startDownload(url:String,fileId:String){
+//        let url = "https://dc-cloud-priv.tos-cn-guangzhou.volces.com/music-file/2025_10_09/1976135290430767105.mp3"
+        HttpClient.download(url: url,fileId: fileId){[weak self] filePath in
+            guard let self = self else {return}
+            if let path = filePath{
+                startPlay(path: path)
+            }
+        }
+    }
+    func startPlay(path:String){
+        guard let path = Bundle.main.path(forResource: "大约在冬季", ofType: "mp3") else {return}
+        AudioPlayerManager.shared.play(filePath: path)
+        AudioPlayerManager.shared.start()
     }
-    
 }
index 5ff12e3..8bc1034 100644 (file)
@@ -29,3 +29,6 @@ class MusicModel:HandyJSON {
     var categoryId = ""
     required public init() { }
 }
+    class MusicDownloadModel:ResponseBaseModel {
+        var data = ""
+    }
index b0eeb1e..9a09fa2 100644 (file)
@@ -57,9 +57,9 @@ extension SleepVM{
             completion(model)
         }
     }
-    static public func getMusicUrl(filedId id:String, completion:@escaping (_ result:ResponseBaseModel) ->Void) {
+    static public func getMusicUrl(filedId id:String, completion:@escaping (_ result:MusicDownloadModel) ->Void) {
         let api = CommomApi.musicUrl(filedId: id)
-        HttpClient.request(api) { (model:MusicCategoryRoot,res) in
+        HttpClient.request(api) { (model:MusicDownloadModel,res) in
             completion(model)
         }
     }