APP- 录音录制上传下载播放的实现 -YB

来源自己有道笔记:
【有道云笔记】20241129 APP 录音功能调研:https://note.youdao.com/s/Ckfbvrah

一、先说结论:

实现效果:在 apicloud 平台下分别写 html 方式和 vue 方式(V52)实现。录音操作效果录屏如下:

  • html 方式的录屏:

视频地址:https://yuanbao-oss.oss-cn-shenzhen.aliyuncs.com/img/public_imgs/PicGo/202412161615528.mp4

  • vue 方式(V52 项目应用)的录屏::

视频地址:https://yuanbao-oss.oss-cn-shenzhen.aliyuncs.com/img/public_imgs/PicGo/202412161617457.mp4
​​

二、录音功能调研实现过程:

2.1 原始需求:

设备模块的 APP 页面,需要语音录入保存到单据上作为附件,后续可以在其他页面播放这个录音。

2.2 功能拆解

按这个功能机制拆分为三个步骤:

  1. 录音:生成录音文件;

  2. 将录音文件上传到后端;

  3. 从后端下载获取后播放录音文件;

2.3 调研各步骤的实现方式

1、录音:

用友(APICLOUD)平台,API 方式或模块方式,各有相关逻辑。

  • API 方式:

  • ** 模块方式:** 商城里部分模块收费、少部分免费。

    • 收费:audioRecorder 和 mp3Recorder 模块。

      • audioRecorder 模块收费 64 块钱 / 每年。贵了点。为官方提供。

image-20241216162216802

image-20241216162229151

  • mp3Recorder:非官方。

image-20241216162243758

  • 免费:FNRecordMp3 和 recordForMP3

image-20241216162319107

接口 API 文档:https://developer.yonyou.com/docs/Client-API/Func-Ext/FNRecordMp3#open

image-20241216162326248

接口 API 文档:https://developer.yonyou.com/docs/Client-API/Func-Ext/recordForMP3

2、将录音文件上传到后端; (以下为代码片段为前期参考,完整可用代码见文末 DEMO 代码)

var url = “http://” + serverIp + “:” + serverPort + “/” + serverProject + “/padController!uploadPhotos4APP.m”;

// 上传图片  
// url:上传的url地址  
// data:上传的文件  
// callback:上传成功返回地址  
function uploadFile(url, data) {  
 var params = new Object();  
 params.url = url;  
 params.timeout = 30;  
 params.dataType = 'json';  
 params.method = 'post';  
 var data1 = {  
 files: {  
 file: data  
 }  
 };  
​  
 api.ajax({  
 url: url,  
 method: 'post',  
 data: {  
 files: {  
 //file: 'fs://a.gif'  
 file: data  
 }  
 }  
 }, function (res, err) {  
 if (res) {  
 // alert( JSON.stringify( res ) );  
 } else {  
 // alert( JSON.stringify( res ) );  
 }  
 });  
}
3、从后端获取后播放录音文件 (以下为代码片段为前期参考,完整可用代码见文末 DEMO 代码)

分为两步:

最好是先判断这个文件是否存在,不存在再下载,若存在则直接走播放。

api.download({  
 url: downloadUrl,  
 report: true,  
 savePath: 'fs://'+pdfUrl,  
}, function(ret, err){  
 if (ret && 1 == ret.state) {/* 下载完成 */  
 savePath = ret.savePath;  
 console.log(ret.savePath);  
   
 var fs = api.require('fs');  
 fs.rename({  
 oldPath: 'fs://'+pdfUrl,  
 newPath: 'fs://C-'+pdfUrl  
 }, function(ret, err) {  
 //下载完成后调用PDF批注模块  
 var modulePDF = api.require('modulePDF');  
 modulePDF.open({  
 x:0,  
 y:headerPos.h,  
 w:api.winWidth,  
 h:frameH,  
 appId : api.appId + '',//当前应用的appid  
 inpath: 'C-'+pdfUrl,//输入的pdf文件名,路径为sdcard/UZMap/ + appId + /123.pdf  
 outpath: pdfUrl//输出的pdf文件名,路径为sdcard/UZMap/ + appId + /tttt.pdf  
 },function(ret,err){  
 });  
 });  
 }  
});
  • 播放音频文件:
