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