Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
lxm 1 year ago
parent
commit
2feb6cbec0
  1. 11
      ios/Podfile.lock
  2. 37
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  3. 25
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt
  4. 2
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift
  5. 131
      local_plugins/location_service/ios/Classes/LocationService.swift
  6. 44
      local_plugins/location_service/ios/Classes/LocationServicePlugin.swift
  7. 6
      local_plugins/location_service/ios/location_service.podspec
  8. 32
      local_plugins/location_service/ios/location_service/Package.swift
  9. 19
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift
  10. 14
      local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy
  11. 27
      local_plugins/location_service/lib/location_service.dart
  12. 43
      local_plugins/location_service/pubspec.yaml
  13. 74
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt
  14. 32
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift

11
ios/Podfile.lock

@ -3,6 +3,8 @@ PODS:
- AMapNavi (~> 10.0.900)
- Flutter
- AMapFoundation (1.8.2)
- AMapLocation (2.10.0):
- AMapFoundation (>= 1.8.0)
- AMapNavi (10.0.900):
- AMapFoundation (>= 1.8.2)
- audio_waveforms (0.0.1):
@ -54,6 +56,9 @@ PODS:
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- location_service (0.0.1):
- AMapLocation (~> 2.10.0)
- Flutter
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
@ -98,6 +103,7 @@ DEPENDENCIES:
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`)
- location_service (from `.symlinks/plugins/location_service/ios`)
- ota (from `.symlinks/plugins/ota/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_darwin (from `.symlinks/plugins/record_darwin/ios`)
@ -107,6 +113,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- AMapFoundation
- AMapLocation
- AMapNavi
- FBAEMKit
- FBSDKCoreKit
@ -146,6 +153,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluwx/ios"
haptic_feedback:
:path: ".symlinks/plugins/haptic_feedback/ios"
location_service:
:path: ".symlinks/plugins/location_service/ios"
ota:
:path: ".symlinks/plugins/ota/ios"
permission_handler_apple:
@ -160,6 +169,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b
AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec
AMapLocation: 5248aec2455ebb5d104b367813c946430a2ee033
AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
classic_bluetooth: c146e1618e867a47fa7ab79a3bf262e7d69d557d
@ -177,6 +187,7 @@ SPEC CHECKSUMS:
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b
haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
location_service: fab4ef23d473296a215c49e12c129e466574d94e
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
ota: 20c3e522c57e7ba70ce118109e73650acc9fec76
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d

