Browse Source

上传音乐代码

weicu
liwei1dao 2 years ago
parent
commit
343fc87606
  1. 8
      .mcp.json
  2. 17
      lib/data/services/open_ai_service_adapter.dart
  3. 176
      lib/data/services/spotify_service.dart
  4. 26
      lib/modules/chat/controllers/chat_controller.dart
  5. 37
      lib/modules/chat/views/chat_music_card.dart
  6. 6
      lib/modules/chat/views/chat_view.dart
  7. 4
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt
  8. 38
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt
  9. 7
      local_plugins/open_ai_service/lib/open_ai_service.dart

8
.mcp.json

@ -1,11 +1,7 @@
{
"mcpServers": {
"amap-amap-sse": {
"url": "https://mcp.amap.com/sse?key=e5fdc9605eabdeb5626f18f5721f343d"
},
"web-search": {
"url": "http://mcp.ideapsound.com:8000/sse"
"deepsound-mcp": {
"url": "https://api.deapsound.com/sse"
}
}

17
lib/data/services/open_ai_service_adapter.dart

@ -9,14 +9,15 @@ import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
/// 定义流事件类型,用于区分不同类型的事件
enum StreamEventType { token, complete, error }
enum StreamEventType { token, functionCall, complete, error }
/// 流事件包装类
class StreamEvent {
final StreamEventType type;
final String? content;
final Map? meta;
StreamEvent(this.type, {this.content});
StreamEvent(this.type, {this.content, this.meta});
}
/// OpenAI服务适配器 - 连接AiService接口与OpenAIService插件
@ -63,6 +64,9 @@ class OpenAIServiceAdapter implements AiService {
printInfo(
info:
'接收到 functionCall content: ${event.content} meta: ${event.meta}');
_tokenStreamController.add(StreamEvent(
StreamEventType.functionCall,
meta: event.meta as Map<String, dynamic>?));
// _tokenStreamController.add(StreamEvent(
// StreamEventType.error,
// content: '收到函数调用,该流仅支持文本响应'
@ -170,7 +174,7 @@ class OpenAIServiceAdapter implements AiService {
/// 发送消息并获取流式回复
@override
Stream<String> sendMessageStream({
Stream sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async* {
@ -180,7 +184,7 @@ class OpenAIServiceAdapter implements AiService {
messages.map((m) => Map<String, dynamic>.from(m)).toList();
// 创建用于接收token的控制器
final localController = StreamController<String>();
final localController = StreamController();
// 添加从广播流到本地流的订阅
final subscription = _tokenStreamController.stream.listen((streamEvent) {
@ -190,6 +194,11 @@ class OpenAIServiceAdapter implements AiService {
localController.add(streamEvent.content!);
}
break;
case StreamEventType.functionCall:
if (streamEvent.meta != null && !localController.isClosed) {
localController.add(streamEvent.meta!);
}
break;
case StreamEventType.complete:
if (!localController.isClosed) {
localController.close();

176
lib/data/services/spotify_service.dart

@ -0,0 +1,176 @@
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:spotify_sdk/spotify_sdk.dart';
import 'package:get/get.dart';
import 'ai_service.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
/// OpenAI服务适配器 - 连接AiService接口与OpenAIService插件
class SpotifService {
bool isInitialized = false;
/// 初始化OpenAI服务
Future<bool> initialize() async {
if (!isInitialized) {
getAccessToken();
connectToSpotifyRemote();
isInitialized = true;
printInfo(info: "SpotifService----------- initialize");
}
return true;
}
/// 获取Spotify SDK的版本信息
Future<void> connectToSpotifyRemote() async {
try {
var result = await SpotifySdk.connectToSpotifyRemote(
clientId: dotenv.env['SPOTIFY_CLIENT_ID'].toString(),
redirectUrl: dotenv.env['SPOTIFY_REDIRECT_URL'].toString(),
scope:
'user-read-private user-read-email streaming app-remote-control user-modify-playback-state',
);
printInfo(
info: "SpotifService----------- connectToSpotifyRemote $result");
} on PlatformException catch (e) {
printError(info: "SpotifService------------ PlatformException$e");
} on MissingPluginException {
printError(info: "SpotifService------------ MissingPluginException");
}
}
/// 获取Spotify的访问令牌
Future<String> getAccessToken() async {
try {
var authenticationToken = await SpotifySdk.getAccessToken(
clientId: dotenv.env['SPOTIFY_CLIENT_ID'].toString(),
redirectUrl: dotenv.env['SPOTIFY_REDIRECT_URL'].toString(),
scope: 'app-remote-control, '
'user-modify-playback-state, '
'playlist-read-private, '
'playlist-modify-public,user-read-currently-playing',
);
printInfo(
info: "SpotifService----------- getAccessToken $authenticationToken");
return authenticationToken;
} on PlatformException catch (e) {
printError(
info:
"SpotifService------------getAccessToken ${e.code}: ${e.message}");
return Future.error('$e.code: $e.message');
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
return Future.error('not implemented');
}
}
//播放 音乐
Future<void> play(String url) async {
try {
await SpotifySdk.play(spotifyUri: url);
printInfo(info: "SpotifService----------- play $url");
} on PlatformException catch (e) {
printError(info: "SpotifService------------play ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
//暂停 音乐
Future<void> pause() async {
try {
await SpotifySdk.pause();
printInfo(info: "SpotifService----------- pause");
} on PlatformException catch (e) {
printError(
info: "SpotifService------------pause ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
//停止 音乐
Future<void> resume() async {
try {
await SpotifySdk.resume();
printInfo(info: "SpotifService----------- resume");
} on PlatformException catch (e) {
printError(
info: "SpotifService------------pause ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
//下一首 音乐
Future<void> skipNext() async {
try {
await SpotifySdk.skipNext();
} on PlatformException catch (e) {
printError(
info: "SpotifService------------pause ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
//跳过上一首
Future<void> skipPrevious() async {
try {
await SpotifySdk.skipPrevious();
} on PlatformException catch (e) {
printError(
info: "SpotifService------------pause ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
//获取当前播放状态
Future<void> seekTo() async {
try {
await SpotifySdk.seekTo(positionedMilliseconds: 20000);
} on PlatformException catch (e) {
printError(
info: "SpotifService------------pause ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
//获取当前播放状态
Future<void> seekToRelative() async {
try {
await SpotifySdk.seekToRelativePosition(relativeMilliseconds: 20000);
} on PlatformException catch (e) {
printError(
info: "SpotifService------------pause ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
//获取当前播放状态
Future<void> switchToLocalDevice() async {
try {
await SpotifySdk.switchToLocalDevice();
} on PlatformException catch (e) {
printError(
info: "SpotifService------------pause ${e.code}: ${e.message}");
} on MissingPluginException {
printError(
info: "SpotifService------------getAccessToken not implemented");
}
}
}

26
lib/modules/chat/controllers/chat_controller.dart

@ -1,9 +1,11 @@
import 'dart:async';
import 'package:deep_voice/data/services/spotify_service.dart';
import 'package:get/get.dart';
import 'package:flutter/widgets.dart';
import 'package:get_storage/get_storage.dart';
import 'package:just_audio/just_audio.dart';
import 'package:chat_storage/chat_storage.dart';
import 'package:path/path.dart';
import '../../../data/services/ai_service.dart';
import '../../../data/services/deapsound_ai_service.dart';
import '../../../data/services/kouzi_ai_service.dart';
@ -25,6 +27,7 @@ class ChatController extends GetxController {
final AsrService _asrService = Get.find<AsrService>();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
late SpotifService _spotifService;
// 添加ScrollController用于控制列表滚动
final ScrollController scrollController = ScrollController();
@ -117,7 +120,8 @@ class ChatController extends GetxController {
_aiService = Get.find<OpenAIServiceAdapter>();
Logger.info('使用OpenAIServiceAdapter');
}
Get.put(SpotifService());
_spotifService = Get.find<SpotifService>();
// 使用克隆音色语音合成
final GetStorage storage = GetStorage();
String timbre = storage.read('timbre') ?? '1';
@ -370,11 +374,11 @@ class ChatController extends GetxController {
_aiResponseSubscription = responseStream.listen(
(chunk) {
if (_shouldCancelAiResponse) return;
Logger.info('chat ${chunk}');
if (chunk is Map) {
_updateIntermediateMessage(data: chunk);
if (chunk['musicaudio'] != null) {
play(chunk['musicaudio']);
if (chunk['card_spotify'] != null) {
play(chunk['card_spotify']);
}
} else {
fullResponse += chunk;
@ -453,18 +457,10 @@ class ChatController extends GetxController {
}
// 播放音乐
void play(Map musicaudio) async {
void play(Map card) async {
try {
isPlay.value = false;
if (audiourl.value != musicaudio['audiourl']) {
audiourl.value = '';
await player.setUrl(musicaudio['audiourl']);
audiourl.value = musicaudio['audiourl'];
player.play();
} else {
player.play();
}
isPlay.value = true;
await _spotifService.initialize();
_spotifService.play(card['url'] as String);
} catch (e) {
Logger.error('加载异常: $e');
}

37
lib/modules/chat/views/chat_music_card.dart

@ -12,8 +12,8 @@ class ChatMusicCard extends GetView<ChatController> {
@override
Widget build(BuildContext context) {
final Map musiccards = data?['musiccards'].first;
final Map musicaudio = data?['musicaudio'];
// final Map musiccards = data?['musiccards'].first;
// final Map musicaudio = data?['musicaudio'];
return Row(
children: [
@ -25,14 +25,14 @@ class ChatMusicCard extends GetView<ChatController> {
borderRadius: BorderRadius.circular(6.r),
),
child: Image.network(
musiccards['image'],
data?['image'],
fit: BoxFit.cover,
),
),
10.horizontalSpace,
Expanded(
child: Text(
musiccards['name'],
data?['name'],
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
@ -43,8 +43,7 @@ class ChatMusicCard extends GetView<ChatController> {
),
10.horizontalSpace,
Obx(
() => controller.audiourl == musicaudio['audiourl'] &&
controller.isPlay.value
() => controller.audiourl == data?['url'] && controller.isPlay.value
? GestureDetector(
onTap: () {
controller.pause();
@ -52,30 +51,6 @@ class ChatMusicCard extends GetView<ChatController> {
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 38.w,
height: 38.w,
child: StreamBuilder<Duration>(
stream: controller.player.positionStream,
builder: (context, snapshot) {
final position = snapshot.data ?? Duration.zero;
final total =
controller.player.duration ?? Duration.zero;
final progress = total.inSeconds > 0
? position.inSeconds / total.inSeconds
: 0.0;
return CircularProgressIndicator(
value: progress,
backgroundColor: isDarkMode
? Colors.grey[700]!
: Colors.grey[300]!,
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? Colors.white : Colors.black),
strokeWidth: 2, // 线条粗细
);
},
),
),
Icon(
Icons.pause_rounded,
color: isDarkMode ? Colors.white : Colors.black,
@ -85,7 +60,7 @@ class ChatMusicCard extends GetView<ChatController> {
)
: GestureDetector(
onTap: () {
controller.play(musicaudio);
controller.play(data!);
},
child: Container(
width: 38.w,

6
lib/modules/chat/views/chat_view.dart

@ -356,11 +356,11 @@ class ChatView extends GetView<ChatController> {
);
}
if (message.data != null) {
if (message.data?['weatherDays'] != null) {
if (message.data?['card_weather'] != null) {
box = WeatherDayCard(message.data, isDarkMode);
}
if (message.data?['musicaudio'] != null) {
box = ChatMusicCard(message.data, isDarkMode);
if (message.data?['card_spotify'] != null) {
box = ChatMusicCard(message.data?['card_spotify'], isDarkMode);
}
if (message.data?['navigationcard'] != null) {
box = ChatNavigationCard(message.data, isDarkMode);

4
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt

@ -391,10 +391,10 @@ class MCPSubClient(
else -> ""
}
}
JSONObject().apply {put("meta",result?._meta?.toMap()); put("context",context) }
JSONObject().apply { put("name",name);put("meta",result?._meta?.toMap()); put("context",context) }
} catch (e: Exception) {
Log.e(TAG, "调用MCP工具失败: ${e.message}", e)
JSONObject().apply { put("context", "调用MCP工具失败: ${e.message}") }
JSONObject().apply { put("context", "调用MCP工具失败: ${e.message}")}
}
}

38
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt

@ -22,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.min
/** OpenAIServicePlugin */
class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
@ -164,24 +165,35 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
}
override fun onFunctionCall(functionCall: JSONObject) {
Log.e("OpenAIServicePlugin", "工具调用结果: ${functionCall.toString()}")
Log.e("OpenAIServicePlugin", "context:${functionCall.toString()}")
val context = functionCall["context"]
// val meta = functionCall["meta"]
val meta: Map<String, Any>? = functionCall.optJSONObject("meta")?.let { metaJson ->
metaJson.keys().asSequence().associateWith { key ->
metaJson.get(key) // 或 opt(key),更安全
if (functionCall.has("meta")) {
val meta = mapOf<String, Any>(
"card_spotify" to mapOf<String, Any>(
"name" to "泡沫",
"image" to "https://i.scdn.co/image/ab67616d0000b2739f6a38e4bb9c2f824c215e58",
"url" to "spotify:track:2IfSPLPDW6RGosdB5huwAA",
"sgener" to "Xposed",
)
)
Log.e("OpenAIServicePlugin", "工具调用结果 context:${context} meta:${meta.toString()}")
// 发送函数调用事件
mainHandler.post {
eventSink?.success(mapOf("type" to "functionCall", "content" to context,"meta" to meta))
}
}
val card_spotifyKey = meta?.containsKey("card_spotify") ?: false
if (card_spotifyKey) { //是否是音乐卡片
}
// val meta: Map<String, Any>? = functionCall.optJSONObject("meta")?.let { metaJson ->
// metaJson.keys().asSequence().associateWith { key ->
// metaJson.get(key) // 或 opt(key),更安全
// }
// } ?: mapOf(
// // 如果 null,就用默认值
// "card_spotify" to {
// "url" to "spotify:track:58kNJana4w5BIjlZE2wq5m"
// },
// )
// 发送函数调用事件
mainHandler.post {
// val functionCallMap = functionCall.toMap()
eventSink?.success(mapOf("type" to "functionCall", "content" to context,"meta" to meta))
}
}
}
)

7
local_plugins/open_ai_service/lib/open_ai_service.dart

@ -25,17 +25,19 @@ enum OpenAIEventType {
class OpenAIEvent {
final OpenAIEventType type;
final dynamic content;
final dynamic meta;
final Map<String, dynamic>? meta;
OpenAIEvent({required this.type, this.content, this.meta});
factory OpenAIEvent.fromMap(Map<String, dynamic> map) {
final typeStr = map['type'] as String;
final content = map['content'];
final rawMeta = map['meta'];
final meta = rawMeta is Map ? Map<String, dynamic>.from(rawMeta) : null;
return OpenAIEvent(
type: _typeFromString(typeStr),
content: content,
meta: meta,
);
}
@ -77,6 +79,7 @@ class OpenAIService {
// 监听原生事件
_eventChannel.receiveBroadcastStream().listen(
(dynamic event) {
print("收到事件 OpenAIEvent: $event");
if (event is Map<dynamic, dynamic>) {
final eventMap = Map<String, dynamic>.from(event);
final openAIEvent = OpenAIEvent.fromMap(eventMap);

Loading…
Cancel
Save