Compare commits
20 Commits
5c9a8ea6e6
...
ae3a951950
| Author | SHA1 | Date |
|---|---|---|
|
|
ae3a951950 | |
|
|
508244fac3 | |
|
|
75080d0c0d | |
|
|
e961996ec6 | |
|
|
aba8b44ab8 | |
|
|
ac234d99ec | |
|
|
3e8fe73e27 | |
|
|
6146508bed | |
|
|
0af37c5b87 | |
|
|
ed51fa89bd | |
|
|
d1ab67e60e | |
|
|
d63124203b | |
|
|
ea32370fcc | |
|
|
36fa796c10 | |
|
|
c47ad5c435 | |
|
|
d18c3fc607 | |
|
|
9053132874 | |
|
|
58056c6c09 | |
|
|
80d99d6bdd | |
|
|
ae1ec191a8 |
|
|
@ -1,31 +0,0 @@
|
||||||
Extension Discovery Cache
|
|
||||||
=========================
|
|
||||||
|
|
||||||
This folder is used by `package:extension_discovery` to cache lists of
|
|
||||||
packages that contains extensions for other packages.
|
|
||||||
|
|
||||||
DO NOT USE THIS FOLDER
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
* Do not read (or rely) the contents of this folder.
|
|
||||||
* Do write to this folder.
|
|
||||||
|
|
||||||
If you're interested in the lists of extensions stored in this folder use the
|
|
||||||
API offered by package `extension_discovery` to get this information.
|
|
||||||
|
|
||||||
If this package doesn't work for your use-case, then don't try to read the
|
|
||||||
contents of this folder. It may change, and will not remain stable.
|
|
||||||
|
|
||||||
Use package `extension_discovery`
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
If you want to access information from this folder.
|
|
||||||
|
|
||||||
Feel free to delete this folder
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Files in this folder act as a cache, and the cache is discarded if the files
|
|
||||||
are older than the modification time of `.dart_tool/package_config.json`.
|
|
||||||
|
|
||||||
Hence, it should never be necessary to clear this cache manually, if you find a
|
|
||||||
need to do please file a bug.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"version":2,"entries":[{"package":"yx_asr","rootUri":"../","packageUri":"lib/"}]}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"version":2,"entries":[{"package":"yx_asr","rootUri":"../","packageUri":"lib/"}]}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
// This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`.
|
|
||||||
//
|
|
||||||
|
|
||||||
// @dart = 3.0
|
|
||||||
|
|
||||||
import 'dart:io'; // flutter_ignore: dart_io_import.
|
|
||||||
import 'package:record_linux/record_linux.dart';
|
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
|
||||||
class _PluginRegistrant {
|
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
|
||||||
static void register() {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
} else if (Platform.isIOS) {
|
|
||||||
} else if (Platform.isLinux) {
|
|
||||||
try {
|
|
||||||
RecordLinux.registerWith();
|
|
||||||
} catch (err) {
|
|
||||||
print(
|
|
||||||
'`record_linux` threw an error: $err. '
|
|
||||||
'The app may not function as expected until you remove this plugin from pubspec.yaml'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (Platform.isMacOS) {
|
|
||||||
} else if (Platform.isWindows) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -252,8 +252,8 @@ file:///Users/max/.pub-cache/hosted/pub.flutter-io.cn/xdg_directories-1.1.0/
|
||||||
file:///Users/max/.pub-cache/hosted/pub.flutter-io.cn/xdg_directories-1.1.0/lib/
|
file:///Users/max/.pub-cache/hosted/pub.flutter-io.cn/xdg_directories-1.1.0/lib/
|
||||||
yx_asr
|
yx_asr
|
||||||
3.0
|
3.0
|
||||||
file:///Users/max/SourceCode/yuanxuan/yx_asr/
|
file:///Users/max/SourceCode/yuanxuan/yx_speech_to_text_flutter/
|
||||||
file:///Users/max/SourceCode/yuanxuan/yx_asr/lib/
|
file:///Users/max/SourceCode/yuanxuan/yx_speech_to_text_flutter/lib/
|
||||||
sky_engine
|
sky_engine
|
||||||
3.7
|
3.7
|
||||||
file:///Users/max/fvm/versions/3.32.0/bin/cache/pkg/sky_engine/
|
file:///Users/max/fvm/versions/3.32.0/bin/cache/pkg/sky_engine/
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,18 @@
|
||||||
"vm_service"
|
"vm_service"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "path_provider",
|
||||||
|
"version": "2.1.5",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"path_provider_android",
|
||||||
|
"path_provider_foundation",
|
||||||
|
"path_provider_linux",
|
||||||
|
"path_provider_platform_interface",
|
||||||
|
"path_provider_windows"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "record",
|
"name": "record",
|
||||||
"version": "6.1.1",
|
"version": "6.1.1",
|
||||||
|
|
@ -361,6 +373,52 @@
|
||||||
"webdriver"
|
"webdriver"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "path_provider_windows",
|
||||||
|
"version": "2.3.0",
|
||||||
|
"dependencies": [
|
||||||
|
"ffi",
|
||||||
|
"flutter",
|
||||||
|
"path",
|
||||||
|
"path_provider_platform_interface"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path_provider_platform_interface",
|
||||||
|
"version": "2.1.2",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"platform",
|
||||||
|
"plugin_platform_interface"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path_provider_linux",
|
||||||
|
"version": "2.2.1",
|
||||||
|
"dependencies": [
|
||||||
|
"ffi",
|
||||||
|
"flutter",
|
||||||
|
"path",
|
||||||
|
"path_provider_platform_interface",
|
||||||
|
"xdg_directories"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path_provider_foundation",
|
||||||
|
"version": "2.4.2",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"path_provider_platform_interface"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path_provider_android",
|
||||||
|
"version": "2.2.17",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"path_provider_platform_interface"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "record_macos",
|
"name": "record_macos",
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
|
@ -543,6 +601,21 @@
|
||||||
"vm_service"
|
"vm_service"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "plugin_platform_interface",
|
||||||
|
"version": "2.1.8",
|
||||||
|
"dependencies": [
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xdg_directories",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"dependencies": [
|
||||||
|
"meta",
|
||||||
|
"path"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
|
@ -560,13 +633,6 @@
|
||||||
"vector_math"
|
"vector_math"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "plugin_platform_interface",
|
|
||||||
"version": "2.1.8",
|
|
||||||
"dependencies": [
|
|
||||||
"meta"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "fixnum",
|
"name": "fixnum",
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
|
@ -590,72 +656,6 @@
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"collection"
|
"collection"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path_provider",
|
|
||||||
"version": "2.1.5",
|
|
||||||
"dependencies": [
|
|
||||||
"flutter",
|
|
||||||
"path_provider_android",
|
|
||||||
"path_provider_foundation",
|
|
||||||
"path_provider_linux",
|
|
||||||
"path_provider_platform_interface",
|
|
||||||
"path_provider_windows"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path_provider_linux",
|
|
||||||
"version": "2.2.1",
|
|
||||||
"dependencies": [
|
|
||||||
"ffi",
|
|
||||||
"flutter",
|
|
||||||
"path",
|
|
||||||
"path_provider_platform_interface",
|
|
||||||
"xdg_directories"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path_provider_windows",
|
|
||||||
"version": "2.3.0",
|
|
||||||
"dependencies": [
|
|
||||||
"ffi",
|
|
||||||
"flutter",
|
|
||||||
"path",
|
|
||||||
"path_provider_platform_interface"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path_provider_platform_interface",
|
|
||||||
"version": "2.1.2",
|
|
||||||
"dependencies": [
|
|
||||||
"flutter",
|
|
||||||
"platform",
|
|
||||||
"plugin_platform_interface"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path_provider_foundation",
|
|
||||||
"version": "2.4.2",
|
|
||||||
"dependencies": [
|
|
||||||
"flutter",
|
|
||||||
"path_provider_platform_interface"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "xdg_directories",
|
|
||||||
"version": "1.1.0",
|
|
||||||
"dependencies": [
|
|
||||||
"meta",
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path_provider_android",
|
|
||||||
"version": "2.2.17",
|
|
||||||
"dependencies": [
|
|
||||||
"flutter",
|
|
||||||
"path_provider_platform_interface"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configVersion": 1
|
"configVersion": 1
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -2,14 +2,14 @@ group 'com.yuanxuan.yx_asr'
|
||||||
version '1.0.0'
|
version '1.0.0'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
ext.kotlin_version = '1.8.22'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.yuanxuan.yx_asr'
|
||||||
compileSdkVersion 33
|
compileSdkVersion 33
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,9 @@ class YxAsrPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegist
|
||||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"isAvailable" -> {
|
"isAvailable" -> {
|
||||||
result.success(SpeechRecognizer.isRecognitionAvailable(context))
|
context?.let {
|
||||||
|
result.success(SpeechRecognizer.isRecognitionAvailable(it))
|
||||||
|
} ?: result.success(false)
|
||||||
}
|
}
|
||||||
"hasPermission" -> {
|
"hasPermission" -> {
|
||||||
result.success(hasPermission())
|
result.success(hasPermission())
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
{"buildConfigurations":[{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e987f5b3155b7b26c88d68696c21f46cc3e","buildSettings":{"CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/record_ios","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"record_ios","INFOPLIST_FILE":"Target Support Files/record_ios/ResourceBundle-record_ios_privacy-record_ios-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","ONLY_ACTIVE_ARCH":"NO","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"record_ios_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e986f06ec316eb44a4c4da4527cd08c3a8f","name":"Debug"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e98bfb1d663e62fa3d6f40293efaffb810f","buildSettings":{"CLANG_ENABLE_OBJC_WEAK":"NO","CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/record_ios","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"record_ios","INFOPLIST_FILE":"Target Support Files/record_ios/ResourceBundle-record_ios_privacy-record_ios-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"record_ios_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","VALIDATE_PRODUCT":"YES","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e98d3a7bc21be11eea36650c14a981a4a21","name":"Profile"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e98bfb1d663e62fa3d6f40293efaffb810f","buildSettings":{"CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/record_ios","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"record_ios","INFOPLIST_FILE":"Target Support Files/record_ios/ResourceBundle-record_ios_privacy-record_ios-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"record_ios_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e9893f6c2ca2ac7ceb884c507476cc3a3a1","name":"Release"}],"buildPhases":[{"buildFiles":[],"guid":"bfdfe7dc352907fc980b868725387e987eba38ad46157cacab6baf46e2c97b28","type":"com.apple.buildphase.sources"},{"buildFiles":[],"guid":"bfdfe7dc352907fc980b868725387e98585c515449a114c265106ae35ef78056","type":"com.apple.buildphase.frameworks"},{"buildFiles":[{"fileReference":"bfdfe7dc352907fc980b868725387e98b18105efd5bafedefc5647c8499985e6","guid":"bfdfe7dc352907fc980b868725387e98a02cab793f6ffe791c8a532f4724d833"}],"guid":"bfdfe7dc352907fc980b868725387e988ccaa3aab9ec360ff6e16378731b23fa","type":"com.apple.buildphase.resources"}],"buildRules":[],"dependencies":[],"guid":"bfdfe7dc352907fc980b868725387e982a2ee81fc4f9376a4b6bb6d6bb502a00","name":"record_ios-record_ios_privacy","productReference":{"guid":"bfdfe7dc352907fc980b868725387e98c34601a2dc07dcfea6b09ca49bc4da60","name":"record_ios_privacy.bundle","type":"product"},"productTypeIdentifier":"com.apple.product-type.bundle","provisioningSourceData":[{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Debug","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Profile","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Release","provisioningStyle":0}],"type":"standard"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"buildConfigurations":[{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e981397e4ca66b8bc1b6b787af196ee2a0c","buildSettings":{"CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/permission_handler_apple","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"permission_handler_apple","INFOPLIST_FILE":"Target Support Files/permission_handler_apple/ResourceBundle-permission_handler_apple_privacy-permission_handler_apple-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","ONLY_ACTIVE_ARCH":"NO","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"permission_handler_apple_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e98d769a3a3853c9249977754ea24f3b8bb","name":"Debug"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e98201acde82baf32765e719536c1fcd415","buildSettings":{"CLANG_ENABLE_OBJC_WEAK":"NO","CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/permission_handler_apple","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"permission_handler_apple","INFOPLIST_FILE":"Target Support Files/permission_handler_apple/ResourceBundle-permission_handler_apple_privacy-permission_handler_apple-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"permission_handler_apple_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","VALIDATE_PRODUCT":"YES","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e989a076473f270b5d52145183a44aba102","name":"Profile"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e98201acde82baf32765e719536c1fcd415","buildSettings":{"CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/permission_handler_apple","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"permission_handler_apple","INFOPLIST_FILE":"Target Support Files/permission_handler_apple/ResourceBundle-permission_handler_apple_privacy-permission_handler_apple-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"permission_handler_apple_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e98682380182db58e4ded87336dc6e1d894","name":"Release"}],"buildPhases":[{"buildFiles":[],"guid":"bfdfe7dc352907fc980b868725387e98c26bdf2e10ce31dd6b04911f1aadd468","type":"com.apple.buildphase.sources"},{"buildFiles":[],"guid":"bfdfe7dc352907fc980b868725387e98cf73538f434be7a8ded6885071e43bbb","type":"com.apple.buildphase.frameworks"},{"buildFiles":[{"fileReference":"bfdfe7dc352907fc980b868725387e981993b5887705d08bd566da0ef34911dc","guid":"bfdfe7dc352907fc980b868725387e9830ed596f4e641fbb73e74f191ee1d3f3"}],"guid":"bfdfe7dc352907fc980b868725387e98966a90ee2025d7afde1b9bd8d4be3432","type":"com.apple.buildphase.resources"}],"buildRules":[],"dependencies":[],"guid":"bfdfe7dc352907fc980b868725387e9802f35ab680609a626ebd2ddd692a3822","name":"permission_handler_apple-permission_handler_apple_privacy","productReference":{"guid":"bfdfe7dc352907fc980b868725387e983e9a904e8a35cb34b69458780be142b3","name":"permission_handler_apple_privacy.bundle","type":"product"},"productTypeIdentifier":"com.apple.product-type.bundle","provisioningSourceData":[{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Debug","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Profile","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Release","provisioningStyle":0}],"type":"standard"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"buildConfigurations":[{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e9881514ee24132a163c8de1269d399c3bc","buildSettings":{"CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/path_provider_foundation","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"path_provider_foundation","INFOPLIST_FILE":"Target Support Files/path_provider_foundation/ResourceBundle-path_provider_foundation_privacy-path_provider_foundation-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","ONLY_ACTIVE_ARCH":"NO","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"path_provider_foundation_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e980f1ae418d2bbf8ce0c0848ff9e9d99f1","name":"Debug"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e98aed8c561cc665a91c70adde791d8e192","buildSettings":{"CLANG_ENABLE_OBJC_WEAK":"NO","CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/path_provider_foundation","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"path_provider_foundation","INFOPLIST_FILE":"Target Support Files/path_provider_foundation/ResourceBundle-path_provider_foundation_privacy-path_provider_foundation-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"path_provider_foundation_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","VALIDATE_PRODUCT":"YES","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e98fbefabc0b715bbc6bf42d5c546a088eb","name":"Profile"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e98aed8c561cc665a91c70adde791d8e192","buildSettings":{"CODE_SIGNING_ALLOWED":"NO","CODE_SIGNING_IDENTITY":"-","CODE_SIGNING_REQUIRED":"NO","CONFIGURATION_BUILD_DIR":"$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/path_provider_foundation","ENABLE_BITCODE":"NO","EXPANDED_CODE_SIGN_IDENTITY":"-","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IBSC_MODULE":"path_provider_foundation","INFOPLIST_FILE":"Target Support Files/path_provider_foundation/ResourceBundle-path_provider_foundation_privacy-path_provider_foundation-Info.plist","IPHONEOS_DEPLOYMENT_TARGET":"13.0","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","PRODUCT_NAME":"path_provider_foundation_privacy","SDKROOT":"iphoneos","SKIP_INSTALL":"YES","TARGETED_DEVICE_FAMILY":"1,2","WRAPPER_EXTENSION":"bundle"},"guid":"bfdfe7dc352907fc980b868725387e98b6b192a2f6d1562adae81a957dd2959b","name":"Release"}],"buildPhases":[{"buildFiles":[],"guid":"bfdfe7dc352907fc980b868725387e98cb74bc54605fe2f3d0ea9f0289a8b241","type":"com.apple.buildphase.sources"},{"buildFiles":[],"guid":"bfdfe7dc352907fc980b868725387e9881eaa5d02bc8b82d2651fdd2f994e058","type":"com.apple.buildphase.frameworks"},{"buildFiles":[{"fileReference":"bfdfe7dc352907fc980b868725387e9817004907bf7c7d94734b13afa4b26c8b","guid":"bfdfe7dc352907fc980b868725387e985e64de23d5b31c171c50ec6da5267645"}],"guid":"bfdfe7dc352907fc980b868725387e98c72ecd8a391cdbdaacff87f6f0d76da8","type":"com.apple.buildphase.resources"}],"buildRules":[],"dependencies":[],"guid":"bfdfe7dc352907fc980b868725387e987ea64ee8d53085bf9edd1a57aaf8cbb5","name":"path_provider_foundation-path_provider_foundation_privacy","productReference":{"guid":"bfdfe7dc352907fc980b868725387e986e649604f74c414a7c2dbe5ef4cc4e75","name":"path_provider_foundation_privacy.bundle","type":"product"},"productTypeIdentifier":"com.apple.product-type.bundle","provisioningSourceData":[{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Debug","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Profile","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"${PRODUCT_BUNDLE_IDENTIFIER}","configurationName":"Release","provisioningStyle":0}],"type":"standard"}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
{"buildConfigurations":[{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e983b4389e41a26a4fc852e2feb08c32b31","buildSettings":{"ASSETCATALOG_COMPILER_APPICON_NAME":"AppIcon","ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME":"AccentColor","CLANG_ENABLE_OBJC_WEAK":"NO","ENABLE_BITCODE":"NO","ENABLE_USER_SCRIPT_SANDBOXING":"NO","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IPHONEOS_DEPLOYMENT_TARGET":"13.0","LD_RUNPATH_SEARCH_PATHS":"$(inherited) @executable_path/Frameworks","ONLY_ACTIVE_ARCH":"NO","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","SDKROOT":"iphoneos","TARGETED_DEVICE_FAMILY":"1,2"},"guid":"bfdfe7dc352907fc980b868725387e982cf0da236cf10d087750aa1434da9227","name":"Debug"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e980eb0dfc45486a9a195a5a19657d4bdde","buildSettings":{"ASSETCATALOG_COMPILER_APPICON_NAME":"AppIcon","ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME":"AccentColor","CLANG_ENABLE_OBJC_WEAK":"NO","ENABLE_BITCODE":"NO","ENABLE_USER_SCRIPT_SANDBOXING":"NO","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IPHONEOS_DEPLOYMENT_TARGET":"13.0","LD_RUNPATH_SEARCH_PATHS":"$(inherited) @executable_path/Frameworks","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","SDKROOT":"iphoneos","TARGETED_DEVICE_FAMILY":"1,2","VALIDATE_PRODUCT":"YES"},"guid":"bfdfe7dc352907fc980b868725387e98cc28f154213fd8181aa70d4c188a8335","name":"Profile"},{"baseConfigurationFileReference":"bfdfe7dc352907fc980b868725387e980eb0dfc45486a9a195a5a19657d4bdde","buildSettings":{"ASSETCATALOG_COMPILER_APPICON_NAME":"AppIcon","ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME":"AccentColor","CLANG_ENABLE_OBJC_WEAK":"NO","ENABLE_BITCODE":"NO","ENABLE_USER_SCRIPT_SANDBOXING":"NO","GCC_PREPROCESSOR_DEFINITIONS":"$(inherited) PERMISSION_MICROPHONE=1 PERMISSION_SPEECH_RECOGNIZER=1","IPHONEOS_DEPLOYMENT_TARGET":"13.0","LD_RUNPATH_SEARCH_PATHS":"$(inherited) @executable_path/Frameworks","OTHER_LDFLAGS":"$(inherited) -framework AudioToolbox -framework AVFoundation -framework Speech","SDKROOT":"iphoneos","TARGETED_DEVICE_FAMILY":"1,2","VALIDATE_PRODUCT":"YES"},"guid":"bfdfe7dc352907fc980b868725387e981f19fefc6e52ad9e4e005a2248234387","name":"Release"}],"buildPhases":[],"buildRules":[],"dependencies":[],"guid":"bfdfe7dc352907fc980b868725387e989da425bb6d6d5d8dbb95e4afffb82217","name":"Flutter","provisioningSourceData":[{"bundleIdentifierFromInfoPlist":"","configurationName":"Debug","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"","configurationName":"Profile","provisioningStyle":0},{"bundleIdentifierFromInfoPlist":"","configurationName":"Release","provisioningStyle":0}],"type":"aggregate"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/max/SourceCode/yuanxuan/yx_speech_to_text_flutter/example/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=7f6a4f64a6e2fd3720afe00c8dd0402f_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"format-version":[1,0,0],"native-assets":{}}
|
|
||||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
assets/models/README.md
assetassets/models/README.md.assets/models/decoder-epoch-99-avg-1.int8.onnx
asset.assets/models/decoder-epoch-99-avg-1.int8.onnx.assets/models/encoder-epoch-99-avg-1.int8.onnx
asset.assets/models/encoder-epoch-99-avg-1.int8.onnx-assets/models/joiner-epoch-99-avg-1.int8.onnx
asset-assets/models/joiner-epoch-99-avg-1.int8.onnxassets/models/tokens.txt
assetassets/models/tokens.txt7packages/record_web/assets/js/record.fixwebmduration.js
asset7packages/record_web/assets/js/record.fixwebmduration.js/packages/record_web/assets/js/record.worklet.js
asset/packages/record_web/assets/js/record.worklet.js
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"assets/models/README.md":["assets/models/README.md"],"assets/models/decoder-epoch-99-avg-1.int8.onnx":["assets/models/decoder-epoch-99-avg-1.int8.onnx"],"assets/models/encoder-epoch-99-avg-1.int8.onnx":["assets/models/encoder-epoch-99-avg-1.int8.onnx"],"assets/models/joiner-epoch-99-avg-1.int8.onnx":["assets/models/joiner-epoch-99-avg-1.int8.onnx"],"assets/models/tokens.txt":["assets/models/tokens.txt"],"packages/record_web/assets/js/record.fixwebmduration.js":["packages/record_web/assets/js/record.fixwebmduration.js"],"packages/record_web/assets/js/record.worklet.js":["packages/record_web/assets/js/record.worklet.js"]}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
[]
|
|
||||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
{"format-version":[1,0,0],"native-assets":{}}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
# 语音识别模型文件
|
|
||||||
|
|
||||||
这个目录包含了 sherpa_onnx 语音识别所需的模型文件。
|
|
||||||
|
|
||||||
## 模型结构
|
|
||||||
|
|
||||||
```
|
|
||||||
assets/models/
|
|
||||||
├── zh-cn/ # 中文模型
|
|
||||||
│ ├── encoder.onnx # 编码器模型
|
|
||||||
│ ├── decoder.onnx # 解码器模型
|
|
||||||
│ ├── joiner.onnx # 连接器模型
|
|
||||||
│ └── tokens.txt # 词汇表
|
|
||||||
├── en-us/ # 英文模型
|
|
||||||
│ ├── encoder.onnx
|
|
||||||
│ ├── decoder.onnx
|
|
||||||
│ ├── joiner.onnx
|
|
||||||
│ └── tokens.txt
|
|
||||||
└── multilingual/ # 多语言模型
|
|
||||||
├── encoder.onnx
|
|
||||||
├── decoder.onnx
|
|
||||||
├── joiner.onnx
|
|
||||||
└── tokens.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## 模型下载
|
|
||||||
|
|
||||||
由于模型文件较大,请从以下地址下载对应的模型文件:
|
|
||||||
|
|
||||||
### 中文模型 (推荐)
|
|
||||||
- 模型名称: sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20
|
|
||||||
- 下载地址: https://github.com/k2-fsa/sherpa-onnx/releases/
|
|
||||||
- 大小: ~40MB
|
|
||||||
|
|
||||||
### 英文模型
|
|
||||||
- 模型名称: sherpa-onnx-streaming-zipformer-en-2023-02-21
|
|
||||||
- 下载地址: https://github.com/k2-fsa/sherpa-onnx/releases/
|
|
||||||
- 大小: ~40MB
|
|
||||||
|
|
||||||
### 多语言模型
|
|
||||||
- 模型名称: sherpa-onnx-streaming-zipformer-multilingual-2023-02-20
|
|
||||||
- 下载地址: https://github.com/k2-fsa/sherpa-onnx/releases/
|
|
||||||
- 大小: ~60MB
|
|
||||||
|
|
||||||
## 使用说明
|
|
||||||
|
|
||||||
1. 下载对应的模型文件
|
|
||||||
2. 解压到对应的语言目录
|
|
||||||
3. 确保文件名和路径正确
|
|
||||||
4. 重新构建应用
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- 模型文件会增加应用包大小
|
|
||||||
- 建议根据需要只包含必要的语言模型
|
|
||||||
- 模型文件支持热更新,可以在运行时下载
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -1,507 +0,0 @@
|
||||||
(function (name, definition) {
|
|
||||||
window.jsFixWebmDuration = definition();
|
|
||||||
})('fix-webm-duration', function () {
|
|
||||||
/*
|
|
||||||
* This is the list of possible WEBM file sections by their IDs.
|
|
||||||
* Possible types: Container, Binary, Uint, Int, String, Float, Date
|
|
||||||
*/
|
|
||||||
var sections = {
|
|
||||||
0xa45dfa3: { name: 'EBML', type: 'Container' },
|
|
||||||
0x286: { name: 'EBMLVersion', type: 'Uint' },
|
|
||||||
0x2f7: { name: 'EBMLReadVersion', type: 'Uint' },
|
|
||||||
0x2f2: { name: 'EBMLMaxIDLength', type: 'Uint' },
|
|
||||||
0x2f3: { name: 'EBMLMaxSizeLength', type: 'Uint' },
|
|
||||||
0x282: { name: 'DocType', type: 'String' },
|
|
||||||
0x287: { name: 'DocTypeVersion', type: 'Uint' },
|
|
||||||
0x285: { name: 'DocTypeReadVersion', type: 'Uint' },
|
|
||||||
0x6c: { name: 'Void', type: 'Binary' },
|
|
||||||
0x3f: { name: 'CRC-32', type: 'Binary' },
|
|
||||||
0xb538667: { name: 'SignatureSlot', type: 'Container' },
|
|
||||||
0x3e8a: { name: 'SignatureAlgo', type: 'Uint' },
|
|
||||||
0x3e9a: { name: 'SignatureHash', type: 'Uint' },
|
|
||||||
0x3ea5: { name: 'SignaturePublicKey', type: 'Binary' },
|
|
||||||
0x3eb5: { name: 'Signature', type: 'Binary' },
|
|
||||||
0x3e5b: { name: 'SignatureElements', type: 'Container' },
|
|
||||||
0x3e7b: { name: 'SignatureElementList', type: 'Container' },
|
|
||||||
0x2532: { name: 'SignedElement', type: 'Binary' },
|
|
||||||
0x8538067: { name: 'Segment', type: 'Container' },
|
|
||||||
0x14d9b74: { name: 'SeekHead', type: 'Container' },
|
|
||||||
0xdbb: { name: 'Seek', type: 'Container' },
|
|
||||||
0x13ab: { name: 'SeekID', type: 'Binary' },
|
|
||||||
0x13ac: { name: 'SeekPosition', type: 'Uint' },
|
|
||||||
0x549a966: { name: 'Info', type: 'Container' },
|
|
||||||
0x33a4: { name: 'SegmentUID', type: 'Binary' },
|
|
||||||
0x3384: { name: 'SegmentFilename', type: 'String' },
|
|
||||||
0x1cb923: { name: 'PrevUID', type: 'Binary' },
|
|
||||||
0x1c83ab: { name: 'PrevFilename', type: 'String' },
|
|
||||||
0x1eb923: { name: 'NextUID', type: 'Binary' },
|
|
||||||
0x1e83bb: { name: 'NextFilename', type: 'String' },
|
|
||||||
0x444: { name: 'SegmentFamily', type: 'Binary' },
|
|
||||||
0x2924: { name: 'ChapterTranslate', type: 'Container' },
|
|
||||||
0x29fc: { name: 'ChapterTranslateEditionUID', type: 'Uint' },
|
|
||||||
0x29bf: { name: 'ChapterTranslateCodec', type: 'Uint' },
|
|
||||||
0x29a5: { name: 'ChapterTranslateID', type: 'Binary' },
|
|
||||||
0xad7b1: { name: 'TimecodeScale', type: 'Uint' },
|
|
||||||
0x489: { name: 'Duration', type: 'Float' },
|
|
||||||
0x461: { name: 'DateUTC', type: 'Date' },
|
|
||||||
0x3ba9: { name: 'Title', type: 'String' },
|
|
||||||
0xd80: { name: 'MuxingApp', type: 'String' },
|
|
||||||
0x1741: { name: 'WritingApp', type: 'String' },
|
|
||||||
// 0xf43b675: { name: 'Cluster', type: 'Container' },
|
|
||||||
0x67: { name: 'Timecode', type: 'Uint' },
|
|
||||||
0x1854: { name: 'SilentTracks', type: 'Container' },
|
|
||||||
0x18d7: { name: 'SilentTrackNumber', type: 'Uint' },
|
|
||||||
0x27: { name: 'Position', type: 'Uint' },
|
|
||||||
0x2b: { name: 'PrevSize', type: 'Uint' },
|
|
||||||
0x23: { name: 'SimpleBlock', type: 'Binary' },
|
|
||||||
0x20: { name: 'BlockGroup', type: 'Container' },
|
|
||||||
0x21: { name: 'Block', type: 'Binary' },
|
|
||||||
0x22: { name: 'BlockVirtual', type: 'Binary' },
|
|
||||||
0x35a1: { name: 'BlockAdditions', type: 'Container' },
|
|
||||||
0x26: { name: 'BlockMore', type: 'Container' },
|
|
||||||
0x6e: { name: 'BlockAddID', type: 'Uint' },
|
|
||||||
0x25: { name: 'BlockAdditional', type: 'Binary' },
|
|
||||||
0x1b: { name: 'BlockDuration', type: 'Uint' },
|
|
||||||
0x7a: { name: 'ReferencePriority', type: 'Uint' },
|
|
||||||
0x7b: { name: 'ReferenceBlock', type: 'Int' },
|
|
||||||
0x7d: { name: 'ReferenceVirtual', type: 'Int' },
|
|
||||||
0x24: { name: 'CodecState', type: 'Binary' },
|
|
||||||
0x35a2: { name: 'DiscardPadding', type: 'Int' },
|
|
||||||
0xe: { name: 'Slices', type: 'Container' },
|
|
||||||
0x68: { name: 'TimeSlice', type: 'Container' },
|
|
||||||
0x4c: { name: 'LaceNumber', type: 'Uint' },
|
|
||||||
0x4d: { name: 'FrameNumber', type: 'Uint' },
|
|
||||||
0x4b: { name: 'BlockAdditionID', type: 'Uint' },
|
|
||||||
0x4e: { name: 'Delay', type: 'Uint' },
|
|
||||||
0x4f: { name: 'SliceDuration', type: 'Uint' },
|
|
||||||
0x48: { name: 'ReferenceFrame', type: 'Container' },
|
|
||||||
0x49: { name: 'ReferenceOffset', type: 'Uint' },
|
|
||||||
0x4a: { name: 'ReferenceTimeCode', type: 'Uint' },
|
|
||||||
0x2f: { name: 'EncryptedBlock', type: 'Binary' },
|
|
||||||
0x654ae6b: { name: 'Tracks', type: 'Container' },
|
|
||||||
0x2e: { name: 'TrackEntry', type: 'Container' },
|
|
||||||
0x57: { name: 'TrackNumber', type: 'Uint' },
|
|
||||||
0x33c5: { name: 'TrackUID', type: 'Uint' },
|
|
||||||
0x3: { name: 'TrackType', type: 'Uint' },
|
|
||||||
0x39: { name: 'FlagEnabled', type: 'Uint' },
|
|
||||||
0x8: { name: 'FlagDefault', type: 'Uint' },
|
|
||||||
0x15aa: { name: 'FlagForced', type: 'Uint' },
|
|
||||||
0x1c: { name: 'FlagLacing', type: 'Uint' },
|
|
||||||
0x2de7: { name: 'MinCache', type: 'Uint' },
|
|
||||||
0x2df8: { name: 'MaxCache', type: 'Uint' },
|
|
||||||
0x3e383: { name: 'DefaultDuration', type: 'Uint' },
|
|
||||||
0x34e7a: { name: 'DefaultDecodedFieldDuration', type: 'Uint' },
|
|
||||||
0x3314f: { name: 'TrackTimecodeScale', type: 'Float' },
|
|
||||||
0x137f: { name: 'TrackOffset', type: 'Int' },
|
|
||||||
0x15ee: { name: 'MaxBlockAdditionID', type: 'Uint' },
|
|
||||||
0x136e: { name: 'Name', type: 'String' },
|
|
||||||
0x2b59c: { name: 'Language', type: 'String' },
|
|
||||||
0x6: { name: 'CodecID', type: 'String' },
|
|
||||||
0x23a2: { name: 'CodecPrivate', type: 'Binary' },
|
|
||||||
0x58688: { name: 'CodecName', type: 'String' },
|
|
||||||
0x3446: { name: 'AttachmentLink', type: 'Uint' },
|
|
||||||
0x1a9697: { name: 'CodecSettings', type: 'String' },
|
|
||||||
0x1b4040: { name: 'CodecInfoURL', type: 'String' },
|
|
||||||
0x6b240: { name: 'CodecDownloadURL', type: 'String' },
|
|
||||||
0x2a: { name: 'CodecDecodeAll', type: 'Uint' },
|
|
||||||
0x2fab: { name: 'TrackOverlay', type: 'Uint' },
|
|
||||||
0x16aa: { name: 'CodecDelay', type: 'Uint' },
|
|
||||||
0x16bb: { name: 'SeekPreRoll', type: 'Uint' },
|
|
||||||
0x2624: { name: 'TrackTranslate', type: 'Container' },
|
|
||||||
0x26fc: { name: 'TrackTranslateEditionUID', type: 'Uint' },
|
|
||||||
0x26bf: { name: 'TrackTranslateCodec', type: 'Uint' },
|
|
||||||
0x26a5: { name: 'TrackTranslateTrackID', type: 'Binary' },
|
|
||||||
0x60: { name: 'Video', type: 'Container' },
|
|
||||||
0x1a: { name: 'FlagInterlaced', type: 'Uint' },
|
|
||||||
0x13b8: { name: 'StereoMode', type: 'Uint' },
|
|
||||||
0x13c0: { name: 'AlphaMode', type: 'Uint' },
|
|
||||||
0x13b9: { name: 'OldStereoMode', type: 'Uint' },
|
|
||||||
0x30: { name: 'PixelWidth', type: 'Uint' },
|
|
||||||
0x3a: { name: 'PixelHeight', type: 'Uint' },
|
|
||||||
0x14aa: { name: 'PixelCropBottom', type: 'Uint' },
|
|
||||||
0x14bb: { name: 'PixelCropTop', type: 'Uint' },
|
|
||||||
0x14cc: { name: 'PixelCropLeft', type: 'Uint' },
|
|
||||||
0x14dd: { name: 'PixelCropRight', type: 'Uint' },
|
|
||||||
0x14b0: { name: 'DisplayWidth', type: 'Uint' },
|
|
||||||
0x14ba: { name: 'DisplayHeight', type: 'Uint' },
|
|
||||||
0x14b2: { name: 'DisplayUnit', type: 'Uint' },
|
|
||||||
0x14b3: { name: 'AspectRatioType', type: 'Uint' },
|
|
||||||
0xeb524: { name: 'ColourSpace', type: 'Binary' },
|
|
||||||
0xfb523: { name: 'GammaValue', type: 'Float' },
|
|
||||||
0x383e3: { name: 'FrameRate', type: 'Float' },
|
|
||||||
0x61: { name: 'Audio', type: 'Container' },
|
|
||||||
0x35: { name: 'SamplingFrequency', type: 'Float' },
|
|
||||||
0x38b5: { name: 'OutputSamplingFrequency', type: 'Float' },
|
|
||||||
0x1f: { name: 'Channels', type: 'Uint' },
|
|
||||||
0x3d7b: { name: 'ChannelPositions', type: 'Binary' },
|
|
||||||
0x2264: { name: 'BitDepth', type: 'Uint' },
|
|
||||||
0x62: { name: 'TrackOperation', type: 'Container' },
|
|
||||||
0x63: { name: 'TrackCombinePlanes', type: 'Container' },
|
|
||||||
0x64: { name: 'TrackPlane', type: 'Container' },
|
|
||||||
0x65: { name: 'TrackPlaneUID', type: 'Uint' },
|
|
||||||
0x66: { name: 'TrackPlaneType', type: 'Uint' },
|
|
||||||
0x69: { name: 'TrackJoinBlocks', type: 'Container' },
|
|
||||||
0x6d: { name: 'TrackJoinUID', type: 'Uint' },
|
|
||||||
0x40: { name: 'TrickTrackUID', type: 'Uint' },
|
|
||||||
0x41: { name: 'TrickTrackSegmentUID', type: 'Binary' },
|
|
||||||
0x46: { name: 'TrickTrackFlag', type: 'Uint' },
|
|
||||||
0x47: { name: 'TrickMasterTrackUID', type: 'Uint' },
|
|
||||||
0x44: { name: 'TrickMasterTrackSegmentUID', type: 'Binary' },
|
|
||||||
0x2d80: { name: 'ContentEncodings', type: 'Container' },
|
|
||||||
0x2240: { name: 'ContentEncoding', type: 'Container' },
|
|
||||||
0x1031: { name: 'ContentEncodingOrder', type: 'Uint' },
|
|
||||||
0x1032: { name: 'ContentEncodingScope', type: 'Uint' },
|
|
||||||
0x1033: { name: 'ContentEncodingType', type: 'Uint' },
|
|
||||||
0x1034: { name: 'ContentCompression', type: 'Container' },
|
|
||||||
0x254: { name: 'ContentCompAlgo', type: 'Uint' },
|
|
||||||
0x255: { name: 'ContentCompSettings', type: 'Binary' },
|
|
||||||
0x1035: { name: 'ContentEncryption', type: 'Container' },
|
|
||||||
0x7e1: { name: 'ContentEncAlgo', type: 'Uint' },
|
|
||||||
0x7e2: { name: 'ContentEncKeyID', type: 'Binary' },
|
|
||||||
0x7e3: { name: 'ContentSignature', type: 'Binary' },
|
|
||||||
0x7e4: { name: 'ContentSigKeyID', type: 'Binary' },
|
|
||||||
0x7e5: { name: 'ContentSigAlgo', type: 'Uint' },
|
|
||||||
0x7e6: { name: 'ContentSigHashAlgo', type: 'Uint' },
|
|
||||||
0xc53bb6b: { name: 'Cues', type: 'Container' },
|
|
||||||
0x3b: { name: 'CuePoint', type: 'Container' },
|
|
||||||
0x33: { name: 'CueTime', type: 'Uint' },
|
|
||||||
0x37: { name: 'CueTrackPositions', type: 'Container' },
|
|
||||||
0x77: { name: 'CueTrack', type: 'Uint' },
|
|
||||||
0x71: { name: 'CueClusterPosition', type: 'Uint' },
|
|
||||||
0x70: { name: 'CueRelativePosition', type: 'Uint' },
|
|
||||||
0x32: { name: 'CueDuration', type: 'Uint' },
|
|
||||||
0x1378: { name: 'CueBlockNumber', type: 'Uint' },
|
|
||||||
0x6a: { name: 'CueCodecState', type: 'Uint' },
|
|
||||||
0x5b: { name: 'CueReference', type: 'Container' },
|
|
||||||
0x16: { name: 'CueRefTime', type: 'Uint' },
|
|
||||||
0x17: { name: 'CueRefCluster', type: 'Uint' },
|
|
||||||
0x135f: { name: 'CueRefNumber', type: 'Uint' },
|
|
||||||
0x6b: { name: 'CueRefCodecState', type: 'Uint' },
|
|
||||||
0x941a469: { name: 'Attachments', type: 'Container' },
|
|
||||||
0x21a7: { name: 'AttachedFile', type: 'Container' },
|
|
||||||
0x67e: { name: 'FileDescription', type: 'String' },
|
|
||||||
0x66e: { name: 'FileName', type: 'String' },
|
|
||||||
0x660: { name: 'FileMimeType', type: 'String' },
|
|
||||||
0x65c: { name: 'FileData', type: 'Binary' },
|
|
||||||
0x6ae: { name: 'FileUID', type: 'Uint' },
|
|
||||||
0x675: { name: 'FileReferral', type: 'Binary' },
|
|
||||||
0x661: { name: 'FileUsedStartTime', type: 'Uint' },
|
|
||||||
0x662: { name: 'FileUsedEndTime', type: 'Uint' },
|
|
||||||
0x43a770: { name: 'Chapters', type: 'Container' },
|
|
||||||
0x5b9: { name: 'EditionEntry', type: 'Container' },
|
|
||||||
0x5bc: { name: 'EditionUID', type: 'Uint' },
|
|
||||||
0x5bd: { name: 'EditionFlagHidden', type: 'Uint' },
|
|
||||||
0x5db: { name: 'EditionFlagDefault', type: 'Uint' },
|
|
||||||
0x5dd: { name: 'EditionFlagOrdered', type: 'Uint' },
|
|
||||||
0x36: { name: 'ChapterAtom', type: 'Container' },
|
|
||||||
0x33c4: { name: 'ChapterUID', type: 'Uint' },
|
|
||||||
0x1654: { name: 'ChapterStringUID', type: 'String' },
|
|
||||||
0x11: { name: 'ChapterTimeStart', type: 'Uint' },
|
|
||||||
0x12: { name: 'ChapterTimeEnd', type: 'Uint' },
|
|
||||||
0x18: { name: 'ChapterFlagHidden', type: 'Uint' },
|
|
||||||
0x598: { name: 'ChapterFlagEnabled', type: 'Uint' },
|
|
||||||
0x2e67: { name: 'ChapterSegmentUID', type: 'Binary' },
|
|
||||||
0x2ebc: { name: 'ChapterSegmentEditionUID', type: 'Uint' },
|
|
||||||
0x23c3: { name: 'ChapterPhysicalEquiv', type: 'Uint' },
|
|
||||||
0xf: { name: 'ChapterTrack', type: 'Container' },
|
|
||||||
0x9: { name: 'ChapterTrackNumber', type: 'Uint' },
|
|
||||||
0x0: { name: 'ChapterDisplay', type: 'Container' },
|
|
||||||
0x5: { name: 'ChapString', type: 'String' },
|
|
||||||
0x37c: { name: 'ChapLanguage', type: 'String' },
|
|
||||||
0x37e: { name: 'ChapCountry', type: 'String' },
|
|
||||||
0x2944: { name: 'ChapProcess', type: 'Container' },
|
|
||||||
0x2955: { name: 'ChapProcessCodecID', type: 'Uint' },
|
|
||||||
0x50d: { name: 'ChapProcessPrivate', type: 'Binary' },
|
|
||||||
0x2911: { name: 'ChapProcessCommand', type: 'Container' },
|
|
||||||
0x2922: { name: 'ChapProcessTime', type: 'Uint' },
|
|
||||||
0x2933: { name: 'ChapProcessData', type: 'Binary' },
|
|
||||||
0x254c367: { name: 'Tags', type: 'Container' },
|
|
||||||
0x3373: { name: 'Tag', type: 'Container' },
|
|
||||||
0x23c0: { name: 'Targets', type: 'Container' },
|
|
||||||
0x28ca: { name: 'TargetTypeValue', type: 'Uint' },
|
|
||||||
0x23ca: { name: 'TargetType', type: 'String' },
|
|
||||||
0x23c5: { name: 'TagTrackUID', type: 'Uint' },
|
|
||||||
0x23c9: { name: 'TagEditionUID', type: 'Uint' },
|
|
||||||
0x23c4: { name: 'TagChapterUID', type: 'Uint' },
|
|
||||||
0x23c6: { name: 'TagAttachmentUID', type: 'Uint' },
|
|
||||||
0x27c8: { name: 'SimpleTag', type: 'Container' },
|
|
||||||
0x5a3: { name: 'TagName', type: 'String' },
|
|
||||||
0x47a: { name: 'TagLanguage', type: 'String' },
|
|
||||||
0x484: { name: 'TagDefault', type: 'Uint' },
|
|
||||||
0x487: { name: 'TagString', type: 'String' },
|
|
||||||
0x485: { name: 'TagBinary', type: 'Binary' }
|
|
||||||
};
|
|
||||||
|
|
||||||
function doInherit(newClass, baseClass) {
|
|
||||||
newClass.prototype = Object.create(baseClass.prototype);
|
|
||||||
newClass.prototype.constructor = newClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
function WebmBase(name, type) {
|
|
||||||
this.name = name || 'Unknown';
|
|
||||||
this.type = type || 'Unknown';
|
|
||||||
}
|
|
||||||
WebmBase.prototype.updateBySource = function () { };
|
|
||||||
WebmBase.prototype.setSource = function (source) {
|
|
||||||
this.source = source;
|
|
||||||
this.updateBySource();
|
|
||||||
};
|
|
||||||
WebmBase.prototype.updateByData = function () { };
|
|
||||||
WebmBase.prototype.setData = function (data) {
|
|
||||||
this.data = data;
|
|
||||||
this.updateByData();
|
|
||||||
};
|
|
||||||
|
|
||||||
function WebmUint(name, type) {
|
|
||||||
WebmBase.call(this, name, type || 'Uint');
|
|
||||||
}
|
|
||||||
doInherit(WebmUint, WebmBase);
|
|
||||||
function padHex(hex) {
|
|
||||||
return hex.length % 2 === 1 ? '0' + hex : hex;
|
|
||||||
}
|
|
||||||
WebmUint.prototype.updateBySource = function () {
|
|
||||||
// use hex representation of a number instead of number value
|
|
||||||
this.data = '';
|
|
||||||
for (var i = 0; i < this.source.length; i++) {
|
|
||||||
var hex = this.source[i].toString(16);
|
|
||||||
this.data += padHex(hex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
WebmUint.prototype.updateByData = function () {
|
|
||||||
var length = this.data.length / 2;
|
|
||||||
this.source = new Uint8Array(length);
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
var hex = this.data.substr(i * 2, 2);
|
|
||||||
this.source[i] = parseInt(hex, 16);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
WebmUint.prototype.getValue = function () {
|
|
||||||
return parseInt(this.data, 16);
|
|
||||||
};
|
|
||||||
WebmUint.prototype.setValue = function (value) {
|
|
||||||
this.setData(padHex(value.toString(16)));
|
|
||||||
};
|
|
||||||
|
|
||||||
function WebmFloat(name, type) {
|
|
||||||
WebmBase.call(this, name, type || 'Float');
|
|
||||||
}
|
|
||||||
doInherit(WebmFloat, WebmBase);
|
|
||||||
WebmFloat.prototype.getFloatArrayType = function () {
|
|
||||||
return this.source && this.source.length === 4 ? Float32Array : Float64Array;
|
|
||||||
};
|
|
||||||
WebmFloat.prototype.updateBySource = function () {
|
|
||||||
var byteArray = this.source.reverse();
|
|
||||||
var floatArrayType = this.getFloatArrayType();
|
|
||||||
var floatArray = new floatArrayType(byteArray.buffer);
|
|
||||||
this.data = floatArray[0];
|
|
||||||
};
|
|
||||||
WebmFloat.prototype.updateByData = function () {
|
|
||||||
var floatArrayType = this.getFloatArrayType();
|
|
||||||
var floatArray = new floatArrayType([this.data]);
|
|
||||||
var byteArray = new Uint8Array(floatArray.buffer);
|
|
||||||
this.source = byteArray.reverse();
|
|
||||||
};
|
|
||||||
WebmFloat.prototype.getValue = function () {
|
|
||||||
return this.data;
|
|
||||||
};
|
|
||||||
WebmFloat.prototype.setValue = function (value) {
|
|
||||||
this.setData(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function WebmContainer(name, type) {
|
|
||||||
WebmBase.call(this, name, type || 'Container');
|
|
||||||
}
|
|
||||||
doInherit(WebmContainer, WebmBase);
|
|
||||||
WebmContainer.prototype.readByte = function () {
|
|
||||||
return this.source[this.offset++];
|
|
||||||
};
|
|
||||||
WebmContainer.prototype.readUint = function () {
|
|
||||||
var firstByte = this.readByte();
|
|
||||||
var bytes = 8 - firstByte.toString(2).length;
|
|
||||||
var value = firstByte - (1 << (7 - bytes));
|
|
||||||
for (var i = 0; i < bytes; i++) {
|
|
||||||
// don't use bit operators to support x86
|
|
||||||
value *= 256;
|
|
||||||
value += this.readByte();
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
WebmContainer.prototype.updateBySource = function () {
|
|
||||||
this.data = [];
|
|
||||||
for (this.offset = 0; this.offset < this.source.length; this.offset = end) {
|
|
||||||
var id = this.readUint();
|
|
||||||
var len = this.readUint();
|
|
||||||
var end = Math.min(this.offset + len, this.source.length);
|
|
||||||
var data = this.source.slice(this.offset, end);
|
|
||||||
|
|
||||||
var info = sections[id] || { name: 'Unknown', type: 'Unknown' };
|
|
||||||
var ctr = WebmBase;
|
|
||||||
switch (info.type) {
|
|
||||||
case 'Container':
|
|
||||||
ctr = WebmContainer;
|
|
||||||
break;
|
|
||||||
case 'Uint':
|
|
||||||
ctr = WebmUint;
|
|
||||||
break;
|
|
||||||
case 'Float':
|
|
||||||
ctr = WebmFloat;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var section = new ctr(info.name, info.type);
|
|
||||||
section.setSource(data);
|
|
||||||
this.data.push({
|
|
||||||
id: id,
|
|
||||||
idHex: id.toString(16),
|
|
||||||
data: section
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
WebmContainer.prototype.writeUint = function (x, draft) {
|
|
||||||
for (var bytes = 1, flag = 0x80; x >= flag && bytes < 8; bytes++, flag *= 0x80) { }
|
|
||||||
|
|
||||||
if (!draft) {
|
|
||||||
var value = flag + x;
|
|
||||||
for (var i = bytes - 1; i >= 0; i--) {
|
|
||||||
// don't use bit operators to support x86
|
|
||||||
var c = value % 256;
|
|
||||||
this.source[this.offset + i] = c;
|
|
||||||
value = (value - c) / 256;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.offset += bytes;
|
|
||||||
};
|
|
||||||
WebmContainer.prototype.writeSections = function (draft) {
|
|
||||||
this.offset = 0;
|
|
||||||
for (var i = 0; i < this.data.length; i++) {
|
|
||||||
var section = this.data[i],
|
|
||||||
content = section.data.source,
|
|
||||||
contentLength = content.length;
|
|
||||||
this.writeUint(section.id, draft);
|
|
||||||
this.writeUint(contentLength, draft);
|
|
||||||
if (!draft) {
|
|
||||||
this.source.set(content, this.offset);
|
|
||||||
}
|
|
||||||
this.offset += contentLength;
|
|
||||||
}
|
|
||||||
return this.offset;
|
|
||||||
};
|
|
||||||
WebmContainer.prototype.updateByData = function () {
|
|
||||||
// run without accessing this.source to determine total length - need to know it to create Uint8Array
|
|
||||||
var length = this.writeSections('draft');
|
|
||||||
this.source = new Uint8Array(length);
|
|
||||||
// now really write data
|
|
||||||
this.writeSections();
|
|
||||||
};
|
|
||||||
WebmContainer.prototype.getSectionById = function (id) {
|
|
||||||
for (var i = 0; i < this.data.length; i++) {
|
|
||||||
var section = this.data[i];
|
|
||||||
if (section.id === id) {
|
|
||||||
return section.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function WebmFile(source) {
|
|
||||||
WebmContainer.call(this, 'File', 'File');
|
|
||||||
this.setSource(source);
|
|
||||||
}
|
|
||||||
doInherit(WebmFile, WebmContainer);
|
|
||||||
WebmFile.prototype.fixDuration = function (duration, options) {
|
|
||||||
var logger = options && options.logger;
|
|
||||||
if (logger === undefined) {
|
|
||||||
logger = function (message) {
|
|
||||||
console.log(message);
|
|
||||||
};
|
|
||||||
} else if (!logger) {
|
|
||||||
logger = function () { };
|
|
||||||
}
|
|
||||||
|
|
||||||
var segmentSection = this.getSectionById(0x8538067);
|
|
||||||
if (!segmentSection) {
|
|
||||||
logger('[fix-webm-duration] Segment section is missing');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var infoSection = segmentSection.getSectionById(0x549a966);
|
|
||||||
if (!infoSection) {
|
|
||||||
logger('[fix-webm-duration] Info section is missing');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeScaleSection = infoSection.getSectionById(0xad7b1);
|
|
||||||
if (!timeScaleSection) {
|
|
||||||
logger('[fix-webm-duration] TimecodeScale section is missing');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var durationSection = infoSection.getSectionById(0x489);
|
|
||||||
if (durationSection) {
|
|
||||||
if (durationSection.getValue() <= 0) {
|
|
||||||
logger('[fix-webm-duration] Duration section is present, but the value is empty. Applying ' + duration.toLocaleString() + ' ms.');
|
|
||||||
durationSection.setValue(duration);
|
|
||||||
} else {
|
|
||||||
logger('[fix-webm-duration] Duration section is present');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger('[fix-webm-duration] Duration section is missing. Applying ' + duration.toLocaleString() + ' ms.');
|
|
||||||
// append Duration section
|
|
||||||
durationSection = new WebmFloat('Duration', 'Float');
|
|
||||||
durationSection.setValue(duration);
|
|
||||||
infoSection.data.push({
|
|
||||||
id: 0x489,
|
|
||||||
data: durationSection
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// set default time scale to 1 millisecond (1000000 nanoseconds)
|
|
||||||
timeScaleSection.setValue(1000000);
|
|
||||||
infoSection.updateByData();
|
|
||||||
segmentSection.updateByData();
|
|
||||||
this.updateByData();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
WebmFile.prototype.toBlob = function (mimeType) {
|
|
||||||
return new Blob([this.source.buffer], { type: mimeType || 'audio/webm' });
|
|
||||||
};
|
|
||||||
|
|
||||||
function fixWebmDuration(blob, duration, callback, options) {
|
|
||||||
// The callback may be omitted - then the third argument is options
|
|
||||||
if (typeof callback === "object") {
|
|
||||||
options = callback;
|
|
||||||
callback = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
fixWebmDuration(blob, duration, resolve, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onloadend = function () {
|
|
||||||
try {
|
|
||||||
var file = new WebmFile(new Uint8Array(reader.result));
|
|
||||||
if (file.fixDuration(duration, options)) {
|
|
||||||
blob = file.toBlob(blob.type);
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
callback(blob);
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(blob);
|
|
||||||
} catch (ex) {
|
|
||||||
callback(blob);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support AMD import default
|
|
||||||
fixWebmDuration.default = fixWebmDuration;
|
|
||||||
|
|
||||||
return fixWebmDuration;
|
|
||||||
});
|
|
||||||
|
|
@ -1,407 +0,0 @@
|
||||||
class RecorderProcessor extends AudioWorkletProcessor {
|
|
||||||
static get parameterDescriptors() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'numChannels',
|
|
||||||
defaultValue: 1,
|
|
||||||
minValue: 1,
|
|
||||||
maxValue: 16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sampleRate',
|
|
||||||
defaultValue: 48000,
|
|
||||||
minValue: 8000,
|
|
||||||
maxValue: 96000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'streamBufferSize',
|
|
||||||
defaultValue: 2048,
|
|
||||||
minValue: 256,
|
|
||||||
maxValue: 8192
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer size compromise between size and process call frequency
|
|
||||||
_bufferSize = 2048
|
|
||||||
// The current buffer fill level
|
|
||||||
_bytesWritten = 0
|
|
||||||
// Buffer per channel
|
|
||||||
_buffers = []
|
|
||||||
// Resampler (passthrough, down or up)
|
|
||||||
_resampler = null
|
|
||||||
// Config
|
|
||||||
_numChannels = 1
|
|
||||||
_sampleRate = 48000
|
|
||||||
|
|
||||||
constructor(options) {
|
|
||||||
super(options)
|
|
||||||
|
|
||||||
this._numChannels = options.parameterData.numChannels
|
|
||||||
this._sampleRate = options.parameterData.sampleRate
|
|
||||||
this._bufferSize = options.parameterData.streamBufferSize
|
|
||||||
|
|
||||||
// Resampler(current context sample rate, desired sample rate, num channels, buffer size)
|
|
||||||
// num channels is always 1 since we resample after interleaving channels
|
|
||||||
this._resampler = new Resampler(sampleRate, this._sampleRate, 1, this._bufferSize * this._numChannels)
|
|
||||||
|
|
||||||
this.initBuffers()
|
|
||||||
}
|
|
||||||
|
|
||||||
initBuffers() {
|
|
||||||
this._bytesWritten = 0
|
|
||||||
this._buffers = []
|
|
||||||
|
|
||||||
for (let channel = 0; channel < this._numChannels; channel++) {
|
|
||||||
this._buffers[channel] = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isBufferEmpty() {
|
|
||||||
return this._bytesWritten === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isBufferFull() {
|
|
||||||
return this._bytesWritten >= this._bufferSize
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Float32Array[][]} inputs
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
process(inputs) {
|
|
||||||
if (this.isBufferFull()) {
|
|
||||||
this.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
const input = inputs[0]
|
|
||||||
|
|
||||||
if (input.length == 0) {
|
|
||||||
// Sometimes, Firefox doesn't give any input. Skip this frame to not fail.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let channel = 0; channel < this._numChannels; channel++) {
|
|
||||||
// Push a copy of the array.
|
|
||||||
// The underlying implementation may reuse it which will break the recording.
|
|
||||||
this._buffers[channel].push([...input[channel % input.length]])
|
|
||||||
}
|
|
||||||
|
|
||||||
this._bytesWritten += input[0].length
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
flush() {
|
|
||||||
let channels = []
|
|
||||||
for (let channel = 0; channel < this._numChannels; channel++) {
|
|
||||||
channels.push(this.mergeFloat32Arrays(this._buffers[channel], this._bytesWritten))
|
|
||||||
}
|
|
||||||
|
|
||||||
let interleaved = this.interleave(channels)
|
|
||||||
|
|
||||||
let resampled = this._resampler.resample(interleaved)
|
|
||||||
|
|
||||||
this.port.postMessage(this.floatTo16BitPCM(resampled))
|
|
||||||
|
|
||||||
this.initBuffers()
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeFloat32Arrays(arrays, bytesWritten) {
|
|
||||||
let result = new Float32Array(bytesWritten)
|
|
||||||
var offset = 0
|
|
||||||
|
|
||||||
for (let i = 0; i < arrays.length; i++) {
|
|
||||||
result.set(arrays[i], offset)
|
|
||||||
offset += arrays[i].length
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interleave data from channels from LLLLRRRR to LRLRLRLR
|
|
||||||
interleave(channels) {
|
|
||||||
if (channels === 1) {
|
|
||||||
return channels[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var length = 0
|
|
||||||
for (let i = 0; i < channels.length; i++) {
|
|
||||||
length += channels[i].length
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = new Float32Array(length)
|
|
||||||
|
|
||||||
var index = 0
|
|
||||||
var inputIndex = 0
|
|
||||||
|
|
||||||
while (index < length) {
|
|
||||||
for (let i = 0; i < channels.length; i++) {
|
|
||||||
result[index] = channels[i][inputIndex]
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
inputIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
floatTo16BitPCM(input) {
|
|
||||||
let output = new DataView(new ArrayBuffer(input.length * 2))
|
|
||||||
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
|
||||||
let s = Math.max(-1, Math.min(1, input[i]))
|
|
||||||
let s16 = s < 0 ? s * 0x8000 : s * 0x7FFF
|
|
||||||
output.setInt16(i * 2, s16, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Int16Array(output.buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Resampler {
|
|
||||||
constructor(fromSampleRate, toSampleRate, channels, inputBufferSize) {
|
|
||||||
|
|
||||||
if (!fromSampleRate || !toSampleRate || !channels) {
|
|
||||||
throw (new Error("Invalid settings specified for the resampler."));
|
|
||||||
}
|
|
||||||
this.resampler = null;
|
|
||||||
this.fromSampleRate = fromSampleRate;
|
|
||||||
this.toSampleRate = toSampleRate;
|
|
||||||
this.channels = channels || 0;
|
|
||||||
this.inputBufferSize = inputBufferSize;
|
|
||||||
this.initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
if (this.fromSampleRate == this.toSampleRate) {
|
|
||||||
|
|
||||||
// Setup resampler bypass - Resampler just returns what was passed through
|
|
||||||
this.resampler = (buffer) => {
|
|
||||||
return buffer
|
|
||||||
};
|
|
||||||
this.ratioWeight = 1;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (this.fromSampleRate < this.toSampleRate) {
|
|
||||||
|
|
||||||
// Use generic linear interpolation if upsampling,
|
|
||||||
// as linear interpolation produces a gradient that we want
|
|
||||||
// and works fine with two input sample points per output in this case.
|
|
||||||
this.linearInterpolation();
|
|
||||||
this.lastWeight = 1;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Custom resampler I wrote that doesn't skip samples
|
|
||||||
// like standard linear interpolation in high downsampling.
|
|
||||||
// This is more accurate than linear interpolation on downsampling.
|
|
||||||
this.multiTap();
|
|
||||||
this.tailExists = false;
|
|
||||||
this.lastWeight = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the internal buffer:
|
|
||||||
this.initializeBuffers();
|
|
||||||
this.ratioWeight = this.fromSampleRate / this.toSampleRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferSlice(sliceAmount) {
|
|
||||||
|
|
||||||
//Typed array and normal array buffer section referencing:
|
|
||||||
try {
|
|
||||||
return this.outputBuffer.subarray(0, sliceAmount);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
try {
|
|
||||||
//Regular array pass:
|
|
||||||
this.outputBuffer.length = sliceAmount;
|
|
||||||
return this.outputBuffer;
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
//Nightly Firefox 4 used to have the subarray function named as slice:
|
|
||||||
return this.outputBuffer.slice(0, sliceAmount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeBuffers() {
|
|
||||||
this.outputBufferSize = (Math.ceil(this.inputBufferSize * this.toSampleRate / this.fromSampleRate / this.channels * 1.000000476837158203125) + this.channels) + this.channels;
|
|
||||||
try {
|
|
||||||
this.outputBuffer = new Float32Array(this.outputBufferSize);
|
|
||||||
this.lastOutput = new Float32Array(this.channels);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
this.outputBuffer = [];
|
|
||||||
this.lastOutput = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
linearInterpolation() {
|
|
||||||
this.resampler = (buffer) => {
|
|
||||||
let bufferLength = buffer.length,
|
|
||||||
channels = this.channels,
|
|
||||||
outLength,
|
|
||||||
ratioWeight,
|
|
||||||
weight,
|
|
||||||
firstWeight,
|
|
||||||
secondWeight,
|
|
||||||
sourceOffset,
|
|
||||||
outputOffset,
|
|
||||||
outputBuffer,
|
|
||||||
channel;
|
|
||||||
|
|
||||||
if ((bufferLength % channels) !== 0) {
|
|
||||||
throw (new Error("Buffer was of incorrect sample length."));
|
|
||||||
}
|
|
||||||
if (bufferLength <= 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
outLength = this.outputBufferSize;
|
|
||||||
ratioWeight = this.ratioWeight;
|
|
||||||
weight = this.lastWeight;
|
|
||||||
firstWeight = 0;
|
|
||||||
secondWeight = 0;
|
|
||||||
sourceOffset = 0;
|
|
||||||
outputOffset = 0;
|
|
||||||
outputBuffer = this.outputBuffer;
|
|
||||||
|
|
||||||
for (; weight < 1; weight += ratioWeight) {
|
|
||||||
secondWeight = weight % 1;
|
|
||||||
firstWeight = 1 - secondWeight;
|
|
||||||
this.lastWeight = weight % 1;
|
|
||||||
for (channel = 0; channel < this.channels; ++channel) {
|
|
||||||
outputBuffer[outputOffset++] = (this.lastOutput[channel] * firstWeight) + (buffer[channel] * secondWeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
weight -= 1;
|
|
||||||
for (bufferLength -= channels, sourceOffset = Math.floor(weight) * channels; outputOffset < outLength && sourceOffset < bufferLength;) {
|
|
||||||
secondWeight = weight % 1;
|
|
||||||
firstWeight = 1 - secondWeight;
|
|
||||||
for (channel = 0; channel < this.channels; ++channel) {
|
|
||||||
outputBuffer[outputOffset++] = (buffer[sourceOffset + ((channel > 0) ? (channel) : 0)] * firstWeight) + (buffer[sourceOffset + (channels + channel)] * secondWeight);
|
|
||||||
}
|
|
||||||
weight += ratioWeight;
|
|
||||||
sourceOffset = Math.floor(weight) * channels;
|
|
||||||
}
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
this.lastOutput[channel] = buffer[sourceOffset++];
|
|
||||||
}
|
|
||||||
return this.bufferSlice(outputOffset);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
multiTap() {
|
|
||||||
this.resampler = (buffer) => {
|
|
||||||
let bufferLength = buffer.length,
|
|
||||||
outLength,
|
|
||||||
output_variable_list,
|
|
||||||
channels = this.channels,
|
|
||||||
ratioWeight,
|
|
||||||
weight,
|
|
||||||
channel,
|
|
||||||
actualPosition,
|
|
||||||
amountToNext,
|
|
||||||
alreadyProcessedTail,
|
|
||||||
outputBuffer,
|
|
||||||
outputOffset,
|
|
||||||
currentPosition;
|
|
||||||
|
|
||||||
if ((bufferLength % channels) !== 0) {
|
|
||||||
throw (new Error("Buffer was of incorrect sample length."));
|
|
||||||
}
|
|
||||||
if (bufferLength <= 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
outLength = this.outputBufferSize;
|
|
||||||
output_variable_list = [];
|
|
||||||
ratioWeight = this.ratioWeight;
|
|
||||||
weight = 0;
|
|
||||||
actualPosition = 0;
|
|
||||||
amountToNext = 0;
|
|
||||||
alreadyProcessedTail = !this.tailExists;
|
|
||||||
this.tailExists = false;
|
|
||||||
outputBuffer = this.outputBuffer;
|
|
||||||
outputOffset = 0;
|
|
||||||
currentPosition = 0;
|
|
||||||
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
output_variable_list[channel] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (alreadyProcessedTail) {
|
|
||||||
weight = ratioWeight;
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
output_variable_list[channel] = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
weight = this.lastWeight;
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
output_variable_list[channel] = this.lastOutput[channel];
|
|
||||||
}
|
|
||||||
alreadyProcessedTail = true;
|
|
||||||
}
|
|
||||||
while (weight > 0 && actualPosition < bufferLength) {
|
|
||||||
amountToNext = 1 + actualPosition - currentPosition;
|
|
||||||
if (weight >= amountToNext) {
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
output_variable_list[channel] += buffer[actualPosition++] * amountToNext;
|
|
||||||
}
|
|
||||||
currentPosition = actualPosition;
|
|
||||||
weight -= amountToNext;
|
|
||||||
} else {
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
output_variable_list[channel] += buffer[actualPosition + ((channel > 0) ? channel : 0)] * weight;
|
|
||||||
}
|
|
||||||
currentPosition += weight;
|
|
||||||
weight = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (weight === 0) {
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
outputBuffer[outputOffset++] = output_variable_list[channel] / ratioWeight;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.lastWeight = weight;
|
|
||||||
for (channel = 0; channel < channels; ++channel) {
|
|
||||||
this.lastOutput[channel] = output_variable_list[channel];
|
|
||||||
}
|
|
||||||
this.tailExists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (actualPosition < bufferLength && outputOffset < outLength);
|
|
||||||
return this.bufferSlice(outputOffset);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
resample(buffer) {
|
|
||||||
if (this.fromSampleRate == this.toSampleRate) {
|
|
||||||
this.ratioWeight = 1;
|
|
||||||
} else {
|
|
||||||
if (this.fromSampleRate < this.toSampleRate) {
|
|
||||||
this.lastWeight = 1;
|
|
||||||
} else {
|
|
||||||
this.tailExists = false;
|
|
||||||
this.lastWeight = 0;
|
|
||||||
}
|
|
||||||
this.initializeBuffers();
|
|
||||||
this.ratioWeight = this.fromSampleRate / this.toSampleRate;
|
|
||||||
}
|
|
||||||
return this.resampler(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerProcessor("recorder.worklet", RecorderProcessor)
|
|
||||||
Binary file not shown.
|
|
@ -1,44 +0,0 @@
|
||||||
plugins {
|
|
||||||
id("com.android.application")
|
|
||||||
id("kotlin-android")
|
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "com.yuanxuan.yx_asr_example"
|
|
||||||
compileSdk = flutter.compileSdkVersion
|
|
||||||
ndkVersion = flutter.ndkVersion
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "com.yuanxuan.yx_asr_example"
|
|
||||||
// You can update the following values to match your application needs.
|
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
|
||||||
targetSdk = flutter.targetSdkVersion
|
|
||||||
versionCode = flutter.versionCode
|
|
||||||
versionName = flutter.versionName
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
// TODO: Add your own signing config for the release build.
|
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flutter {
|
|
||||||
source = "../.."
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
|
||||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
|
||||||
|
|
||||||
subprojects {
|
|
||||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
|
||||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
|
||||||
}
|
|
||||||
subprojects {
|
|
||||||
project.evaluationDependsOn(":app")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
|
||||||
delete(rootProject.layout.buildDirectory)
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +1,3 @@
|
||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
pluginManagement {
|
|
||||||
val flutterSdkPath = run {
|
|
||||||
val properties = java.util.Properties()
|
|
||||||
file("local.properties").inputStream().use { properties.load(it) }
|
|
||||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
|
||||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
|
||||||
flutterSdkPath
|
|
||||||
}
|
|
||||||
|
|
||||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
|
||||||
id("com.android.application") version "8.7.3" apply false
|
|
||||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
|
||||||
}
|
|
||||||
|
|
||||||
include(":app")
|
|
||||||
|
|
@ -15,7 +15,7 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'YX ASR Example',
|
title: 'YX ASR Example',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||||
useMaterial3: true,
|
useMaterial3: false, // 暂时禁用Material 3以避免着色器编译问题
|
||||||
),
|
),
|
||||||
home: const SpeechRecognitionPage(),
|
home: const SpeechRecognitionPage(),
|
||||||
);
|
);
|
||||||
|
|
@ -40,10 +40,9 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
bool _isListening = false;
|
bool _isListening = false;
|
||||||
String _currentText = '';
|
String _currentText = '';
|
||||||
String _errorMessage = '';
|
String _errorMessage = '';
|
||||||
List<String> _recognitionHistory = [];
|
|
||||||
|
|
||||||
// 录音相关
|
/// 本次录音开始前的文本内容(用于实时追加)
|
||||||
final List<String> _realtimeResults = []; // 存储实时识别片段
|
String _baseText = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -95,8 +94,6 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
if (result.recognizedWords.isNotEmpty) {
|
if (result.recognizedWords.isNotEmpty) {
|
||||||
print('📱 [Example] 实时识别: ${result.recognizedWords}');
|
print('📱 [Example] 实时识别: ${result.recognizedWords}');
|
||||||
_currentText = result.recognizedWords;
|
_currentText = result.recognizedWords;
|
||||||
// 更新文本框显示
|
|
||||||
_updateTextController();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -151,83 +148,16 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 清除历史记录
|
/// 清除内容
|
||||||
void _clearHistory() {
|
void _clearContent() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_recognitionHistory.clear();
|
|
||||||
_realtimeResults.clear();
|
|
||||||
_currentText = '';
|
_currentText = '';
|
||||||
|
_baseText = ''; // 也清空基础文本
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
_errorMessage = '';
|
_errorMessage = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 更新文本控制器内容
|
|
||||||
void _updateTextController() {
|
|
||||||
// 组合已确认的结果和当前实时结果
|
|
||||||
final confirmedText = _realtimeResults.join(' ');
|
|
||||||
final displayText = _currentText.isNotEmpty
|
|
||||||
? '$confirmedText ${_currentText}'.trim()
|
|
||||||
: confirmedText;
|
|
||||||
|
|
||||||
// 只有当内容真正改变时才更新,避免光标跳动
|
|
||||||
if (_textController.text != displayText) {
|
|
||||||
final cursorPosition = _textController.selection.baseOffset;
|
|
||||||
_textController.text = displayText;
|
|
||||||
|
|
||||||
// 尝试保持光标位置,如果超出范围则移到末尾
|
|
||||||
if (cursorPosition <= displayText.length) {
|
|
||||||
_textController.selection = TextSelection.fromPosition(
|
|
||||||
TextPosition(offset: cursorPosition),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
_textController.selection = TextSelection.fromPosition(
|
|
||||||
TextPosition(offset: displayText.length),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 切换录音状态
|
|
||||||
Future<void> _toggleRecording() async {
|
|
||||||
if (!_isInitialized) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (_isListening) {
|
|
||||||
print('📱 [Example] 停止录音');
|
|
||||||
await _speechService.stopListening();
|
|
||||||
|
|
||||||
// 录音结束后,将当前识别的文本保存到历史记录
|
|
||||||
if (_currentText.isNotEmpty) {
|
|
||||||
setState(() {
|
|
||||||
_realtimeResults.add(_currentText);
|
|
||||||
_recognitionHistory.insert(0, _currentText);
|
|
||||||
print('📱 [Example] 添加到历史记录: $_currentText');
|
|
||||||
// 保持历史记录在合理数量
|
|
||||||
if (_recognitionHistory.length > 10) {
|
|
||||||
_recognitionHistory.removeLast();
|
|
||||||
}
|
|
||||||
// 清空当前文本
|
|
||||||
_currentText = '';
|
|
||||||
_updateTextController();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print('📱 [Example] 开始录音');
|
|
||||||
// 清空之前的结果,开始新的录音
|
|
||||||
setState(() {
|
|
||||||
_realtimeResults.clear();
|
|
||||||
_currentText = '';
|
|
||||||
_textController.clear();
|
|
||||||
});
|
|
||||||
await _speechService.startListening(partialResults: true);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('📱 [Example] 录音操作失败: $e');
|
|
||||||
_showErrorSnackBar('录音操作失败: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -237,8 +167,8 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.clear_all),
|
icon: const Icon(Icons.clear_all),
|
||||||
onPressed: _clearHistory,
|
onPressed: _clearContent,
|
||||||
tooltip: '清除历史',
|
tooltip: '清除内容',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -253,9 +183,6 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
// 识别结果卡片
|
// 识别结果卡片
|
||||||
_buildRecognitionCard(),
|
_buildRecognitionCard(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 历史记录
|
|
||||||
Expanded(child: _buildHistoryCard()),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -427,12 +354,16 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.mic, size: 14, color: Colors.blue[600]),
|
Icon(Icons.mic, size: 14, color: Colors.blue[600]),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Flexible(
|
||||||
'实时识别中...',
|
child: Text(
|
||||||
style: TextStyle(
|
_currentText.isEmpty ? '实时识别中...' : _currentText,
|
||||||
fontSize: 12,
|
style: TextStyle(
|
||||||
color: Colors.blue[600],
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
color: Colors.blue[600],
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -456,66 +387,6 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建历史记录卡片
|
|
||||||
Widget _buildHistoryCard() {
|
|
||||||
return Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'识别历史',
|
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Expanded(
|
|
||||||
child: _recognitionHistory.isEmpty
|
|
||||||
? Center(
|
|
||||||
child: Text(
|
|
||||||
'暂无识别历史',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.builder(
|
|
||||||
itemCount: _recognitionHistory.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 8.0),
|
|
||||||
child: ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
child: Text(
|
|
||||||
'${index + 1}',
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(_recognitionHistory[index]),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.copy),
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: 实现复制到剪贴板功能
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('已复制到剪贴板'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 构建浮动操作按钮
|
/// 构建浮动操作按钮
|
||||||
Widget _buildFloatingActionButton() {
|
Widget _buildFloatingActionButton() {
|
||||||
if (!_isInitialized) {
|
if (!_isInitialized) {
|
||||||
|
|
@ -526,44 +397,63 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
// 使用我们提供的 RecordingButton 组件
|
||||||
onTap: () {
|
return RecordingButton(
|
||||||
// 触觉反馈
|
speechService: _speechService,
|
||||||
HapticFeedback.mediumImpact();
|
size: 80,
|
||||||
_toggleRecording();
|
onResult: (result) {
|
||||||
|
print(
|
||||||
|
'📱 [Example] RecordingButton 接收到识别结果: "${result.recognizedWords}"');
|
||||||
|
setState(() {
|
||||||
|
if (result.recognizedWords.isNotEmpty) {
|
||||||
|
// 所有结果都实时更新到输入框(移除最终结果的特殊处理)
|
||||||
|
print('📱 [Example] 实时识别,更新输入框: ${result.recognizedWords}');
|
||||||
|
_currentText = result.recognizedWords;
|
||||||
|
|
||||||
|
// 实时更新输入框内容 = 基础文本 + 当前识别文本
|
||||||
|
String newText = _baseText;
|
||||||
|
if (newText.isNotEmpty &&
|
||||||
|
!newText.endsWith(' ') &&
|
||||||
|
_currentText.isNotEmpty) {
|
||||||
|
newText += ' '; // 添加空格分隔
|
||||||
|
}
|
||||||
|
newText += _currentText;
|
||||||
|
|
||||||
|
_textController.text = newText;
|
||||||
|
// 将光标移到最后
|
||||||
|
_textController.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: newText.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: Container(
|
onError: (error) {
|
||||||
width: 80,
|
setState(() {
|
||||||
height: 80,
|
_errorMessage = error.errorMsg;
|
||||||
decoration: BoxDecoration(
|
});
|
||||||
shape: BoxShape.circle,
|
_showErrorSnackBar('识别错误: ${error.errorMsg}');
|
||||||
gradient: LinearGradient(
|
},
|
||||||
begin: Alignment.topLeft,
|
onListeningStatusChanged: (isListening) {
|
||||||
end: Alignment.bottomRight,
|
setState(() {
|
||||||
colors: _isListening
|
_isListening = isListening;
|
||||||
? [Colors.red.shade400, Colors.red.shade600]
|
});
|
||||||
: [Colors.blue.shade400, Colors.blue.shade600],
|
|
||||||
),
|
if (!isListening) {
|
||||||
boxShadow: [
|
// 录音结束后,更新基础文本为下次录音做准备
|
||||||
BoxShadow(
|
setState(() {
|
||||||
color: (_isListening ? Colors.red : Colors.blue)
|
_baseText = _textController.text; // 保存当前文本作为下次的基础
|
||||||
.withValues(alpha: 0.4),
|
_currentText = ''; // 清空当前识别文本
|
||||||
spreadRadius: 4,
|
});
|
||||||
blurRadius: 12,
|
} else {
|
||||||
offset: const Offset(0, 6),
|
// 开始录音时记录当前文本作为基础,清空当前识别文本
|
||||||
),
|
setState(() {
|
||||||
],
|
_baseText = _textController.text; // 记录录音开始前的文本
|
||||||
),
|
_currentText = ''; // 清空当前识别文本
|
||||||
child: AnimatedSwitcher(
|
});
|
||||||
duration: const Duration(milliseconds: 300),
|
}
|
||||||
child: Icon(
|
},
|
||||||
_isListening ? Icons.stop_rounded : Icons.mic_rounded,
|
tooltip: _isListening ? '点击停止录音' : '点击开始录音',
|
||||||
key: ValueKey(_isListening),
|
enabled: _isInitialized,
|
||||||
size: 32,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,13 @@ flutter:
|
||||||
# 配置模型文件 assets
|
# 配置模型文件 assets
|
||||||
assets:
|
assets:
|
||||||
- assets/models/
|
- assets/models/
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/encoder-epoch-99-avg-1.int8.onnx
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/decoder-epoch-99-avg-1.int8.onnx
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/joiner-epoch-99-avg-1.int8.onnx
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/tokens.txt
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-int8-2025-06-30/
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-int8-2025-06-30/encoder.int8.onnx
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-int8-2025-06-30/decoder.onnx
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-int8-2025-06-30/joiner.int8.onnx
|
||||||
|
- assets/models/sherpa-onnx-streaming-zipformer-zh-int8-2025-06-30/tokens.txt
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
// This is a generated file; do not edit or check into version control.
|
|
||||||
FLUTTER_ROOT=/Users/max/fvm/versions/3.32.0
|
|
||||||
FLUTTER_APPLICATION_PATH=/Users/max/SourceCode/yuanxuan/yx_asr
|
|
||||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
|
||||||
FLUTTER_TARGET=lib/main.dart
|
|
||||||
FLUTTER_BUILD_DIR=build
|
|
||||||
FLUTTER_BUILD_NAME=1.0.0
|
|
||||||
FLUTTER_BUILD_NUMBER=1.0.0
|
|
||||||
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
|
||||||
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
|
|
||||||
DART_OBFUSCATION=false
|
|
||||||
TRACK_WIDGET_CREATION=true
|
|
||||||
TREE_SHAKE_ICONS=false
|
|
||||||
PACKAGE_CONFIG=.dart_tool/package_config.json
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#
|
|
||||||
# Generated file, do not edit.
|
|
||||||
#
|
|
||||||
|
|
||||||
import lldb
|
|
||||||
|
|
||||||
def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
|
|
||||||
"""Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
|
|
||||||
base = frame.register["x0"].GetValueAsAddress()
|
|
||||||
page_len = frame.register["x1"].GetValueAsUnsigned()
|
|
||||||
|
|
||||||
# Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
|
|
||||||
# first page to see if handled it correctly. This makes diagnosing
|
|
||||||
# misconfiguration (e.g. missing breakpoint) easier.
|
|
||||||
data = bytearray(page_len)
|
|
||||||
data[0:8] = b'IHELPED!'
|
|
||||||
|
|
||||||
error = lldb.SBError()
|
|
||||||
frame.GetThread().GetProcess().WriteMemory(base, data, error)
|
|
||||||
if not error.Success():
|
|
||||||
print(f'Failed to write into {base}[+{page_len}]', error)
|
|
||||||
return
|
|
||||||
|
|
||||||
def __lldb_init_module(debugger: lldb.SBDebugger, _):
|
|
||||||
target = debugger.GetDummyTarget()
|
|
||||||
# Caveat: must use BreakpointCreateByRegEx here and not
|
|
||||||
# BreakpointCreateByName. For some reasons callback function does not
|
|
||||||
# get carried over from dummy target for the later.
|
|
||||||
bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
|
|
||||||
bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
|
|
||||||
bp.SetAutoContinue(True)
|
|
||||||
print("-- LLDB integration loaded --")
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
#
|
|
||||||
# Generated file, do not edit.
|
|
||||||
#
|
|
||||||
|
|
||||||
command script import --relative-to-command-file flutter_lldb_helper.py
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# This is a generated file; do not edit or check into version control.
|
|
||||||
export "FLUTTER_ROOT=/Users/max/fvm/versions/3.32.0"
|
|
||||||
export "FLUTTER_APPLICATION_PATH=/Users/max/SourceCode/yuanxuan/yx_asr"
|
|
||||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
|
||||||
export "FLUTTER_TARGET=lib/main.dart"
|
|
||||||
export "FLUTTER_BUILD_DIR=build"
|
|
||||||
export "FLUTTER_BUILD_NAME=1.0.0"
|
|
||||||
export "FLUTTER_BUILD_NUMBER=1.0.0"
|
|
||||||
export "DART_OBFUSCATION=false"
|
|
||||||
export "TRACK_WIDGET_CREATION=true"
|
|
||||||
export "TREE_SHAKE_ICONS=false"
|
|
||||||
export "PACKAGE_CONFIG=.dart_tool/package_config.json"
|
|
||||||
|
|
@ -3,24 +3,14 @@ class SpeechRecognitionResult {
|
||||||
/// 识别出的文字内容
|
/// 识别出的文字内容
|
||||||
final String recognizedWords;
|
final String recognizedWords;
|
||||||
|
|
||||||
/// 识别置信度(0.0 到 1.0)
|
|
||||||
final double confidence;
|
|
||||||
|
|
||||||
/// 备选识别结果
|
|
||||||
final List<String> alternatives;
|
|
||||||
|
|
||||||
const SpeechRecognitionResult({
|
const SpeechRecognitionResult({
|
||||||
required this.recognizedWords,
|
required this.recognizedWords,
|
||||||
this.confidence = 0.0,
|
|
||||||
this.alternatives = const [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 从 Map 创建 [SpeechRecognitionResult] 实例
|
/// 从 Map 创建 [SpeechRecognitionResult] 实例
|
||||||
factory SpeechRecognitionResult.fromMap(Map<String, dynamic> map) {
|
factory SpeechRecognitionResult.fromMap(Map<String, dynamic> map) {
|
||||||
return SpeechRecognitionResult(
|
return SpeechRecognitionResult(
|
||||||
recognizedWords: map['recognizedWords'] as String? ?? '',
|
recognizedWords: map['recognizedWords'] as String? ?? '',
|
||||||
confidence: (map['confidence'] as num?)?.toDouble() ?? 0.0,
|
|
||||||
alternatives: List<String>.from(map['alternatives'] as List? ?? []),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,31 +18,23 @@ class SpeechRecognitionResult {
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
'recognizedWords': recognizedWords,
|
'recognizedWords': recognizedWords,
|
||||||
'confidence': confidence,
|
|
||||||
'alternatives': alternatives,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords, '
|
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords)';
|
||||||
'confidence: $confidence, alternatives: $alternatives)';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return other is SpeechRecognitionResult &&
|
return other is SpeechRecognitionResult &&
|
||||||
other.recognizedWords == recognizedWords &&
|
other.recognizedWords == recognizedWords;
|
||||||
other.confidence == confidence &&
|
|
||||||
other.alternatives.length == alternatives.length &&
|
|
||||||
other.alternatives.every((alt) => alternatives.contains(alt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return recognizedWords.hashCode ^
|
return recognizedWords.hashCode;
|
||||||
confidence.hashCode ^
|
|
||||||
alternatives.hashCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import '../interfaces/speech_recognition_service.dart';
|
import '../interfaces/speech_recognition_service.dart';
|
||||||
import '../yx_asr_service.dart';
|
import '../yx_asr_service.dart';
|
||||||
import '../models/speech_recognition_result.dart';
|
import '../models/speech_recognition_result.dart';
|
||||||
|
|
@ -69,6 +70,7 @@ class _RecordingButtonState extends State<RecordingButton>
|
||||||
late SpeechRecognitionService _speechService;
|
late SpeechRecognitionService _speechService;
|
||||||
bool _isListening = false;
|
bool _isListening = false;
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
|
bool _isProcessing = false; // 防抖标志
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
late Animation<double> _scaleAnimation;
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
|
@ -146,13 +148,29 @@ class _RecordingButtonState extends State<RecordingButton>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _toggleRecording() async {
|
Future<void> _toggleRecording() async {
|
||||||
if (!_isInitialized || !widget.enabled) return;
|
// 防抖检查:如果正在处理中或未初始化或被禁用,则直接返回
|
||||||
|
if (_isProcessing || !_isInitialized || !widget.enabled) return;
|
||||||
|
|
||||||
|
// 设置处理中标志,防止重复点击
|
||||||
|
setState(() {
|
||||||
|
_isProcessing = true;
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 触觉反馈
|
||||||
|
await HapticFeedback.lightImpact();
|
||||||
|
|
||||||
|
// 播放点击动画
|
||||||
|
_animationController.forward().then((_) {
|
||||||
|
_animationController.reverse();
|
||||||
|
});
|
||||||
|
|
||||||
if (_isListening) {
|
if (_isListening) {
|
||||||
await _speechService.stopListening();
|
await _speechService.stopListening();
|
||||||
} else {
|
} else {
|
||||||
await _speechService.startListening(partialResults: widget.partialResults);
|
await _speechService.startListening(
|
||||||
|
partialResults: widget.partialResults,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
widget.onError?.call(SpeechRecognitionError(
|
widget.onError?.call(SpeechRecognitionError(
|
||||||
|
|
@ -160,51 +178,74 @@ class _RecordingButtonState extends State<RecordingButton>
|
||||||
errorMsg: '切换录音状态失败: $e',
|
errorMsg: '切换录音状态失败: $e',
|
||||||
errorCode: null,
|
errorCode: null,
|
||||||
));
|
));
|
||||||
|
} finally {
|
||||||
|
// 延迟重置处理标志,确保有足够的防抖时间
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isProcessing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
Color iconColor;
|
||||||
|
|
||||||
Color buttonColor;
|
|
||||||
if (!widget.enabled || !_isInitialized) {
|
if (!widget.enabled || !_isInitialized) {
|
||||||
buttonColor = widget.disabledColor ?? Colors.grey;
|
iconColor = widget.disabledColor ?? Colors.grey[850]!;
|
||||||
} else if (_isListening) {
|
|
||||||
buttonColor = widget.recordingColor ?? Colors.red;
|
|
||||||
} else {
|
} else {
|
||||||
buttonColor = widget.idleColor ?? theme.primaryColor;
|
iconColor = _isListening
|
||||||
|
? (widget.recordingColor ?? const Color(0xFFFF5252))
|
||||||
|
: (widget.idleColor ?? const Color(0xFF2196F3));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget button = AnimatedBuilder(
|
Widget button = AnimatedBuilder(
|
||||||
animation: _scaleAnimation,
|
animation: _scaleAnimation,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return Transform.scale(
|
return Container(
|
||||||
scale: _scaleAnimation.value,
|
width: widget.size,
|
||||||
child: Container(
|
height: widget.size,
|
||||||
width: widget.size,
|
decoration: BoxDecoration(
|
||||||
height: widget.size,
|
color: iconColor.withValues(alpha: 0.12),
|
||||||
decoration: BoxDecoration(
|
shape: BoxShape.circle,
|
||||||
color: buttonColor,
|
),
|
||||||
shape: BoxShape.circle,
|
child: Material(
|
||||||
boxShadow: [
|
color: Colors.transparent,
|
||||||
BoxShadow(
|
child: InkWell(
|
||||||
color: buttonColor.withValues(alpha: 0.3),
|
borderRadius: BorderRadius.circular(widget.size / 2),
|
||||||
blurRadius: _isListening ? 20 : 8,
|
onTap: _toggleRecording,
|
||||||
spreadRadius: _isListening ? 5 : 2,
|
child: Stack(
|
||||||
),
|
children: [
|
||||||
],
|
Positioned(
|
||||||
),
|
left: 0,
|
||||||
child: Material(
|
right: 0,
|
||||||
color: Colors.transparent,
|
bottom: 0,
|
||||||
child: InkWell(
|
top: 0,
|
||||||
borderRadius: BorderRadius.circular(widget.size / 2),
|
child: Icon(
|
||||||
onTap: _toggleRecording,
|
_isListening ? Icons.stop_rounded : Icons.mic_rounded,
|
||||||
child: Icon(
|
size: widget.size * 0.55,
|
||||||
_isListening ? Icons.stop : Icons.mic,
|
color: iconColor,
|
||||||
size: widget.size * 0.4,
|
),
|
||||||
color: Colors.white,
|
),
|
||||||
),
|
if (_isProcessing || _isListening)
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
top: 0,
|
||||||
|
child: SizedBox(
|
||||||
|
width: widget.size,
|
||||||
|
height: widget.size,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
bool _isStartingRecording = false; // 防抖保护:防止重复启动录音
|
bool _isStartingRecording = false; // 防抖保护:防止重复启动录音
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
String _currentModelPath = '';
|
String _currentModelPath = '';
|
||||||
|
String _lastRecognizedText = ''; // 记录上次识别的文本,避免重复发送
|
||||||
|
|
||||||
// 识别速度配置
|
// 识别速度配置
|
||||||
RecognitionSpeed _recognitionSpeed = RecognitionSpeed.fast;
|
RecognitionSpeed _recognitionSpeed = RecognitionSpeed.fast;
|
||||||
|
|
@ -513,6 +514,7 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
await _startAudioRecording(_sampleRate.hz);
|
await _startAudioRecording(_sampleRate.hz);
|
||||||
|
|
||||||
_isListening = true;
|
_isListening = true;
|
||||||
|
_lastRecognizedText = ''; // 重置上次识别的文本
|
||||||
_statusController.add(true);
|
_statusController.add(true);
|
||||||
|
|
||||||
// 开始识别循环处理
|
// 开始识别循环处理
|
||||||
|
|
@ -572,29 +574,8 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
// 停止音频录制
|
// 停止音频录制
|
||||||
await _stopAudioRecording();
|
await _stopAudioRecording();
|
||||||
|
|
||||||
// 获取最终识别结果
|
// 重置流,准备下次识别
|
||||||
if (_recognizer != null && _stream != null) {
|
if (_stream != null) {
|
||||||
try {
|
|
||||||
// 暂时禁用离线识别,使用改进的流式识别
|
|
||||||
debugPrint('🛑 [YxAsr] 获取流式识别最终结果...');
|
|
||||||
final result = _recognizer!.getResult(_stream!);
|
|
||||||
debugPrint('🛑 [YxAsr] 流式最终结果: "${result.text}"');
|
|
||||||
|
|
||||||
if (result.text.isNotEmpty) {
|
|
||||||
debugPrint('📤 [YxAsr] 发送流式最终结果: ${result.text}');
|
|
||||||
_sendResult(
|
|
||||||
recognizedWords: result.text,
|
|
||||||
confidence: 1.0,
|
|
||||||
alternatives: [],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
debugPrint('⚠️ [YxAsr] 流式最终结果为空');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('⚠️ [YxAsr] 获取最终结果时出错: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置流,准备下次识别
|
|
||||||
_stream = null;
|
_stream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -761,13 +742,18 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
final result = _recognizer!.getResult(_stream!);
|
final result = _recognizer!.getResult(_stream!);
|
||||||
debugPrint('🔍 [YxAsr] 获取识别结果: "${result.text}"');
|
debugPrint('🔍 [YxAsr] 获取识别结果: "${result.text}"');
|
||||||
|
|
||||||
if (result.text.isNotEmpty && partialResults) {
|
// 只有当识别结果不为空、启用了部分结果、且与上次结果不同时才发送
|
||||||
|
if (result.text.isNotEmpty &&
|
||||||
|
partialResults &&
|
||||||
|
result.text != _lastRecognizedText) {
|
||||||
debugPrint('🎤 [YxAsr] 发送实时识别结果: ${result.text}');
|
debugPrint('🎤 [YxAsr] 发送实时识别结果: ${result.text}');
|
||||||
|
_lastRecognizedText = result.text; // 更新最后识别的文本
|
||||||
_sendResult(
|
_sendResult(
|
||||||
recognizedWords: result.text,
|
recognizedWords: result.text,
|
||||||
confidence: 0.8,
|
|
||||||
alternatives: [],
|
|
||||||
);
|
);
|
||||||
|
} else if (result.text.isNotEmpty &&
|
||||||
|
result.text == _lastRecognizedText) {
|
||||||
|
debugPrint('🔄 [YxAsr] 跳过重复识别结果: "${result.text}"');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 端点检测已禁用,由用户手动控制录音结束
|
// 端点检测已禁用,由用户手动控制录音结束
|
||||||
|
|
@ -783,14 +769,10 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
/// 发送识别结果到结果流
|
/// 发送识别结果到结果流
|
||||||
void _sendResult({
|
void _sendResult({
|
||||||
required String recognizedWords,
|
required String recognizedWords,
|
||||||
required double confidence,
|
|
||||||
required List<String> alternatives,
|
|
||||||
}) {
|
}) {
|
||||||
debugPrint('📤 [YxAsr] 发送识别结果: "$recognizedWords"');
|
debugPrint('📤 [YxAsr] 发送识别结果: "$recognizedWords"');
|
||||||
final result = SpeechRecognitionResult(
|
final result = SpeechRecognitionResult(
|
||||||
recognizedWords: recognizedWords,
|
recognizedWords: recognizedWords,
|
||||||
confidence: confidence,
|
|
||||||
alternatives: alternatives,
|
|
||||||
);
|
);
|
||||||
_resultController.add(result);
|
_resultController.add(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,5 +24,10 @@ dev_dependencies:
|
||||||
flutter_lints: ^3.0.0
|
flutter_lints: ^3.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
assets:
|
plugin:
|
||||||
- assets/models/
|
platforms:
|
||||||
|
android:
|
||||||
|
package: com.yuanxuan.yx_asr
|
||||||
|
pluginClass: YxAsrPlugin
|
||||||
|
ios:
|
||||||
|
pluginClass: YxAsrPlugin
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue