Browse Source

Merge branch 'new_dev' of github.com:deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
lxm 12 months ago
parent
commit
ecbc262e8f
  1. 1
      lib/modules/agent/controllers/agent_controller.dart
  2. 1
      lib/modules/agent/views/music_card.dart
  3. 75
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  4. 76
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  5. 696
      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(

75
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -18,6 +18,7 @@ import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType import com.deep_voice.speech.tts.TtsEventType
import com.yunqiinnovation.ble_service.BleService import com.yunqiinnovation.ble_service.BleService
import com.deep_voice.speech.tts.AudioOutputDevice import com.deep_voice.speech.tts.AudioOutputDevice
import com.deep_voice.speech.tts.AudioDataListener
import kotlinx.coroutines.* import kotlinx.coroutines.*
import com.yunqiinnovation.azure_speech.tools.RecordFile import com.yunqiinnovation.azure_speech.tools.RecordFile
/** AzureSpeechPlugin */ /** AzureSpeechPlugin */
@ -105,6 +106,70 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
} }
} }
} }
/**
* 从WAV格式音频数据中提取纯PCM数据
*
* @param wavData WAV格式的音频数据
* @return 纯PCM音频数据
*/
private fun extractPcmFromWav(wavData: ByteArray): ByteArray {
try {
// WAV文件头最小长度为44字节
if (wavData.size < 44) {
FileLogger.e(tag, "WAV数据长度不足,无法解析文件头")
return ByteArray(0)
}
// 检查RIFF标识符
val riffHeader = String(wavData, 0, 4, Charsets.US_ASCII)
if (riffHeader != "RIFF") {
FileLogger.e(tag, "不是有效的RIFF WAV文件,标识符: $riffHeader")
return ByteArray(0)
}
// 检查WAVE标识符
val waveHeader = String(wavData, 8, 4, Charsets.US_ASCII)
if (waveHeader != "WAVE") {
FileLogger.e(tag, "不是有效的WAVE文件,标识符: $waveHeader")
return ByteArray(0)
}
// 查找data chunk
var offset = 12 // 跳过RIFF头部
while (offset < wavData.size - 8) {
val chunkId = String(wavData, offset, 4, Charsets.US_ASCII)
val chunkSize = (wavData[offset + 4].toInt() and 0xFF) or
((wavData[offset + 5].toInt() and 0xFF) shl 8) or
((wavData[offset + 6].toInt() and 0xFF) shl 16) or
((wavData[offset + 7].toInt() and 0xFF) shl 24)
if (chunkId == "data") {
// 找到data chunk,提取PCM数据
val dataOffset = offset + 8
val dataSize = minOf(chunkSize, wavData.size - dataOffset)
if (dataSize > 0) {
val pcmData = ByteArray(dataSize)
System.arraycopy(wavData, dataOffset, pcmData, 0, dataSize)
FileLogger.d(tag, "成功提取PCM数据: ${pcmData.size}字节")
return pcmData
}
break
}
// 移动到下一个chunk
offset += 8 + chunkSize
}
FileLogger.e(tag, "未找到data chunk")
return ByteArray(0)
} catch (e: Exception) {
FileLogger.e(tag, "解析WAV数据异常: ${e.message}")
return ByteArray(0)
}
}
// 音频数据 事件发送方法 // 音频数据 事件发送方法
private fun sendAudioDataEvent(event: Map<String, Any>) { private fun sendAudioDataEvent(event: Map<String, Any>) {
if (audioDataEventSink == null) { if (audioDataEventSink == null) {
@ -937,7 +1002,15 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
} }
override fun onSynthesisAudioGenerated(text: String, audioData: ByteArray) { override fun onSynthesisAudioGenerated(text: String, audioData: ByteArray) {
FileLogger.d(tag, "语音合成音频生成,文本=${text},音频数据大小=${audioData.size}") FileLogger.d(tag, "语音合成音频生成,文本=${text},音频数据大小=${audioData.size}")
BleService.writeExternalAudioData(audioData) // Azure TTS返回的是RIFF WAV格式,需要去除文件头
val pcmData = extractPcmFromWav(audioData)
if (pcmData.isNotEmpty()) {
FileLogger.d(tag, "提取PCM数据,大小=${pcmData.size}字节")
BleService.writeExternalRightAudioData(pcmData)
}
else {
FileLogger.e(tag, "提取PCM数据失败,音频数据可能不是WAV格式")
}
} }
override fun onSynthesisFailed(text: String, error: String) { override fun onSynthesisFailed(text: String, error: String) {
// sendAstEvent( // sendAstEvent(

76
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -18,7 +18,7 @@ import ble_service
// 日志标签 // 日志标签
private let tag = "AzureSpeechPlugin" private let tag = "AzureSpeechPlugin"
// 日志对象 // 日志对象
private let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin") internal let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin")
// MARK: - 防抖相关(iOS 原生层) // MARK: - 防抖相关(iOS 原生层)
// 函数级注释:用于限制 startContinuousRecognition/stopContinuousRecognition 在极短时间内的重复触发 // 函数级注释:用于限制 startContinuousRecognition/stopContinuousRecognition 在极短时间内的重复触发
@ -264,6 +264,65 @@ private func sendAstEvent(_ event: [String: Any]) {
sendAudioDataEvent(eventMap) sendAudioDataEvent(eventMap)
} }
/**
* 从WAV格式音频数据中提取纯PCM数据
* @param wavData WAV格式的音频数据
* @return 纯PCM音频数据,如果提取失败则返回空Data
*/
internal func extractPcmFromWav(_ wavData: Data) -> Data {
// WAV文件头最小长度为44字节
guard wavData.count >= 44 else {
os_log("WAV数据长度不足,无法解析文件头", log: log, type: .error)
return Data()
}
// 检查RIFF标识符
let riffHeader = String(data: wavData.subdata(in: 0..<4), encoding: .ascii) ?? ""
guard riffHeader == "RIFF" else {
os_log("不是有效的RIFF WAV文件,标识符: %@", log: log, type: .error, riffHeader)
return Data()
}
// 检查WAVE标识符
let waveHeader = String(data: wavData.subdata(in: 8..<12), encoding: .ascii) ?? ""
guard waveHeader == "WAVE" else {
os_log("不是有效的WAVE文件,标识符: %@", log: log, type: .error, waveHeader)
return Data()
}
// 查找data chunk
var offset = 12 // 跳过RIFF头部
while offset < wavData.count - 8 {
let chunkIdData = wavData.subdata(in: offset..<(offset + 4))
let chunkId = String(data: chunkIdData, encoding: .ascii) ?? ""
// 读取chunk大小(小端序)
let chunkSizeData = wavData.subdata(in: (offset + 4)..<(offset + 8))
let chunkSize = chunkSizeData.withUnsafeBytes { bytes in
return bytes.load(as: UInt32.self).littleEndian
}
if chunkId == "data" {
// 找到data chunk,提取PCM数据
let dataOffset = offset + 8
let dataSize = min(Int(chunkSize), wavData.count - dataOffset)
if dataSize > 0 {
let pcmData = wavData.subdata(in: dataOffset..<(dataOffset + dataSize))
os_log("成功提取PCM数据: %d字节", log: log, type: .info, pcmData.count)
return pcmData
}
break
}
// 移动到下一个chunk
offset += 8 + Int(chunkSize)
}
os_log("未找到data chunk", log: log, type: .error)
return Data()
}
// MARK: - ASR 方法处理 // MARK: - ASR 方法处理
private func handleAsrMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { private func handleAsrMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@ -950,9 +1009,18 @@ private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventC
* @param audioData 音频数据 * @param audioData 音频数据
*/ */
public func onSynthesisAudioGenerated(text: String, audioData: Data) { public func onSynthesisAudioGenerated(text: String, audioData: Data) {
print("语音合成音频数据生成回调", text, audioData.count) guard let plugin = plugin else { return }
// 调用BLE服务写入音频数据
BleService.shared.writeExternalAudioData(data: audioData) os_log("语音合成音频生成,文本=%@,音频数据大小=%d", log: plugin.log, type: .info, text, audioData.count)
// Azure TTS返回的是RIFF WAV格式,需要去除文件头提取PCM数据
let pcmData = plugin.extractPcmFromWav(audioData)
if !pcmData.isEmpty {
os_log("提取PCM数据,大小=%d字节", log: plugin.log, type: .info, pcmData.count)
BleService.shared.writeExternalAudioData(data: pcmData)
} else {
os_log("提取PCM数据失败,音频数据可能不是WAV格式", log: plugin.log, type: .error)
}
// plugin?.sendAsrEvent([ // plugin?.sendAsrEvent([
// "type": "synthesisAudioGenerated", // "type": "synthesisAudioGenerated",

696
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,33 @@ public class MusicService: NSObject {
} }
// MARK: - 音频会话设置 // MARK: - 音频会话设置
private func setupAudioSession() { private func setupAudioSession() {
print("🎵 [MusicService] 🚀 开始设置音频会话和通知监听器") do {
// 设置音频会话类别,添加 mixWithOthers 选项以确保通知正常显示
// 先注册通知监听器,不依赖音频会话设置成功 try AVAudioSession.sharedInstance().setCategory(
setupNotificationObservers() .playback,
mode: .default,
// 然后尝试设置音频会话 options: [.allowAirPlay, .allowBluetooth, .mixWithOthers]
setupAudioSessionConfiguration() )
try AVAudioSession.sharedInstance().setActive(true)
// 确保媒体控制中心可以接收到我们的应用 print("🎵 [MusicService] ✅ 音频会话设置成功")
setupRemoteCommandCenter() } catch {
print("🎵 [MusicService] ❌ 设置音频会话失败: \(error)")
print("🎵 [MusicService] 📋 所有通知观察者注册完成") // 尝试备用配置
print("🎵 [MusicService] 🔍 当前MusicService实例: \(Unmanaged.passUnretained(self).toOpaque())") do {
try AVAudioSession.sharedInstance().setCategory(.playback)
try AVAudioSession.sharedInstance().setActive(true)
print("🎵 [MusicService] ✅ 使用备用音频会话配置")
} catch {
print("🎵 [MusicService] ❌ 备用音频会话配置也失败: \(error)")
}
}
} }
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 +103,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 +399,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 +428,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 +437,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 +446,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 +537,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 +563,216 @@ 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.removeTarget(nil) commandCenter.playCommand.removeTarget(nil)
commandCenter.pauseCommand.removeTarget(nil) commandCenter.pauseCommand.removeTarget(nil)
commandCenter.nextTrackCommand.removeTarget(nil) commandCenter.nextTrackCommand.removeTarget(nil)
commandCenter.previousTrackCommand.removeTarget(nil) commandCenter.previousTrackCommand.removeTarget(nil)
commandCenter.changePlaybackPositionCommand.removeTarget(nil) commandCenter.stopCommand.removeTarget(nil)
// 启用命令
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.previousTrackCommand.isEnabled = true
commandCenter.stopCommand.isEnabled = true
// 播放命令
commandCenter.playCommand.addTarget { [weak self] _ in commandCenter.playCommand.addTarget { [weak self] _ in
print("🎵 [RemoteControl] 播放命令") print("🎵 [RemoteCommand] 播放命令触发")
self?.resume() self?.resume()
return .success return .success
} }
// 暂停命令
commandCenter.pauseCommand.addTarget { [weak self] _ in commandCenter.pauseCommand.addTarget { [weak self] _ in
print("🎵 [RemoteControl] 暂停命令") print("🎵 [RemoteCommand] 暂停命令触发")
self?.pause() self?.pause()
return .success return .success
} }
// 下一首命令
commandCenter.nextTrackCommand.addTarget { [weak self] _ in commandCenter.nextTrackCommand.addTarget { [weak self] _ in
print("🎵 [RemoteControl] 下一首命令") print("🎵 [RemoteCommand] 下一首命令触发")
self?.playNext() self?.next()
return .success return .success
} }
// 上一首命令
commandCenter.previousTrackCommand.addTarget { [weak self] _ in commandCenter.previousTrackCommand.addTarget { [weak self] _ in
print("🎵 [RemoteControl] 上一首命令") print("🎵 [RemoteCommand] 上一首命令触发")
self?.playPrevious() self?.previous()
return .success return .success
} }
// 进度调整命令(iOS 9.1+) commandCenter.stopCommand.addTarget { [weak self] _ in
if #available(iOS 9.1, *) { print("🎵 [RemoteCommand] 停止命令触发")
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in self?.stop()
if let positionEvent = event as? MPChangePlaybackPositionCommandEvent { return .success
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] ✅ 远程控制中心设置完成") print("🎵 [MusicService] ✅ 远程控制中心设置完成")
} }
// 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
])
// 确保在主线程更新
DispatchQueue.main.async {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
print("🎵 [MusicService] ✅ 媒体会话进度已更新: \(currentPosition/1000)s/\(duration/1000)s")
}
} }
/// 启用媒体会话回调
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
// 异步加载封面图片
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
metadata["playlistSize"] = playlist.count if let error = error {
metadata["currentIndex"] = currentIndex print("图片加载失败: \(error.localizedDescription)")
DispatchQueue.main.async {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
return
}
// 能力标志 guard let data = data, let image = UIImage(data: data) else {
metadata["canSkipToNext"] = currentIndex < playlist.count - 1 print("图片数据无效或格式不支持")
metadata["canSkipToPrevious"] = currentIndex > 0 DispatchQueue.main.async {
metadata["canSeek"] = true MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
return
}
flutterChannel.invokeMethod("onMetadataChanged", arguments: metadata) 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) { public func updateMediaSessionPlaybackState(isPlaying: Bool) {
guard let flutterChannel = flutterChannel else { return } self.isPlaying = isPlaying
let progressInfo = getProgress() // 确保在主线程更新
DispatchQueue.main.async {
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
let playbackState: [String: Any] = [ print("🎵 [MusicService] ✅ 媒体会话播放状态已更新: \(isPlaying ? "播放中" : "已暂停")")
"isPlaying": isPlaying, }
"currentPosition": progressInfo["currentPosition"] ?? 0,
"duration": progressInfo["duration"] ?? 0,
"progress": progressInfo["progress"] ?? 0.0,
"playbackSpeed": isPlaying ? 1.0 : 0.0
]
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 +785,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 +840,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
// 更新媒体会话进度
updateMediaSessionProgress(currentPosition: currentPosition,
duration: totalDuration,
progress: progress)
// 更新系统媒体信息 flutterChannel?.invokeMethod("onMediaSessionProgress", arguments: [
updateSystemMediaInfo(currentTime: currentTime, duration: duration) "currentIdInternal": currentIdInternal ?? 0,
"currentPosition": currentPosition,
"duration": totalDuration,
"progress": progress
])
} }
} }
/// 立即更新进度信息(用于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 +1032,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 +1073,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 +1097,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