您的位置:首页 > 移动开发 > 微信开发

【微信小程序】微信小程序--倒放音频的实现

2020-01-13 01:54 113 查看

微信小程序–倒放音频的实现

注:灵感来源与玩法参考:https://www.bilibili.com/video/av76976000

设计思路:

  • 1.微信小程序端:使用微信开发者工具实现微信小程序端的展示及交互的设计:
  • 1.1包括小程序的展示页面:
  • 1.2与服务器交互的逻辑部分:index.js页面。
  • 2.服务器端:服务器端采用java(jdk1.8) + tomcat搭建。
  • 2.1为完成音频的正常反转,我们还需要python语言完成转换音频的脚本,所以必须还要python语言环境,需要导入pydub包和ffmepg包(这两个包在python2.7中存在,可以不用额外导入)。

正式开始我们项目的编写:

1.微信小程序端:
  • 1.1微信小程序的架构:
  • 1.1.1 pages就是用来展示各个页面的。单独的一个page(比如图中的index就是一个页面)就是一个单独的页面。因为我们这个小程序只要需要一个页面就可以了,所以我们就是用系统配置的index page。logs是创建项目就给定的page,可以不用管。
  • 1.1.2 utils可以参见这个博客https://www.cnblogs.com/bellagao/p/6305485.html,我们当前的项目是不需要用到的。
  • 1.1.3 接下来的五个文件是关于全局配置的。暂时我们也不需要特别的关注,感兴趣的可以去官方文档就行。
  • 1.2小程序页面的展示
  • 1.2.1 进入index.wxml,语言类似于html语言,只有一部分不同,希望下代码如下:
<view class="container">
<button bindtap="startRecordMp3" class='btn'>开始录音</button>
<view class="=combine"><text>\n</text></view>
<button bindtap="stopRecord" class='btn'>停止录音</button>
<view class="=combine"><text>\n</text></view>
<button bindtap="playRecord" class='btn'>播放录音</button>
</view>

以上代码非常简单实现了三个按钮,分别使用bindtap绑定了index.js中的方法,即一点击改button就会调用index.js中的方法。
写完这些代码之后,页面就会是这个样子:

此时点击是不会有任何作用的,接下来我们进入index.js中编写我们的逻辑部分。

  • 1.3小程序逻辑的编写
