|
|
|
@ -16,6 +16,7 @@ public class MusicService: NSObject { |
|
|
|
private var player: AVPlayer? |
|
|
|
private var playlist: [[String: Any]] = [] |
|
|
|
private var currentIndex = 0 |
|
|
|
private var currentsongid = Int64(0) |
|
|
|
public var isPlaying = false |
|
|
|
private var flutterChannel: FlutterMethodChannel? |
|
|
|
private var progressTimer: Timer? |
|
|
|
@ -34,7 +35,7 @@ public class MusicService: NSObject { |
|
|
|
private override init() { |
|
|
|
super.init() |
|
|
|
setupRemoteCommandCenter() |
|
|
|
//setupAudioSession() |
|
|
|
setupAudioSession() |
|
|
|
} |
|
|
|
|
|
|
|
public func initialize(serviceUrl: String, userToken: String) { |
|
|
|
@ -44,15 +45,15 @@ public class MusicService: NSObject { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// // MARK: - 音频会话设置 |
|
|
|
// private func setupAudioSession() { |
|
|
|
// do { |
|
|
|
// try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.allowAirPlay, .allowBluetooth]) |
|
|
|
// try AVAudioSession.sharedInstance().setActive(true) |
|
|
|
// } catch { |
|
|
|
// print("设置音频会话失败: \(error)") |
|
|
|
// } |
|
|
|
// } |
|
|
|
// 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? { |
|
|
|
@ -153,7 +154,7 @@ public class MusicService: NSObject { |
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 获取歌曲URL |
|
|
|
public func getSongUrl(id: Int64, completion: @escaping (Result<[String: Any?], Error>) -> Void) { |
|
|
|
public func getSongUrl(id: Int64,curragnt:Int,allagent:Int, completion: @escaping (Result<[String: Any?], Error>) -> Void) { |
|
|
|
guard !serviceUrl.isEmpty, let url = URL(string: "\(serviceUrl)/api/home/music_url") else { |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Service URL is not configured"]))) |
|
|
|
return |
|
|
|
@ -172,7 +173,12 @@ public class MusicService: NSObject { |
|
|
|
do { |
|
|
|
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: []) |
|
|
|
} catch { |
|
|
|
completion(.failure(error)) |
|
|
|
if (allagent > 0 && curragnt < allagent){ |
|
|
|
getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) |
|
|
|
}else{ |
|
|
|
completion(.failure(error)) |
|
|
|
} |
|
|
|
|
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
@ -181,20 +187,32 @@ public class MusicService: NSObject { |
|
|
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in |
|
|
|
if let error = error { |
|
|
|
print("获取URL请求失败: \(error.localizedDescription)") |
|
|
|
completion(.failure(error)) |
|
|
|
if (allagent > 0 && curragnt < allagent){ |
|
|
|
self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) |
|
|
|
}else{ |
|
|
|
completion(.failure(error)) |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { |
|
|
|
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 |
|
|
|
print("获取URL失败,HTTP状态码: \(statusCode)") |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "获取URL失败,HTTP状态码: \(statusCode)"]))) |
|
|
|
if (allagent > 0 && curragnt < allagent){ |
|
|
|
self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) |
|
|
|
}else{ |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "获取URL失败,HTTP状态码: \(statusCode)"]))) |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
guard let data = data else { |
|
|
|
print("获取URL响应数据为空") |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: -2, userInfo: [NSLocalizedDescriptionKey: "响应数据为空"]))) |
|
|
|
if (allagent > 0 && curragnt < allagent){ |
|
|
|
self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) |
|
|
|
}else{ |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: -2, userInfo: [NSLocalizedDescriptionKey: "响应数据为空"]))) |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
@ -202,13 +220,29 @@ public class MusicService: NSObject { |
|
|
|
if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { |
|
|
|
let urlData = self.parseUrlFromResponse(jsonResponse: jsonResponse) |
|
|
|
print("获取URL成功: \(urlData)") |
|
|
|
completion(.success(urlData)) |
|
|
|
if (!urlData.isEmpty){ |
|
|
|
completion(.success(urlData)) |
|
|
|
}else{ |
|
|
|
if (allagent > 0 && curragnt < allagent){ |
|
|
|
self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) |
|
|
|
}else{ |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: -3, userInfo: [NSLocalizedDescriptionKey: "获取连接失败!"]))) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: -3, userInfo: [NSLocalizedDescriptionKey: "解析JSON响应失败"]))) |
|
|
|
if (allagent > 0 && curragnt < allagent){ |
|
|
|
self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) |
|
|
|
}else{ |
|
|
|
completion(.failure(NSError(domain: "MusicService", code: -3, userInfo: [NSLocalizedDescriptionKey: "解析JSON响应失败"]))) |
|
|
|
} |
|
|
|
} |
|
|
|
} catch { |
|
|
|
print("解析获取URL响应时发生异常: \(error.localizedDescription)") |
|
|
|
completion(.failure(error)) |
|
|
|
if (allagent > 0 && curragnt < allagent){ |
|
|
|
self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) |
|
|
|
}else{ |
|
|
|
completion(.failure(error)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -248,7 +282,7 @@ public class MusicService: NSObject { |
|
|
|
id = 0 |
|
|
|
currentIdInternal = 0 |
|
|
|
} |
|
|
|
|
|
|
|
currentsongid = id |
|
|
|
print("开始播放play id: \(id)") |
|
|
|
|
|
|
|
// 检查 URL 和时间戳 |
|
|
|
@ -262,7 +296,7 @@ public class MusicService: NSObject { |
|
|
|
if needNewUrl { |
|
|
|
print("URL 不存在或已过期,重新获取 URL") |
|
|
|
// 调用 getSongUrl 获取新的 URL |
|
|
|
getSongUrl(id: id) { [weak self] result in |
|
|
|
getSongUrl(id: id,curragnt: 0,allagent: 3) { [weak self] result in |
|
|
|
DispatchQueue.main.async { |
|
|
|
switch result { |
|
|
|
case .success(let urlData): |
|
|
|
@ -275,8 +309,11 @@ public class MusicService: NSObject { |
|
|
|
// 更新 playlist 中对应的歌曲数据 |
|
|
|
self?.updatePlaylistSong(songId: id, updatedSong: updatedSong) |
|
|
|
|
|
|
|
// 使用新 URL 开始播放 |
|
|
|
self?.startPlayback(song: updatedSong, url: newUrl) |
|
|
|
if (self?.currentsongid == id) { |
|
|
|
// 使用新 URL 开始播放 |
|
|
|
self?.startPlayback(song: updatedSong, url: newUrl) |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
print("获取到的 URL 为空") |
|
|
|
} |
|
|
|
@ -601,20 +638,39 @@ public class MusicService: NSObject { |
|
|
|
|
|
|
|
// 异步加载封面图片 |
|
|
|
if !coverUrl.isEmpty, let url = URL(string: coverUrl) { |
|
|
|
print("开始加载封面图片: \(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 |
|
|
|
// 创建带超时的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 |
|
|
|
} |
|
|
|
} else { |
|
|
|
|
|
|
|
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 |
|
|
|
} |
|
|
|
|
|
|
|
|