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)"
}
}
}
}
+ 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{
}
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
-//
-// 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()
}
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)
+ }
}
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
}
}
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))
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()
}
-
}
var categoryId = ""
required public init() { }
}
+ class MusicDownloadModel:ResponseBaseModel {
+ var data = ""
+ }
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)
}
}