const recorderManager = wx.getRecorderManager()
const innerAudioContext = wx.createInnerAudioContext()
const appURL = "http://xx.xxx.xx.xxx:8080/reverseAudio/reverseAudio/"
Page({

/**
* 页面的初始数据
*/
data: {
src:"",//本机存储录音的临时路径
audioTime: "",//录音的时间
reverseAudioURl: "",//服务器存储录音的临时路径
downloadAudio:""//本机存放反转后的临时路径
},

/**
* 录制mp3音频
*/
startRecordMp3: function () {
recorderManager.start({
format: 'mp3'
});
},

/**
* 停止录音
*/
stopRecord: function () {
recorderManager.stop()
},

/**
* 播放录音
*/
playRecord: function () {
var that = this;
// if (that.data.src == '') {
//   console.log("未进行录音");
//   return;
// }

console.log("开始播放");
innerAudioContext.src = that.data.downloadAudio;
innerAudioContext.play()
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this;

recorderManager.onStart(function (res) {
console.log("---开始录音---");
});

//停止音频监听事件
recorderManager.onStop(function (res) {
console.log(res)
that.setData({
src: res.tempFilePath,
audioTime: res.duration
})
console.log("音频长度" + that.data.audioTime);

//上传音频
wx.uploadFile({
url: appURL + "upload.do",//这是你自己后台的连接
filePath: that.data.src,
name: "file",//后台要绑定的名称
header: {
"Content-Type": "multipart/form-data"
},
//参数绑定
formData: {
audioTime: that.data.audioTime,
},
success: function (ress) {
var data = JSON.parse(ress.data);
console.log(data);

console.log("后台回复:" + data.code + " == " + data.obj);
that.setData({
reverseAudioURl: data.obj
});
console.log("开始下载===");
//下载反转音频
wx.downloadFile({
url: appURL + "download.do?fileName=" + that.data.reverseAudioURl,
success: function (res) {
that.data.downloadAudio = res.tempFilePath;
}
});
wx.showToast({
title: '倒放完成',
duration: 500
})
},
fail: function (ress) {
wx.showToast({
title: '录音有误,时间不允许超过7秒哦~',
duration: 1000
})
}
});

});

//播放监听的时间
innerAudioContext.onError(function (res){
console.log("错误播放--" + res.errMsg+" = "+res.errCode);
wx.showToast({
title: '录音好像出了些小问题,重新录一下试试看~',
duration: 1000
})
});
innerAudioContext.onPlay(function (res){
console.log("监听播放--"+res);
});
innerAudioContext.onEnded(function (res) {
console.log("自然播放--" +res);
});
innerAudioContext.onCanplay(function (res) {
console.log("进入播放状态--" +res);
});

},

/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {

},

/**
* 生命周期函数--监听页面显示
*/
onShow: function () {

},

/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {

},

/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {

},

/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {

},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {

},

/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {

}
})
  • 1.3.1 编写说明:
    为了能够录音完成后发送给服务端,服务端再将反转后的音频发回。我们需要做一些设计:
  • 1.3.2 开始录音:这个方法没有任何复杂的逻辑,调用相应的方法,执行即可。为了保证能拿到具体的信息相关,也可以加上录音方法的监听事件。
  • 1.3.3 停止录音:这个方法我们要做一些设计:当录音完成后我们应该通过录音完成的回调方法。拿到录音完成的音频的临时存储路径,并将其发送给服务端。此时我们需要给page的data设置一个值,用来存储服务端成功接收并反转后的音频地址。也就是我在页面上写出的reverseAudioURl。最后,因为innerAudioContext.src的一些局限性(无法以get请求携带参数来返回一个音频流,这会在服务端会报错的),所以我们需要在data中设置一个downloadAudio用来存放 小程序端从服务端下载来的反转音频(临时文件按路径)。

**理清停止录音的逻辑:
第一步:停止录音;
第二步:上传录音;
第三步:将收到的服务器发来的生成的路径赋值给page的data的reverseAudio中,并在将反转后的音频下载到本地,路径存储在data的reverseAudio中。

**为什么要这样做第三步?
由于服务器接收到录音后,肯定将录音反转后存储在服务器的某个位置上。但是又因为微信小程序是不允许使用session的,所以没有办法鉴别当前点击播放按钮的用户是要播放哪个音频。所以我采用了这种方法:将生成好的音频地址当做返回结果发送给小程序,小程序在处理完停止录音的success回调函数的时候,又会将这个地址发回给服务器,服务器再根据这个地址,将音频返回。从而下载到音频。

  • 1.3.4 最后我们根据上述原因,把reverseAudio当做参数发回服务器,服务器将文件发回,小程序将地址存储在data的reverseAudio中。
  • 1.3.5 播放录音:点击播放录音,使用data.reverseAudio赋值给InnerAudioContext.src,即可正常进入播放,如果为了其严谨性,将其中的一些错误播放之类的监听函数也可以加载进去。
2.服务端(Java编写):
  • 2.1 服务端的架构:
  • 2.1.1 我们采用mavn架构,Spring+SpringMVC(其实也可以不用采用Spring,毕竟项目极其的娇小)。controller层只需要做一些简单的数据接收(显然我没有按照这个风格写,因为项目小,又是一个小demo,所以没那么严谨)。
package com.reverse.audio.controller;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.reverse.audio.service.Reverse;

