1
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
|
||||
android:usesCleartextTraffic="true"
|
||||
package="io.dcloud.uni_modules.xTexttovoiceS">
|
||||
<meta-data android:name="ScopedStorage" android:value="true" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESSIBILITY_SERVICE" />
|
||||
<!-- 音频音量设置 -->
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"minSdkVersion": "21"
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import TextToSpeech from "android.speech.tts.TextToSpeech"
|
||||
import OnInitListener from "android.speech.tts.TextToSpeech.OnInitListener"
|
||||
import OnUtteranceCompletedListener from "android.speech.tts.TextToSpeech.OnUtteranceCompletedListener"
|
||||
import UtteranceProgressListener from "android.speech.tts.UtteranceProgressListener"
|
||||
import Locale from "java.util.Locale"
|
||||
import { XuiTextToVoiceOpts, XuiTextToVoiceResult, XTTSImpl } from "../interface.uts"
|
||||
import { XuiTextToVoiceFailImpl } from "../unierror.uts"
|
||||
import Bundle from "android.os.Bundle";
|
||||
let tts : TextToSpeech | null = null
|
||||
|
||||
/**
|
||||
* 检查权限
|
||||
*/
|
||||
export function checkPermissions(call : (isAuth : boolean) => void) {
|
||||
let cehckPerMissionsArs = [] as string[]
|
||||
if (UTSAndroid.checkSystemPermissionGranted(UTSAndroid.getUniActivity()!, cehckPerMissionsArs)) {
|
||||
call(true)
|
||||
} else {
|
||||
UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, cehckPerMissionsArs, function (_A : boolean, _B : string[]) {
|
||||
call(true)
|
||||
}, function (_B : boolean, _A : string[]) {
|
||||
call(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class TtsObj implements XTTSImpl {
|
||||
config : XuiTextToVoiceOpts;
|
||||
constructor(opts : XuiTextToVoiceOpts) {
|
||||
this.config = opts;
|
||||
}
|
||||
isPlaying() : boolean {
|
||||
let isplay = tts?.isSpeaking;
|
||||
return isplay == null ? false : isplay
|
||||
}
|
||||
play(c : string) {
|
||||
const params = Bundle()
|
||||
tts?.speak(c, TextToSpeech.QUEUE_FLUSH, params, Math.random().toString(16).substring(4))
|
||||
}
|
||||
stop() {
|
||||
tts?.stop()
|
||||
tts?.shutdown()
|
||||
this.config.onStop?.()
|
||||
}
|
||||
}
|
||||
let TtsObjIml:TtsObj|null = null;
|
||||
export class xTtsImplIns {
|
||||
public static play(c:string){
|
||||
TtsObjIml?.play(c)
|
||||
}
|
||||
public static isPlaying() : boolean {
|
||||
return TtsObjIml?.isPlaying()??false
|
||||
}
|
||||
public static stop(){
|
||||
TtsObjIml?.stop()
|
||||
}
|
||||
}
|
||||
|
||||
export function XTtsSpeek(opts : XuiTextToVoiceOpts) {
|
||||
function initTTS() {
|
||||
class Listen extends OnInitListener {
|
||||
override onInit(status : Int) {
|
||||
if (status != TextToSpeech.SUCCESS) {
|
||||
tts = null
|
||||
console.error("TMUI Error:", status)
|
||||
opts.fail?.(new XuiTextToVoiceFailImpl(1001));
|
||||
opts.complete?.(null)
|
||||
return;
|
||||
}
|
||||
let ttsObj = tts!;
|
||||
const result = ttsObj.setLanguage(Locale.CHINESE)
|
||||
if (result == TextToSpeech.LANG_MISSING_DATA) {
|
||||
tts = null
|
||||
opts.fail?.(new XuiTextToVoiceFailImpl(1002));
|
||||
opts.complete?.(null)
|
||||
return;
|
||||
}
|
||||
TtsObjIml = new TtsObj(opts)
|
||||
opts.success?.(TtsObjIml!)
|
||||
opts.complete?.(null)
|
||||
}
|
||||
}
|
||||
class ProgressListen extends UtteranceProgressListener {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
public override onError(param : string | null) {
|
||||
opts.onError?.(new XuiTextToVoiceFailImpl(1004));
|
||||
}
|
||||
public override onStart(param : string | null) {
|
||||
opts.onStart?.();
|
||||
}
|
||||
|
||||
public override onDone(param : string | null) {
|
||||
opts.onDone?.();
|
||||
}
|
||||
}
|
||||
tts = new TextToSpeech(UTSAndroid.getAppContext()!, new Listen())
|
||||
tts!.setOnUtteranceProgressListener(new ProgressListen())
|
||||
}
|
||||
|
||||
if (tts != null) {
|
||||
tts?.stop()
|
||||
tts?.shutdown()
|
||||
tts = null;
|
||||
}
|
||||
initTTS()
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { XuiTextToVoiceOpts, XuiTextToVoiceResult, XTTSImpl } from "../interface.uts"
|
||||
import { XuiTextToVoiceFailImpl } from "../unierror.uts"
|
||||
import { textToSpeech } from '@kit.CoreSpeechKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
let tts : textToSpeech.TextToSpeechEngine | null = null;
|
||||
class TtsObj implements XTTSImpl {
|
||||
config : XuiTextToVoiceOpts;
|
||||
constructor(opts : XuiTextToVoiceOpts) {
|
||||
this.config = opts;
|
||||
}
|
||||
isPlaying() : boolean {
|
||||
return tts?.isBusy() ?? false
|
||||
}
|
||||
play(c : string) {
|
||||
// 设置播报相关参数
|
||||
let extraParam:Record<string, Object> = {
|
||||
"queueMode": 0, "speed": 0.9, "pitch": 1, "languageContext": 'zh-CN',
|
||||
"audioType": "pcm", "soundChannel": 3, "playType": 1
|
||||
};
|
||||
let speakParams : textToSpeech.SpeakParams = {
|
||||
requestId: Math.random().toString(16).substring(4),
|
||||
extraParams: extraParam
|
||||
};
|
||||
tts?.speak(c, speakParams);
|
||||
}
|
||||
|
||||
stop() {
|
||||
tts?.stop()
|
||||
tts?.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
let TtsObjIml : TtsObj | null = null;
|
||||
export class xTtsImplIns {
|
||||
public static play(c : string) {
|
||||
TtsObjIml?.play(c)
|
||||
}
|
||||
public static isPlaying() : boolean {
|
||||
return TtsObjIml?.isPlaying() ?? false
|
||||
}
|
||||
public static stop() {
|
||||
TtsObjIml?.stop()
|
||||
}
|
||||
}
|
||||
export function XTtsSpeek(opts : XuiTextToVoiceOpts) {
|
||||
tts = null;
|
||||
let extraParam:Record<string, Object> = { "style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName' };
|
||||
let initParamsInfo : textToSpeech.CreateEngineParams = {
|
||||
language: 'zh-CN',
|
||||
person: 0,
|
||||
online: 1,
|
||||
extraParams: extraParam
|
||||
};
|
||||
let speakListener : textToSpeech.SpeakListener = {
|
||||
// 开始播报回调
|
||||
onStart(requestId : string, response : textToSpeech.StartResponse) {
|
||||
opts.onStart?.()
|
||||
},
|
||||
// 合成完成及播报完成回调
|
||||
onComplete(requestId : string, response : textToSpeech.CompleteResponse) {
|
||||
opts.onDone?.()
|
||||
},
|
||||
// 停止播报回调
|
||||
onStop(requestId : string, response : textToSpeech.StopResponse) {
|
||||
opts.onStop?.()
|
||||
},
|
||||
// 返回音频流
|
||||
onData(requestId : string, audio : ArrayBuffer, response : textToSpeech.SynthesisResponse) {
|
||||
},
|
||||
// 错误回调
|
||||
onError(requestId : string, errorCode : number, errorMessage : string) {
|
||||
console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);
|
||||
opts.onError?.(new XuiTextToVoiceFailImpl(1004))
|
||||
}
|
||||
};
|
||||
// 调用createEngine方法
|
||||
textToSpeech.createEngine(initParamsInfo, (err : BusinessError, textToSpeechEngine : textToSpeech.TextToSpeechEngine) => {
|
||||
if (!err) {
|
||||
console.info('Succeeded in creating engine');
|
||||
// 接收创建引擎的实例
|
||||
tts = textToSpeechEngine;
|
||||
tts!.setListener(speakListener);
|
||||
TtsObjIml = new TtsObj(opts)
|
||||
opts.success?.(TtsObjIml!)
|
||||
opts.complete?.(null)
|
||||
} else {
|
||||
console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);
|
||||
opts.fail?.(new XuiTextToVoiceFailImpl(1001))
|
||||
opts.complete?.(null)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"deploymentTarget": "12",
|
||||
"abis": ["armeabi-v7a", "arm64-v8a"]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { XuiTextToVoiceOpts, XuiTextToVoiceResult, XTTSImpl } from "../interface.uts"
|
||||
import { XuiTextToVoiceFailImpl } from "../unierror.uts"
|
||||
import { AVSpeechBoundary, AVSpeechSynthesisVoice, AVSpeechSynthesizer, AVSpeechSynthesizerDelegate, AVSpeechUtterance } from "AVFAudio";
|
||||
import { NSObject } from "ObjectiveC";
|
||||
import {AVPlayerLayer} from "AVFoundation"
|
||||
let tts:AVSpeechSynthesizer|null = null
|
||||
|
||||
class TtsObj implements XTTSImpl {
|
||||
config : XuiTextToVoiceOpts;
|
||||
constructor(opts : XuiTextToVoiceOpts) {
|
||||
this.config = opts;
|
||||
}
|
||||
isPlaying() : boolean {
|
||||
return tts!.isSpeaking;
|
||||
}
|
||||
play(c : string) {
|
||||
|
||||
console.log(c)
|
||||
let utterance = new AVSpeechUtterance(string = c)
|
||||
utterance.voice = new AVSpeechSynthesisVoice(language = "zh-CN")
|
||||
tts!.speak(utterance)
|
||||
}
|
||||
|
||||
stop() {
|
||||
tts!.stopSpeaking(at = AVSpeechBoundary.immediate)
|
||||
this.config.onStop?.()
|
||||
}
|
||||
}
|
||||
// 委托类
|
||||
class TTSDelegate implements AVSpeechSynthesizerDelegate<AVSpeechSynthesizerDelegate>{
|
||||
private config: XuiTextToVoiceOpts
|
||||
constructor(opts : XuiTextToVoiceOpts) {
|
||||
this.config = opts
|
||||
}
|
||||
speechSynthesizer(synthesizer: AVSpeechSynthesizer, @argumentLabel("didStart") utterance: AVSpeechUtterance) {
|
||||
this.config.onStart?.()
|
||||
}
|
||||
|
||||
speechSynthesizer(synthesizer: AVSpeechSynthesizer, @argumentLabel("didFinish") utterance: AVSpeechUtterance) {
|
||||
this.config.onDone?.()
|
||||
}
|
||||
|
||||
speechSynthesizer(synthesizer: AVSpeechSynthesizer, @argumentLabel("didCancel") utterance: AVSpeechUtterance) {
|
||||
this.config.onDone?.()
|
||||
}
|
||||
|
||||
speechSynthesizer(synthesizer: AVSpeechSynthesizer, @argumentLabel("didPause") utterance: AVSpeechUtterance) {
|
||||
this.config.onStop?.()
|
||||
}
|
||||
|
||||
}
|
||||
let TtsObjIml:TtsObj|null = null;
|
||||
let ttsDelegate:TTSDelegate|null = null;
|
||||
export class xTtsImplIns {
|
||||
public static play(c:string){
|
||||
TtsObjIml?.play(c)
|
||||
}
|
||||
public static isPlaying() : boolean {
|
||||
return TtsObjIml?.isPlaying()??false
|
||||
}
|
||||
public static stop(){
|
||||
TtsObjIml?.stop()
|
||||
}
|
||||
}
|
||||
export function XTtsSpeek(opts : XuiTextToVoiceOpts) {
|
||||
tts = null;
|
||||
DispatchQueue.main.async(execute=():void => {
|
||||
try {
|
||||
tts = new AVSpeechSynthesizer()
|
||||
TtsObjIml = new TtsObj(opts)
|
||||
ttsDelegate = new TTSDelegate(opts)
|
||||
tts!.delegate = ttsDelegate!
|
||||
opts.success?.(TtsObjIml!)
|
||||
opts.complete?.(null)
|
||||
} catch (error) {
|
||||
console.error("TMUI Error:",error)
|
||||
opts.fail?.(new XuiTextToVoiceFailImpl(1001))
|
||||
opts.complete?.(null)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
export type XuiTextToVoiceResult = {
|
||||
|
||||
}
|
||||
|
||||
export interface XuiTextToVoiceFail extends IUniError {
|
||||
errCode : number
|
||||
};
|
||||
|
||||
export type XuiTextToVoiceOpts = {
|
||||
onStart ?:() => void
|
||||
onStop ?:() => void
|
||||
onError ?:(res : XuiTextToVoiceFail) => void
|
||||
/** 播放完成 */
|
||||
onDone ?:() => void
|
||||
/** 函数执行初始化成功,然后就可以播放,停止等操作了。 */
|
||||
success ?: (res : XTTSImpl) => void
|
||||
/** 函数初始化失败 */
|
||||
fail ?: (res : XuiTextToVoiceFail) => void
|
||||
/** 不管初始失败还是成功都执行。 */
|
||||
complete ?: (res : XuiTextToVoiceResult|null) => void
|
||||
}
|
||||
|
||||
export interface XTTSImpl {
|
||||
isPlaying(): boolean;
|
||||
play(c:string):void
|
||||
stop():void
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/* 此规范为 uni 规范,可以按照自己的需要选择是否实现 */
|
||||
import { XuiTextToVoiceFail } from "./interface.uts"
|
||||
|
||||
export const XuiErrors : Map<number, string> = new Map([
|
||||
[1000, '没有相关权限'],
|
||||
[1001, '初始化失败'],
|
||||
[1002, '设置的语文包在系统中缺失'],
|
||||
[1003, '初始化中或者还在报播中'],
|
||||
[1004, '播放中出现错误'],
|
||||
]);
|
||||
|
||||
export class XuiTextToVoiceFailImpl extends UniError implements XuiTextToVoiceFail {
|
||||
/**
|
||||
* 错误对象构造函数
|
||||
*/
|
||||
constructor(errCode : number) {
|
||||
super();
|
||||
this.errSubject = "x-chinesetopinyin-s";
|
||||
this.errCode = errCode;
|
||||
this.errMsg = XuiErrors.get(errCode) ?? "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { XuiTextToVoiceOpts, XuiTextToVoiceResult, XTTSImpl } from "../interface.uts"
|
||||
import { XuiTextToVoiceFailImpl } from "../unierror.uts"
|
||||
let tts:SpeechSynthesisUtterance|null = null;
|
||||
class TtsObj implements XTTSImpl {
|
||||
config : XuiTextToVoiceOpts;
|
||||
constructor(opts : XuiTextToVoiceOpts) {
|
||||
this.config = opts;
|
||||
}
|
||||
isPlaying() : boolean {
|
||||
return speechSynthesis.speaking;
|
||||
}
|
||||
play(c : string) {
|
||||
tts!.text = c;
|
||||
tts!.pitch = 0.5
|
||||
speechSynthesis.speak(tts!);
|
||||
}
|
||||
|
||||
stop() {
|
||||
speechSynthesis.pause()
|
||||
this.config.onStop?.()
|
||||
}
|
||||
}
|
||||
|
||||
let TtsObjIml:TtsObj|null = null;
|
||||
export class xTtsImplIns {
|
||||
public static play(c:string){
|
||||
TtsObjIml?.play(c)
|
||||
}
|
||||
public static isPlaying() : boolean {
|
||||
return TtsObjIml?.isPlaying()??false
|
||||
}
|
||||
public static stop(){
|
||||
TtsObjIml?.stop()
|
||||
}
|
||||
}
|
||||
export function XTtsSpeek(opts : XuiTextToVoiceOpts) {
|
||||
tts = null;
|
||||
// 检查浏览器支持
|
||||
if ('speechSynthesis' in window) {
|
||||
tts = new SpeechSynthesisUtterance();
|
||||
tts!.onend = ()=>{
|
||||
opts.onDone?.()
|
||||
}
|
||||
tts!.onerror = ()=>{
|
||||
opts.onError?.(new XuiTextToVoiceFailImpl(1004))
|
||||
}
|
||||
tts!.onstart = ()=>{
|
||||
opts.onStart?.()
|
||||
}
|
||||
TtsObjIml = new TtsObj(opts)
|
||||
opts.success?.(TtsObjIml!)
|
||||
opts.complete?.(null)
|
||||
} else {
|
||||
console.error("TMUI Error:",'浏览器不支持语音合成')
|
||||
opts.fail?.(new XuiTextToVoiceFailImpl(1001))
|
||||
opts.complete?.(null)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user