57 changed files with 1890 additions and 1925 deletions
@ -1,15 +1,2 @@ |
|||
rootProject.name = "agent_service" |
|||
|
|||
// 包含项目依赖 |
|||
include(":azure_speech") |
|||
include(":open_ai_service") |
|||
include(":deepvoice_utils") |
|||
include(":chat_storage") |
|||
include(":music_service") |
|||
|
|||
// 设置项目路径 |
|||
project(":azure_speech").projectDir = file("../../azure_speech/android") |
|||
project(":open_ai_service").projectDir = file("../../open_ai_service/android") |
|||
project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android") |
|||
project(":chat_storage").projectDir = file("../../chat_storage/android") |
|||
project(":music_service").projectDir = file("../../music_service/android") |
|||
@ -1 +0,0 @@ |
|||
rootProject.name = "deepvoice_utils" |
|||
@ -1,7 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
package="com.yunqiinnovation.deepvoice_utils"> |
|||
|
|||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
|||
</manifest> |
|||
@ -1,33 +0,0 @@ |
|||
package com.yunqiinnovation.deepvoice_utils |
|||
|
|||
import androidx.annotation.NonNull |
|||
import io.flutter.embedding.engine.plugins.FlutterPlugin |
|||
import io.flutter.plugin.common.MethodCall |
|||
import io.flutter.plugin.common.MethodChannel |
|||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler |
|||
import io.flutter.plugin.common.MethodChannel.Result |
|||
|
|||
/** DeepvoiceUtilsPlugin */ |
|||
class DeepvoiceUtilsPlugin: FlutterPlugin, MethodCallHandler { |
|||
private lateinit var channel : MethodChannel |
|||
|
|||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "deepvoice_utils") |
|||
channel.setMethodCallHandler(this) |
|||
} |
|||
|
|||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|||
when (call.method) { |
|||
"getPlatformVersion" -> { |
|||
result.success("Android ${android.os.Build.VERSION.RELEASE}") |
|||
} |
|||
else -> { |
|||
result.notImplemented() |
|||
} |
|||
} |
|||
} |
|||
|
|||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { |
|||
channel.setMethodCallHandler(null) |
|||
} |
|||
} |
|||
@ -1,237 +0,0 @@ |
|||
package com.yunqiinnovation.deepvoice_utils |
|||
|
|||
import android.content.Context |
|||
import android.util.Log |
|||
import java.io.File |
|||
import java.io.FileWriter |
|||
import java.io.IOException |
|||
import java.text.SimpleDateFormat |
|||
import java.util.* |
|||
import java.util.concurrent.ExecutorService |
|||
import java.util.concurrent.Executors |
|||
|
|||
/** |
|||
* 文件日志工具 |
|||
* 将日志同时输出到Logcat和文件系统 |
|||
*/ |
|||
class FileLogger private constructor(context: Context) { |
|||
private val logDir: File |
|||
private var currentLogFile: File? = null |
|||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) |
|||
private val timeFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()) |
|||
private val logExecutor: ExecutorService = Executors.newSingleThreadExecutor() |
|||
|
|||
init { |
|||
// 初始化日志目录 |
|||
logDir = File(context.getExternalFilesDir(null), "logs") |
|||
if (!logDir.exists()) { |
|||
logDir.mkdirs() |
|||
} |
|||
|
|||
// 创建当天的日志文件 |
|||
updateLogFile() |
|||
} |
|||
|
|||
/** |
|||
* 更新日志文件为当天日期 |
|||
*/ |
|||
private fun updateLogFile() { |
|||
val today = dateFormat.format(Date()) |
|||
val logFile = File(logDir, "log_$today.txt") |
|||
|
|||
if (currentLogFile == null || currentLogFile?.absolutePath != logFile.absolutePath) { |
|||
currentLogFile = logFile |
|||
|
|||
// 如果是新文件,添加文件头 |
|||
if (!logFile.exists()) { |
|||
try { |
|||
logFile.createNewFile() |
|||
FileWriter(logFile, true).use { writer -> |
|||
writer.append("===== 日志开始: ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())} =====\n") |
|||
} |
|||
} catch (e: IOException) { |
|||
Log.e(TAG, "创建日志文件失败: ${e.message}") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 调试级别日志 |
|||
*/ |
|||
fun d(tag: String, message: String) { |
|||
// 输出到Logcat |
|||
Log.d(tag, message) |
|||
|
|||
// 异步写入文件 |
|||
logToFile("D", tag, message) |
|||
} |
|||
|
|||
/** |
|||
* 信息级别日志 |
|||
*/ |
|||
fun i(tag: String, message: String) { |
|||
// 输出到Logcat |
|||
Log.i(tag, message) |
|||
|
|||
// 异步写入文件 |
|||
logToFile("I", tag, message) |
|||
} |
|||
|
|||
/** |
|||
* 警告级别日志 |
|||
*/ |
|||
fun w(tag: String, message: String) { |
|||
// 输出到Logcat |
|||
Log.w(tag, message) |
|||
|
|||
// 异步写入文件 |
|||
logToFile("W", tag, message) |
|||
} |
|||
|
|||
/** |
|||
* 错误级别日志 |
|||
*/ |
|||
fun e(tag: String, message: String, throwable: Throwable? = null) { |
|||
// 输出到Logcat |
|||
if (throwable != null) { |
|||
Log.e(tag, message, throwable) |
|||
} else { |
|||
Log.e(tag, message) |
|||
} |
|||
|
|||
// 异步写入文件 |
|||
val errorMsg = if (throwable != null) { |
|||
"$message\n${throwable.message}\n${throwable.stackTraceToString()}" |
|||
} else { |
|||
message |
|||
} |
|||
|
|||
logToFile("E", tag, errorMsg) |
|||
} |
|||
|
|||
/** |
|||
* 将日志写入文件 |
|||
*/ |
|||
private fun logToFile(level: String, tag: String, message: String) { |
|||
|
|||
logExecutor.execute { |
|||
try { |
|||
// 检查是否需要更新日志文件 |
|||
updateLogFile() |
|||
|
|||
// 格式化日志内容 |
|||
val timestamp = timeFormat.format(Date()) |
|||
val logLine = "$timestamp $level/$tag: $message\n" |
|||
|
|||
// 写入文件 |
|||
if (currentLogFile != null && currentLogFile!!.exists()) { |
|||
FileWriter(currentLogFile!!, true).use { writer -> |
|||
writer.append(logLine) |
|||
} |
|||
} |
|||
} catch (e: IOException) { |
|||
Log.e(TAG, "写入日志文件失败: ${e.message}") |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 清理旧日志文件 |
|||
* @param daysToKeep 保留的天数 |
|||
*/ |
|||
fun cleanOldLogs(daysToKeep: Int = 7) { |
|||
logExecutor.execute { |
|||
try { |
|||
val currentTime = System.currentTimeMillis() |
|||
val maxAge = daysToKeep * 24 * 60 * 60 * 1000L |
|||
|
|||
logDir.listFiles()?.forEach { file -> |
|||
if (file.isFile && file.name.startsWith("log_") && file.name.endsWith(".txt")) { |
|||
val fileAge = currentTime - file.lastModified() |
|||
if (fileAge > maxAge) { |
|||
val deleted = file.delete() |
|||
if (deleted) { |
|||
Log.d(TAG, "已删除旧日志文件: ${file.name}") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "清理旧日志失败: ${e.message}") |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 关闭日志系统 |
|||
*/ |
|||
fun shutdown() { |
|||
logExecutor.shutdown() |
|||
} |
|||
|
|||
companion object { |
|||
private const val TAG = "FileLogger" |
|||
private var INSTANCE: FileLogger? = null |
|||
|
|||
/** |
|||
* 初始化日志系统 |
|||
*/ |
|||
@Synchronized |
|||
fun init(context: Context) { |
|||
if (INSTANCE == null) { |
|||
INSTANCE = FileLogger(context.applicationContext) |
|||
d(TAG, "文件日志系统已初始化") |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 调试日志 |
|||
*/ |
|||
fun d(tag: String, message: String) { |
|||
INSTANCE?.d(tag, message) ?: Log.d(tag, message) |
|||
} |
|||
|
|||
/** |
|||
* 信息日志 |
|||
*/ |
|||
fun i(tag: String, message: String) { |
|||
INSTANCE?.i(tag, message) ?: Log.i(tag, message) |
|||
} |
|||
|
|||
/** |
|||
* 警告日志 |
|||
*/ |
|||
fun w(tag: String, message: String) { |
|||
INSTANCE?.w(tag, message) ?: Log.w(tag, message) |
|||
} |
|||
|
|||
/** |
|||
* 错误日志 |
|||
*/ |
|||
fun e(tag: String, message: String, throwable: Throwable? = null) { |
|||
INSTANCE?.e(tag, message, throwable) ?: run { |
|||
if (throwable != null) { |
|||
Log.e(tag, message, throwable) |
|||
} else { |
|||
Log.e(tag, message) |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 清理旧日志 |
|||
*/ |
|||
fun cleanOldLogs(daysToKeep: Int = 7) { |
|||
INSTANCE?.cleanOldLogs(daysToKeep) |
|||
} |
|||
|
|||
/** |
|||
* 关闭日志系统 |
|||
*/ |
|||
fun shutdown() { |
|||
INSTANCE?.shutdown() |
|||
INSTANCE = null |
|||
} |
|||
} |
|||
} |
|||
@ -1,4 +0,0 @@ |
|||
#import <Flutter/Flutter.h> |
|||
|
|||
@interface DeepvoiceUtilsPlugin : NSObject<FlutterPlugin> |
|||
@end |
|||
@ -1,12 +0,0 @@ |
|||
#import "DeepvoiceUtilsPlugin.h" |
|||
#if __has_include(<deepvoice_utils/deepvoice_utils-Swift.h>) |
|||
#import <deepvoice_utils/deepvoice_utils-Swift.h> |
|||
#else |
|||
#import "deepvoice_utils-Swift.h" |
|||
#endif |
|||
|
|||
@implementation DeepvoiceUtilsPlugin |
|||
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { |
|||
[SwiftDeepvoiceUtilsPlugin registerWithRegistrar:registrar]; |
|||
} |
|||
@end |
|||
@ -1,30 +0,0 @@ |
|||
import Flutter |
|||
import UIKit |
|||
|
|||
public class DeepvoiceUtilsPlugin: NSObject, FlutterPlugin { |
|||
private static let TAG = "DeepvoiceUtilsPlugin" |
|||
|
|||
public static func register(with registrar: FlutterPluginRegistrar) { |
|||
let channel = FlutterMethodChannel(name: "deepvoice_utils", binaryMessenger: registrar.messenger()) |
|||
let instance = DeepvoiceUtilsPlugin() |
|||
registrar.addMethodCallDelegate(instance, channel: channel) |
|||
|
|||
// 初始化文件日志 |
|||
FileLogger.initialize() |
|||
FileLogger.d(TAG, "DeepvoiceUtilsPlugin 已初始化") |
|||
} |
|||
|
|||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
|||
switch call.method { |
|||
case "getPlatformVersion": |
|||
result("iOS \(UIDevice.current.systemVersion)") |
|||
default: |
|||
result(FlutterMethodNotImplemented) |
|||
} |
|||
} |
|||
|
|||
public func detachFromEngine(for registrar: FlutterPluginRegistrar) { |
|||
// 关闭日志系统 |
|||
FileLogger.shutdown() |
|||
} |
|||
} |
|||
@ -1,286 +0,0 @@ |
|||
import Foundation |
|||
import os.log |
|||
|
|||
/** |
|||
* 文件日志工具 |
|||
* 将日志同时输出到控制台和文件系统 |
|||
*/ |
|||
@objc public class FileLogger: NSObject { |
|||
|
|||
private static let TAG = "FileLogger" |
|||
private static var instance: FileLogger? |
|||
|
|||
private let logDir: URL |
|||
private var currentLogFile: URL? |
|||
private let logQueue = DispatchQueue(label: "com.yunqiinnovation.deepvoice_utils.logger", qos: .utility) |
|||
|
|||
private let dateFormatter: DateFormatter = { |
|||
let formatter = DateFormatter() |
|||
formatter.dateFormat = "yyyy-MM-dd" |
|||
return formatter |
|||
}() |
|||
|
|||
private let timeFormatter: DateFormatter = { |
|||
let formatter = DateFormatter() |
|||
formatter.dateFormat = "HH:mm:ss.SSS" |
|||
return formatter |
|||
}() |
|||
|
|||
/** |
|||
* 初始化日志系统 |
|||
*/ |
|||
private init(directory: URL) { |
|||
self.logDir = directory.appendingPathComponent("logs", isDirectory: true) |
|||
super.init() |
|||
|
|||
// 创建日志目录 |
|||
try? FileManager.default.createDirectory(at: logDir, withIntermediateDirectories: true) |
|||
|
|||
// 创建当天日志文件 |
|||
updateLogFile() |
|||
|
|||
// 记录初始化完成 |
|||
self.d(FileLogger.TAG, "文件日志系统已初始化") |
|||
} |
|||
|
|||
/** |
|||
* 更新日志文件为当天日期 |
|||
*/ |
|||
private func updateLogFile() { |
|||
let today = dateFormatter.string(from: Date()) |
|||
let logFile = logDir.appendingPathComponent("log_\(today).txt") |
|||
|
|||
if currentLogFile == nil || currentLogFile?.path != logFile.path { |
|||
currentLogFile = logFile |
|||
|
|||
// 如果是新文件,添加文件头 |
|||
if !FileManager.default.fileExists(atPath: logFile.path) { |
|||
let headerDate = DateFormatter() |
|||
headerDate.dateFormat = "yyyy-MM-dd HH:mm:ss" |
|||
let headerLine = "===== 日志开始: \(headerDate.string(from: Date())) =====\n" |
|||
|
|||
try? headerLine.write(to: logFile, atomically: true, encoding: .utf8) |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 将日志写入文件 |
|||
*/ |
|||
private func logToFile(level: String, tag: String, message: String) { |
|||
logQueue.async { [weak self] in |
|||
guard let self = self else { return } |
|||
|
|||
do { |
|||
// 检查是否需要更新日志文件 |
|||
self.updateLogFile() |
|||
|
|||
guard let logFile = self.currentLogFile, |
|||
FileManager.default.fileExists(atPath: logFile.path) else { |
|||
return |
|||
} |
|||
|
|||
// 格式化日志内容 |
|||
let timestamp = self.timeFormatter.string(from: Date()) |
|||
let logLine = "\(timestamp) \(level)/\(tag): \(message)\n" |
|||
|
|||
// 获取文件句柄并追加内容 |
|||
if let fileHandle = try? FileHandle(forWritingTo: logFile) { |
|||
fileHandle.seekToEndOfFile() |
|||
if let data = logLine.data(using: .utf8) { |
|||
fileHandle.write(data) |
|||
} |
|||
fileHandle.closeFile() |
|||
} else { |
|||
// 如果无法获取文件句柄,尝试直接写入 |
|||
let existingContent = try? String(contentsOf: logFile, encoding: .utf8) |
|||
let newContent = (existingContent ?? "") + logLine |
|||
try newContent.write(to: logFile, atomically: true, encoding: .utf8) |
|||
} |
|||
} catch { |
|||
print("写入日志文件失败: \(error.localizedDescription)") |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 调试级别日志 |
|||
*/ |
|||
private func d(_ tag: String, _ message: String) { |
|||
// 输出到控制台 |
|||
print("D/\(tag): \(message)") |
|||
|
|||
// 写入文件 |
|||
logToFile(level: "D", tag: tag, message: message) |
|||
} |
|||
|
|||
/** |
|||
* 信息级别日志 |
|||
*/ |
|||
private func i(_ tag: String, _ message: String) { |
|||
// 输出到控制台 |
|||
print("I/\(tag): \(message)") |
|||
|
|||
// 写入文件 |
|||
logToFile(level: "I", tag: tag, message: message) |
|||
} |
|||
|
|||
/** |
|||
* 警告级别日志 |
|||
*/ |
|||
private func w(_ tag: String, _ message: String) { |
|||
// 输出到控制台 |
|||
print("W/\(tag): \(message)") |
|||
|
|||
// 写入文件 |
|||
logToFile(level: "W", tag: tag, message: message) |
|||
} |
|||
|
|||
/** |
|||
* 错误级别日志 |
|||
*/ |
|||
private func e(_ tag: String, _ message: String, _ error: Error? = nil) { |
|||
var fullMessage = message |
|||
|
|||
// 如果有错误,添加错误信息 |
|||
if let error = error { |
|||
fullMessage += "\n\(error.localizedDescription)" |
|||
} |
|||
|
|||
// 输出到控制台 |
|||
print("E/\(tag): \(fullMessage)") |
|||
|
|||
// 写入文件 |
|||
logToFile(level: "E", tag: tag, message: fullMessage) |
|||
} |
|||
|
|||
/** |
|||
* 清理旧日志文件 |
|||
*/ |
|||
private func cleanOldLogs(daysToKeep: Int = 7) { |
|||
logQueue.async { |
|||
do { |
|||
let currentDate = Date() |
|||
let fileManager = FileManager.default |
|||
|
|||
let logFiles = try fileManager.contentsOfDirectory(at: self.logDir, includingPropertiesForKeys: [.creationDateKey], options: []) |
|||
|
|||
for fileURL in logFiles where fileURL.pathExtension == "txt" { |
|||
if let fileAttributes = try? fileManager.attributesOfItem(atPath: fileURL.path), |
|||
let creationDate = fileAttributes[.creationDate] as? Date { |
|||
|
|||
let age = Calendar.current.dateComponents([.day], from: creationDate, to: currentDate) |
|||
|
|||
if let days = age.day, days > daysToKeep { |
|||
try? fileManager.removeItem(at: fileURL) |
|||
print("D/\(FileLogger.TAG): 已删除旧日志文件: \(fileURL.lastPathComponent)") |
|||
} |
|||
} |
|||
} |
|||
} catch { |
|||
print("E/\(FileLogger.TAG): 清理旧日志失败: \(error.localizedDescription)") |
|||
} |
|||
} |
|||
} |
|||
|
|||
// MARK: - 公共静态方法 |
|||
|
|||
/** |
|||
* 初始化日志系统 |
|||
*/ |
|||
@objc public static func initialize() { |
|||
if instance == nil { |
|||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] |
|||
instance = FileLogger(directory: documentsDirectory) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 初始化日志系统(使用自定义目录) |
|||
*/ |
|||
@objc public static func initialize(withDirectory directory: URL) { |
|||
if instance == nil { |
|||
instance = FileLogger(directory: directory) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 调试日志 |
|||
*/ |
|||
@objc public static func d(_ tag: String, _ message: String) { |
|||
// 如果实例不存在,先使用默认目录初始化 |
|||
if instance == nil { |
|||
initialize() |
|||
} |
|||
|
|||
instance?.d(tag, message) |
|||
|
|||
// 使用系统日志 |
|||
if #available(iOS 10.0, *) { |
|||
os_log("%{public}@", log: OSLog(subsystem: "com.yunqiinnovation.deepvoice_utils", category: tag), type: .debug, message) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 信息日志 |
|||
*/ |
|||
@objc public static func i(_ tag: String, _ message: String) { |
|||
if instance == nil { |
|||
initialize() |
|||
} |
|||
|
|||
instance?.i(tag, message) |
|||
|
|||
// 使用系统日志 |
|||
if #available(iOS 10.0, *) { |
|||
os_log("%{public}@", log: OSLog(subsystem: "com.yunqiinnovation.deepvoice_utils", category: tag), type: .info, message) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 警告日志 |
|||
*/ |
|||
@objc public static func w(_ tag: String, _ message: String) { |
|||
if instance == nil { |
|||
initialize() |
|||
} |
|||
|
|||
instance?.w(tag, message) |
|||
|
|||
// 使用系统日志 |
|||
if #available(iOS 10.0, *) { |
|||
os_log("%{public}@", log: OSLog(subsystem: "com.yunqiinnovation.deepvoice_utils", category: tag), type: .default, message) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 错误日志 |
|||
*/ |
|||
@objc public static func e(_ tag: String, _ message: String, _ error: Error? = nil) { |
|||
if instance == nil { |
|||
initialize() |
|||
} |
|||
|
|||
instance?.e(tag, message, error) |
|||
|
|||
// 使用系统日志 |
|||
if #available(iOS 10.0, *) { |
|||
let errorMessage = error != nil ? "\(message): \(error!.localizedDescription)" : message |
|||
os_log("%{public}@", log: OSLog(subsystem: "com.yunqiinnovation.deepvoice_utils", category: tag), type: .error, errorMessage) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 清理旧日志 |
|||
*/ |
|||
@objc public static func cleanOldLogs(daysToKeep: Int = 7) { |
|||
instance?.cleanOldLogs(daysToKeep: daysToKeep) |
|||
} |
|||
|
|||
/** |
|||
* 关闭日志系统 |
|||
*/ |
|||
@objc public static func shutdown() { |
|||
instance = nil |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
import Flutter |
|||
import UIKit |
|||
|
|||
// 这个类是为了满足Flutter插件的命名约定 |
|||
// 实际实现在DeepvoiceUtilsPlugin.swift中 |
|||
public class SwiftDeepvoiceUtilsPlugin: NSObject, FlutterPlugin { |
|||
public static func register(with registrar: FlutterPluginRegistrar) { |
|||
// 调用真正的实现 |
|||
DeepvoiceUtilsPlugin.register(with: registrar) |
|||
} |
|||
} |
|||
@ -1,54 +0,0 @@ |
|||
# DeepVoice Utils iOS 实现 |
|||
|
|||
## 简介 |
|||
|
|||
本模块是 `deepvoice_utils` 插件的 iOS 实现部分,主要提供以下功能: |
|||
|
|||
- 文件日志记录(FileLogger):将日志记录到文件,方便调试和问题排查 |
|||
|
|||
## 使用说明 |
|||
|
|||
### 日志系统 |
|||
|
|||
该插件自动初始化日志系统,开发者无需手动调用初始化方法。日志文件默认保存在应用的 Documents 目录下的 logs 文件夹中,按日期分文件存储。 |
|||
|
|||
## 配置要求 |
|||
|
|||
- iOS 11.0 或更高版本 |
|||
- Swift 5.0 |
|||
- 确保应用拥有文件写入权限 |
|||
|
|||
## 开发说明 |
|||
|
|||
### 文件结构 |
|||
|
|||
- `DeepvoiceUtilsPlugin.swift`:插件主类,负责与 Flutter 交互 |
|||
- `FileLogger.swift`:日志系统实现,提供日志记录功能 |
|||
- `SwiftDeepvoiceUtilsPlugin.swift`:命名约定桥接类 |
|||
- `DeepvoiceUtilsPlugin.h/.m`:Objective-C 桥接文件 |
|||
|
|||
### 自定义日志目录 |
|||
|
|||
如需使用自定义日志目录,可以在应用启动时手动调用初始化方法: |
|||
|
|||
```swift |
|||
import deepvoice_utils |
|||
|
|||
// 在 AppDelegate 中 |
|||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { |
|||
// 自定义日志目录 |
|||
let customDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("custom_logs") |
|||
FileLogger.initialize(withDirectory: customDirectory) |
|||
|
|||
return true |
|||
} |
|||
``` |
|||
|
|||
### 清理旧日志 |
|||
|
|||
默认情况下,日志系统不会自动清理旧日志。如果需要清理,可以手动调用: |
|||
|
|||
```swift |
|||
// 清理7天前的日志 |
|||
FileLogger.cleanOldLogs(daysToKeep: 7) |
|||
``` |
|||
@ -1,19 +0,0 @@ |
|||
# |
|||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. |
|||
# |
|||
Pod::Spec.new do |s| |
|||
s.name = 'deepvoice_utils' |
|||
s.version = '0.0.1' |
|||
s.summary = '深语工具库' |
|||
s.description = <<-DESC |
|||
提供文件日志记录等通用功能的Flutter插件。 |
|||
DESC |
|||
s.homepage = 'http://yunqiinnovation.com' |
|||
s.license = { :file => '../LICENSE' } |
|||
s.author = { 'YunQi Innovation' => 'support@yunqiinnovation.com' } |
|||
s.source = { :path => '.' } |
|||
s.source_files = 'Classes/**/*' |
|||
|
|||
s.ios.deployment_target = '11.0' |
|||
s.swift_version = '5.0' |
|||
end |
|||
@ -1,3 +0,0 @@ |
|||
library deepvoice_utils; |
|||
|
|||
export 'src/file_logger.dart'; |
|||
@ -1,31 +0,0 @@ |
|||
import 'dart:io'; |
|||
import 'package:flutter/foundation.dart'; |
|||
|
|||
/// 文件日志记录器 |
|||
/// |
|||
/// 提供文件日志记录功能,支持简单的日志记录 |
|||
class FileLogger { |
|||
/// 记录调试日志 |
|||
static void d(String tag, String message) { |
|||
debugPrint('[$tag] DEBUG: $message'); |
|||
} |
|||
|
|||
/// 记录信息日志 |
|||
static void i(String tag, String message) { |
|||
debugPrint('[$tag] INFO: $message'); |
|||
} |
|||
|
|||
/// 记录警告日志 |
|||
static void w(String tag, String message) { |
|||
debugPrint('[$tag] WARNING: $message'); |
|||
} |
|||
|
|||
/// 记录错误日志 |
|||
static void e(String tag, String message, [dynamic error, StackTrace? stackTrace]) { |
|||
final errorMsg = error != null ? '$message: $error' : message; |
|||
debugPrint('[$tag] ERROR: $errorMsg'); |
|||
if (stackTrace != null) { |
|||
debugPrint(stackTrace.toString()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
name: deepvoice_utils |
|||
description: 通用工具库,包含日志、文件处理等功能 |
|||
version: 0.0.1 |
|||
|
|||
environment: |
|||
sdk: ">=2.17.0 <4.0.0" |
|||
flutter: ">=2.5.0" |
|||
|
|||
dependencies: |
|||
flutter: |
|||
sdk: flutter |
|||
logger: ^2.0.0 |
|||
path_provider: ^2.1.0 |
|||
|
|||
dev_dependencies: |
|||
flutter_test: |
|||
sdk: flutter |
|||
flutter_lints: ^2.0.0 |
|||
|
|||
flutter: |
|||
plugin: |
|||
platforms: |
|||
android: |
|||
package: com.yunqiinnovation.deepvoice_utils |
|||
pluginClass: DeepvoiceUtilsPlugin |
|||
ios: |
|||
pluginClass: DeepvoiceUtilsPlugin |
|||
@ -0,0 +1 @@ |
|||
rootProject.name = "open_ai_service" |
|||
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
package="com.deep_voice.speech"> |
|||
|
|||
<!-- 语音识别和合成所需权限 --> |
|||
<uses-permission android:name="android.permission.INTERNET" /> |
|||
<uses-permission android:name="android.permission.RECORD_AUDIO" /> |
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
|||
|
|||
</manifest> |
|||
@ -0,0 +1,52 @@ |
|||
package com.deep_voice.speech.asr |
|||
|
|||
/** |
|||
* ASR事件类型枚举 |
|||
*/ |
|||
enum class AsrEventType { |
|||
/** 会话开始 */ |
|||
SESSION_STARTED, |
|||
|
|||
/** 会话结束 */ |
|||
SESSION_STOPPED, |
|||
|
|||
|
|||
/** 识别中(中间结果) */ |
|||
RECOGNIZING, |
|||
|
|||
/** 识别完成(最终结果) */ |
|||
RECOGNIZED, |
|||
|
|||
/** 识别取消 */ |
|||
CANCELED, |
|||
|
|||
/** 发生错误 */ |
|||
ERROR, |
|||
|
|||
} |
|||
|
|||
/** |
|||
* ASR事件数据类 |
|||
* |
|||
* @param type 事件类型 |
|||
* @param params 事件参数,根据不同的事件类型包含不同的内容: |
|||
* - ERROR: 包含 "errorCode" 和 "errorMessage" |
|||
* - CANCELED: 包含 "reason" 和 "errorDetails" |
|||
* - RECOGNIZING/RECOGNIZED: 包含 "text", "languageCode" |
|||
*/ |
|||
data class AsrEvent( |
|||
val type: AsrEventType, |
|||
val params: Map<String, Any> = emptyMap() |
|||
) |
|||
|
|||
/** |
|||
* ASR事件监听接口 |
|||
*/ |
|||
interface AsrEventListener { |
|||
/** |
|||
* 当发生ASR事件时调用 |
|||
* |
|||
* @param event ASR事件 |
|||
*/ |
|||
fun onEvent(event: AsrEvent) |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
package com.deep_voice.speech.asr |
|||
|
|||
/** |
|||
* ASR服务通用接口 |
|||
* |
|||
* 定义了语音识别服务的标准接口,可用于不同提供商的实现,如火山引擎、Azure等 |
|||
*/ |
|||
interface IAsrService { |
|||
/** |
|||
* 初始化语音识别服务 |
|||
* |
|||
* @param subscriptionKey 服务订阅密钥/访问令牌 |
|||
* @param region 服务区域/应用ID |
|||
* @param supportedLanguages 支持的语言列表,如["zh-CN", "en-US"] |
|||
* @return 是否初始化成功 |
|||
*/ |
|||
suspend fun initialize( |
|||
subscriptionKey: String, |
|||
region: String, |
|||
supportedLanguages: List<String>? = null |
|||
): Boolean |
|||
|
|||
/** |
|||
* 单次识别(非流式) |
|||
* |
|||
* @param audioData 要识别的音频数据字节数组 |
|||
* @param callback 识别结果回调 |
|||
* @return 是否成功开始识别 |
|||
*/ |
|||
suspend fun recognizeOnce(audioData: ByteArray, callback: (String) -> Unit): Boolean |
|||
|
|||
/** |
|||
* 启动连续识别 |
|||
* 开始持续监听音频输入并识别 |
|||
* |
|||
* @return 是否成功启动 |
|||
*/ |
|||
suspend fun startContinuousRecognition(): Boolean |
|||
|
|||
/** |
|||
* 停止连续识别 |
|||
* |
|||
* @return 是否成功停止 |
|||
*/ |
|||
suspend fun stopContinuousRecognition(): Boolean |
|||
|
|||
/** |
|||
* 推送音频数据 |
|||
* 在连续识别模式下推送新的音频数据 |
|||
* |
|||
* @param audioData 要识别的音频数据字节数组 |
|||
* @return 是否成功处理 |
|||
*/ |
|||
suspend fun pushAudioData(audioData: ByteArray): Boolean |
|||
|
|||
/** |
|||
* 添加ASR事件监听器 |
|||
* |
|||
* @param listener 事件监听器 |
|||
*/ |
|||
fun addListener(listener: AsrEventListener) |
|||
|
|||
/** |
|||
* 移除ASR事件监听器 |
|||
* |
|||
* @param listener 要移除的事件监听器 |
|||
*/ |
|||
fun removeListener(listener: AsrEventListener) |
|||
|
|||
/** |
|||
* 释放资源 |
|||
* 在不再需要服务时调用,释放底层资源 |
|||
*/ |
|||
fun release() |
|||
|
|||
/** |
|||
* 判断当前是否正在识别 |
|||
* |
|||
* @return 是否正在进行语音识别 |
|||
*/ |
|||
fun isRecognizing(): Boolean |
|||
|
|||
/** |
|||
* 设置识别参数 |
|||
* |
|||
* @param params 参数键值对 |
|||
* @return 是否设置成功 |
|||
*/ |
|||
fun setParameters(params: Map<String, Any>): Boolean |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
package com.deep_voice.speech.tts |
|||
|
|||
/** |
|||
* TTS服务通用接口 |
|||
* |
|||
* 定义了语音合成服务的标准接口,可用于不同提供商的实现,如微软Azure、火山语音等 |
|||
*/ |
|||
interface ITtsService { |
|||
/** |
|||
* 初始化语音合成服务 |
|||
* |
|||
* @param ttsAppId 服务应用ID |
|||
* @param ttsAppToken 服务访问令牌/订阅密钥 |
|||
* @param ttsResource 服务资源ID/区域(可选) |
|||
* @param language 语言代码,如"zh-CN" |
|||
* @return 是否初始化成功 |
|||
*/ |
|||
suspend fun initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String = "", language: String = "zh-CN"): Boolean |
|||
|
|||
/** |
|||
* 设置语音角色 |
|||
* |
|||
* @param voiceName 语音角色名称(不同服务的语音角色命名可能不同) |
|||
* @return 是否设置成功 |
|||
*/ |
|||
fun setVoice(voiceName: String): Boolean |
|||
|
|||
/** |
|||
* 单次播放文本(非流式) |
|||
* |
|||
* @param text 要合成的文本 |
|||
* @return 是否成功开始合成 |
|||
*/ |
|||
suspend fun speakOnce(text: String): Boolean |
|||
|
|||
/** |
|||
* 流式合成 |
|||
* 在流式场景下合成并播放文本,适用于连续的文本输入 |
|||
* |
|||
* @param text 要合成的文本片段 |
|||
* @return 是否成功处理 |
|||
*/ |
|||
suspend fun speakStream(text: String): Boolean |
|||
|
|||
/** |
|||
* 刷新并播放流式文本缓冲区中的剩余内容 |
|||
* |
|||
* @return 是否成功处理 |
|||
*/ |
|||
suspend fun flushStream(): Boolean |
|||
|
|||
/** |
|||
* 停止当前合成和播放 |
|||
* |
|||
* @return 是否成功停止 |
|||
*/ |
|||
suspend fun stop(): Boolean |
|||
|
|||
/** |
|||
* 释放资源 |
|||
* 在不再需要服务时调用,释放底层资源 |
|||
*/ |
|||
fun release() |
|||
|
|||
/** |
|||
* 添加TTS事件监听器 |
|||
* |
|||
* @param listener 事件监听器 |
|||
*/ |
|||
fun addListener(listener: TtsEventListener) |
|||
|
|||
/** |
|||
* 移除TTS事件监听器 |
|||
* |
|||
* @param listener 要移除的事件监听器 |
|||
*/ |
|||
fun removeListener(listener: TtsEventListener) |
|||
|
|||
/** |
|||
* 添加音频数据监听器 |
|||
* |
|||
* @param listener 音频数据监听器 |
|||
*/ |
|||
fun addAudioDataListener(listener: AudioDataListener) |
|||
|
|||
/** |
|||
* 移除音频数据监听器 |
|||
* |
|||
* @param listener 要移除的音频数据监听器 |
|||
*/ |
|||
fun removeAudioDataListener(listener: AudioDataListener) |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
package com.deep_voice.speech.tts |
|||
|
|||
/** |
|||
* TTS事件类型枚举 |
|||
*/ |
|||
enum class TtsEventType { |
|||
/** 开始合成 */ |
|||
SYNTHESIS_STARTED, |
|||
|
|||
/** 合成完成 */ |
|||
SYNTHESIS_COMPLETED, |
|||
|
|||
/** 合成取消 */ |
|||
SYNTHESIS_CANCELED, |
|||
|
|||
/** 开始播放 */ |
|||
STARTED, |
|||
|
|||
/** 播放完成 */ |
|||
COMPLETED, |
|||
|
|||
/** 播放取消 */ |
|||
CANCELED, |
|||
|
|||
/** 发生错误 */ |
|||
ERROR |
|||
} |
|||
|
|||
/** |
|||
* TTS事件数据类 |
|||
* |
|||
* @param type 事件类型 |
|||
* @param params 事件参数,根据不同的事件类型包含不同的内容 |
|||
* 错误事件通常包含 "errorCode" 和 "errorMessage" |
|||
*/ |
|||
data class TtsEvent( |
|||
val type: TtsEventType, |
|||
val params: Map<String, Any> = emptyMap() |
|||
) |
|||
|
|||
/** |
|||
* TTS事件监听接口 |
|||
*/ |
|||
interface TtsEventListener { |
|||
/** |
|||
* 当发生TTS事件时调用 |
|||
* |
|||
* @param event TTS事件 |
|||
*/ |
|||
fun onEvent(event: TtsEvent) |
|||
} |
|||
|
|||
/** |
|||
* 音频数据监听接口 |
|||
*/ |
|||
interface AudioDataListener { |
|||
/** |
|||
* 当接收到音频数据时调用 |
|||
* |
|||
* @param data 音频数据字节数组 |
|||
*/ |
|||
fun onAudioData(data: ByteArray) |
|||
} |
|||
Loading…
Reference in new issue