Compare commits

...

10 Commits

Author SHA1 Message Date
yangyakun
d87dd7d6a5 发布 2026-04-24 19:15:40 +08:00
yangyakun
8d5f093355 跨进程通讯 2026-04-10 17:27:36 +08:00
yangyakun
724fbbadb0 整理 2026-04-09 19:06:36 +08:00
yangyakun
2c4cf902af 整理 2026-04-09 18:13:07 +08:00
yangyakun
63ac5bb7e2 onnx 加载本地模型 tts基础功能完成 2026-04-09 18:01:08 +08:00
yangyakun
e4bd908714 图标变化 2026-04-08 19:26:33 +08:00
yangyakun
76caabd295 tts 初始化 2026-04-08 18:28:27 +08:00
yangyakun
9af378cec7 版本变更 2026-04-07 16:58:35 +08:00
yangyakun
964aaa9edc net location 减少调用频率 2025-07-08 17:17:01 +08:00
yangyakun
ec36a966f5 网球请求节流 2025-04-01 14:44:43 +08:00
467 changed files with 332021 additions and 22 deletions

View File

@@ -5,7 +5,7 @@ ext {
compileSdkVersion: 34,
buildToolsVersion: "29.0.2",
minSdkVersion : 19,
minSdkVersion : 21,
targetSdkVersion : 29,
]
dependencies = [

View File

@@ -11,17 +11,19 @@ import okhttp3.Response
class HttpHeaderInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val aiCloudClientConfig = MoGoAiCloudClient.getInstance().aiCloudClientConfig
val iHttpCurrentLocation = aiCloudClientConfig.iHttpCurrentLocation.currentLocation
var request = original.newBuilder()
.header("token", MoGoAiCloudClient.getInstance().aiCloudClientConfig.token)
.header("cityCode", MoGoAiCloudClient.getInstance().aiCloudClientConfig.iHttpCurrentLocation.currentLocation?.cityCode.toString())
.header("lat",MoGoAiCloudClient.getInstance().aiCloudClientConfig.iHttpCurrentLocation.currentLocation?.lat.toString())
.header("lon",MoGoAiCloudClient.getInstance().aiCloudClientConfig.iHttpCurrentLocation.currentLocation?.lon.toString())
.header("env",MoGoAiCloudClient.getInstance().aiCloudClientConfig.netMode.toString())
.header("sn",MoGoAiCloudClient.getInstance().aiCloudClientConfig.sn)
.method(original.method(), original.body())
.build()
if(MoGoAiCloudClient.getInstance().aiCloudClientConfig.securityKey.isNotBlank()){
request = request.newBuilder().header("authKey",MoGoAiCloudClient.getInstance().aiCloudClientConfig.securityKey).build()
.header("token", aiCloudClientConfig.token)
.header("cityCode", iHttpCurrentLocation?.cityCode.toString())
.header("lat", iHttpCurrentLocation?.lat.toString())
.header("lon", iHttpCurrentLocation?.lon.toString())
.header("env", aiCloudClientConfig.netMode.toString())
.header("sn", aiCloudClientConfig.sn)
.method(original.method(), original.body())
.build()
if(aiCloudClientConfig.securityKey.isNotBlank()){
request = request.newBuilder().header("authKey", aiCloudClientConfig.securityKey).build()
}
return chain.proceed(request)
}

View File

@@ -16,11 +16,14 @@ import static com.mogo.cloud.passport.MoGoAiCloudClient.TAG;
import static com.mogo.cloud.passport.MoGoAiCloudClientConfig.HTTP_DNS_ENV_DEV;
import static com.mogo.cloud.passport.MoGoAiCloudClientConfig.HTTP_DNS_ENV_QA;
import java.util.concurrent.atomic.AtomicBoolean;
public class ThirdPassportManager {
private static volatile ThirdPassportManager mInstance;
private IPassport mPassport;
private AtomicBoolean isRequestIng = new AtomicBoolean(false);
private ThirdPassportManager() {
}
@@ -37,9 +40,13 @@ public class ThirdPassportManager {
}
public void refreshToken(Context context, IPassport passport) {
if(isRequestIng.get()){
return;
}
this.mPassport = passport;
MoGoAiCloudClientConfig mAiCloudClientConfig = MoGoAiCloudClient.getInstance().getAiCloudClientConfig();
if (mAiCloudClientConfig != null) {
isRequestIng.set(true);
ThirdLoginParam thirdLoginParam = ThirdLoginParam.of(
mAiCloudClientConfig.getThirdPartyDeviceId(),
mAiCloudClientConfig.getThirdPartyAppKey()
@@ -48,6 +55,7 @@ public class ThirdPassportManager {
LoginCallback loginCallback = new LoginCallback() {
@Override
public void onSuccess(TokenData.TokenResult result) {
isRequestIng.set(false);
if (mPassport != null) {
mPassport.onSuccess(result.token, result.sn);
}

View File

@@ -38,23 +38,23 @@ RELEASE=true
# 工具类
MOGO_UTILS_VERSION=1.4.7.62
# 网络请求
MOGO_NETWORK_VERSION=1.4.7.62
MOGO_NETWORK_VERSION=1.4.7.65
# 鉴权
MOGO_PASSPORT_VERSION=1.4.7.62
MOGO_PASSPORT_VERSION=1.4.7.65
# 常链接
MOGO_SOCKET_VERSION=1.4.7.62
MOGO_SOCKET_VERSION=1.4.7.65
# 数据采集
MOGO_REALTIME_VERSION=1.4.7.62
MOGO_REALTIME_VERSION=1.4.7.65
# 探路,道路事件发布,获取
MOGO_TANLU_VERSION=1.4.7.62
MOGO_TANLU_VERSION=1.4.7.65
# 直播推流
MOGO_LIVE_VERSION=1.4.7.62
MOGO_LIVE_VERSION=1.4.7.65
# 直播拉流
MOGO_TRAFFICLIVE_VERSION=1.4.7.62
MOGO_TRAFFICLIVE_VERSION=1.4.7.65
# 定位服务
MOGO_LOCATION_VERSION=1.4.7.62
MOGO_LOCATION_VERSION=1.4.7.65
# 远程通讯模块
MOGO_TELEMATIC_VERSION=1.4.7.62
MOGO_TELEMATIC_VERSION=1.4.7.65
# v2x
MOGO_V2X_VERSION=1.4.7.62
# SKIN
@@ -65,6 +65,11 @@ SDK_NATIVE_VERSION=1.4.7.49.18-debug
UPLOAD_PLUGIN_VERSION=1.4.7.49.18-debug
TTS_BASE_VERSION=1.0.0.1
TTS_MOGO_VERSION=1.0.0.1
TTS_IFLYTEK_OFFLINE_VERSION=1.0.0.1
######################################################################
# for vanniktech
@@ -75,7 +80,7 @@ SONATYPE_HOST=S01
RELEASE_SIGNING_ENABLED=false
# 组织名
GROUP=com.mogo.cloud
VERSION_NAME=1.4.7.62
VERSION_NAME=1.4.7.64
# 主页
POM_URL=https://gitlab.zhidaoauto.com/SCA/L4HA/AndroidApp/support/countly-sdk-android
# 版本控制信息

1
libraries/mogo-tts/app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,7 @@
# 将 platform.pk8 生成 platform.pem
openssl pkcs8 -inform DER -nocrypt -in ./platform.pk8 -out ./platform.pem
# platform.x509.pe m生成 platform.p12
openssl pkcs12 -export -in ./platform.x509.pem -inkey ./platform.pem -out ./platform.p12 -password pass:android -name eb5_system
# 使用keytool工具通过指令生成keystore
keytool -importkeystore -deststorepass password -destkeystore ./eb5_system.keystore -srckeystore ./platform.p12 -srcstoretype PKCS12 -srcstorepass android

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCceAWSrA1dOBze
qmXsyKYAbjZIDG1yB7EgEb5Qhjqr4rVdAJrfcUbW8iAigMfNTXvbJiQ7ioBsJrNL
E3UjpJJoIkkE3AFJPnwKzxoFyHT2mwN7YDCdkHTSQoDha60qhzQ2GVHq9ypILQmy
BLGHXhKsmMGqdz1oALnq/eVtWL7Y6NoW+aNgCZw3qDSm3+23trRKBJ4Homn8zyxU
lvLPNtZN+Qo7jY80o7qrTPUzcasncZs7pYdUrQxT/BTh20XVHiNPu+k8m6Tt+c5U
JhNQ7FNWB79pov9KoH219+ogDQmmwbSeIUAvie0RkIk6q1qRgPFS6C+FpFdTz1/B
kHHF7sgnAgEDAoIBAGhQA7cdXj4laJRxmUiFxABJeYVdnkwFIMAL1DWu0cfseOir
EepLhI9MFWxV2ojeUpIZbX0HAEgZzNy3o20YYZrBhgM9VjDUUrHfZq6Fo08SAlJA
IGkK+IwsVeudHhxaIs67i/H6HDAeBnaty6+UDHMQgRxPfkVV0UdT7kjl1JCaMTTc
hXxr+oGsH5d5EHCPBfEU4v8BqOBSEgkUFJ1Y6Y5AQqbSe2nSZ6bKZpE8YY27Ipvq
BrZEN0j7SKcHKuJDm81DGfLrh3vOmW6U0ZWy7o9qv7INYXNDvtKUMQigXYYANKUp
KvveUSgKJ+ixfFP8Ye92j8mp+d/+16i4Wa7Wu5sCgYEAyCd3wu4wsRAu/8q1abL1
4/V8M95+dlfhJBl3H9VwUbsH+CP2VRQ9KYkaYdPwOb8hz1h8gqr74ASbEvz+ixMH
zI6zel07VDCew+6OUZOBiOxGc8p7SpZuCeDsL+GEZxkOMQ7PXUpDI/p8nx84eEJU
ZrHc2kv1VcpdAXLhtrpiicUCgYEAyCA3GDc282T3BXj061vqR2rpFaqhdlRy7S2B
tA3BJj2Qrh1bgQGq9agC+YWm3xO3F/lJ+phK2WHoFOpR+m6w7Q0jx/L/BYOlJD+4
h1zeZJKhLBQToodM1ZktcLGYFkSzNJeUImt5T1sErj5op9Ex97nZfYswCrl0GtGa
NIVKJPsCgYEAhW+lLJ7LILV0qocjm8yj7U5SzT7++Y/rbWZPao5K4SdapW1O42LT
cQYRlo1K0SoWijr9rHH9QAMSDKipsgyv3bR3pujSOCBp1/Re4Q0BBfLZoob83GRJ
W+tIH+utmhC0ILSKPjGCF/xTFL96+tbi7yE95t1OOTGTVkyWedGXBoMCgYEAhWrP
ZXokokNPWPtN8j1G2kdGDnHA+Y2h83OrzV6AxCkLHr49AKvHTnAB+65vP2J6D/uG
pxAx5kFFY0bhUZ8gngjChUyqA60YwtUlr5M+7bcWHWK3wa+IjmYeSyEQDth3eGUN
bEemNOdYdCmbGot2pSaQ/lzKsdD4EeEReFjcGKcCgYA+JeOt5WFENpv4LT+7P+j1
k3xvOZ9sJGuXRXk9HXzsJvRCnc5oScwqku6i5HjzG8gyNVZg1sQGbVbWWmcNtaS8
I3XalYAHYQyb8SGxlQP4ctKAN4j2Hbk1OHAMW84dfAQYQwFcBdaJTtMXQlbbX5Rh
x90wE4qFuIapx6IKOmDxRw==
-----END PRIVATE KEY-----

Binary file not shown.

View File

@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEqDCCA5CgAwIBAgIJALOZgIbQVs/6MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
Fw0wODA0MTUyMjQwNTBaFw0zNTA5MDEyMjQwNTBaMIGUMQswCQYDVQQGEwJVUzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
hvcNAQEBBQADggENADCCAQgCggEBAJx4BZKsDV04HN6qZezIpgBuNkgMbXIHsSAR
vlCGOqvitV0Amt9xRtbyICKAx81Ne9smJDuKgGwms0sTdSOkkmgiSQTcAUk+fArP
GgXIdPabA3tgMJ2QdNJCgOFrrSqHNDYZUer3KkgtCbIEsYdeEqyYwap3PWgAuer9
5W1Yvtjo2hb5o2AJnDeoNKbf7be2tEoEngeiafzPLFSW8s821k35CjuNjzSjuqtM
9TNxqydxmzulh1StDFP8FOHbRdUeI0+76TybpO35zlQmE1DsU1YHv2mi/0qgfbX3
6iANCabBtJ4hQC+J7RGQiTqrWpGA8VLoL4WkV1PPX8GQccXuyCcCAQOjgfwwgfkw
HQYDVR0OBBYEFE/koLPdnLop9x1yh8Tnw48ghsKZMIHJBgNVHSMEgcEwgb6AFE/k
oLPdnLop9x1yh8Tnw48ghsKZoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJALOZgIbQVs/6MAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAFclUbjZOh9z3g9tRp+G2tZwFAAp
PIigzXzXeLc9r8wZf6t25iEuVsHHYc/EL9cz3lLFCuCIFM78CjtaGkNGBU2Cnx2C
tCsgSL+ItdFJKe+F9g7dEtctVWV+IuPoXQTIMdYT0Zk4u4mCJH+jISVroS0dao+S
6h2xw3Mxe6DAN/DRr/ZFrvIkl5+6bnoUvAJccbmBOM7z3fwFlhfPJIRc97QNY4L3
J17XOElatuWTG5QhdlxJG3L7aOCA29tYwgKdNHyLMozkPvaosVUz7fvpib1qSN1L
IC7alMarjdW4OZID2q4u1EYjLk/pvZYTlMYwDlE448/Shebk5INTjLixs1c=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,129 @@
import java.text.SimpleDateFormat
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
signingConfigs {
releaseEB5 {
keyAlias = 'eb5_system'
storeFile file('./EB5/eb5_system.keystore')
storePassword 'password'
keyPassword 'password'
v1SigningEnabled true
v2SigningEnabled true
}
}
defaultConfig {
applicationId "com.mogo.tts"
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
debuggable true
minifyEnabled false
zipAlignEnabled true
shrinkResources false
signingConfig signingConfigs.releaseEB5
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
debuggable true
minifyEnabled false
zipAlignEnabled true
shrinkResources false
signingConfig signingConfigs.releaseEB5
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'com.blankj:utilcodex:1.30.0'
implementation "com.bytedance.boost_multidex:boost_multidex:1.0.1"
implementation(project(":libraries:mogo-tts:tts-iflytek-offline"))
implementation(project(":libraries:mogo-tts:tts-base"))
implementation(project(":libraries:mogo-tts:tts-mogo"))
// ======================集成 Bugly================================================================
implementation 'com.tencent.bugly:crashreport:4.1.9.2'
// implementation 'androidx.activity:activity:1.13.0'
// ======================集成 Bugly================================================================
}
//
//
//// 打包出来的应用文件名称
//android.applicationVariants.all { variant ->
// def buildTime = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08:00"))
// def flavor = variant.productFlavors.collect { it.name }.join('-')
//
// variant.outputs.all { output ->
// outputFileName = ["MoGoTTS",
// "v${variant.versionName}",
// getWorkingBranchName(),
// getWorkingBranchHash(),
// buildTime,
// flavor.length() > 0 ? "[${flavor}]" : "",
// variant.buildType.name].findAll { it.length() > 0 }.join('_') << ".apk"
// }
//}
//
//// 获取当前分支名称
//def getWorkingBranchName() {
// def workingBranchName = ""
// def proc = "git rev-parse --abbrev-ref HEAD".execute()
// proc.in.eachLine { line -> workingBranchName = line }
// proc.err.eachLine { line -> println line }
// proc.waitFor()
// println "Working branch name: " + workingBranchName
// return workingBranchName
//}
//
//// 获取当前分支hash
//def getWorkingBranchHash() {
// def workingBranchHash = ""
// def proc = "git log -n1 --format=format:%h".execute()
// proc.in.eachLine { line -> workingBranchHash = line }
// proc.err.eachLine { line -> println line }
// proc.waitFor()
// println "Working branch hash: " + workingBranchHash
// return workingBranchHash
//}
//
//// 当前构建时间
//static def getBuildTime() {
// def buildTimeFormat = "yyyy-MM-dd HH:mm:ss"
// //设置时间格式
// SimpleDateFormat formatter = new SimpleDateFormat(buildTimeFormat, Locale.getDefault())
// //获取当前时间
// Date curDate = new Date(System.currentTimeMillis())
// def buildTime = formatter.format(curDate)
// return "\"${buildTime}\""
//}

View File

@@ -0,0 +1,33 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /projects/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# This renames the file name on stack traces from "UnknownSource" to "SourceFile"
-renamesourcefileattribute SourceFile
# This values are kept on stack traces to debug any issue easily
-keepattributes SourceFile,LineNumberTable
# Rules recommended for Huawei PushKit
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.hianalytics.android.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}

View File

@@ -0,0 +1,6 @@
### Sample Android app for demo
This is an example app that sends data to a remote Countly server.
In order to build and run this application, just open up the project in Android Studio and hit run with an appropriate application.

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.tts">
<!-- 允许后台启动服务 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.RESTART_PACKAGES" /> <!-- 系统级权限:绕过后台服务限制 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- 关键:允许后台启动 Service & 前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_*" /> <!-- 系统签名专用:豁免后台限制 -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name=".MoGoApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:persistent="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="orientation|screenSize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".TTSService"
android:exported="true"
android:process=":ttsP"
>
<intent-filter>
<action android:name="com.mogo.tts.ttsservice" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,57 @@
package com.mogo.tts
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatButton
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.mogo.tts.common.IMogoTTSCallback
import com.mogo.tts.common.log.TtsLogManager
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
private val ttsManager = TtsManager()
private val TAG = "MainActivity"
private val ranx = Random(10)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
findViewById<AppCompatButton>(R.id.actv_test_tts1).setOnClickListener {
//TtsInnerManager.speakTTSVoice("此时每个单兵要负责自己的一个视线防区")
val info = "此时每个单兵要负责自己的一个视线防区${ranx.nextInt()}"
TtsLogManager.d(TAG,"要播放的语音:${info}")
ttsManager.speakTTSVoice(info,object :IMogoTTSCallback{
override fun onSpeakStart(speakText: String?) {
TtsLogManager.d(TAG,"onSpeakStart:${speakText}")
}
override fun onSpeakComple(speakText: String?) {
TtsLogManager.d(TAG,"onSpeakComple:${speakText}")
}
override fun onStopTts(speakText: String?) {
TtsLogManager.d(TAG,"onStopTts:${speakText}")
}
override fun onSpeakError(speakText: String?, errorMsg: String?) {
TtsLogManager.d(TAG,"onSpeakError:${speakText}")
}
})
}
findViewById<AppCompatButton>(R.id.actv_test_tts2).setOnClickListener {
ttsManager.stopTTS()
}
ttsManager.bindService(this)
}
}

View File

@@ -0,0 +1,85 @@
package com.mogo.tts
import android.app.Application
import android.content.Context
import android.util.Log
import com.blankj.utilcode.util.DeviceUtils
import com.blankj.utilcode.util.FileIOUtils
import com.blankj.utilcode.util.PathUtils
import com.blankj.utilcode.util.TimeUtils
import com.bytedance.boost_multidex.BoostMultiDex
import com.mogo.tts.utils.PhoneUtilsExtend
import com.tencent.bugly.crashreport.CrashReport
import com.tencent.bugly.crashreport.CrashReport.CrashHandleCallback
import com.tencent.bugly.crashreport.CrashReport.UserStrategy
class MoGoApplication : Application() {
val TAG = "MoGoApplication"
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
Log.d(TAG, "--->>>MoGo APP attachBaseContext")
BoostMultiDex.install(base)
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "--->>>MoGo APP 应用商店启动onCreate")
initBugly()
initTts()
}
private fun initTts() {
TTSService.start(this)
}
private fun initBugly() {
val strategy = UserStrategy(this)
// 通过UserStrategy设置
strategy.deviceID = PhoneUtilsExtend.getDevicesSn()
// 通过UserStrategy设置
strategy.deviceModel = DeviceUtils.getModel()
// 最新版SDK支持trace文件采集和anr过程中的主线程堆栈信息采集由于抓取堆栈的系统接口 Thread.getStackTrace 可能造成crash建议只对少量用户开启
// 这里只对Debug模式下的开启
if (BuildConfig.DEBUG) {
strategy.appChannel = "MoGo_Debug"
// 设置anr时是否获取系统trace文件默认为false
strategy.isEnableCatchAnrTrace = true
// 设置是否获取anr过程中的主线程堆栈默认为true
strategy.isEnableRecordAnrMainStack = false
} else {
strategy.appChannel = "MoGo_Debug"
}
// 设置Crash回调,将日志信息本地存储
strategy.setCrashHandleCallback(object : CrashHandleCallback() {
override fun onCrashHandleStart(
crashType: Int, errorType: String,
errorMessage: String, errorStack: String
): Map<String, String> {
val map = LinkedHashMap<String, String>()
map["device_sn"] = PhoneUtilsExtend.getDevicesSn()
FileIOUtils.writeFileFromString(
PathUtils.getExternalAppFilesPath() + "/crash/" + TimeUtils.getNowString() + ".log",
"crashType = $crashType \nerrorType = $errorType \nerrorMessage = $errorMessage \nerrorStack = $errorStack"
)
return map
}
override fun onCrashHandleStart2GetExtraDatas(
crashType: Int, errorType: String,
errorMessage: String, errorStack: String
): ByteArray? {
return try {
"Extra data.".toByteArray(charset("UTF-8"))
} catch (e: Exception) {
null
}
}
})
CrashReport.initCrashReport(this, strategy)
}
}

View File

@@ -0,0 +1,29 @@
package com.mogo.tts.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
/**
* 监听系统启动
*/
class BootReceiver : BroadcastReceiver() {
private val TAG = "BootReceiver"
private val TARGET_PKG: String = "com.mogo.launcher.f"
private val mHandler = Handler(Looper.getMainLooper())
private var mRetryCount = 0
private val MAX_RETRY: Int = 20 // 最多重试 20 次
private val DELAY: Int = 500 // 每 500ms 查一次
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BOOT_COMPLETED == intent.action||Intent.ACTION_LOCKED_BOOT_COMPLETED == intent.action) { //接收BOOT_COMPLETED广播
}
}
}

View File

@@ -0,0 +1,13 @@
package com.mogo.tts.utils
import android.os.Build
/**
* 对PhoneUtils扩展一些方法获取系统的参数
*/
object PhoneUtilsExtend {
fun getDevicesSn(): String {
return "name"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/actv_test_tts1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="开始播放"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/actv_test_tts2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/actv_test_tts1"
android:text="停止播放"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -0,0 +1,6 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">TTS Demo</string>
<string name="deep_link">Deep Link Test</string>
<string name="countly_hannel_name">General Notifications</string>
<string name="countly_channel_description"><![CDATA[News & announcements]]></string>
<string name="title_activity_another">AnotherActivity</string>
<string name="another_activity_text">Another Activity</string>
</resources>

View File

@@ -0,0 +1,17 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>

View File

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,67 @@
### 换肤SDK
#### 使用方式
```java
public class MoGoApplication extends MultiDexApplication {
private static final String TAG = "MoGoApplication";
@Override
public void onCreate() {
super.onCreate();
// 初始化动态换肤SDK
SkinManager.init(this);
}
}
```
##### 基础控件0改动完成资源替换只需要保持使用的「资源文件drawable、string、color等」皮肤包与APP中保持一致即可
TextView 控件的静态文字一定要用string.xml 的方式引用文字显示
例如:
```xml
<TextView
android:id="@+id/tvDevicesId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@string/devicesId"
android:textColor="@color/colorAccent"
android:textSize="20dp" />
```
##### 如果代码中动态控制了 ImageView 的 src、background 一定要在 XML 中替换成 SkinImageView.java
##### 代码中动态获取资源设置:图片、文字、颜色
```java
// 获取颜色
SkinResources.getInstance().getColor(resId);
// 获取图片
SkinResources.getInstance().getDrawable(resId);
// 获取文字
SkinResources.getInstance().getString(resId);
// 获取指定的资源id
SkinResources.getInstance().getIdentifier(resId);
// 获取指定的Raw资源 InputStream
SkinResources.getInstance().getRawInputStream(resId);
// 获取指定的Raw资源的byte[]
SkinResources.getInstance().getRawResourceBytes(resId);
```
#### 手动控制切换皮肤
```java
Skin skin = new Skin(
// 皮肤包 MD5 用于文件损坏校验的
"d5493244467d3970834e42dc1a6f07c9",
// 皮肤文件名称
"app-skin-debug.skin",
// 外网可访问的文件服务器地址
"https://carlife-static-1255510688.cos.ap-beijing.myqcloud.com/MoGoEagleEye/app-skin-debug.skin");
// 换肤
SkinManager.getInstance().selectSkin(this, skin);
// 还原
SkinManager.getInstance().loadSkin("");
```

View File

@@ -0,0 +1,39 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "${MOGO_REALTIME_VERSION}"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation rootProject.ext.dependencies.androidxappcompat
api "com.elegant.utils:common-utils:1.0.58"
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,4 @@
GROUP=com.mogo.cloud
POM_ARTIFACT_ID=ttsbase
VERSION_CODE=1
VERSION_NAME=1.0.1-SNAPSHOT

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.tts.common">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>

View File

@@ -0,0 +1,24 @@
package com.mogo.tts;
import com.mogo.tts.ITTSCallbackBinder;
interface ICoreBinder {
void speakTTSVoice1(String tts);
void speakTTSVoice2(String tts,in ITTSCallbackBinder callback);
void speakTTSVoic3(String tts, int languageType,in ITTSCallbackBinder callback);
void registerTtsListener(String tag,in ITTSCallbackBinder callback);
void unRegisterTtsListener(String tag);
void clearTtsListener();
void stopTts();
void stopTtsByText(String tts);
}

View File

@@ -0,0 +1,10 @@
package com.mogo.tts;
oneway interface ITTSCallbackBinder {
void onSpeakStart(String speakText);
void onSpeakComple(String speakText);
void onStopTts(String speakText);
void onSpeakError(String speakText,String errorMsg);
}

View File

@@ -0,0 +1,254 @@
package com.mogo.tts
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.mogo.tts.common.IMogoTTSCallback
import com.mogo.tts.common.LangTtsEntity
import com.mogo.tts.common.R
import com.mogo.tts.common.log.TtsLogManager
import java.util.concurrent.ConcurrentHashMap
class TTSService : Service() {
private val TAG = "TTSService"
private val globalStart = "global_"
// 🔥 最佳方案ConcurrentHashMap 管理回调key = tagvalue = 回调)
private val callbackMap = ConcurrentHashMap<String, ITTSCallbackBinder>()
private val iCorebinder = object : ICoreBinder.Stub() {
override fun speakTTSVoice1(tts: String?) {
// 防御空字符串
val text = tts?.takeIf { it.isNotEmpty() } ?: return
TtsInnerManager.speakTTSVoice(tts)
}
override fun speakTTSVoice2(tts: String?, callback: ITTSCallbackBinder?) {
// 防御空字符串
val text = tts?.takeIf { it.isNotEmpty() } ?: return
if(callback==null){
TtsInnerManager.speakTTSVoice(tts)
return
}
// 先缓存 callback
callbackMap[text] = callback
TtsInnerManager.speakTTSVoice(tts,object :IMogoTTSCallback{
override fun onSpeakStart(speakText: String?) {
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onSpeakStart(speakText)
}
}
override fun onSpeakComple(speakText: String?){
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onSpeakComple(speakText)
}
// ✅ 执行完自动销毁 callback
callbackMap.remove(speakText)
}
override fun onStopTts(speakText: String?) {
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onStopTts(speakText)
}
// ✅ 执行完自动销毁 callback
callbackMap.remove(speakText)
}
override fun onSpeakError(speakText: String?, errorMsg: String?) {
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onSpeakError(speakText,errorMsg)
}
// ✅ 执行完自动销毁 callback
callbackMap.remove(speakText)
}
})
}
override fun speakTTSVoic3(tts: String?, languageType: Int, callback: ITTSCallbackBinder?) {
// 防御空字符串
val text = tts?.takeIf { it.isNotEmpty() } ?: return
if(callback==null){
TtsInnerManager.speakTTSVoice(tts)
return
}
// 先缓存 callback
callbackMap[text] = callback
val langTtsEntity = LangTtsEntity.getLangTtsEntity(tts, languageType)
TtsInnerManager.speakTTSVoice(langTtsEntity,object :IMogoTTSCallback{
override fun onSpeakStart(speakText: String?) {
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onSpeakStart(speakText)
}
}
override fun onSpeakComple(speakText: String?) {
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onSpeakComple(speakText)
}
// ✅ 执行完自动销毁 callback
callbackMap.remove(speakText)
}
override fun onStopTts(speakText: String?) {
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onStopTts(speakText)
}
// ✅ 执行完自动销毁 callback
callbackMap.remove(speakText)
}
override fun onSpeakError(speakText: String?, errorMsg: String?) {
val cb = callbackMap[speakText] ?: return
safeCallback {
cb.onSpeakError(speakText,errorMsg)
}
// ✅ 执行完自动销毁 callback
callbackMap.remove(speakText)
}
})
}
override fun registerTtsListener(tag: String?, callback: ITTSCallbackBinder?) {
if(tag.isNullOrEmpty()){
return
}
if(callback==null){
return
}
val key = "${globalStart}${tag}"
callbackMap[key] = callback
}
override fun unRegisterTtsListener(tag: String?) {
if(tag.isNullOrEmpty()){
return
}
val key = "${globalStart}${tag}"
callbackMap.remove(key)
}
override fun clearTtsListener() {
callbackMap.clear()
}
override fun stopTts() {
TtsInnerManager.stopTTS()
}
override fun stopTtsByText(tts: String?) {
// TODO: 如果正在播放直接停止、如果在排队播放从队伍中移除
}
}
companion object{
fun start(context: Context) {
val serviceIntent = Intent(context, TTSService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
}
}
override fun onCreate() {
super.onCreate()
TtsLogManager.d(TAG,"onCreate")
TtsInnerManager.init(applicationContext)
TtsInnerManager.registerTtsListener(object :IMogoTTSCallback{
override fun onSpeakStart(speakText: String?) {
callbackMap.forEach {
if (it.key.startsWith(globalStart)) {
safeCallback {
it.value.onSpeakStart(speakText)
}
}
}
}
override fun onSpeakComple(speakText: String?) {
callbackMap.forEach {
if (it.key.startsWith(globalStart)) {
safeCallback {
it.value.onSpeakComple(speakText)
}
}
}
}
override fun onSpeakError(speakText: String?, errorMsg: String) {
callbackMap.forEach {
if (it.key.startsWith(globalStart)) {
safeCallback {
it.value.onSpeakError(speakText,errorMsg)
}
}
}
}
})
}
override fun onBind(intent: Intent?): IBinder? {
TtsLogManager.d(TAG,"onBind")
return iCorebinder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
TtsLogManager.d(TAG,"onStartCommand")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (manager != null) {
val nc = NotificationChannel("Voice", "语音服务", NotificationManager.IMPORTANCE_HIGH)
manager.createNotificationChannel(nc)
}
}
val notification: Notification = NotificationCompat.Builder(applicationContext, "Voice")
.setContentTitle("语音服务运行中")
.setSmallIcon(R.drawable.voice_manager_icon)
.build()
startForeground(1, notification)
return START_STICKY
}
override fun onDestroy() {
TtsLogManager.d(TAG,"onDestroy")
super.onDestroy()
TtsInnerManager.unRegisterTtsListener()
TtsInnerManager.release()
}
// 跨进程回调安全工具方法
private inline fun safeCallback(call: () -> Unit) {
try {
call()
} catch (e: Exception) {
TtsLogManager.e(TAG,e.toString(),e)
}
}
}

