|
|
|
@ -14,17 +14,37 @@ public class MusicService: NSObject { |
|
|
|
public static let shared: MusicService = MusicService() |
|
|
|
|
|
|
|
private var player: AVPlayer? |
|
|
|
private var playlist: [[String: String]] = [] |
|
|
|
private var playlist: [[String: Any]] = [] |
|
|
|
private var currentIndex = 0 |
|
|
|
public var isPlaying = false |
|
|
|
private var flutterChannel: FlutterMethodChannel? |
|
|
|
private var progressTimer: Timer? |
|
|
|
|
|
|
|
// 音量控制相关 |
|
|
|
private var currentVolume: Float = 1.0 |
|
|
|
private var originalVolume: Float = 1.0 |
|
|
|
private var isVolumeReduced: Bool = false |
|
|
|
|
|
|
|
// 当前播放的歌曲ID |
|
|
|
private var currentIdInternal: Int64? = nil |
|
|
|
|
|
|
|
private override init() { |
|
|
|
super.init() |
|
|
|
setupRemoteCommandCenter() |
|
|
|
setupAudioSession() |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 音频会话设置 |
|
|
|
private func setupAudioSession() { |
|
|
|
do { |
|
|
|
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.allowAirPlay, .allowBluetooth]) |
|
|
|
try AVAudioSession.sharedInstance().setActive(true) |
|
|
|
} catch { |
|
|
|
print("设置音频会话失败: \(error)") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 基础播放控制 |
|
|
|
func getCurrentUrlString() -> String? { |
|
|
|
guard let urlAsset = player?.currentItem?.asset as? AVURLAsset else { |
|
|
|
return nil |
|
|
|
@ -32,53 +52,149 @@ public class MusicService: NSObject { |
|
|
|
return urlAsset.url.absoluteString |
|
|
|
} |
|
|
|
|
|
|
|
public func play(song: [String: String]) { |
|
|
|
guard let urlString = song["url"], let url = URL(string: urlString) else { return } |
|
|
|
player = AVPlayer(url: url) |
|
|
|
player?.play() |
|
|
|
isPlaying = true |
|
|
|
updateNowPlayingInfo(song: song) |
|
|
|
func getCurrentId() -> Int64? { |
|
|
|
return currentIdInternal |
|
|
|
} |
|
|
|
|
|
|
|
public func playPlaylist(songs: [[String: String]]) { |
|
|
|
public func play(song: [String: Any]) { |
|
|
|
guard let urlString = song["url"] as? String, let url = URL(string: urlString) else { return } |
|
|
|
|
|
|
|
// 获取歌曲ID |
|
|
|
if let id = song["id"] as? NSNumber { |
|
|
|
currentIdInternal = id.int64Value |
|
|
|
} else { |
|
|
|
currentIdInternal = 0 |
|
|
|
} |
|
|
|
|
|
|
|
print("开始播放: \(urlString), ID: \(currentIdInternal ?? 0)") |
|
|
|
|
|
|
|
// 检查是否已在播放列表中 |
|
|
|
let existingIndex = playlist.firstIndex { existingSong in |
|
|
|
if let existingId = existingSong["id"] as? NSNumber, |
|
|
|
let currentId = song["id"] as? NSNumber { |
|
|
|
return existingId.int64Value == currentId.int64Value |
|
|
|
} |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
if existingIndex == nil { |
|
|
|
playlist.append(song) |
|
|
|
currentIndex = playlist.count - 1 |
|
|
|
} else { |
|
|
|
currentIndex = existingIndex! |
|
|
|
} |
|
|
|
|
|
|
|
// 创建播放项 |
|
|
|
let playerItem = AVPlayerItem(url: url) |
|
|
|
player = AVPlayer(playerItem: playerItem) |
|
|
|
|
|
|
|
// 设置播放器观察者 |
|
|
|
setupPlayerObservers() |
|
|
|
|
|
|
|
// 监听播放项状态变化 |
|
|
|
setupPlayerItemObservers(playerItem: playerItem, song: song) |
|
|
|
|
|
|
|
currentVolume = 1.0 |
|
|
|
player?.volume = currentVolume |
|
|
|
|
|
|
|
print("播放器已创建,等待加载完成...") |
|
|
|
} |
|
|
|
|
|
|
|
public func playPlaylist(songs: [[String: Any]]) { |
|
|
|
playlist = songs |
|
|
|
currentIndex = 0 |
|
|
|
if !playlist.isEmpty { |
|
|
|
play(song: playlist[currentIndex]) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func pause() { |
|
|
|
player?.pause() |
|
|
|
isPlaying = false |
|
|
|
updatePlaybackState() |
|
|
|
updateMediaSessionPlaybackState(isPlaying: false) |
|
|
|
stopProgressTimer() |
|
|
|
print("已暂停") |
|
|
|
} |
|
|
|
|
|
|
|
func resume() { |
|
|
|
player?.play() |
|
|
|
isPlaying = true |
|
|
|
updatePlaybackState() |
|
|
|
updateMediaSessionPlaybackState(isPlaying: true) |
|
|
|
startProgressTimer() |
|
|
|
print("继续播放") |
|
|
|
} |
|
|
|
|
|
|
|
func next() { |
|
|
|
guard currentIndex + 1 < playlist.count else { return } |
|
|
|
currentIndex += 1 |
|
|
|
play(song: playlist[currentIndex]) |
|
|
|
playNext() |
|
|
|
} |
|
|
|
|
|
|
|
func previous() { |
|
|
|
guard currentIndex - 1 >= 0 else { return } |
|
|
|
currentIndex -= 1 |
|
|
|
playPrevious() |
|
|
|
} |
|
|
|
|
|
|
|
private func playNext() { |
|
|
|
guard !playlist.isEmpty else { return } |
|
|
|
currentIndex = (currentIndex + 1) % playlist.count |
|
|
|
play(song: playlist[currentIndex]) |
|
|
|
} |
|
|
|
|
|
|
|
private func playPrevious() { |
|
|
|
guard !playlist.isEmpty else { return } |
|
|
|
currentIndex = currentIndex - 1 < 0 ? playlist.count - 1 : currentIndex - 1 |
|
|
|
play(song: playlist[currentIndex]) |
|
|
|
} |
|
|
|
|
|
|
|
private func playNextOrStop() { |
|
|
|
if currentIndex + 1 < playlist.count { |
|
|
|
currentIndex += 1 |
|
|
|
play(song: playlist[currentIndex]) |
|
|
|
} else { |
|
|
|
stop() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func stop() { |
|
|
|
player?.pause() |
|
|
|
player?.replaceCurrentItem(with: nil) |
|
|
|
isPlaying = false |
|
|
|
currentIdInternal = nil |
|
|
|
updatePlaybackState() |
|
|
|
updateMediaSessionPlaybackState(isPlaying: false) |
|
|
|
stopProgressTimer() |
|
|
|
print("已停止") |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 音量控制 |
|
|
|
func setVolume(volume: Float) { |
|
|
|
player?.volume = volume |
|
|
|
let clampedVolume = max(0.0, min(1.0, volume)) |
|
|
|
currentVolume = clampedVolume |
|
|
|
player?.volume = clampedVolume |
|
|
|
print("设置音量: \(clampedVolume)") |
|
|
|
} |
|
|
|
|
|
|
|
/// 降低音量(AI说话时) |
|
|
|
func reduceVolume() { |
|
|
|
if !isVolumeReduced { |
|
|
|
originalVolume = currentVolume |
|
|
|
let reducedVolume = max(0.0, min(1.0, currentVolume * 0.3)) // 降低到30% |
|
|
|
setVolume(volume: reducedVolume) |
|
|
|
isVolumeReduced = true |
|
|
|
print("AI说话开始,音量从 \(originalVolume) 降低到 \(reducedVolume)") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// 恢复原始音量(AI说话结束时) |
|
|
|
func restoreVolume() { |
|
|
|
if isVolumeReduced { |
|
|
|
setVolume(volume: originalVolume) |
|
|
|
isVolumeReduced = false |
|
|
|
print("AI说话结束,音量恢复到 \(originalVolume)") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 进度控制 |
|
|
|
func getProgress() -> [String: Any] { |
|
|
|
guard let player = player, let currentItem = player.currentItem else { |
|
|
|
return [ |
|
|
|
@ -91,23 +207,55 @@ public class MusicService: NSObject { |
|
|
|
let currentTime = CMTimeGetSeconds(player.currentTime()) |
|
|
|
let duration = CMTimeGetSeconds(currentItem.duration) |
|
|
|
|
|
|
|
// 处理无效值 |
|
|
|
let currentPosition = currentTime.isNaN || currentTime.isInfinite ? 0 : Int(currentTime * 1000) |
|
|
|
let totalDuration = duration.isNaN || duration.isInfinite ? 0 : Int(duration * 1000) |
|
|
|
let progress = totalDuration > 0 ? currentTime / duration : 0.0 |
|
|
|
// 更严格的有效性检查 |
|
|
|
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 |
|
|
|
|
|
|
|
return [ |
|
|
|
let result = [ |
|
|
|
"currentPosition": currentPosition, |
|
|
|
"duration": totalDuration, |
|
|
|
"progress": progress.isNaN || progress.isInfinite ? 0.0 : progress |
|
|
|
] |
|
|
|
|
|
|
|
// 添加调试日志 |
|
|
|
if totalDuration == 0 { |
|
|
|
print("警告: 音频时长为0,可能还在加载中。原始duration: \(duration)") |
|
|
|
} |
|
|
|
|
|
|
|
return result |
|
|
|
} |
|
|
|
|
|
|
|
func seekTo(positionMs: Int) { |
|
|
|
guard let player = player else { return } |
|
|
|
|
|
|
|
// 记录当前播放状态 |
|
|
|
let wasPlaying = isPlaying |
|
|
|
|
|
|
|
let time = CMTime(seconds: Double(positionMs) / 1000.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) |
|
|
|
player.seek(to: time) |
|
|
|
|
|
|
|
player.seek(to: time) { [weak self] completed in |
|
|
|
if completed { |
|
|
|
print("跳转到位置: \(positionMs)ms") |
|
|
|
|
|
|
|
// 如果之前是播放状态,确保继续播放 |
|
|
|
if wasPlaying { |
|
|
|
self?.player?.play() |
|
|
|
self?.isPlaying = true |
|
|
|
self?.updateMediaSessionPlaybackState(isPlaying: true) |
|
|
|
self?.startProgressTimer() |
|
|
|
} |
|
|
|
|
|
|
|
// 立即更新进度信息到系统媒体控制中心 |
|
|
|
self?.updateProgressInfoImmediately() |
|
|
|
|
|
|
|
// 通知Flutter层 |
|
|
|
self?.flutterChannel?.invokeMethod("onMediaSessionSeekTo", arguments: ["position": positionMs]) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 媒体会话控制 |
|
|
|
private func setupRemoteCommandCenter() { |
|
|
|
let commandCenter = MPRemoteCommandCenter.shared() |
|
|
|
|
|
|
|
@ -130,22 +278,42 @@ public class MusicService: NSObject { |
|
|
|
self.previous() |
|
|
|
return .success |
|
|
|
} |
|
|
|
|
|
|
|
commandCenter.stopCommand.addTarget { [unowned self] _ in |
|
|
|
self.stop() |
|
|
|
return .success |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private func updateNowPlayingInfo(song: [String: String]) { |
|
|
|
private func updateNowPlayingInfo(song: [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] = [ |
|
|
|
MPMediaItemPropertyTitle: song["title"] ?? "Unknown Title", |
|
|
|
MPMediaItemPropertyArtist: song["artist"] ?? "Unknown Artist" |
|
|
|
MPMediaItemPropertyTitle: title, |
|
|
|
MPMediaItemPropertyArtist: artist |
|
|
|
] |
|
|
|
|
|
|
|
if let coverUrlString = song["coverUrl"], let coverUrl = URL(string: coverUrlString), |
|
|
|
let data = try? Data(contentsOf: coverUrl), |
|
|
|
// 异步加载封面图片 |
|
|
|
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 { |
|
|
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private func updatePlaybackState() { |
|
|
|
// iOS manages playback state automatically, but you can update NowPlayingInfo if needed |
|
|
|
@ -170,14 +338,22 @@ public class MusicService: NSObject { |
|
|
|
flutterChannel = channel |
|
|
|
setupEnhancedRemoteCommandCenter() |
|
|
|
startProgressTimer() |
|
|
|
print("媒体会话回调已启用") |
|
|
|
} |
|
|
|
|
|
|
|
/// 更新媒体会话元数据 |
|
|
|
public func updateMediaSessionMetadata(song: [String: Any]) { |
|
|
|
let title = song["title"] as? String ?? song["name"] as? String ?? "Unknown Title" |
|
|
|
let artist = song["artist"] as? String ?? song["sgener"] as? String ?? "Unknown Artist" |
|
|
|
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 ?? "" |
|
|
|
|
|
|
|
// 更新当前歌曲ID |
|
|
|
if let id = song["id"] as? NSNumber { |
|
|
|
currentIdInternal = id.int64Value |
|
|
|
} |
|
|
|
|
|
|
|
print("updateMediaSessionMetadata currentIdInternal: \(currentIdInternal ?? 0)") |
|
|
|
|
|
|
|
var nowPlayingInfo: [String: Any] = [ |
|
|
|
MPMediaItemPropertyTitle: title, |
|
|
|
MPMediaItemPropertyArtist: artist |
|
|
|
@ -201,6 +377,8 @@ public class MusicService: NSObject { |
|
|
|
} else { |
|
|
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo |
|
|
|
} |
|
|
|
|
|
|
|
print("媒体会话元数据已更新: \(title) - \(artist)") |
|
|
|
} |
|
|
|
|
|
|
|
/// 更新媒体会话播放状态 |
|
|
|
@ -215,6 +393,8 @@ public class MusicService: NSObject { |
|
|
|
} else { |
|
|
|
stopProgressTimer() |
|
|
|
} |
|
|
|
|
|
|
|
print("媒体会话播放状态已更新: \(isPlaying)") |
|
|
|
} |
|
|
|
|
|
|
|
/// 设置增强的远程控制中心 |
|
|
|
@ -226,6 +406,7 @@ public class MusicService: NSObject { |
|
|
|
commandCenter.pauseCommand.removeTarget(nil) |
|
|
|
commandCenter.nextTrackCommand.removeTarget(nil) |
|
|
|
commandCenter.previousTrackCommand.removeTarget(nil) |
|
|
|
commandCenter.changePlaybackPositionCommand.removeTarget(nil) |
|
|
|
|
|
|
|
// 播放命令 |
|
|
|
commandCenter.playCommand.addTarget { [weak self] _ in |
|
|
|
@ -255,13 +436,12 @@ public class MusicService: NSObject { |
|
|
|
return .success |
|
|
|
} |
|
|
|
|
|
|
|
// 跳转命令 |
|
|
|
// 跳转命令 - 关键升级点 |
|
|
|
commandCenter.changePlaybackPositionCommand.isEnabled = true |
|
|
|
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in |
|
|
|
if let event = event as? MPChangePlaybackPositionCommandEvent { |
|
|
|
let positionMs = Int(event.positionTime * 1000) |
|
|
|
self?.seekTo(positionMs: positionMs) |
|
|
|
self?.flutterChannel?.invokeMethod("onMediaSessionSeekTo", arguments: ["position": positionMs]) |
|
|
|
return .success |
|
|
|
} |
|
|
|
return .commandFailed |
|
|
|
@ -273,6 +453,8 @@ public class MusicService: NSObject { |
|
|
|
self?.flutterChannel?.invokeMethod("onMediaSessionStop", arguments: nil) |
|
|
|
return .success |
|
|
|
} |
|
|
|
|
|
|
|
print("增强的远程控制中心已设置,支持进度条滑动") |
|
|
|
} |
|
|
|
|
|
|
|
/// 启动进度定时器 |
|
|
|
@ -297,11 +479,147 @@ public class MusicService: NSObject { |
|
|
|
let duration = CMTimeGetSeconds(currentItem.duration) |
|
|
|
|
|
|
|
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 = totalDuration > 0 ? Float(currentTime / duration) : 0.0 |
|
|
|
|
|
|
|
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 } |
|
|
|
|
|
|
|
let currentTime = CMTimeGetSeconds(player.currentTime()) |
|
|
|
let duration = CMTimeGetSeconds(currentItem.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 ?? [:] |
|
|
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime |
|
|
|
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration |
|
|
|
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1.0 |
|
|
|
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0 |
|
|
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 播放完成处理 |
|
|
|
private func setupPlayerObservers() { |
|
|
|
guard let player = player else { return } |
|
|
|
|
|
|
|
NotificationCenter.default.addObserver( |
|
|
|
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, .initial], context: nil) |
|
|
|
playerItem.addObserver(self, forKeyPath: "duration", options: [.new], context: nil) |
|
|
|
playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.new], context: nil) |
|
|
|
|
|
|
|
// 播放完成通知 |
|
|
|
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) |
|
|
|
} |
|
|
|
|
|
|
|
// 存储歌曲信息,用于状态变化时更新 |
|
|
|
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 playerItem = object as? AVPlayerItem else { return } |
|
|
|
|
|
|
|
switch keyPath { |
|
|
|
case "status": |
|
|
|
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 "duration": |
|
|
|
let duration = CMTimeGetSeconds(playerItem.duration) |
|
|
|
if !duration.isNaN && !duration.isInfinite && duration > 0 { |
|
|
|
print("获取到音频时长: \(duration) 秒") |
|
|
|
// 更新系统媒体信息 |
|
|
|
updateProgressInfoImmediately() |
|
|
|
} |
|
|
|
|
|
|
|
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: |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
|