微信小程序語(yǔ)音同步智能識(shí)別的實(shí)現(xiàn)案例代碼解析
一、背景
在小程序的一些應(yīng)用場(chǎng)景中,會(huì)有語(yǔ)音轉(zhuǎn)文字的需求。原有的做法一般是先通過(guò)小程序的錄音功能錄下語(yǔ)音文件,然后再通過(guò)調(diào)用語(yǔ)音智能識(shí)別WebApi(比如百度云AI平臺(tái),科大訊飛平臺(tái))將語(yǔ)音文件轉(zhuǎn)成文字信息,以上的做法比較繁瑣且用戶的體驗(yàn)性較差。
為解決此問(wèn)題,微信直接開(kāi)放了同聲傳譯的插件,小程序作者可以直接使用該插件進(jìn)行語(yǔ)音同聲傳譯的開(kāi)發(fā)。此文章將通過(guò)前后端整合應(yīng)用的完整案例完成語(yǔ)音的實(shí)時(shí)轉(zhuǎn)換,并將語(yǔ)音上傳到服務(wù)端后臺(tái)備份。
二、同聲傳譯插件介紹
微信同聲傳譯由微信智聆語(yǔ)音團(tuán)隊(duì)、微信翻譯團(tuán)隊(duì)與公眾平臺(tái)聯(lián)合推出的同傳開(kāi)放接口,首期開(kāi)放
1、 微信小程序后臺(tái)添加插件
進(jìn)入微信小程序后臺(tái)-->進(jìn)入設(shè)置-->第三方設(shè)置-->添加插件->搜索同聲傳譯-->完成添加。
2、 微信小程序啟用插件
在小程序app.json文件中增加插件版本等信息:
"plugins": { "WechatSI": { "version": "0.3.3", "provider": "wx069ba97219f66d99" } },
在頁(yè)面程序文件中引入插件:
/* index.js */ const plugin = requirePlugin("WechatSI") // 獲取**全局唯一**的語(yǔ)音識(shí)別管理器**recordRecoManager** const manager = plugin.getRecordRecognitionManager()
recordRecoManager 對(duì)象的方法列表:
方法 | 參數(shù) | 說(shuō)明 |
---|---|---|
start | options | 開(kāi)始識(shí)別 |
stop | 結(jié)束識(shí)別 | |
onStart | callback | 正常開(kāi)始錄音識(shí)別時(shí)會(huì)調(diào)用此事件 |
onRecognize | callback | 有新的識(shí)別內(nèi)容返回,則會(huì)調(diào)用此事件 |
onStop | callback | 識(shí)別結(jié)束事件 |
onError | callback | 識(shí)別錯(cuò)誤事件 |
官方開(kāi)發(fā)文檔:插件的語(yǔ)音識(shí)別管理器
三、語(yǔ)音同步轉(zhuǎn)換的前端實(shí)現(xiàn)
1、界面UI與操作
UI參考微信官方的DEMO:長(zhǎng)按按鈕進(jìn)行錄音,松開(kāi)按鈕實(shí)時(shí)將錄音轉(zhuǎn)換為文字。
用戶可對(duì)同步轉(zhuǎn)換的文字進(jìn)行編輯,同時(shí)可將原始語(yǔ)音文件與文字上傳后臺(tái)服務(wù)端。
2、代碼實(shí)現(xiàn)
語(yǔ)音同步轉(zhuǎn)換的主要代碼:
//導(dǎo)入插件 const plugin = requirePlugin("WechatSI"); // 獲取**全局唯一**的語(yǔ)音識(shí)別管理器**recordRecoManager** const manager = plugin.getRecordRecognitionManager(); /** * 加載進(jìn)行初始化 */ onLoad: function () { //獲取錄音權(quán)限 app.getRecordAuth(); //初始化語(yǔ)音識(shí)別回調(diào) this.initRecord(); }, ... /** * 初始化語(yǔ)音識(shí)別回調(diào) * 綁定語(yǔ)音播放開(kāi)始事件 */ initRecord: function () { //有新的識(shí)別內(nèi)容返回,則會(huì)調(diào)用此事件 manager.onRecognize = (res) => { let currentData = Object.assign({}, this.data.currentTranslate, { text: res.result, }); this.setData({ currentTranslate: currentData, }); this.scrollToNew(); }; // 識(shí)別結(jié)束事件 manager.onStop = (res) => { let text = res.result; console.log(res.tempFilePath); if (text == "") { this.showRecordEmptyTip(); return; } let lastId = this.data.lastId + 1; let currentData = Object.assign({}, this.data.currentTranslate, { text: res.result, translateText: "正在識(shí)別中", id: lastId, voicePath: res.tempFilePath, duration: res.duration }); this.setData({ currentTranslate: currentData, recordStatus: 1, lastId: lastId, }); //將當(dāng)前識(shí)別內(nèi)容與語(yǔ)音文件加入列表 this.addRecordFile(currentData, this.data.dialogList.length); //刷新列表 this.scrollToNew(); }; // 識(shí)別錯(cuò)誤事件 manager.onError = (res) => { this.setData({ recording: false, bottomButtonDisabled: false, }); }; }, /** * 按住按鈕開(kāi)始語(yǔ)音識(shí)別 */ streamRecord: function (e) { let detail = e.detail || {}; let buttonItem = detail.buttonItem || {}; //開(kāi)始中文錄音 manager.start({ lang: buttonItem.lang, }); this.setData({ recordStatus: 0, recording: true, currentTranslate: { // 當(dāng)前語(yǔ)音輸入內(nèi)容 create: util.recordTime(new Date()), text: "正在聆聽(tīng)中", lfrom: buttonItem.lang, lto: buttonItem.lto, }, }); //刷新列表 this.scrollToNew(); }, /** * 松開(kāi)按鈕結(jié)束語(yǔ)音識(shí)別 */ streamRecordEnd: function (e) { let detail = e.detail || {}; // 自定義組件觸發(fā)事件時(shí)提供的detail對(duì)象 let buttonItem = detail.buttonItem || {}; // 防止重復(fù)觸發(fā)stop函數(shù) if (!this.data.recording || this.data.recordStatus != 0) { console.warn("has finished!"); return; } manager.stop(); this.setData({ bottomButtonDisabled: true, }); },
編輯識(shí)別文字并完上傳的主要代碼:
/** * 頁(yè)面的初始數(shù)據(jù) */ data: { edit_text_max: 200, remain_length: 200, edit_text: "", is_focus: false, tips: "", index: -1, voicePath: "", }, /** * 加載初始化 */ onLoad: function (options) { //根據(jù)傳入的文字內(nèi)容填充編輯框 this.setEditText(options.content) this.setData({ index: index, oldText:options.content, voicePath: options.voicePath }) }, /** * 編輯文字 */ editInput: function (event) { console.log(event) if (event.detail.value.length > this.getEditTextMax()) { } else { this.data.edit_text = event.detail.value this.updateRemainLength(this.data.edit_text) } }, /** * 上傳文字與語(yǔ)音文件 */ editConfirm: function (event) { let json=this.data.edit_text //調(diào)用微信上傳文件api將信息上傳至服務(wù)端webApi wx.uploadFile({ url: api.wxFileUploadUrl, filePath: this.data.voicePath, name: "file", header: { Authorization: wx.getStorageSync("loginFlag"), "Content-Type": "multipart/form-data", }, formData: { openId: app.globalData.userInfo.openId, realName: "語(yǔ)音文件", json: JSON.stringify(json), }, success: (result) => { console.log("success:", result); if (result.statusCode == "200") { let data = JSON.parse(result.data); console.log("data", data); if (data.success == true) { let module = data.module; console.log("module", module); app.showInfo("上傳成功"); setTimeout( ()=>{ wx.navigateBack(); }, 2000) } else { app.showInfo("異常錯(cuò)誤" + data.errMsg + ",請(qǐng)重新進(jìn)入"); wx.navigateTo({ url: "/pages/index/index", }); } } else { app.showInfo("訪問(wèn)后臺(tái)異常,重新進(jìn)入系統(tǒng)"); wx.navigateTo({ url: "/pages/index/index", }); } }, fail: (result) => { console.log("fail", result); wx.navigateTo({ url: "/pages/index/index", }); }, complete: () => {}, }); },
四、后端SpringBoot實(shí)現(xiàn)語(yǔ)音文件上傳webApi
1、SpringBoot項(xiàng)目API相關(guān)結(jié)構(gòu)樹(shù)
2、文件上傳工具類(lèi)的實(shí)現(xiàn)
tools工具類(lèi)包中主要存文件通用的文件上傳工具類(lèi),該工具類(lèi)會(huì)將文件上傳至配置指定的文件夾下,并將文件信息寫(xiě)入upload_file表中。
- 文件信息實(shí)體類(lèi):與數(shù)據(jù)庫(kù)中表upload_file對(duì)應(yīng);
- 文件存儲(chǔ)倉(cāng)庫(kù)類(lèi):通過(guò)Spring Data JPA接口實(shí)現(xiàn)數(shù)據(jù)的CRUD;
- 文件上傳工具接口:對(duì)外統(tǒng)一封裝文件上傳方法;
- 文件上傳工具實(shí)現(xiàn)類(lèi):實(shí)現(xiàn)文件上傳方法接口。
文件信息實(shí)體類(lèi):UploadFile.java
/** * 文件信息表 * * @author zhuhuix * @date 2020-04-20 */ @Entity @Getter @Setter @Table(name = "upload_file") public class UploadFile { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @NotNull(groups = Update.class) private Long id; /** * 文件實(shí)際名稱(chēng) */ @Column(name = "real_name") private String realName; /** * 文件名 */ @NotNull @Column(name = "file_name") private String fileName; /** * 文件主名稱(chēng) */ @NotNull @Column(name = "primary_name") private String primaryName; /** * 文件擴(kuò)展名 */ @NotNull private String extension; /** * 存放路徑 */ @NotNull private String path; /** * 文件類(lèi)型 */ private String type; /** * 文件大小 */ private Long size; /** * 上傳人 */ private String uploader; @JsonIgnore @Column(name = "create_time") @CreationTimestamp private Timestamp createTime; public UploadFile(String realName, @NotNull String fileName, @NotNull String primaryName, @NotNull String extension, @NotNull String path, String type, Long size, String uploader) { this.realName = realName; this.fileName = fileName; this.primaryName = primaryName; this.extension = extension; this.path = path; this.type = type; this.size = size; this.uploader = uploader; } @Override public String toString() { return "UploadFile{" + "fileName='" + fileName + '\'' + ", uploader='" + uploader + '\'' + ", createTime=" + createTime + '}'; } }
文件存儲(chǔ)倉(cāng)庫(kù)類(lèi):UploadFileRepository.java
/** * 上傳文件DAO接口層 * * @author zhuhuix * @date 2020-04-03 */ public interface UploadFileRepository extends JpaRepository<UploadFile, Long>, JpaSpecificationExecutor<UploadFile> { //該接口繼承JpaRepository及CrudRepository接口,已實(shí)現(xiàn)了如findById,save,delete等CRUD方法 }
UploadFileRepository 接口繼承JpaRepository及CrudRepository接口,已實(shí)現(xiàn)了如findById,save,delete等CRUD方法
文件上傳工具接口:UploadFileTool.java
/** * 文件上傳接口定義 * * @author zhuhuix * @date 2020-04-20 */ public interface UploadFileTool { /** * 文件上傳 * @param multipartFile 文件 * @return 上傳信息 */ UploadFile upload(String uploader,String realName,MultipartFile multipartFile); }
文件上傳工具實(shí)現(xiàn)類(lèi):UploadFileToolImpl.java
/** * 文件上傳實(shí)現(xiàn)類(lèi) * * @author zhuhuix * @date 2020-04-20 */ @Service @Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) public class UploadFileToolImpl implements UploadFileTool { private final UploadFileRepository uploadFileRepository; @Value("${uploadFile.path}") private String path; @Value("${uploadFile.maxSize}") private long maxSize; public UploadFileToolImpl(UploadFileRepository uploadFileRepository) { this.uploadFileRepository = uploadFileRepository; } @Override @Transactional(rollbackFor = Exception.class) public UploadFile upload(String uploader, String realName, MultipartFile multipartFile) { //檢查文件大小 if (multipartFile.getSize() > maxSize * Constant.MB) { throw new RuntimeException("超出文件上傳大小限制" + maxSize + "MB"); } //獲取上傳文件的主文件名與擴(kuò)展名 String primaryName = FileUtil.mainName(multipartFile.getOriginalFilename()); String extension = FileUtil.extName(multipartFile.getOriginalFilename()); //根據(jù)文件擴(kuò)展名得到文件類(lèi)型 String type = getFileType(extension); //給上傳的文件加上時(shí)間戳 LocalDateTime date = LocalDateTime.now(); DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMddhhmmssS"); String nowStr = "-" + date.format(format); String fileName = primaryName + nowStr + "." + extension; try { String filePath = path + type + File.separator + fileName; File dest = new File(filePath).getCanonicalFile(); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } multipartFile.transferTo(dest); if (ObjectUtil.isNull(dest)) { throw new RuntimeException("上傳文件失敗"); } UploadFile uploadFile = new UploadFile(realName, fileName, primaryName, extension, dest.getPath(), type, multipartFile.getSize(), uploader); return uploadFileRepository.save(uploadFile); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } } /** * 根據(jù)文件擴(kuò)展名給文件類(lèi)型 * * @param extension 文件擴(kuò)展名 * @return 文件類(lèi)型 */ private static String getFileType(String extension) { String document = "txt doc pdf ppt pps xlsx xls docx csv"; String music = "mp3 wav wma mpa ram ra aac aif m4a"; String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg"; String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg"; if (image.contains(extension)) { return "image"; } else if (document.contains(extension)) { return "document"; } else if (music.contains(extension)) { return "music"; } else if (video.contains(extension)) { return "video"; } else { return "other"; } } }
注意,該程序代碼中用到了@Value注解獲取配置文件中的uploadFile.path及uploadFile.maxsize參數(shù),一般在項(xiàng)目靜態(tài)配置文件中按如下書(shū)寫(xiě)(yml配置文件)。
# 測(cè)試環(huán)境文件存儲(chǔ)路徑 uploadFile: path: C:\startup\file\ # 文件大小 /M maxSize: 50
3、小程序上傳文件接口的實(shí)現(xiàn)
wx-miniprogram包定義了小程序CRM webApi的接口,小程序調(diào)用webApi實(shí)現(xiàn)文件的上傳及其他功能。
- 微信小程序 webApi:對(duì)外提供小程序上傳文件webApi;
- 微信小程序服務(wù)接口:封裝小程序上傳文件服務(wù)接口;
- 微信小程序服務(wù)實(shí)現(xiàn):小程序上傳文件服務(wù)的實(shí)現(xiàn),該服務(wù)實(shí)現(xiàn)中會(huì)調(diào)用tools包中的UploadFile接口進(jìn)行文件的上傳。
微信小程序CRM webApi:WxMiniCrmController.java
/** * 微信小程序Crm webApi * * @author zhuhuix * @date 2020-03-30 */ @Slf4j @RestController @RequestMapping("/api/wx-mini") @Api(tags = "微信小程序Crm接口") public class WxMiniCrmController { private final WxMiniCrm wxMiniCrm; public WxMiniCrmController(WxMiniCrm wxMiniCrm) { this.wxMiniCrm = wxMiniCrm; } @ApiOperation(value = "微信小程序端上傳文件") @PostMapping(value = "/fileUpload") public ResponseEntity fileUpload(HttpServletRequest request) { MultipartHttpServletRequest req = (MultipartHttpServletRequest) request; MultipartFile multipartFile = req.getFile("file"); String openId = req.getParameter("openId"); String realName = req.getParameter("realName"); String json = req.getParameter("json"); return ResponseEntity.ok(wxMiniCrm.uploadFile(json, openId,realName, multipartFile)); } }
微信小程序CRM服務(wù)接口:WxMiniCrm.java
/** * 微信小程序CRM服務(wù)接口定義 * * @author zhuhuix * @date 2020-04-20 */ public interface WxMiniCrm { /** * 將微信小程序傳入的json對(duì)象寫(xiě)入數(shù)據(jù)庫(kù),并同時(shí)將文件上傳至服務(wù)端 * * @param json 微信端傳入json對(duì)象 * @param openId 上傳人 * @param realName 文件實(shí)際名稱(chēng) * @param multipartFile 上傳文件 * @return 返回上傳信息 */ Result<UploadFile> uploadFile(String json, String openId, String realName,MultipartFile multipartFile); }
微信小程序CRM服務(wù)實(shí)現(xiàn):WxMiniCrmImpl.java
/** * 微信小程序CRM實(shí)現(xiàn)類(lèi) * * @author zhuhuix * @date 2020-04-20 */ @Slf4j @Service @Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) public class WxMiniCrmImpl implements WxMiniCrm { private final UploadFileTool uploadFileTool; public WxMiniCrmImpl(UploadFileTool uploadFileTool) { this.uploadFileTool = uploadFileTool; } @Override @Transactional(rollbackFor = Exception.class) public Result<UploadFile> uploadFile(String json, String openId,String realName, MultipartFile multipartFile) { return new Result<UploadFile>().ok(uploadFileTool.upload(openId,realName, multipartFile)); } }
4、小程序上傳文件接口的查看
訪問(wèn)Swagger2可查看該接口,Swagger2與SpringBoot的集成可參考SpringBoot JWT認(rèn)證機(jī)制項(xiàng)目集成Swagger2
五、實(shí)際測(cè)試
語(yǔ)音測(cè)試正常
上傳文件至后臺(tái):
上傳的日志信息查看:
總結(jié)
到此這篇關(guān)于微信小程序語(yǔ)音同步智能識(shí)別的實(shí)現(xiàn)案例代碼解析的文章就介紹到這了,更多相關(guān)微信小程序語(yǔ)音同步智能識(shí)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js獲取當(dāng)前年月日詳細(xì)教程(看這一篇就夠了)
這篇文章主要給大家介紹了關(guān)于js獲取當(dāng)前年月日的相關(guān)資料,JavaScript內(nèi)置的Date對(duì)象是獲取當(dāng)前日期最常用的工具之一,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12js中for...in循環(huán)對(duì)象時(shí)輸出key值順序混亂問(wèn)題解決
很久之前就有前輩告訴我用for...in循環(huán)對(duì)象屬性的順序不是固定的,xiam?這篇文章主要給大家介紹了關(guān)于js中for...in循環(huán)對(duì)象時(shí)輸出key值順序混亂問(wèn)題解決方法,需要的朋友可以參考下2023-11-11Javascript中引用類(lèi)型傳遞的知識(shí)點(diǎn)小結(jié)
這篇文章主要給大家介紹了關(guān)于Javascript中引用類(lèi)型傳遞的知識(shí)點(diǎn),文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-03-03利用JavaScript實(shí)現(xiàn)防抖節(jié)流函數(shù)的示例代碼
在開(kāi)發(fā)中我們經(jīng)常會(huì)遇到一些高頻操作,比如:鼠標(biāo)移動(dòng),滑動(dòng)窗口,鍵盤(pán)輸入等等,節(jié)流和防抖就是對(duì)此類(lèi)事件進(jìn)行優(yōu)化,降低觸發(fā)的頻率,以達(dá)到提高性能的目的。本文就教大家如何實(shí)現(xiàn)一個(gè)讓面試官拍大腿的防抖節(jié)流函數(shù),需要的可以參考一下2022-08-08JS實(shí)現(xiàn)兼容各種瀏覽器的獲取選擇文本的方法【測(cè)試可用】
這篇文章主要介紹了JS實(shí)現(xiàn)兼容各種瀏覽器的獲取選擇文本的方法,可實(shí)現(xiàn)鼠標(biāo)拖動(dòng)選擇文本的同時(shí),下方顯示區(qū)同步實(shí)時(shí)顯示選中內(nèi)容的功能,涉及javascript響應(yīng)鼠標(biāo)事件及頁(yè)面元素動(dòng)態(tài)操作技巧,需要的朋友可以參考下2016-06-06CSS鼠標(biāo)響應(yīng)事件經(jīng)過(guò)、移動(dòng)、點(diǎn)擊示例介紹
本文為大家介紹下CSS 鼠標(biāo)響應(yīng)事件:鼠標(biāo)經(jīng)過(guò)CSS、鼠標(biāo)移動(dòng)CSS、鼠標(biāo)點(diǎn)擊CSS以及示例,喜歡的朋友可以參考下2013-09-09JS實(shí)現(xiàn)表單中點(diǎn)擊小眼睛顯示隱藏密碼框中的密碼
這篇文章主要介紹了JS實(shí)現(xiàn)表單中點(diǎn)擊小眼睛顯示隱藏密碼框中的密碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考價(jià)值,需要的朋友可以參考下2020-04-04