api.startPlay({  
 path: 'widget://res/1.mp3'  
}, function(ret, err) {  
 if (ret) {  
 api.alert({  
 msg:'播放完成'  
 });  
 } else {  
 api.alert({  
 msg:JSON.stringify(err)  
 });  
 }  
});

暂停播放:api.stopPlay() 不需要传参

停止播放:api.stopPlay() 不需要传参

三、完成 DEMO 开发测试:

效果差不多符合预期。页面如下:

image-20241216162502969

再引入到 MOM V5.2 项目 APP 中:

image-20241216162525272

四、附上可用的 DEMO 代码:

1、主要 js:

// 录音开始  
function testAudioRecordStart() {  
 let fileName = this.generateUniqueFileName('AUDIO');  
 let param = {  
 type: 'wav',  
 path: 'fs://' + fileName + '.wav',  
 }  
 api.startRecord(param);  
 api.toast({  
 msg: '录音开始...',  
 duration: 3000,  
 location: 'top'  
 });  
}  
​  
// 录音结束-本地  
function testAudioRecordStop() {  
 api.stopRecord(function(ret, err) {  
 if (ret) {  
 var path = ret.path; // 音频文件的路径  
 var duration = ret.duration; // 音频文件的时长  
 console.log('audio 路径:' + path);  
 console.log('audio 时长:' + duration);  
 audioPath = path;  
 }  
 api.toast({  
 msg: '录音结束,路径:' + ret.path + ",时长:" + ret.duration,  
 duration: 3000,  
 location: 'top'  
 });  
 });  
}  
// 录音播放-本地  
function testAudioPlay() {  
 startPlay(audioPath);  
}  
// 解析路径播放录音文件  
function startPlay(audioPath) {  
 // 判断录音文件路径是否为空  
 if (!audioPath) {  
 alert("录音文件路径为空,请先完成录制。");  
 return;  
 }  
    
 // 截取出文件名  
 console.log("文件路径:" + audioPath);  
 let fileName = getFileNameFromPath(audioPath);  
 console.log("文件名称:" + fileName);  
​  
 api.startPlay({  
 path: 'fs://' + fileName,  
 }, function(ret, err) {  
 if (ret) {  
 api.toast({  
 msg: '播放完成!',  
 duration: 3000,  
 location: 'top'  
 });  
 } else {  
 api.alert({  
 msg:JSON.stringify(err)  
 });  
 }  
 });  
}  
// 停止播放  
function testAudioPlayStop()  
{  
 api.stopPlay()  
}  
// 从文件路径中截取出文件名  
function getFileNameFromPath(path) {  
 // 使用 '/' 作为分隔符将路径分割成数组  
 var parts = path.split('/');  
 // 数组的最后一项即为文件名  
 return parts[parts.length - 1];  
}  
// 生成唯一文件名:基础名_时间戳_随机数  
function generateUniqueFileName(baseName) {  
 // 获取当前时间戳(毫秒级别)  
 const timestamp = new Date().getTime();  
    
 // 生成一个4位随机数  
 const randomPart = Math.floor(1000 + Math.random() * 9000);  
    
 // 拼接成最终文件名  
 return `${baseName}_${timestamp}_${randomPart}`;  
}  
// 录音结束-上传到服务器  
function testAudioRecordStopUpload()  
{  
 api.stopRecord(function(ret, err) {  
 if (ret) {  
 var path = ret.path; // 音频文件的路径  
 var duration = ret.duration; // 音频文件的时长  
 console.log('audio 路径:' + path);  
 console.log('audio 时长:' + duration);  
 audioPath = path;  
​  
 // 截取出文件名  
 console.log("文件路径:" + audioPath);  
 let fileName = getFileNameFromPath(audioPath);  
 console.log("文件名称:" + fileName);  
​  
 let url = serverUrl + "ybTestController!uploadAudio.m";  
 api.ajax({  
 url: url,  
 method: 'post',  
 data: {  
 files: {  
 file: 'fs://' + fileName,  
 }  
 }  
 }, function (res, err) {  
 console.log( JSON.stringify( res ) );  
 if (res && res.type) {  
 api.toast({  
 msg: '录音结束,并上传文件成功,objId:' + res.data,  
 duration: 3000,  
 location: 'top'  
 });  
 // 赋值文件对象GID  
 fileGid = res.data;   
 } else {  
 alert( JSON.stringify( err ) );  
 }  
 });  
 }  
 });  
}  
// 录音播放-从服务器上下载后播放  
function testAudioPlayDownload() {  
 // 判断录音文件路径是否为空  
 if (!fileGid) {  
 alert("录音文件对象为空,请先完成录制上传。");  
 return;  
 }  
​  
 let url = serverUrl + "ybTestController!downloadAudio.m?id=" + fileGid;  
 let fileName = new Date().getTime() + '.wav'; // 用时间戳作名称,下载不用加随机数  
 api.download({  
 url: url,  
 report: true,  
 savePath: 'fs://' + fileName,  
 }, function(ret, err){  
 /* 下载进行中时 */  
 if(ret && 0 == ret.state){  
 api.toast({  
 msg: "正在下载音频" + ret.percent + "%",  
 duration: 2000  
 });  
 }  
​  
 /* 下载完成,之后进行播报 */  
 if (ret && 1 == ret.state) {  
 var savePath = ret.savePath;  
 console.log('savePath :' + savePath);  
 startPlay(savePath);  
 }  
​  
 /* 下载失败 */  
 if(ret && 2 == ret.state){  
 api.toast({  
 msg: "下载失败",  
 duration: 5000  
 });  
 }  
 });  
}

