Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
A
aivoice_plugin
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
王苏进
aivoice_plugin
Commits
29fa1f76
提交
29fa1f76
authored
9月 11, 2024
作者:
edy
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 语音合成相关
上级
a5965975
显示空白字符变更
内嵌
并排
正在显示
13 个修改的文件
包含
1283 行增加
和
18 行删除
+1283
-18
AivoicePlugin.kt
...c/main/kotlin/com/example/aivoice_plugin/AivoicePlugin.kt
+57
-6
Podfile.lock
example/ios/Podfile.lock
+2
-2
main.dart
example/lib/main.dart
+24
-3
AivoicePlugin.m
ios/Classes/AivoicePlugin.m
+52
-3
SensitiveDefines.h
ios/Classes/SensitiveDefines.h
+2
-2
SensitiveDefines.m
ios/Classes/SensitiveDefines.m
+2
-2
TtsNovel.h
ios/Classes/TtsNovel.h
+41
-0
TtsNovel.m
ios/Classes/TtsNovel.m
+962
-0
VoiceAsr.h
ios/Classes/VoiceAsr.h
+2
-0
VoiceAsr.m
ios/Classes/VoiceAsr.m
+4
-0
aivoice_plugin.dart
lib/aivoice_plugin.dart
+45
-0
aivoice_plugin_method_channel.dart
lib/aivoice_plugin_method_channel.dart
+45
-0
aivoice_plugin_platform_interface.dart
lib/aivoice_plugin_platform_interface.dart
+45
-0
没有找到文件。
android/src/main/kotlin/com/example/aivoice_plugin/AivoicePlugin.kt
浏览文件 @
29fa1f76
...
...
@@ -16,20 +16,71 @@ class AivoicePlugin: FlutterPlugin, MethodCallHandler {
/// when the Flutter Engine is detached from the Activity
private
lateinit
var
channel
:
MethodChannel
override
fun
onAttachedToEngine
(
flutterPluginBinding
:
FlutterPlugin
.
FlutterPluginBinding
)
{
override
fun
onAttachedToEngine
(
@NonNull
flutterPluginBinding
:
FlutterPlugin
.
FlutterPluginBinding
)
{
channel
=
MethodChannel
(
flutterPluginBinding
.
binaryMessenger
,
"aivoice_plugin"
)
channel
.
setMethodCallHandler
(
this
)
}
override
fun
onMethodCall
(
call
:
MethodCall
,
result
:
Result
)
{
if
(
call
.
method
==
"getPlatformVersion"
)
{
result
.
success
(
"Android ${android.os.Build.VERSION.RELEASE}"
)
}
else
{
override
fun
onMethodCall
(
@NonNull
call
:
MethodCall
,
@NonNull
result
:
Result
)
{
when
(
call
.
method
)
{
"initEngine"
->
{
// 现有的实现
}
"stopEngine"
->
{
// 现有的实现
}
"uninitEngine"
->
{
// 现有的实现
}
"startOrStopEngine"
->
{
// 现有的实现
}
"prepareEnvironment"
->
{
// 现有的实现
}
"ttsStartEngineBtnClick"
->
{
// 空实现
result
.
success
(
null
)
}
"ttsSynthesis"
->
{
// 空实现
result
.
success
(
null
)
}
"ttsStopEngineBtnClicked"
->
{
// 空实现
result
.
success
(
null
)
}
"ttsPausePlayback"
->
{
// 空实现
result
.
success
(
null
)
}
"ttsResumePlayback"
->
{
// 空实现
result
.
success
(
null
)
}
"ttsInitEngine"
->
{
// 空实现
result
.
success
(
null
)
}
"ttsUnInitEngine"
->
{
// 空实现
result
.
success
(
null
)
}
"destoryTtsNovel"
->
{
// 空实现
result
.
success
(
null
)
}
"destoryAsrVoice"
->
{
// 空实现
result
.
success
(
null
)
}
else
->
{
result
.
notImplemented
()
}
}
}
override
fun
onDetachedFromEngine
(
binding
:
FlutterPlugin
.
FlutterPluginBinding
)
{
override
fun
onDetachedFromEngine
(
@NonNull
binding
:
FlutterPlugin
.
FlutterPluginBinding
)
{
channel
.
setMethodCallHandler
(
null
)
}
}
example/ios/Podfile.lock
浏览文件 @
29fa1f76
...
...
@@ -34,6 +34,6 @@ SPEC CHECKSUMS:
SpeechEngineToB: a49185c07a099cdc052de97218bc10dc4ff60152
TTNetworkManager: 47d93100d944e2ae807e035d8636df92fd5cc390
PODFILE CHECKSUM:
bde3e45995fad5550475b342803cb71575488751
PODFILE CHECKSUM:
0212500e480860ee905e9b132693e030f85d651b
COCOAPODS: 1.1
4.3
COCOAPODS: 1.1
5.2
example/lib/main.dart
浏览文件 @
29fa1f76
...
...
@@ -66,19 +66,40 @@ class _MyAppState extends State<MyApp> {
children:
[
TextButton
(
onPressed:
()
{
_aivoicePlugin
.
i
nitEngine
(
configMap
);
_aivoicePlugin
.
ttsI
nitEngine
(
configMap
);
},
child:
const
Text
(
'init'
)),
TextButton
(
onPressed:
()
{
_aivoicePlugin
.
startOrStopEngine
(
true
);
_aivoicePlugin
.
ttsStartEngineBtnClick
(
{
"text"
:
"引擎启动成功,收到该回调后,在单次合成场景下收到该回调时语音合成已经开始,同时数据字段为该次请求的请求 ID; 连续合成场景下还需要再发送合成指令,才真正的开始合成。"
});
},
child:
const
Text
(
'start'
)),
TextButton
(
onPressed:
()
{
_aivoicePlugin
.
stopEngine
();
_aivoicePlugin
.
ttsStopEngineBtnClicked
();
},
child:
const
Text
(
'stop'
)),
TextButton
(
onPressed:
()
{
_aivoicePlugin
.
ttsSynthesis
({});
},
child:
const
Text
(
'合成'
))
// TextButton(
// onPressed: () {
// _aivoicePlugin.initEngine(configMap);
// },
// child: const Text('init')),
// TextButton(
// onPressed: () {
// _aivoicePlugin.startOrStopEngine(true);
// },
// child: const Text('start')),
// TextButton(
// onPressed: () {
// _aivoicePlugin.stopEngine();
// },
// child: const Text('stop')),
],
),
),
...
...
ios/Classes/AivoicePlugin.m
浏览文件 @
29fa1f76
#import "AivoicePlugin.h"
#import "VoiceAsr.h"
#import "TtsNovel.h"
@interface
AivoicePlugin
()
<
FlutterStreamHandler
,
VoiceAsrDelegate
>
@interface
AivoicePlugin
()
<
FlutterStreamHandler
,
VoiceAsrDelegate
,
TtsNovelDelegate
>
@property
(
nonatomic
,
strong
)
FlutterEventSink
eventSink
;
@property
(
nonatomic
,
strong
)
VoiceAsr
*
voiceAsr
;
@property
(
nonatomic
,
strong
)
TtsNovel
*
ttsNovel
;
@property
(
nonatomic
,
strong
)
NSDictionary
*
config
;
@property
(
nonatomic
,
strong
)
NSDictionary
*
ttsNovelconfig
;
@end
@implementation
AivoicePlugin
...
...
@@ -40,14 +45,40 @@
[
self
.
voiceAsr
uninitEngine
];
result
(
nil
);
}
else
if
([
@"startOrStopEngine"
isEqualToString
:
call
.
method
])
{
// BOOL arg = [NSNumber numberWithBool:call.arguments];
[
self
.
voiceAsr
startEngineBtnClicked
];
result
(
nil
);
}
else
if
([
@"prepareEnvironment"
isEqualToString
:
call
.
method
])
{
[
VoiceAsr
prepareEnvironment
:
call
.
arguments
];
result
(
nil
);
}
else
if
([
@"ttsStartEngineBtnClick"
isEqualToString
:
call
.
method
])
{
[
self
.
ttsNovel
startEngineBtnClick
:
call
.
arguments
[
@"text"
]];
result
(
nil
);
}
else
if
([
@"ttsSynthesis"
isEqualToString
:
call
.
method
])
{
[
self
.
ttsNovel
synthesis
];
result
(
nil
);
}
else
if
([
@"ttsStopEngineBtnClicked"
isEqualToString
:
call
.
method
])
{
[
self
.
ttsNovel
stopEngineBtnClicked
];
result
(
nil
);
}
else
if
([
@"ttsPausePlayback"
isEqualToString
:
call
.
method
])
{
[
self
.
ttsNovel
pausePlayback
];
result
(
nil
);
}
else
if
([
@"ttsResumePlayback"
isEqualToString
:
call
.
method
])
{
[
self
.
ttsNovel
resumePlayback
];
result
(
nil
);
}
else
if
([
@"ttsInitEngine"
isEqualToString
:
call
.
method
])
{
self
.
ttsNovelconfig
=
call
.
arguments
;
[
self
.
ttsNovel
switchEngine
];
result
(
nil
);
}
else
if
([
@"ttsUnInitEngine"
isEqualToString
:
call
.
method
])
{
[
self
.
ttsNovel
switchEngine
];
result
(
nil
);
}
else
if
([
@"destoryTtsNovel"
isEqualToString
:
call
.
method
])
{
[
self
destoryTtsNovel
];
result
(
nil
);
}
else
if
([
@"destoryAsrVoice"
isEqualToString
:
call
.
method
])
{
[
self
destoryVoiceAsr
];
result
(
nil
);
}
else
{
result
(
FlutterMethodNotImplemented
);
}
...
...
@@ -70,12 +101,29 @@
}
}
-
(
void
)
destoryVoiceAsr
{
[
self
.
voiceAsr
destroyEngine
];
_voiceAsr
=
nil
;
}
-
(
void
)
destoryTtsNovel
{
[
self
.
ttsNovel
destroyEngine
];
_ttsNovel
=
nil
;
}
-
(
VoiceAsr
*
)
voiceAsr
{
if
(
!
_voiceAsr
)
{
_voiceAsr
=
[[
VoiceAsr
alloc
]
initWithDelegate
:
self
config
:
self
.
config
];
}
return
_voiceAsr
;
}
-
(
TtsNovel
*
)
ttsNovel
{
if
(
!
_ttsNovel
)
{
_ttsNovel
=
[[
TtsNovel
alloc
]
initWithDelegate
:
self
config
:
self
.
ttsNovelconfig
];
}
return
_ttsNovel
;
}
-
(
void
)
onRecieve
:
(
nonnull
NSDictionary
*
)
message
{
...
...
@@ -83,4 +131,5 @@
}
@end
ios/Classes/SensitiveDefines.h
浏览文件 @
29fa1f76
...
...
@@ -46,8 +46,8 @@ extern NSString* SDEF_AU_DEFAULT_URI;
extern
NSString
*
SDEF_TTS_DEFAULT_URI
;
extern
NSString
*
SDEF_TTS_DEFAULT_CLUSTER
;
extern
NSString
*
SDEF_TTS_DEFAULT_BACKEND_CLUSTER
;
//
extern NSString* SDEF_TTS_DEFAULT_ONLINE_VOICE;
//
extern NSString* SDEF_TTS_DEFAULT_ONLINE_VOICE_TYPE;
extern
NSString
*
SDEF_TTS_DEFAULT_ONLINE_VOICE
;
extern
NSString
*
SDEF_TTS_DEFAULT_ONLINE_VOICE_TYPE
;
extern
NSString
*
SDEF_TTS_DEFAULT_OFFLINE_VOICE
;
extern
NSString
*
SDEF_TTS_DEFAULT_OFFLINE_VOICE_TYPE
;
extern
NSString
*
SDEF_TTS_DEFAULT_ONLINE_LANGUAGE
;
...
...
ios/Classes/SensitiveDefines.m
浏览文件 @
29fa1f76
...
...
@@ -43,8 +43,8 @@ const NSString* SDEF_AU_DEFAULT_URI = @"/api/v1/sauc";
const
NSString
*
SDEF_TTS_DEFAULT_URI
=
@"/api/v1/tts/ws_binary"
;
const
NSString
*
SDEF_TTS_DEFAULT_CLUSTER
=
@"volcano_tts"
;
const
NSString
*
SDEF_TTS_DEFAULT_BACKEND_CLUSTER
=
@"YOUR TTS BACKEND CLUSTER"
;
//
const NSString* SDEF_TTS_DEFAULT_ONLINE_VOICE = @"灿灿";
//const NSString* SDEF_TTS_DEFAULT_ONLINE_VOICE_TYPE = @"BV213_w5H18f6VbKnhg3Ph
";
const
NSString
*
SDEF_TTS_DEFAULT_ONLINE_VOICE
=
@"灿灿"
;
const
NSString
*
SDEF_TTS_DEFAULT_ONLINE_VOICE_TYPE
=
@"BV002_streaming
"
;
//BV002_streaming BV021_PSj8BvWAZyepfUPB BV705_streaming BV115_H74MBi790rUFu993 BV213_w5H18f6VbKnhg3Ph
const
NSString
*
SDEF_TTS_DEFAULT_OFFLINE_VOICE
=
@"YOUR TTS OFFLINE VOICE"
;
const
NSString
*
SDEF_TTS_DEFAULT_OFFLINE_VOICE_TYPE
=
@"YOUR TTS OFFLINE VOICE TYPE"
;
...
...
ios/Classes/TtsNovel.h
0 → 100644
浏览文件 @
29fa1f76
//
// TtsNovel.h
// aivoice_plugin
//
// Created by edy on 2024/9/11.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol
TtsNovelDelegate
<
NSObject
>
-
(
void
)
onRecieve
:
(
NSDictionary
*
)
message
;
@end
@interface
TtsNovel
:
NSObject
-
(
instancetype
)
initWithDelegate
:(
id
<
TtsNovelDelegate
>
)
delegate
config
:(
NSDictionary
*
)
config
;
-
(
void
)
switchEngine
;
-
(
void
)
startEngineBtnClick
:(
NSString
*
)
text
;
-
(
void
)
stopEngineBtnClicked
;
-
(
void
)
synthesis
;
-
(
void
)
destroyEngine
;
-
(
void
)
pausePlayback
;
-
(
void
)
resumePlayback
;
@end
NS_ASSUME_NONNULL_END
ios/Classes/TtsNovel.m
0 → 100644
浏览文件 @
29fa1f76
//
// TtsNovel.m
// aivoice_plugin
//
// Created by edy on 2024/9/11.
//
#import "TtsNovel.h"
#include <CoreFoundation/CoreFoundation.h>
#include <objc/objc.h>
#import <SpeechEngineToB/SpeechEngine.h>
#import <AVFoundation/AVFoundation.h>
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "SensitiveDefines.h"
#import <SpeechEngineToB/SpeechResourceManager.h>
static
int
TTS_MAX_RETRY_COUNT
=
3
;
@interface
TtsNovel
()
<
TtsNovelDelegate
>
// Debug Path: 用于存放一些 SDK 相关的文件,比如模型、日志等
@property
(
strong
,
nonatomic
)
NSString
*
debugPath
;
// SpeechEngine
@property
(
strong
,
nonatomic
)
SpeechEngine
*
curEngine
;
// Engine State
@property
(
assign
,
nonatomic
)
BOOL
engineInited
;
@property
(
assign
,
nonatomic
)
BOOL
engineStarted
;
@property
(
assign
,
nonatomic
)
BOOL
engineErrorOccurred
;
@property
(
assign
,
nonatomic
)
BOOL
playerPaused
;
// Settings
@property
(
strong
,
nonatomic
)
Settings
*
settings
;
// 一些在线合成的配置
@property
(
strong
,
nonatomic
)
NSString
*
ttsAppId
;
@property
(
strong
,
nonatomic
)
NSString
*
ttsVoiceOnline
;
@property
(
strong
,
nonatomic
)
NSString
*
ttsVoiceTypeOnline
;
// 一些离线合成的配置
@property
(
strong
,
nonatomic
)
NSString
*
ttsVoiceOffline
;
@property
(
strong
,
nonatomic
)
NSString
*
ttsVoiceTypeOffline
;
// 小说模式相关
@property
(
assign
,
nonatomic
)
BOOL
ttsSynthesisFromPlayer
;
@property
(
assign
,
nonatomic
)
int
ttsSynthesisIndex
;
@property
(
assign
,
nonatomic
)
int
ttsPlayingIndex
;
@property
(
assign
,
nonatomic
)
double
ttsPlayingProgress
;
@property
(
strong
,
nonatomic
)
NSMutableArray
*
ttsSynthesisText
;
@property
(
strong
,
nonatomic
)
NSMutableDictionary
*
ttsSynthesisMap
;
@property
(
nonatomic
,
weak
)
id
<
TtsNovelDelegate
>
delegate
;
@property
(
strong
,
nonatomic
)
NSDictionary
*
config
;
@property
(
strong
,
nonatomic
)
NSString
*
textFromFlutter
;
@property
(
assign
,
nonatomic
)
int
ttsRetryCount
;
@end
@implementation
TtsNovel
-
(
instancetype
)
init
{
return
[
self
initWithDelegate
:
nil
config
:@{}];
}
-
(
instancetype
)
initWithDelegate
:
(
id
<
TtsNovelDelegate
>
)
delegate
config
:
(
NSDictionary
*
)
config
{
self
=
[
super
init
];
if
(
self
)
{
self
.
delegate
=
delegate
;
self
.
config
=
config
;
// 初始化和小说模式有关的字段
self
.
ttsSynthesisFromPlayer
=
FALSE
;
self
.
ttsSynthesisIndex
=
0
;
self
.
ttsPlayingIndex
=
-
1
;
self
.
ttsPlayingProgress
=
0
.
0
;
self
.
ttsSynthesisText
=
[[
NSMutableArray
alloc
]
init
];
self
.
ttsSynthesisMap
=
[[
NSMutableDictionary
alloc
]
init
];
self
.
debugPath
=
NSSearchPathForDirectoriesInDomains
(
NSDocumentDirectory
,
NSUserDomainMask
,
YES
).
firstObject
;
self
.
ttsRetryCount
=
TTS_MAX_RETRY_COUNT
;
NSLog
(
@"当前调试路径 %@"
,
self
.
debugPath
);
[[
NSNotificationCenter
defaultCenter
]
addObserver
:
self
selector
:
@selector
(
audioInterruptionHandler
:
)
name
:
AVAudioSessionInterruptionNotification
object
:
nil
];
self
.
settings
=
[
SettingsHelper
shareInstance
].
ttsSettings
;
}
return
self
;
}
#pragma mark - Config & Init & Uninit Methods
-
(
void
)
configInitParams
{
//【必需配置】Engine Name
[
self
.
curEngine
setStringParam
:
SE_TTS_ENGINE
forKey
:
SE_PARAMS_KEY_ENGINE_NAME_STRING
];
//【必需配置】Work Mode, 可选值如下
// SETtsWorkModeOnline, 只进行在线合成,不需要配置离线合成相关参数;
// SETtsWorkModeOffline, 只进行离线合成,不需要配置在线合成相关参数;
// SETtsWorkModeAlternate, 先发起在线合成,失败后(网络超时),启动离线合成引擎开始合成;
[
self
.
curEngine
setIntParam
:[
self
getTtsWorkMode
]
forKey
:
SE_PARAMS_KEY_TTS_WORK_MODE_INT
];
//【可选配置】Debug & Log
[
self
.
curEngine
setStringParam
:
self
.
debugPath
forKey
:
SE_PARAMS_KEY_DEBUG_PATH_STRING
];
[
self
.
curEngine
setStringParam
:
SE_LOG_LEVEL_DEBUG
forKey
:
SE_PARAMS_KEY_LOG_LEVEL_STRING
];
//【可选配置】User ID(用以辅助定位线上用户问题)
[
self
.
curEngine
setStringParam
:
SDEF_UID
forKey
:
SE_PARAMS_KEY_UID_STRING
];
// [self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
//【可选配置】是否将合成出的音频保存到设备上,为 true 时需要正确配置 PARAMS_KEY_TTS_AUDIO_PATH_STRING 才会生效
[
self
.
curEngine
setBoolParam
:[
self
.
settings
getBool
:
SETTING_TTS_ENABLE_DUMP
]
forKey
:
SE_PARAMS_KEY_TTS_ENABLE_DUMP_BOOL
];
// TTS 音频文件保存目录,必须在合成之前创建好且 APP 具有访问权限,保存的音频文件名格式为 tts_{reqid}.wav, {reqid} 是本次合成的请求 id
// PARAMS_KEY_TTS_ENABLE_DUMP_BOOL 配置为 true 的音频时为【必需配置】,否则为【可选配置】
[
self
.
curEngine
setStringParam
:
self
.
debugPath
forKey
:
SE_PARAMS_KEY_TTS_AUDIO_PATH_STRING
];
//【可选配置】合成出的音频的采样率,默认为 24000
[
self
.
curEngine
setIntParam
:[
self
.
settings
getInt
:
SETTING_TTS_SAMPLE_RATE
]
forKey
:
SE_PARAMS_KEY_TTS_SAMPLE_RATE_INT
];
//【可选配置】打断播放时使用多长时间淡出停止,单位:毫秒。默认值 0 表示不淡出
[
self
.
curEngine
setIntParam
:[
self
.
settings
getInt
:
SETTING_AUDIO_FADEOUT_DURATION
]
forKey
:
SE_PARAMS_KEY_AUDIO_FADEOUT_DURATION_INT
];
// ------------------------ 在线合成相关配置 -----------------------
// NSString* appid = [self.settings getString:SETTING_APPID];
self
.
ttsAppId
=
self
.
config
[
@"appId"
];
//【必需配置】在线合成鉴权相关:Appid
[
self
.
curEngine
setStringParam
:
self
.
ttsAppId
forKey
:
SE_PARAMS_KEY_APP_ID_STRING
];
// NSString* token = [self.settings getString:SETTING_TOKEN];
NSString
*
ttsAppToken
=
self
.
config
[
@"token"
];
//【必需配置】在线合成鉴权相关:Token
[
self
.
curEngine
setStringParam
:
ttsAppToken
forKey
:
SE_PARAMS_KEY_APP_TOKEN_STRING
];
//【必需配置】语音合成服务域名
NSString
*
address
=
[
self
.
settings
getString
:
SETTING_ADDRESS
];
NSString
*
ttsAddress
=
address
.
length
>
0
?
address
:
SDEF_DEFAULT_ADDRESS
;
[
self
.
curEngine
setStringParam
:
ttsAddress
forKey
:
SE_PARAMS_KEY_TTS_ADDRESS_STRING
];
//【必需配置】语音合成服务Uri
NSString
*
uri
=
[
self
.
settings
getString
:
SETTING_URI
];
NSString
*
ttsUri
=
uri
.
length
>
0
?
uri
:
SDEF_TTS_DEFAULT_URI
;
[
self
.
curEngine
setStringParam
:
ttsUri
forKey
:
SE_PARAMS_KEY_TTS_URI_STRING
];
//【必需配置】语音合成服务所用集群
NSString
*
cluster
=
SDEF_TTS_DEFAULT_CLUSTER
;
[
self
.
curEngine
setStringParam
:
cluster
forKey
:
SE_PARAMS_KEY_TTS_CLUSTER_STRING
];
//【可选配置】在线合成下发的 opus-ogg 音频的压缩倍率
[
self
.
curEngine
setIntParam
:
10
forKey
:
SE_PARAMS_KEY_TTS_COMPRESSION_RATE_INT
];
// ------------------------ 离线合成相关配置 -----------------------
// if ([self getTtsWorkMode] != SETtsWorkModeOnline && [self getTtsWorkMode] != SETtsWorkModeFile) {
// NSString* resourcePath = @"";
// if ([[self.settings getOptionsValue:SETTING_TTS_OFFLINE_RESOURCE_FORMAT] isEqual: @"SingleVoice"]) {
// resourcePath = [[SpeechResourceManager shareInstance] getModelPath];
// } else if ([[self.settings getOptionsValue:SETTING_TTS_OFFLINE_RESOURCE_FORMAT] isEqual: @"MultipleVoice"]) {
// NSString *model_name = [self.settings getString:SETTING_TTS_MODEL_NAME];
// resourcePath = [[SpeechResourceManager shareInstance] getModelPath:model_name];
// }
// NSLog(@"TTS resource root path: %@", resourcePath);
// //【必需配置】离线合成所需资源存放路径
// [self.curEngine setStringParam:resourcePath forKey:SE_PARAMS_KEY_TTS_OFF_RESOURCE_PATH_STRING];
// }
//
// //【必需配置】离线合成鉴权相关:证书文件存放路径
// [self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_LICENSE_DIRECTORY_STRING];
// NSString* authenticationType = [self getAuthenticationType];
// //【必需配置】Authenticate Type
// [self.curEngine setStringParam:authenticationType forKey:SE_PARAMS_KEY_AUTHENTICATE_TYPE_STRING];
// if ([authenticationType isEqualToString:SE_AUTHENTICATE_TYPE_PRE_BIND]) {
// // 按包名授权,获取到授权的 APP 可以不限次数、不限设备数的使用离线合成
// NSString *licenseName = [self.settings getString:SETTING_LICENSE_NAME];
// NSString *licenseBusiId = [self.settings getString:SETTING_LICENSE_BUSI_ID];
// // 证书名和业务 ID, 离线合成鉴权相关,使用火山提供的证书下发服务时为【必需配置】, 否则为【无需配置】
// // 证书名,用于下载按报名授权的证书文件
// [self.curEngine setStringParam:licenseName forKey:SE_PARAMS_KEY_LICENSE_NAME_STRING];
// // 业务 ID, 用于下载按报名授权的证书文件
// [self.curEngine setStringParam:licenseBusiId forKey:SE_PARAMS_KEY_LICENSE_BUSI_ID_STRING];
// } else if ([authenticationType isEqualToString:SE_AUTHENTICATE_TYPE_LATE_BIND]) {
// // 按装机量授权,不限制 APP 的包名和使用次数,但是限制使用离线合成的设备数量
// //【必需配置】离线合成鉴权相关:Authenticate Address
// [self.curEngine setStringParam:SDEF_AUTHENTICATE_ADDRESS forKey:SE_PARAMS_KEY_AUTHENTICATE_ADDRESS_STRING];
// //【必需配置】离线合成鉴权相关:Authenticate Uri
// [self.curEngine setStringParam:SDEF_AUTHENTICATE_URI forKey:SE_PARAMS_KEY_AUTHENTICATE_URI_STRING];
// NSString* curBusinessKey = [self.settings getString:SETTING_BUSINESS_KEY];
// NSString* curAuthenticateSecret = [self.settings getString:SETTING_AUTHENTICATE_SECRET];
// //【必需配置】离线合成鉴权相关:Business Key
// [self.curEngine setStringParam:curBusinessKey forKey:SE_PARAMS_KEY_BUSINESS_KEY_STRING];
// //【必需配置】离线合成鉴权相关:Authenticate Secret
// [self.curEngine setStringParam:curAuthenticateSecret forKey:SE_PARAMS_KEY_AUTHENTICATE_SECRET_STRING];
// }
}
-
(
void
)
configStartTtsParams
{
//【必需配置】TTS 使用场景
[
self
.
curEngine
setStringParam
:
SE_TTS_SCENARIO_TYPE_NOVEL
forKey
:
SE_PARAMS_KEY_TTS_SCENARIO_STRING
];
// 准备待合成的小说文本
if
(
!
[
self
prepareNovelText
])
{
char
fake_error_info
[]
=
"{err_code:3006, err_msg:
\"
Invalid input text.
\"
}"
;
[
self
speechEngineError
:[
NSData
dataWithBytes
:
fake_error_info
length
:
sizeof
(
fake_error_info
)]];
return
;
}
//【可选配置】是否使用 SDK 内置播放器播放合成出的音频,默认为 true
[
self
.
curEngine
setBoolParam
:[
self
.
settings
getBool
:
SETTING_TTS_ENABLE_PLAYER
]
forKey
:
SE_PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL
];
//【可选配置】是否令 SDK 通过回调返回合成的音频数据,默认不返回。
// 开启后,SDK 会流式返回音频,收到 SETtsAudioData 回调表示当次合成所有的音频已经全部返回
[
self
.
curEngine
setIntParam
:[
self
.
settings
getBool
:
SETTING_TTS_ENABLE_DATA_CALLBACK
]
?
SETtsDataCallbackModeAll
:
SETtsDataCallbackModeNone
forKey
:
SE_PARAMS_KEY_TTS_DATA_CALLBACK_MODE_INT
];
}
-
(
void
)
configSynthesisParams
{
NSString
*
text
=
self
.
ttsSynthesisText
[
self
.
ttsSynthesisIndex
];
NSLog
(
@"Synthesis: %d, text: %@"
,
self
.
ttsSynthesisIndex
,
text
);
//【必需配置】需合成的文本,不可超过 80 字
[
self
.
curEngine
setStringParam
:
text
forKey
:
SE_PARAMS_KEY_TTS_TEXT_STRING
];
//【可选配置】需合成的文本的类型,支持直接传文本(TTS_TEXT_TYPE_PLAIN)和传 SSML 形式(TTS_TEXT_TYPE_SSML)的文本
[
self
.
curEngine
setStringParam
:[
self
getTtsTextType
]
forKey
:
SE_PARAMS_KEY_TTS_TEXT_TYPE_STRING
];
//【可选配置】用于控制 TTS 音频的语速,支持的配置范围参考火山官网 语音技术/语音合成/离在线语音合成SDK/参数说明 文档
[
self
.
curEngine
setDoubleParam
:[
self
.
settings
getInt
:
SETTING_TTS_SPEAK_SPEED
]
forKey
:
SE_PARAMS_KEY_TTS_SPEED_RATIO_DOUBLE
];
//【可选配置】用于控制 TTS 音频的音量,支持的配置范围参考火山官网 语音技术/语音合成/离在线语音合成SDK/参数说明 文档
// [self.curEngine setDoubleParam:[self.settings getDouble:SETTING_TTS_AUDIO_VOLUME] forKey:SE_PARAMS_KEY_TTS_VOLUME_RATIO_DOUBLE];
//【可选配置】用于控制 TTS 音频的音高,支持的配置范围参考火山官网 语音技术/语音合成/离在线语音合成SDK/参数说明 文档
// [self.curEngine setDoubleParam:[self.settings getDouble:SETTING_TTS_AUDIO_PITCH] forKey:SE_PARAMS_KEY_TTS_PITCH_RATIO_DOUBLE];
//【可选配置】是否在文本的每句结尾处添加静音段,单位:毫秒,默认为 0ms
[
self
.
curEngine
setIntParam
:[
self
.
settings
getInt
:
SETTING_TTS_SILENCE_DURATION
]
forKey
:
SE_PARAMS_KEY_TTS_SILENCE_DURATION_INT
];
// ------------------------ 在线合成相关配置 -----------------------
NSString
*
voiceOnline
=
SDEF_TTS_DEFAULT_ONLINE_VOICE
;
self
.
ttsVoiceOnline
=
voiceOnline
;
//【必需配置】在线合成使用的发音人代号
[
self
.
curEngine
setStringParam
:
self
.
ttsVoiceOnline
forKey
:
SE_PARAMS_KEY_TTS_VOICE_ONLINE_STRING
];
NSString
*
voiceTypeOnline
=
SDEF_TTS_DEFAULT_ONLINE_VOICE_TYPE
;
self
.
ttsVoiceTypeOnline
=
voiceTypeOnline
;
//【必需配置】在线合成使用的音色代号
[
self
.
curEngine
setStringParam
:
self
.
ttsVoiceTypeOnline
forKey
:
SE_PARAMS_KEY_TTS_VOICE_TYPE_ONLINE_STRING
];
//【可选配置】是否打开在线合成的服务端缓存,默认关闭
[
self
.
curEngine
setBoolParam
:[
self
.
settings
getBool
:
SETTING_TTS_ENABLE_CACHE
]
forKey
:
SE_PARAMS_KEY_TTS_ENABLE_CACHE_BOOL
];
//【可选配置】指定在线合成的语种,默认为空,即不指定
[
self
.
curEngine
setStringParam
:[
self
.
settings
getString
:
SETTING_TTS_ONLINE_LANGUAGE
]
forKey
:
SE_PARAMS_KEY_TTS_LANGUAGE_ONLINE_STRING
];
//【可选配置】是否启用在线合成的情感预测功能
[
self
.
curEngine
setBoolParam
:[
self
.
settings
getBool
:
SETTING_TTS_WITH_INTENT
]
forKey
:
SE_PARAMS_KEY_TTS_WITH_INTENT_BOOL
];
//【可选配置】指定在线合成的情感,例如 happy, sad 等
[
self
.
curEngine
setStringParam
:[
self
.
settings
getString
:
SETTING_TTS_EMOTION
]
forKey
:
SE_PARAMS_KEY_TTS_EMOTION_STRING
];
//【可选配置】需要返回详细的播放进度或需要启用断点续播功能时应配置为 1, 否则配置为 0 或不配置
[
self
.
curEngine
setIntParam
:
1
forKey
:
SE_PARAMS_KEY_TTS_WITH_FRONTEND_INT
];
//【可选配置】需要返回字粒度的播放进度时应配置为 simple, 同时要求 PARAMS_KEY_TTS_WITH_FRONTEND_INT 也配置为 1; 默认为空
// [self.curEngine setStringParam:[self.settings getBool:SETTING_TTS_ENABLE_WORD_LEVEL_PROGRESS_UPDATE] ? @"simple" : @"" forKey:SE_PARAMS_KEY_TTS_FRONTEND_TYPE_STRING];
//【可选配置】使用复刻音色
[
self
.
curEngine
setBoolParam
:[
self
.
settings
getBool
:
SETTING_TTS_USE_VOICECLONE_VOICE
]
forKey
:
SE_PARAMS_KEY_TTS_USE_VOICECLONE_BOOL
];
//【可选配置】在开启前述使用复刻音色的开关后,制定复刻音色所用的后端集群
[
self
.
curEngine
setStringParam
:[
self
.
settings
getString
:
SETTING_TTS_BACKEND_CLUSTER
]
forKey
:
SE_PARAMS_KEY_TTS_BACKEND_CLUSTER_STRING
];
// ------------------------ 离线合成相关配置 -----------------------
NSString
*
voiceOffline
=
[
self
.
settings
getString
:
SETTING_OFFLINE_VOICE
];
if
(
voiceOffline
.
length
<=
0
)
{
voiceOffline
=
[
self
.
settings
getOptionsValue
:
SETTING_OFFLINE_VOICE
];
}
self
.
ttsVoiceOffline
=
voiceOffline
;
//【必需配置】离线合成使用的发音人代号
[
self
.
curEngine
setStringParam
:
self
.
ttsVoiceOffline
forKey
:
SE_PARAMS_KEY_TTS_VOICE_OFFLINE_STRING
];
NSString
*
voiceTypeOffline
=
[
self
.
settings
getString
:
SETTING_OFFLINE_VOICE_TYPE
];
if
(
voiceTypeOffline
.
length
<=
0
)
{
voiceTypeOffline
=
[
self
.
settings
getOptionsValue
:
SETTING_OFFLINE_VOICE_TYPE
];
}
self
.
ttsVoiceTypeOffline
=
voiceTypeOffline
;
//【必需配置】离线合成使用的音色代号
[
self
.
curEngine
setStringParam
:
self
.
ttsVoiceTypeOffline
forKey
:
SE_PARAMS_KEY_TTS_VOICE_TYPE_OFFLINE_STRING
];
//【可选配置】是否降低离线合成的 CPU 利用率,默认关闭
// 打开该配置会使离线合成的实时率变大,仅当必要(例如为避免系统主动杀死CPU占用持续过高的进程)时才应开启
[
self
.
curEngine
setBoolParam
:[
self
.
settings
getBool
:
SETTING_TTS_LIMIT_CPU_USAGE
]
forKey
:
SE_PARAMS_KEY_TTS_LIMIT_CPU_USAGE_BOOL
];
}
-
(
void
)
initEngine
{
// NSLog(@"获取设备ID,调试使用");
// AppDelegate *appDelegate = [ViewController getAppDelegate];
// if (appDelegate == nil) {
// appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// }
// [ViewController setAppDelegate:appDelegate];
// self.deviceID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
// NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog
(
@"创建引擎"
);
if
(
self
.
curEngine
==
nil
)
{
self
.
curEngine
=
[[
SpeechEngine
alloc
]
init
];
if
(
!
[
self
.
curEngine
createEngineWithDelegate
:
self
])
{
NSLog
(
@"引擎创建失败."
);
return
;
}
}
NSLog
(
@"SDK 版本号: %@"
,
[
self
.
curEngine
getVersion
]);
if
([
self
getTtsWorkMode
]
==
SETtsWorkModeOnline
||
[
self
getTtsWorkMode
]
==
SETtsWorkModeFile
)
{
// 当使用纯在线模式时,不需要下载离线合成所需资源
[
self
initEngineInternal
];
}
else
{
// [self.statusTextView setText:@"Waiting for loading model."];
// 下载离线合成所需资源需要区分多音色资源和单音色资源,下载这两种资源所调用的方法略有不同
if
([[
self
.
settings
getOptionsValue
:
SETTING_TTS_OFFLINE_RESOURCE_FORMAT
]
isEqual
:
@"MultipleVoice"
])
{
// 多音色资源是指一个资源文件中包含了多个离线音色,这种资源一般是旧版(V2)离线合成所用资源
NSLog
(
@"当前所用资源类别为多音色资源,开始准备多音色资源"
);
[
self
prepareMultipleVoiceResource
];
}
else
if
([[
self
.
settings
getOptionsValue
:
SETTING_TTS_OFFLINE_RESOURCE_FORMAT
]
isEqual
:
@"SingleVoice"
])
{
// 单音色资源是指一个资源文件仅包含一个离线音色,新版(V4 及以上)离线合成用的就是单音色资源
NSLog
(
@"当前所用资源类别为单音色资源,开始准备单音色资源"
);
[
self
prepareSingleVoiceResource
];
}
}
}
-
(
void
)
prepareMultipleVoiceResource
{
// 因为多音色资源的一个文件包含了多个音色,导致资源的名字和音色的名字无法一一对应
// 所以下载资源需要显式指定资源名字
NSString
*
model_name
=
[
self
.
settings
getString
:
SETTING_TTS_MODEL_NAME
];
SpeechResourceManager
*
speechResourceManager
=
[
SpeechResourceManager
shareInstance
];
NSLog
(
@"检查本地是否存在可用模型"
);
if
(
!
[
speechResourceManager
checkModelExist
:
model_name
])
{
NSLog
(
@"本地没有模型,开始下载"
);
[
self
fetchMultipleVoiceResource
:
model_name
];
}
else
{
NSLog
(
@"模型存在,检查是否需要更新模型"
);
[
speechResourceManager
checkModelVersion
:
model_name
completion
:
^
(
SEResourceStatus
status
,
BOOL
needUpdate
,
NSData
*
data
)
{
if
(
status
!=
kSERSuccess
||
needUpdate
==
NO
)
{
NSLog
(
@"无需更新,直接使用本地已有模型。"
);
[
self
initEngineInternal
];
}
else
{
NSLog
(
@"存在更新,开始下载模型"
);
[
self
fetchMultipleVoiceResource
:
model_name
];
}
}];
}
}
-
(
void
)
fetchMultipleVoiceResource
:
(
NSString
*
)
model_name
{
NSLog
(
@"需要下载的模型名为 %@"
,
model_name
);
SpeechResourceManager
*
speechResourceManager
=
[
SpeechResourceManager
shareInstance
];
[
speechResourceManager
fetchModelByName
:
model_name
completion
:
^
(
SEResourceStatus
status
,
NSData
*
data
)
{
if
(
status
==
kSERSuccess
)
{
NSLog
(
@"下载成功"
);
[
self
initEngineInternal
];
}
else
{
NSLog
(
@"下载失败,错误码: %d"
,
status
);
[
self
speechEngineInitFailed
:
kSERDownloadFailed
];
}
}];
}
-
(
void
)
prepareSingleVoiceResource
{
SpeechResourceManager
*
speechResourceManager
=
[
SpeechResourceManager
shareInstance
];
NSString
*
offlineLanguage
=
[
self
.
settings
getString
:
SETTING_TTS_OFFLINE_LANGUAGE
];
if
(
offlineLanguage
.
length
<=
0
)
{
offlineLanguage
=
SDEF_TTS_DEFAULT_OFFLINE_LANGUAGE
;
}
NSArray
*
ttsLanguageArray
=
@[
offlineLanguage
];
NSLog
(
@"需要下载的离线合成语种资源有: %@"
,
ttsLanguageArray
);
[
speechResourceManager
setTtsLanguage
:
ttsLanguageArray
];
NSArray
*
needDownloadVoiceType
=
(
NSArray
*
)
SDEF_TTS_DEFAULT_DOWNLOAD_OFFLINE_VOICES
();
NSArray
*
voiceTypeArray
=
[
self
.
settings
getOptions
:
SETTING_OFFLINE_VOICE_TYPE
].
optionsArray
;
if
(
voiceTypeArray
!=
nil
&&
voiceTypeArray
.
count
>
0
)
{
needDownloadVoiceType
=
voiceTypeArray
;
}
NSLog
(
@"需要下载的离线合成音色资源有: %@"
,
needDownloadVoiceType
);
[
speechResourceManager
setTtsVoiceType
:
needDownloadVoiceType
];
NSLog
(
@"检查本地是否存在可用模型"
);
if
([
speechResourceManager
checkModelExist
])
{
NSLog
(
@"本地没有模型,开始下载"
);
[
self
fetchSingleVoiceResource
];
}
else
{
NSLog
(
@"模型存在,检查是否需要更新模型"
);
[
speechResourceManager
checkModelVersion
:
^
(
SEResourceStatus
status
,
BOOL
needUpdate
,
NSData
*
data
)
{
if
(
status
!=
kSERSuccess
||
needUpdate
==
NO
)
{
NSLog
(
@"无需更新,直接使用本地已有模型。"
);
[
self
initEngineInternal
];
}
else
{
NSLog
(
@"存在更新,开始下载模型"
);
[
self
fetchSingleVoiceResource
];
}
}];
}
}
-
(
void
)
fetchSingleVoiceResource
{
SpeechResourceManager
*
speechResourceManager
=
[
SpeechResourceManager
shareInstance
];
[
speechResourceManager
fetchModel
:
^
(
SEResourceStatus
status
,
NSData
*
data
)
{
if
(
status
==
kSERSuccess
)
{
NSLog
(
@"下载成功"
);
[
self
initEngineInternal
];
}
else
{
NSLog
(
@"下载失败,错误码: %d"
,
status
);
[
self
speechEngineInitFailed
:
kSERDownloadFailed
];
}
}];
}
-
(
void
)
initEngineInternal
{
NSLog
(
@"配置初始化参数"
);
[
self
configInitParams
];
NSLog
(
@"引擎初始化"
);
SEEngineErrorCode
ret
=
[
self
.
curEngine
initEngine
];
self
.
engineInited
=
(
ret
==
SENoError
);
if
(
self
.
engineInited
)
{
NSLog
(
@"初始化成功"
);
[
self
speechEngineInitSucceeded
];
}
else
{
NSLog
(
@"初始化失败,返回值: %d"
,
ret
);
[
self
speechEngineInitFailed
:
ret
];
}
}
-
(
void
)
uninitEngine
{
if
(
self
.
curEngine
!=
nil
)
{
NSLog
(
@"引擎析构"
);
[
self
.
curEngine
destroyEngine
];
self
.
curEngine
=
nil
;
NSLog
(
@"引擎析构完成"
);
}
}
-
(
void
)
destroyEngine
{
[
self
.
curEngine
destroyEngine
];
}
#pragma mark - UI Actions
-
(
void
)
switchEngine
{
if
(
self
.
engineStarted
)
{
// [self.statusTextView setText:@"Engine is busy, stop it first!"];
return
;
}
// [self clearResult:nil];
// self.startEngineButton.enabled = FALSE;
// self.synthesisButton.enabled = FALSE;
// self.pauseResumeButton.enabled = FALSE;
if
(
self
.
engineInited
)
{
// self.referTextView.editable = FALSE;
[
self
uninitEngine
];
self
.
engineInited
=
FALSE
;
// [self.statusTextView setText:@"Waiting for init."];
// self.engineSwitchButton.enabled = TRUE;
// [self.engineSwitchButton setTitle:@"Init Engine" forState:UIControlStateNormal];
// self.stopEngineButton.enabled = FALSE;
}
else
{
// self.referTextView.editable = TRUE;
[
self
initEngine
];
}
}
-
(
void
)
synthesis
{
[
self
triggerSynthesis
];
}
-
(
void
)
startEngineBtnClick
:
(
NSString
*
)
text
{
self
.
textFromFlutter
=
text
;
NSLog
(
@"Start engine, current status: %d"
,
self
.
engineStarted
);
if
(
!
self
.
engineStarted
)
{
// [self clearResult:nil];
self
.
engineErrorOccurred
=
FALSE
;
// Directive:启动引擎前调用SYNC_STOP指令,保证前一次请求结束。
NSLog
(
@"关闭引擎(同步)"
);
NSLog
(
@"Directive: SEDirectiveSyncStopEngine"
);
SEEngineErrorCode
ret
=
[
self
.
curEngine
sendDirective
:
SEDirectiveSyncStopEngine
];
if
(
ret
!=
SENoError
)
{
NSLog
(
@"Send directive syncstop failed: %d"
,
ret
);
}
else
{
[
self
configStartTtsParams
];
NSLog
(
@"启动引擎."
);
NSLog
(
@"Directive: SEDirectiveStartEngine"
);
SEEngineErrorCode
ret
=
[
self
.
curEngine
sendDirective
:
SEDirectiveStartEngine
];
if
(
SENoError
!=
ret
)
{
NSString
*
message
=
[
NSString
stringWithFormat
:
@"发送启动引擎指令失败: %d"
,
ret
];
[
self
sendStartEngineDirectiveFailed
:
message
];
}
}
}
}
-
(
void
)
stopEngineBtnClicked
{
NSLog
(
@"关闭引擎"
);
NSLog
(
@"Directive: SEDirectiveStopEngine"
);
[
self
.
curEngine
sendDirective
:
SEDirectiveStopEngine
];
}
-
(
void
)
pausePlayback
{
NSLog
(
@"暂停播放"
);
NSLog
(
@"Directive: SEDirectivePausePlayer"
);
SEEngineErrorCode
ret
=
[
self
.
curEngine
sendDirective
:
SEDirectivePausePlayer
];
if
(
ret
==
SENoError
)
{
self
.
playerPaused
=
TRUE
;
// [self.pauseResumeButton setTitle:@"Resume" forState:UIControlStateNormal];
}
NSLog
(
@"Pause playback status: %d"
,
ret
);
}
-
(
void
)
resumePlayback
{
NSLog
(
@"继续播放"
);
NSLog
(
@"Directive: SEDirectiveResumePlayer"
);
SEEngineErrorCode
ret
=
[
self
.
curEngine
sendDirective
:
SEDirectiveResumePlayer
];
if
(
ret
==
SENoError
)
{
self
.
playerPaused
=
FALSE
;
// [self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
}
NSLog
(
@"Resume playback status: %d"
,
ret
);
}
#pragma mark - Message Callback
-
(
void
)
onMessageWithType
:
(
SEMessageType
)
type
andData
:
(
NSData
*
)
data
{
NSLog
(
@"Message Type: %d."
,
type
);
switch
(
type
)
{
case
SEEngineStart
:
NSLog
(
@"Callback: 引擎启动成功: data: %@"
,
data
);
[
self
speechEngineStarted
];
break
;
case
SEEngineStop
:
NSLog
(
@"Callback: 引擎关闭: data: %@"
,
data
);
[
self
speechEngineStopped
];
break
;
case
SEEngineError
:
NSLog
(
@"Callback: 错误信息: %@"
,
data
);
[
self
speechEngineError
:
data
];
break
;
case
SETtsSynthesisBegin
:
NSLog
(
@"Callback: 合成开始: %@"
,
data
);
[
self
speechStartSynthesis
:
data
];
break
;
case
SETtsSynthesisEnd
:
NSLog
(
@"Callback: 合成结束: %@"
,
data
);
[
self
speechFinishSynthesis
:
data
];
break
;
case
SETtsStartPlaying
:
NSLog
(
@"Callback: 播放开始: %@"
,
data
);
[
self
speechStartPlaying
:
data
];
break
;
case
SETtsPlaybackProgress
:
NSLog
(
@"Callback: 播放进度"
);
[
self
updatePlayingProgress
:
data
];
break
;
case
SETtsFinishPlaying
:
NSLog
(
@"Callback: 播放结束: %@"
,
data
);
[
self
speechFinishPlaying
:
data
];
break
;
case
SETtsAudioData
:
NSLog
(
@"Callback: 音频数据,长度 %lu 字节"
,
(
unsigned
long
)
data
.
length
);
[
self
speechTtsAudioData
:
data
];
break
;
default
:
break
;
}
}
-
(
void
)
speechEngineInitSucceeded
{
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// self.engineSwitchButton.enabled = TRUE;
// [self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
// [self.statusTextView setText:@"Ready"];
// [self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@.", self.deviceID]];
// self.referTextView.editable = TRUE;
// self.startEngineButton.enabled = TRUE;
});
}
-
(
void
)
speechEngineInitFailed
:
(
int
)
initStatus
{
dispatch_async
(
dispatch_get_main_queue
(),
^
{
[
self
uninitEngine
];
// [self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
// self.engineSwitchButton.enabled = TRUE;
});
}
-
(
void
)
sendSynthesisDirectiveFailed
:
(
NSString
*
)
tipText
{
NSLog
(
@"%@"
,
tipText
);
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// [self.resultTextView setText:tipText];
[
self
.
curEngine
sendDirective
:
SEDirectiveStopEngine
];
});
}
-
(
void
)
sendStartEngineDirectiveFailed
:
(
NSString
*
)
tipText
{
NSLog
(
@"%@"
,
tipText
);
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// [self.resultTextView setText:tipText];
self
.
engineStarted
=
FALSE
;
});
}
-
(
void
)
speechEngineStarted
{
self
.
ttsRetryCount
=
TTS_MAX_RETRY_COUNT
;
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// self.referTextView.editable = FALSE;
self
.
engineStarted
=
true
;
// [self.statusTextView setText:@"Engine Started!"];
// self.startEngineButton.enabled = FALSE;
// self.synthesisButton.enabled = TRUE;
// self.stopEngineButton.enabled = TRUE;
});
}
-
(
void
)
speechEngineStopped
{
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// self.referTextView.editable = TRUE;
self
.
engineStarted
=
FALSE
;
// [self.statusTextView setText:@"Engine Stopped!"];
// self.startEngineButton.enabled = TRUE;
// self.synthesisButton.enabled = FALSE;
// self.stopEngineButton.enabled = FALSE;
// [self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
// self.pauseResumeButton.enabled = FALSE;
self
.
playerPaused
=
FALSE
;
});
}
-
(
void
)
speechEngineError
:
(
NSData
*
)
data
{
dispatch_async
(
dispatch_get_main_queue
(),
^
{
BOOL
needStop
=
NO
;
id
json_obj
=
[
NSJSONSerialization
JSONObjectWithData
:
data
options
:
NSJSONReadingMutableContainers
error
:
nil
];
if
([
json_obj
isKindOfClass
:[
NSDictionary
class
]])
{
NSDictionary
*
error_info
=
json_obj
;
NSInteger
code
=
[[
error_info
objectForKey
:
@"err_code"
]
intValue
];
switch
(
code
)
{
case
SETTSLimitQps
:
case
SETTSLimitCount
:
case
SETTSServerBusy
:
case
SETTSLongText
:
case
SETTSInvalidText
:
case
SETTSSynthesisTimeout
:
case
SETTSSynthesisError
:
case
SETTSSynthesisWaitingTimeout
:
case
SETTSErrorUnknown
:
NSLog
(
@"When meeting this kind of error, continue to synthesize."
);
[
self
synthesisNextSentence
];
break
;
case
SEConnectTimeout
:
case
SEReceiveTimeout
:
case
SENetLibError
:
// 遇到网络错误时建议重试,重试次数不超过 3 次
needStop
=
!
[
self
retrySynthesis
];
if
(
needStop
)
{
self
.
engineErrorOccurred
=
TRUE
;
}
break
;
default
:
needStop
=
YES
;
self
.
engineErrorOccurred
=
TRUE
;
// [self.resultTextView
// setText:[[NSString alloc]
// initWithData:data
// encoding:NSUTF8StringEncoding]];
break
;
}
}
else
{
needStop
=
YES
;
}
if
(
needStop
)
{
[
self
.
curEngine
sendDirective
:
SEDirectiveStopEngine
];
}
});
}
// 根据 SDK 返回的播放进度高亮正在播放的文本,用红色表示
// 根据 SDK 返回的合成开始和合成结束回调高亮正在合成的文本,用蓝色表示
-
(
void
)
updateTtsResultText
:
(
NSString
*
)
playingId
{
if
(
self
.
engineErrorOccurred
)
{
NSLog
(
@"When a fatal error occurs, prevent the playback text from being displayed."
);
return
;
}
NSNumber
*
val
=
[
self
.
ttsSynthesisMap
objectForKey
:
playingId
];
if
(
val
!=
nil
)
{
self
.
ttsPlayingIndex
=
[
val
intValue
];
}
int
beginIndex
=
MAX
(
self
.
ttsPlayingIndex
,
0
);
int
maxSentencesDisplayed
=
MIN
((
int
)[
self
.
ttsSynthesisText
count
],
16
);
NSMutableAttributedString
*
resultStr
=
[[
NSMutableAttributedString
alloc
]
initWithString
:
@""
];
for
(
int
cnt
=
0
;
cnt
<
maxSentencesDisplayed
;
++
cnt
)
{
int
index
=
(
beginIndex
+
cnt
)
%
[
self
.
ttsSynthesisText
count
];
NSString
*
current_sentence
=
self
.
ttsSynthesisText
[
index
];
NSInteger
playedPosition
=
0
;
if
(
index
==
self
.
ttsPlayingIndex
)
{
playedPosition
=
MIN
(
ceil
((
double
)(
self
.
ttsPlayingProgress
)
*
(
double
)([
current_sentence
length
])),
[
current_sentence
length
]);
NSLog
(
@"played position: %ld"
,
(
long
)
playedPosition
);
NSString
*
playedString
=
[
current_sentence
substringToIndex
:
playedPosition
];
NSAttributedString
*
playedSpan
=
[[
NSAttributedString
alloc
]
initWithString
:
playedString
attributes
:[
NSDictionary
dictionaryWithObject
:[
UIColor
redColor
]
forKey
:
NSForegroundColorAttributeName
]];
[
resultStr
appendAttributedString
:
playedSpan
];
}
NSString
*
remainString
=
[
current_sentence
substringFromIndex
:
playedPosition
];
NSAttributedString
*
span
=
[[
NSAttributedString
alloc
]
initWithString
:
remainString
attributes
:[
NSDictionary
dictionaryWithObject
:[
UIColor
blackColor
]
forKey
:
NSForegroundColorAttributeName
]];
[
resultStr
appendAttributedString
:
span
];
}
// [self.resultTextView setAttributedText:resultStr];
}
-
(
void
)
speechStartSynthesis
:
(
NSData
*
)
data
{
if
(
self
.
ttsSynthesisIndex
<
[
self
.
ttsSynthesisText
count
])
{
NSString
*
req_id
=
[[
NSString
alloc
]
initWithData
:
data
encoding
:
NSUTF8StringEncoding
];
[
self
.
ttsSynthesisMap
setValue
:[
NSNumber
numberWithInt
:
self
.
ttsSynthesisIndex
]
forKey
:
req_id
];
}
// dispatch_async(dispatch_get_main_queue(), ^{
// self.synthesisButton.enabled = FALSE;
// });
}
-
(
void
)
speechFinishSynthesis
:
(
NSData
*
)
data
{
if
(
self
.
ttsRetryCount
<
TTS_MAX_RETRY_COUNT
)
{
self
.
ttsRetryCount
=
TTS_MAX_RETRY_COUNT
;
}
[
self
synthesisNextSentence
];
}
-
(
void
)
speechStartPlaying
:
(
NSData
*
)
data
{
NSString
*
playingId
=
[[
NSString
alloc
]
initWithData
:
data
encoding
:
NSUTF8StringEncoding
];
NSLog
(
@"TTS start playing: %@"
,
playingId
);
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// self.pauseResumeButton.enabled = TRUE;
self
.
ttsPlayingProgress
=
0
.
0
;
// [self updateTtsResultText:playingId];
});
}
-
(
void
)
updatePlayingProgress
:
(
NSData
*
)
data
{
if
(
data
!=
nil
)
{
NSError
*
error
=
nil
;
id
object
=
[
NSJSONSerialization
JSONObjectWithData
:
data
options
:
0
error
:&
error
];
if
(
error
)
{
NSLog
(
@"Parse data as json error!"
);
return
;
}
if
([
object
isKindOfClass
:[
NSDictionary
class
]])
{
NSDictionary
*
results
=
object
;
float
percentage
=
[[
results
valueForKey
:
@"progress"
]
floatValue
];
NSString
*
reqid
=
[
results
valueForKey
:
@"reqid"
];
NSLog
(
@"playing id: %@, progress in percent: %.2f"
,
reqid
,
percentage
);
dispatch_async
(
dispatch_get_main_queue
(),
^
{
self
.
ttsPlayingProgress
=
percentage
;
[
self
updateTtsResultText
:
reqid
];
});
}
}
}
-
(
void
)
speechFinishPlaying
:
(
NSData
*
)
data
{
NSString
*
playingId
=
[[
NSString
alloc
]
initWithData
:
data
encoding
:
NSUTF8StringEncoding
];
NSLog
(
@"TTS finish playing: %@"
,
playingId
);
// if (![self.settings getBool:SETTING_TTS_ENABLE_DUMP_NOVEL_TTS_DETAIL]) {
// [self.ttsSynthesisMap removeObjectForKey:playingId];
// }
dispatch_async
(
dispatch_get_main_queue
(),
^
{
self
.
ttsPlayingProgress
=
1
.
0
;
[
self
updateTtsResultText
:
playingId
];
});
if
(
self
.
ttsSynthesisFromPlayer
)
{
if
(
self
.
ttsSynthesisIndex
==
(
self
.
ttsSynthesisText
.
count
-
1
))
{
[
self
stopEngineBtnClicked
];
}
else
{
[
self
triggerSynthesis
];
self
.
ttsSynthesisFromPlayer
=
FALSE
;
}
}
}
-
(
void
)
speechTtsAudioData
:
(
NSData
*
)
data
{
}
-
(
BOOL
)
retrySynthesis
{
BOOL
ret
=
FALSE
;
if
(
self
.
engineStarted
&&
self
.
ttsRetryCount
>
0
)
{
NSLog
(
@"Retry synthesis for text: %@"
,
self
.
ttsSynthesisText
[
self
.
ttsSynthesisIndex
]);
dispatch_after
(
dispatch_time
(
DISPATCH_TIME_NOW
,
NSEC_PER_SEC
),
dispatch_get_main_queue
(),
^
{
[
self
triggerSynthesis
];
});
self
.
ttsRetryCount
-=
1
;
ret
=
TRUE
;
}
return
ret
;
}
-
(
void
)
synthesisNextSentence
{
self
.
ttsSynthesisIndex
=
(
self
.
ttsSynthesisIndex
+
1
)
%
[
self
.
ttsSynthesisText
count
];
if
(
!
self
.
ttsSynthesisFromPlayer
)
{
[
self
triggerSynthesis
];
}
}
-
(
void
)
triggerSynthesis
{
[
self
configSynthesisParams
];
// DIRECTIVE_SYNTHESIS 是连续合成必需的一个指令,在成功调用 DIRECTIVE_START_ENGINE 之后,每次合成新的文本需要再调用 DIRECTIVE_SYNTHESIS 指令
// DIRECTIVE_SYNTHESIS 需要在当前没有正在合成的文本时才可以成功调用,否则就会报错 -901,可以在收到 MESSAGE_TYPE_TTS_SYNTHESIS_END 之后调用
// 当使用 SDK 内置的播放器时,为了避免缓存过多的音频导致内存占用过高,SDK 内部限制缓存的音频数量不超过 5 次合成的结果,
// 如果 DIRECTIVE_SYNTHESIS 后返回 -902, 就需要在下一次收到 MESSAGE_TYPE_TTS_FINISH_PLAYING 再去调用 MESSAGE_TYPE_TTS_FINISH_PLAYING
NSLog
(
@"触发合成"
);
NSLog
(
@"Directive: DIRECTIVE_SYNTHESIS"
);
SEEngineErrorCode
ret
=
[
self
.
curEngine
sendDirective
:
SEDirectiveSynthesis
];
if
(
ret
!=
SENoError
)
{
NSLog
(
@"Synthesis faile: %d"
,
ret
);
if
(
ret
==
SESynthesisPlayerIsBusy
)
{
self
.
ttsSynthesisFromPlayer
=
TRUE
;
}
else
{
NSString
*
message
=
[
NSString
stringWithFormat
:
@"发送合成指令失败: %d"
,
ret
];
[
self
sendSynthesisDirectiveFailed
:
message
];
}
}
}
-
(
void
)
addSentence
:
(
NSString
*
)
text
{
NSCharacterSet
*
blankChar
=
[
NSCharacterSet
characterSetWithCharactersInString
:
@" "
];
NSString
*
tmp
=
[
text
stringByTrimmingCharactersInSet
:
blankChar
];
if
(
tmp
.
length
>
0
)
{
[
self
.
ttsSynthesisText
addObject
:
tmp
];
}
}
-
(
void
)
resetTtsContext
{
self
.
ttsSynthesisIndex
=
0
;
self
.
ttsPlayingIndex
=
-
1
;
self
.
ttsSynthesisFromPlayer
=
FALSE
;
[
self
.
ttsSynthesisText
removeAllObjects
];
[
self
.
ttsSynthesisMap
removeAllObjects
];
}
-
(
BOOL
)
prepareNovelText
{
[
self
resetTtsContext
];
NSString
*
text
=
self
.
textFromFlutter
;
if
(
self
.
ttsSynthesisText
==
nil
||
[
self
.
ttsSynthesisText
count
]
<=
0
)
{
// 使用下面几个标点符号来分句,会让通过 MESSAGE_TYPE_TTS_PLAYBACK_PROGRESS 返回的播放进度更加准确
NSArray
*
temp
=
[
text
componentsSeparatedByCharactersInSet
:[
NSCharacterSet
characterSetWithCharactersInString
:
@";!?。!?;…"
]];
for
(
int
j
=
0
;
j
<
temp
.
count
;
++
j
)
{
[
self
addSentence
:
temp
[
j
]];
}
}
NSLog
(
@"Synthesis text item num: %ld."
,
[
self
.
ttsSynthesisText
count
]);
return
[
self
.
ttsSynthesisText
count
]
>
0
;
}
#pragma mark - Helper
-
(
NSString
*
)
getTtsTextType
{
switch
([
self
.
settings
getOptions
:
SETTING_TTS_TEXT_TYPE
].
chooseIdx
)
{
case
0
:
return
SE_TTS_TEXT_TYPE_PLAIN
;
case
1
:
return
SE_TTS_TEXT_TYPE_SSML
;
default
:
break
;
}
return
SE_TTS_TEXT_TYPE_PLAIN
;;
}
-
(
int
)
getTtsWorkMode
{
switch
([
self
.
settings
getOptions
:
SETTING_TTS_WORK_MODE
].
chooseIdx
)
{
case
0
:
return
SETtsWorkModeOnline
;
case
1
:
return
SETtsWorkModeOffline
;
case
2
:
return
SETtsWorkModeAlternate
;
default
:
break
;
}
return
SETtsWorkModeOnline
;;
}
-
(
NSString
*
)
getAuthenticationType
{
switch
([
self
.
settings
getOptions
:
SETTING_AUTHENTICATION_TYPE
].
chooseIdx
)
{
case
0
:
return
SE_AUTHENTICATE_TYPE_PRE_BIND
;
case
1
:
return
SE_AUTHENTICATE_TYPE_LATE_BIND
;
default
:
break
;
}
return
SE_AUTHENTICATE_TYPE_PRE_BIND
;
}
-
(
long
)
timeDelayFrom
:
(
long
)
pastTimestamp
{
return
[[
NSDate
date
]
timeIntervalSince1970
]
*
1000
-
pastTimestamp
;
}
#pragma mark - Notifications
-
(
void
)
appWillTerminate
:
(
NSNotification
*
)
note
{
[[
NSNotificationCenter
defaultCenter
]
removeObserver
:
self
name
:
AVAudioSessionInterruptionNotification
object
:
nil
];
}
-
(
void
)
audioInterruptionHandler
:
(
NSNotification
*
)
notification
{
AVAudioSessionInterruptionType
interruptionType
=
(
AVAudioSessionInterruptionType
)[[
notification
.
userInfo
objectForKey
:
AVAudioSessionInterruptionTypeKey
]
unsignedIntegerValue
];
AVAudioSessionInterruptionOptions
intertuptionOptions
=
[[
notification
.
userInfo
objectForKey
:
AVAudioSessionInterruptionOptionKey
]
unsignedIntValue
];
NSLog
(
@"Receive audio interruption notification, type: %lu, options: %lu."
,
(
unsigned
long
)
interruptionType
,
(
unsigned
long
)
intertuptionOptions
);
if
(
interruptionType
==
AVAudioSessionInterruptionTypeBegan
)
{
NSLog
(
@"Audio session interruption began"
);
@synchronized
(
self
)
{
[
self
pausePlayback
];
}
}
else
if
(
interruptionType
==
AVAudioSessionInterruptionTypeEnded
)
{
@synchronized
(
self
)
{
NSLog
(
@"Audio session interruption ended"
);
if
(
intertuptionOptions
==
AVAudioSessionInterruptionOptionShouldResume
)
{
AVAudioSession
*
session
=
[
AVAudioSession
sharedInstance
];
AVAudioSessionCategoryOptions
cur_options
=
session
.
categoryOptions
;
// AudioQueueStart() will return AVAudioSessionErrorCodeCannotInterruptOthers if options didn't contains AVAudioSessionCategoryOptionMixWithOthers
if
(
!
(
cur_options
&
AVAudioSessionCategoryOptionMixWithOthers
))
{
AVAudioSessionCategoryOptions
readyOptions
=
AVAudioSessionCategoryOptionMixWithOthers
|
cur_options
;
[
session
setCategory
:
AVAudioSessionCategoryPlayback
withOptions
:
readyOptions
error
:
nil
];
}
[
self
resumePlayback
];
cur_options
=
session
.
categoryOptions
;
// Remove AVAudioSessionCategoryOptionMixWithOthers, or the playback will not be interrupted any more
if
(
cur_options
&
AVAudioSessionCategoryOptionMixWithOthers
)
{
[
session
setCategory
:
AVAudioSessionCategoryPlayback
withOptions
:((
~
AVAudioSessionCategoryOptionMixWithOthers
)
&
cur_options
)
error
:
nil
];
}
}
}
}
}
-
(
void
)
onRecieve
:
(
nonnull
NSDictionary
*
)
message
{
}
@end
ios/Classes/VoiceAsr.h
浏览文件 @
29fa1f76
...
...
@@ -26,6 +26,8 @@ NS_ASSUME_NONNULL_BEGIN
-
(
void
)
uninitEngine
;
-
(
void
)
destroyEngine
;
-
(
void
)
startEngineBtnClicked
;
-
(
void
)
stopEngineBtnClicked
;
...
...
ios/Classes/VoiceAsr.m
浏览文件 @
29fa1f76
...
...
@@ -148,6 +148,10 @@
}
}
-
(
void
)
destroyEngine
{
[
self
.
curEngine
destroyEngine
];
}
#pragma mark - Config & Init & Uninit Methods
-
(
void
)
configInitParams
{
...
...
lib/aivoice_plugin.dart
浏览文件 @
29fa1f76
...
...
@@ -36,4 +36,49 @@ class AivoicePlugin {
Stream
<
Map
<
String
,
dynamic
>>
get
onAsrResultReceived
{
return
_eventChannel
.
receiveBroadcastStream
().
map
((
event
)
=>
Map
<
String
,
dynamic
>.
from
(
event
));
}
// 更改方法名
Future
<
void
>
ttsStartEngineBtnClick
(
Map
<
String
,
dynamic
>
params
)
{
return
AivoicePluginPlatform
.
instance
.
ttsStartEngineBtnClick
(
params
);
}
// 新增的 ttsSynthesis 方法
Future
<
void
>
ttsSynthesis
(
Map
<
String
,
dynamic
>
params
)
{
return
AivoicePluginPlatform
.
instance
.
ttsSynthesis
(
params
);
}
// 修改方法名
Future
<
void
>
ttsStopEngineBtnClicked
()
{
return
AivoicePluginPlatform
.
instance
.
ttsStopEngineBtnClicked
();
}
// 新增的 ttsPausePlayback 方法
Future
<
void
>
ttsPausePlayback
()
{
return
AivoicePluginPlatform
.
instance
.
ttsPausePlayback
();
}
// 新增的 ttsResumePlayback 方法
Future
<
void
>
ttsResumePlayback
()
{
return
AivoicePluginPlatform
.
instance
.
ttsResumePlayback
();
}
// 新增的 ttsInitEngine 方法
Future
<
void
>
ttsInitEngine
(
Map
<
String
,
dynamic
>
config
)
{
return
AivoicePluginPlatform
.
instance
.
ttsInitEngine
(
config
);
}
// 新增的 ttsUnInitEngine 方法
Future
<
void
>
ttsUnInitEngine
()
{
return
AivoicePluginPlatform
.
instance
.
ttsUnInitEngine
();
}
// 新增的 destoryTtsNovel 方法
Future
<
void
>
destoryTtsNovel
()
{
return
AivoicePluginPlatform
.
instance
.
destoryTtsNovel
();
}
// 新增的 destoryAsrVoice 方法
Future
<
void
>
destoryAsrVoice
()
{
return
AivoicePluginPlatform
.
instance
.
destoryAsrVoice
();
}
}
lib/aivoice_plugin_method_channel.dart
浏览文件 @
29fa1f76
...
...
@@ -35,4 +35,49 @@ class MethodChannelAivoicePlugin extends AivoicePluginPlatform {
Future
<
void
>
prepareEnvironment
(
Map
<
String
,
dynamic
>
config
)
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'prepareEnvironment'
,
config
);
}
@override
Future
<
void
>
ttsStartEngineBtnClick
(
Map
<
String
,
dynamic
>
params
)
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'ttsStartEngineBtnClick'
,
params
);
}
@override
Future
<
void
>
ttsSynthesis
(
Map
<
String
,
dynamic
>
params
)
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'ttsSynthesis'
,
params
);
}
@override
Future
<
void
>
ttsStopEngineBtnClicked
()
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'ttsStopEngineBtnClicked'
);
}
@override
Future
<
void
>
ttsPausePlayback
()
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'ttsPausePlayback'
);
}
@override
Future
<
void
>
ttsResumePlayback
()
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'ttsResumePlayback'
);
}
@override
Future
<
void
>
ttsInitEngine
(
Map
<
String
,
dynamic
>
config
)
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'ttsInitEngine'
,
config
);
}
@override
Future
<
void
>
ttsUnInitEngine
()
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'ttsUnInitEngine'
);
}
@override
Future
<
void
>
destoryTtsNovel
()
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'destoryTtsNovel'
);
}
@override
Future
<
void
>
destoryAsrVoice
()
async
{
await
methodChannel
.
invokeMethod
<
void
>(
'destoryAsrVoice'
);
}
}
lib/aivoice_plugin_platform_interface.dart
浏览文件 @
29fa1f76
...
...
@@ -39,4 +39,49 @@ abstract class AivoicePluginPlatform extends PlatformInterface {
// 新增的 prepareEnvironment 方法
Future
<
void
>
prepareEnvironment
(
Map
<
String
,
dynamic
>
config
);
// 新增的 ttsStartEngineBtnClick 方法
Future
<
void
>
ttsStartEngineBtnClick
(
Map
<
String
,
dynamic
>
params
)
{
throw
UnimplementedError
(
'ttsStartEngineBtnClick() has not been implemented.'
);
}
// 新增的 ttsSynthesis 方法
Future
<
void
>
ttsSynthesis
(
Map
<
String
,
dynamic
>
params
)
{
throw
UnimplementedError
(
'ttsSynthesis() has not been implemented.'
);
}
// 修改方法名
Future
<
void
>
ttsStopEngineBtnClicked
()
{
throw
UnimplementedError
(
'ttsStopEngineBtnClicked() has not been implemented.'
);
}
// 新增的 ttsPausePlayback 方法
Future
<
void
>
ttsPausePlayback
()
{
throw
UnimplementedError
(
'ttsPausePlayback() has not been implemented.'
);
}
// 新增的 ttsResumePlayback 方法
Future
<
void
>
ttsResumePlayback
()
{
throw
UnimplementedError
(
'ttsResumePlayback() has not been implemented.'
);
}
// 新增的 ttsInitEngine 方法
Future
<
void
>
ttsInitEngine
(
Map
<
String
,
dynamic
>
config
)
{
throw
UnimplementedError
(
'ttsInitEngine() has not been implemented.'
);
}
// 新增的 ttsUnInitEngine 方法
Future
<
void
>
ttsUnInitEngine
()
{
throw
UnimplementedError
(
'ttsUnInitEngine() has not been implemented.'
);
}
// 新增的 destoryTtsNovel 方法
Future
<
void
>
destoryTtsNovel
()
{
throw
UnimplementedError
(
'destoryTtsNovel() has not been implemented.'
);
}
// 新增的 destoryAsrVoice 方法
Future
<
void
>
destoryAsrVoice
()
{
throw
UnimplementedError
(
'destoryAsrVoice() has not been implemented.'
);
}
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论