node.jsstreampipeaudio-streaminghttp-streaming

Node.js server that streams an infinite silence sound switches to streaming mew audio data receiving an event, and then returns to silence


To create a Node.js server that streams an infinite silence sound, switches to streaming actual audio data upon receiving an event, and then returns to silence after the event's audio data ends, steps:

Plan:

  1. Create an HTTP Server: Set up an HTTP server to handle incoming requests.
  2. Infinite Silence Sound: Stream silence sound infinitely when the request starts.
  3. Handle Audio Events: On receiving an appEvent.on('audio'), start streaming the audio data.
  4. Return to Silence: Once the audio data ends, go back to streaming silence.
  5. Keep the Request Open: Ensure the request stays open as long as the client is connected.

My Problem:

  1. switching between file streaming doesn't work
  2. silence sound always infinite play good but speech.mp3 doesn't work

How to resolve switching between file stream for pipe to response ? This sevice must work like Radio

My Implementation:

import events from 'events'
import express from 'express'
import * as fs from 'fs'
import path from 'path'
import { Readable } from 'stream'

const appEvent = new events.EventEmitter()

const silencePath = path.join(__dirname, 'silence.mp3')

// Helper function to generate silence sound
function silence() {
  const buffer = fs.readFileSync(silencePath) // Read file as Buffer
  return Readable.from(
    (async function* () {
      while (true) {
        yield buffer // Yield Buffer, not ArrayBuffer
      }
    })(),
  )
}

const app = express()

app.get('/stream', (req, res) => {
  // Example: Emit audio event to start streaming audio data
  setTimeout(() => {
    const speechPath = path.join(__dirname, 'speech.mp3')
    const speechStream = fs.createReadStream(speechPath)

    appEvent.emit('audio', speechStream)
  }, 5000)

  res.writeHead(200, {
    'Content-Type': 'audio/mpeg',
    'Transfer-Encoding': 'chunked',
    Connection: 'keep-alive',
  })

  let silenceStream = silence()
  silenceStream.pipe(res)

  let audioStream: fs.ReadStream
  let audioInProgress = false

  // Listen for audio event to switch to the audio stream
  appEvent.on('audio', (audioDataStream: fs.ReadStream) => {
    if (audioInProgress) return // Ignore if already playing audio

    audioInProgress = true
    silenceStream.unpipe(res)

    audioStream = audioDataStream
    audioStream.pipe(res)

    // When the audio finishes, return to silence
    audioStream.on('end', () => {
      silenceStream = silence()
      silenceStream.pipe(res)
      audioInProgress = false
    })
  })

  req.on('close', () => {
    if (audioStream) {
      audioStream.unpipe(res)
      audioStream.destroy()
    }
    silenceStream.unpipe(res)
  })
})

app.listen(3000, () => console.log(`Client side start on 3000 port http://localhost:3000`))

Solution

  • To do this properly, you actually need to generate a continuous PCM audio stream and mix in your event samples (or replace the silent samples), and then go back to the silent stream. And then, encode that audio stream with something like FFmpeg that can give you your continuous radio-like stream.

    The reason for this stream is that you cannot simply concat chunks arbitrarily. Each container format has its own specification you'd have to follow. It's easier to do your mixing in PCM samples, which gives you more flexibility for how you encode and mux into containers later.