Search code examples
javascriptnode.jssocket.ioblobweb-audio-api

Is there a good way to play raw wav data in the browser?


I am trying to play wav raw data in the browser, which is transmitted from the nodejs server via socket.io, the main idea is to play the receiving data as soon as posible, so I don't need to wait until receiving data is done. I tried to use blob for this case, but next thing always happens: if I've got a chunk of raw data and I am converting it with blob for playing, then when the second chunk is ready and being converted with blob again, then while playing the audio breaks up for a while.

Also I was trying to use the code below and it's worked, but I could hear a lot of noises while playing it. Can anyone, please, help me with this problem and tell me how can I play audio without noises?

P.S. the timeout in the transmitting is a part of the main idea, I tried playing music without it, but still no luck.

<html>
<head>
 <script src="socket.io.js"></script>
 <script src=" https://cdnjs.cloudflare.com/ajax/libs/socket.io-stream/0.9.1/socket.io-stream.js"> 
 </script>
 <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
</head>
<body>
 <button id="start">Play music!</button>
 <script>
     $(function () {
         var socket = io.connect("http://localhost:3000");
         var stream = ss.createStream();
         // receive data
         window.AudioContext = window.AudioContext || window.webkitAudioContext;
         var audioCtx = new AudioContext();
         var audioBuffer = audioCtx.createBuffer(2, 22050, 44100);
         var source = audioCtx.createBufferSource();
         source.buffer = audioBuffer;
         //  source.connect(audioCtx.destination);
         gainNode = audioCtx.createGain();
         gainNode.gain.value = 0;
         gainNode.connect(audioCtx.destination);
         source.connect(gainNode);
         source.start(0);
         socket.on('chat message', function (msg) {
             console.log(".");
             console.log(msg);
             playsound(msg)
         });
         function playsound(raw) {
             var context = audioCtx;
             var buffer = new Int16Array(raw);
             console.log(buffer);
             var channel0buffer = new Float32Array(buffer.length / 2);
             var channel1buffer = new Float32Array(buffer.length / 2);

             var j = 0;
             for (var i = 0; i < buffer.length; i++) {
                 channel0buffer[j] = buffer[i];
                 i++;
                 channel1buffer[j] = buffer[i];
                 j++;
             }
             var src = context.createBufferSource(),
                 audioBuffer = context.createBuffer(2, buffer.length, context.sampleRate);
             audioBuffer.getChannelData(0).set(channel1buffer);
             audioBuffer.getChannelData(1).set(channel0buffer);
             src.buffer = audioBuffer;
             src.connect(gainNode);
             src.start(0);
         }
         document.getElementById("start").addEventListener('click', function () {
             audioCtx.resume().then(() => {
                 console.log('Playback resumed successfully');
             });
         });
     });
 </script>
 <p>Volume</p>
 <input id="volume" type="range" min="0" max="1" step="0.1" value="0.0" />
 <script>
     document.getElementById('volume').addEventListener('change', function () {
         gainNode.gain.value = this.value;
     });
     function touchStarted() {
         getAudioContext().resume();
     }
 </script>
</body>
</html>

My server code looks like this:

const fs = require("fs");
var app = require("express")();
var http = require("http").createServer(app);
var io = require("socket.io")(http);

app.get("/", (req, res) => {
    res.sendFile(__dirname + "/index.html");
});
io.on("connection", (socket) => {
    console.log("a user connected");
    socket.on("disconnect", () => {
        console.log("user disconnected");
    });
    socket.on("chat message", (msg) => {
        console.log("message: " + msg);
        io.emit("chat message", msg);
    });
});
http.listen(3000, () => {
    console.log("listening on *:3000");
});
const stream = fs.createReadStream("Original1.wav", {
    highWaterMark: 640000, // internal buffer size
});
stream.on("data", async (chunk) => {
    stream.pause();
    let newChunk = new Int16Array(chunk);
    await asyncHandle(chunk);
    setTimeout(() => {
        stream.resume();
    }, 1000);
});
async function asyncHandle(chunk) {
    console.log(chunk);
    io.emit("chat message", chunk);
}

Solution

  • I found solution, which I was in need to, by myself. This code helps u to play every wav files via socket.io and Web Audio Api whithout any problem.

    Client side:

    <!doctype html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Test</title>
        <script src="socket.io.js"></script>
        <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
    
        <script type="text/javascript">
            var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    
            var socket = io.connect("http://localhost:3000");
    
            var channels = 1;
            let subcounter = 0;
            let audiobuffer = [];
            socket.on('chat message', function (msg) {
                audiobuffer.push(new Uint16Array(msg));
                console.log(".");
                console.log(msg);
            });
            function play(soundName) {
                var frameCount = audiobuffer[subcounter].length;
                var myAudioBuffer = audioCtx.createBuffer(channels, frameCount, 22050);
                for (var channel = 0; channel < channels; channel++) {
                    var nowBuffering = myAudioBuffer.getChannelData(channel);
                    for (var i = 0; i < frameCount; i++) {
                        // audio needs to be in [-1.0; 1.0]
                        var word = audiobuffer[subcounter][i];
                        nowBuffering[i] = ((word + 32768) % 65536 - 32768) / 32768.0;
                    }
                }
                subcounter += 1;
                var source = audioCtx.createBufferSource();
                source.buffer = myAudioBuffer;
                source.connect(audioCtx.destination);
                source.onended = play;
                source.start();
            }
        </script>
    </head>
    <div>
        <button id="play-sinewave" type="button" onclick="play('sinewave')">Play sound 'sinewave'</button>
    </div>
    </body>
    
    </html>
    

    Server side:

    const fs = require("fs");
    var app = require("express")();
    var http = require("http").createServer(app);
    var io = require("socket.io")(http);
    var cors = require("cors");
    app.use(cors());
    
    io.on("connection", (socket) => {
        console.log("a user connected");
        socket.on("disconnect", () => {
            console.log("user disconnected");
        });
        socket.on("chat message", (msg) => {
            console.log("message: " + msg);
            io.emit("chat message", msg);
        });
    });
    http.listen(3000, () => {
        console.log("listening on *:3000");
    });
    const stream = fs.createReadStream("Original122050.wav", {
        highWaterMark: 128000, // internal buffer size
    });
    
    let flag = true;
    stream.on("data", async (chunk) => {
        stream.pause();
        setTimeout(async () => {
            await asyncHandle(chunk);
            flag = false;
            stream.resume();
        }, 1000); // I need this timeout because of my reasons, if u don't need it, just remove.
    });
    
    async function asyncHandle(chunk) {
        if (flag) {
            //U may have another header size.
            chunk = chunk.slice(206); // Because I don't wanna play header as music!
        }
        console.log(chunk);
        io.emit("chat message", chunk);
    }
    
    

    Hope it will help anyone! More info about streaming pcm data you may find, thank to the kind man from the comment's section of the following link, here Play PCM with javascript