天天看點

【記錄】微信錄音開發(企業微信JSSDK-下載下傳到自己的伺服器-ffmpeg轉碼h5播放)

前段時間公司要求做一個錄音功能。沒有頭緒,到網上查了下,發現微信的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:下載下傳下來的時候,電腦也播放不了。這個問題卡了挺久,後來給前輩看了下我的代碼才發現問題。

【記錄】微信錄音開發(企業微信JSSDK-下載下傳到自己的伺服器-ffmpeg轉碼h5播放)

一開始寫的是"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來通路到本機了。

【記錄】微信錄音開發(企業微信JSSDK-下載下傳到自己的伺服器-ffmpeg轉碼h5播放)

繼續閱讀