This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
## 1.0.02025-09-21
支持Android,iOS,Harmony,web/h5文本语音播报功能。
+97
View File
@@ -0,0 +1,97 @@
{
"id": "x-texttovoice-s",
"displayName": "x-texttovoice-s",
"version": "1.0.0",
"description": "x-texttovoice-s",
"keywords": [
"x-texttovoice-s"
],
"repository": "",
"engines": {
"HBuilderX": "^3.6.8",
"uni-app": "^3.1.0",
"uni-app-x": "^3.1.0"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "",
"data": "",
"permissions": ""
},
"npmurl": "",
"darkmode": "-",
"i18n": "-",
"widescreen": "-"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "-",
"aliyun": "-",
"alipay": "-"
},
"client": {
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "-",
"nvue": "-",
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
}
}
+90
View File
@@ -0,0 +1,90 @@
# x-texttovoice-s
### 开发文档
将文字转为语音播报。
### 兼容性
**注意:Harmony需要使用模拟器6.0+(api 20+)才支持在模拟器上使用本插件,低于此版本需要真机调试。**
| iOS | Harmony | 小程序 | Andriod | WEB |
| --- | --- | --- | --- | --- |
| 12+ | 12+ | x | 5.0+ | 全部 |
### 方法说明
使用
```ts
import { XTtsSpeek, xTtsImplIns } from '@/uni_modules/x-texttovoice-s';
const word = ref("我是待播放的语音文本。")
const onclick = () => {
XTtsSpeek({
fail(res) {
console.error(res)
},
onDone() {
console.log('播放完毕')
},
onStop() {
console.log('stop')
},
success(res) {
// 初始成功后,调用播放功能。
xTtsImplIns.play(word.value)
}
})
}
```
### XTtsSpeek(opts:XuiTextToVoiceOpts)
引擎初始方法
```ts
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
}
```
### xTtsImplIns
语音播放静态类方法。
#### isPlaying(): boolean; 获取当前状态
#### play(c:string); 播播文本语音
#### stop(); 停止播报
```ts
import { XTtsSpeek, xTtsImplIns } from '@/uni_modules/x-texttovoice-s';
// xTtsImplIns就是实例 XTTSImpl,含有以下方法。
export interface XTTSImpl {
isPlaying(): boolean;
play(c:string):void
stop():void
}
```
@@ -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)
}
}