Browse Source

Merge commit '36b0a8ea90591cc8fc8ef6b8de823a673235acff' into lxm

newdev_shunjiawei
lxm 1 year ago
parent
commit
bb4e53d7b5
  1. 136
      lib/modules/pairing/controllers/pairing_controller.dart
  2. 1
      local_plugins/agent_service/android/build.gradle.kts
  3. 70
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  4. 1
      local_plugins/music_service/android/build.gradle
  5. 9
      local_plugins/music_service/android/src/main/AndroidManifest.xml
  6. 87
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt
  7. 11
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt

136
lib/modules/pairing/controllers/pairing_controller.dart

@ -10,13 +10,16 @@ import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice;
import '../../../data/services/ble_manager.dart'; import '../../../data/services/ble_manager.dart';
import 'package:agent_service/agent_service.dart'; import 'package:agent_service/agent_service.dart';
import 'package:ble_service/ble_service.dart'; import 'package:ble_service/ble_service.dart';
/// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程 /// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程
class PairingController extends GetxController { class PairingController extends GetxController {
static PairingController get to => Get.find(); static PairingController get to => Get.find();
//是否需要连接耳机
final isneedearphone = false;
// 状态标志 // 状态标志
final isLoading = false.obs; final isLoading = false.obs;
// 权限是否已授予 // 权限是否已授予
final _hasPermissions = false.obs; final _hasPermissions = false.obs;
bool get hasPermissions => _hasPermissions.value; bool get hasPermissions => _hasPermissions.value;
@ -25,22 +28,22 @@ class PairingController extends GetxController {
final _bluetoothState = 'unknown'.obs; final _bluetoothState = 'unknown'.obs;
String get bluetoothState => _bluetoothState.value; String get bluetoothState => _bluetoothState.value;
bool get isBluetoothOn => _bluetoothState.value == 'on'; bool get isBluetoothOn => _bluetoothState.value == 'on';
// 状态描述(用于UI显示) // 状态描述(用于UI显示)
final statusDescription = 'initializing'.tr.obs; final statusDescription = 'initializing'.tr.obs;
// 是否正在跳转到主页 // 是否正在跳转到主页
bool _isNavigatingToHome = false; bool _isNavigatingToHome = false;
// 是否正在执行Companion关联流程 // 是否正在执行Companion关联流程
bool _isAssociating = false; bool _isAssociating = false;
// 当前连接设备信息 // 当前连接设备信息
BluetoothDevice? get currentDevice => _bluetoothService.currentDevice; BluetoothDevice? get currentDevice => _bluetoothService.currentDevice;
// 是否已连接设备 // 是否已连接设备
bool get isDeviceConnected => currentDevice != null; bool get isDeviceConnected => currentDevice != null;
// 是否已完成Companion关联 // 是否已完成Companion关联
final _companionAssociated = false.obs; final _companionAssociated = false.obs;
bool get isCompanionAssociated => _companionAssociated.value; bool get isCompanionAssociated => _companionAssociated.value;
@ -48,10 +51,10 @@ class PairingController extends GetxController {
// 服务状态监听 // 服务状态监听
Worker? _bluetoothStateWorker; Worker? _bluetoothStateWorker;
Worker? _deviceWorker; Worker? _deviceWorker;
// 蓝牙服务 // 蓝牙服务
final _bluetoothService = Get.find<BluetoothManager>(); final _bluetoothService = Get.find<BluetoothManager>();
// BLE服务 // BLE服务
final _bleService = BleService(); final _bleService = BleService();
@ -96,7 +99,7 @@ class PairingController extends GetxController {
// 刷新已连接设备 // 刷新已连接设备
statusDescription.value = 'searchingDevice'.tr; statusDescription.value = 'searchingDevice'.tr;
await refreshConnectedDevices(); await refreshConnectedDevices();
// 检查是否已存在Companion设备关联,但不要重复调用关联流程 // 检查是否已存在Companion设备关联,但不要重复调用关联流程
await _checkExistingCompanionAssociation(); await _checkExistingCompanionAssociation();
} catch (e) { } catch (e) {
@ -107,16 +110,16 @@ class PairingController extends GetxController {
_updateStatusDescription(); _updateStatusDescription();
} }
} }
/// 检查是否已存在Companion设备关联 /// 检查是否已存在Companion设备关联
Future<void> _checkExistingCompanionAssociation() async { Future<void> _checkExistingCompanionAssociation() async {
// 只在Android平台执行 // 只在Android平台执行
if (!Platform.isAndroid) return; if (!Platform.isAndroid) return;
try { try {
final isSupported = await _bleService.isCompanionSupported(); final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) return; if (!isSupported) return;
final pairedMac = await _bleService.getPairedMacAddress(); final pairedMac = await _bleService.getPairedMacAddress();
if (pairedMac != null) { if (pairedMac != null) {
Logger.info('发现已关联的Companion设备: $pairedMac'); Logger.info('发现已关联的Companion设备: $pairedMac');
@ -156,18 +159,18 @@ class PairingController extends GetxController {
Logger.warning('权限 ${permission.toString()} 未被授予: $status'); Logger.warning('权限 ${permission.toString()} 未被授予: $status');
} }
}); });
_hasPermissions.value = allGranted; _hasPermissions.value = allGranted;
return allGranted; return allGranted;
} }
// 辅助方法获取Android SDK版本 // 辅助方法获取Android SDK版本
Future<int> _getAndroidSdkVersion() async { Future<int> _getAndroidSdkVersion() async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
// 这里需要原生代码支持来获取确切的SDK版本, // 这里需要原生代码支持来获取确切的SDK版本,
// 或者使用 device_info_plus 插件 // 或者使用 device_info_plus 插件
// 暂时返回一个较高值以触发新权限请求 // 暂时返回一个较高值以触发新权限请求
return 31; // 假设运行在需要新权限的设备上 return 31; // 假设运行在需要新权限的设备上
} }
return 0; return 0;
} }
@ -175,38 +178,40 @@ class PairingController extends GetxController {
/// 设置服务状态监听 /// 设置服务状态监听
void _setupServiceStateListeners() { void _setupServiceStateListeners() {
// 监听蓝牙服务的蓝牙状态变化 // 监听蓝牙服务的蓝牙状态变化
_bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) { _bluetoothStateWorker =
ever(_bluetoothService.bluetoothState.obs, (String state) {
_bluetoothState.value = state; _bluetoothState.value = state;
_updateStatusDescription(); _updateStatusDescription();
// 如果蓝牙关闭,可能需要更新UI或提示用户 // 如果蓝牙关闭,可能需要更新UI或提示用户
if (state == 'off') { if (state == 'off') {
Logger.info('蓝牙已关闭'); Logger.info('蓝牙已关闭');
} }
}); });
// 监听当前设备变化 - 使用公开的currentDeviceRx字段 // 监听当前设备变化 - 使用公开的currentDeviceRx字段
_deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { _deviceWorker =
ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) {
if (device != null && !_isNavigatingToHome) { if (device != null && !_isNavigatingToHome) {
Logger.info('配对页面发现设备已连接: ${device.name}'); Logger.info('配对页面发现设备已连接: ${device.name}');
// 当经典蓝牙设备连接成功,尝试进行Companion关联 // 当经典蓝牙设备连接成功,尝试进行Companion关联
_handleDeviceConnected(); _handleDeviceConnected();
} }
_updateStatusDescription(); _updateStatusDescription();
}); });
if (!isneedearphone) {
//不需要耳机
_handleDeviceConnected();
}
} }
/// 处理设备连接成功事件 /// 处理设备连接成功事件
Future<void> _handleDeviceConnected() async { Future<void> _handleDeviceConnected() async {
if (!isDeviceConnected || _isNavigatingToHome) return; if ((!isDeviceConnected && isneedearphone) || _isNavigatingToHome) return;
// 如果已经完成关联,直接跳转 // 如果已经完成关联,直接跳转
if (_companionAssociated.value) { if (isneedearphone && _companionAssociated.value) {
_autoNavigateToHome(); _autoNavigateToHome();
return; return;
} }
// 启动关联流程 // 启动关联流程
_startCompanionAssociation(); _startCompanionAssociation();
} }
@ -217,7 +222,7 @@ class PairingController extends GetxController {
statusDescription.value = 'permissionDenied'.tr; statusDescription.value = 'permissionDenied'.tr;
return; return;
} }
if (bluetoothState == 'off') { if (bluetoothState == 'off') {
statusDescription.value = 'bluetoothTurnedOff'.tr; statusDescription.value = 'bluetoothTurnedOff'.tr;
return; return;
@ -225,11 +230,14 @@ class PairingController extends GetxController {
if (isDeviceConnected) { if (isDeviceConnected) {
if (Platform.isAndroid && !isCompanionAssociated && _isAssociating) { if (Platform.isAndroid && !isCompanionAssociated && _isAssociating) {
statusDescription.value = '${'associatingDevice'.tr} ${currentDevice!.name}'; statusDescription.value =
'${'associatingDevice'.tr} ${currentDevice!.name}';
} else if (isCompanionAssociated) { } else if (isCompanionAssociated) {
statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; statusDescription.value =
'${'connectedDevice'.tr} ${currentDevice!.name}';
} else { } else {
statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; statusDescription.value =
'${'connectedDevice'.tr} ${currentDevice!.name}';
} }
return; return;
} }
@ -248,18 +256,18 @@ class PairingController extends GetxController {
statusDescription.value = 'permissionDenied'.tr; statusDescription.value = 'permissionDenied'.tr;
return; return;
} }
try { try {
isLoading.value = true; isLoading.value = true;
statusDescription.value = 'searchingDevice'.tr; statusDescription.value = 'searchingDevice'.tr;
// 使用蓝牙服务刷新连接 // 使用蓝牙服务刷新连接
await _bluetoothService.refreshDeviceConnection(); await _bluetoothService.refreshDeviceConnection();
// 检查连接状态 // 检查连接状态
if (isDeviceConnected) { if (isDeviceConnected) {
Logger.info('刷新发现设备已连接: ${currentDevice!.name}'); Logger.info('刷新发现设备已连接: ${currentDevice!.name}');
// 当发现设备已连接,统一由_handleDeviceConnected处理 // 当发现设备已连接,统一由_handleDeviceConnected处理
_handleDeviceConnected(); _handleDeviceConnected();
} else { } else {
@ -273,18 +281,20 @@ class PairingController extends GetxController {
_updateStatusDescription(); _updateStatusDescription();
} }
} }
/// 启动Companion设备关联流程 /// 启动Companion设备关联流程
Future<void> _startCompanionAssociation() async { Future<void> _startCompanionAssociation() async {
// 如果不是Android平台,已经跳转到首页,已经完成关联或正在进行关联,直接返回 // 如果不是Android平台,已经跳转到首页,已经完成关联或正在进行关联,直接返回
if (!Platform.isAndroid || _isNavigatingToHome || _companionAssociated.value || _isAssociating) { if (!Platform.isAndroid ||
_isNavigatingToHome ||
_companionAssociated.value ||
_isAssociating) {
return; return;
} }
// 设置关联状态标志 // 设置关联状态标志
_isAssociating = true; _isAssociating = true;
_updateStatusDescription(); _updateStatusDescription();
try { try {
// 检查是否支持Companion Device功能 // 检查是否支持Companion Device功能
final isSupported = await _bleService.isCompanionSupported(); final isSupported = await _bleService.isCompanionSupported();
@ -294,7 +304,6 @@ class PairingController extends GetxController {
_autoNavigateToHome(); _autoNavigateToHome();
return; return;
} }
// 检查是否已有配对的MAC地址 // 检查是否已有配对的MAC地址
final pairedMac = await _bleService.getPairedMacAddress(); final pairedMac = await _bleService.getPairedMacAddress();
if (pairedMac != null) { if (pairedMac != null) {
@ -303,34 +312,32 @@ class PairingController extends GetxController {
Logger.info('已关联的Companion设备与当前连接的设备一致'); Logger.info('已关联的Companion设备与当前连接的设备一致');
_companionAssociated.value = true; _companionAssociated.value = true;
await _bleService.registerBackgroundScan(); await _bleService.registerBackgroundScan();
_autoNavigateToHome(); // _autoNavigateToHome();
return; // return;
} else { } else {
// 不一致,清除旧关联 // 不一致,清除旧关联
Logger.info('已关联的Companion设备与当前连接的设备不一致,清除旧关联'); Logger.info('已关联的Companion设备与当前连接的设备不一致,清除旧关联');
await _bleService.clearAssociations(); await _bleService.clearAssociations();
} }
} }
// 开始关联流程 // 开始关联流程
if (currentDevice != null) { if (currentDevice != null) {
Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}'); Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}');
final success = await _bleService.startDeviceAssociation( var success = await _bleService.startDeviceAssociation(
deviceNamePattern: currentDevice!.name, deviceNamePattern: currentDevice!.name,
macAddress: currentDevice!.address, macAddress: currentDevice!.address,
); );
if (success) { if (success) {
Logger.info('Companion设备关联成功'); Logger.info('Companion设备关联成功');
_companionAssociated.value = true; _companionAssociated.value = true;
// 注册后台扫描,确保应用被杀死后仍能被唤醒 // 注册后台扫描,确保应用被杀死后仍能被唤醒
await _bleService.registerBackgroundScan(macAddress: currentDevice!.address); await _bleService.registerBackgroundScan(
macAddress: currentDevice!.address);
// 启动BLE后台服务(可选) // 启动BLE后台服务(可选)
await _startAgentService(); await _startAgentService();
// 关联和后台扫描都成功后,再跳转 // 关联和后台扫描都成功后,再跳转
_autoNavigateToHome(); _autoNavigateToHome();
} else { } else {
@ -338,6 +345,12 @@ class PairingController extends GetxController {
// 关联失败,但仍然可以跳转到主页(用户体验考虑) // 关联失败,但仍然可以跳转到主页(用户体验考虑)
_autoNavigateToHome(); _autoNavigateToHome();
} }
} else if (!isneedearphone) {
// 启动BLE后台服务(可选)
await _startAgentService();
// 关联和后台扫描都成功后,再跳转
_autoNavigateToHome();
} }
} catch (e) { } catch (e) {
Logger.error('Companion设备关联过程发生错误: $e'); Logger.error('Companion设备关联过程发生错误: $e');
@ -353,10 +366,10 @@ class PairingController extends GetxController {
/// 自动跳转到主页 /// 自动跳转到主页
Future<void> _autoNavigateToHome() async { Future<void> _autoNavigateToHome() async {
if (_isNavigatingToHome) return; if (_isNavigatingToHome) return;
// 如果没有连接,不进行跳转 // 如果没有连接,不进行跳转
if (!isDeviceConnected) return; if (!isDeviceConnected) return;
// 在Android平台上,如果支持Companion且仍在关联中,不跳转 // 在Android平台上,如果支持Companion且仍在关联中,不跳转
if (Platform.isAndroid) { if (Platform.isAndroid) {
final isSupported = await _bleService.isCompanionSupported(); final isSupported = await _bleService.isCompanionSupported();
@ -365,25 +378,24 @@ class PairingController extends GetxController {
return; return;
} }
} }
_isNavigatingToHome = true; _isNavigatingToHome = true;
Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页'); Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页');
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
Get.offAllNamed(Routes.home); Get.offAllNamed(Routes.home);
}); });
} }
/// 启动BLE后台服务 /// 启动BLE后台服务
Future<void> _startAgentService() async { Future<void> _startAgentService() async {
try { try {
if (!isDeviceConnected) return; if (isneedearphone && !isDeviceConnected) return;
// 启动BLE服务,无需传递配置参数,将从.env读取 // 启动BLE服务,无需传递配置参数,将从.env读取
final result = await BleManager.to.startAgentService(); final result = await BleManager.to.startAgentService();
if (result) { if (result) {
Logger.info('BLE后台服务启动成功'); Logger.info('BLE后台服务启动成功');
} else { } else {
@ -393,12 +405,12 @@ class PairingController extends GetxController {
Logger.error('启动BLE后台服务发生错误: $e'); Logger.error('启动BLE后台服务发生错误: $e');
} }
} }
/// 停止BLE后台服务 /// 停止BLE后台服务
Future<void> _stopAgentService() async { Future<void> _stopAgentService() async {
try { try {
final result = await BleManager.to.stopAgentService(); final result = await BleManager.to.stopAgentService();
if (result) { if (result) {
Logger.info('BLE后台服务停止成功'); Logger.info('BLE后台服务停止成功');
} else { } else {
@ -425,7 +437,7 @@ class PairingController extends GetxController {
Logger.error('打开设置失败: $e'); Logger.error('打开设置失败: $e');
} }
} }
/// 打开应用设置 /// 打开应用设置
Future<void> openAppSettings() async { Future<void> openAppSettings() async {
await AppSettings.openAppSettings(); await AppSettings.openAppSettings();

1
local_plugins/agent_service/android/build.gradle.kts

@ -48,4 +48,5 @@ dependencies {
add("compileOnly", project(":deepvoice_utils")) add("compileOnly", project(":deepvoice_utils"))
add("compileOnly", project(":chat_storage")) add("compileOnly", project(":chat_storage"))
add("compileOnly", project(":ble_service")) add("compileOnly", project(":ble_service"))
add("compileOnly", project(":music_service"))
} }

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

@ -690,6 +690,42 @@ object AgentService : CoroutineScope {
"result" to functionCallResult.toString(), "result" to functionCallResult.toString(),
)) ))
FileLogger.d(TAG, "mcp调用结果: $functionCallResult") FileLogger.d(TAG, "mcp调用结果: $functionCallResult")
val metaStr = functionCallResult.optString("meta")
if (metaStr.isNotEmpty()) {
metadata = metaStr;
val meta = JSONObject(metaStr)
FileLogger.d(TAG, "mcp调用结果: $metaStr")
if (meta.has("card_music")) { //音乐卡片
val cardMusic = meta.getJSONObject("card_music")
FileLogger.d(TAG, "检查到音乐卡片: $cardMusic")
val musicUrl = cardMusic.optString("url", "")
val name = cardMusic.optString("name", "")
val sgener = cardMusic.optString("sgener", "")
processMusicPlay(musicUrl, name, sgener)
}else if (meta.has("card_musiclist")) { //音乐列表
val cardMusiclist = meta.getJSONObject("card_musiclist")
val musics = cardMusiclist.optJSONArray("musics")
if (musics != null) {
val playlist = mutableListOf<Map<String, String>>()
for (i in 0 until musics.length()) {
val item = musics.optJSONObject(i) ?: continue
val url = item.optString("url", "")
val name = item.optString("name", "")
val sgener = item.optString("sgener", "")
if (url.isNotEmpty()) {
playlist.add(mapOf("url" to url, "name" to name, "sgener" to sgener))
}
}
if (playlist.isNotEmpty()) {
processMusicPlayList(playlist)
} else {
FileLogger.w(TAG, "card_musiclist 中没有有效的音乐条目")
}
} else {
FileLogger.w(TAG, "card_musiclist.musics 不是一个数组")
}
}
}
} }
} }
) )
@ -1040,4 +1076,36 @@ object AgentService : CoroutineScope {
} }
} }
} }
}
/**
* 处理音乐播放
* 将ai返回的音乐连接 在后台启动播放
*
* @param musicUrl 图片文件路径
* @param name 歌曲名称,默认为空
* @param singer 歌手,默认为false
* @return 是否成功开始处理
*/
fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){
// 在其他 Service、BroadcastReceiver 或 Application 中调用
FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl")
MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl)
}
/**
* 处理音乐播放列表
* 将ai返回的音乐连接 在后台启动播放
*
* @param musicUrl 图片文件路径
* @param name 歌曲名称,默认为空
* @param singer 歌手,默认为false
* @return 是否成功开始处理
*/
fun processMusicPlayList(playlist:List<Map<String,String>>){
// 在其他 Service、BroadcastReceiver 或 Application 中调用
val urls = playlist.mapNotNull { it["url"] }.distinct()
FileLogger.i(TAG, "启动音乐服务 播放音乐列表 $urls")
MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls = urls)
}
}

1
local_plugins/music_service/android/build.gradle

@ -67,4 +67,5 @@ android {
dependencies { dependencies {
implementation project(':deepvoice_utils') implementation project(':deepvoice_utils')
implementation("androidx.media:media:1.6.0")
} }

9
local_plugins/music_service/android/src/main/AndroidManifest.xml

@ -3,7 +3,6 @@
<!-- 基础前台服务权限 --> <!-- 基础前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 14 及以上需要声明具体的前台服务类型权限 --> <!-- Android 14 及以上需要声明具体的前台服务类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
@ -13,5 +12,13 @@
android:exported="false" android:exported="false"
android:enabled="true" android:enabled="true"
android:foregroundServiceType="mediaPlayback" /> android:foregroundServiceType="mediaPlayback" />
<!-- 用于接收媒体按钮(耳机按钮、蓝牙播放控制) -->
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

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

@ -9,6 +9,8 @@ import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger import com.yunqiinnovation.deepvoice_utils.FileLogger
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -28,7 +30,7 @@ class MusicService : Service() {
fun isPlaying(): Boolean = currentStatus fun isPlaying(): Boolean = currentStatus
fun getCurrentUrl(): String? = currentUrlInternal fun getCurrentUrl(): String? = currentUrlInternal
} }
private lateinit var mediaSession: MediaSessionCompat
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private val playlist = mutableListOf<String>() // 可选:初始化为预置歌曲 private val playlist = mutableListOf<String>() // 可选:初始化为预置歌曲
private var currentIndex = 0 private var currentIndex = 0
@ -38,9 +40,38 @@ class MusicService : Service() {
FileLogger.init(this) FileLogger.init(this)
FileLogger.i(TAG, "MusicService 正在创建...") FileLogger.i(TAG, "MusicService 正在创建...")
isRunning.set(true) isRunning.set(true)
Log.i(TAG, "MusicService Created") mediaSession = MediaSessionCompat(this, TAG).apply {
setCallback(object : MediaSessionCompat.Callback() {
override fun onPlay() {
resume()
}
override fun onPause() {
pause()
}
override fun onSkipToNext() {
if (currentIndex + 1 < playlist.size) {
currentIndex++
play(playlist[currentIndex])
}
}
override fun onSkipToPrevious() {
if (currentIndex - 1 >= 0) {
currentIndex--
play(playlist[currentIndex])
}
}
override fun onStop() {
stopSelf()
}
})
isActive = true
}
startForeground(NOTIFICATION_ID, createNotification("等待播放...")) startForeground(NOTIFICATION_ID, createNotification("等待播放..."))
isRunning.set(true) Log.i(TAG, "MusicService Created")
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -173,15 +204,14 @@ class MusicService : Service() {
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
} }
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { // 修复 pendingIntent:打开 App 主 Activity
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
} val pendingIntent = PendingIntent.getActivity(
val pendingIntent = intent?.let { this,
PendingIntent.getActivity( 0,
this, 0, it, activityIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
) )
}
return NotificationCompat.Builder(this, CHANNEL_ID) return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("DeepSound") .setContentTitle("DeepSound")
@ -189,6 +219,39 @@ class MusicService : Service() {
.setSmallIcon(android.R.drawable.ic_media_play) .setSmallIcon(android.R.drawable.ic_media_play)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setOngoing(true) .setOngoing(true)
.setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
.setShowActionsInCompactView(0, 1, 2)
)
.addAction(
NotificationCompat.Action(
android.R.drawable.ic_media_previous, "上一曲",
androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
)
)
)
.addAction(
NotificationCompat.Action(
if (currentStatus) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play,
if (currentStatus) "暂停" else "播放",
androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
PlaybackStateCompat.ACTION_PLAY_PAUSE
)
)
)
.addAction(
NotificationCompat.Action(
android.R.drawable.ic_media_next, "下一曲",
androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
PlaybackStateCompat.ACTION_SKIP_TO_NEXT
)
)
)
.build() .build()
} }

11
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger import com.yunqiinnovation.deepvoice_utils.FileLogger
import com.yunqiinnovation.music_service.MusicServiceStarter.startServiceWithCommand
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
@ -27,7 +28,7 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
when (call.method) { when (call.method) {
"play" -> { "play" -> {
val url = call.argument<String>("url") val url = call.argument<String>("url")
MusicServiceStarter.startServiceWithCommand(context, "play", url) startServiceWithCommand(context, "play", url)
result.success(null) result.success(null)
} }
"playlist" -> { "playlist" -> {
@ -36,20 +37,20 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
result.success(null) result.success(null)
} }
"pause" -> { "pause" -> {
MusicServiceStarter.startServiceWithCommand(context, "pause") startServiceWithCommand(context, "pause")
result.success(null) result.success(null)
} }
"resume" -> { "resume" -> {
MusicServiceStarter.startServiceWithCommand(context, "resume") startServiceWithCommand(context, "resume")
result.success(null) result.success(null)
} }
"stop" -> { "stop" -> {
MusicServiceStarter.startServiceWithCommand(context, "stop") startServiceWithCommand(context, "stop")
result.success(null) result.success(null)
} }
"setVolume" -> { "setVolume" -> {
val volume = call.argument<Double>("volume")?.toFloat() val volume = call.argument<Double>("volume")?.toFloat()
MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume) startServiceWithCommand(context, "setVolume", volume = volume)
result.success(null) result.success(null)
} }
"getStatus" -> { "getStatus" -> {

Loading…
Cancel
Save