From: liangzhenying <867174124@qq.com> Date: Tue, 2 Dec 2025 10:14:28 +0000 (+0800) Subject: 音乐播放支持锁屏播放 X-Git-Tag: 1.0.2~48 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=95cc6238bf53f5faca60320d2aba6382b11e26b4;p=mirrors%2FShenYin.git 音乐播放支持锁屏播放 --- diff --git a/ShenYin/ShenYin.xcodeproj/project.pbxproj b/ShenYin/ShenYin.xcodeproj/project.pbxproj index 4903484..9856e4f 100644 --- a/ShenYin/ShenYin.xcodeproj/project.pbxproj +++ b/ShenYin/ShenYin.xcodeproj/project.pbxproj @@ -281,7 +281,7 @@ "$(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 = ""; @@ -328,7 +328,7 @@ "$(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 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 index 0000000..349f9e6 --- /dev/null +++ b/ShenYin/ShenYin/Assets.xcassets/logo_2.imageset/Contents.json @@ -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 + } +} diff --git a/ShenYin/ShenYin/Launch/AppDelegate.swift b/ShenYin/ShenYin/Launch/AppDelegate.swift index 75c219d..a316843 100644 --- a/ShenYin/ShenYin/Launch/AppDelegate.swift +++ b/ShenYin/ShenYin/Launch/AppDelegate.swift @@ -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 } diff --git a/ShenYin/ShenYin/Launch/SceneDelegate.swift b/ShenYin/ShenYin/Launch/SceneDelegate.swift index 5b24ec6..b12effb 100644 --- a/ShenYin/ShenYin/Launch/SceneDelegate.swift +++ b/ShenYin/ShenYin/Launch/SceneDelegate.swift @@ -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 index e7a5a91..0000000 --- a/ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.h +++ /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 - - -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 index cc24682..0000000 --- a/ShenYin/ShenYin/SDKBleManager/HandleBroadcastPtl.m +++ /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 diff --git a/ShenYin/ShenYin/SDKBleManager/JLBleAssistManager.h b/ShenYin/ShenYin/SDKBleManager/JLBleAssistManager.h index e0cba12..8a1334f 100644 --- a/ShenYin/ShenYin/SDKBleManager/JLBleAssistManager.h +++ b/ShenYin/ShenYin/SDKBleManager/JLBleAssistManager.h @@ -10,7 +10,6 @@ #import //#import #import -#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 index fb237b3..0000000 --- a/ShenYin/ShenYin/SDKBleManager/JLBleEntity.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// JLBleEntity.h -// JL_OTA -// -// Created by 凌煊峰 on 2021/10/11. -// - -#import -#import - - -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 index 1aaf5a9..0000000 --- a/ShenYin/ShenYin/SDKBleManager/JLBleEntity.m +++ /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 index 344349e..0000000 --- a/ShenYin/ShenYin/SDKBleManager/JL_RunSDK.h +++ /dev/null @@ -1,71 +0,0 @@ -// -// JL_RunSDK.h -// JL_OTA_InnerBle -// -// Created by 凌煊峰 on 2021/10/9. -// - -#import -#import - -#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 - -@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 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 index 996b0a5..0000000 --- a/ShenYin/ShenYin/SDKBleManager/JL_RunSDK.m +++ /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() - -@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 diff --git a/ShenYin/ShenYin/UI/Sleep/AudioPlayView.swift b/ShenYin/ShenYin/UI/Sleep/AudioPlayView.swift index bd8db62..67cab35 100644 --- a/ShenYin/ShenYin/UI/Sleep/AudioPlayView.swift +++ b/ShenYin/ShenYin/UI/Sleep/AudioPlayView.swift @@ -119,7 +119,7 @@ class AudioPlayView: BaseView { } } @objc func closeAction(){ - AudioPlayerManager.shared.pause() + AudioPlayerManager.shared.stop() self.isHidden = true } func show(){ diff --git a/ShenYin/ShenYin/UI/Sleep/AudioPlayerManager.swift b/ShenYin/ShenYin/UI/Sleep/AudioPlayerManager.swift index 46831be..e1f6583 100644 --- a/ShenYin/ShenYin/UI/Sleep/AudioPlayerManager.swift +++ b/ShenYin/ShenYin/UI/Sleep/AudioPlayerManager.swift @@ -1,84 +1,153 @@ 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 index 0000000..6911301 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sleep/MusicPlayer.swift @@ -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) +// } +//}