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) - AMapNavi (~> 10.0.900)
- Flutter - Flutter
- AMapFoundation (1.8.2) - AMapFoundation (1.8.2)
- AMapLocation (2.10.0):
- AMapFoundation (>= 1.8.0)
- AMapNavi (10.0.900): - AMapNavi (10.0.900):
- AMapFoundation (>= 1.8.2) - AMapFoundation (>= 1.8.2)
- audio_waveforms (0.0.1): - audio_waveforms (0.0.1):
@ -54,6 +56,9 @@ PODS:
- libwebp/sharpyuv (1.5.0) - libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0): - libwebp/webp (1.5.0):
- libwebp/sharpyuv - libwebp/sharpyuv
- location_service (0.0.1):
- AMapLocation (~> 2.10.0)
- Flutter
- Mantle (2.2.0): - Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0) - Mantle/extobjc (= 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`) - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`) - fluwx (from `.symlinks/plugins/fluwx/ios`)
- haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`) - haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`)
- location_service (from `.symlinks/plugins/location_service/ios`)
- ota (from `.symlinks/plugins/ota/ios`) - ota (from `.symlinks/plugins/ota/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_darwin (from `.symlinks/plugins/record_darwin/ios`) - record_darwin (from `.symlinks/plugins/record_darwin/ios`)
@ -107,6 +113,7 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- AMapFoundation - AMapFoundation
- AMapLocation
- AMapNavi - AMapNavi
- FBAEMKit - FBAEMKit
- FBSDKCoreKit - FBSDKCoreKit
@ -146,6 +153,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluwx/ios" :path: ".symlinks/plugins/fluwx/ios"
haptic_feedback: haptic_feedback:
:path: ".symlinks/plugins/haptic_feedback/ios" :path: ".symlinks/plugins/haptic_feedback/ios"
location_service:
:path: ".symlinks/plugins/location_service/ios"
ota: ota:
:path: ".symlinks/plugins/ota/ios" :path: ".symlinks/plugins/ota/ios"
permission_handler_apple: permission_handler_apple:
@ -160,6 +169,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b
AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec
AMapLocation: 5248aec2455ebb5d104b367813c946430a2ee033
AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
classic_bluetooth: c146e1618e867a47fa7ab79a3bf262e7d69d557d classic_bluetooth: c146e1618e867a47fa7ab79a3bf262e7d69d557d
@ -177,6 +187,7 @@ SPEC CHECKSUMS:
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b
haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0 haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
location_service: fab4ef23d473296a215c49e12c129e466574d94e
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
ota: 20c3e522c57e7ba70ce118109e73650acc9fec76 ota: 20c3e522c57e7ba70ce118109e73650acc9fec76
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d 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 { protocol AgentServiceListener: AnyObject {
func onEvent(eventName: String, data: [String: Any]) 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 { class AgentServiceImpl: NSObject {
private let TAG = "AgentServiceImpl" private let TAG = "AgentServiceImpl"
@ -55,6 +59,7 @@ class AgentServiceImpl: NSObject {
private let maxIdleSeconds: TimeInterval = 10 private let maxIdleSeconds: TimeInterval = 10
internal var audioPlayer: AudioPlayer? internal var audioPlayer: AudioPlayer?
private var locationInfo:[String: String]?
internal let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AgentServiceImpl") internal let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AgentServiceImpl")
@ -161,7 +166,9 @@ class AgentServiceImpl: NSObject {
sendError("Azure语音区域缺失", code: "CONFIG_ERROR") sendError("Azure语音区域缺失", code: "CONFIG_ERROR")
return false return false
} }
//启动定位服务
NotificationCenter.default.post(name: .locationstartEvent, object: nil)
addlocationmonitor()
if let mcpServer = config["mcpServer"] as? String { if let mcpServer = config["mcpServer"] as? String {
self.mcpServer = mcpServer self.mcpServer = mcpServer
} }
@ -441,7 +448,10 @@ class AgentServiceImpl: NSObject {
var messages: [[String: Any]] = [] var messages: [[String: Any]] = []
if !systemPrompt.isEmpty { 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) os_log("添加系统提示消息", log: logger, type: .info)
} }
@ -690,6 +700,27 @@ class AgentServiceImpl: NSObject {
MusicService.shared.playPlaylist(songs: songs) 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 { func clearChatHistory() -> Bool {
chatHistory.removeAll() 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.os.Build
import android.provider.CalendarContract import android.provider.CalendarContract
import android.telephony.SmsManager import android.telephony.SmsManager
import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.RECEIVER_NOT_EXPORTED
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -393,7 +395,10 @@ private class GetCurrentLocationHandler(private val context: Context?) : Functio
private class MediaPlayHandler(private val context: Context?) : FunctionHandler { private class MediaPlayHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { 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\": \"已开始播放媒体\"}" return "{\"result\": \"已开始播放媒体\"}"
} }
} }
@ -403,8 +408,12 @@ private class MediaPlayHandler(private val context: Context?) : FunctionHandler
*/ */
private class MediaPauseHandler(private val context: Context?) : FunctionHandler { private class MediaPauseHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { 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\": \"已暂停媒体播放\"}" return "{\"result\": \"已暂停媒体播放\"}"
} }
} }
@ -415,7 +424,11 @@ private class MediaPauseHandler(private val context: Context?) : FunctionHandler
private class MediaPreviousHandler(private val context: Context?) : FunctionHandler { private class MediaPreviousHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { 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\": \"已切换到上一首\"}" return "{\"result\": \"已切换到上一首\"}"
} }
} }
@ -426,7 +439,11 @@ private class MediaPreviousHandler(private val context: Context?) : FunctionHand
private class MediaNextHandler(private val context: Context?) : FunctionHandler { private class MediaNextHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { 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\": \"已切换到下一首\"}" 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) registerGetCurrentTimeFunction(client: client)
// 注册获取当前位置函数 // 注册获取当前位置函数
registerGetCurrentLocationFunction(client: client) // registerGetCurrentLocationFunction(client: client)
// 注册媒体控制函数 // 注册媒体控制函数
registerMediaPlayFunction(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.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' } s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'location_service/Sources/location_service/**/*' s.source_files = 'Classes/**/*'
s.dependency 'Flutter' s.dependency 'Flutter'
s.dependency 'AMapNavi', '~> 10.0.900' s.dependency 'AMapLocation', '~> 2.10.0'
s.static_framework = true s.static_framework = true
s.pod_target_xcconfig = { s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES', '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', '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() { 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();
}
Future<Map<String, double>> location() async { Future<Map<String, double>> location() async {
final result = await _methodChannel.invokeMethod<Map>('location'); final result = await _methodChannel.invokeMethod<Map>('location');

43
local_plugins/location_service/pubspec.yaml

@ -17,21 +17,8 @@ dev_dependencies:
sdk: flutter sdk: flutter
flutter_lints: ^5.0.0 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: 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: plugin:
platforms: platforms:
android: android:
@ -40,33 +27,3 @@ flutter:
ios: ios:
pluginClass: LocationServicePlugin 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.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.AudioManager import android.media.AudioManager
import android.media.MediaPlayer import android.media.MediaPlayer
@ -42,7 +44,7 @@ class MusicService : MediaBrowserServiceCompat() {
fun isPlaying() = currentStatus fun isPlaying() = currentStatus
fun getCurrentUrl(): String? = currentUrlInternal fun getCurrentUrl(): String? = currentUrlInternal
} }
private var isReceiverRegistered = false
private lateinit var mediaSession: MediaSessionCompat private lateinit var mediaSession: MediaSessionCompat
private lateinit var audioManager: AudioManager private lateinit var audioManager: AudioManager
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
@ -53,6 +55,22 @@ class MusicService : MediaBrowserServiceCompat() {
setupMediaSession() setupMediaSession()
startForeground(NOTIFICATION_ID, createNotification("等待播放","DeepSound","")) startForeground(NOTIFICATION_ID, createNotification("等待播放","DeepSound",""))
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager 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 启动完成") Log.i(TAG, "MediaService 启动完成")
} }
@ -101,6 +119,32 @@ class MusicService : MediaBrowserServiceCompat() {
return START_STICKY 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() { private fun setupMediaSession() {
mediaSession = MediaSessionCompat(this, "MusicMediaSession").apply { mediaSession = MediaSessionCompat(this, "MusicMediaSession").apply {
setFlags( setFlags(
@ -209,19 +253,33 @@ class MusicService : MediaBrowserServiceCompat() {
return builder.build() return builder.build()
} }
fun mediaButtonPendingIntent(action: Long): PendingIntent { fun mediaButtonPendingIntent(action: Long): PendingIntent {
val intent = Intent(this, MusicService::class.java).apply { val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, action.toKeyCode())
this.action = Intent.ACTION_MEDIA_BUTTON val intent = Intent(Intent.ACTION_MEDIA_BUTTON).apply {
putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, action.toKeyCode())) setPackage(packageName)
putExtra(Intent.EXTRA_KEY_EVENT, keyEvent)
} }
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else { } else {
PendingIntent.FLAG_UPDATE_CURRENT 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 // 转换 PlaybackStateCompat.ACTION 到 KeyEvent
fun Long.toKeyCode(): Int = when (this) { fun Long.toKeyCode(): Int = when (this) {
PlaybackStateCompat.ACTION_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY PlaybackStateCompat.ACTION_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY
@ -358,6 +416,10 @@ class MusicService : MediaBrowserServiceCompat() {
) = result.sendResult(mutableListOf()) ) = result.sendResult(mutableListOf())
override fun onDestroy() { override fun onDestroy() {
if (isReceiverRegistered) {
unregisterReceiver(controlReceiver)
isReceiverRegistered = false
}
mediaPlayer?.release() mediaPlayer?.release()
stopForeground(true) stopForeground(true)
isRunning.set(false) 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() let instance = MusicServicePlugin()
registrar.addMethodCallDelegate(instance, channel: channel) registrar.addMethodCallDelegate(instance, channel: channel)
self.channel = channel self.channel = channel
instance.addNSNotification()
} }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@ -68,4 +69,35 @@ public class MusicServicePlugin: NSObject, FlutterPlugin {
result(FlutterMethodNotImplemented) 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