diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 55169ac42..ebae04500 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -1002,6 +1002,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { if (function_name.contains("music")) { Future.delayed(Duration(seconds: 1), () { _qqmusicManager.syncCurrentPlayInfo(isOpenPlay: false); + musicStatus(); }); // } } diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart index 841b721a2..b60025eef 100644 --- a/lib/modules/agent/views/music_card.dart +++ b/lib/modules/agent/views/music_card.dart @@ -17,7 +17,6 @@ Widget buildMusicItem( 'coverUrl': image, }; - controller.musicStatus(); //刷新状态 return Container( padding: EdgeInsets.all(14.w), decoration: BoxDecoration( diff --git a/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift b/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift index 1e5f2260d..52d1c4228 100644 --- a/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift +++ b/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift @@ -37,14 +37,9 @@ public class MusicService: NSObject { private override init() { super.init() - - // 确保应用能接收远程控制事件 - UIApplication.shared.beginReceivingRemoteControlEvents() - setupRemoteCommandCenter() setupAudioSession() - - print("🎵 [MusicService] 🚀 MusicService初始化完成,远程控制已激活") + setupNotificationObservers() } public func initialize(serviceUrl: String, userToken: String) { @@ -53,34 +48,20 @@ public class MusicService: NSObject { print("MusicService initialized with url: \(serviceUrl)") } - - // MARK: - 音频会话设置 - private func setupAudioSession() { - print("🎵 [MusicService] 🚀 开始设置音频会话和通知监听器") - - // 先注册通知监听器,不依赖音频会话设置成功 - setupNotificationObservers() - - // 然后尝试设置音频会话 - setupAudioSessionConfiguration() - - // 确保媒体控制中心可以接收到我们的应用 - setupRemoteCommandCenter() - - print("🎵 [MusicService] 📋 所有通知观察者注册完成") - print("🎵 [MusicService] 🔍 当前MusicService实例: \(Unmanaged.passUnretained(self).toOpaque())") + + // MARK: - 音频会话设置 + private func setupAudioSession() { + do { + try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.allowAirPlay, .allowBluetooth]) + try AVAudioSession.sharedInstance().setActive(true) + } catch { + print("设置音频会话失败: \(error)") + } } + private func setupNotificationObservers() { - // 监听系统音频中断(来电/通话/Siri等) - NotificationCenter.default.addObserver( - self, - selector: #selector(handleAudioInterruption(_:)), - name: AVAudioSession.interruptionNotification, - object: nil - ) print("🎵 [MusicService] ✅ 已注册系统音频中断通知") - // 注册AudioSessionHub的自定义中断通知 NotificationCenter.default.addObserver( self, @@ -108,31 +89,6 @@ public class MusicService: NSObject { print("🎵 [MusicService] ✅ 已注册AudioSessionHub_ResumeMusicPlayback通知") } - private func setupAudioSessionConfiguration() { - do { - let audioSession = AVAudioSession.sharedInstance() - - // 使用更简单的配置,避免参数冲突 - try audioSession.setCategory(.playback, mode: .default, options: [.allowAirPlay, .allowBluetooth, .allowBluetoothA2DP]) - print("🎵 [MusicService] ✅ 音频会话类别设置成功") - - // 延迟激活音频会话,避免与其他服务冲突 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - do { - try audioSession.setActive(true) - print("🎵 [MusicService] ✅ 音频会话激活成功") - } catch { - print("🎵 [MusicService] ⚠️ 音频会话激活失败: \(error)") - // 激活失败不影响通知系统工作 - } - } - - } catch { - print("🎵 [MusicService] ❌ 设置音频会话失败: \(error)") - print("🎵 [MusicService] ℹ️ 音频会话设置失败不影响通知系统工作") - } - } - // MARK: - 基础播放控制 func getCurrentUrlString() -> String? { guard let urlAsset = player?.currentItem?.asset as? AVURLAsset else { @@ -144,7 +100,7 @@ public class MusicService: NSObject { func getCurrentId() -> Int64? { return currentIdInternal } - + // MARK: - 歌曲搜索 public func searchSongs(keyword: String, searchType: Int = 0, completion: @escaping (Result<[[String: Any?]], Error>) -> Void) { @@ -256,7 +212,7 @@ public class MusicService: NSObject { }else{ completion(.failure(error)) } - + return } @@ -326,7 +282,7 @@ public class MusicService: NSObject { task.resume() } - + private func parseUrlFromResponse(jsonResponse: [String: Any]) -> [String: Any?] { var urlData: [String: Any?] = [:] @@ -340,7 +296,7 @@ public class MusicService: NSObject { return urlData } - + public func playPlaylist(songs: [[String: Any]]) { playlist = songs currentIndex = 0 @@ -391,7 +347,7 @@ public class MusicService: NSObject { // 使用新 URL 开始播放 self?.startPlayback(song: updatedSong, url: newUrl) } - + } else { print("获取到的 URL 为空") } @@ -405,7 +361,7 @@ public class MusicService: NSObject { startPlayback(song: song, url: url!) } } - + // 新增方法:更新 playlist 中指定 ID 的歌曲数据 private func updatePlaylistSong(songId: Int64, updatedSong: [String: Any]) { for i in 0.. [String: Any] { guard let player = player, let currentItem = player.currentItem else { @@ -588,21 +513,21 @@ public class MusicService: NSObject { "progress": 0.0 ] } - + let currentTime = CMTimeGetSeconds(player.currentTime()) let duration = CMTimeGetSeconds(currentItem.duration) - + // 更严格的有效性检查 let currentPosition = (currentTime.isNaN || currentTime.isInfinite || currentTime < 0) ? 0 : Int(currentTime * 1000) let totalDuration = (duration.isNaN || duration.isInfinite || duration <= 0) ? 0 : Int(duration * 1000) let progress = (totalDuration > 0 && currentTime >= 0) ? currentTime / duration : 0.0 - + let result: [String: Any] = [ // Add explicit type annotation here - "currentPosition": currentPosition, - "duration": totalDuration, - "progress": progress.isNaN || progress.isInfinite ? 0.0 : progress - ] - + "currentPosition": currentPosition, + "duration": totalDuration, + "progress": progress.isNaN || progress.isInfinite ? 0.0 : progress + ] + // 添加调试日志 if totalDuration == 0 { @@ -611,7 +536,7 @@ public class MusicService: NSObject { return result } - + func seekTo(positionMs: Int) { guard let player = player else { return } @@ -624,221 +549,185 @@ public class MusicService: NSObject { if completed { print("跳转到位置: \(positionMs)ms") - // 如果之前在播放,跳转后继续播放 + // 如果之前是播放状态,确保继续播放 if wasPlaying { - self?.resume() + self?.player?.play() + self?.isPlaying = true + self?.updateMediaSessionPlaybackState(isPlaying: true) + self?.startProgressTimer() } - } else { - print("跳转失败") + + // 立即更新进度信息到系统媒体控制中心 + self?.updateProgressInfoImmediately() + + // 通知Flutter层 + self?.flutterChannel?.invokeMethod("onMediaSessionSeekTo", arguments: ["position": positionMs]) } } } - // MARK: - 远程控制中心设置 + // MARK: - 媒体会话控制 private func setupRemoteCommandCenter() { let commandCenter = MPRemoteCommandCenter.shared() - // 清除之前的目标,避免重复注册 - commandCenter.playCommand.removeTarget(nil) - commandCenter.pauseCommand.removeTarget(nil) - commandCenter.nextTrackCommand.removeTarget(nil) - commandCenter.previousTrackCommand.removeTarget(nil) - commandCenter.changePlaybackPositionCommand.removeTarget(nil) - - // 播放命令 - commandCenter.playCommand.addTarget { [weak self] _ in - print("🎵 [RemoteControl] 播放命令") - self?.resume() + commandCenter.playCommand.addTarget { [unowned self] _ in + self.resume() return .success } - // 暂停命令 - commandCenter.pauseCommand.addTarget { [weak self] _ in - print("🎵 [RemoteControl] 暂停命令") - self?.pause() + commandCenter.pauseCommand.addTarget { [unowned self] _ in + self.pause() return .success } - // 下一首命令 - commandCenter.nextTrackCommand.addTarget { [weak self] _ in - print("🎵 [RemoteControl] 下一首命令") - self?.playNext() + commandCenter.nextTrackCommand.addTarget { [unowned self] _ in + self.next() return .success } - // 上一首命令 - commandCenter.previousTrackCommand.addTarget { [weak self] _ in - print("🎵 [RemoteControl] 上一首命令") - self?.playPrevious() + commandCenter.previousTrackCommand.addTarget { [unowned self] _ in + self.previous() return .success } - // 进度调整命令(iOS 9.1+) - if #available(iOS 9.1, *) { - commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in - if let positionEvent = event as? MPChangePlaybackPositionCommandEvent { - let positionMs = Int(positionEvent.positionTime * 1000) - print("🎵 [RemoteControl] 跳转到 \(positionMs)ms") - self?.seekTo(positionMs: positionMs) - return .success - } - return .commandFailed - } - commandCenter.changePlaybackPositionCommand.isEnabled = true - } - - // 启用远程控制命令 - commandCenter.playCommand.isEnabled = true - commandCenter.pauseCommand.isEnabled = true - commandCenter.nextTrackCommand.isEnabled = true - commandCenter.previousTrackCommand.isEnabled = true - - // 确保音频会话支持远程控制 - do { - let audioSession = AVAudioSession.sharedInstance() - try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) - print("🎵 [RemoteControl] 音频会话配置成功") - } catch { - print("🎵 [RemoteControl] ⚠️ 音频会话配置失败: \(error)") + commandCenter.stopCommand.addTarget { [unowned self] _ in + self.stop() + return .success } - - print("🎵 [RemoteControl] ✅ 远程控制中心设置完成") } - // MARK: - 媒体信息更新 private func updateNowPlayingInfo(song: [String: Any]) { - var nowPlayingInfo = [String: Any]() + let title = song["title"] as? String ?? song["name"] as? String ?? "未知标题" + let artist = song["artist"] as? String ?? song["sgener"] as? String ?? "未知歌手" + let coverUrl = song["coverUrl"] as? String ?? song["image"] as? String ?? "" - // 设置歌曲信息 - nowPlayingInfo[MPMediaItemPropertyTitle] = song["name"] as? String ?? "未知歌曲" - nowPlayingInfo[MPMediaItemPropertyArtist] = song["singer"] as? String ?? "未知歌手" - - // 设置专辑信息(如果有的话) - if let album = song["album"] as? String, !album.isEmpty { - nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = album - } + var nowPlayingInfo: [String: Any] = [ + MPMediaItemPropertyTitle: title, + MPMediaItemPropertyArtist: artist + ] - // 设置播放时间信息 - if let player = player, let currentItem = player.currentItem { - let currentTime = CMTimeGetSeconds(player.currentTime()) - let duration = CMTimeGetSeconds(currentItem.duration) - - if !currentTime.isNaN && !currentTime.isInfinite { - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime - } - - if !duration.isNaN && !duration.isInfinite && duration > 0 { - nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration + // 异步加载封面图片 + if !coverUrl.isEmpty, let url = URL(string: coverUrl) { + DispatchQueue.global(qos: .background).async { + if let data = try? Data(contentsOf: url), + let image = UIImage(data: data) { + DispatchQueue.main.async { + nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image } + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + } else { + DispatchQueue.main.async { + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + } } - } - - // 设置播放速率 - nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0 - - // 设置媒体类型为音频 - nowPlayingInfo[MPMediaItemPropertyMediaType] = MPMediaType.music.rawValue - - // 强制更新媒体信息 - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - - // 确保在主线程上再次设置,解决某些iOS版本的兼容性问题 - DispatchQueue.main.async { + } else { MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } - - print("🎵 [MediaInfo] ✅ 已更新媒体信息: \(song["name"] as? String ?? "未知")") - print("🎵 [MediaInfo] 📱 播放状态: \(isPlaying ? "播放中" : "暂停")") } private func updatePlaybackState() { - guard let flutterChannel = flutterChannel else { return } - - let progressInfo = getProgress() - - flutterChannel.invokeMethod("onPlaybackStateChanged", arguments: [ - "isPlaying": isPlaying, - "currentPosition": progressInfo["currentPosition"] ?? 0, - "duration": progressInfo["duration"] ?? 0, - "progress": progressInfo["progress"] ?? 0.0 - ]) + // iOS manages playback state automatically, but you can update NowPlayingInfo if needed } + // MARK: - 新增媒体会话功能 + + /// 更新媒体会话进度信息 public func updateMediaSessionProgress(currentPosition: Int, duration: Int, progress: Float) { - guard let flutterChannel = flutterChannel else { return } + var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:] - flutterChannel.invokeMethod("onProgressChanged", arguments: [ - "currentPosition": currentPosition, - "duration": duration, - "progress": progress - ]) + // 更新播放时长和当前位置 + nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = Double(duration) / 1000.0 + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Double(currentPosition) / 1000.0 + nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0 + + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } + /// 启用媒体会话回调 public func enableMediaSessionCallbacks(channel: FlutterMethodChannel) { - self.flutterChannel = channel + flutterChannel = channel + setupEnhancedRemoteCommandCenter() + startProgressTimer() print("媒体会话回调已启用") } + /// 更新媒体会话元数据 public func updateMediaSessionMetadata(song: [String: Any]) { - guard let flutterChannel = flutterChannel else { return } - - // 构建媒体元数据 - var metadata: [String: Any] = [:] + let title = song["title"] as? String ?? song["name"] as? String ?? "未知标题" + let artist = song["artist"] as? String ?? song["sgener"] as? String ?? "未知歌手" + let coverUrl = song["coverUrl"] as? String ?? song["image"] as? String ?? "" - // 基本信息 - if let id = song["id"] { - metadata["id"] = id + // 更新当前歌曲ID + if let id = song["id"] as? NSNumber { + currentIdInternal = id.int64Value } - if let name = song["name"] as? String { - metadata["title"] = name - } - if let singer = song["singer"] as? String { - metadata["artist"] = singer - } - if let image = song["image"] as? String { - metadata["artUri"] = image - } - - // 播放状态 - metadata["isPlaying"] = isPlaying - // 进度信息 - let progressInfo = getProgress() - metadata["currentPosition"] = progressInfo["currentPosition"] ?? 0 - metadata["duration"] = progressInfo["duration"] ?? 0 - metadata["progress"] = progressInfo["progress"] ?? 0.0 + print("updateMediaSessionMetadata currentIdInternal: \(currentIdInternal ?? 0)") - // 播放列表信息 - metadata["playlistSize"] = playlist.count - metadata["currentIndex"] = currentIndex - - // 能力标志 - metadata["canSkipToNext"] = currentIndex < playlist.count - 1 - metadata["canSkipToPrevious"] = currentIndex > 0 - metadata["canSeek"] = true + var nowPlayingInfo: [String: Any] = [ + MPMediaItemPropertyTitle: title, + MPMediaItemPropertyArtist: artist + ] - flutterChannel.invokeMethod("onMetadataChanged", arguments: metadata) + // 异步加载封面图片 + if !coverUrl.isEmpty, let url = URL(string: coverUrl) { + print("开始加载封面图片: \(coverUrl)") + + DispatchQueue.global(qos: .background).async { + // 创建带超时的URLRequest + var request = URLRequest(url: url) + request.timeoutInterval = 10.0 + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + print("图片加载失败: \(error.localizedDescription)") + DispatchQueue.main.async { + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + return + } + + guard let data = data, let image = UIImage(data: data) else { + print("图片数据无效或格式不支持") + DispatchQueue.main.async { + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + return + } + + print("图片加载成功") + DispatchQueue.main.async { + nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image } + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + }.resume() + } + } else { + print("封面图片URL为空") + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } - print("已更新媒体元数据: \(metadata)") + print("媒体会话元数据已更新: \(title) - \(artist)") } + /// 更新媒体会话播放状态 public func updateMediaSessionPlaybackState(isPlaying: Bool) { - guard let flutterChannel = flutterChannel else { return } - - let progressInfo = getProgress() - - let playbackState: [String: Any] = [ - "isPlaying": isPlaying, - "currentPosition": progressInfo["currentPosition"] ?? 0, - "duration": progressInfo["duration"] ?? 0, - "progress": progressInfo["progress"] ?? 0.0, - "playbackSpeed": isPlaying ? 1.0 : 0.0 - ] + self.isPlaying = isPlaying + var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:] + nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0 + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - flutterChannel.invokeMethod("onPlaybackStateChanged", arguments: playbackState) + if isPlaying { + startProgressTimer() + } else { + stopProgressTimer() + } - print("已更新播放状态: isPlaying=\(isPlaying)") + print("媒体会话播放状态已更新: \(isPlaying)") } + /// 设置增强的远程控制中心 private func setupEnhancedRemoteCommandCenter() { let commandCenter = MPRemoteCommandCenter.shared() @@ -851,54 +740,54 @@ public class MusicService: NSObject { // 播放命令 commandCenter.playCommand.addTarget { [weak self] _ in - print("远程控制: 播放") self?.resume() + self?.flutterChannel?.invokeMethod("onMediaSessionPlay", arguments: nil) return .success } // 暂停命令 commandCenter.pauseCommand.addTarget { [weak self] _ in - print("远程控制: 暂停") self?.pause() + self?.flutterChannel?.invokeMethod("onMediaSessionPause", arguments: nil) return .success } - // 下一首命令 + // 下一曲命令 commandCenter.nextTrackCommand.addTarget { [weak self] _ in - print("远程控制: 下一首") - self?.playNext() + self?.next() + self?.flutterChannel?.invokeMethod("onMediaSessionNext", arguments: nil) return .success } - // 上一首命令 + // 上一曲命令 commandCenter.previousTrackCommand.addTarget { [weak self] _ in - print("远程控制: 上一首") - self?.playPrevious() + self?.previous() + self?.flutterChannel?.invokeMethod("onMediaSessionPrevious", arguments: nil) return .success } - // 进度调整命令 + // 跳转命令 - 关键升级点 + commandCenter.changePlaybackPositionCommand.isEnabled = true commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in - if let positionEvent = event as? MPChangePlaybackPositionCommandEvent { - let positionMs = Int(positionEvent.positionTime * 1000) - print("远程控制: 跳转到 \(positionMs)ms") + if let event = event as? MPChangePlaybackPositionCommandEvent { + let positionMs = Int(event.positionTime * 1000) self?.seekTo(positionMs: positionMs) return .success } return .commandFailed } - // 启用命令 - commandCenter.playCommand.isEnabled = true - commandCenter.pauseCommand.isEnabled = true - commandCenter.nextTrackCommand.isEnabled = playlist.count > 1 - commandCenter.previousTrackCommand.isEnabled = playlist.count > 1 - commandCenter.changePlaybackPositionCommand.isEnabled = true + // 停止命令 + commandCenter.stopCommand.addTarget { [weak self] _ in + self?.stop() + self?.flutterChannel?.invokeMethod("onMediaSessionStop", arguments: nil) + return .success + } - print("增强远程控制中心设置完成") + print("增强的远程控制中心已设置,支持进度条滑动") } - // MARK: - 进度定时器 + /// 启动进度定时器 private func startProgressTimer() { stopProgressTimer() progressTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in @@ -906,217 +795,169 @@ public class MusicService: NSObject { } } + /// 停止进度定时器 private func stopProgressTimer() { progressTimer?.invalidate() progressTimer = nil } + /// 更新进度信息 private func updateProgressInfo() { - guard isPlaying, let player = player, let currentItem = player.currentItem else { - return - } + guard let player = player, let currentItem = player.currentItem, isPlaying else { return } let currentTime = CMTimeGetSeconds(player.currentTime()) let duration = CMTimeGetSeconds(currentItem.duration) - if !currentTime.isNaN && !currentTime.isInfinite && - !duration.isNaN && !duration.isInfinite && duration > 0 { + if !currentTime.isNaN && !currentTime.isInfinite && !duration.isNaN && !duration.isInfinite { + // 更新系统媒体信息 - 关键升级点 + updateSystemMediaInfo(currentTime: currentTime, duration: duration) + // 通知Flutter层 let currentPosition = Int(currentTime * 1000) let totalDuration = Int(duration * 1000) - let progress = Float(currentTime / duration) - - // 更新媒体会话进度 - updateMediaSessionProgress(currentPosition: currentPosition, - duration: totalDuration, - progress: progress) + let progress = totalDuration > 0 ? Float(currentTime / duration) : 0.0 - // 更新系统媒体信息 - updateSystemMediaInfo(currentTime: currentTime, duration: duration) + flutterChannel?.invokeMethod("onMediaSessionProgress", arguments: [ + "currentIdInternal": currentIdInternal ?? 0, + "currentPosition": currentPosition, + "duration": totalDuration, + "progress": progress + ]) } } + /// 立即更新进度信息(用于seekTo后) private func updateProgressInfoImmediately() { - guard let player = player, let currentItem = player.currentItem else { - return - } + guard let player = player, let currentItem = player.currentItem else { return } let currentTime = CMTimeGetSeconds(player.currentTime()) let duration = CMTimeGetSeconds(currentItem.duration) - updateSystemMediaInfo(currentTime: currentTime, duration: duration) + if !currentTime.isNaN && !currentTime.isInfinite && !duration.isNaN && !duration.isInfinite { + updateSystemMediaInfo(currentTime: currentTime, duration: duration) + } } + /// 更新系统媒体信息 private func updateSystemMediaInfo(currentTime: Double, duration: Double) { var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:] - - if !currentTime.isNaN && !currentTime.isInfinite { - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime - } - - if !duration.isNaN && !duration.isInfinite && duration > 0 { - nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration - } - + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime + nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0 - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } - // MARK: - 播放器观察者 + // MARK: - 播放完成处理 private func setupPlayerObservers() { guard let player = player else { return } - // 监听播放器状态 - player.addObserver(self, forKeyPath: "timeControlStatus", options: [.new], context: nil) - - // 监听播放结束通知 NotificationCenter.default.addObserver( - self, - selector: #selector(playerDidFinishPlaying), - name: .AVPlayerItemDidPlayToEndTime, - object: player.currentItem - ) + forName: .AVPlayerItemDidPlayToEndTime, + object: player.currentItem, + queue: .main + ) { [weak self] _ in + self?.stopProgressTimer() + self?.playNextOrStop() + } } + // MARK: - 播放项观察者设置 private func setupPlayerItemObservers(playerItem: AVPlayerItem, song: [String: Any]) { + // 移除之前的观察者 + NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil) + NotificationCenter.default.removeObserver(self, name: .AVPlayerItemFailedToPlayToEndTime, object: nil) + // 监听播放项状态 - playerItem.addObserver(self, forKeyPath: "status", options: [.new], context: nil) + playerItem.addObserver(self, forKeyPath: "status", options: [.new, .initial], context: nil) + playerItem.addObserver(self, forKeyPath: "duration", options: [.new], context: nil) + playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.new], context: nil) - // 监听缓冲状态 - playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: [.new], context: nil) - playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.new], context: nil) - } - - @objc private func playerDidFinishPlaying() { - print("歌曲播放完成") - isPlaying = false - updatePlaybackState() - updateMediaSessionPlaybackState(isPlaying: false) - stopProgressTimer() + // 播放完成通知 + NotificationCenter.default.addObserver( + forName: .AVPlayerItemDidPlayToEndTime, + object: playerItem, + queue: .main + ) { [weak self] _ in + print("歌曲播放完成") + self?.stopProgressTimer() + self?.playNextOrStop() + } + + // 播放失败通知 + NotificationCenter.default.addObserver( + forName: .AVPlayerItemFailedToPlayToEndTime, + object: playerItem, + queue: .main + ) { [weak self] notification in + print("播放失败: \(notification)") + self?.isPlaying = false + self?.updateMediaSessionPlaybackState(isPlaying: false) + } - // 自动播放下一首或停止 - playNextOrStop() + // 存储歌曲信息,用于状态变化时更新 + objc_setAssociatedObject(playerItem, "songInfo", song, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } // MARK: - KVO 观察者 override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - guard let keyPath = keyPath else { return } + guard let playerItem = object as? AVPlayerItem else { return } switch keyPath { case "status": - if let playerItem = object as? AVPlayerItem { - switch playerItem.status { - case .readyToPlay: - print("🎵 [Player] 播放器准备就绪,开始播放") - player?.play() - isPlaying = true - updatePlaybackState() - updateMediaSessionPlaybackState(isPlaying: true) - startProgressTimer() - - // 强制更新媒体信息,确保通知栏显示 - if let currentSong = playlist.isEmpty ? nil : playlist[currentIndex] { - updateNowPlayingInfo(song: currentSong) - updateMediaSessionMetadata(song: currentSong) - print("🎵 [Player] ✅ 播放开始时已更新媒体信息") - } - - // 再次确保远程控制激活 - DispatchQueue.main.async { - UIApplication.shared.beginReceivingRemoteControlEvents() - print("🎵 [Player] ✅ 远程控制事件接收已重新激活") - } - - case .failed: - print("播放器加载失败: \(playerItem.error?.localizedDescription ?? "未知错误")") - isPlaying = false - updatePlaybackState() - updateMediaSessionPlaybackState(isPlaying: false) - - case .unknown: - print("播放器状态未知") - - @unknown default: - break + switch playerItem.status { + case .readyToPlay: + print("播放项准备就绪,开始播放") + + // 获取关联的歌曲信息 + if let song = objc_getAssociatedObject(playerItem, "songInfo") as? [String: Any] { + updateNowPlayingInfo(song: song) } + + // 开始播放 + player?.play() + isPlaying = true + updateMediaSessionPlaybackState(isPlaying: true) + startProgressTimer() + + case .failed: + print("播放项加载失败: \(playerItem.error?.localizedDescription ?? "未知错误")") + isPlaying = false + updateMediaSessionPlaybackState(isPlaying: false) + + case .unknown: + print("播放项状态未知") + + @unknown default: + break } - case "timeControlStatus": - if let player = object as? AVPlayer { - switch player.timeControlStatus { - case .playing: - print("播放器正在播放") - if !isPlaying { - isPlaying = true - updatePlaybackState() - updateMediaSessionPlaybackState(isPlaying: true) - startProgressTimer() - } - - case .paused: - print("播放器已暂停") - if isPlaying { - isPlaying = false - updatePlaybackState() - updateMediaSessionPlaybackState(isPlaying: false) - stopProgressTimer() - } - - case .waitingToPlayAtSpecifiedRate: - print("播放器等待缓冲") - - @unknown default: - break - } + case "duration": + let duration = CMTimeGetSeconds(playerItem.duration) + if !duration.isNaN && !duration.isInfinite && duration > 0 { + print("获取到音频时长: \(duration) 秒") + // 更新系统媒体信息 + updateProgressInfoImmediately() } - case "playbackBufferEmpty": - print("播放缓冲区为空") - - case "playbackLikelyToKeepUp": - if let playerItem = object as? AVPlayerItem, playerItem.isPlaybackLikelyToKeepUp { - print("播放缓冲充足,可以继续播放") + case "loadedTimeRanges": + if let timeRange = playerItem.loadedTimeRanges.first?.timeRangeValue { + let loadedDuration = CMTimeGetSeconds(CMTimeAdd(timeRange.start, timeRange.duration)) + let totalDuration = CMTimeGetSeconds(playerItem.duration) + if !totalDuration.isNaN && !totalDuration.isInfinite && totalDuration > 0 { + let loadedPercentage = loadedDuration / totalDuration * 100 + print("缓冲进度: \(String(format: "%.1f", loadedPercentage))%") + } } default: - super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) - } - } - - // MARK: - 音频中断处理 - @objc private func handleAudioInterruption(_ notification: Notification) { - print("🎵 [MusicService] 收到系统音频中断通知") - - guard let info = notification.userInfo, - let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt, - let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { - print("🎵 [MusicService] 无法解析中断通知信息") - return - } - - switch type { - case .began: - print("🎵 [MusicService] 系统音频中断开始 - 当前播放状态: \(isPlaying)") - // 记录中断发生时是否在播放,并暂停 - interruptedDuringPlayback = isPlaying - if isPlaying { - pause() - print("🎵 [MusicService] 已暂停音乐播放") - } - - case .ended: - print("🎵 [MusicService] 系统音频中断结束 - 等待AudioSessionHub处理") - // 系统中断结束时不立即处理,等待AudioSessionHub的通知 - - @unknown default: - print("🎵 [MusicService] 未知的中断类型: \(typeValue)") break } } // 处理AudioSessionHub发送的中断开始通知 @objc private func handleAudioSessionHubInterruption(_ notification: Notification) { + interruptedDuringPlayback = isPlaying + pause() print("🎵 [MusicService] 收到AudioSessionHub中断开始通知") // AudioSessionHub已经处理了中断开始,我们这里可以做额外的处理 } @@ -1146,9 +987,9 @@ public class MusicService: NSObject { do { // 设置音频会话为播放模式 - let audioSession = AVAudioSession.sharedInstance() - try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) - try audioSession.setActive(true, options: []) +// let audioSession = AVAudioSession.sharedInstance() +// try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) +// try audioSession.setActive(true, options: []) print("🎵 [MusicService] 音频会话已设置为播放模式") // 恢复播放 @@ -1187,9 +1028,9 @@ public class MusicService: NSObject { do { // 设置音频会话为播放模式 - let audioSession = AVAudioSession.sharedInstance() - try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) - try audioSession.setActive(true, options: []) +// let audioSession = AVAudioSession.sharedInstance() +// try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) +// try audioSession.setActive(true, options: []) print("🎵 [MusicService] 音频会话已设置为播放模式") // 恢复播放 @@ -1211,12 +1052,6 @@ public class MusicService: NSObject { } deinit { - NotificationCenter.default.removeObserver( - self, - name: AVAudioSession.interruptionNotification, - object: nil - ) - NotificationCenter.default.removeObserver( self, name: NSNotification.Name("AudioSessionInterruptionBegan"),