主頁 > 企業開發 > js實作mp3錄音通過websocket實時傳送+簡易波形圖

js實作mp3錄音通過websocket實時傳送+簡易波形圖

2020-09-18 01:34:00 企業開發

在分享之前先貼上借鑒的大佬們的博客,感謝這些巨人,關于錄音:https://blog.csdn.net/sweetsuzyhyf/article/details/50469881 

關于波形圖:https://blog.csdn.net/weixin_44204580/article/details/90112055   https://blog.csdn.net/qq_32447301/article/details/84948717?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase    廢話:想不到我的第一篇博客是關于前端,作為一名后端的小菜,前端方面肯定還有很多不足之處,如果文章有任何問題歡迎指正,感謝大家,好了!廢話不多說下面講一下需求, 需求:公司要求實作web端的錄音并通過websocket實時上傳至java后臺,而且能通過vlc實時播放,簡單一點講就是我用網頁在那一邊講話,一個大喇叭就能實時把我的話播出去,這樣是不是通俗易懂呀,而且呢公司要求用mp3格式,當然啦!為了知道自己在講話需要一個波形圖,這里主要實作前半部分功能,后半部分臣妾也做不到呀!后半部分的vlc播放呢如果大家想知道,可以留言,屆時可以給大家指條明路   前端實作: 引入:<script type="text/javascript" src="https://www.cnblogs.com/js/recorder/recordmp3.js"></script> 這個跟大佬的js有點不一樣,我在里面加了一點東西,而且在這個js里面引入了兩個另外的js,lame.min.js和worker-realtime.js,這倆在大佬的代碼里有 2020-08-12補充:由于大佬們的博客不見了,現在補充上兩個js檔案worker-realtime.js,lame.min.js 頁面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>
    <title>測驗</title>
</head>
<body>
<button id="intercomBegin">開始對講</button>
<button id="intercomEnd">關閉對講</button>
<canvas id="casvased" style="width: 400px;height: 100px"></canvas>
</body>
<script type="text/javascript" src="/js/jquery-3.3.1.js"></script>
<script type="text/javascript" src="/js/recorder/recordmp3.js"></script>
<script type="text/javascript">
    var begin = document.getElementById('intercomBegin');
    var end = document.getElementById('intercomEnd');

    var canvas = document.getElementById("casvased");
    var canvasCtx = canvas.getContext("2d");

    var ws = null; //實作WebSocket

    var recorder;

    /*
    * WebSocket
    */
    function useWebSocket() {
        ws = new WebSocket("ws://127.0.0.1:8089/send/voice");
        ws.binaryType = 'arraybuffer'; //傳輸的是 ArrayBuffer 型別的資料
        ws.onopen = function () {
            console.log('握手成功');
            if (ws.readyState == 1) { //ws進入連接狀態,則每隔500毫秒發送一包資料
                recorder.start();
            }
        };

        ws.onmessage = function (msg) {
            console.info(msg)
        }

        ws.onerror = function (err) {
            console.info(err)
        }
    }

    /*
    * 開始對講
    */
    begin.onclick = function () {
        recorder = new MP3Recorder({
            debug: true,
            funOk: function () {
                console.log('點擊錄制,開始錄音! ');
            },
            funCancel: function (msg) {
                console.log(msg);
                recorder = null;
            }
        });
    }

    /*
    * 關閉對講
    */
    end.onclick = function () {
        if (ws) {
            ws.close();
            recorder.stop();
            console.log('關閉對講以及WebSocket');
        }
    }

    var sendData = function() { //對以獲取的資料進行處理(分包)
        var reader = new FileReader();
        reader.onload = e => {
            var outbuffer = e.target.result;
            var arr = new Int8Array(outbuffer);
            if (arr.length > 0) {
                var tmparr = new Int8Array(1024);
                var j = 0;
                for (var i = 0; i < arr.byteLength; i++) {
                    tmparr[j++] = arr[i];
                    if (((i + 1) % 1024) == 0) {
                        ws.send(tmparr);
                        if (arr.byteLength - i - 1 >= 1024) {
                            tmparr = new Int8Array(1024);
                        } else {
                            tmparr = new Int8Array(arr.byteLength - i - 1);
                        }
                        j = 0;
                    }
                    if ((i + 1 == arr.byteLength) && ((i + 1) % 1024) != 0) {
                        ws.send(tmparr);
                    }
                }
            }
        };
        recorder.getMp3Blob(function (blob) {
            reader.readAsArrayBuffer(blob);//這里拿到mp3格式的音頻流寫入到reader中
})
  }; </script> </html>

recordmp3.js

