微信小程序語(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、文件上傳工具類的實(shí)現(xiàn)
tools工具類包中主要存文件通用的文件上傳工具類,該工具類會(huì)將文件上傳至配置指定的文件夾下,并將文件信息寫(xiě)入upload_file表中。
- 文件信息實(shí)體類:與數(shù)據(jù)庫(kù)中表upload_file對(duì)應(yīng);
- 文件存儲(chǔ)倉(cāng)庫(kù)類:通過(guò)Spring Data JPA接口實(shí)現(xiàn)數(shù)據(jù)的CRUD;
- 文件上傳工具接口:對(duì)外統(tǒng)一封裝文件上傳方法;
- 文件上傳工具實(shí)現(xiàn)類:實(shí)現(xiàn)文件上傳方法接口。
文件信息實(shí)體類: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í)際名稱
*/
@Column(name = "real_name")
private String realName;
/**
* 文件名
*/
@NotNull
@Column(name = "file_name")
private String fileName;
/**
* 文件主名稱
*/
@NotNull
@Column(name = "primary_name")
private String primaryName;
/**
* 文件擴(kuò)展名
*/
@NotNull
private String extension;
/**
* 存放路徑
*/
@NotNull
private String path;
/**
* 文件類型
*/
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ù)類: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)類:UploadFileToolImpl.java
/**
* 文件上傳實(shí)現(xiàn)類
*
* @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ò)展名得到文件類型
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ò)展名給文件類型
*
* @param extension 文件擴(kuò)展名
* @return 文件類型
*/
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í)際名稱
* @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)類
*
* @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)文章
JavaScript中的FileReader圖片預(yù)覽上傳功能實(shí)現(xiàn)代碼
本文通過(guò)實(shí)例代碼給大家介紹了js中的FileReader圖片預(yù)覽上傳功能,代碼分為html和js代碼兩部分,具體實(shí)現(xiàn)代碼大家參考下本文2017-07-07
超實(shí)用的javascript時(shí)間處理總結(jié)
在大家日常開(kāi)發(fā)中常常會(huì)要對(duì)時(shí)間進(jìn)行處理,以達(dá)到大家想要的效果,所以本文整理了一些javascript常用的時(shí)間處理類,有需要的可以參考借鑒。2016-08-08
Javascript實(shí)現(xiàn)頁(yè)面滾動(dòng)時(shí)導(dǎo)航智能定位
本篇文章主要介紹了Javascript實(shí)現(xiàn)頁(yè)面滾動(dòng)時(shí)導(dǎo)航智能定位,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
javascript實(shí)現(xiàn)自由編輯圖片代碼詳解
這篇文章主要介紹了javascript實(shí)現(xiàn)自由編輯圖片代碼詳解,在當(dāng)下的的前端項(xiàng)目中,圖片功能可以說(shuō)是非常常見(jiàn)的,圖片的展示、圖片的裁剪編輯、圖片的上傳等,那么我們的項(xiàng)目便來(lái)了個(gè)需求。,需要的朋友可以參考下2019-06-06
JavaScript深入淺出__proto__和prototype
這篇文章主要介紹了JavaScript深入淺出__proto__和prototype,文章基于JavaScript的相關(guān)資料展開(kāi)詳細(xì)的內(nèi)容介紹。具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05
JavaScript字符串操作的四個(gè)實(shí)用技巧
在制作前端頁(yè)面的過(guò)程中,經(jīng)常需要用到JavaScript進(jìn)行邏輯處理,很多時(shí)候都需要對(duì)字符串進(jìn)行操作,這篇文章主要給大家介紹了關(guān)于JavaScript字符串操作的四個(gè)實(shí)用技巧,需要的朋友可以參考下2021-07-07
JavaScript性能優(yōu)化技術(shù)深入研究
這篇文章主要介紹了JavaScript性能優(yōu)化技術(shù),優(yōu)化涉及多個(gè)方面,包括代碼執(zhí)行效率、內(nèi)存使用、DOM操作、網(wǎng)絡(luò)請(qǐng)求等,通過(guò)合理的優(yōu)化策略,可以顯著提升應(yīng)用的響應(yīng)速度和用戶體驗(yàn),掌握這些技術(shù)對(duì)于構(gòu)建高性能的JavaScript應(yīng)用至關(guān)重要,需要的朋友可以參考下2025-02-02

