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

前言

前端消息的實時推送我相信不少人不陌生,咱們能夠想到利用WebSocket,服務端主動向客戶端推送數據,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。其優勢有不少,能更好的節省服務器資源和帶寬,而且可以更實時地進行通信等等。語音播報則可以在人們視覺沒有來的及關注時侯,經過聽覺來獲取須要信息。javascript

這篇文章主要介紹的是基於websocket,利用Stomp.js以及HTML5語音Web Speech API——SpeechSynthesis來實現前端消息的實時推送與語音播報。css

StompJS

讓咱們先了解一下STOMP(the Simple (or Streaming) Text Orientated Messaging Protocol)——面向消息(或流)的簡單文本協議。它提供了一個可互操做的鏈接格式,容許STOMP客戶端與任意STOMP消息代理(Broker)進行交互。html

WebSocket的實現客戶端看起來比較簡單,可是須要與後臺進行很好的配合和調試才能達到最佳效果。經過SockJS 、Stomp來進行瀏覽器兼容,能夠增長消息語義和可用性。簡而言之,WebSocket 是底層協議,SockJS 是WebSocket 的備選方案,也是底層協議,而 STOMP 是基於 WebSocket(SockJS)的上層協議。前端

建立STOMP客戶端

下面簡單的介紹一下經常使用的方法。
在web瀏覽器中咱們能夠經過兩種方式進行客戶端的建立:
一、使用普通的WebSocketjava

let url = "ws://localhost:61614/stomp";
let client = Stomp.client(url);

二、使用定製的WebSocket
若是須要使用其餘類型的Websocket(例如由SockJS包裝的Websocket),就利用下面的方式建立客戶端react

let url = "ws://localhost:61614/stomp";
let socket = new SockJS(url);
let client = Stomp.over(socket);

除上面的客戶端建立方式不一樣外,後續的鏈接等操做都是同樣的。web

鏈接服務端

咱們能夠用client.connect()方法來鏈接服務端chrome

client.connect(login,passcode,successCallback,errorCallback);

其中loginpasscode都是字符串,至關因而用戶的登陸名和密碼憑證。successCallback爲鏈接成功的回調函數,errorCallback爲鏈接失敗的回調函數。
還能夠這樣寫:apache

client.connect({
    login:'name',
    passcode:'666',
    'token':'2333'
},successCallback,errorCallback);

斷開鏈接:segmentfault

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,直接點擊官網
http://activemq.apache.org/do...
選擇最新發佈下來的壓縮包,解壓便可,而後進入解壓後的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的,那是怎麼回事?答案就在下面:


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 發條消息瞧一瞧,是否是與這個有關:


能夠看見isTrusted的值爲false,這個模擬點擊事件是不被瀏覽器信任的,而後咱們再手動點擊一下咱們寫好的按鈕:


isTrusted的值爲true,咱們的聲音也出來了,當咱們再在ActiveMQ 發送一條消息:


聲音自動播放出來了。

參考

https://segmentfault.com/a/11...
https://www.cnblogs.com/golov...
https://www.jianshu.com/p/92d...
https://developer.mozilla.org...

最後

須要提一點的是,我在實際是在react中開發的,相關方法都和上述的寫法相似,可是卻不會觸發Chrome對於SpeechSynthesis.speak()的限制,這個限制也是我在寫這篇文章的時候,用原生js+HTML的時候發現的。總的來講用原生js+HTML實現的並不算完美,在Chrome下須要在頁面加載完成後進行一次點擊,才能把後續的語音實時的播報出來。若是你們有相關的解決辦法,或者用其它的方式實現了前端消息實時的語音播報,歡迎提出來,先謝過了~

相關文章
相關標籤/搜索