首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

实现实时语音转文字功能鸿蒙示例代码

  • 25-04-18 12:14
  • 3283
  • 10812
juejin.cn

本文原创发布在华为开发者社区。

介绍

本示例介绍如何使用speechRecognizer实时语言转文字,并且根据光标位置插入文字,以及文本一键清空功能。

实现实时语音转文字功能源码链接

效果预览

图片名称

使用说明

  1. 点击顶部按钮可切换本人或非本人模拟聊天界面发送消息,本人发送右对齐,非本人发送左对齐。
  2. 点击RichEditor组件唤起输入法,已发送的消息自动避让。
  3. 长按启动实时语音转文字,松开停止语音转文字,根据光标所在位置插入语音识别的文字,点击清空可以清除RichEditor组件中的内容。
  4. 点击发送可以将RichEditor组件中的内容发送出去,可发送文字和图片消息。

实现思路

聊天页面左右布局

通过Flex组件实现左右布局,本人发送时设置方向为FlexDirection.RowReverse,非本人发送时设置方向为FlexDirection.Row。具体实现如下:

typescript
代码解读
复制代码
List({scroller: this.listScroller}) { ForEach(this.data, (item: MsgContent) => { ListItem() { // 通过isSelf判断是否是本人发送的消息,来决定Flex组件的direction是否需要进行反转。 Flex({ direction: item.isSelf ? FlexDirection.RowReverse : FlexDirection.Row, space: { main: LengthMetrics.vp(8) } }) { Image($r('app.media.avatar')) .width(32) .aspectRatio(1) Text() { ... } ... } .width('100%') } .margin(12) }, (item: MsgContent, index: number) => `${JSON.stringify(item)}_${JSON.stringify(index)}`) }

图文混排消息显示

整体使用Text去布局,文字通过内嵌Span组件显示,图片显示通过内嵌ImageSpan组件显示,具体代码如下:

typescript
代码解读
复制代码
Text() { ForEach(item.content, (content: MsgTextImage) => { if (content.type === MessageType.Text) { Span(content.content) } if (content.type === MessageType.Image) { ImageSpan(content.content) .clip(true) .objectFit(ImageFit.Contain) // 图片填充效果设置为Contain,防止图片超出范围 .size({ width: '20vp', height: '20vp' }) .margin(2) .verticalAlign(ImageSpanAlignment.CENTER) } }, (item: MsgTextImage, index: number) => `${JSON.stringify(item)}_${JSON.stringify(index)}`) } .padding(6) .borderRadius(4) .lineSpacing(LengthMetrics.vp(8)) // 设置下每行之间的空格,这样不至于看着很紧凑 .backgroundColor('#ADD8E6') .constraintSize({ maxWidth: '75%', minHeight: 32 }) // 这里设置下最大宽度和最小高度,消息太长时不要覆盖整个屏幕宽度

点击发送消息时,需对RichEditor组件中的消息转换成其他结构,发送完毕,清理RichEditor输入区域具体代码如下:

typescript
代码解读
复制代码
private sendMessage() { let message: MsgTextImage[] = []; richController.getSpans().forEach(span => { if ((span as RichEditorTextSpanResult).textStyle !== undefined) { message.push({ // 文本消息转换,type为Text,使用Span组件显示 type: MessageType.Text, content: (span as RichEditorTextSpanResult).value }); } else { message.push({ // 图片消息转换,type为Image,使用ImageSpan组件显示 type: MessageType.Image, content: (span as RichEditorImageSpanResult).valueResourceStr }); } }) if (message.length > 0) { this.data.push({ isSelf: this.isSelf, content: message }); } richController.deleteSpans(); }

实现已发送的消息自动避让

首先在aboutToAppear中设置键盘模式为上抬模式,代码如下:

typescript
代码解读
复制代码
aboutToAppear(): void { this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE); }

但仅仅只设置上抬模式还不够,消息数超过屏幕时,仍然看不到最新发送的消息。因此,在点击RichEditor组件唤起输入法时,需要将消息滚动到底部,代码如下:

typescript
代码解读
复制代码
RichEditor(this.options) .width('100%') .borderRadius(4) .backgroundColor('#08000000') .constraintSize({ maxHeight: 128 }) .placeholder(this.placeHolder, { fontColor: '#4D242E3E', font: { size: 13 } }) .layoutWeight(1) .clip(true) .onDidChange(() => { this.isHaveMsg = (richController.getSpans().length !== Number(0)) }) .onEditingChange(this.editingChangedCb) // 监听编辑状态是否发生改变,并执行回调 editingChanged = () => { this.curMenuAction = EditMenuAction.None; // 需要延迟一会再触发,键盘弹起之后再触发,否则无效果 setTimeout(() => { this.listScroller.scrollEdge(Edge.End); }, 100) }

实时语音转文字

  1. 语音转文字需使用麦克风权限,需要在module.json5文件中申明麦克风权限,使用时去请求麦克风权限
typescript
代码解读
复制代码
// module.json5中申明权限 "requestPermissions": [ { "name": "ohos.permission.MICROPHONE", "reason": "$string:microphone_reason", "usedScene": { "abilities": [ "EntryAbility" ], "when": "always" }, } ] // 页面即将加载时,请求麦克风权限 async aboutToAppear(): Promise<void> { this.speechRecognizer.intiEngine(); await requestPermission(['ohos.permission.MICROPHONE'], getContext() as common.UIAbilityContext); } // 调用系统API请求所需权限 export async function requestPermission(permissions: Permissions[], context: common.UIAbilityContext): Promise<boolean> { let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); const result = await atManager.requestPermissionsFromUser(context, permissions); return !!result.authResults.length && result.authResults.every(authResults => authResults === 0); }
  1. 调用系统speechRecognizerAPI进行实时语音识别,具体代码如下:
typescript
代码解读
复制代码
import { speechRecognizer } from '@kit.CoreSpeechKit'; export class SpeechRecognizer { private engineParams: speechRecognizer.CreateEngineParams = { language: 'zh-CN', // 目前系统API只支持设置中文 online: 1, // 目前系统API只支持离线模式 extraParams: { 'locate': 'CN', 'recognizerMode': 'long' } // 设置recognizerMode为长时模式,设置短时模式时说完一句话会自动结束识别 }; private asrEngine?: speechRecognizer.SpeechRecognitionEngine; private sessionId: string = 'SpeechRecognizer_' + Date.now(); public async intiEngine() { this.asrEngine = await speechRecognizer.createEngine(this.engineParams); } // 开始语音识别,并提供回调函数,用于返回结果 public start(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => {}) { this.setListener(callback); this.startListening(); } // 停止语音识别 public stop() { this.asrEngine?.finish(this.sessionId); } public shutdown() { this.asrEngine?.shutdown(); } // 启动监听 private startListening() { let recognizerParams: speechRecognizer.StartParams = { sessionId: this.sessionId, audioInfo: { audioType: 'pcm', sampleRate: 16000, soundChannel: 1, sampleBit: 16 }, extraParams: { recognitionMode: 0, maxAudioDuration: 60000 } } this.asrEngine?.startListening(recognizerParams); } private setListener(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => {}) { let listener: speechRecognizer.RecognitionListener = { onStart(sessionId: string, eventMessage: string) { }, onEvent(sessionId: string, eventCode: number, eventMessage: string) { }, onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) { // 语音识别到结果后,通过回调函数将识别的结果返回 callback && callback(result); }, onComplete(sessionId: string, eventMessage: string) { // recognizerMode设置为短时模式时,如果仍需继续识别,需要在此处再次调用startListening,启动监听。 }, onError(sessionId: string, errorCode: number, errorMessage: string) { }, } this.asrEngine?.setListener(listener); } }

根据光标位置插入语音识别的文字

通过getCaretOffset获取到光标所在位置。在插入文字时设置对应的偏移量来达成目的,RichEditor输入框无内容时增加正在识别...文字提示,有内容时增加...内容提示

typescript
代码解读
复制代码
startSpeechRecognizer() { // 输入框无内容时,直接使用提示文本 this.placeHolder = '正在识别...'; this.fillPlaceHolder(); // 填充提示文本 this.speechRecognizer.start((result) => { this.insertSpan(result.result); // 处理语音识别到的结果 if (result.isFinal) { // 语音识别完,将caretOffset,speechTextLength重置 this.caretOffset = richController.getCaretOffset(); this.speechTextLength = 0; } }) } stopSpeechRecognizer() { this.placeHolder = ''; this.speechRecognizer.stop(); // 结束识别,需将插入的...提示删除 richController.deleteSpans({ start: this.caretOffset, end: this.caretOffset + 3 }); } private insertSpan(text: string) { if (text.length <= 0) { // 未识别到内容时,直接return return; } if (this.speechTextLength === 0) { // 首次识别,直接根据光标位置将识别到的文字插入 richController.addTextSpan(text, { offset: this.caretOffset }); } else { // 非首次识别,需先将上次识别的文字删除,再填充新识别的文字 richController.deleteSpans({ start: this.caretOffset, end: this.caretOffset + this.speechTextLength }); richController.addTextSpan(text, { offset: this.caretOffset }); } this.speechTextLength = text.length; // 每次记录识别文字的长度 } private fillPlaceHolder() { this.caretOffset = richController.getCaretOffset(); if (richController.getSpans().length > 0) { // 输入框有内容时,插入...提示,并更新caretOffset位置 richController.addTextSpan('...', { offset: this.caretOffset, style: { fontColor: '#4D242E3E', fontSize: 13, } }) richController.setCaretOffset(this.caretOffset); } }
注:本文转载自juejin.cn的鸿蒙场景化示例代码技术工程师的文章"https://juejin.cn/post/7493968018339659830"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

133
开发工具
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top