View File

@@ -0,0 +1,71 @@
package com.mogo.tts
import android.content.Context
import com.mogo.tts.common.IMogoTTS
import com.mogo.tts.common.IMogoTTSCallback
import com.mogo.tts.common.LangTtsEntity
import com.mogo.tts.common.LanguageType
import com.mogo.tts.common.log.TtsLogManager
object TtsInnerManager {
private const val TAG = "TtsManager"
private var mTTS: IMogoTTS? = null
fun init(context: Context) {
try {
// 暂时换成反射,解决死锁问题
var clazz1: Class<*>? = null
try {
// clazz1 = Class.forName("com.mogo.tts.iflytek.offline.IFlyTekOfflineTts")
clazz1 = Class.forName("com.k2fsa.sherpa.onnx.MogoOfflineTTS")
} catch (ignored: Exception) {
}
if (clazz1 != null) {
mTTS = clazz1.getConstructor().newInstance() as IMogoTTS
}
mTTS?.initTts(context)
} catch (e: Exception) {
e.printStackTrace()
TtsLogManager.d(TAG, "TTS 模块初始化异常")
}
}
fun speakTTSVoice(tts: String?) {
if(tts.isNullOrEmpty()) return
speakTTSVoice(tts,null)
}
fun speakTTSVoice(tts: String?, callBack: IMogoTTSCallback?) {
if(tts.isNullOrEmpty()) return
speakTTSVoice(LangTtsEntity(tts, LanguageType.CHINESE), callBack);
}
fun speakTTSVoice(ttsEntity: LangTtsEntity, callBack: IMogoTTSCallback?) {
if(ttsEntity.ttsContent.isEmpty()){
return
}
mTTS?.speakTTSVoice(ttsEntity, callBack)
}
fun registerTtsListener(callback: IMogoTTSCallback) {
mTTS?.registerTtsListener(callback)
}
fun unRegisterTtsListener() {
mTTS?.unRegisterTtsListener()
}
fun stopTTS() {
mTTS?.stopTts()
}
fun release() {
mTTS?.release()
}
}