(function (exports) {

    var MP3Recorder = function (config) {

        var recorder = this;
        config = config || {};
        config.sampleRate = config.sampleRate || 44100;
        config.bitRate = config.bitRate || 128;

        navigator.getUserMedia = navigator.getUserMedia ||
            navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia ||
            navigator.msGetUserMedia;

        if (navigator.getUserMedia) {
            navigator.getUserMedia({
                    audio: true
                },
                function (stream) {
                    var context = new AudioContext(),
                        microphone = context.createMediaStreamSource(stream),
                        processor = context.createScriptProcessor(16384, 1, 1),//bufferSize大小,輸入channel數,輸出channel數
                        mp3ReceiveSuccess, currentErrorCallback;

                    var height = 100;
                    var width = 400;

                    const analyser = context.createAnalyser()
                    analyser.fftSize = 1024
                    //連接到音頻源
                    microphone.connect(analyser);
                    analyser.connect(context.destination);

                    const bufferLength = analyser.frequencyBinCount // 回傳的是 analyser的fftsize的一半
                    const dataArray = new Uint8Array(bufferLength);

                    function draw() {
                        canvasCtx.clearRect(0, 0, width, height); //清除畫布
                        analyser.getByteFrequencyData(dataArray); // 將當前頻率資料復制到傳入其中的Uint8Array
                        const requestAnimFrame = window.requestAnimationFrame(draw) || window.webkitRequestAnimationFrame(draw);
                        canvasCtx.fillStyle = '#000130';
                        canvasCtx.fillRect(0, 0, width, height);
                        let barWidth = (width / bufferLength) * 2;
                        let barHeight;
                        let x = 0;
                        let c = 2
                        for (let i = 0; i < bufferLength; i++) {
                            barHeight = c+(dataArray[i]/400)*height;
                            canvasCtx.fillStyle = 'rgb(0, 255, 30)';
                            canvasCtx.fillRect(x, height / 2 - barHeight / 2, barWidth, barHeight);
                            x += barWidth + 1;
                        }
                    }

                    draw();

                    useWebSocket();
                    config.sampleRate = context.sampleRate;
                    processor.onaudioprocess = function (event) {
                        //邊錄音邊轉換
                        var array = event.inputBuffer.getChannelData(0);
                        realTimeWorker.postMessage({cmd: 'encode', buf: array});
                        sendData();
                    };

                    var realTimeWorker = new Worker('/js/recorder/worker-realtime.js');
                    realTimeWorker.onmessage = function (e) {
                        switch (e.data.cmd) {
                            case 'init':
                                log('初始化成功');
                                if (config.funOk) {
                                    config.funOk();
                                }
                                break;
                            case 'end':
                                log('MP3大小:', e.data.buf.length);
                                if (mp3ReceiveSuccess) {
                                    mp3ReceiveSuccess(new Blob(e.data.buf, {type: 'audio/mp3'}));
                                }
                                break;
                            case 'error':
                                log('錯誤資訊:' + e.data.error);
                                if (currentErrorCallback) {
                                    currentErrorCallback(e.data.error);
                                }
                                break;
                            default:
                                log('未知資訊:', e.data);
                        }
                    };

                    recorder.getMp3Blob = function (onSuccess, one rror) {
                        currentErrorCallback = one rror;
                        mp3ReceiveSuccess = onSuccess;
                        realTimeWorker.postMessage({cmd: 'finish'});
                    };

                    recorder.start = function () {
                        if (processor && microphone) {
                            microphone.connect(processor);
                            processor.connect(context.destination);
                            log('開始錄音');
                        }
                    }

                    recorder.stop = function () {
                        if (processor && microphone) {
                            microphone.disconnect();
                            processor.disconnect();
                            log('錄音結束');
                        }
                    }

                    realTimeWorker.postMessage({
                        cmd: 'init',
                        config: {
                            sampleRate: config.sampleRate,
                            bitRate: config.bitRate
                        }
                    });
                },
                function (error) {
                    var msg;
                    switch (error.code || error.name) {
                        case 'PERMISSION_DENIED':
                        case 'PermissionDeniedError':
                            msg = '用戶拒絕訪問麥客風';
                            break;
                        case 'NOT_SUPPORTED_ERROR':
                        case 'NotSupportedError':
                            msg = '瀏覽器不支持麥客風';
                            break;
                        case 'MANDATORY_UNSATISFIED_ERROR':
                        case 'MandatoryUnsatisfiedError':
                            msg = '找不到麥客風設備';
                            break;
                        default:
                            msg = '無法打開麥克風,例外資訊:' + (error.code || error.name);
                            break;
                    }
                    if (config.funCancel) {
                        config.funCancel(msg);
                    }
                });
        } else {
            if (config.funCancel) {
                config.funCancel('當前瀏覽器不支持錄音功能');
            }
        }

        function log(str) {
            if (config.debug) {
                console.log(str);
            }
        }
    }

    exports.MP3Recorder = MP3Recorder;

})(window);

后端websocket:

這里實作的是保存為mp3檔案

package com.jetosend.common.socket;

import com.jetosend.common.utils.Utils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Hashtable;
import java.util.Map;

@ServerEndpoint("/send/{key}")
@Component
public class ServerSocket {

    private static final Map<String, Session> connections = new Hashtable<>();
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    /***
     * @Description:打開連接
     * @Param: [id, 保存對方平臺的資源編碼
     * session]
     * @Return: void
     * @Author: Liting
     * @Date: 2019-10-10 09:22
     */
    @OnOpen
    public void onOpen(@PathParam("key") String id, Session session) {
        System.out.println(id + "連上了");
        connections.put(id, session);
    }

    /**
     * 接收訊息
     */
    @OnMessage
    public void onMessage(@PathParam("key") String id, InputStream inputStream) {
        System.out.println("來自" + id);
        try {
            int rc = 0;
            byte[] buff = new byte[100];
            while ((rc = inputStream.read(buff, 0, 100)) > 0) {
                byteArrayOutputStream.write(buff, 0, rc);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 例外處理
     *
     * @param throwable
     */
    @OnError
    public void one rror(Throwable throwable) {
        throwable.printStackTrace();
        //TODO 日志列印例外
    }

    /**
     * 關閉連接
     */
    @OnClose
    public void onClose(@PathParam("key") String id) {
        System.out.println(id + "斷開");
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file = null;
        try {
            file = new File("D:\\testtest.mp3");

            //輸出流
            fos = new FileOutputStream(file);

            //緩沖流
            bos = new BufferedOutputStream(fos);

            //將位元組陣列寫出
            bos.write(byteArrayOutputStream.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        connections.remove(id);
    }

實作效果:

 

好了,到這里就是全部了,如有疑問可以留言

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/69774.html

標籤:JavaScript

上一篇:實作微前端需要了解的 Vue Genesis 渲染器

下一篇:壞代碼與重構手法速查表

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more