37
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -12,7 +12,11 @@ import music_service
protocol AgentServiceListener: AnyObject {
func onEvent(eventName: String, data: [String: Any])
}
extension Notification.Name {
static let navigationdEvent = Notification.Name("navigationdEvent") //导航事件
static let locationstartEvent = Notification.Name("locationstartEvent") //启动定位
static let updatelocationEvent = Notification.Name("updatelocationEvent") //更新位置事件
}
class AgentServiceImpl: NSObject {
private let TAG = "AgentServiceImpl"
@ -55,6 +59,7 @@ class AgentServiceImpl: NSObject {
private let maxIdleSeconds: TimeInterval = 10
internal var audioPlayer: AudioPlayer?
private var locationInfo:[String: String]?
internal let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AgentServiceImpl")
@ -161,7 +166,9 @@ class AgentServiceImpl: NSObject {
sendError("Azure语音区域缺失", code: "CONFIG_ERROR")
return false
}
//启动定位服务
NotificationCenter.default.post(name: .locationstartEvent, object: nil)
addlocationmonitor()
if let mcpServer = config["mcpServer"] as? String {
self.mcpServer = mcpServer
}
@ -441,7 +448,10 @@ class AgentServiceImpl: NSObject {
var messages: [[String: Any]] = []
if !systemPrompt.isEmpty {
messages.append(chatApiService.createSystemMessage(content: systemPrompt))
let info: [String: String] = self.locationInfo ?? ["user_location": "还未获取到位置信息", "user_lat": "未开启定位", "user_lng": "未开启定位"]
let _systemPrompt = fillTemplate(systemPrompt, with: info)
messages.append(chatApiService.createSystemMessage(content: _systemPrompt))
os_log("添加系统提示消息", log: logger, type: .info)
}
@ -690,6 +700,27 @@ class AgentServiceImpl: NSObject {
MusicService.shared.playPlaylist(songs: songs)
}
// 添加定位监听
private func addlocationmonitor(){
// 通常放在启动后或模块初始化时
NotificationCenter.default.addObserver(
forName: .updatelocationEvent,
object: nil,
queue: .main
) { notification in
if let info = notification.userInfo as? [String: String] {
print("📤 地址信息:\(info)")
self.locationInfo = info
}
}
}
func fillTemplate(_ template: String, with values: [String: String]) -> String {
var result = template
for (key, value) in values {
result = result.replacingOccurrences(of: "{\(key)}", with: value)
}
return result
}
func clearChatHistory() -> Bool {
chatHistory.removeAll()

25
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

@ -8,7 +8,9 @@ import android.net.Uri
import android.os.Build
import android.provider.CalendarContract
import android.telephony.SmsManager
import android.util.Log
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.RECEIVER_NOT_EXPORTED
import java.text.SimpleDateFormat
import java.util.*
@ -393,7 +395,10 @@ private class GetCurrentLocationHandler(private val context: Context?) : Functio
private class MediaPlayHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
// 发送媒体播放广播
context?.sendBroadcast(Intent("com.yunqiinnovation.deepsound.MEDIA_PLAY"))
val intent = Intent("com.yunqiinnovation.music_service.MEDIA_PLAY").apply {
setPackage(context?.packageName) // 设置包名使其成为显式 Intent
}
context?.sendBroadcast(intent)
return "{\"result\": \"已开始播放媒体\"}"
}
}
@ -403,8 +408,12 @@ private class MediaPlayHandler(private val context: Context?) : FunctionHandler
*/
private class MediaPauseHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
Log.d("MediaPauseHandler", "📤 正在发送广播 context = $context")
// 发送媒体暂停广播
context?.sendBroadcast(Intent("com.yunqiinnovation.deepsound.MEDIA_PAUSE"))
val intent = Intent("com.yunqiinnovation.music_service.MEDIA_PAUSE").apply {
setPackage(context?.packageName) // 设置包名使其成为显式 Intent
}
context?.sendBroadcast(intent)
return "{\"result\": \"已暂停媒体播放\"}"
}
}
@ -415,7 +424,11 @@ private class MediaPauseHandler(private val context: Context?) : FunctionHandler
private class MediaPreviousHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
// 发送切换上一首广播
context?.sendBroadcast(Intent("com.yunqiinnovation.deepsound.MEDIA_PREVIOUS"))
// context?.applicationContext?.sendBroadcast(Intent("com.yunqiinnovation.music_service.MEDIA_PREVIOUS"))
val intent = Intent("com.yunqiinnovation.music_service.MEDIA_PREVIOUS").apply {
setPackage(context?.packageName) // 设置包名使其成为显式 Intent
}
context?.sendBroadcast(intent)
return "{\"result\": \"已切换到上一首\"}"
}
}
@ -426,7 +439,11 @@ private class MediaPreviousHandler(private val context: Context?) : FunctionHand
private class MediaNextHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
// 发送切换下一首广播
context?.sendBroadcast(Intent("com.yunqiinnovation.deepsound.MEDIA_NEXT"))
// context?.applicationContext?.sendBroadcast(Intent("com.yunqiinnovation.music_service.MEDIA_NEXT"))
val intent = Intent("com.yunqiinnovation.music_service.MEDIA_NEXT").apply {
setPackage(context?.packageName) // 设置包名使其成为显式 Intent
}
context?.sendBroadcast(intent)
return "{\"result\": \"已切换到下一首\"}"
}
}

2
local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

@ -67,7 +67,7 @@ public class SystemFunctionHandler: NSObject {
registerGetCurrentTimeFunction(client: client)
// 注册获取当前位置函数
registerGetCurrentLocationFunction(client: client)
// registerGetCurrentLocationFunction(client: client)
// 注册媒体控制函数
registerMediaPlayFunction(client: client)

131
local_plugins/location_service/ios/Classes/LocationService.swift

@ -0,0 +1,131 @@
import Foundation
import AMapLocationKit
extension Notification.Name {
static let locationstartEvent = Notification.Name("locationstartEvent") //启动定位
static let updatelocationEvent = Notification.Name("updatelocationEvent") //更新位置事件
}
class LocationService: NSObject {
static let shared = LocationService()
private let TAG = "LocationService"
private var locationManager: AMapLocationManager!
private var currentLocation: CLLocation?
private var currentReGeocode: AMapLocationReGeocode?
private var initialized = false
private override init() {
super.init()
}
// 初始化定位服务
func initialize() {
if initialized { return }
print("\(TAG)--------- 初始化高德定位服务")
locationManager = AMapLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest // 定位精度
locationManager.distanceFilter = 10.0 // 移动距离阈值(单位:米)
locationManager.locatingWithReGeocode = true // 是否返回逆地理编码信息
locationManager.allowsBackgroundLocationUpdates = true // 是否允许后台定位
locationManager.locationTimeout = 10 // 定位超时时间(秒)
locationManager.reGeocodeTimeout = 5 // 逆地理编码超时时间(秒)
locationManager.startUpdatingLocation()
initialized = true
}
// 获取缓存的位置信息
func getCachedLocation() -> [String: String]? {
guard let location = currentLocation else { return nil }
var locationInfo: [String: String] = [
"user_lat": "\(location.coordinate.latitude)",
"user_lng": "\(location.coordinate.longitude)"
]
if let regeocode = currentReGeocode {
let address = [
regeocode.province, // 省
regeocode.city, // 市
regeocode.district, // 区
regeocode.street, // 街道
regeocode.number // 门牌号
].compactMap { $0 }.joined(separator: "")
locationInfo["user_location"] = address
}
return locationInfo
}
// 获取当前位置(带回调)
func getCurrentLocation(completion: @escaping ([String: String]?, Error?) -> Void) {
if let cached = getCachedLocation() {
completion(cached, nil)
} else {
let error = NSError(
domain: "LocationService",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "当前位置还未获取"]
)
completion(nil, error)
}
}
// 停止定位服务
func stop() {
locationManager.stopUpdatingLocation()
locationManager = nil
currentLocation = nil
currentReGeocode = nil
initialized = false
}
}
// MARK: - AMapLocationManagerDelegate
extension LocationService: AMapLocationManagerDelegate {
// 定位更新回调
func amapLocationManager(_ manager: AMapLocationManager!, didUpdate location: CLLocation!, reGeocode: AMapLocationReGeocode!) {
currentLocation = location
currentReGeocode = reGeocode
print("\(TAG)--------- 定位更新: \(location.coordinate.latitude), \(location.coordinate.longitude)")
var locationInfo: [String: String] = [
"user_lat": "\(location.coordinate.latitude)",
"user_lng": "\(location.coordinate.longitude)"
]
if let regeocode = currentReGeocode {
let address = [
reGeocode.province, // 省
reGeocode.city, // 市
reGeocode.district, // 区
reGeocode.street, // 街道
reGeocode.number // 门牌号
].compactMap { $0 }.joined(separator: "")
print("\(TAG)--------- 定位地址: \(address)")
locationInfo["user_location"] = address
}
NotificationCenter.default.post(
name: .updatelocationEvent,
object: nil,
userInfo: locationInfo
)
}
// 定位失败回调
func amapLocationManager(_ manager: AMapLocationManager!, didFailWithError error: Error!) {
print("\(TAG)--------- 定位失败: \(error.localizedDescription)")
}
// 权限变更回调
func amapLocationManager(_ manager: AMapLocationManager!, doRequireLocationAuth locationManager: CLLocationManager!) {
locationManager.requestWhenInUseAuthorization()
}
}

44
local_plugins/location_service/ios/Classes/LocationServicePlugin.swift

