Compare commits
10 Commits
ec17b6d633
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d87dd7d6a5 | ||
|
|
8d5f093355 | ||
|
|
724fbbadb0 | ||
|
|
2c4cf902af | ||
|
|
63ac5bb7e2 | ||
|
|
e4bd908714 | ||
|
|
76caabd295 | ||
|
|
9af378cec7 | ||
|
|
964aaa9edc | ||
|
|
ec36a966f5 |
@@ -5,7 +5,7 @@ ext {
|
|||||||
|
|
||||||
compileSdkVersion: 34,
|
compileSdkVersion: 34,
|
||||||
buildToolsVersion: "29.0.2",
|
buildToolsVersion: "29.0.2",
|
||||||
minSdkVersion : 19,
|
minSdkVersion : 21,
|
||||||
targetSdkVersion : 29,
|
targetSdkVersion : 29,
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -11,17 +11,19 @@ import okhttp3.Response
|
|||||||
class HttpHeaderInterceptor : Interceptor {
|
class HttpHeaderInterceptor : Interceptor {
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val original = chain.request()
|
val original = chain.request()
|
||||||
|
val aiCloudClientConfig = MoGoAiCloudClient.getInstance().aiCloudClientConfig
|
||||||
|
val iHttpCurrentLocation = aiCloudClientConfig.iHttpCurrentLocation.currentLocation
|
||||||
var request = original.newBuilder()
|
var request = original.newBuilder()
|
||||||
.header("token", MoGoAiCloudClient.getInstance().aiCloudClientConfig.token)
|
.header("token", aiCloudClientConfig.token)
|
||||||
.header("cityCode", MoGoAiCloudClient.getInstance().aiCloudClientConfig.iHttpCurrentLocation.currentLocation?.cityCode.toString())
|
.header("cityCode", iHttpCurrentLocation?.cityCode.toString())
|
||||||
.header("lat",MoGoAiCloudClient.getInstance().aiCloudClientConfig.iHttpCurrentLocation.currentLocation?.lat.toString())
|
.header("lat", iHttpCurrentLocation?.lat.toString())
|
||||||
.header("lon",MoGoAiCloudClient.getInstance().aiCloudClientConfig.iHttpCurrentLocation.currentLocation?.lon.toString())
|
.header("lon", iHttpCurrentLocation?.lon.toString())
|
||||||
.header("env",MoGoAiCloudClient.getInstance().aiCloudClientConfig.netMode.toString())
|
.header("env", aiCloudClientConfig.netMode.toString())
|
||||||
.header("sn",MoGoAiCloudClient.getInstance().aiCloudClientConfig.sn)
|
.header("sn", aiCloudClientConfig.sn)
|
||||||
.method(original.method(), original.body())
|
.method(original.method(), original.body())
|
||||||
.build()
|
.build()
|
||||||
if(MoGoAiCloudClient.getInstance().aiCloudClientConfig.securityKey.isNotBlank()){
|
if(aiCloudClientConfig.securityKey.isNotBlank()){
|
||||||
request = request.newBuilder().header("authKey",MoGoAiCloudClient.getInstance().aiCloudClientConfig.securityKey).build()
|
request = request.newBuilder().header("authKey", aiCloudClientConfig.securityKey).build()
|
||||||
}
|
}
|
||||||
return chain.proceed(request)
|
return chain.proceed(request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_DEV;
|
||||||
import static com.mogo.cloud.passport.MoGoAiCloudClientConfig.HTTP_DNS_ENV_QA;
|
import static com.mogo.cloud.passport.MoGoAiCloudClientConfig.HTTP_DNS_ENV_QA;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
public class ThirdPassportManager {
|
public class ThirdPassportManager {
|
||||||
|
|
||||||
private static volatile ThirdPassportManager mInstance;
|
private static volatile ThirdPassportManager mInstance;
|
||||||
private IPassport mPassport;
|
private IPassport mPassport;
|
||||||
|
|
||||||
|
private AtomicBoolean isRequestIng = new AtomicBoolean(false);
|
||||||
|
|
||||||
private ThirdPassportManager() {
|
private ThirdPassportManager() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -37,9 +40,13 @@ public class ThirdPassportManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void refreshToken(Context context, IPassport passport) {
|
public void refreshToken(Context context, IPassport passport) {
|
||||||
|
if(isRequestIng.get()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.mPassport = passport;
|
this.mPassport = passport;
|
||||||
MoGoAiCloudClientConfig mAiCloudClientConfig = MoGoAiCloudClient.getInstance().getAiCloudClientConfig();
|
MoGoAiCloudClientConfig mAiCloudClientConfig = MoGoAiCloudClient.getInstance().getAiCloudClientConfig();
|
||||||
if (mAiCloudClientConfig != null) {
|
if (mAiCloudClientConfig != null) {
|
||||||
|
isRequestIng.set(true);
|
||||||
ThirdLoginParam thirdLoginParam = ThirdLoginParam.of(
|
ThirdLoginParam thirdLoginParam = ThirdLoginParam.of(
|
||||||
mAiCloudClientConfig.getThirdPartyDeviceId(),
|
mAiCloudClientConfig.getThirdPartyDeviceId(),
|
||||||
mAiCloudClientConfig.getThirdPartyAppKey()
|
mAiCloudClientConfig.getThirdPartyAppKey()
|
||||||
@@ -48,6 +55,7 @@ public class ThirdPassportManager {
|
|||||||
LoginCallback loginCallback = new LoginCallback() {
|
LoginCallback loginCallback = new LoginCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(TokenData.TokenResult result) {
|
public void onSuccess(TokenData.TokenResult result) {
|
||||||
|
isRequestIng.set(false);
|
||||||
if (mPassport != null) {
|
if (mPassport != null) {
|
||||||
mPassport.onSuccess(result.token, result.sn);
|
mPassport.onSuccess(result.token, result.sn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,23 +38,23 @@ RELEASE=true
|
|||||||
# 工具类
|
# 工具类
|
||||||
MOGO_UTILS_VERSION=1.4.7.62
|
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
|
# v2x
|
||||||
MOGO_V2X_VERSION=1.4.7.62
|
MOGO_V2X_VERSION=1.4.7.62
|
||||||
# SKIN
|
# SKIN
|
||||||
@@ -65,6 +65,11 @@ SDK_NATIVE_VERSION=1.4.7.49.18-debug
|
|||||||
UPLOAD_PLUGIN_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
|
# for vanniktech
|
||||||
@@ -75,7 +80,7 @@ SONATYPE_HOST=S01
|
|||||||
RELEASE_SIGNING_ENABLED=false
|
RELEASE_SIGNING_ENABLED=false
|
||||||
# 组织名
|
# 组织名
|
||||||
GROUP=com.mogo.cloud
|
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
|
POM_URL=https://gitlab.zhidaoauto.com/SCA/L4HA/AndroidApp/support/countly-sdk-android
|
||||||
# 版本控制信息
|
# 版本控制信息
|
||||||
|
|||||||
1
libraries/mogo-tts/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
7
libraries/mogo-tts/app/EB5/a_keystore_make_eb5.sh
Normal 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
|
||||||
|
|
||||||
BIN
libraries/mogo-tts/app/EB5/eb5_system.keystore
Normal file
BIN
libraries/mogo-tts/app/EB5/platform.p12
Normal file
28
libraries/mogo-tts/app/EB5/platform.pem
Normal 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-----
|
||||||
BIN
libraries/mogo-tts/app/EB5/platform.pk8
Normal file
27
libraries/mogo-tts/app/EB5/platform.x509.pem
Normal 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-----
|
||||||
129
libraries/mogo-tts/app/build.gradle
Normal 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}\""
|
||||||
|
//}
|
||||||
|
|
||||||
33
libraries/mogo-tts/app/proguard-rules.pro
vendored
Normal 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.**{*;}
|
||||||
6
libraries/mogo-tts/app/readme.md
Normal 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.
|
||||||
|
|
||||||
45
libraries/mogo-tts/app/src/main/AndroidManifest.xml
Normal 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>
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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广播
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.mogo.tts.utils
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对PhoneUtils扩展一些方法,获取系统的参数
|
||||||
|
*/
|
||||||
|
object PhoneUtilsExtend {
|
||||||
|
|
||||||
|
fun getDevicesSn(): String {
|
||||||
|
return "name"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
libraries/mogo-tts/app/src/main/res/drawable/ic_message.png
Normal file
|
After Width: | Height: | Size: 167 B |
26
libraries/mogo-tts/app/src/main/res/layout/activity_main.xml
Normal 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>
|
||||||
BIN
libraries/mogo-tts/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
BIN
libraries/mogo-tts/app/src/main/res/mipmap-hdpi/icon.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
libraries/mogo-tts/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
6
libraries/mogo-tts/app/src/main/res/values/dimens.xml
Normal 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>
|
||||||
10
libraries/mogo-tts/app/src/main/res/values/strings.xml
Normal 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>
|
||||||
17
libraries/mogo-tts/app/src/main/res/values/styles.xml
Normal 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>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<base-config cleartextTrafficPermitted="true" />
|
||||||
|
</network-security-config>
|
||||||
0
libraries/mogo-tts/readme.md
Normal file
1
libraries/mogo-tts/tts-base/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
67
libraries/mogo-tts/tts-base/README.md
Normal 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("");
|
||||||
|
```
|
||||||
39
libraries/mogo-tts/tts-base/build.gradle
Normal 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()
|
||||||
0
libraries/mogo-tts/tts-base/consumer-rules.pro
Normal file
4
libraries/mogo-tts/tts-base/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
GROUP=com.mogo.cloud
|
||||||
|
POM_ARTIFACT_ID=ttsbase
|
||||||
|
VERSION_CODE=1
|
||||||
|
VERSION_NAME=1.0.1-SNAPSHOT
|
||||||
21
libraries/mogo-tts/tts-base/proguard-rules.pro
vendored
Normal 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
|
||||||
9
libraries/mogo-tts/tts-base/src/main/AndroidManifest.xml
Normal 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>
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 = tag,value = 回调)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.mogo.tts.common
|
||||||
|
|
||||||
|
enum class LanguageType(val langName: String) {
|
||||||
|
CHINESE("chinese"),
|
||||||
|
ENGLISH("english"),
|
||||||
|
KOREAN("korean")
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.mogo.tts.common.log;
|
||||||
|
|
||||||
|
public enum LogLevel {
|
||||||
|
info,warn,debug,error
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.mogo.tts.common.log;
|
||||||
|
|
||||||
|
public interface LogListener {
|
||||||
|
void writeLog(LogLevel logLevel,String tag,String message);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<attr name="skinTypeface" format="string" />
|
||||||
|
<!--防止大面积误伤原生View,这里做一个手动开启换肤的开关,来控制是否要在这个控件上使用-->
|
||||||
|
<attr name="isUseSkin" format="boolean" />
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">skin-core2</string>
|
||||||
|
</resources>
|
||||||
1
libraries/mogo-tts/tts-iflytek-offline/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
67
libraries/mogo-tts/tts-iflytek-offline/README.md
Normal 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("");
|
||||||
|
```
|
||||||
44
libraries/mogo-tts/tts-iflytek-offline/build.gradle
Normal 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()
|
||||||
4
libraries/mogo-tts/tts-iflytek-offline/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
GROUP=com.mogo.cloud
|
||||||
|
POM_ARTIFACT_ID=ttsiflytekoffline
|
||||||
|
VERSION_CODE=1
|
||||||
|
VERSION_NAME=1.0.1-SNAPSHOT
|
||||||
BIN
libraries/mogo-tts/tts-iflytek-offline/libs/AIKit.aar
Normal file
21
libraries/mogo-tts/tts-iflytek-offline/proguard-rules.pro
vendored
Normal 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
|
||||||
@@ -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>
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
1
libraries/mogo-tts/tts-mogo/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
67
libraries/mogo-tts/tts-mogo/README.md
Normal 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("");
|
||||||
|
```
|
||||||
44
libraries/mogo-tts/tts-mogo/build.gradle
Normal 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()
|
||||||
0
libraries/mogo-tts/tts-mogo/consumer-rules.pro
Normal file
4
libraries/mogo-tts/tts-mogo/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
GROUP=com.mogo.cloud
|
||||||
|
POM_ARTIFACT_ID=ttsmogo
|
||||||
|
VERSION_CODE=1
|
||||||
|
VERSION_NAME=1.0.1-SNAPSHOT
|
||||||
21
libraries/mogo-tts/tts-mogo/proguard-rules.pro
vendored
Normal 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
|
||||||
5
libraries/mogo-tts/tts-mogo/src/main/AndroidManifest.xml
Normal 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>
|
||||||
@@ -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'])
|
||||||
|
```
|
||||||