前段時間公司要求做一個錄音功能。沒有頭緒,到網上查了下,發現微信的api提供的jssdk能滿足我的需求,就決定用這個開發了。先後查了不少資料加上自己琢磨和問前輩的經驗,花了大概一個多星期總算完成了,這邊記錄一下過程。
首先貼一篇教程出來,這篇教程前期省了我不少時間。
微信JSSDK中Config配置:http://blog.csdn.net/zhaojiacan/article/details/51025097 這篇講的很詳細,跟着配置是沒問題的,
主要注意的有兩點,其一是保證你的項目運作環境是你配置的js安全接口域名之下。其二是連結裡面出現的util工具類他沒有上傳,裡面
封裝的都是些普通的寫入寫出方法,自己寫都可以這不是重點。
jssdk配置成功後,就可以直接引入微信接口了。
wx.ready(function(){
/**
config資訊驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個用戶端的異步操作,
是以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確定正确執行。對于使用者觸發時才調用的接口,
則可以直接調用,不需要放在ready函數中。
*/
var localIds
var lysj //錄音時間
var lyCss //樣式
var lyName //圖檔名字
var localIdArr //本地ID集合字元串
var serverIdArr //服務端ID集合字元串
//注冊微信播放錄音結束事件【一定要放在wx.ready函數内】
/* wx.onVoicePlayEnd({
success: function (res) {
}
}); */
//假設全局變量已經在外部定義
//按下開始錄音
$('#talk_btn').on('touchstart', function(event){
event.preventDefault();
START = new Date().getTime();
if(localIds != null){
if(localIdArr == null){
localIdArr = localIds
}else{
localIdArr += "," + localIds;
}
}
localIds = null;
recordTimer = setTimeout(function(){
wx.startRecord({
success: function(){
localStorage.rainAllowRecord = 'true';
wx.onVoiceRecordEnd({
// 錄音時間超過一分鐘沒有停止的時候會執行 complete 回調
complete: function (res) {
END = new Date().getTime();
localIds = res.localId;
alert('最多隻能錄制一分鐘');
}
})
},
cancel: function () {
alert('使用者拒絕授權錄音');
}
});
},300);
});
var yyTotal = 1; //語音數量
//松手結束錄音
$('#talk_btn').on('touchend', function(event){
event.preventDefault();
if(localIds != null){
}else{
END = new Date().getTime();
}
if((END - START) < 300){
END = 0;
START = 0;
//小于300ms,不錄音
clearTimeout(recordTimer);
alert("錄音時間太短,錄音不做儲存!")
}else{
$("#voiceButton").show();
lysj = (END - START)/1000;
if(lysj <= 10){
lyName = 10;
lyCss = "height: 42px;width: 79px;";
}else if(10 < lysj && lysj <= 20){
lyName = 20;
lyCss = "height: 42px;width: 129px;";
}else if(20 < lysj && lysj <= 30){
lyName = 30;
lyCss = "height: 42px;width: 177px;";
}else if(30 < lysj && lysj <= 40){
lyName = 40;
lyCss = "height: 42px;width: 227px;";
}else if(40 < lysj && lysj <= 50){
lyName = 50;
lyCss = "height: 42px;width: 277px;";
}else if(50 < lysj && lysj <= 60){
lyName = 60;
lyCss = "height: 42px;width: 327px;";
}else{
lyName = 60;
lyCss = "height: 42px;width: 327px;";
lysj = "60.00";
}
lysj = ""+ lysj +"";
lysj = lysj.substr(0,lysj.lastIndexOf("."));
wx.stopRecord({
success: function (res) {
if(localIds != null){
}else{
localIds = res.localId;
}
if(yyTotal == 1){
$("#rwnrVoice").append("<li id='"+ localIds.substr(localIds.lastIndexOf("/")+1) +"' style='text-align: left;'><p style='position: absolute;margin-left:40px;margin-top:-2px'>"+ lysj +"s</p>"
+ "<img id='"+ localIds +"' οnclick='playVoice2(this.id)' "
+ "src='${ctxStatic }/common/img/yy/"+ lyName +"s.png?v=154' style='"+ lyCss +"'>"
+ "</li>");
yyTotal = 2;
}else{
$("#rwnrVoice").append("<li id='"+ localIds.substr(localIds.lastIndexOf("/")+1) +"' style='text-align: left;'><p style='position: absolute;margin-left:40px;margin-top:-2px'>"+ lysj +"s</p>"
+ "<img id='"+ localIds +"' οnclick='playVoice2(this.id)' "
+ "src='${ctxStatic }/common/img/yy/"+ lyName +"s.png?v=154' style='"+ lyCss +"'>"
+ "</li>");
yyTotal++;
}
playVoice(localIds);
},
fail: function (res) {
alert(JSON.stringify(res));
},
});
}
});
//播放語音接口
function playVoice(obj){
wx.playVoice({
localId: obj // 需要播放的音頻的本地ID,由stopRecord接口獲得
});
}
//播放語音接口
$('#_playVoice').on('click',function(){
wx.playVoice({
localId: localIds // 需要播放的音頻的本地ID,由stopRecord接口獲得
});
})
//暫停播放接口
$('#_pauseVoice').on('click',function(){
wx.pauseVoice({
localId: localIds // 需要播放的音頻的本地ID,由stopRecord接口獲得
});
})
//停止播放接口
$('#_stopVoice').on('click',function(){
wx.stopVoice({
localId: localIds // 需要播放的音頻的本地ID,由stopRecord接口獲得
});
})
//上傳錄音接口
$('#_uploadVoice').on('click',function(){
var result = confirm("确定上傳所有錄音嗎?");
if(result == true){
//隻有一段錄音直接上傳
if((yyTotal - 1) == 1){
uploadVoice(localIds);
}else{//多段錄音,周遊數組上傳
//删除最後一段錄音
if(localIds == null){
}else{
localIdArr += "," + localIds;
}
var strs= new Array();
strs = localIdArr.split(",");
for (i=0;i<strs.length ;i++ ){
uploadVoice(strs[i]);
}
}
//上傳後不可用
$("#_uploadVoice").attr("disabled","disabled");
//上傳後不可傳回普通任務
$("#inputText").attr("disabled","disabled")
isUploadVoice = true;
}else{
}
})
//上傳錄音
function uploadVoice(localId){
//調用微信的上傳錄音接口把本地錄音先上傳到微信的伺服器
//不過,微信隻保留3天,而我們需要長期儲存,我們需要把資源從微信伺服器下載下傳到自己的伺服器
wx.uploadVoice({
localId: localId, // 需要上傳的音頻的本地ID,由stopRecord接口獲得
isShowProgressTips: 1, // 預設為1,顯示進度提示
success: function (res) {
var serverId = res.serverId; // 傳回音頻的伺服器端ID
if(serverIdArr == null){
serverIdArr = serverId;
}else{
serverIdArr += "," + serverId;
}
$("#serverIds").val(serverIdArr);
},
error:function(res){
alert("上傳出錯")
}
});
}
//删除語音
/**
删除後把本地錄音集合中的最後一段錄音id賦予給目前id
**/
$("#_deleteVoice").on("click",function(){
var name = localIds.substr(localIds.lastIndexOf("/")+1);
$("li#" + name).remove();
var temp = localIdArr.substr(localIdArr.lastIndexOf("w"));
if(localIdArr.indexOf(",") != -1){
localIdArr = localIdArr.replace("," + temp,"");
}else{
localIdArr = localIdArr.replace(temp,"");
}
localIds = temp;
yyTotal--;
})
});
這邊是我調用到的接口,前端辭職了,我也就知道把樣式寫上來了,将就看。因為這邊考慮到使用者會多條語音上傳,且不用使用者錄一條點一次上傳這麼麻煩,這邊就建立幾個全局變量,錄好的本地id在存在變量裡。使用者點上傳的時候直接全部上傳。同時把傳回的serverId也是放到一個集合裡在表單送出到背景下載下傳到自己伺服器上。
接着是背景下載下傳微信臨時資源到本地伺服器比且轉碼(我們上傳的錄音),因為下載下傳的資源是amr格式的,h5不支援播放,我們得轉碼,為了不浪費伺服器的記憶體,轉碼後順便把amr格式的錄音删了。
/**
*
* 根據檔案id下載下傳檔案
*
*
*
* @param mediaId
*
* 媒體id
*
* @throws Exception
*/
public static String getInputStream(String mediaId) {
InputStream is = null;
File amrPath = null;
File mp3Path = null;
String access_token = PropertiesLoader.readProperties("access_token", "wxHelp.properties");
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token="
+ access_token + "&media_id=" + mediaId;
try {
URL urlGet = new URL(url);
HttpURLConnection http = (HttpURLConnection) urlGet
.openConnection();
http.setRequestMethod("GET"); // 必須是get方式請求
http.setRequestProperty("Content-Type","audio/mp3");
http.setDoOutput(true);
http.setDoInput(true);
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 連接配接逾時30秒
System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 讀取逾時30秒
http.connect();
// 擷取檔案轉化為byte流
is = http.getInputStream();
//擷取項目路徑
String path = Thread.currentThread().getContextClassLoader().getResource("").toString();
path = path.replace('/', '\\'); // 将/換成\
path = path.replace("file:", ""); //去掉file:
path = path.replace("classes\\", ""); //去掉classes\
path = path.replace("target\\", ""); //去掉target\
path = path.replace("WEB-INF\\", "");//去掉web-inf\
path = path.substring(1); //去掉第一個\,如 \D:\JavaWeb...
//檔案添加下級目錄位址
path += "static"+File.separator +"common" + File.separator +"voice";
UUID uuid = UUID.randomUUID();
String fileName = uuid.toString().replace("-", "");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
File file = new File(path);
File todayFile = new File(path + "\\" + sdf.format(new Date()));
amrPath = new File(todayFile + "\\" + fileName + ".amr");
mp3Path = new File(amrPath.toString().replace(".amr", ".mp3"));
//如果檔案夾不存在則建立
if (!file.exists() && !file.isDirectory()){
file.mkdir();
}
if (!todayFile.exists() && !todayFile.isDirectory()){
todayFile.mkdir();
}
BufferedInputStream in = new BufferedInputStream(is);
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(amrPath));
byte[] by = new byte[1024];
int lend = 0;
while((lend = in.read(by)) != -1){
out.write(by,0,lend);
}
in.close();
out.close();
//轉碼
changeToMp3(amrPath.toString(),mp3Path.toString());
//删除amr檔案
if(amrPath.isFile() && amrPath.exists()){
amrPath.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
//傳回一個資料庫存儲的格式 :wgBank\static\common\voice\2017-10-23\3d1043b7dafe44a78cf4f2e45047d999.mp3
String newPath = mp3Path.toString().replace("\\", "/");
newPath = newPath.substring(newPath.toString().lastIndexOf("/wgBank"));
return newPath;
}
public static void changeToMp3(String sourcePath, String targetPath) {
File source = new File(sourcePath);
File target = new File(targetPath);
AudioAttributes audio = new AudioAttributes();
Encoder encoder = new Encoder();
audio.setCodec("libmp3lame");
EncodingAttributes attrs = new EncodingAttributes();
attrs.setFormat("mp3");
attrs.setAudioAttributes(audio);
try {
encoder.encode(source, target, attrs);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InputFormatException e) {
e.printStackTrace();
} catch (EncoderException e) {
e.printStackTrace();
}
}
至此,傳回的路徑就可以存在資料庫了,展示的時候直接資料庫擷取路徑,然後h5播放就可以了。
<c:if test="${bean.voices != null && bean.voices != ''}">
<li class="mui-table-view-cell">
<span style="font-weight: bold;">語音詳情:</span>
<div style="font-size:14px;">
<c:forEach items="${bean.voices}" var="item" varStatus="sta">
<audio controls="controls" src="${item }">
</audio>
</c:forEach>
</div>
</li>
</c:if>
=========================================開發過程中遇到的一些問題和解決方法=============================================================
1:防止長按button時,自動觸發系統事件(複制,拷貝,剪切啥的)。給button加個樣式:ontouchstart="return false;"
2:連續多次點選播放的時候,會導緻微信閃退卡死。是以播放之前最好先初始化一下錄音
//播放語音接口 播放的時候先初始化
function playVoice2(obj){
var ua = navigator.userAgent.toLowerCase();
if (/iphone|ipad|ipod/.test(ua)) {
wx.stopVoice({
localId: obj // 需要播放的音頻的本地ID,由stopRecord接口獲得
});
} else if (/android/.test(ua)) {
wx.stopVoice({
localId: obj // 需要播放的音頻的本地ID,由stopRecord接口獲得
});
}
wx.playVoice({
localId: obj // 需要播放的音頻的本地ID,由stopRecord接口獲得
});
}
3:時間過短不錄音和超過一分鐘停止錄音,上面代碼已經有解決方法。
4:下載下傳下來的時候,電腦也播放不了。這個問題卡了挺久,後來給前輩看了下我的代碼才發現問題。
一開始寫的是"application/x-www-form-urlencoded"的json文本形式,但是我下的是音頻,是以一直都是檔案損壞,播放不了。
視訊:video/mpeg4
音頻:audio/mp3
5:最最最烏龍 卡我最最最長時間 也是解決方法中最最最簡單的,~~用錯api了,我開發的是企業号,但是我用的是服務号的api,之前一直不知道企業号和服務号的api是分開的
服務号api:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
企業号api:http://qydev.weixin.qq.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5
(微信接口開發,測試肯定是要在外網上才能測試,如何本地測試接口呢,就得把本機的ip封裝成正規域名,然後修改微信後排的js安全接口。這樣就可以測試拉。)
推薦大家用這款工具:natapp,官網有教程~成功的界面大緻是這樣的,這樣可以直接通路域名:h4g4rc.natappfree.cc來通路到本機了。