diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index de01cd468..d26cbc350 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -5,6 +5,7 @@ repositories {
mavenCentral()
flatDir {
dirs(project(":spotify_sdk").projectDir.resolve("libs"))
+ dirs(project(":qq_music").projectDir.resolve("libs")) // 添加这行
}
}
diff --git a/android/app/sign/DeepSound.properties b/android/app/sign/DeepSound.properties
deleted file mode 100644
index 96bb7e16a..000000000
--- a/android/app/sign/DeepSound.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-storePassword=13071376716
-keyPassword=13071376716
-keyAlias=DeepSound
-storeFile=sign/DeepSound.keystore
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ebb128fb5..ceba71e9f 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -96,7 +96,7 @@
-
+
+
+
diff --git a/local_plugins/qq_music/android/src/main/aidl/com/yunqiinnovation/qq_music/IPlayService.aidl b/local_plugins/qq_music/android/src/main/aidl/com/yunqiinnovation/qq_music/IPlayService.aidl
new file mode 100644
index 000000000..718a193aa
--- /dev/null
+++ b/local_plugins/qq_music/android/src/main/aidl/com/yunqiinnovation/qq_music/IPlayService.aidl
@@ -0,0 +1,15 @@
+// IPlayService.aidl
+package com.yunqiinnovation.qq_music;
+
+// Declare any non-default types here with import statements
+
+import com.yunqiinnovation.qq_music.IPrint;
+
+interface IPlayService {
+ void bindService();
+ void startPcmMode();
+ void stopPcmMode();
+ void resumeOrPause();
+ void playNext();
+ void setPrintMessageCallback(IPrint callback);
+}
\ No newline at end of file
diff --git a/local_plugins/qq_music/android/src/main/aidl/com/yunqiinnovation/qq_music/IPrint.aidl b/local_plugins/qq_music/android/src/main/aidl/com/yunqiinnovation/qq_music/IPrint.aidl
new file mode 100644
index 000000000..70d14f41c
--- /dev/null
+++ b/local_plugins/qq_music/android/src/main/aidl/com/yunqiinnovation/qq_music/IPrint.aidl
@@ -0,0 +1,8 @@
+// IPrintMessage.aidl
+package com.yunqiinnovation.qq_music;
+
+// Declare any non-default types here with import statements
+
+interface IPrint {
+ void print(String msg);
+}
\ No newline at end of file
diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/ApiSample.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/ApiSample.kt
new file mode 100644
index 000000000..1f1fb70d7
--- /dev/null
+++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/ApiSample.kt
@@ -0,0 +1,361 @@
+package com.tencent.qqmusic.api.demo
+
+import android.os.Bundle
+import androidx.annotation.Nullable
+import android.util.Log
+import com.google.gson.Gson
+import com.tencent.qqmusic.third.api.contract.*
+import com.tencent.qqmusic.third.api.contract.ErrorCodes.ERROR_API_NOT_INITIALIZED
+
+
+//
+// Created by tylertan on 2020-03-19.
+// Copyright (c) 2020 Tencent. All rights reserved.
+//
+
+/**
+ * 对[Methods]中的接口进行基础封装
+ *
+ */
+object ApiSample {
+
+ const val TAG = "ApiSample"
+
+ /**
+ * 获取接口返回错误码,对应[ErrorCodes]中的错误码列表
+ *
+ * @return 返回[ErrorCodes.ERROR_OK]代表成功,反之为失败
+ */
+ private fun apiCode(bundle: Bundle?): Int? {
+ return bundle?.getInt(Keys.API_RETURN_KEY_CODE)
+ }
+
+ /**
+ * 获取接口返回错误详细信息
+ *
+ */
+ private fun apiError(bundle: Bundle?): String? {
+ return bundle?.getString(Keys.API_RETURN_KEY_ERROR)
+ }
+
+ fun hi(api: IQQMusicApi?) {
+ val bundle = Bundle()
+ bundle.putInt(Keys.API_PARAM_KEY_SDK_VERSION, CommonCmd.SDK_VERSION)
+ bundle.putString(Keys.API_PARAM_KEY_PLATFORM_TYPE, CommonCmd.AIDL_PLATFORM_TYPE_PHONE)
+ val result = api?.execute("hi", bundle)
+ Log.i(TAG, "sayHi ret:" + result!!.getInt(Keys.API_RETURN_KEY_CODE))
+ }
+
+ fun playSongMid(api: IQQMusicApi?, ids: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("midList", ids)
+ api?.executeAsync("playSongMid", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ private fun playSongMidAtIndex(api: IQQMusicApi?, songList: List, index: Int, block: (() -> Unit)) {
+ val midList = ArrayList()
+ for (i in songList.indices) {
+ midList.add(songList[i].mid)
+ }
+
+ val params = Bundle()
+ params.putStringArrayList("midList", midList)
+ params.putInt("index", index)
+ Log.i(TAG, "[playSongMidAtIndex] 播放歌曲列表... $index,${songList.size}")
+ api?.executeAsync("playSongMidAtIndex", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun playSongLocalPath(api: IQQMusicApi?, pathList: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("pathList", pathList)
+ api?.executeAsync("playSongLocalPath", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun playMusic(api: IQQMusicApi?): Int {
+ val ret = api?.execute("playMusic", null)
+ return ret?.getInt(Keys.API_RETURN_KEY_CODE) ?: 0
+ }
+
+ fun stopMusic(api: IQQMusicApi?): Int {
+ val ret = api?.execute("stopMusic", null)
+ return ret?.getInt(Keys.API_RETURN_KEY_CODE) ?: 0
+ }
+
+ fun pauseMusic(api: IQQMusicApi?): Int {
+ val ret = api?.execute("pauseMusic", null)
+ return ret?.getInt(Keys.API_RETURN_KEY_CODE) ?: 0
+ }
+
+ fun resumeMusic(api: IQQMusicApi?): Int {
+ val ret = api?.execute("resumeMusic", null)
+ return ret?.getInt(Keys.API_RETURN_KEY_CODE) ?: 0
+ }
+
+ fun skipToNext(api: IQQMusicApi?): Int {
+ val ret = api?.execute("skipToNext", null)
+ return ret?.getInt(Keys.API_RETURN_KEY_CODE) ?: 0
+ }
+
+ fun skipToPrevious(api: IQQMusicApi?): Int {
+ val ret = api?.execute("skipToPrevious", null)
+ return ret?.getInt(Keys.API_RETURN_KEY_CODE) ?: 0
+ }
+
+ fun getPlaybackState(api: IQQMusicApi?): Int {
+ val ret = api?.execute("getPlaybackState", null)
+ return ret?.getInt(Keys.API_RETURN_KEY_CODE) ?: 0
+ }
+
+ @Nullable
+ fun getCurrentSong(api: IQQMusicApi?, gson: Gson): Data.Song? {
+ val ret = api?.execute("getCurrentSong", null)
+ val curSongJson = ret?.getString(Keys.API_RETURN_KEY_DATA)
+ return gson.fromJson(curSongJson, Data.Song::class.java)
+ }
+
+ fun addToFavourite(api: IQQMusicApi?, midList: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("midList", midList)
+ api?.executeAsync("addToFavourite", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun removeFromFavourite(api: IQQMusicApi?, midList: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("midList", midList)
+ api?.executeAsync("removeFromFavourite", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun addLocalPathToFavourite(api: IQQMusicApi?, localPathList: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("localPathList", localPathList)
+ api?.executeAsync("addLocalPathToFavourite", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun removeLocalPathFromFavourite(api: IQQMusicApi?, localPathList: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("localPathList", localPathList)
+ api?.executeAsync("removeLocalPathFromFavourite", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun isFavouriteMid(api: IQQMusicApi?, midList: ArrayList, block: ((ret: BooleanArray?) -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("midList", midList)
+ api?.executeAsync("isFavouriteMid", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ val ret = result.getBooleanArray(Keys.API_RETURN_KEY_DATA)
+ block(ret)
+ }
+ })
+ }
+
+ fun isFavouriteLocalPath(api: IQQMusicApi?, localPathList: ArrayList, block: ((ret: BooleanArray?) -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("localPathList", localPathList)
+ api?.executeAsync("isFavouriteLocalPath", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ val ret = result.getBooleanArray(Keys.API_RETURN_KEY_DATA)
+ block(ret)
+ }
+ })
+ }
+
+ fun registerEventListener(api: IQQMusicApi?, events: ArrayList, block: (() -> Unit)) {
+ api?.registerEventListener(events, object : IQQMusicApiEventListener.Stub() {
+ override fun onEvent(event: String, extra: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun unregisterEventListener(api: IQQMusicApi?, events: ArrayList, block: (() -> Unit)) {
+ api?.unregisterEventListener(events, object : IQQMusicApiEventListener.Stub() {
+ override fun onEvent(event: String, extra: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun playFromChorus(api: IQQMusicApi?, fromChorus: Boolean) {
+ val params = Bundle()
+ params.putBoolean("fromChorus", fromChorus)
+ api?.execute("playFromChorus", params)
+ }
+
+ @Deprecated("Use playSongMid instead.")
+ fun playSongId(api: IQQMusicApi?, ids: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("songIdList", ids)
+ api?.executeAsync("playSongId", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ @Deprecated("Use playSongMidAtIndex instead.")
+ fun playSongIdAtIndex(api: IQQMusicApi?, ids: ArrayList, block: (() -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("songIdList", ids)
+ api?.executeAsync("playSongIdAtIndex", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ block()
+ }
+ })
+ }
+
+ fun getTotalTime(api: IQQMusicApi?): Long {
+ val result = api?.execute("getTotalTime", null)
+ return (result?.getLong(Keys.API_RETURN_KEY_DATA) ?: 0) / 1000
+ }
+
+ fun getCurrTime(api: IQQMusicApi?): Long {
+ val result = api?.execute("getCurrTime", null)
+ return (result?.getLong(Keys.API_RETURN_KEY_DATA) ?: 0) / 1000
+ }
+
+ fun getPlayList(api: IQQMusicApi?, ids: ArrayList, block: ((json: String?) -> Unit)) {
+ val params = Bundle()
+ params.putStringArrayList("songIdList", ids)
+ api?.executeAsync("getPlayList", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(result: Bundle) {
+ val json = result.getString(Keys.API_RETURN_KEY_DATA)
+ block(json)
+ }
+ })
+ }
+
+ fun getFolderList(api: IQQMusicApi?, folderId: String, folderType: Int, page: Int, block: (json: String?) -> Unit) {
+ val params = Bundle().apply {
+ putString("folderId", folderId)
+ putInt("folderType", folderType)
+ putInt("page", page)
+ }
+
+ api?.executeAsync("getFolderList", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(p0: Bundle?) {
+ val json = p0?.getString(Keys.API_RETURN_KEY_DATA)
+ block(json)
+ }
+ })
+ }
+
+ fun getSongList(api: IQQMusicApi?, folderId: String, folderType: Int, page: Int, block: (json: String?) -> Unit) {
+ val params = Bundle().apply {
+ putString("folderId", folderId)
+ putInt("folderType", folderType)
+ putInt("page", page)
+ }
+
+ api?.executeAsync("getSongList", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(p0: Bundle?) {
+ val json = p0?.getString(Keys.API_RETURN_KEY_DATA)
+ block(json)
+ }
+ })
+ }
+
+ fun getUserFolderList(api: IQQMusicApi?, openId: String, openToken: String, folderId: String, folderType: Int, page: Int, block: (json: String?) -> Unit) {
+ val params = Bundle().apply {
+ putString("openId", openId)
+ putString("openToken", openToken)
+ putString("folderId", folderId)
+ putInt("folderType", folderType)
+ putInt("page", page)
+ }
+
+ api?.executeAsync("getUserFolderList", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(p0: Bundle?) {
+ val json = p0?.getString(Keys.API_RETURN_KEY_DATA)
+ block(json)
+ }
+ })
+ }
+
+ fun getUserSongList(api: IQQMusicApi?, openId: String, openToken: String, folderId: String, folderType: Int, page: Int, block: (json: String?) -> Unit) {
+ val params = Bundle().apply {
+ putString("openId", openId)
+ putString("openToken", openToken)
+ putString("folderId", folderId)
+ putInt("folderType", folderType)
+ putInt("page", page)
+ }
+
+ api?.executeAsync("getUserSongList", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(p0: Bundle?) {
+ val json = p0?.getString(Keys.API_RETURN_KEY_DATA)
+ block(json)
+ }
+ })
+ }
+
+ fun search(api: IQQMusicApi?, block: () -> Unit) {
+ val params = Bundle().apply {
+ putString("keyword", "周杰伦")
+ putInt("searchType", 0)
+ putBoolean("firstPage", true)
+ }
+
+ api?.executeAsync("search", params, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(p0: Bundle?) {
+ block()
+ }
+ })
+ }
+
+ fun voiceShortcut(api: IQQMusicApi?, intent: String, block: (code: Int?) -> Unit) {
+ val bundle = Bundle()
+ bundle.apply {
+ putString("intent", intent)
+ }
+ api?.executeAsync("voiceShortcut", bundle, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(p0: Bundle?) {
+ val code = p0?.getInt(Keys.API_RETURN_KEY_CODE) ?: ERROR_API_NOT_INITIALIZED
+ block(code)
+ }
+ })
+ }
+
+ fun voicePlay(api: IQQMusicApi?, query: String, slotList: ArrayList, block: (code: Int, json: String?) -> Unit) {
+ val bundle = Bundle()
+ bundle.apply {
+ putString("query", query)
+ putStringArrayList("slotList", slotList)
+ }
+ api?.executeAsync("voicePlay", bundle, object : IQQMusicApiCallback.Stub() {
+ override fun onReturn(p0: Bundle?) {
+ val code = p0?.getInt(Keys.API_RETURN_KEY_CODE) ?: ERROR_API_NOT_INITIALIZED
+ val json = p0?.getString(Keys.API_RETURN_KEY_DATA)
+ block(code, json)
+ }
+ })
+ }
+
+}
\ No newline at end of file
diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/Config.java b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/Config.java
new file mode 100644
index 000000000..b27ae44ea
--- /dev/null
+++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/Config.java
@@ -0,0 +1,53 @@
+package com.yunqiinnovation.qq_music;
+
+import com.tencent.qqmusic.third.api.contract.CommonCmd;
+
+public class Config {
+
+ /**
+ * 需向QQ音乐发邮件申请,具体参考README
+ */
+ public static final String OPENID_APPID = "2000000921";
+
+ /**
+ * 配置接入方自己生成的RSA私钥,由于是Demo,所以直接写在代码中了,真实环境中需要注意私钥泄漏的风险。
+ */
+ public static final String OPENID_APP_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" +
+ "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALWuTayxg0296yfm\n" +
+ "h8WhunybY1BEFt9fB4qb3TGvB0vkd+oKDoBdiTevtgwSL47h4O0ISNTQqqmVOvk1\n" +
+ "EQMGcLESBEdHquPX2uOplwmmVWx1un9NYB4JKaHmUkCXOIoiCgmEY052zFY2T+a0\n" +
+ "RfFiaUTc0wRMynquE8Qp18i23IUBAgMBAAECgYBf/s1D5jd+qE7MMl6i6iVjGaeM\n" +
+ "Da4YMFoHvvtM+TJdw0Yo/09SPBn4tnr2PdWrJWZ3V91DtU23dQZIDuYCNzDrOkVr\n" +
+ "93W5Pkqprdbde6qRhWm1VTZVN7h85DStGBzovSxQ6DQNHZFzIriJkfGDjPopmIil\n" +
+ "awQvMPlS3lLYkzHwAQJBAOWBlshEMHXeqXekjJExwX7YSy2oabWXtllCFbcPprAY\n" +
+ "MiM8cdNsEhnhrsmdBN4crqTQKJq/ATaYFhzASuIiZ2ECQQDKp17+A3u6q7RZ3Vmb\n" +
+ "khvAATQpVK5loCOzuyPU7/UGU49AO1DIaPwDvaUqL6PuF9eFrcRHA5AFbBjOgYNn\n" +
+ "piGhAkEAr5gMo0D4ytYHVxDAs74by071hgNajaN70qLrNlp1MDny8l6mwBnpZ3/y\n" +
+ "WTMXrFDJljPlPTByXzDRikHdiVLFQQJATMOTpxK4tqA+A+BmBhkVa+jI0nMiUJqV\n" +
+ "i4py60EuBoR2efU+B3xFIkGfI9jB9vmPCTMma2P9FSC6UNccaJG0oQJBAJYb6Ch9\n" +
+ "yowGPpY1CShaR+TfauUreJBd4PSpu8mFlYUssp2AXYcJM51RMzrZKXvTTOvwkStc\n" +
+ "NRCBcPVAYcVUnXo=\n" +
+ "-----END PRIVATE KEY-----";
+
+ /**
+ * 获取纯Base64格式的私钥(去除PEM头尾标记)
+ * @return 纯Base64格式的私钥字符串
+ */
+ public static String getBase64PrivateKey() {
+ return OPENID_APP_PRIVATE_KEY
+ .replace("-----BEGIN PRIVATE KEY-----", "")
+ .replace("-----END PRIVATE KEY-----", "")
+ .replaceAll("\\s+", ""); // 移除所有空白字符(包括换行符)
+ }
+
+ /**
+ * 配置平台类型,可选值如下:
+ * {@link CommonCmd#AIDL_PLATFORM_TYPE_PHONE }
+ * {@link CommonCmd#AIDL_PLATFORM_TYPE_TV}
+ * {@link CommonCmd#AIDL_PLATFORM_TYPE_CAR}
+ * {@link CommonCmd#AIDL_PLATFORM_TYPE_PAD}
+ */
+ // 修改这一行:使用CommonCmd中的常量而不是字符串
+ public static String BIND_PLATFORM = CommonCmd.AIDL_PLATFORM_TYPE_PHONE;
+
+}
\ No newline at end of file
diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt
new file mode 100644
index 000000000..6cee7e034
--- /dev/null
+++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt
@@ -0,0 +1,240 @@
+package com.yunqiinnovation.qq_music
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import com.yunqiinnovation.qq_music.openid.OpenIDHelper
+import com.yunqiinnovation.qq_music.util.QPlayBindHelper
+import com.yunqiinnovation.qq_music.util.QQMusicApiWrapper
+import com.tencent.qqmusic.third.api.contract.*
+import org.json.JSONObject
+import org.json.JSONTokener
+
+/**
+ * QQ音乐登录管理器
+ * 负责处理QQ音乐的登录授权流程
+ */
+class QQMusicLoginManager(private val activity: Activity) {
+
+ companion object {
+ private const val TAG = "QQMusicLoginManager"
+ private const val BASE_SCHEME = "voitrans://"
+ private const val URI_LOGIN = BASE_SCHEME + "login"
+
+ // 定义错误码常量,替代 LoginExampleActivity 中的常量
+ const val LOGIN_FAILED = 1
+ }
+
+ private lateinit var qPlayBindHelper: QPlayBindHelper
+ private var qqMusicApi: IQQMusicApi? = null
+ private lateinit var qqMusicApiWrapper: QQMusicApiWrapper
+ private val handler = Handler(Looper.getMainLooper())
+
+ /**
+ * 登录结果回调接口
+ */
+ interface LoginCallback {
+ fun onSuccess(openId: String, openToken: String, expireTime: String)
+ fun onError(errorCode: String, errorMessage: String)
+ }
+
+ /**
+ * 开始登录流程
+ */
+ fun startLogin(callback: LoginCallback) {
+ Log.d(TAG, "开始QQ音乐登录流程")
+
+ // 检查配置
+ if (Config.OPENID_APPID.isEmpty() || Config.OPENID_APP_PRIVATE_KEY.isEmpty()) {
+ callback.onError("CONFIG_ERROR", "请先配置APPID和私钥")
+ return
+ }
+
+ // 初始化绑定助手
+ qPlayBindHelper = QPlayBindHelper(activity)
+
+ // 开始绑定QQ音乐服务
+ bindQQMusicService(callback)
+ }
+
+ /**
+ * 绑定QQ音乐服务
+ */
+ private fun bindQQMusicService(callback: LoginCallback) {
+ Log.d(TAG, "开始绑定QQ音乐服务")
+
+ qPlayBindHelper.ensureQQMusicBindByStartProcess { success ->
+ if (success && qPlayBindHelper.isBindQQMusic()) {
+ Log.d(TAG, "QQ音乐服务绑定成功")
+ qqMusicApi = qPlayBindHelper.getQQMusicApi()!!
+ qqMusicApiWrapper = QQMusicApiWrapper(qqMusicApi!!)
+
+ // 检查是否已经授权
+ checkAuthStatus(callback)
+
+ } else {
+ Log.e(TAG, "QQ音乐服务绑定失败")
+ callback.onError("APP_NOT_INSTALLED", "请先安装QQ音乐App才能使用音乐功能")
+ }
+ }
+ }
+
+ /**
+ * 检查授权状态
+ */
+ private fun checkAuthStatus(callback: LoginCallback) {
+ Log.d(TAG, "检查授权状态")
+
+ val bundle = Bundle().apply {
+ putInt(Keys.API_PARAM_KEY_SDK_VERSION, CommonCmd.SDK_VERSION)
+ putString(Keys.API_PARAM_KEY_PLATFORM_TYPE, Config.BIND_PLATFORM)
+ }
+
+ try {
+ val result = qqMusicApi?.execute("hi", bundle)
+ val code = result?.getInt(Keys.API_RETURN_KEY_CODE)
+
+ when (code) {
+ ErrorCodes.ERROR_OK -> {
+ // 已授权,直接请求登录信息
+ Log.d(TAG, "已授权,直接获取登录信息")
+ callback.onSuccess("","","")
+// requestAuth(callback)
+ }
+ ErrorCodes.ERROR_API_NO_PERMISSION -> {
+ // 需要授权
+ Log.d(TAG, "需要授权,启动授权流程")
+ startAuthProcess(callback)
+ }
+ else -> {
+ Log.e(TAG, "检查授权状态失败: $code")
+ callback.onError("AUTH_CHECK_FAILED", "检查授权状态失败")
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "检查授权状态异常", e)
+ callback.onError("AUTH_CHECK_ERROR", "检查授权状态异常: ${e.message}")
+ }
+ }
+
+ /**
+ * 启动授权流程
+ */
+ private fun startAuthProcess(callback: LoginCallback) {
+ Log.d(TAG, "启动QQ音乐登录页面")
+
+ // 启动QQ音乐登录
+ CommonCmd.loginQQMusic(activity, URI_LOGIN)
+
+ // 等待用户登录完成后进行身份验证
+ handler.postDelayed({
+ verifyIdentity(callback)
+ }, 3000) // 给用户足够的登录时间
+ }
+
+ /**
+ * 验证身份
+ */
+ private fun verifyIdentity(callback: LoginCallback) {
+ Log.d(TAG, "开始身份验证")
+
+ val time = System.currentTimeMillis()
+ val nonce = time.toString()
+ val encryptString = OpenIDHelper.getEncryptString(nonce)
+
+ CommonCmd.verifyCallerIdentity(
+ activity,
+ Config.OPENID_APPID,
+ activity.packageName,
+ encryptString,
+ BASE_SCHEME
+ )
+
+ // 延迟后请求授权
+ handler.postDelayed({
+ requestAuth(callback)
+ }, 2000)
+ }
+
+ /**
+ * 请求授权并获取登录信息
+ */
+ private fun requestAuth(callback: LoginCallback) {
+ Log.d(TAG, "开始请求授权")
+
+ qqMusicApiWrapper.requestAuthNew(
+ success = { encrypt ->
+ Log.d(TAG, "授权成功,开始解密登录信息")
+ parseLoginResult(encrypt, callback)
+ },
+ failed = { errorCode, errorMsg ->
+ Log.e(TAG, "授权失败: code=$errorCode, msg=$errorMsg")
+
+ val errorMessage = when (errorCode) {
+ LOGIN_FAILED -> "登录失败: $errorMsg"
+ else -> "授权失败: $errorMsg"
+ }
+
+ callback.onError("AUTH_FAILED", errorMessage)
+ }
+ )
+ }
+
+ /**
+ * 解析登录结果
+ */
+ private fun parseLoginResult(encrypt: String, callback: LoginCallback) {
+ try {
+ val qmDecryptString = OpenIDHelper.decryptQQMEncryptString(encrypt)
+ if (qmDecryptString != null) {
+ val appParser = JSONTokener(qmDecryptString)
+ val appDecryptJson = appParser.nextValue() as JSONObject
+ val sign = appDecryptJson.getString(Keys.API_RETURN_KEY_SIGN)
+ val returnNonce = appDecryptJson.getString(Keys.API_RETURN_KEY_NONCE)
+
+ // 检查签名
+ if (OpenIDHelper.checkQMSign(sign, returnNonce)) {
+ val openId = appDecryptJson.getString(Keys.API_RETURN_KEY_OPEN_ID)
+ val openToken = appDecryptJson.getString(Keys.API_RETURN_KEY_OPEN_TOKEN)
+ val expireTime = appDecryptJson.getString(Keys.API_PARAM_KEY_SDK_EXPIRETIME)
+
+ Log.d(TAG, "登录成功: openId=$openId")
+
+ handler.post {
+ callback.onSuccess(openId, openToken, expireTime)
+ }
+ } else {
+ Log.e(TAG, "签名验证失败")
+ handler.post {
+ callback.onError("SIGN_ERROR", "签名验证失败")
+ }
+ }
+ } else {
+ Log.e(TAG, "解密失败")
+ handler.post {
+ callback.onError("DECRYPT_ERROR", "解密失败")
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "解析登录结果失败", e)
+ handler.post {
+ callback.onError("PARSE_ERROR", "解析登录结果失败: ${e.message}")
+ }
+ }
+ }
+
+ /**
+ * 清理资源
+ */
+ fun cleanup() {
+ try {
+ if (::qPlayBindHelper.isInitialized) {
+ qPlayBindHelper.unBindQQMusic()
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "清理资源失败", e)
+ }
+ }
+}
\ No newline at end of file
diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt
new file mode 100644
index 000000000..1e4f8a5c1
--- /dev/null
+++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt
@@ -0,0 +1,645 @@
+package com.yunqiinnovation.qq_music
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.IBinder
+import android.util.Log
+import com.google.gson.Gson
+import com.google.gson.JsonParser
+import com.tencent.qqmusic.third.api.contract.*
+import android.os.Handler
+import android.os.Looper
+
+class QQMusicManager(private val activity: Activity) {
+ companion object {
+ const val TAG = "QQMusicManager"
+ }
+
+ private var qqmusicApi: IQQMusicApi? = null
+ private var isBindQQMusicService = false
+ private val gson = Gson()
+
+ // 回调接口
+ interface SearchCallback {
+ fun onSuccess(songs: List