Browse Source

上传iOS 音乐服务问题

weicu
liwei1dao 1 year ago
parent
commit
97079eeb76
  1. 1
      lib/modules/agent/controllers/agent_controller.dart
  2. 1
      lib/modules/agent/views/music_card.dart
  3. 679
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift

1
lib/modules/agent/controllers/agent_controller.dart

@ -1002,6 +1002,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
if (function_name.contains("music")) { if (function_name.contains("music")) {
Future.delayed(Duration(seconds: 1), () { Future.delayed(Duration(seconds: 1), () {
_qqmusicManager.syncCurrentPlayInfo(isOpenPlay: false); _qqmusicManager.syncCurrentPlayInfo(isOpenPlay: false);
musicStatus();
}); });
// } // }
} }

1
lib/modules/agent/views/music_card.dart

@ -17,7 +17,6 @@ Widget buildMusicItem(
'coverUrl': image, 'coverUrl': image,
}; };
controller.musicStatus(); //刷新状态
return Container( return Container(
padding: EdgeInsets.all(14.w), padding: EdgeInsets.all(14.w),
decoration: BoxDecoration( decoration: BoxDecoration(

679
local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift

@ -37,14 +37,9 @@ public class MusicService: NSObject {
private override init() { private override init() {
super.init() super.init()
// 确保应用能接收远程控制事件
UIApplication.shared.beginReceivingRemoteControlEvents()
setupRemoteCommandCenter() setupRemoteCommandCenter()
setupAudioSession() setupAudioSession()
setupNotificationObservers()
print("🎵 [MusicService] 🚀 MusicService初始化完成,远程控制已激活")
} }
public func initialize(serviceUrl: String, userToken: String) { public func initialize(serviceUrl: String, userToken: String) {
@ -54,33 +49,19 @@ public class MusicService: NSObject {
} }
// MARK: - 音频会话设置 // MARK: - 音频会话设置
private func setupAudioSession() { private func setupAudioSession() {
print("🎵 [MusicService] 🚀 开始设置音频会话和通知监听器") do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.allowAirPlay, .allowBluetooth])
// 先注册通知监听器,不依赖音频会话设置成功 try AVAudioSession.sharedInstance().setActive(true)
setupNotificationObservers() } catch {
print("设置音频会话失败: \(error)")
// 然后尝试设置音频会话 }
setupAudioSessionConfiguration()
// 确保媒体控制中心可以接收到我们的应用
setupRemoteCommandCenter()
print("🎵 [MusicService] 📋 所有通知观察者注册完成")
print("🎵 [MusicService] 🔍 当前MusicService实例: \(Unmanaged.passUnretained(self).toOpaque())")
} }
private func setupNotificationObservers() { private func setupNotificationObservers() {
// 监听系统音频中断(来电/通话/Siri等)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleAudioInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
print("🎵 [MusicService] ✅ 已注册系统音频中断通知") print("🎵 [MusicService] ✅ 已注册系统音频中断通知")
// 注册AudioSessionHub的自定义中断通知 // 注册AudioSessionHub的自定义中断通知
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,
@ -108,31 +89,6 @@ public class MusicService: NSObject {
print("🎵 [MusicService] ✅ 已注册AudioSessionHub_ResumeMusicPlayback通知") 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: - 基础播放控制 // MARK: - 基础播放控制
func getCurrentUrlString() -> String? { func getCurrentUrlString() -> String? {
guard let urlAsset = player?.currentItem?.asset as? AVURLAsset else { guard let urlAsset = player?.currentItem?.asset as? AVURLAsset else {
@ -429,16 +385,6 @@ public class MusicService: NSObject {
print("开始播放: \(url), ID: \(currentIdInternal ?? 0)") print("开始播放: \(url), ID: \(currentIdInternal ?? 0)")
// 强制设置音频会话为活跃状态
do {
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
try audioSession.setActive(true, options: [])
print("🎵 [StartPlayback] ✅ 音频会话强制激活成功")
} catch {
print("🎵 [StartPlayback] ⚠️ 音频会话设置失败: \(error)")
}
// 检查是否已在播放列表中 // 检查是否已在播放列表中
let existingIndex = playlist.firstIndex { existingSong in let existingIndex = playlist.firstIndex { existingSong in
if let existingId = existingSong["id"] as? NSNumber, if let existingId = existingSong["id"] as? NSNumber,
@ -468,15 +414,6 @@ public class MusicService: NSObject {
currentVolume = 1.0 currentVolume = 1.0
player?.volume = currentVolume player?.volume = currentVolume
// 立即设置媒体信息,即使播放器还未准备就绪
updateNowPlayingInfo(song: song)
// 强制重新设置远程控制中心
setupRemoteCommandCenter()
// 强制激活远程控制
UIApplication.shared.beginReceivingRemoteControlEvents()
print("播放器已创建,等待加载完成...") print("播放器已创建,等待加载完成...")
} }
@ -486,12 +423,6 @@ public class MusicService: NSObject {
isPlaying = false isPlaying = false
updatePlaybackState() updatePlaybackState()
updateMediaSessionPlaybackState(isPlaying: false) updateMediaSessionPlaybackState(isPlaying: false)
// 更新媒体信息,保持通知栏显示但状态为暂停
if currentIndex < playlist.count {
updateNowPlayingInfo(song: playlist[currentIndex])
}
stopProgressTimer() stopProgressTimer()
print("已暂停") print("已暂停")
} }
@ -501,12 +432,6 @@ public class MusicService: NSObject {
isPlaying = true isPlaying = true
updatePlaybackState() updatePlaybackState()
updateMediaSessionPlaybackState(isPlaying: true) updateMediaSessionPlaybackState(isPlaying: true)
// 重新设置媒体信息,确保通知栏显示
if currentIndex < playlist.count {
updateNowPlayingInfo(song: playlist[currentIndex])
}
startProgressTimer() startProgressTimer()
print("继续播放") print("继续播放")
} }
@ -598,10 +523,10 @@ public class MusicService: NSObject {
let progress = (totalDuration > 0 && currentTime >= 0) ? currentTime / duration : 0.0 let progress = (totalDuration > 0 && currentTime >= 0) ? currentTime / duration : 0.0
let result: [String: Any] = [ // Add explicit type annotation here let result: [String: Any] = [ // Add explicit type annotation here
"currentPosition": currentPosition, "currentPosition": currentPosition,
"duration": totalDuration, "duration": totalDuration,
"progress": progress.isNaN || progress.isInfinite ? 0.0 : progress "progress": progress.isNaN || progress.isInfinite ? 0.0 : progress
] ]
// 添加调试日志 // 添加调试日志
@ -624,221 +549,185 @@ public class MusicService: NSObject {
if completed { if completed {
print("跳转到位置: \(positionMs)ms") print("跳转到位置: \(positionMs)ms")
// 如果之前在播放,跳转后继续播放 // 如果之前是播放状态,确保继续播放
if wasPlaying { 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() { private func setupRemoteCommandCenter() {
let commandCenter = MPRemoteCommandCenter.shared() let commandCenter = MPRemoteCommandCenter.shared()
// 清除之前的目标,避免重复注册 commandCenter.playCommand.addTarget { [unowned self] _ in
commandCenter.playCommand.removeTarget(nil) self.resume()
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()
return .success return .success
} }
// 暂停命令 commandCenter.pauseCommand.addTarget { [unowned self] _ in
commandCenter.pauseCommand.addTarget { [weak self] _ in self.pause()
print("🎵 [RemoteControl] 暂停命令")
self?.pause()
return .success return .success
} }
// 下一首命令 commandCenter.nextTrackCommand.addTarget { [unowned self] _ in
commandCenter.nextTrackCommand.addTarget { [weak self] _ in self.next()
print("🎵 [RemoteControl] 下一首命令")
self?.playNext()
return .success return .success
} }
// 上一首命令 commandCenter.previousTrackCommand.addTarget { [unowned self] _ in
commandCenter.previousTrackCommand.addTarget { [weak self] _ in self.previous()
print("🎵 [RemoteControl] 上一首命令")
self?.playPrevious()
return .success return .success
} }
// 进度调整命令(iOS 9.1+) commandCenter.stopCommand.addTarget { [unowned self] _ in
if #available(iOS 9.1, *) { self.stop()
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in return .success
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)")
} }
print("🎵 [RemoteControl] ✅ 远程控制中心设置完成")
} }
// MARK: - 媒体信息更新
private func updateNowPlayingInfo(song: [String: Any]) { 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 ?? ""
// 设置歌曲信息 var nowPlayingInfo: [String: Any] = [
nowPlayingInfo[MPMediaItemPropertyTitle] = song["name"] as? String ?? "未知歌曲" MPMediaItemPropertyTitle: title,
nowPlayingInfo[MPMediaItemPropertyArtist] = song["singer"] as? String ?? "未知歌手" MPMediaItemPropertyArtist: artist
]
// 设置专辑信息(如果有的话)
if let album = song["album"] as? String, !album.isEmpty {
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = album
}
// 设置播放时间信息
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
}
}
} }
} } else {
// 设置播放速率
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
// 设置媒体类型为音频
nowPlayingInfo[MPMediaItemPropertyMediaType] = MPMediaType.music.rawValue
// 强制更新媒体信息
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
// 确保在主线程上再次设置,解决某些iOS版本的兼容性问题
DispatchQueue.main.async {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
} }
print("🎵 [MediaInfo] ✅ 已更新媒体信息: \(song["name"] as? String ?? "未知")")
print("🎵 [MediaInfo] 📱 播放状态: \(isPlaying ? "播放中" : "暂停")")
} }
private func updatePlaybackState() { private func updatePlaybackState() {
guard let flutterChannel = flutterChannel else { return } // iOS manages playback state automatically, but you can update NowPlayingInfo if needed
let progressInfo = getProgress()
flutterChannel.invokeMethod("onPlaybackStateChanged", arguments: [
"isPlaying": isPlaying,
"currentPosition": progressInfo["currentPosition"] ?? 0,
"duration": progressInfo["duration"] ?? 0,
"progress": progressInfo["progress"] ?? 0.0
])
} }
// MARK: - 新增媒体会话功能
/// 更新媒体会话进度信息
public func updateMediaSessionProgress(currentPosition: Int, duration: Int, progress: Float) { 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, nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = Double(duration) / 1000.0
"duration": duration, nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Double(currentPosition) / 1000.0
"progress": progress nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
])
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
} }
/// 启用媒体会话回调
public func enableMediaSessionCallbacks(channel: FlutterMethodChannel) { public func enableMediaSessionCallbacks(channel: FlutterMethodChannel) {
self.flutterChannel = channel flutterChannel = channel
setupEnhancedRemoteCommandCenter()
startProgressTimer()
print("媒体会话回调已启用") print("媒体会话回调已启用")
} }
/// 更新媒体会话元数据
public func updateMediaSessionMetadata(song: [String: Any]) { public func updateMediaSessionMetadata(song: [String: Any]) {
guard let flutterChannel = flutterChannel else { return } 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 ?? ""
var metadata: [String: Any] = [:]
// 基本信息 // 更新当前歌曲ID
if let id = song["id"] { if let id = song["id"] as? NSNumber {
metadata["id"] = id 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
} }
// 播放状态 print("updateMediaSessionMetadata currentIdInternal: \(currentIdInternal ?? 0)")
metadata["isPlaying"] = isPlaying
// 进度信息 var nowPlayingInfo: [String: Any] = [
let progressInfo = getProgress() MPMediaItemPropertyTitle: title,
metadata["currentPosition"] = progressInfo["currentPosition"] ?? 0 MPMediaItemPropertyArtist: artist
metadata["duration"] = progressInfo["duration"] ?? 0 ]
metadata["progress"] = progressInfo["progress"] ?? 0.0
// 播放列表信息 // 异步加载封面图片
metadata["playlistSize"] = playlist.count if !coverUrl.isEmpty, let url = URL(string: coverUrl) {
metadata["currentIndex"] = currentIndex print("开始加载封面图片: \(coverUrl)")
// 能力标志 DispatchQueue.global(qos: .background).async {
metadata["canSkipToNext"] = currentIndex < playlist.count - 1 // 创建带超时的URLRequest
metadata["canSkipToPrevious"] = currentIndex > 0 var request = URLRequest(url: url)
metadata["canSeek"] = true request.timeoutInterval = 10.0
flutterChannel.invokeMethod("onMetadataChanged", arguments: metadata) URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("图片加载失败: \(error.localizedDescription)")
DispatchQueue.main.async {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
return
}
print("已更新媒体元数据: \(metadata)") guard let data = data, let image = UIImage(data: data) else {
} print("图片数据无效或格式不支持")
DispatchQueue.main.async {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
return
}
public func updateMediaSessionPlaybackState(isPlaying: Bool) { print("图片加载成功")
guard let flutterChannel = flutterChannel else { return } DispatchQueue.main.async {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
}.resume()
}
} else {
print("封面图片URL为空")
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
let progressInfo = getProgress() print("媒体会话元数据已更新: \(title) - \(artist)")
}
let playbackState: [String: Any] = [ /// 更新媒体会话播放状态
"isPlaying": isPlaying, public func updateMediaSessionPlaybackState(isPlaying: Bool) {
"currentPosition": progressInfo["currentPosition"] ?? 0, self.isPlaying = isPlaying
"duration": progressInfo["duration"] ?? 0, var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
"progress": progressInfo["progress"] ?? 0.0, nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
"playbackSpeed": 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() { private func setupEnhancedRemoteCommandCenter() {
let commandCenter = MPRemoteCommandCenter.shared() let commandCenter = MPRemoteCommandCenter.shared()
@ -851,54 +740,54 @@ public class MusicService: NSObject {
// 播放命令 // 播放命令
commandCenter.playCommand.addTarget { [weak self] _ in commandCenter.playCommand.addTarget { [weak self] _ in
print("远程控制: 播放")
self?.resume() self?.resume()
self?.flutterChannel?.invokeMethod("onMediaSessionPlay", arguments: nil)
return .success return .success
} }
// 暂停命令 // 暂停命令
commandCenter.pauseCommand.addTarget { [weak self] _ in commandCenter.pauseCommand.addTarget { [weak self] _ in
print("远程控制: 暂停")
self?.pause() self?.pause()
self?.flutterChannel?.invokeMethod("onMediaSessionPause", arguments: nil)
return .success return .success
} }
// 下一首命令 // 下一曲命令
commandCenter.nextTrackCommand.addTarget { [weak self] _ in commandCenter.nextTrackCommand.addTarget { [weak self] _ in
print("远程控制: 下一首") self?.next()
self?.playNext() self?.flutterChannel?.invokeMethod("onMediaSessionNext", arguments: nil)
return .success return .success
} }
// 上一首命令 // 上一曲命令
commandCenter.previousTrackCommand.addTarget { [weak self] _ in commandCenter.previousTrackCommand.addTarget { [weak self] _ in
print("远程控制: 上一首") self?.previous()
self?.playPrevious() self?.flutterChannel?.invokeMethod("onMediaSessionPrevious", arguments: nil)
return .success return .success
} }
// 进度调整命令 // 跳转命令 - 关键升级点
commandCenter.changePlaybackPositionCommand.isEnabled = true
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in
if let positionEvent = event as? MPChangePlaybackPositionCommandEvent { if let event = event as? MPChangePlaybackPositionCommandEvent {
let positionMs = Int(positionEvent.positionTime * 1000) let positionMs = Int(event.positionTime * 1000)
print("远程控制: 跳转到 \(positionMs)ms")
self?.seekTo(positionMs: positionMs) self?.seekTo(positionMs: positionMs)
return .success return .success
} }
return .commandFailed return .commandFailed
} }
// 启用命令 // 停止命令
commandCenter.playCommand.isEnabled = true commandCenter.stopCommand.addTarget { [weak self] _ in
commandCenter.pauseCommand.isEnabled = true self?.stop()
commandCenter.nextTrackCommand.isEnabled = playlist.count > 1 self?.flutterChannel?.invokeMethod("onMediaSessionStop", arguments: nil)
commandCenter.previousTrackCommand.isEnabled = playlist.count > 1 return .success
commandCenter.changePlaybackPositionCommand.isEnabled = true }
print("增强远程控制中心设置完成") print("增强的远程控制中心已设置,支持进度条滑动")
} }
// MARK: - 进度定时器 /// 启动进度定时器
private func startProgressTimer() { private func startProgressTimer() {
stopProgressTimer() stopProgressTimer()
progressTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in progressTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
@ -906,217 +795,169 @@ public class MusicService: NSObject {
} }
} }
/// 停止进度定时器
private func stopProgressTimer() { private func stopProgressTimer() {
progressTimer?.invalidate() progressTimer?.invalidate()
progressTimer = nil progressTimer = nil
} }
/// 更新进度信息
private func updateProgressInfo() { private func updateProgressInfo() {
guard isPlaying, let player = player, let currentItem = player.currentItem else { guard let player = player, let currentItem = player.currentItem, isPlaying else { return }
return
}
let currentTime = CMTimeGetSeconds(player.currentTime()) let currentTime = CMTimeGetSeconds(player.currentTime())
let duration = CMTimeGetSeconds(currentItem.duration) let duration = CMTimeGetSeconds(currentItem.duration)
if !currentTime.isNaN && !currentTime.isInfinite && if !currentTime.isNaN && !currentTime.isInfinite && !duration.isNaN && !duration.isInfinite {
!duration.isNaN && !duration.isInfinite && duration > 0 { // 更新系统媒体信息 - 关键升级点
updateSystemMediaInfo(currentTime: currentTime, duration: duration)
// 通知Flutter层
let currentPosition = Int(currentTime * 1000) let currentPosition = Int(currentTime * 1000)
let totalDuration = Int(duration * 1000) let totalDuration = Int(duration * 1000)
let progress = Float(currentTime / duration) let progress = totalDuration > 0 ? Float(currentTime / duration) : 0.0
// 更新媒体会话进度 flutterChannel?.invokeMethod("onMediaSessionProgress", arguments: [
updateMediaSessionProgress(currentPosition: currentPosition, "currentIdInternal": currentIdInternal ?? 0,
duration: totalDuration, "currentPosition": currentPosition,
progress: progress) "duration": totalDuration,
"progress": progress
// 更新系统媒体信息 ])
updateSystemMediaInfo(currentTime: currentTime, duration: duration)
} }
} }
/// 立即更新进度信息(用于seekTo后)
private func updateProgressInfoImmediately() { private func updateProgressInfoImmediately() {
guard let player = player, let currentItem = player.currentItem else { guard let player = player, let currentItem = player.currentItem else { return }
return
}
let currentTime = CMTimeGetSeconds(player.currentTime()) let currentTime = CMTimeGetSeconds(player.currentTime())
let duration = CMTimeGetSeconds(currentItem.duration) 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) { private func updateSystemMediaInfo(currentTime: Double, duration: Double) {
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:] var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
if !currentTime.isNaN && !currentTime.isInfinite { nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
}
if !duration.isNaN && !duration.isInfinite && duration > 0 {
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
}
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0 nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
} }
// MARK: - 播放器观察者 // MARK: - 播放完成处理
private func setupPlayerObservers() { private func setupPlayerObservers() {
guard let player = player else { return } guard let player = player else { return }
// 监听播放器状态
player.addObserver(self, forKeyPath: "timeControlStatus", options: [.new], context: nil)
// 监听播放结束通知
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, forName: .AVPlayerItemDidPlayToEndTime,
selector: #selector(playerDidFinishPlaying), object: player.currentItem,
name: .AVPlayerItemDidPlayToEndTime, queue: .main
object: player.currentItem ) { [weak self] _ in
) self?.stopProgressTimer()
self?.playNextOrStop()
}
} }
// MARK: - 播放项观察者设置
private func setupPlayerItemObservers(playerItem: AVPlayerItem, song: [String: Any]) { 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) NotificationCenter.default.addObserver(
playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.new], context: nil) forName: .AVPlayerItemDidPlayToEndTime,
} object: playerItem,
queue: .main
) { [weak self] _ in
print("歌曲播放完成")
self?.stopProgressTimer()
self?.playNextOrStop()
}
@objc private func playerDidFinishPlaying() { // 播放失败通知
print("歌曲播放完成") NotificationCenter.default.addObserver(
isPlaying = false forName: .AVPlayerItemFailedToPlayToEndTime,
updatePlaybackState() object: playerItem,
updateMediaSessionPlaybackState(isPlaying: false) queue: .main
stopProgressTimer() ) { [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 观察者 // MARK: - KVO 观察者
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 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 { switch keyPath {
case "status": case "status":
if let playerItem = object as? AVPlayerItem { switch playerItem.status {
switch playerItem.status { case .readyToPlay:
case .readyToPlay: print("播放项准备就绪,开始播放")
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("播放器状态未知") if let song = objc_getAssociatedObject(playerItem, "songInfo") as? [String: Any] {
updateNowPlayingInfo(song: song)
@unknown default:
break
} }
}
case "timeControlStatus": // 开始播放
if let player = object as? AVPlayer { player?.play()
switch player.timeControlStatus { isPlaying = true
case .playing: updateMediaSessionPlaybackState(isPlaying: true)
print("播放器正在播放") startProgressTimer()
if !isPlaying {
isPlaying = true
updatePlaybackState()
updateMediaSessionPlaybackState(isPlaying: true)
startProgressTimer()
}
case .paused: case .failed:
print("播放器已暂停") print("播放项加载失败: \(playerItem.error?.localizedDescription ?? "未知错误")")
if isPlaying { isPlaying = false
isPlaying = false updateMediaSessionPlaybackState(isPlaying: false)
updatePlaybackState()
updateMediaSessionPlaybackState(isPlaying: false)
stopProgressTimer()
}
case .waitingToPlayAtSpecifiedRate: case .unknown:
print("播放器等待缓冲") print("播放项状态未知")
@unknown default: @unknown default:
break break
}
} }
case "playbackBufferEmpty": case "duration":
print("播放缓冲区为空") let duration = CMTimeGetSeconds(playerItem.duration)
if !duration.isNaN && !duration.isInfinite && duration > 0 {
case "playbackLikelyToKeepUp": print("获取到音频时长: \(duration) 秒")
if let playerItem = object as? AVPlayerItem, playerItem.isPlaybackLikelyToKeepUp { // 更新系统媒体信息
print("播放缓冲充足,可以继续播放") updateProgressInfoImmediately()
} }
default: case "loadedTimeRanges":
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 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 {
// MARK: - 音频中断处理 let loadedPercentage = loadedDuration / totalDuration * 100
@objc private func handleAudioInterruption(_ notification: Notification) { print("缓冲进度: \(String(format: "%.1f", loadedPercentage))%")
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: default:
print("🎵 [MusicService] 系统音频中断结束 - 等待AudioSessionHub处理")
// 系统中断结束时不立即处理,等待AudioSessionHub的通知
@unknown default:
print("🎵 [MusicService] 未知的中断类型: \(typeValue)")
break break
} }
} }
// 处理AudioSessionHub发送的中断开始通知 // 处理AudioSessionHub发送的中断开始通知
@objc private func handleAudioSessionHubInterruption(_ notification: Notification) { @objc private func handleAudioSessionHubInterruption(_ notification: Notification) {
interruptedDuringPlayback = isPlaying
pause()
print("🎵 [MusicService] 收到AudioSessionHub中断开始通知") print("🎵 [MusicService] 收到AudioSessionHub中断开始通知")
// AudioSessionHub已经处理了中断开始,我们这里可以做额外的处理 // AudioSessionHub已经处理了中断开始,我们这里可以做额外的处理
} }
@ -1146,9 +987,9 @@ public class MusicService: NSObject {
do { do {
// 设置音频会话为播放模式 // 设置音频会话为播放模式
let audioSession = AVAudioSession.sharedInstance() // let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) // try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
try audioSession.setActive(true, options: []) // try audioSession.setActive(true, options: [])
print("🎵 [MusicService] 音频会话已设置为播放模式") print("🎵 [MusicService] 音频会话已设置为播放模式")
// 恢复播放 // 恢复播放
@ -1187,9 +1028,9 @@ public class MusicService: NSObject {
do { do {
// 设置音频会话为播放模式 // 设置音频会话为播放模式
let audioSession = AVAudioSession.sharedInstance() // let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) // try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
try audioSession.setActive(true, options: []) // try audioSession.setActive(true, options: [])
print("🎵 [MusicService] 音频会话已设置为播放模式") print("🎵 [MusicService] 音频会话已设置为播放模式")
// 恢复播放 // 恢复播放
@ -1211,12 +1052,6 @@ public class MusicService: NSObject {
} }
deinit { deinit {
NotificationCenter.default.removeObserver(
self,
name: AVAudioSession.interruptionNotification,
object: nil
)
NotificationCenter.default.removeObserver( NotificationCenter.default.removeObserver(
self, self,
name: NSNotification.Name("AudioSessionInterruptionBegan"), name: NSNotification.Name("AudioSessionInterruptionBegan"),

Loading…
Cancel
Save