View File

@@ -0,0 +1,211 @@
package com.mogo.tts
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.os.Build
import android.os.IBinder
import android.os.RemoteException
import com.elegant.utils.UiThreadHandler
import com.mogo.tts.common.IMogoTTSCallback
import com.mogo.tts.common.LangTtsEntity
import com.mogo.tts.common.LanguageType
import java.util.concurrent.atomic.AtomicBoolean
class TtsManager {
companion object {
private const val SERVER_PACKAGE: String = "com.mogo.tts"
}
private val isBind = AtomicBoolean(false)
private val isServiceException = AtomicBoolean(false) //服务绑定成功后出现异常
protected var binder: ICoreBinder? = null
private var context: Context? = null
fun speakTTSVoice(tts: String?) {
if(tts.isNullOrEmpty()) return
speakTTSVoice(LangTtsEntity(tts, LanguageType.CHINESE), null);
}
fun speakTTSVoice(tts: String?, callBack: IMogoTTSCallback?) {
if(tts.isNullOrEmpty()) return
speakTTSVoice(LangTtsEntity(tts, LanguageType.CHINESE), callBack);
}
fun speakTTSVoice(ttsEntity: LangTtsEntity, callBack: IMogoTTSCallback?) {
if(ttsEntity.ttsContent.isEmpty()){
return
}
if(binder!=null){
try {
binder?.speakTTSVoic3(ttsEntity.ttsContent,ttsEntity.getLanguageDef(),object :
ITTSCallbackBinder.Stub() {
override fun onSpeakStart(speakText: String?) {
callBack?.onSpeakStart(speakText)
}
override fun onSpeakComple(speakText: String?) {
callBack?.onSpeakComple(speakText)
}
override fun onStopTts(speakText: String?) {
callBack?.onStopTts(speakText)
}
override fun onSpeakError(speakText: String?, errorMsg: String?) {
callBack?.onSpeakError(speakText,errorMsg)
}
})
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
fun registerTtsListener(tag:String,callback: IMogoTTSCallback) {
if(binder!=null){
try {
binder?.registerTtsListener(tag,object :ITTSCallbackBinder.Stub(){
override fun onSpeakStart(speakText: String?) {
callback.onSpeakStart(speakText)
}
override fun onSpeakComple(speakText: String?) {
callback.onSpeakComple(speakText)
}
override fun onStopTts(speakText: String?) {
callback.onStopTts(speakText)
}
override fun onSpeakError(speakText: String?, errorMsg: String?) {
callback.onSpeakError(speakText,errorMsg)
}
})
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
fun unRegisterTtsListener(tag: String) {
if(binder!=null){
try {
binder?.unRegisterTtsListener(tag)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
fun stopTTS() {
if(binder!=null){
try {
binder?.stopTts()
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
fun stopTTS(tts:String) {
if(binder!=null){
try {
binder?.stopTtsByText(tts)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
binder = ICoreBinder.Stub.asInterface(service);
}
override fun onServiceDisconnected(name: ComponentName?) {
isServiceException.set(true);
unbind()
delayBind()
}
}
fun bindService(
context: Context,
) {
this.context = context
bindService()
}
private fun bindService() {
context?.let {
if (!isBind.get()) {
if (isServerInstalled()) {
isBind.set(true)
val intent = Intent("com.mogo.tts.ttsservice")
intent.setPackage(SERVER_PACKAGE)
val isSuccess: Boolean =
it.bindService(intent, connection, Context.BIND_AUTO_CREATE)
if (!isSuccess) {
unbind()
}
} else {
}
}
}
}
private fun unbind() {
if (binder != null) {
try {
binder?.clearTtsListener()
} catch (e: RemoteException) {
e.printStackTrace()
}
binder = null
}
context?.let {
if (isBind.get()) {
isBind.set(false)
try {
it.unbindService(connection)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
private fun delayBind() {
if (isServiceException.get()) {
isServiceException.set(false)
UiThreadHandler.postDelayed({
bindService()
}, 4000L)
}
}
private fun isServerInstalled(): Boolean {
context?.let {
val pm: PackageManager = it.getPackageManager()
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getApplicationInfo(
SERVER_PACKAGE,
PackageManager.ApplicationInfoFlags.of(0)
).enabled
} else {
pm.getApplicationInfo(SERVER_PACKAGE, 0).enabled
}
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
return false
}
}

View File

@@ -0,0 +1,11 @@
package com.mogo.tts.common
interface IGlobalTtsCallback {
fun onTtsSpeakStart(speakText: String?)
fun onTtsSpeakEnd(speakText: String?)
fun onTtsSpeakComple(speakText: String?)
fun onTtsSpeakError(speakText: String?, errorMsg: String)
}

View File

@@ -0,0 +1,29 @@
package com.mogo.tts.common;
import android.content.Context;
public
/**
* @author congtaowang
* @since 2020/10/12
*
* 语音抽象
*/
interface IMogoTTS {
void initTts(Context context);
/**
* 释放资源
*/
void release();
void registerTtsListener(IMogoTTSCallback callback);
void unRegisterTtsListener();
void speakTTSVoice(LangTtsEntity ttsEntity, IMogoTTSCallback callBack);
void stopTts();
}

View File

@@ -0,0 +1,24 @@
package com.mogo.tts.common;
public
/**
* @author congtaowang
* @since 2020/10/12
*
* 描述
*/
interface IMogoTTSCallback {
default void onSpeakStart( String speakText ) {
}
default void onSpeakComple( String speakText ) {
}
default void onStopTts( String speakText ) {
}
default void onSpeakError( String speakText, String errorMsg) {
}
}

View File

@@ -0,0 +1,38 @@
package com.mogo.tts.common
data class LangTtsEntity(
var ttsContent: String,
var language: LanguageType
) {
override fun toString(): String {
return "ttsContent is:$ttsContent,language is:${language.langName}"
}
fun getLanguageDef(): Int {
return when (language) {
LanguageType.CHINESE -> LanguageTypeDef.MODE_CHINESE
LanguageType.ENGLISH -> LanguageTypeDef.MODE_ENGLISH
LanguageType.KOREAN -> LanguageTypeDef.MODE_KOREAN
}
}
companion object {
fun getLangTtsEntity(tts: String, languageType: Int): LangTtsEntity {
val languageType = when (languageType) {
LanguageTypeDef.MODE_ENGLISH -> {
LanguageType.ENGLISH
}
LanguageTypeDef.MODE_KOREAN -> {
LanguageType.KOREAN
}
else -> {
LanguageType.CHINESE
}
}
return LangTtsEntity(tts, languageType)
}
}
}

View File

@@ -0,0 +1,7 @@
package com.mogo.tts.common
enum class LanguageType(val langName: String) {
CHINESE("chinese"),
ENGLISH("english"),
KOREAN("korean")
}

View File

@@ -0,0 +1,14 @@
package com.mogo.tts.common;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
public @interface LanguageTypeDef{
public static final int MODE_CHINESE = 0;
public static final int MODE_ENGLISH = 1;
public static final int MODE_KOREAN = 2;
}

View File

@@ -0,0 +1,32 @@
package com.mogo.tts.common;
public
/**
* @author congtaowang
* @since 2020/10/12
*
* 语音打算类型
*/
enum PreemptType {
/**
* 不打断
*/
PREEMPT_TYPE_NONE,
/**
* 立即打断取消当前的tts插队播放
*/
PREEMPT_TYPE_IMMEDIATELY,
/**
* 下一个插入不取消当前的tts插队下一个播放
*/
PREEMPT_TYPE_NEXT,
/**
* 清空队列
*/
PREEMPT_TYPE_FLUSH,
/**
* 立即打断不取消当前tts
*/
PREEMPT_TYPE_IMMEDIATELY_WITHOUT_CANCEL
}

View File

@@ -0,0 +1,134 @@
package com.mogo.tts.common.impl;
import android.content.Context;
import android.os.Looper;
import com.elegant.utils.UiThreadHandler;
import com.mogo.tts.common.IMogoTTS;
import com.mogo.tts.common.IMogoTTSCallback;
import com.mogo.tts.common.LangTtsEntity;
import com.mogo.tts.common.log.TtsLogManager;
import java.util.HashMap;
public abstract class BaseMogoTTS implements IMogoTTS {
protected Context context;
// 由于主动打断不会有回调事件所以主动打断时清掉map中被打断的text和callback
protected volatile String curTtsContent = "";
protected volatile LangTtsEntity curTtsEntity = null;
protected HashMap<String, IMogoTTSCallback> speakVoiceMap = new HashMap<>();
protected IMogoTTSCallback mGlobalTtsCallback = null;
protected String getTAG() {
return "BaseMogoTTS";
}
@Override
public void initTts(Context context) {
this.context = context;
}
public void registerTtsListener(IMogoTTSCallback callback) {
this.mGlobalTtsCallback = callback;
}
public void unRegisterTtsListener(){
this.mGlobalTtsCallback = null;
}
@Override
public void speakTTSVoice(LangTtsEntity ttsEntity, IMogoTTSCallback callBack) {
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
UiThreadHandler.post(() -> {
if (callBack != null) {
speakVoiceMap.put(ttsEntity.toString(),callBack);
}
speakMultiLangTTS(ttsEntity);
});
}else {
if (callBack != null) {
speakVoiceMap.put(ttsEntity.toString(),callBack);
}
speakMultiLangTTS(ttsEntity);
}
}
protected void speakMultiLangTTS(LangTtsEntity ttsEntity){
// 合成并播放
TtsLogManager.d(getTAG(), "tts准备合成"+ttsEntity);
}
@Override
public void stopTts() {
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
UiThreadHandler.post(this::realStop);
} else {
realStop();
}
}
protected void realStop() {
TtsLogManager.d(getTAG(),"停止tts");
if(curTtsEntity!=null){
String key = curTtsEntity.toString();
if (speakVoiceMap.containsKey(key)) {
IMogoTTSCallback remove = speakVoiceMap.remove(key);
if(remove!=null) {
remove.onStopTts(curTtsEntity.getTtsContent());
}
}
curTtsEntity = null;
}
this.curTtsContent = "";
}
public void onSpeakBegin() {
if(mGlobalTtsCallback != null) {
mGlobalTtsCallback.onSpeakStart(curTtsContent);
}
if (curTtsEntity!=null) {
String key = curTtsEntity.toString();
IMogoTTSCallback iMogoTTSCallback = speakVoiceMap.get(key);
if(iMogoTTSCallback!=null){
iMogoTTSCallback.onSpeakStart(curTtsEntity.getTtsContent());
}
}
}
public void handleCompleteEvent() {
if(mGlobalTtsCallback!=null) {
mGlobalTtsCallback.onSpeakComple(curTtsContent);
}
if (curTtsEntity!=null) {
String key = curTtsEntity.toString();
IMogoTTSCallback iMogoTTSCallback = speakVoiceMap.get(key);
if(iMogoTTSCallback!=null){
iMogoTTSCallback.onSpeakComple(curTtsEntity.getTtsContent());
}
}
curTtsEntity = null;
curTtsContent = "";
}
public void handleErrorEvent(String error) {
if(mGlobalTtsCallback!=null) {
mGlobalTtsCallback.onSpeakError(curTtsContent,error);
}
if (curTtsEntity != null) {
String key = curTtsEntity.toString();
IMogoTTSCallback iMogoTTSCallback = speakVoiceMap.get(key);
if(iMogoTTSCallback!=null){
iMogoTTSCallback.onSpeakError(curTtsEntity.getTtsContent(), error);
}
} else {
IMogoTTSCallback iMogoTTSCallback = speakVoiceMap.get(curTtsContent);
if(iMogoTTSCallback!=null){
iMogoTTSCallback.onSpeakError(curTtsContent, error);
}
}
curTtsEntity = null;
curTtsContent = "";
}
}

View File

@@ -0,0 +1,5 @@
package com.mogo.tts.common.log;
public enum LogLevel {
info,warn,debug,error
}

View File

@@ -0,0 +1,6 @@
package com.mogo.tts.common.log;
public interface LogListener {
void writeLog(LogLevel logLevel,String tag,String message);
}

View File

@@ -0,0 +1,59 @@
package com.mogo.tts.common.log
import android.util.Log
object TtsLogManager {
private var isDebug = true
private var logListener:LogListener? = null
fun setDebug(isDebug:Boolean){
this.isDebug = isDebug
}
fun setLogListener(logListener: LogListener){
this.logListener = logListener
}
@JvmStatic
fun d(tag: String, message: String) {
if(isDebug) {
Log.d(tag, message);
}
logListener?.writeLog(LogLevel.debug,tag,message)
}
@JvmStatic
fun i(tag: String, message: String) {
if(isDebug) {
Log.i(tag, message)
}
logListener?.writeLog(LogLevel.info,tag,message)
}
@JvmStatic
fun w(tag: String, message: String) {
if(isDebug) {
Log.w(tag, message)
}
logListener?.writeLog(LogLevel.warn,tag,message)
}
@JvmStatic
fun e(tag: String, message: String) {
if(isDebug) {
Log.e(tag, message)
}
logListener?.writeLog(LogLevel.error,tag,message)
}
@JvmStatic
fun e(tag: String, message: String,tr:Throwable?) {
if(isDebug) {
Log.e(tag, message,tr)
}
logListener?.writeLog(LogLevel.error,tag,message)
}
}

View File

@@ -0,0 +1,71 @@
package com.mogo.tts.common.utils;
import android.content.Context;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileUtils {
public static void copyAssetsToLocal(Context context, String assetDir, String localDir) throws IOException {
// 获取assets目录下的所有文件和目录
String[] files = context.getAssets().list(assetDir);
if (files == null || files.length == 0) {
return;
}
// 创建本地目标目录
File localDirFile = new File(localDir);
if (!localDirFile.exists()) {
localDirFile.mkdirs();
}
for (String fileName : files) {
String assetPath = assetDir + File.separator + fileName;
String localPath = localDir + File.separator + fileName;
// 如果是文件夹,递归调用
if (isDirectory(context, assetPath)) {
copyAssetsToLocal(context, assetPath, localPath);
} else {
try (InputStream in = context.getAssets().open(assetPath);
FileOutputStream out = new FileOutputStream(localPath)) {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
}
}
}
// 判断 assets 中的路径是否是文件夹
public static boolean isDirectory(Context context, String path) throws IOException {
String[] files = context.getAssets().list(path);
return files != null && files.length > 0;
}
public static void writeFile(String path, byte[] bytes) {
boolean append = false;
try {
File file = new File(path);
if (file.exists()) {
append = true;
}else {
file.createNewFile();
}
FileOutputStream out = new FileOutputStream(path,true);//指定写到哪个路径中
FileChannel fileChannel = out.getChannel();
fileChannel.write(ByteBuffer.wrap(bytes)); //将字节流写入文件中
fileChannel.force(true);//强制刷新
fileChannel.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.tts.utils
import android.content.Context
class ApkUtils {
companion object{
@JvmStatic
fun isInstallAppStore(ctx: Context): Boolean {
return try {
val installedPackages = ctx.packageManager.getInstalledPackages(0)
installedPackages.find { it.packageName == "com.mogo.tts" } != null
} catch (t: Throwable) {
false
}
}
}
}

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,15c1.66,0 2.99,-1.34 2.99,-3L15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6v6c0,1.66 1.34,3 3,3zM17.3,12c0,3 -2.54,5.1 -5.3,5.1S6.7,15 6.7,12L5,12c0,3.42 2.72,6.23 6,6.72L11,22h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="skinTypeface" format="string" />
<!--防止大面积误伤原生View这里做一个手动开启换肤的开关来控制是否要在这个控件上使用-->
<attr name="isUseSkin" format="boolean" />
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">skin-core2</string>
</resources>

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,67 @@
### 换肤SDK
#### 使用方式
```java
public class MoGoApplication extends MultiDexApplication {
private static final String TAG = "MoGoApplication";
@Override
public void onCreate() {
super.onCreate();
// 初始化动态换肤SDK
SkinManager.init(this);
}
}
```
##### 基础控件0改动完成资源替换只需要保持使用的「资源文件drawable、string、color等」皮肤包与APP中保持一致即可
TextView 控件的静态文字一定要用string.xml 的方式引用文字显示
例如:
```xml
<TextView
android:id="@+id/tvDevicesId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@string/devicesId"
android:textColor="@color/colorAccent"
android:textSize="20dp" />
```
##### 如果代码中动态控制了 ImageView 的 src、background 一定要在 XML 中替换成 SkinImageView.java
##### 代码中动态获取资源设置:图片、文字、颜色
```java
// 获取颜色
SkinResources.getInstance().getColor(resId);
// 获取图片
SkinResources.getInstance().getDrawable(resId);
// 获取文字
SkinResources.getInstance().getString(resId);
// 获取指定的资源id
SkinResources.getInstance().getIdentifier(resId);
// 获取指定的Raw资源 InputStream
SkinResources.getInstance().getRawInputStream(resId);
// 获取指定的Raw资源的byte[]
SkinResources.getInstance().getRawResourceBytes(resId);
```
#### 手动控制切换皮肤
```java
Skin skin = new Skin(
// 皮肤包 MD5 用于文件损坏校验的
"d5493244467d3970834e42dc1a6f07c9",
// 皮肤文件名称
"app-skin-debug.skin",
// 外网可访问的文件服务器地址
"https://carlife-static-1255510688.cos.ap-beijing.myqcloud.com/MoGoEagleEye/app-skin-debug.skin");
// 换肤
SkinManager.getInstance().selectSkin(this, skin);
// 还原
SkinManager.getInstance().loadSkin("");
```

View File

@@ -0,0 +1,44 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "${MOGO_REALTIME_VERSION}"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
if (Boolean.valueOf(RELEASE)) {
implementation "com.mogo.cloud:ttsbase:${TTS_BASE_VERSION}"
}else {
implementation(project(":libraries:mogo-tts:tts-base"))
}
implementation files('libs/AIKit.aar')
implementation rootProject.ext.dependencies.androidxappcompat
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,4 @@
GROUP=com.mogo.cloud
POM_ARTIFACT_ID=ttsiflytekoffline
VERSION_CODE=1
VERSION_NAME=1.0.1-SNAPSHOT

Binary file not shown.

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.tts.iflytek.offline">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>

View File

@@ -0,0 +1,229 @@
package com.mogo.tts.iflytek.offline
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord
import android.media.AudioTrack
import android.os.Process
import java.io.DataInputStream
import java.io.File
import java.io.FileInputStream
class AudioTrackManager {
private var mAudioTrack: AudioTrack? = null
private var mDis: DataInputStream? = null //播放文件的数据流
private var mRecordThread: Thread? = null
private var isStart = false
//指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。
private var mMinBufferSize = 0
private var mSampleRate = 16000
private var listener: OnCompleteListener? = null
enum class sampleRateType {
SAMPLE_RATE_16k,
SAMPLE_RATE_24k
}
private fun initData() {
//根据采样率采样精度单双声道来得到frame的大小。
mMinBufferSize =
AudioTrack.getMinBufferSize(mSampleRate, mChannelConfig, mAudioFormat) //计算最小缓冲区
//注意按照数字音频的知识这个算出来的是一秒钟buffer的大小。
//创建AudioTrack
mAudioTrack = AudioTrack(
mStreamType, mSampleRate, mChannelConfig,
mAudioFormat, mMinBufferSize, mMode
)
}
fun setListener(listener: OnCompleteListener) {
this.listener = listener
}
fun setSampleRate(sampleRate: sampleRateType?) {
when (sampleRate) {
sampleRateType.SAMPLE_RATE_16k -> mSampleRate = mSampleRateIn16KHz
sampleRateType.SAMPLE_RATE_24k -> mSampleRate = mSampleRateIn24KHz
else -> {
mSampleRate = mSampleRateIn24KHz
}
}
}
/**
* 销毁线程方法
*/
private fun destroyThread() {
try {
isStart = false
if (null != mRecordThread && Thread.State.RUNNABLE == mRecordThread!!.state) {
try {
Thread.sleep(500)
mRecordThread!!.interrupt()
} catch (e: Exception) {
mRecordThread = null
}
}
mRecordThread = null
} catch (e: Exception) {
e.printStackTrace()
} finally {
mRecordThread = null
}
}
/**
* 启动播放线程
*/
private fun startThread() {
destroyThread()
isStart = true
if (mRecordThread == null) {
mRecordThread = Thread(playRunnable)
mRecordThread!!.start()
}
}
/**
* 播放线程
*/
var playRunnable: Runnable = Runnable {
try {
//设置线程的优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
val tempBuffer = ByteArray(mMinBufferSize)
var readCount = 0
while (mDis!!.available() > 0) {
readCount = mDis!!.read(tempBuffer)
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
continue
}
if (readCount != 0 && readCount != -1) { //一边播放一边写入语音数据
//判断AudioTrack未初始化停止播放的时候释放了状态就为STATE_UNINITIALIZED
if (mAudioTrack!!.state == AudioTrack.STATE_UNINITIALIZED) {
initData()
}
mAudioTrack!!.play()
mAudioTrack!!.write(tempBuffer, 0, readCount)
}
}
stopPlay() //播放完就停止播放
} catch (e: Exception) {
e.printStackTrace()
}
listener?.onComplete()
}
init {
initData()
}
/**
* 播放文件
* @param path
* @throws Exception
*/
@Throws(Exception::class)
private fun setPath(path: String) {
val file = File(path)
mDis = DataInputStream(FileInputStream(file))
}
/**
* 启动播放
*
* @param path
*/
fun startPlay(path: String) {
try {
// //AudioTrack未初始化
// if(mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED){
// throw new RuntimeException("The AudioTrack is not uninitialized");
// }//AudioRecord.getMinBufferSize的参数是否支持当前的硬件设备
// else if (AudioTrack.ERROR_BAD_VALUE == mMinBufferSize || AudioTrack.ERROR == mMinBufferSize) {
// throw new RuntimeException("AudioTrack Unable to getMinBufferSize");
// }else{
setPath(path)
startThread()
// }
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 停止播放
*/
fun stopPlay() {
try {
destroyThread() //销毁线程
if (mAudioTrack != null) {
if (mAudioTrack!!.state == AudioRecord.STATE_INITIALIZED) { //初始化成功
mAudioTrack!!.stop() //停止播放
}
if (mAudioTrack != null) {
mAudioTrack!!.release() //释放audioTrack资源
}
}
if (mDis != null) {
mDis!!.close() //关闭数据输入流
}
} catch (e: Exception) {
e.printStackTrace()
}
}
val playState: Int
get() {
if (mAudioTrack != null) {
return mAudioTrack!!.playState
}
return AudioTrack.PLAYSTATE_STOPPED
}
companion object {
@Volatile
private var mInstance: AudioTrackManager? = null
//音频流类型
private const val mStreamType = AudioManager.STREAM_MUSIC
//指定采样率 MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100目前为常用的采样率官方文档表示这个值可以兼容所有的设置
const val mSampleRateIn16KHz: Int = 16000
const val mSampleRateIn24KHz: Int = 24000
//指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量
private const val mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO //单声道
//指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制它实际上是原始音频样本。
//因此可以设置每个样本的分辨率为16位或者8位16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
private const val mAudioFormat = AudioFormat.ENCODING_PCM_16BIT
//STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样
// 应用层从某个地方获取数据例如通过编解码得到PCM数据然后write到audiotrack。
private const val mMode = AudioTrack.MODE_STREAM
val instance: AudioTrackManager?
/**
* 获取单例引用
*
* @return
*/
get() {
if (mInstance == null) {
synchronized(AudioTrackManager::class.java) {
if (mInstance == null) {
mInstance = AudioTrackManager()
}
}
}
return mInstance
}
}
interface OnCompleteListener {
fun onComplete()
}
}

View File

@@ -0,0 +1,255 @@
package com.mogo.tts.iflytek.offline
import android.content.Context
import android.os.Looper
import androidx.annotation.Keep
import com.elegant.utils.ResourcesHelper
import com.elegant.utils.ThreadPoolService
import com.elegant.utils.UiThreadHandler
import com.iflytek.aikit.core.AeeEvent
import com.iflytek.aikit.core.AiHandle
import com.iflytek.aikit.core.AiHelper
import com.iflytek.aikit.core.AiInput
import com.iflytek.aikit.core.AiListener
import com.iflytek.aikit.core.AiRequest
import com.iflytek.aikit.core.AiResponse
import com.iflytek.aikit.core.AiText
import com.iflytek.aikit.core.BaseLibrary
import com.iflytek.aikit.core.CoreListener
import com.iflytek.aikit.core.ErrType
import com.mogo.tts.common.LangTtsEntity
import com.mogo.tts.common.log.TtsLogManager
import com.mogo.tts.common.impl.BaseMogoTTS
import com.mogo.tts.common.utils.FileUtils
import java.io.File
@Keep
class IFlyTekOfflineTts : BaseMogoTTS() {
private var aiHandle: AiHandle? = null
private var OUTPUT_DIR :String = ""
private val ABILITYID by lazy {
"e2e44feff"
}
override fun getTAG(): String {
return "IFlyTekOfflineTts"
}
override fun initTts(context: Context) {
super.initTts(context)
initSDK()
}
private fun initSDK() {
context?.let {
val workPath = it.filesDir.absolutePath+File.separator+"aikit"+File.separator+"xtts"
OUTPUT_DIR = workPath+File.separator+"output"
val file = File("$workPath/e3fe94474_1.0.0_xTTS_CnCn_xiaoyan_2018_arm.irf")
if (!file.exists() || file.length() == 0L) {
ThreadPoolService.execute {
FileUtils.copyAssetsToLocal(context, "xTTS", workPath)
initEngine(workPath)
}
} else {
ThreadPoolService.execute {
initEngine(workPath)
}
}
AiHelper.getInst().registerListener(coreListener)
AiHelper.getInst().registerListener(ABILITYID, aiRespListener)
}
}
private fun initEngine(workPath: String) {
val params = BaseLibrary.Params.builder()
.appId("0c498b42")
.apiKey("8579f566eb7f3c4f4a07148ad9e2408c")
.apiSecret("NTRmMmI5MWI4NzIzZTIxN2Q5N2FjMWVl")
.ability("e2e44feff;e867a88f2")
.workDir(workPath)
.build()
AiHelper.getInst().init(context, params)
AudioTrackManager.instance?.setSampleRate(AudioTrackManager.sampleRateType.SAMPLE_RATE_16k)
AudioTrackManager.instance?.setListener(completeListener)
}
private val coreListener = CoreListener { type, code ->
when (type) {
ErrType.AUTH -> {
if (code == 0) {
// SDK授权成功
TtsLogManager.d(tag, "科大讯飞离线语音合成授权成功!")
} else {
// SDK授权失败授权码为code
TtsLogManager.d(tag, "科大讯飞离线语音合成授权失败码:$code")
}
}
else -> {
// SDK状态为type, code
TtsLogManager.d(tag, "type:$type, code:$code")
}
}
}
private val completeListener = object : AudioTrackManager.OnCompleteListener {
override fun onComplete() {
handleCompleteEvent()
}
}
override fun release() {
if (Thread.currentThread() != Looper.getMainLooper().thread) {
UiThreadHandler.post {
AiHelper.getInst().unInit()
}
} else {
AiHelper.getInst().unInit()
}
}
override fun realStop() {
super.realStop()
AudioTrackManager.instance?.stopPlay()
if (aiHandle == null || aiHandle?.isSuccess == false) {
return
}
val end = AiHelper.getInst().end(aiHandle)
aiHandle = null
TtsLogManager.d(tag,"停止tts:${end}")
}
override fun speakMultiLangTTS(ttsEntity: LangTtsEntity) {
super.speakMultiLangTTS(ttsEntity)
stopTts()
startSpeak(ttsEntity)
}
private fun startSpeak(langTtsEntity: LangTtsEntity?) {
langTtsEntity?.let {
curTtsEntity = it
curTtsContent = it.ttsContent
realSpeak(it.ttsContent)
}
}
/**
* 1:中文, 2:英文, 3:法语, 5:日语, 6:俄语, 9:德语, 15:意大利语, 16:韩语, 23:西班牙语, 12:粤语, 8:印地语, 27:泰语
*/
private fun realSpeak(content: String) {
deleteAllOutPutDir()
// 开启会话
val builder = AiInput.builder()
builder.param("vcn", "xiaoyan")
builder.param("language", 1)
builder.param("textEncoding", "UTF-8")
builder.param("pitch", 50)
builder.param("volume", 50)
builder.param("speed", 50)
aiHandle = AiHelper.getInst().start(ABILITYID, builder.build(), null)
if (!aiHandle!!.isSuccess) {
// handleErrorEvent("开启会话报错:${aiHandle!!.code}")
val errorInfo = ResourcesHelper.getString(context, R.string.module_tts_start_ai_handle_error)
handleErrorEvent("${errorInfo}${aiHandle!!.code}")
AiHelper.getInst().end(aiHandle)
aiHandle = null
return
}
// 开始写入
val dataBuilder = AiRequest.Builder()
val input = AiText.get("text").data(content).valid()
dataBuilder.payload(input)
val ret = AiHelper.getInst().write(dataBuilder.build(), aiHandle)
if (ret != 0) {
// handleErrorEvent("写能力输入数据失败")
val errorInfo = ResourcesHelper.getString(context, R.string.module_tts_write_ai_handle_error)
handleErrorEvent(errorInfo)
AiHelper.getInst().end(aiHandle)
aiHandle = null
}
}
private fun deleteAllOutPutDir() {
val dir = File(OUTPUT_DIR)
if (!dir.exists() || !dir.isDirectory || dir.listFiles() == null) return
for (file in dir.listFiles()!!) {
if (file.isFile) file.delete() // 删除所有文件
}
if (!dir.exists()) {
dir.mkdirs()
}
}
private val aiRespListener = object : AiListener {
override fun onResult(handleID: Int, list: MutableList<AiResponse>?, usrCxt: Any?) {
if (null != list && list.size > 0) {
val dir = File(OUTPUT_DIR)
var bytes: ByteArray?
for (i in list.indices) {
bytes = list[i].value ?: continue
TtsLogManager.d(tag, "onResult:handleID:" + handleID + ":" + list[i].key)
if (!dir.exists()) {
dir.mkdirs()
}
FileUtils.writeFile("${OUTPUT_DIR}/OutPut_mogo.pcm", bytes)
}
}
}
override fun onEvent(
handleID: Int,
event: Int,
eventData: MutableList<AiResponse>?,
usrCxt: Any?
) {
when (event) {
AeeEvent.AEE_EVENT_UNKNOWN.value -> {
TtsLogManager.d(tag, "未知错误")
// handleErrorEvent("未知错误")
aiHandle?.let {
val ret = AiHelper.getInst().end(it)
aiHandle = null
TtsLogManager.d(tag, "AIKit_End$ret")
}
val errorInfo = ResourcesHelper.getString(context, R.string.module_tts_unknown_error)
handleErrorEvent(errorInfo)
}
AeeEvent.AEE_EVENT_TIMEOUT.value->{
TtsLogManager.d(tag, "超时错误")
// handleErrorEvent("未知错误")
aiHandle?.let {
val ret = AiHelper.getInst().end(it)
aiHandle = null
TtsLogManager.d(tag, "AIKit_End$ret")
}
val errorInfo = ResourcesHelper.getString(context, R.string.module_tts_unknown_error)
handleErrorEvent(errorInfo)
}
AeeEvent.AEE_EVENT_START.value->{
}
AeeEvent.AEE_EVENT_END.value -> {
aiHandle?.let {
val ret = AiHelper.getInst().end(it)
aiHandle = null
TtsLogManager.d(tag, "AIKit_End$ret")
}
onSpeakBegin()
AudioTrackManager.instance?.startPlay("${OUTPUT_DIR}/OutPut_mogo.pcm")
}
}
}
override fun onError(handleID: Int, err: Int, msg: String?, usrCxt: Any?) {
TtsLogManager.d(tag, "错误码:$err,错误信息:$msg")
// handleErrorEvent("错误码:$err,错误信息:$msg")
val errorInfo = ResourcesHelper.getResources(context)
.getString(R.string.module_tts_ai_handle_error_code, err, msg)
handleErrorEvent(errorInfo)
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="module_tts_start_ai_handle_error">Failed to start session:</string>
<string name="module_tts_write_ai_handle_error">Failed to write capability input data</string>
<string name="module_tts_ai_handle_error_code">Error code: %d, Error message: %s</string>
<string name="module_tts_unknown_error">Unknown error</string>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="module_tts_start_ai_handle_error">开启会话报错:</string>
<string name="module_tts_write_ai_handle_error">写能力输入数据失败</string>
<string name="module_tts_ai_handle_error_code">错误码:%d错误信息%s</string>
<string name="module_tts_unknown_error">未知错误</string>
</resources>

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,67 @@
### 换肤SDK
#### 使用方式
```java
public class MoGoApplication extends MultiDexApplication {
private static final String TAG = "MoGoApplication";
@Override
public void onCreate() {
super.onCreate();
// 初始化动态换肤SDK
SkinManager.init(this);
}
}
```
##### 基础控件0改动完成资源替换只需要保持使用的「资源文件drawable、string、color等」皮肤包与APP中保持一致即可
TextView 控件的静态文字一定要用string.xml 的方式引用文字显示
例如:
```xml
<TextView
android:id="@+id/tvDevicesId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@string/devicesId"
android:textColor="@color/colorAccent"
android:textSize="20dp" />
```
##### 如果代码中动态控制了 ImageView 的 src、background 一定要在 XML 中替换成 SkinImageView.java
##### 代码中动态获取资源设置:图片、文字、颜色
```java
// 获取颜色
SkinResources.getInstance().getColor(resId);
// 获取图片
SkinResources.getInstance().getDrawable(resId);
// 获取文字
SkinResources.getInstance().getString(resId);
// 获取指定的资源id
SkinResources.getInstance().getIdentifier(resId);
// 获取指定的Raw资源 InputStream
SkinResources.getInstance().getRawInputStream(resId);
// 获取指定的Raw资源的byte[]
SkinResources.getInstance().getRawResourceBytes(resId);
```
#### 手动控制切换皮肤
```java
Skin skin = new Skin(
// 皮肤包 MD5 用于文件损坏校验的
"d5493244467d3970834e42dc1a6f07c9",
// 皮肤文件名称
"app-skin-debug.skin",
// 外网可访问的文件服务器地址
"https://carlife-static-1255510688.cos.ap-beijing.myqcloud.com/MoGoEagleEye/app-skin-debug.skin");
// 换肤
SkinManager.getInstance().selectSkin(this, skin);
// 还原
SkinManager.getInstance().loadSkin("");
```

View File

@@ -0,0 +1,44 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "${MOGO_REALTIME_VERSION}"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation rootProject.ext.dependencies.androidxappcompat
if (Boolean.valueOf(RELEASE)) {
implementation "com.mogo.cloud:ttsbase:${TTS_BASE_VERSION}"
}else {
implementation(project(":libraries:mogo-tts:tts-base"))
}
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,4 @@
GROUP=com.mogo.cloud
POM_ARTIFACT_ID=ttsmogo
VERSION_CODE=1
VERSION_NAME=1.0.1-SNAPSHOT

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.tts.offline">
</manifest>

View File

@@ -0,0 +1,34 @@
# Introduction
Model files are from
https://modelscope.cn/models/dengcunqin/matcha_tts_zh_en_20251010/summary
Note that you have to use
vocos-16khz-univ.onnx
You can download it from
https://modelscope.cn/models/dengcunqin/matcha_tts_zh_en_20251010/resolve/master/vocos-16khz-univ.onnx
or
https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos-16khz-univ.onnx
```
{'am': './model-steps-3.onnx', 'vocoder': './vocos-16khz-univ.onnx', 'tokens': './tokens.txt', 'lexicon': './lexicon.txt', 'text': '中英文合成测试. It supports both English 和中文合成', 'out_wav': 'generated.wav'}
{'use_eos_bos': '1', 'modelscope_url': 'https://modelscope.cn/models/dengcunqin/matcha_tts_zh_en_20251010', 'sample_rate': '16000', 'language': 'chinese English', 'model_type': 'matcha-tts', 'n_speakers': '1', 'model_author': 'dengcunqin', 'version': '1', 'pad_id': '0', 'voice': 'zh en-us', 'demo_url': 'https://www.tulingyun.com/tts.html', 'num_ode_steps': '3'}
NodeArg(name='x', type='tensor(int64)', shape=['N', 'L'])
NodeArg(name='x_length', type='tensor(int64)', shape=['N'])
NodeArg(name='noise_scale', type='tensor(float)', shape=[1])
NodeArg(name='length_scale', type='tensor(float)', shape=[1])
-----
NodeArg(name='mel', type='tensor(float)', shape=['N', 80, 'L'])
vocos {'modelscope_url': 'https://modelscope.cn/models/dengcunqin/matcha_tts_zh_en_20251010', 'use_eos_bos': '1', 'n_speakers': '1', 'sample_rate': '16000', 'pad_id': '0', 'language': 'chinese English', 'model_type': 'matcha-tts vocos', 'voice': 'zh en-us', 'version': '1', 'demo_url': 'https://www.tulingyun.com/tts.html', 'model_author': 'dengcunqin'}
----------vocos----------
NodeArg(name='mels', type='tensor(float)', shape=['batch_size', 80, 'time'])
-----
NodeArg(name='mag', type='tensor(float)', shape=['batch_size', 'Clipmag_dim_1', 'time'])
NodeArg(name='x', type='tensor(float)', shape=['batch_size', 'Cosx_dim_1', 'time'])
NodeArg(name='y', type='tensor(float)', shape=['batch_size', 'Cosx_dim_1', 'time'])
```

Some files were not shown because too many files have changed in this diff Show More