2、vue 完整页面,自行提取所需内容块。

完整 vue 代码文件: 见打赏区。
vue 上传代码:

stopRecord() {  
 // 判断任务单是否选择  
 if (!this.taskId) {  
 alert("请先选择任务单!");  
 return;  
 }  
​  
 console.log("-------------:停止录音");  
 let self = this;  
 this.recordAudioText = "录音录制开始";  
 this.recordAudioColor = "primary";  
 document.getElementById("listen_gif").style.display = "none";  
​  
 api.stopRecord(function(ret, err) {  
 if (ret) {  
 var path = ret.path; // 音频文件的路径  
 var duration = ret.duration; // 音频文件的时长  
 console.log("audio 路径:" + path);  
 console.log("audio 时长:" + duration);  
 self.audioPath = path;  
​  
 // 如果录音时间过短,则提示录音时间过短,不作保存  
 if (duration < 1) {  
 api.toast({  
 msg: "录音时间过短,少于一秒,请重新录音!",  
 duration: 3000,  
 location: "top"  
 });  
 return;  
 }  
​  
 // 截取出文件名  
 console.log("文件路径:" + self.audioPath);  
 let fileName = self.getFileNameFromPath(self.audioPath);  
 console.log("文件名称:" + fileName);  
​  
 let url = "uempEquipCheckController!uploadAudio.m?taskId=" + self.taskId;  
​  
 // 只能用原生api.ajax方式才能获取录音文件上传,用http.post方式上传不了  
 // var serverUrl = http.getPrefix(); // "http://10.1.8.246/prj-v5-web/";  
 var serverUrl = self.getServerUrl(); // 自己写获取服务地址前缀的方法  
 console.log("serverUrl:" + serverUrl);  
​  
 api.ajax(  
 {  
 url: serverUrl + url,  
 method: "post",  
 data: {  
 files: {  
 file: "fs://audio/" + fileName  
 },  
 }  
 },  
 function(res, err) {  
 console.log(JSON.stringify(res));  
 if (res && res.type == "success") {  
 api.toast({  
 msg: "录音结束,并上传文件成功,objId:" + res.data,  
 duration: 3000,  
 location: "top"  
 });  
 // 赋值文件对象GID  
 self.fileGid = res.data;  
 } else {  
 alert(JSON.stringify(res));  
 }  
 }  
 );  
 }  
 });  
},

3、后端代码:

测试时:

image-20241216164515717

正式使用时:

image-20241216164635848

注意:后端的内容没什么差别,主要还是前端的上传写法不太一样。

0 打赏
打赏 10 积分后可见