Browse Source

上传qq音乐服务判断

weicu
liwei1dao 1 year ago
parent
commit
77716198b8
  1. 37
      lib/modules/agent/controllers/agent_controller.dart
  2. 10
      lib/modules/agent/views/music_card.dart
  3. 4
      lib/modules/music/views/music_playlist_view.dart
  4. 12
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  5. 6
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MusicSystemFunctionHandler.kt
  6. 90
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt
  7. 97
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicSingleton.kt
  8. 24
      local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt

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

@ -1683,56 +1683,20 @@ class AgentController extends GetxController with WidgetsBindingObserver {
duration.value = 0;
progress.value = 0.0;
final id = song['id'] ?? 0;
final timestampMs = song['ts'] ?? 0;
// 检查URL是否需要刷新(只有当有时间戳时才检查)
if (timestampMs > 0) {
final now = DateTime.now();
final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add(
const Duration(minutes: 10), // 转换为北京时间
);
// 判断是否过期
if (tsTime.isBefore(now)) {
Logger.d(TAG, '音乐URL已过期,正在获取新URL...');
try {
var response = await Api.getMusicUrl({
"ids": [id]
});
Logger.d(
TAG, 'getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据
final urldata = response['urls'][0] as Map<String, dynamic>;
song["url"] = urldata['url'] ?? '';
// 修复:确保currentId接收int类型
final newSongId = song['id'];
currentId.value = newSongId ?? 0;
Logger.d(TAG, '获取新URL成功');
} catch (e) {
Logger.e(TAG, '获取新URL失败: $e');
// 如果获取新URL失败,尝试使用原URL
}
}
}
// 转换为MusicManager期望的格式
final musicData = <String, dynamic>{
'id': song['id'],
'url': song['url']?.toString() ?? '',
'title': song['title']?.toString() ?? song['name']?.toString() ?? '',
'artist':
song['artist']?.toString() ?? song['sgener']?.toString() ?? '',
'coverUrl':
song['coverUrl']?.toString() ?? song['image']?.toString() ?? '',
};
// 调用音乐管理器播放
await _musiceManager.play(musicData);
// 更新媒体会话元数据和播放状态
await _updateMediaSessionMetadata(musicData);
await _updateMediaSessionPlaybackState(true);
Logger.d(TAG, '音乐播放启动成功');
} catch (e) {
Logger.e(TAG, '播放音乐失败: $e');
@ -1978,7 +1942,6 @@ class AgentController extends GetxController with WidgetsBindingObserver {
final formattedPlaylist = playlist.map((song) {
return {
'id': song['id'],
'url': song['url']?.toString() ?? '',
'title': song['title']?.toString() ?? song['name']?.toString() ?? '',
'artist':
song['artist']?.toString() ?? song['sgener']?.toString() ?? '',

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

@ -6,17 +6,15 @@ import '../controllers/agent_controller.dart';
import '../widgets/circular_progress_button.dart';
/// 构建音乐项目
Widget buildMusicItem(BuildContext context, int id, String name, String url,
String singer, String image, int ts) {
Widget buildMusicItem(
BuildContext context, int id, String name, String singer, String image) {
final controller = Get.find<AgentController>(); // 获取控制器
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
Map<String, dynamic> song = {
'id': id,
'url': url,
'title': name,
'artist': singer,
'coverUrl': image,
'ts': ts,
};
controller.musicStatus(); //刷新状态
@ -232,13 +230,11 @@ Widget buildMusicContent(
final item = entry.value;
final id = item["id"];
final name = item["name"] ?? "未知歌曲";
final url = item["url"] ?? "";
final singer = item["singer"] ?? "未知歌手";
final image = item["image"] ?? "";
final ts = item["ts"] ?? 0;
return Column(
children: [
buildMusicItem(context, id, name, url, singer, image, ts),
buildMusicItem(context, id, name, singer, image),
if (index != playlist.length - 1)
Divider(
height: 1,

4
lib/modules/music/views/music_playlist_view.dart

@ -193,7 +193,6 @@ class MusicPlaylistView extends StatelessWidget {
final item = playlist[index];
final id = item["id"] ?? 0;
final name = item["name"] ?? 'unknownSong'.tr; // 未知歌曲
final url = item["url"] ?? "";
final singer =
item["singer"] ?? item["sgener"] ?? 'unknownArtist'.tr; // 未知歌手
final image = item["image"] ?? "";
@ -203,7 +202,6 @@ class MusicPlaylistView extends StatelessWidget {
controller,
id,
name,
url,
singer,
image,
isDarkMode,
@ -217,7 +215,6 @@ class MusicPlaylistView extends StatelessWidget {
AgentController controller,
int id,
String name,
String url,
String singer,
String image,
bool isDarkMode,
@ -225,7 +222,6 @@ class MusicPlaylistView extends StatelessWidget {
final song = {
'id': id,
'title': name,
'url': url,
'artist': singer,
'coverUrl': image,
};

12
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -1879,19 +1879,19 @@ object AgentService : CoroutineScope {
if (meta.has("card_music")) { //音乐卡片
val cardMusic = meta.getJSONObject("card_music")
val id = cardMusic.optLong("id", 0)
val url = cardMusic.optString("url", "")
// val url = cardMusic.optString("url", "")
val name = cardMusic.optString("name", "")
val sgener = cardMusic.optString("sgener", "")
val image = cardMusic.optString("image", "")
val ts = cardMusic.optLong("ts", 0)
// val ts = cardMusic.optLong("ts", 0)
processMusicPlay(
mapOf(
"id" to id,
"url" to url,
// "url" to url,
"title" to name,
"artist" to sgener,
"coverUrl" to image,
"ts" to ts
// "ts" to ts
)
)
} else if (toolname == "music_searchandplay_songs" && meta.has("data")) { //qq音乐
@ -1901,19 +1901,15 @@ object AgentService : CoroutineScope {
for (i in 0 until musics.length()) {
val item = musics.optJSONObject(i) ?: continue
val id = item.optLong("id", 0)
val url = item.optString("url", "")
val name = item.optString("name", "")
val sgener = item.optString("sgener", "")
val image = item.optString("image", "")
val ts = item.optLong("ts", 0)
playlist.add(
mapOf(
"id" to id,
"url" to url,
"title" to name,
"artist" to sgener,
"coverUrl" to image,
"ts" to ts
)
)
}

6
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MusicSystemFunctionHandler.kt

@ -142,10 +142,10 @@ private class MusicSearchHandler(private val context: Context?) : FunctionHandle
val id = songMap["id"] ?: 0
val name = songMap["name"] ?: "unknown"
val singer = songMap["singer"] ?: "unknown"
val url = songMap["url"] ?: ""
// val url = songMap["url"] ?: ""
val image = songMap["image"] ?: ""
val ts = songMap["ts"] ?: 0
"{\"id\": $id, \"name\": \"$name\", \"singer\": \"$singer\", \"url\": \"$url\", \"ts\":$ts,\"image\": \"$image\"}"
// val ts = songMap["ts"] ?: 0
"{\"id\": $id, \"name\": \"$name\", \"singer\": \"$singer\",\"image\": \"$image\"}"
}
"{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}"
} else {

90
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

@ -50,37 +50,6 @@ class MusicService : MediaBrowserServiceCompat() {
fun isPlaying() = currentStatus
fun getCurrentId(): Long? = currentIdInternal
// // 获取播放进度信息
// fun getProgress(): Map<String, Any> {
// return if (instance != null && instance!!.mediaPlayer != null) {
// val player = instance!!.mediaPlayer!!
// try {
// val currentPosition = if (player.isPlaying || currentStatus) player.currentPosition else 0
// val duration = player.duration
// val progress = if (duration > 0) currentPosition.toDouble() / duration.toDouble() else 0.0
// mapOf(
// "currentPosition" to currentPosition,
// "duration" to duration,
// "progress" to progress
// )
// } catch (e: Exception) {
// Log.e(TAG, "获取播放进度失败: ${e.message}")
// mapOf(
// "currentPosition" to 0,
// "duration" to 0,
// "progress" to 0.0
// )
// }
// } else {
// mapOf(
// "currentPosition" to 0,
// "duration" to 0,
// "progress" to 0.0
// )
// }
// }
private var instance: MusicService? = null
private var flutterChannel: io.flutter.plugin.common.MethodChannel? = null
@ -544,17 +513,70 @@ private fun updateProgress() {
}
private fun play(song: Map<String,Any>) {
val url = song["url"] as? String ?: return
Log.i(TAG, "开始播放: $url")
val id = (song["id"] as? Number)?.toLong() ?: 0L
Log.i(TAG, "开始播放play id: $id")
val title = song["title"] as? String ?: "未知标题"
val artist = song["artist"] as? String ?: "未知歌手"
val coverUrl = song["coverUrl"] as String
// 检查 URL 和时间戳
val url = song["url"] as? String ?: ""
val ts = song["ts"] as? Long
val currentTime = System.currentTimeMillis() / 1000 // 当前时间戳(秒)
// 判断是否需要重新获取 URL
val needNewUrl = url.isEmpty() || ts == null || ts <= currentTime
if (needNewUrl) {
Log.i(TAG, "URL 不存在或已过期,重新获取 URL")
// 调用 MusicSingleton 获取新的 URL
MusicSingleton.getInstance().getSongUrl(id) { result ->
result.fold(
onSuccess = { urlData ->
val newUrl = urlData["url"] as? String
if (!newUrl.isNullOrEmpty()) {
// 更新 song 数据
val updatedSong = song.toMutableMap().apply {
put("url", newUrl)
put("ts", urlData["ts"] ?: 0L)
}
// 更新 playlist 中对应的歌曲数据
updatePlaylistSong(id, updatedSong)
// 使用新 URL 开始播放
startPlayback(updatedSong, newUrl, title, artist, coverUrl, id)
} else {
Log.e(TAG, "获取到的 URL 为空")
}
},
onFailure = { exception ->
Log.e(TAG, "获取 URL 失败: ${exception.message}")
}
)
}
} else {
// URL 有效,直接播放
startPlayback(song, url, title, artist, coverUrl, id)
}
}
// 新增方法:更新 playlist 中指定 ID 的歌曲数据
private fun updatePlaylistSong(songId: Long, updatedSong: Map<String, Any>) {
for (i in playlist.indices) {
val playlistSong = playlist[i]
val playlistSongId = (playlistSong["id"] as? Number)?.toLong() ?: 0L
if (playlistSongId == songId) {
// 找到匹配的歌曲,更新数据
playlist[i] = updatedSong.toMap()
Log.i(TAG, "已更新 playlist 中 ID 为 $songId 的歌曲数据")
break
}
}
}
private fun startPlayback(song: Map<String,Any>, url: String, title: String, artist: String, coverUrl: String, id: Long) {
mediaPlayer?.release()
currentStatus = true
currentIdInternal = id
Log.i(TAG, "开始播放play currentIdInternal: $currentIdInternal")
Log.i(TAG, "开始播放play currentIdInternal: $currentIdInternal")
mediaPlayer = MediaPlayer().apply {
setDataSource(url)
setAudioStreamType(AudioManager.STREAM_MUSIC)

97
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicSingleton.kt

@ -187,9 +187,9 @@ class MusicSingleton private constructor() {
"id" to songObj.optLong("id"),
"name" to songObj.optString("name"),
"singer" to songObj.optString("singer"),
"url" to songObj.optString("url"),
// "url" to songObj.optString("url"),
"image" to songObj.optString("image"),
"ts" to songObj.optLong("ts"),
// "ts" to songObj.optLong("ts"),
))
}
}
@ -199,4 +199,97 @@ class MusicSingleton private constructor() {
return songs
}
fun getSongUrl(
id: Long,
callback: (Result<Map<String, Any?>>) -> Unit
) {
// 使用协程异步处理,避免阻塞主线程
coroutineScope.launch {
try {
// 配置带超时的 OkHttpClient
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
// 构建搜索请求的 JSON 数据
val jsonObject = JSONObject().apply {
put("id", id)
}
val json = jsonObject.toString()
val mediaType = "application/json".toMediaType()
val body = json.toRequestBody(mediaType)
// 构建请求
val request = Request.Builder()
.url("$url/api/home/music_url")
.addHeader("Authorization", token)
.addHeader("Content-Type", "application/json")
.post(body)
.build()
Log.d(TAG, "发送搜索请求: $json")
// 使用异步调用
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e(TAG, "搜索歌曲请求失败: ${e.message}", e)
callback(Result.failure(e))
}
override fun onResponse(call: Call, response: Response) {
response.use {
try {
if (response.isSuccessful) {
val responseBody = response.body?.string()
Log.d(TAG, "搜索响应: $responseBody")
if (responseBody != null) {
val jsonResponse = JSONObject(responseBody)
val url = parseUrlFromResponse(jsonResponse)
callback(Result.success(url))
} else {
callback(Result.failure(Exception("响应体为空")))
}
} else {
val errorMsg = "搜索失败,HTTP状态码: ${response.code}"
Log.w(TAG, errorMsg)
callback(Result.failure(Exception(errorMsg)))
}
} catch (e: Exception) {
Log.e(TAG, "解析搜索响应时发生异常: ${e.message}", e)
callback(Result.failure(e))
}
}
}
})
} catch (e: Exception) {
Log.e(TAG, "搜索歌曲时发生异常: ${e.message}", e)
callback(Result.failure(e))
}
}
}
/**
* 解析服务器响应中的歌曲列表
*/
private fun parseUrlFromResponse(jsonResponse: JSONObject): Map<String, Any?> {
val urlobj = mutableMapOf<String, Any?>()
try {
val data = jsonResponse.optJSONObject("data")
val id = data?.optLong("id")
val url = data?.optString("url")
val ts = data?.optLong("ts")
urlobj["id"] = id
urlobj["url"] = url
urlobj["ts"] = ts
} catch (e: Exception) {
Log.e(TAG, "Failed to parse songs from response", e)
}
return urlobj
}
}

24
local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt

@ -366,9 +366,25 @@ class QQMusicSingleton private constructor() {
* 暂停音乐
*/
fun autopauseMusic(callback: (Result<Unit>) -> Unit) {
executePlaybackAction("自动暂停") { musicManager, playCallback ->
musicManager.autopauseMusic(playCallback)
}(callback)
if (context == null || musicManager == null) {
callback(Result.failure(Exception("服务未初始化")))
return
}
musicManager?.autopauseMusic(object : QQMusicManager.PlayCallback {
override fun onSuccess() {
handler.post {
Log.d(TAG, "暂停成功")
callback(Result.success(Unit))
}
}
override fun onError(errorCode: Int, errorMessage: String) {
handler.post {
Log.e(TAG, "暂停失败: $errorMessage")
callback(Result.failure(Exception("暂停失败: $errorMessage")))
}
}
})
}
/**
* 恢复播放
@ -576,7 +592,7 @@ class QQMusicSingleton private constructor() {
callback(Result.success(Unit))
}
}
override fun onError(errorCode: Int, errorMessage: String) {
handler.post {
Log.e(TAG, "播放失败: $errorCode $errorMessage")

Loading…
Cancel
Save