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:
My Problem:
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`))
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.