前言
前端訊息的實時推送我相信很多人不陌生,我們可以想到利用WebSocket,服務端主動向客戶端推送資料,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。其優點有很多,能更好的節省伺服器資源和頻寬,並且能夠更實時地進行通訊等等。語音播報則能夠在人們視覺沒有來的及關注時侯,通過聽覺來獲取需要資訊。
這篇文章主要介紹的是基於websocket,利用Stomp.js以及HTML5語音Web Speech API——SpeechSynthesis來實現前端訊息的實時推送與語音播報。
StompJS
讓我們先了解一下STOMP(the Simple (or Streaming) Text Orientated Messaging Protocol)——面向訊息(或流)的簡單文字協議。它提供了一個可互操作的連線格式,允許STOMP客戶端與任意STOMP訊息代理(Broker)進行互動。
WebSocket的實現客戶端看起來比較簡單,但是需要與後臺進行很好的配合和除錯才能達到最佳效果。通過SockJS 、Stomp來進行瀏覽器相容,可以增加訊息語義和可用性。簡而言之,WebSocket 是底層協議,SockJS 是WebSocket 的備選方案,也是底層協議,而 STOMP 是基於 WebSocket(SockJS)的上層協議。
建立STOMP客戶端
下面簡單的介紹一下常用的方法。 在web瀏覽器中我們可以通過兩種方式進行客戶端的建立: 1、使用普通的WebSocket
let url = "ws://localhost:61614/stomp";
let client = Stomp.client(url);
複製程式碼
2、使用定製的WebSocket 如果需要使用其他型別的Websocket(例如由SockJS包裝的Websocket),就利用下面的方式建立客戶端
let url = "ws://localhost:61614/stomp";
let socket = new SockJS(url);
let client = Stomp.over(socket);
複製程式碼
除上面的客戶端建立方式不同外,後續的連線等操作都是一樣的。
連線服務端
我們可以用client.connect()
方法來連線服務端
client.connect(login,passcode,successCallback,errorCallback);
複製程式碼
其中login
和passcode
都是字串,相當於是使用者的登入名和密碼憑證。successCallback
為連線成功的回撥函式,errorCallback
為連線失敗的回撥函式。
還可以這樣寫:
client.connect({
login:'name',
passcode:'666',
'token':'2333'
},successCallback,errorCallback);
複製程式碼
斷開連線:
client.disconnect(function(){console.log("再見")})
複製程式碼
Heart-beating(心跳)
heart-beating也就是訊息傳送的頻率,incoming
是接收頻率,outgoing
是傳送頻率,其預設值都為10000ms,我們可以手動設定:
client.heartbeat.outgoing = 5000;
client.heartbeat.incoming = 0;
複製程式碼
傳送訊息
客戶端向服務端傳送訊息利用send()
方法,此方法有三個引數:第一個引數(string)必需,為傳送訊息的目的地;第二個引數(object)可選,包含了額外的頭部資訊;第三個引數(string)可選,為傳送的訊息。
client.send(destination, {}, body);
複製程式碼
訂閱訊息
訂閱訊息也就是客戶端接收服務端傳送的訊息,訂閱訊息可以利用subscribe()
方法,此方法有三個引數:第一個引數(string)必需,為接收訊息的目的地;第二個引數必需為回撥函式;第三個引數{object}為可選,包含額外的頭部資訊。
client.subscribe(destination, callback, {});
複製程式碼
取消訂閱訊息可以利用unsubscribe()
方法:
let mySubscribe = client.subscribe;
mySubscribe.unsubscribe();
複製程式碼
客戶端訂閱訊息可以訂閱廣播,如下所示:
client.subscribe('/topic/msg',function(messages){
console.log(messages);
})
複製程式碼
也可以進行一對一訊息的接收:
//第一種方式
const userId = 666;
client.subscribe('/user/' + userId + '/msg',,function(messages){
console.log(messages);
})
//第二種方式
client.subscribe('/msg',function(messages){
console.log(messages);
}, {"userId ": userId })
複製程式碼
客戶端採用的寫法要根據服務端程式碼來做選擇。
Web Speech API
在HTML5中,與語音相關的Web Speech API可以分為兩種:一種為語音識別(Speech Recognition),另一種為語音合成(Speech Synthesis)。他們的作用分別為“語音轉文字”和“文字轉語音”。 既然是HTML5中的東西,我們還是要先看看他們的相容性如何: Speech Recognition:
Speech Synthesis: 從上面的圖中可以看出:語音識別(Speech Recognition)很慘烈,大部分瀏覽器還不支援。語音合成(Speech Synthesis)除開IE和Opera,基本上都支援了。 本文要實現的是語音播報,就是要把文字訊息,轉成語音播報出來,而語音合成(Speech Synthesis)就是實現這樣的功能,而且相容性也是不錯的,所以我們就能拿來使用啦~SpeechSynthesis
語音識別(Speech Recognition)就不過多介紹了,我們來詳細看看語音合成(Speech Synthesis)。我們可以先把下面這段程式碼打到瀏覽器的控制檯上:
let speechInstance = new window.SpeechSynthesisUtterance('你好,可以交個朋友嗎');
window.speechSynthesis.speak(speechInstance);
複製程式碼
不出意外,瀏覽器說話了,說明瀏覽器是支援這個API的。下面簡單介紹一下相關的屬性和方法: SpeechSynthesisUtterance物件的屬性:
屬性 | 型別 | 描述 |
---|---|---|
text | string | 需要要讀的內容 |
lang | string | 使用的語言(比如:"zh-CN") |
volume | number | 音量,值在0-1之間(預設是1) |
rate | number | 語速的倍數,值在0.1-10之間(預設1倍) |
pitch | number | 音高,值在0-2之間,(預設是1) |
voice | string | 指定希望使用的聲音 |
SpeechSynthesisUtterance物件的方法:
方法 | 描述 |
---|---|
onstart | 語音開始合成時觸發 |
onpause | 語音暫停時觸發 |
onresume | 語音合成重新開始時觸發 |
onend | 語音結束時觸發 |
上述定義的speechInstance其實是我們建立的文字例項,而真實的語音是由speechSynthesis來建立的,其常用的方法如下:
方法 | 描述 |
---|---|
speak() | 開始合成語音,將對應的例項新增到語音佇列中 |
cancel() | 停止合成語音,刪除佇列中所有的語音 |
pause() | 暫停語音合成 |
resume() | 恢復暫停後的語音 |
getVoices() | 返回瀏覽器所支援的語音包陣列 |
實戰環節
上面介紹了StompJS和SpeechSynthesis常用的屬性和方法,是時候動手碼一碼了。
前端web頁面訊息實時接收
模擬服務端傳送訊息
想要接收實時訊息,我們當先然要有訊息的來源對不對。訊息是從後臺發來的,一般是利用Java,然後結合ActiveMQ或者RabbitMQ等訊息中介軟體,Java程式碼就不多說,我們接下來就利用ActiveMQ來模擬服務端向客戶端傳送訊息。 這裡說一說windows環境下吧,首先要下載ActiveMQ,直接點選官網: activemq.apache.org/download-ar… 選擇最新釋出下來的壓縮包,解壓即可,然後進入解壓後的bin目錄,可以看見裡面有兩個資料夾,win32和win64,這個就根據自己電腦的作業系統來選擇,點選進去,再雙擊activemq.bat啟動,如果看見下面這樣就說明啟動成功:
如果沒有啟動成功,那多半是因為沒有jdk,點這裡,跟著安裝就歐克啦~ 安裝完畢後,再雙擊activemq.bat,現在我們就啟動成功了。在瀏覽器中輸入:http://localhost:8161,可以看到:
點選Manage ActiveMQ broker,使用者名稱和密碼都是admin,然後再點選Topics 在輸入框中輸入msg,然後點選create按鈕 可以看見下方列表中多了一個Name為msg的Topics 其中需要注意的是下面幾項,以msg為例:- Number Of Consumers :消費者數量,相當於連線服務端msg的客戶端的數量;
- MessagesEnqueued:進入佇列的訊息,相當於服務端向客戶端傳送的訊息數量;
- MessagesDequeued:出了佇列的訊息,相當於客戶端消費(訂閱)掉的訊息數量。
- 其它想要多瞭解的可以搜尋一波。
模擬“服務端”準備就緒。
客戶端訊息接收
這“服務端”搞好了,接下來就是客戶端的實現,程式碼貼出來index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>實時語音播報</title>
</head>
<body>
<script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js"></script>
<script>
window.onload = function() {
let data = '';
//建立連線
function connect(){
let client;
let url = 'ws:127.0.0.1:61614/stomp';
client = Stomp.client(url);
client.heartbeat.outgoing=0;
client.connect({},
//連線成功回撥
function connectCallback() {
console.log("連線成功~");
//訂閱訊息
// 因為我們訂閱的是topic下的msg,所以這裡是'/topic/msg'
client.subscribe('/topic/msg', function(message){
if(message.body){
data = message.body;
console.log(message.body);
}
})
},
//連線失敗回撥
function errorCallBack(error){
console.log(error)
}
)
}
connect();
}
</script>
</body>
</html>
複製程式碼
在瀏覽器中開啟這個HTML檔案,然後開啟控制檯,可以看見,我們已經連線服務端成功了:
連線成功之後呢,我們就可以嘗試在服務端向客戶端傳送訊息,先切換到ActiveMQ 的頁面: 可以看見我們的消費者的數量為1了,然後點選Send To,就可以開始發訊息了: 比如我們傳送一個“你好”,然後我們再切到index.html: 我們收到了來自服務端的問候~SpeechSynthesis語音播報
訊息已經能夠實時接收了,現在就是需要把接收到的訊息讀出來,思路很簡單,就是把語音合成相關API封裝成一個函式,然後當我們服務端傳送訊息到客戶端之後,把訊息資料傳到為我們定義好的語音播報函式裡面,然後就能讀出我們服務端發出的訊息了,說幹就幹:
//語音播報
speechInfo = () => {
let speechInstance = new SpeechSynthesisUtterance();
return {
//語音播報開始
start: function (content) {
let lang = 'zh-CN';
let text = content;
if( text !== '') {
speechInstance.text = text;
speechInstance.lang = lang;
speechInstance.volume = 1;
speechInstance.rate = 1;
speechSynthesis.speak(speechInstance);
speechInstance.onend = function(event){
console.log("語音播報完畢");
}
}
},
//暫停
pause : function () {
speechSynthesis.pause();
},
//重新開始
resume: function() {
speechSynthesis.resume();
},
//取消
cancel: function() {
speechSynthesis.cancel();
}
}
};
複製程式碼
那我們們呼叫一下,然後在ActiceMQ頁面傳送一條新訊息,看是不是如我們所願:
...
client.subscribe('/topic/msg', function(message){
if(message.body){
data = message.body;
console.log(message.body);
//呼叫語音合成函式
speechInfo().start(data);
}
})
...
複製程式碼
如果是火狐,360安全瀏覽器等瀏覽器,我們的訊息和聲音都如期而至。
但是如果用的是Chrome的話,很難受,並沒有聲音,難道是Chrome不支援了嗎?但是我們之前那兩行測試程式碼說明Chrome是支援SpeechSynthesis
的,那是怎麼回事?答案就在下面:
SpeechSynthesis.speak()
的自動播放,要想用的話,必須使用者手動去呼叫它。原因可以看這裡被垃圾廣告濫用後谷歌瀏覽器71將限制語音合成自動播放
垃圾網站出來背鍋!!!
想不到吧,有一天需要去“相容”Chrome了。語音不能實時的播報出來,我們看看有沒有什麼辦法。我的思路是在頁面加一個按鈕,然後進行模擬人去點選,完整index.html
程式碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<button id="btn"> 點選</button>
</div>
<script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js"></script>
<script>
window.onload = function() {
let data = '';
//建立連線
function connect(){
let client;
let url = 'ws:127.0.0.1:61614/stomp';
client = Stomp.client(url);
client.heartbeat.outgoing=0;
client.connect({},
//連線成功回撥
function connectCallback() {
console.log("連線成功~");
//訂閱訊息
client.subscribe('/topic/msg', function(message){
if(message.body){
data = message.body;
console.log(message.body);
if(navigator.userAgent.toLowerCase().indexOf("chrome") !== -1){
document.getElementById("btn").click();
} else {
speechInfo().start(data);
}
}
})
},
//連線失敗回撥
function errorCallBack(error){
console.log(error)
}
)
}
//語音播報
speechInfo = () => {
let speechInstance = new SpeechSynthesisUtterance();
return {
//語音播報開始
start: function (content) {
let lang = 'zh-CN';
let text = content;
if( text !== '') {
speechInstance.text = text;
speechInstance.lang = lang;
speechInstance.volume = 1;
speechInstance.rate = 1;
speechSynthesis.speak(speechInstance);
speechInstance.onend = function(event){
console.log("語音播報完畢");
}
}
},
//暫停
pause : function () {
speechSynthesis.pause();
},
//重新開始
resume: function() {
speechSynthesis.resume();
},
//取消
cancel: function() {
speechSynthesis.cancel();
}
}
};
document.getElementById("btn").onclick=function () {
console.log("觸發成功")
speechInfo().start(data);
};
document.getElementById("btn").click();
connect();
}
</script>
</body>
</html>
複製程式碼
但是我們能想到,谷歌想不到?這裡又涉及一個知識點:isTrusted
Event介面的isTrusted是一個Boolean型別的只讀屬性.當事件由使用者操作生成時為true,由指令碼建立或修改,或通過呼叫EventTarget.dispatchEvent生成,為false
我們在控制檯程式碼中打個斷點,然後在ActiveMQ 發條訊息瞧一瞧,是不是與這個有關:
可以看見isTrusted的值為false,這個模擬點選事件是不被瀏覽器信任的,然後我們再手動點選一下我們寫好的按鈕: isTrusted的值為true,我們的聲音也出來了,當我們再在ActiveMQ 傳送一條訊息: 聲音自動播放出來了。參考
segmentfault.com/a/119000001…
www.cnblogs.com/goloving/p/…
www.jianshu.com/p/92dec635f…
developer.mozilla.org/zh-CN/docs/…
最後
需要提一點的是,我在實際是在react中開發的,相關方法都和上述的寫法類似,但是卻不會觸發Chrome對於SpeechSynthesis.speak()
的限制,這個限制也是我在寫這篇文章的時候,用原生js+HTML的時候發現的。
總的來說用原生js+HTML實現的並不算完美,在Chrome下需要在頁面載入完成後進行一次點選,才能把後續的語音實時的播報出來。如果大家有相關的解決辦法,或者用其它的方式實現了前端訊息實時的語音播報,歡迎提出來,先謝過了~