音乐播放支持锁屏播放
authorliangzhenying <867174124@qq.com>
Tue, 2 Dec 2025 10:14:28 +0000 (18:14 +0800)
committerliangzhenying <867174124@qq.com>
Tue, 2 Dec 2025 10:14:28 +0000 (18:14 +0800)
15 files changed:
ShenYin/ShenYin.xcodeproj/project.pbxproj
ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/20250911-142330.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Launch/AppDelegate.swift
ShenYin/ShenYin/Launch/SceneDelegate.swift
ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.h [deleted file]
ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.m [deleted file]
ShenYin/ShenYin/SDKBleManager/JLBleAssistManager.h
ShenYin/ShenYin/SDKBleManager/JLBleEntity.h [deleted file]
ShenYin/ShenYin/SDKBleManager/JLBleEntity.m [deleted file]
ShenYin/ShenYin/SDKBleManager/JL_RunSDK.h [deleted file]
ShenYin/ShenYin/SDKBleManager/JL_RunSDK.m [deleted file]
ShenYin/ShenYin/UI/Sleep/AudioPlayView.swift
ShenYin/ShenYin/UI/Sleep/AudioPlayerManager.swift
ShenYin/ShenYin/UI/Sleep/MusicPlayer.swift [new file with mode: 0644]

index 4903484..9856e4f 100644 (file)
                                        "$(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 = "";
diff --git a/ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/20250911-142330.png b/ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/20250911-142330.png
new file mode 100644 (file)
index 0000000..1484aa2
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/20250911-142330.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/Contents.json
new file mode 100644 (file)
index 0000000..349f9e6
--- /dev/null
@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "20250911-142330.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
index 75c219d..a316843 100644 (file)
@@ -13,6 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         // Override point for customization after application launch.
+        
         return true
     }
 
index 5b24ec6..b12effb 100644 (file)
@@ -50,6 +50,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
         // 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) {
diff --git a/ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.h b/ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.h
deleted file mode 100644 (file)
index e7a5a91..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-//  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
diff --git a/ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.m b/ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.m
deleted file mode 100644 (file)
index cc24682..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-//  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
index e0cba12..8a1334f 100644 (file)
@@ -10,7 +10,6 @@
 #import <JL_BLEKit/JL_BLEKit.h>
 //#import <DFUnits/DFUnits.h>
 #import <UIKit/UIKit.h>
-#import "JLBleEntity.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -41,7 +40,6 @@ extern NSString *kFLT_BLE_DISCONNECTED;  //断开连接
 
 @property (assign,nonatomic)BOOL isConnected;
 
-@property (strong, nonatomic) JLBleEntity *__nullable currentEntity;
 
 
 /**
diff --git a/ShenYin/ShenYin/SDKBleManager/JLBleEntity.h b/ShenYin/ShenYin/SDKBleManager/JLBleEntity.h
deleted file mode 100644 (file)
index fb237b3..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-//  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
diff --git a/ShenYin/ShenYin/SDKBleManager/JLBleEntity.m b/ShenYin/ShenYin/SDKBleManager/JLBleEntity.m
deleted file mode 100644 (file)
index 1aaf5a9..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-//  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
diff --git a/ShenYin/ShenYin/SDKBleManager/JL_RunSDK.h b/ShenYin/ShenYin/SDKBleManager/JL_RunSDK.h
deleted file mode 100644 (file)
index 344349e..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-//  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
diff --git a/ShenYin/ShenYin/SDKBleManager/JL_RunSDK.m b/ShenYin/ShenYin/SDKBleManager/JL_RunSDK.m
deleted file mode 100644 (file)
index 996b0a5..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-//
-//  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
index bd8db62..67cab35 100644 (file)
@@ -119,7 +119,7 @@ class AudioPlayView: BaseView {
         }
     }
     @objc func closeAction(){
-        AudioPlayerManager.shared.pause()
+        AudioPlayerManager.shared.stop()
         self.isHidden = true
     }
     func show(){
index 46831be..e1f6583 100644 (file)
 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)
     }
 }
diff --git a/ShenYin/ShenYin/UI/Sleep/MusicPlayer.swift b/ShenYin/ShenYin/UI/Sleep/MusicPlayer.swift
new file mode 100644 (file)
index 0000000..6911301
--- /dev/null
@@ -0,0 +1,184 @@
+//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)
+//    }
+//}