StompJS+SpeechSynthesis實現前端訊息實時語音播報

xmanlin發表於2020-04-05

前言

前端訊息的實時推送我相信很多人不陌生,我們可以想到利用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);
複製程式碼

其中loginpasscode都是字串,相當於是使用者的登入名和密碼憑證。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:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
Speech Synthesis:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
從上面的圖中可以看出:語音識別(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啟動,如果看見下面這樣就說明啟動成功:

StompJS+SpeechSynthesis實現前端訊息實時語音播報

如果沒有啟動成功,那多半是因為沒有jdk,點這裡,跟著安裝就歐克啦~ 安裝完畢後,再雙擊activemq.bat,現在我們就啟動成功了。在瀏覽器中輸入:http://localhost:8161,可以看到:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
點選Manage ActiveMQ broker,使用者名稱和密碼都是admin,然後再點選Topics

StompJS+SpeechSynthesis實現前端訊息實時語音播報
在輸入框中輸入msg,然後點選create按鈕

StompJS+SpeechSynthesis實現前端訊息實時語音播報
可以看見下方列表中多了一個Name為msg的Topics

StompJS+SpeechSynthesis實現前端訊息實時語音播報
其中需要注意的是下面幾項,以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檔案,然後開啟控制檯,可以看見,我們已經連線服務端成功了:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
連線成功之後呢,我們就可以嘗試在服務端向客戶端傳送訊息,先切換到ActiveMQ 的頁面:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
可以看見我們的消費者的數量為1了,然後點選Send To,就可以開始發訊息了:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
比如我們傳送一個“你好”,然後我們再切到index.html:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
我們收到了來自服務端的問候~

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的,那是怎麼回事?答案就在下面:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
Chrome不再支援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 發條訊息瞧一瞧,是不是與這個有關:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
可以看見isTrusted的值為false,這個模擬點選事件是不被瀏覽器信任的,然後我們再手動點選一下我們寫好的按鈕:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
isTrusted的值為true,我們的聲音也出來了,當我們再在ActiveMQ 傳送一條訊息:

StompJS+SpeechSynthesis實現前端訊息實時語音播報
聲音自動播放出來了。

參考

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下需要在頁面載入完成後進行一次點選,才能把後續的語音實時的播報出來。如果大家有相關的解決辦法,或者用其它的方式實現了前端訊息實時的語音播報,歡迎提出來,先謝過了~

相關文章