TML5+API Reference
<template>
<view>
<view @click="go();">開始</view>
<view @click="so();">結束</view>
</view>
</template>
<script>
var r = null;
export default {
data() {
return {
}
},
onLoad() {
// #ifdef APP-PLUS
// 監聽設備網絡狀態變化事件
plus.globalEvent.addEventListener('netchange', function(){});
r = plus.audio.getRecorder();
// #endif
},
methods: {
go(){
if ( r == null ) {
console.log("Device not ready!")
return;
}
r.record( {filename:"_doc/audio/"}, function () {
console.log("Audio record success!")
}, function ( e ) {
console.log("Audio record failed: " + e.message )
} );
},
so(){
r.stop();
}
}
}
</script>
這是用uniapp寫的例子
uni-app App 端內置 HTML5+ 引擎,讓 js 可以直接調用豐富的原生能力。
小程序及 H5 等平臺是沒有 HTML5+ 擴展規范的,因此在 uni-app 調用 HTML5+ 的擴展規范時,需要注意使用條件編譯。否則運行到h5、小程序等平臺會出現 plus is not defined錯誤。
// #ifdef APP-PLUS
var appid = plus.runtime.appid;
console.log('應用的 appid 為:' + appid);
// #endif
在html中使用plus的api,需要等待plus ready。 而uni-app不需要等,可以直接使用。而且如果你調用plus ready,反而不會觸發。
在普通的 H5+ 項目中,需要使用 document.addEventListener 監聽原生擴展的事件。
uni-app 中,沒有 document。可以使用
plus.globalEvent.addEventListener 來實現。
// #ifdef APP-PLUS
// 監聽設備網絡狀態變化事件
plus.globalEvent.addEventListener('netchange', function(){});
// #endif
同理,在 uni-app 中使用 Native.js 時,一些 Native.js 中對于原生事件的監聽同樣需要按照上面的方法去實現。
從Recorder H5 GitHub開源庫優化后,對邊錄邊轉碼成小語音片段文件實時上傳服務器這種操作支持非常良好,因此以前不太好支持的H5語音通話已經有了更好的突破空間。因此花了兩晚時間打造了一個H5語音通話聊天的demo。
歡迎在線把玩:https://xiangyuecn.github.io/Recorder/
局域網H5版對講機
(1)數據傳輸
github demo中考慮到減少對服務器的依賴,因此采用了WebRTC P2P傳輸功能,無需任何服務器支持即可實現局域網內的兩個設備之間互相連接,連接代碼也算簡單。有服務器支持可能就要逆天了,不過代碼也會更復雜。
如果正式使用,可能不太會考慮使用WebRTC,用WebSocket通過服務器進行轉發可能是最佳的選擇。
WebRTC局域網P2P連接要點(實際代碼其實差不多,只不過多做了點兼容):
/******Peer A(本機)******/ var peerA=new RTCPeerConnection(null,null) //開啟會話,等待遠程連接 peerA.createOffer().then(function(offer){ peerA.setLocalDescription(offer); peerAOffer=offer; }); var peerAICEList=[......] //通過peerA.onicecandidate監聽獲得所有的ICE連接信息候選項,如果有多個網絡適配器,就會有多個候選 //創建連接通道對象,A端通過這個來進行數據發送 var peerAChannel=peerA.createDataChannel("RTC Test"); /******Peer B(遠程)******/ var peerB=new RTCPeerConnection(null,null) //連接到Peer A peerB.setRemoteDescription(peerAOffer); //開啟應答會話,等待Peer A確認連接 peerB.createAnswer().then(function(answer){ peerB.setLocalDescription(answer); peerBAnswer=answer; }); //把Peer A的連接點都添加進去 peerB.addIceCandidate(......peerAICEList) var peerBICEList=[......] //通過peerB.onicecandidate監聽獲得所有的ICE連接信息候選項,如果有多個網絡適配器,就會有多個候選 var peerBChannel=... //通過peerB.ondatachannel得到連接通道對象,B端通過這個來進行數據發送 /*******最終完成連接********/ //連接到Peer B peerA.setRemoteDescription(peerBAnswer); //把Peer B的連接點都添加進去 peerA.addIceCandidate(......peerBICEList) /* peerA peerB分別等待peerA/BChannel.onopen回調即完成P2P連接 ,然后通過監聽peerA/BChannel.onmessage獲得對方發送的信息 ,通過peerA/BChannel.send(data) 發送數據。 */
(2)音頻采集和編碼
由于是在我的Recorder庫中新加的demo,因此音頻采集和編碼都是現成的,Recorder庫有好的兼容性和穩定性,因此節省了最大頭的工作量。
編碼最佳使用MP3格式,因為此格式已優化了實時編碼性能,可做到邊錄邊轉碼,16kbps 16khz的情況下可做到2kb每秒的文件大小,音質還可以,實時傳輸時為3kb每秒,15分鐘大概3M的流量。
用wav格式也可以,不過此格式編碼出來的數據量太大,16位 16khz接近50kb每秒的實時傳輸數據,15分鐘要37M多流量。其他格式由于暫未對實時編碼進行優化,使用中會導致明顯卡頓。
降噪、靜音檢測等高級功能是沒有的,畢竟是非專業人員 要求高點可以,但不要超出范圍太多啦。
(3)音頻實時接收和播放
接收到一個音頻片段后,本應該是立即播放的,但由于編碼、網絡傳輸導致的延遲,可能上個片段還未播放完(甚至未開始播放),因此需要緩沖處理。
因為存在緩沖,就需要進行實時同步處理,如果緩沖內積壓了過多的音頻片段,會導致語音播放滯后太多,因此需要適當進行對數據進行丟棄,實測發現網絡正常、設備性能靠譜的情況下基本沒有丟棄的數據。
然后就是播放了,本應是播完一個就播下一個,測試發現這是不靠譜的。因為結束一個片段后再開始播放下一個發出聲音,這個過程會中斷比較長時間,明顯感覺得出來中間存在短暫停頓。因此必須在片段未播完時準備好下一個片段的播放,并且提前開始播放,達到抹掉中間的停頓。
我寫了兩個播放方式:
最開始用一個Audio停頓感太明顯,因此用兩個Audio輪換抹掉中間的停頓,但發現不同格式Auido播放差異巨大,播放wav非常流暢,但播放mp3還是存在停頓(后面用解碼的發現是得到的PCM時長變長了,導致事件觸發會出現誤差,為什么會變長?怪異)。
因此后面寫了一個解碼然后再播放,mp3這次終于能正常連續播放了,wav格式和雙Audio的播放差異不大。實時解碼里面也用到了雙Audio中的技巧,其實也是用到了兩個BufferSource進行類似的輪換操作,以抹掉兩個片段間的停頓。
不過最終播放效果還是不夠好,音質變差了點,并且多了點噪音。如果有現成的播放代碼拿過來用就就好了。
完。
TML5的權限越來越大了,瀏覽器可以直接調用攝像頭、麥克風了,好激動啊。我們要用純潔的HTML代碼造出自己的天地。
視頻采集
本篇介紹的栗子 都是在chrome 47 版本以上的,低版本的可能會出現白屏和錯誤。
1.安全環境
隨著Chrome版本的升高,安全性問題也越來越被重視,較新版本的Chrome瀏覽器在調用一些API時需要頁面處在安全環境中。本篇文章所介紹的API函數,都需要在安全環境中執行。如果處在非安全環境下 ( http頁面 ) 這些API就會有意想不到的問題。
比如 getUserMedia()就會報出警告,并執行出錯。
而在設備枚舉enumerateDevices()時,雖然不會報錯,但是他隱藏了設備label。
注意:第一次在一個安全頁面下執行enumerateDevices()時也會隱藏label,在允許使用攝像頭等設備后,第二次執行才會顯示label。
getUserMedia() no longer works on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. Seehttps://goo.gl/rStTGz for more details.
根據谷歌的意思,常用的安全環境有如下
http://localhost
http://127.0.0.1
https 開頭的地址頁面
如果你做了一個視頻測試的頁面,想嘚瑟給局域網的其他人,但是又沒有域名證書怎么辦?
這時候只能通過修改其他人的hosts文件了
比如你的測試服務器IP地址是192.168.2.18,那么其他人的hosts文件修改如下:
#localhost 127.0.0.1
localhost 192.168.2.18
當使用別人的Chrome瀏覽器訪問 http://localhost/[getUserMediaTestPage]時,就會順利的執行這些API了。
但是移動端的瀏覽器并不認localhost,就算你修改了hosts ,移動端的瀏覽器根本不理你,解析都不解析。
所以想在手機上測試,只能老老實實申請個證書了。
2.設備枚舉
在開啟攝像頭之前,先要把可以使用的麥克風和攝像頭 ( 輸入設備 ) 列出來,如果沒有這兩樣設備也就無法繼續。
代碼如下:
<label for="audioDevice"> 錄音設備: </label>
<select id="audioDevice">
</select>
<br>
<label for="videoDevice"> 錄影設備: </label>
<select id="videoDevice">
</select>
<script>
navigator.mediaDevices.enumerateDevices().then(function (data) {
data.forEach(function (item) {
if(item.kind=="audioinput"){ //麥克風
document.getElementById("audioDevice").innerHTML += "<option value='"+ item.deviceId +"'>" + item.label + " </option> "
}else if(item.kind=="videoinput"){ //攝像頭
document.getElementById("videoDevice").innerHTML += "<option value='"+ item.deviceId +"'>" + item.label + " </option> "
}
})
},function (error) {
console.log(error);
})
</script>
效果如下圖,和瀏覽器自己獲取的一模一樣。
注意:上圖的實例中,瀏覽器地址欄最右邊的攝像頭標識是需要使用 getUserMedia()函數時才會出現。
<script>
var getUserMedia = navigator.webkitGetUserMedia; //Chrome瀏覽器的方法
getUserMedia.call(navigator, {
video:true, // 開啟音頻
audio:true // 開啟視頻
}, function(stream){
console.log(stream); // 成功獲取媒體流
}, function(error){
//處理媒體流創建失敗錯誤
});
</script>
這時候可以通過瀏覽器給出的菜單下拉選擇設備。
3.設置參數,預覽
我們可以通過代碼來指定使用哪個攝像頭和麥克風設備。
也可以通過代碼設置視頻的寬、高和幀率。
代碼如下:
<video id="video" autoplay></video> <!-- 一定要有 autoplay -->
<script>
var getUserMedia = navigator.webkitGetUserMedia ;
getUserMedia.call(navigator, {
"audio":{
"mandatory":{
"sourceId":"" // 指定設備的 deviceId
}
},
"video":{
"optional":[
{"minWidth":400},
{"maxWidth":400}, // 數字類型,固定寬度
{"minHeight":220},
{"maxHeight":220}, // 數字類型,固定高度
{"frameRate":"12"} // 幀率
],"mandatory":{
"sourceId":"" // 指定設備的 deviceId
}
}
}, function(stream){
//綁定本地媒體流到video標簽用于輸出
document.getElementById("video").src = URL.createObjectURL(stream);
}, function(error){
//處理媒體流創建失敗錯誤
});
</script>
輸出的視頻流通過blob對象鏈接綁定到video標簽輸出。
這個deviceId就是從上文設備枚舉 enumerateDevices() 獲取到的。
兩種設備,如果有一個deviceId填寫不正確,就會報出一個DevicesNotFoundError的錯誤。
而且一旦指定了設備后,瀏覽器自己的設備選擇就會變成灰色不可選。
視頻的寬高,并不會因為填寫的數值比例不合法而失真。
比如你設定了寬度30,高度100,那么他會從視頻中心截取 30x100 的畫面,而不是把原畫面擠壓到這個30x100的尺寸。
效果如下:
如果您的預覽一片漆黑,或者只有一個小黑點,那么說明您的攝像頭正在被占用...
吐槽:這個getUserMedia()函數的參數,w3的官方文檔鏈接如下:
https://www.w3.org/TR/mediacapture-streams/
可是Chrome并沒有遵循它,而且差距還挺大...
視頻保存
1. 格式支持
Chrome瀏覽器是大力推廣webm的視頻格式的。可以用MediaRecorder.isTypeSupported("video/webm")來測試是否支持這種類型的編碼。
如果返回true,那么我們錄制的視頻就可以被保存為這種指定的格式。
如果不指定,那么將會使用瀏覽器自動指定的文件格式。文檔原話如下
If this paramater is not specified, the UA will use a platform-specific default format.
但是這個默認值卻無法直接獲取,全靠猜...
2. 視頻錄制 MediaRecorder
我們使用 MediaRecorder來錄制視頻,參數是通過getUserMedia()獲取的媒體流。
通過綁定ondataavailable事件,來獲取視頻片段數據,并在內存中累積。
錄制的開始和結束分別使用 start和stop 函數。
執行start之后會周期性觸發ondataavailable事件。
執行stop之后會停止觸發ondataavailable事件。
錄制結束后,把累計的片段數據保存為blob對象,并從瀏覽器下載存為視頻文件。
代碼如下:
<script>
var getUserMedia = navigator.webkitGetUserMedia ;
var g_stream = null, g_recorder = null;
function startPreview(){
getUserMedia.call(navigator, {
video:true,
audio:true
}, function(stream){
g_stream = stream;
}, function(error){
});
}
function stopRecording(){
g_recorder.stop();
}
function startRecording(){
var chunks = [];
g_recorder = new MediaRecorder(g_stream,{mimeType:"video/webm"});
g_recorder.ondataavailable = function(e) {
chunks.push(e.data);
}
g_recorder.onstop = function(e) {
var blob = new Blob(chunks, { 'type' : 'video/webm' });
var audioURL = URL.createObjectURL(blob);
window.open(audioURL);
}
g_recorder.start();
}
</script>
注意:本例并沒有填寫視頻文件頭,所以保存出來的視頻文件沒有時間軌,無法快進和跳躍。可以用格式工廠轉
“莫基了”上面有一個錄制音頻的例子 傳送門:http://t.cn/RvxZAeo
這篇文章的DEMO請戳 這里:http://t.cn/RVt9Q6I
?―――――――――↓―――――――――?
相關閱讀
多屏互動——H5中級進階
前端,想說愛你不容易!
無需Flash實現圖片裁剪——HTML5中級進階
作者信息
作者來自力譜宿云 LeapCloud 團隊_UX成員:王詩詩 【原創】
力譜宿云 LeapCloud 團隊首發:https://blog.maxleap.cn/archives/1197
歡迎關注微信訂閱號:MaxLeap_yidongyanfa
*請認真填寫需求信息,我們會在24小時內與您取得聯系。