"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0;
+ MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.deepchords.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0;
+ MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.deepchords.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
--- /dev/null
+{
+ "images" : [
+ {
+ "filename" : "20250911-142330.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
+
return true
}
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
App.shared.isInBackground = true
+
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
+++ /dev/null
-//
-// HandleBroadcastPtl.h
-// JL_OTA
-//
-// Created by EzioChan on 2023/1/30.
-// Copyright © 2023 Zhuhia Jieli Technology. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface HandleBroadcastPtl : NSObject
-@property(nonatomic,strong,readonly)NSHashTable *delegates;
-@property(nonatomic,strong)NSLock *delegateLock;
-
--(void)addDelegate:(id)delegate;
--(void)removeDelegate:(id)delegate;
--(void)removeAll;
-@end
-
-NS_ASSUME_NONNULL_END
+++ /dev/null
-//
-// HandleBroadcastPtl.m
-// JL_OTA
-//
-// Created by EzioChan on 2023/1/30.
-// Copyright © 2023 Zhuhia Jieli Technology. All rights reserved.
-//
-
-#import "HandleBroadcastPtl.h"
-
-@interface HandleBroadcastPtl()
-
-@end
-
-@implementation HandleBroadcastPtl
-
-- (instancetype)init
-{
- self = [super init];
- if (self) {
- _delegates = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
- }
- return self;
-}
-
--(NSLock *)delegateLock{
- if (_delegateLock == nil) {
- _delegateLock = [NSLock new];
- }
- return _delegateLock;
-}
-
--(void)addDelegate:(id)delegate{
- [self.delegateLock lock];
- if (![self.delegates containsObject:delegate]) {
- [self.delegates addObject:delegate];
- }
- [self.delegateLock unlock];
-}
--(void)removeDelegate:(id)delegate{
- [self.delegateLock lock];
- if ([self.delegates containsObject:delegate]) {
- [self.delegates removeObject:delegate];
- }
- [self.delegateLock unlock];
-}
--(void)removeAll{
- [self.delegateLock lock];
- [self.delegates removeAllObjects];
- [self.delegateLock unlock];
-}
-
-@end
#import <JL_BLEKit/JL_BLEKit.h>
//#import <DFUnits/DFUnits.h>
#import <UIKit/UIKit.h>
-#import "JLBleEntity.h"
NS_ASSUME_NONNULL_BEGIN
@property (assign,nonatomic)BOOL isConnected;
-@property (strong, nonatomic) JLBleEntity *__nullable currentEntity;
/**
+++ /dev/null
-//
-// JLBleEntity.h
-// JL_OTA
-//
-// Created by 凌煊峰 on 2021/10/11.
-//
-
-#import <Foundation/Foundation.h>
-#import <CoreBluetooth/CoreBluetooth.h>
-
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface JLBleEntity : NSObject //蓝牙设备模型
-
-@property (strong, nonatomic) NSNumber *mRSSI;
-@property (strong, nonatomic) CBPeripheral *mPeripheral;
-@property (strong, nonatomic) NSString *mName;
-@property (strong, nonatomic) NSString *bleMacAddress;
-@property (strong, nonatomic) NSString *edrMacAddress;
-@property (assign, nonatomic) uint8_t mType;
-@property (assign, nonatomic) uint16_t pid;
-@property (assign, nonatomic) uint16_t uid;
-
-@end
-
-NS_ASSUME_NONNULL_END
+++ /dev/null
-//
-// JLBleEntity.m
-// JL_OTA
-//
-// Created by 凌煊峰 on 2021/10/11.
-//
-
-#import "JLBleEntity.h"
-
-@implementation JLBleEntity
-
-- (nonnull id)copyWithZone:(nullable NSZone *)zone {
- JLBleEntity *entity = [[self class] allocWithZone:zone];
- entity.mRSSI = self.mRSSI;
- entity.mPeripheral = self.mPeripheral;
- entity.mName = self.mName;
- entity.bleMacAddress = self.bleMacAddress;
- entity.edrMacAddress = self.edrMacAddress;
- entity.pid = self.pid;
- entity.uid = self.uid;
- entity.mType = self.mType;
- return entity;
-}
-
-@end
+++ /dev/null
-//
-// JL_RunSDK.h
-// JL_OTA_InnerBle
-//
-// Created by 凌煊峰 on 2021/10/9.
-//
-
-#import <Foundation/Foundation.h>
-#import <JL_BLEKit/JL_BLEKit.h>
-
-#define SMALL_V @"v"
-
-#define BIG_V @"V"
-
-#define kJL_AGRESS_PROTOCOL @"agress_protocol"
-
-#define KEY_COMMIT_PROTOCOL @"commit_protocol"
-
-#define kJL_HeightNavBar ([UIApplication sharedApplication].delegate.window.safeAreaInsets.top+44.0)
-
-
-NS_ASSUME_NONNULL_BEGIN
-
-@protocol JL_RunSDKOtaDelegate <NSObject>
-
-@required
-/**
- * ota升级过程状态回调
- */
-- (void)otaProgressWithOtaResult:(JL_OTAResult)result withProgress:(float)progress;
-
-@end
-
-@interface JL_RunSDK : NSObject
-
-@property (strong, nonatomic) JL_BLEMultiple *mBleMultiple;
-@property (weak, nonatomic) JL_EntityM *__nullable mBleEntityM;
-@property (weak, nonatomic) id<JL_RunSDKOtaDelegate> otaDelegate;
-
-/**
- * 单例
- */
-+ (instancetype)sharedInstance;
-
-//+ (NSString *)textEntityStatus:(JL_EntityM_Status)status;
-- (JL_EntityM *)getEntity:(NSString *)uuid;
-
-#pragma mark - 杰理蓝牙库OTA流程相关业务
-
-typedef void(^GET_DEVICE_CALLBACK)(BOOL needForcedUpgrade);
-
-
--(void)startLoopConnect:(NSString *)uuid;
-
-/**
- * 获取已连接的蓝牙设备信息
- */
-- (void)getDeviceInfo:(GET_DEVICE_CALLBACK _Nonnull)callback;
-
-/**
- * OTA升级
- */
-- (void)otaFuncWithFilePath:(NSString *)otaFilePath;
-
-+(double)getWidthWithString:(NSString*)str font:(UIFont*)font;
-
-+(NSAttributedString *)getAttributeWith:(id)sender string:(NSString *)string orginFont:(CGFloat)orginFont orginColor:(UIColor *)orginColor attributeFont:(CGFloat)attributeFont attributeColor:(UIColor *)attributeColor;
-
-@end
-
-NS_ASSUME_NONNULL_END
+++ /dev/null
-//
-// JL_RunSDK.m
-// JL_OTA_InnerBle
-//
-// Created by 凌煊峰 on 2021/10/9.
-//
-
-#import "JL_RunSDK.h"
-
-@interface JL_RunSDK() <JL_ManagerMDelegate>
-
-@property (strong, nonatomic) NSString *selectedOtaFilePath;
-@property (strong, nonatomic) NSString *connectUUID;
-
-@end
-
-@implementation JL_RunSDK
-
-
-//+ (NSString *)textEntityStatus:(JL_EntityM_Status)status {
-// if (status < 0) return kJL_TXT("未知错误");
-// NSArray *arr = @[kJL_TXT("蓝牙未开启"), kJL_TXT("连接失败"), kJL_TXT("正在连接"), kJL_TXT("重复连接"),
-// kJL_TXT("连接超时"), kJL_TXT("被拒绝"), kJL_TXT("配对失败"), kJL_TXT("配对超时"), kJL_TXT("已配对"),
-// kJL_TXT("正在主从切换"), kJL_TXT("断开成功"), kJL_TXT("请打开蓝牙")];
-// if (status+1 <= arr.count) {
-// return arr[status];
-// } else {
-// return kJL_TXT("未知错误");
-// }
-//}
-
-+ (instancetype)sharedInstance {
- static JL_RunSDK *singleton = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- singleton = [[self alloc] init];
- });
- return singleton;
-}
-
-- (instancetype)init {
- self = [super init];
- if (self) {
- /*--- 初始化JL_SDK ---*/
- self.mBleMultiple = [[JL_BLEMultiple alloc] init];
- self.mBleMultiple.BLE_FILTER_ENABLE = YES;
- self.mBleMultiple.BLE_PAIR_ENABLE = YES;
- self.mBleMultiple.BLE_TIMEOUT = 7;
- self.connectUUID = nil;
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noteListen:) name:nil object:nil];
- }
- return self;
-}
-
-
--(void)noteListen:(NSNotification *)note{
- NSString *name = note.name;
- if(!self.connectUUID) return;
- if([name isEqual:kJL_BLE_M_FOUND]){
- NSArray *itemsArray = [note object];
- __weak typeof(self) weakSelf = self;
- for (JL_EntityM *entity in itemsArray) {
- if([entity.mPeripheral.identifier.UUIDString isEqualToString:self.connectUUID]){
- [self.mBleMultiple scanStop];
- self.connectUUID = nil;
- [self.mBleMultiple connectEntity:entity Result:^(JL_EntityM_Status status) {
- if(status == JL_EntityM_StatusPaired){
- weakSelf.mBleEntityM = entity;
- }
- }];
- }
- }
-
- }
-}
-
--(void)startLoopConnect:(NSString *)uuid{
- self.connectUUID = uuid;
- [self.mBleMultiple scanStart];
-}
-
-
-- (JL_EntityM *)getEntity:(NSString *)uuid {
- NSMutableArray *mConnectArr = self.mBleMultiple.bleConnectedArr;
- for (JL_EntityM *entity in mConnectArr) {
- NSString *inUnid = entity.mPeripheral.identifier.UUIDString;
- if ([uuid isEqual:inUnid]) {
- return entity;
- }
- }
- return nil;
-}
-
-
--(void)handelDeviceInfo{
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [self getDeviceInfo:^(BOOL needForcedUpgrade) {
-
- }];
- });
-}
-
-#pragma mark - 杰理蓝牙库OTA流程相关业务
-
-/**
- * 获取已连接的蓝牙设备信息,这里如果上次设备升级没有成功,会要求执行otaFuncWithFilePath:强制升级
- */
-- (void)getDeviceInfo:(GET_DEVICE_CALLBACK _Nonnull)callback {
- __weak typeof(self) weakSelf = self;
- /*--- 获取设备信息 ---*/
- [self.mBleEntityM.mCmdManager cmdTargetFeatureResult:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
- if (status == JL_CMDStatusSuccess) {
- JLModel_Device *model = [weakSelf.mBleEntityM.mCmdManager outputDeviceModel];
- JL_OtaStatus upSt = model.otaStatus;
- if (upSt == JL_OtaStatusForce) {
- kJLLog(JLLOG_DEBUG, @"---> 进入强制升级.");
- if (weakSelf.selectedOtaFilePath) {
- [weakSelf otaFuncWithFilePath:weakSelf.selectedOtaFilePath];
- } else {
- callback(true);
- }
- return;
- } else {
- if (model.otaHeadset == JL_OtaHeadsetYES) {
- kJLLog(JLLOG_DEBUG, @"---> 进入强制升级: OTA另一只耳机.");
- if (weakSelf.selectedOtaFilePath) {
- [weakSelf otaFuncWithFilePath:weakSelf.selectedOtaFilePath];
- } else {
- callback(true);
- }
- return;
- }
- }
- kJLLog(JLLOG_DEBUG, @"---> 设备正常使用...");
- [JL_Tools mainTask:^{
- /*--- 获取公共信息 ---*/
- [weakSelf.mBleEntityM.mCmdManager cmdGetSystemInfo:JL_FunctionCodeCOMMON Result:nil];
- callback(false);
- }];
- } else {
- kJLLog(JLLOG_DEBUG, @"---> ERROR:设备信息获取错误!");
- }
- }];
-}
-
-- (void)otaFuncWithFilePath:(NSString *)otaFilePath {
- kJLLog(JLLOG_DEBUG, @"current otaFilePath ---> %@", otaFilePath);
- self.selectedOtaFilePath = otaFilePath;
- __weak typeof(self) weakSelf = self;
- [self.mBleMultiple otaFuncWithEntityM:self.mBleEntityM withFilePath:otaFilePath Result:^(JL_OTAResult result, float progress) {
- if ([weakSelf.otaDelegate respondsToSelector:@selector(otaProgressWithOtaResult:withProgress:)]) {
- [weakSelf.otaDelegate otaProgressWithOtaResult:result withProgress:progress];
- }
- }];
-}
-
-+(double)getWidthWithString:(NSString*)str font:(UIFont*)font{
- NSDictionary *dict = @{NSFontAttributeName:font};
- CGSize detailSize = [str sizeWithAttributes:dict];
- return detailSize.width;
-}
-
-+ (NSString *)getStringWithRange:(NSRange)range
-{
- NSMutableString *string = [NSMutableString string];
- for (int i = 0; i < range.length ; i++) {
- [string appendString:@" "];
- }
- return string;
-}
-
-@end
}
}
@objc func closeAction(){
- AudioPlayerManager.shared.pause()
+ AudioPlayerManager.shared.stop()
self.isHidden = true
}
func show(){
import AVFoundation
+import MediaPlayer
class AudioPlayerManager {
static let shared = AudioPlayerManager()
- var musicModel:MusicModel?
- var isPlaying:Bool{
- guard let player = player else {
+
+ var musicModel: MusicModel?
+ var isPlaying: Bool {
+ guard let player = audioPlayer else {
return false
}
- return player.timeControlStatus == .playing
+ return player.isPlaying
}
- var player: AVPlayer?
- var playerItem: AVPlayerItem?
- var timeObserverToken: Any?
+
+ private var audioPlayer: AVAudioPlayer?
+ private var isLooping = true
+ private var progressTimer: Timer?
+
+ var currentProgress: Float = 0
+ var duration: Float = 0
typealias ProgressCallback = (_ current: Float, _ duration: Float) -> Void
var progressCallback: ProgressCallback?
- var currentProgress:Float = 0
- var duration:Float = 0
- func play(filePath: String) {
- stop()
-
- Logger.debug("播放路径", filePath)
- let url = URL(fileURLWithPath: filePath)
- playerItem = AVPlayerItem(url: url)
- player = AVPlayer(playerItem: playerItem)
- setupProgressObserver()
- setupPlayToEndObserver()
+ func play(filePath path: String) {
+ do {
+ try setupAudioSession()
+ let url = URL(fileURLWithPath: path)
+ audioPlayer = try AVAudioPlayer(contentsOf: url)
+ audioPlayer?.numberOfLoops = isLooping ? -1 : 0
+ audioPlayer?.prepareToPlay()
+ audioPlayer?.play()
+
+ setupNowPlayingInfoCenter(url)
+ setupRemoteControl()
+ startProgressMonitoring()
+ } catch let error {
+ print("Error playing music: \(error.localizedDescription)")
+ }
}
- 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 total = self.playerItem?.duration else { return }
- if CMTimeGetSeconds(total) > 0 {
- currentProgress = Float(CMTimeGetSeconds(time))
- duration = Float(CMTimeGetSeconds(total))
- self.progressCallback?(currentProgress,duration )
- }
- }
+
+ func start() {
+ audioPlayer?.play()
+ startProgressMonitoring()
}
- 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 stop() {
+ audioPlayer?.stop()
+ stopProgressMonitoring()
+
+ // 停止音频会话并设置为非活动状态
+ do {
+ let audioSession = AVAudioSession.sharedInstance()
+ try audioSession.setActive(false)
+ } catch {
+ print("Failed to deactivate audio session: \(error)")
}
+
+ // 停止接收远程控制事件
+ UIApplication.shared.endReceivingRemoteControlEvents()
+ // 清除MPNowPlayingInfoCenter中的信息
+ let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
+ nowPlayingInfoCenter.nowPlayingInfo = nil
}
-
- func restartPlayback() {
- guard let player = player, let playerItem = playerItem else { return }
- player.seek(to: CMTime.zero)
- player.play()
+ func pause() {
+ audioPlayer?.pause()
+ stopProgressMonitoring()
}
+ func resumeMusic() {
+ audioPlayer?.play()
+ startProgressMonitoring()
+ }
+ func setDuration(_ progress: Float) {
+ let pro = TimeInterval(progress)
+ guard let player = audioPlayer, pro >= 0 && pro <= player.duration else { return }
+ audioPlayer?.currentTime = pro
+ }
- func start() {
- player?.play()
+ private func setupNowPlayingInfoCenter(_ url: URL) {
+ let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
+ var nowPlayingInfo = [String : Any]()
+
+ if let model = musicModel{
+ nowPlayingInfo[MPMediaItemPropertyTitle] = model.name
+// nowPlayingInfo[MPMediaItemPropertyArtist] = "Unknown Artist"
+ let albumArtwork = MPMediaItemArtwork(boundsSize: CGSize(width: 100, height: 100)) { _ in
+ return UIImage(named: "logo_2")!
+ }
+ nowPlayingInfo[MPMediaItemPropertyArtwork] = albumArtwork
+ }
+ else{
+ nowPlayingInfo[MPMediaItemPropertyTitle] = url.lastPathComponent
+ nowPlayingInfo[MPMediaItemPropertyArtist] = "Unknown Artist"
+ }
+
+ // 设置音乐时长
+ if let player = audioPlayer {
+ nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player.duration
+ }
+
+ // 设置当前播放进度(初始为 0)
+ nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = 0
+
+ nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
}
- func pause() {
- player?.pause()
+ private func setupRemoteControl() {
+ UIApplication.shared.beginReceivingRemoteControlEvents()
+ let commandCenter = MPRemoteCommandCenter.shared()
+
+ commandCenter.playCommand.addTarget { [weak self] event in
+ self?.resumeMusic()
+ return.success
+ }
+
+ commandCenter.pauseCommand.addTarget { [weak self] event in
+ self?.pause()
+ return.success
+ }
}
- func stop() {
- player?.pause()
+ private func startProgressMonitoring() {
+ progressTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
+ guard let self = self, let player = self.audioPlayer else { return }
+ self.currentProgress = Float(player.currentTime)
+ self.duration = Float(player.duration)
+
+ // 更新 MPNowPlayingInfoCenter 中的播放进度
+ let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
+ var nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo ?? [:]
+ nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime
+ nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
+
+ self.progressCallback?(self.currentProgress, self.duration)
+ }
+ RunLoop.current.add(progressTimer!, forMode:.common)
}
- func setDuration(_ duration: Float) {
- let seekTime = CMTimeMakeWithSeconds(Float64(duration), preferredTimescale: 1)
- player?.seek(to: seekTime)
+ private func stopProgressMonitoring() {
+ progressTimer?.invalidate()
+ progressTimer = nil
}
- deinit {
- if let token = timeObserverToken {
- player?.removeTimeObserver(token)
- }
- NotificationCenter.default.removeObserver(self, name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
+ private func setupAudioSession() throws {
+ let audioSession = AVAudioSession.sharedInstance()
+ try audioSession.setCategory(.playback, mode:.default)
+ try audioSession.setActive(true)
}
}
--- /dev/null
+//import AVFoundation
+//import MediaPlayer
+//
+//class MusicPlayer {
+// static let shared = MusicPlayer()
+//
+// var musicModel:MusicModel?
+// var isPlaying:Bool{
+// guard let player = audioPlayer else {
+// return false
+// }
+// return player.isPlaying
+// }
+//
+//
+// private var audioPlayer: AVAudioPlayer?
+// private var isLooping = true
+// private var progressTimer: Timer?
+//
+// var currentProgress:Float = 0
+// var duration:Float = 0
+//
+// typealias ProgressCallback = (_ current: Float, _ duration: Float) -> Void
+// var progressCallback: ProgressCallback?
+//
+// func playMusic(with path: String) {
+// do {
+// let url = URL(fileURLWithPath: path)
+// audioPlayer = try AVAudioPlayer(contentsOf: url)
+// audioPlayer?.numberOfLoops = isLooping ? -1 : 0
+// audioPlayer?.prepareToPlay()
+// audioPlayer?.play()
+//
+// setupNowPlayingInfoCenter(url)
+// setupRemoteControl()
+// startProgressMonitoring()
+// } catch let error {
+// print("Error playing music: \(error.localizedDescription)")
+// }
+// }
+//
+// func start() {
+// audioPlayer?.play()
+// startProgressMonitoring()
+// }
+//
+//
+// func pauseMusic() {
+// audioPlayer?.pause()
+// stopProgressMonitoring()
+// }
+//
+// func resumeMusic() {
+// audioPlayer?.play()
+// startProgressMonitoring()
+// }
+//
+// private func setupNowPlayingInfoCenter(_ url: URL) {
+// let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
+// var nowPlayingInfo = [String : Any]()
+//
+// // 假设音乐文件有元数据,这里简单设置标题为文件名
+// nowPlayingInfo[MPMediaItemPropertyTitle] = url.lastPathComponent
+// // 你可以根据需要添加更多元数据,如艺术家等
+//
+// nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
+// }
+//
+// private func setupRemoteControl() {
+// UIApplication.shared.beginReceivingRemoteControlEvents()
+// let commandCenter = MPRemoteCommandCenter.shared()
+//
+// commandCenter.playCommand.addTarget { [weak self] event in
+// self?.resumeMusic()
+// return.success
+// }
+//
+// commandCenter.pauseCommand.addTarget { [weak self] event in
+// self?.pauseMusic()
+// return.success
+// }
+// }
+//
+// private func startProgressMonitoring() {
+// progressTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
+// guard let self = self, let player = self.audioPlayer else { return }
+// currentProgress = Float(player.currentTime)
+// duration = Float(player.duration)
+// self.progressCallback?(currentProgress, duration)
+// }
+// RunLoop.current.add(progressTimer!, forMode:.common)
+// }
+//
+// private func stopProgressMonitoring() {
+// progressTimer?.invalidate()
+// progressTimer = nil
+// }
+//}
+
+
+//import AVFoundation
+//
+//class AudioPlayerManager {
+// static let shared = AudioPlayerManager()
+// var musicModel:MusicModel?
+// var isPlaying:Bool{
+// guard let player = player else {
+// return false
+// }
+// return player.timeControlStatus == .playing
+// }
+//
+// var player: AVPlayer?
+// var playerItem: AVPlayerItem?
+// var timeObserverToken: Any?
+//
+// typealias ProgressCallback = (_ current: Float, _ duration: Float) -> Void
+// var progressCallback: ProgressCallback?
+// var currentProgress:Float = 0
+// var duration:Float = 0
+//
+// func play(filePath: String) {
+// stop()
+//
+// 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 total = self.playerItem?.duration else { return }
+// if CMTimeGetSeconds(total) > 0 {
+// currentProgress = Float(CMTimeGetSeconds(time))
+// duration = Float(CMTimeGetSeconds(total))
+// self.progressCallback?(currentProgress,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()
+// }
+//
+// func pause() {
+// player?.pause()
+// }
+//
+// 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)
+// }
+//}