@Controller
@RequestMapping("/reverseAudio")
public class Main {

@Autowired
Reverse reverse;

public String  path = "/tmp/WechatMini/ReverseAudio/";

@RequestMapping("/upload.do")
@ResponseBody
public Result recvAudio(MultipartFile file,String audioTime) {
if(audioTime == null || audioTime.isEmpty() |Integer.parseInt(audioTime) > 7000) {
return new Result(Result.fail, new String("音频时长超时"));
}
File f = new File(path+"In/"+UUID.randomUUID().toString().replace("-", "").toString()+"Recive.mp3");//
try {
file.transferTo(f);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
return new Result(Result.fail, new String("文件转换出错"));
}

String result = null;
try {
result = reverse.reverse(f.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
return new Result(Result.fail, new String("音频反转出错1"));
} catch (InterruptedException e) {
e.printStackTrace();
return new Result(Result.fail, new String("音频反转出错2"));
}

return new Result(Result.success, result);
}

@RequestMapping("download.do")
public ResponseEntity<byte[]> download(HttpServletRequest request, String fileName) throws IOException {
System.out.println("播放参数:"+fileName);
File file = new File(fileName);
if(!file.exists()) {
System.out.println("文件不存在");
return null;
}
byte[] body = null;
InputStream is = new FileInputStream(file);
body = new byte[is.available()];
is.read(body);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attchement;filename=" + file.getName());
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
return entity;
}
}

接下来是service层:

package com.reverse.audio.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.springframework.stereotype.Service;

@Service
public class Reverse {

public static String executer= "python";//调用者是python
public static String file_path = "/tmp/WechatMini/ReverseAudio/Code/ReverseAudio.py";// python绝对路径
public static String format = "mp3";//转换前的音频格式(微信录制我们定义采用MP3格式)
public String reverse(String orginFilePath) throws IOException, InterruptedException {
String[] command_line = new String[] {executer,file_path,orginFilePath,format};
Process process = Runtime.getRuntime().exec(command_line);
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
StringBuilder sb = new StringBuilder();
while ((line = in.readLine()) != null) {
sb.append(line);
System.out.println("读出的内容"+line);
}
in.close();
process.waitFor();
return sb.toString();
}
}

接下来是python脚本(python脚本一定要放在service中指定的位置下)

from pydub import AudioSegment
from sys import argv
import uuid

def reverse(pathOrgin,format):
#
ted = AudioSegment.from_file(str(pathOrgin),str(format))
#
backwards = ted.reverse()
#
resultPath = "/tmp/WechatMini/ReverseAudio/Out/"+str(uuid.uuid1()).replace("-","")+"1.wav"
#导出格式为wav
backwards.export(resultPath,format="wav")
#打印出来,java在调用的时候才能拿到返回值
print(resultPath)
pathOrgin = str(argv[1])
format = str(argv[2])
reverse(pathOrgin,format)

这已经是所有的服务端代码。

这是我在整个过程中遇到的所有的坑:

  • 1.服务端一切正常却无法链接
  • 在微信开发者工具中,打开【设置】= =》【项目设置】= =》【不检验合法域名】,设置完成后如图所示:
    -
  • 2.无法解析文件上传之后的success回调函数
  • 这个在文档中明确写道

因为返回值是一个String,而我在服务端返回的是一个JSON对象,所以应该这样解决:

success: function (res) {
var data = JSON.parse(res.data);
that.setData({
reverseAudioURl: data.obj
});
}
  • 3.整个开发过程中最大的坑,为什么我录的音在第三方处理的时候回格式错误?
  • 开发工具录的音,不支持在python脚本中的处理,而【真机】是可以的
  • 这是一个惨无人道的坑,因为录音的格式错误,一度都让我放弃了,直到我在某个小小的角落里找到了这个答案,就是因为微信开发者工具知识开发者工具,不能代替真机,所以当有一些问题的时候,多试试,多搜搜就能找到答案。

最后:

要想微信小程序正式上线,仍然需要购买域名并配置,具体情况参见微信开发文档,微信开发者社区也有许多精彩的问题和解答。

  • 点赞 1
  • 收藏
  • 分享
  • 文章举报
后门的东墙 发布了30 篇原创文章 · 获赞 11 · 访问量 757 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: