使用微信js-sdk上傳語音並將語音下載到伺服器
JSSDK使用步驟
步驟一:繫結域名
先登入微信公眾平臺進入“公眾號設定”的“功能設定”裡填寫“JS介面安全域名”。
備註:登入後可在“開發者中心”檢視對應的介面許可權。
步驟二:引入JS檔案
備註:支援使用 AMD/CMD 標準模組載入方法載入
步驟三:通過config介面注入許可權驗證配置
所有需要使用JS-SDK的頁面必須先注入配置資訊,否則將無法呼叫(同一個url僅需呼叫一次,對於變化url的SPA的web app可在每次url變化時進行呼叫,目前Android微信客戶端不支援pushState的H5新特性,所以使用pushState來實現web app的頁面會導致簽名失敗,此問題會在Android6.2中修復)。
wx.config({
debug: true, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
appId: '', // 必填,公眾號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名
jsApiList: [] // 必填,需要使用的JS介面列表
});
步驟四:通過ready介面處理成功驗證
wx.ready(function(){
// config資訊驗證後會執行ready方法,所有介面呼叫都必須在config介面獲得結果之後,config是一個客戶端的非同步操作,所以如果需要在頁面載入時就呼叫相關介面,則須把相關介面放在ready函式中呼叫來確保正確執行。對於使用者觸發時才呼叫的介面,則可以直接呼叫,不需要放在ready函式中。
});
步驟五:通過error介面處理失敗驗證
wx.error(function(res){ // config資訊驗證失敗會執行error函式,如簽名過期導致驗證失敗,具體錯誤資訊可以開啟config的debug模式檢視,也可以在返回的res引數中檢視,對於SPA可以在這裡更新簽名。 });
介面呼叫說明
所有介面通過wx物件(也可使用jWeixin物件)來呼叫,引數是一個物件,除了每個介面本身需要傳的引數之外,還有以下通用引數:
1.success:介面呼叫成功時執行的回撥函式。
2.fail:介面呼叫失敗時執行的回撥函式。
3.complete:介面呼叫完成時執行的回撥函式,無論成功或失敗都會執行。
4.cancel:使用者點選取消時的回撥函式,僅部分有使用者取消操作的api才會用到。
5.trigger: 監聽Menu中的按鈕點選時觸發的方法,該方法僅支援Menu中的相關介面。
備註:不要嘗試在trigger中使用ajax非同步請求修改本次分享的內容,因為客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回。
以上幾個函式都帶有一個引數,型別為物件,其中除了每個介面本身返回的資料之外,還有一個通用屬性errMsg,其值格式如下:
呼叫成功時:"xxx:ok" ,其中xxx為呼叫的介面名
使用者取消時:"xxx:cancel",其中xxx為呼叫的介面名
呼叫失敗時:其值為具體錯誤資訊
音訊介面
開始錄音介面
wx.startRecord();
停止錄音介面
wx.stopRecord({
success: function (res) {
var localId = res.localId;
}
});
監聽錄音自動停止介面
wx.onVoiceRecordEnd({
// 錄音時間超過一分鐘沒有停止的時候會執行 complete 回撥
complete: function (res) {
var localId = res.localId;
}
});
播放語音介面
wx.playVoice({
localId: '' // 需要播放的音訊的本地ID,由stopRecord介面獲得
});
暫停播放介面
wx.pauseVoice({
localId: '' // 需要暫停的音訊的本地ID,由stopRecord介面獲得
});
停止播放介面
wx.stopVoice({
localId: '' // 需要停止的音訊的本地ID,由stopRecord介面獲得
});
監聽語音播放完畢介面
wx.onVoicePlayEnd({
success: function (res) {
var localId = res.localId; // 返回音訊的本地ID
}
});
上傳語音介面
wx.uploadVoice({
localId: '', // 需要上傳的音訊的本地ID,由stopRecord介面獲得
isShowProgressTips: 1, // 預設為1,顯示進度提示
success: function (res) {
var serverId = res.serverId; // 返回音訊的伺服器端ID
}
});
備註:上傳語音有效期3天,可用微信多媒體介面下載語音到自己的伺服器,此處獲得的 serverId 即 media_id,參考文件 .目前多媒體檔案下載介面的頻率限制為10000次/天,如需要調高頻率,請登入微信公眾平臺,在開發 - 介面許可權的列表中,申請提高臨時上限。
下面開始碼程式碼:
voice.js
var URIstring=location.href.split('#')[0];//獲取當前頁面的全路徑 var url="/zf/ConfigParam?Rurl="+encodeURIComponent(URIstring);//將路徑傳入後臺加密時使用 $(document).ready(function(){ $.ajax({ url :encodeURI(url),// encodeURI(encodeURI(url)), type : 'POST', async: false, dataType : 'json', timeout : 5000, error: function(XMLHttpRequest, textStatus, errorThrown) { alert("不好意思,出了點小問題"); }, success : function(req) { wx.config({ debug: false,//開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。 appId: req.appid, // 必填,公眾號的唯一標識 timestamp:req.timestamp, // 必填,生成簽名的時間戳 nonceStr: req.nonceStr, // 必填,生成簽名的隨機串 signature:req.signature,// 必填,簽名,見附錄1 jsApiList:['chooseImage','previewImage','uploadImage','downloadImage',]// 必填,需要使用的JS介面列表,所有JS介面列表見附錄2 }); } }); var voice = { localId: '', serverId: '' };//全域性變數 var END; }); //鬆手結束錄音 }); //上傳錄音 } // 4.4 監聽錄音自動停止 }); document.querySelector('#playVoice').onclick = function () { }; // 暫停播放音訊 |
---|
/*使用微信jssdk介面錄音,在同一個域只需要授權一次,即第一次使用錄音的時候,微信自己會彈出對話方塊詢問是否允許錄音,使用者點選允許後,之後再使用錄音時,便不會再諮詢使用者是否允許。
在第一次按住錄音後,由於使用者未曾允許錄音,微信會提示使用者授權允許在本頁面使用微信錄音功能,這時使用者會放開錄音按鈕轉而去點選允許,在使用者允許後,才真正會開始錄音,而此時使用者早已放開錄音按鈕,那麼錄音按鈕上便不會再有touchend事件,錄音便會一直進行。
解決策略:使用localStorage記錄使用者是否曾授權,並以此來判斷是否需要在剛進入頁面是自動錄一段錄音來觸發使用者授權*/
if(!localStorage.rainAllowRecord || localStorage.rainAllowRecord !== 'true'){
wx.startRecord({
success: function(){
localStorage.rainAllowRecord = 'true';
setTimeout(function(){
wx.stopRecord({
fail:function(res){
//alert("停止失敗");
},
success: function (res) {
var localId = res.localId;
}
});
},800);//這裡設定800毫秒,是因為如果使用者錄音之後馬上鬆開按鈕,會成 wx.stopRecord不起作用的情況,然後會一直錄音,所以時間設定長一點
//clearTimeout(t);
},
cancel: function () {
alert('使用者拒絕授權錄音');
}
});
}
wx.error(function (res) {
wx.stopRecord();
alert(res.errMsg);
});
以上是前端js,具體業務具體操作
後臺程式碼
ConfigParam.java
import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONObject; public class ConfigParam extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); String Rurl=java.net.URLDecoder.decode(request.getParameter("Rurl"),"utf-8"); //System.out.println(Rurl); Map<String, String> ret= WeixinUtil.getJSSDKData(Rurl,WechatUtil.appId,WechatUtil.appSecret); JSONObject json=JSONObject.fromObject(ret); //System.out.println(json.toString()); out.print(json); } } |
---|
WeixinUtil.java 和AccessToken.java
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * 公眾平臺通用介面工具類 * * * */ public class WeixinUtil { private static Log log = LogFactory.getLog(WeixinUtil.class); // 獲取access_token的介面地址(GET) 限200(次/天) public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token? grant_type=client_credential&appid=APPID&secret=APPSECRET"; private static Map<String,AccessToken> tokenMap = new HashMap<String,AccessToken>(); /** * 獲取access_token * * @param appid 憑證 * @param appsecret 金鑰 * @param flag 是否重新獲取 * @return */ public static AccessToken getAccessToken(String appid, String appsecret,boolean flag) { AccessToken accessToken = null; log.info("=================getAccessToken-start"); if(!flag){ accessToken = tokenMap.get(appid); } long now = System.currentTimeMillis(); if(accessToken != null && !flag){ long lastTime = accessToken.getCreateTime(); //當accessToken還在有效期內 if(now <= (lastTime + accessToken.getExpiresIn() * 1000) ){ log.info("讀取快取accessToken=="+accessToken); return accessToken; } } String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret); JSONObject jsonObject = httpRequest(requestUrl, "GET", null); // 如果請求成功 if (null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setCreateTime(now); accessToken.setToken(jsonObject.getString("access_token")); accessToken.setExpiresIn(jsonObject.getInt("expires_in") - 300); log.info("重新獲取accessToken=="+accessToken); } catch (JSONException e) { accessToken = null; // 獲取token失敗 log.error("獲取token失敗 errcode:"+jsonObject.getInt("errcode")+" errmsg:{"+ jsonObject.getString("errmsg")+"}" ); } } log.info("=================getAccessToken-end"); tokenMap.put(appid, accessToken); return accessToken; } /** * 獲取access_token * * @param appid 憑證 * @param appsecret 金鑰 * @return */ public static AccessToken getAccessToken(String appid, String appsecret ) { log.info("getAccessToken:重新獲取AccessToken"); return getAccessToken(appid, appsecret, true); } public static String JS_API_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; /** * * @param accessToken * @return */ public static String getJsapi_ticket(String accessToken){ log.info("=====================getJsapi_ticket:start"); String url = JS_API_URL.replace("ACCESS_TOKEN", accessToken); JSONObject json = httpRequest(url, "GET", null); log.info("getJsapi_ticket:"+json); String jsapi_ticket = ""; try { jsapi_ticket = json.getString("ticket"); } catch (Exception e) { log.error("=====================getJsapi_ticket:error"+json); e.printStackTrace(); } log.info("=====================getJsapi_ticket:end"); return jsapi_ticket; } //JS SDK 中需要的相關的資料 private static Map<String,String > JsSDKMap = new HashMap<String, String>(); public static String getJsapi_ticket_cache(String accessToken ){ String jsapi = JsSDKMap.get(accessToken); if(jsapi != null){ log.info("讀取jsapi_ticket快取"+jsapi ); return jsapi; }else{ jsapi = getJsapi_ticket(accessToken); log.info("重新整理Jsapi_ticket:"+jsapi+"===="+accessToken ); JsSDKMap.put(accessToken, jsapi); } return jsapi; } //獲得jsapi_ticket,noncestr,timestamp public static Map<String ,String> getJSSDKData(String requestUrl,String appid,String appsecret){ log.info("=================getJSSDKData-start"); //1)獲取access_token AccessToken accessToken = getAccessToken(appid, appsecret,false); String token = accessToken.getToken(); log.info("accessToken:"+accessToken); //2)獲取jsapi_ticket(有效期7200秒 String jsapi_ticket = getJsapi_ticket_cache(token); log.info("jsapi_ticket:"+jsapi_ticket); //3)獲取//隨機字串 String noncestr = Double.toString(Math.random()).substring(2, 15);//隨機字串 //4)獲取 隨機時間戳 String timeStamp =((int)(new Date().getTime()/1000))+"";//隨機時間戳 //5)獲取簽名 String str = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + noncestr + "×tamp="+ timeStamp +"&url=" + requestUrl; log.info("str:" + str); String signature = SHA1Util.Sha1(str); Map<String ,String> map = new HashMap<String, String>(); map.put("ticket", jsapi_ticket); map.put("noncestr", noncestr); map.put("timestamp", timeStamp); map.put("signature", signature); map.put("url", requestUrl); map.put("appid", appid); log.info("=================getJSSDKData-end"); return map; } /** * 發起https請求並獲取結果 * * @param requestUrl 請求地址 * @param requestMethod 請求方式(GET、POST) * @param outputStr 提交的資料 * @return JSONObject(通過JSONObject.get(key)的方式獲取json物件的屬性值) */ public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; StringBuffer buffer = new StringBuffer(); try { // 建立SSLContext物件,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext物件中得到SSLSocketFactory物件 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); // 設定請求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); // 當有資料需要提交時 if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意編碼格式,防止中文亂碼 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 將返回的輸入流轉換成字串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 釋放資源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { log.error("Weixin server connection timed out."); } catch (Exception e) { log.error("https request error:{}", e); } return jsonObject; } } /** |
---|
uploadVoice
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); //String mediaId=request.getParameter("mediaId"); Map<String, String> map = new HashMap<String, String>(); String mediaId = request.getParameter("mediaId"); String folderName="/app/ss/"+new SimpleDateFormat("yyyy/MMdd/").format(new Date()); try { File dir = new File(folderName); if (!dir.exists()) dir.mkdirs(); } catch (Exception ex) { } String Str=""; String randstr = UUID.randomUUID().toString().replaceAll("-", ""); String path=folderName+randstr;//絕對路徑 String frontName = "" + randstr;//相對路徑 String absFileName=path+".jpg";//"D:\\img\\a.jpg";// String str= DloadImgUtil.downloadMedia(mediaId, absFileName,path+"_240.mp3","2");//呼叫下載介面 map.put("status", "1"); map.put("msg",Str); map.put("path", ""); map.put("photoUrl",""); JSONObject json = JSONObject.fromObject(map); out.print(json); } |
---|
記住,大坑來了,由於從微信伺服器下載下來的音訊格式是amr格式的,為了實現使用h5標籤能夠播放,需要轉成mp3格式
我是使用ffmpeg進行轉換的
使用ffmpeg將amr格式轉為mp3格式時需注意:windows系統使用ffmpeg.exe,linux系統使用ffmpeg(去官網下載對應版本http://ffmpeg.org/download.html)
DloadVoicUtil.java
public class DloadImgUtil { private final static String FFMPEG_PATH; static { FFMPEG_PATH =DloadImgUtil.class.getResource("ffmpeg.exe").getFile(); //windows系統使用, FFmpeg檔案和DloadImgUtil 同一目錄下 } // static { String filePath = null; AccessToken accessToken = WeixinUtil.getAccessToken(WechatUtil.appId, WechatUtil.appSecret, false); // 拼接請求地址 String requestUrl = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID"; requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken.getToken()) .replace("MEDIA_ID", mediaId); String returnstr="0"; BufferedInputStream bis = null; FileOutputStream fos = null; HttpsURLConnection conn = null; try { // 建立SSLContext物件,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext物件中得到SSLSocketFactory物件 SSLSocketFactory ssf = sslContext.getSocketFactory(); // URL url = new URL(null,requestUrl,new // sun.net.www.protocol.https.Handler()); // URL url = new URL(requestUrl); URL url = new URL(requestUrl); conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(ssf); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 設定請求方式(GET/POST) conn.setRequestMethod("GET"); // 根據內容型別獲取副檔名 // String fileExt = DloadImgUtil // .getFileexpandedName(conn.getHeaderField("Content-Type")); // 將mediaId作為檔名 filePath = savePath; bis = new BufferedInputStream(conn.getInputStream()); fos = new FileOutputStream(new File(filePath)); byte[] buf = new byte[8096]; int size = 0; while ((size = bis.read(buf)) != -1) fos.write(buf, 0, size); amr2mp3(savePath,out); } catch (Exception e) { returnstr="-1"; e.printStackTrace(); } finally { // 釋放資源 conn.disconnect(); try { // 關閉流,釋放資源 if (fos != null) { fos.close(); } if (bis != null) { bis.close(); } } catch (Exception e) { e.getStackTrace(); } } return returnstr; } /** |
---|