@ -0,0 +1,44 @@
import Flutter
import UIKit
import AMapLocationKit
public class LocationServicePlugin: NSObject, FlutterPlugin {
private static var channel: FlutterMethodChannel?
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "com.yunqiinnovation.location_service", binaryMessenger: registrar.messenger())
let instance = LocationServicePlugin()
// 必须先设置隐私合规
DispatchQueue.main.async {
// 2. API Key
AMapServices.shared()?.apiKey = "aa60a03be9f6531bcc64029e60f95d03"
AMapServices.shared().enableHTTPS = true
// 3. 打印验证(调试用)
print("高德SDK初始化完成")
}
registrar.addMethodCallDelegate(instance, channel: channel)
self.channel = channel
instance.addstartlocationmonitor()
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "location":
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
private func addstartlocationmonitor(){
// 通常放在启动后或模块初始化时
NotificationCenter.default.addObserver(
forName: .locationstartEvent,
object: nil,
queue: .main
) { notification in
print("📥 收到 启动定位服务事件")
LocationService.shared.initialize()
}
}
}

6
local_plugins/location_service/ios/location_service.podspec

@ -13,10 +13,10 @@ A new Flutter project.
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'location_service/Sources/location_service/**/*'
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.dependency 'AMapNavi', '~> 10.0.900'
s.dependency 'AMapLocation', '~> 2.10.0'
s.static_framework = true
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',

32
local_plugins/location_service/ios/location_service/Package.swift

@ -1,32 +0,0 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "location_service",
platforms: [
.iOS("12.0")
],
products: [
.library(name: "location-service", targets: ["location_service"])
],
dependencies: [],
targets: [
.target(
name: "location_service",
dependencies: [],
resources: [
// If your plugin requires a privacy manifest, for example if it uses any required
// reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's
// privacy impact, and then uncomment these lines. For more information, see
// https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
// .process("PrivacyInfo.xcprivacy"),
// If you have other resources that need to be bundled with your plugin, refer to
// the following instructions to add them:
// https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
]
)
]
)

19
local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift

@ -1,19 +0,0 @@
import Flutter
import UIKit
public class LocationServicePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "location_service", binaryMessenger: registrar.messenger())
let instance = LocationServicePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}

14
local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

27
local_plugins/location_service/lib/location_service.dart

@ -11,33 +11,8 @@ class LocationService {
'com.yunqiinnovation.location_service',
);
// 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _dataEventChannel;
late final EventChannel _statusEventChannel;
// 事件流
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream;
// 内部构造函数
LocationService._internal() {
_dataEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/data_events',
);
_statusEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/status_events',
);
// 初始化事件流
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
}
LocationService._internal() {}
Future<Map<String, double>> location() async {
final result = await _methodChannel.invokeMethod<Map>('location');

43
local_plugins/location_service/pubspec.yaml

@ -17,21 +17,8 @@ dev_dependencies:
sdk: flutter
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
@ -40,33 +27,3 @@ flutter:
ios:
pluginClass: LocationServicePlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package

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

@ -4,8 +4,10 @@ import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.BitmapFactory
import android.media.AudioManager
import android.media.MediaPlayer
@ -42,7 +44,7 @@ class MusicService : MediaBrowserServiceCompat() {
fun isPlaying() = currentStatus
fun getCurrentUrl(): String? = currentUrlInternal
}
private var isReceiverRegistered = false
private lateinit var mediaSession: MediaSessionCompat
private lateinit var audioManager: AudioManager
private var mediaPlayer: MediaPlayer? = null
@ -53,6 +55,22 @@ class MusicService : MediaBrowserServiceCompat() {
setupMediaSession()
startForeground(NOTIFICATION_ID, createNotification("等待播放","DeepSound",""))
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
// 注册广播监听器
val filter = IntentFilter().apply {
addAction("com.yunqiinnovation.music_service.MEDIA_PLAY")
addAction("com.yunqiinnovation.music_service.MEDIA_PAUSE")
addAction("com.yunqiinnovation.music_service.MEDIA_PREVIOUS")
addAction("com.yunqiinnovation.music_service.MEDIA_NEXT")
}
// Android 14+ 需要指定 flag
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(controlReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(controlReceiver, filter)
}
isReceiverRegistered = true
Log.i(TAG, "MediaService 启动完成")
}
@ -101,6 +119,32 @@ class MusicService : MediaBrowserServiceCompat() {
return START_STICKY
}
private val controlReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.i(TAG, "接受到通知事件 ${intent?.action}")
when (intent?.action) {
"com.yunqiinnovation.music_service.MEDIA_PAUSE" -> {
Log.i(TAG, "收到 MEDIA_PAUSE 广播")
pause()
}
"com.yunqiinnovation.music_service.MEDIA_PLAY" -> {
Log.i(TAG, "收到 MEDIA_PLAY 广播")
resume()
}
"com.yunqiinnovation.music_service.MEDIA_PREVIOUS" -> {
Log.i(TAG, "收到 MEDIA_PREVIOUS 广播")
playPrevious()
}
"com.yunqiinnovation.music_service.MEDIA_NEXT" -> {
Log.i(TAG, "收到 MEDIA_NEXT 广播")
playNext()
}
// 你也可以扩展支持更多指令
}
}
}
private fun setupMediaSession() {
mediaSession = MediaSessionCompat(this, "MusicMediaSession").apply {
setFlags(
@ -209,19 +253,33 @@ class MusicService : MediaBrowserServiceCompat() {
return builder.build()
}
fun mediaButtonPendingIntent(action: Long): PendingIntent {
val intent = Intent(this, MusicService::class.java).apply {
this.action = Intent.ACTION_MEDIA_BUTTON
putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, action.toKeyCode()))
val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, action.toKeyCode())
val intent = Intent(Intent.ACTION_MEDIA_BUTTON).apply {
setPackage(packageName)
putExtra(Intent.EXTRA_KEY_EVENT, keyEvent)
}
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
return PendingIntent.getService(this, action.toInt(), intent, flags)
return PendingIntent.getBroadcast(this, action.toInt(), intent, flags)
}
// fun mediaButtonPendingIntent(action: Long): PendingIntent {
// val intent = Intent(this, MusicService::class.java).apply {
// this.action = Intent.ACTION_MEDIA_BUTTON
// putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, action.toKeyCode()))
// }
// val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
// } else {
// PendingIntent.FLAG_UPDATE_CURRENT
// }
// return PendingIntent.getService(this, action.toInt(), intent, flags)
// }
// 转换 PlaybackStateCompat.ACTION 到 KeyEvent
fun Long.toKeyCode(): Int = when (this) {
PlaybackStateCompat.ACTION_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY
@ -358,6 +416,10 @@ class MusicService : MediaBrowserServiceCompat() {
) = result.sendResult(mutableListOf())
override fun onDestroy() {
if (isReceiverRegistered) {
unregisterReceiver(controlReceiver)
isReceiverRegistered = false
}
mediaPlayer?.release()
stopForeground(true)
isRunning.set(false)

32
local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift

@ -9,6 +9,7 @@ public class MusicServicePlugin: NSObject, FlutterPlugin {
let instance = MusicServicePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
self.channel = channel
instance.addNSNotification()
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@ -68,4 +69,35 @@ public class MusicServicePlugin: NSObject, FlutterPlugin {
result(FlutterMethodNotImplemented)
}
}
private func addNSNotification(){
NotificationCenter.default.addObserver(
forName: NSNotification.Name("MediaPause"),
object: nil,
queue: .main
) { _ in
MusicService.shared.pause()
}
NotificationCenter.default.addObserver(
forName: NSNotification.Name("MediaPlay"),
object: nil,
queue: .main
) { _ in
MusicService.shared.resume()
}
NotificationCenter.default.addObserver(
forName: NSNotification.Name("MediaPrevious"),
object: nil,
queue: .main
) { _ in
MusicService.shared.previous()
}
NotificationCenter.default.addObserver(
forName: NSNotification.Name("MediaNext"),
object: nil,
queue: .main
) { _ in
MusicService.shared.next()
}
}
}

Loading…
Cancel
Save