0

I am running Node.js server with express. I'd also like the server to accept IceCast audio stream.

I could use another port, sure, but not all hostings (like Heroku) allow that. Ice cast's stream request looks like this:

SOURCE /mountpoint ICE/1.0\n
content-type: audio/mpeg\n
Authorization: Basic USER+PASS base64encoded\n
ice-name: This is my server name\n
ice-url: http://www.oddsock.org\n
ice-genre: Rock\n
ice-bitrate: 128\n
ice-private: 0\n
ice-public: 1\n
ice-description: This is my server description\n
ice-audio-info: ice-samplerate=44100;ice-bitrate=128;ice-channels=2\n
\n

After that, audio stream follows. I wrote a separate server that handles this on another port and it works fine.

var headers = "";
var headersEnd = false;
var mp3;
const audioServer = net.createServer(function (socket) {
    if (mp3) {
        socket.write("HTTP/1.0 403 Client already connected\r\n\r\n");
        socket.end();
        socket.on("error", (e) => {});
        return;
    }
    mp3 = fs.createWriteStream("test.mp3", { encoding: null, flags: "a" });
    socket.on("data", (data) => {
        if (!headersEnd) {
            var tmp = "";
            for (let i = 0, l = data.byteLength; i < l; ++i) {
                const item = data[i];
                if (item == CR_NUMBER)
                    continue;
                const character = String.fromCharCode(item);
                tmp += character;

                headers += character;
                if (headers.endsWith("\n\n")) {
                    headersEnd = true;
                    console.log("ICE CAST HEADERS: \n", headers.replace(/\n/g, "\\n\n").replace(/\r/g, "\\r"));
                    break;
                }
            }
        }
        else {
            mp3.write(data);

        }
    });
    socket.on("close", () => {
        console.log("ICE CAST: END");
        if (mp3) {
            mp3.close();
            mp3 = null;
        }

    });
    socket.on("error", (e) => {
        console.log("ICE CAST: ERROR" + e.message);
        socket.end();
    });
});
audioServer.listen(11666);

What I'd like is to somehow bootstrap node's HTTP server so that I can stream over the same port.

I tried to access the req connection info, that doesn't really work, because the server does not even let the SOURCE /mountpoint ICE/1.0 through.

const server = http.createServer(function (req, res) {
    /// does not happen, server closes the connection from icecast
    if (handleAudioStream(req, res)) {
        return;
    }
    else {
        return expressApp(req, res);
    }
});

So I'd need to go deeper. I tried to inspect the net and http code, but didn't fund anything useful.

How can I do this? I really need to use same port, and since icecast DOES send the HTTP-like headers, it should be possible.

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778

1 Answers1

0

This isn't trivial, but possible. You can do some duck punching/monkey patching. See this answer: https://stackoverflow.com/a/24298059/362536

Also, it may be possible to get official support some day, but we're a ways off from that. The first blocker was the non-standard SOURCE method. I sponsored a bounty on that and Ben Noordhuis was kind enough to implement last week: https://github.com/nodejs/http-parser/issues/405 It should land in Node.js eventually.

The next issue is the ICE/1.0. I've opened an issue for that here: https://github.com/nodejs/http-parser/issues/410 There hasn't been any objection to adding it to the parser yet, but if you want to add a pull request, that might help a chance of approval.

You'll find other compatibility issues as well as you continue down this road, but all I've hit I've been able to overcome with various solutions. The trick is, maintaining strict compatibility with the Node.js core as it is updated.

Brad
  • 159,648
  • 54
  • 349
  • 530