Giter Club home page Giter Club logo

node-icy's Introduction

node-icy

Node.js module for parsing and/or injecting ICY metadata

Build Status

This module offers a Reader class for retrieving the raw audio data and parsing the metadata from an ICY stream (commonly SHOUTcast or Icecast broadcasts).

There's also a Writer class that allows you to inject your own metadata into a data stream, which can then be displayed by another ICY client (like VLC).

But you'll probably be most interested in the Client class that builds off of node's core http module, except this version works with servers that return an ICY HTTP version, and automatically sends an "Icy-MetaData: 1" HTTP header to notify the server that we want metadata, and finally it returns a Reader instance in the "response" event, therefore the "res" object also emits "metadata" events. See the example below to see how it works.

A good use case for this module is for HTML5 web apps that host to radio streams; the <audio> tag doesn't know how to deal with the extra metadata and it is impossible to extract (on the client-side). But a WebSocket connection could be used in conjunction with this module to provide those metadata events to a web browser, for instance.

Installation

Install with npm:

$ npm install icy

Example

Here's a basic example of using the HTTP Client to connect to a remote ICY stream, pipe the clean audio data to stdout, and print the HTTP response headers and metadata events to stderr:

var icy = require('icy');
var lame = require('lame');
var Speaker = require('speaker');

// URL to a known ICY stream
var url = 'http://firewall.pulsradio.com';

// connect to the remote stream
icy.get(url, function (res) {

  // log the HTTP response headers
  console.error(res.headers);

  // log any "metadata" events that happen
  res.on('metadata', function (metadata) {
    var parsed = icy.parse(metadata);
    console.error(parsed);
  });

  // Let's play the music (assuming MP3 data).
  // lame decodes and Speaker sends to speakers!
  res.pipe(new lame.Decoder())
     .pipe(new Speaker());
});

You are also able to add custom headers to your request:

var url = require('url');

// URL to a known ICY stream
var opts = url.parse('http://yourstreamurl.tld/');

// add custom headers
opts.headers = { 'User-Agent': 'Your awesome useragent' };

// connect to the remote stream
icy.get(opts, callback);

API

Client()

The Client class is a subclass of the http.ClientRequest object.

It adds a stream preprocessor to make "ICY" responses work. This is only needed because of the strictness of node's HTTP parser. I'll volley for ICY to be supported (or at least configurable) in the http header for the JavaScript HTTP rewrite (v0.12 of node?).

The other big difference is that it passes an icy.Reader instance instead of a http.ClientResponse instance to the "response" event callback, so that the "metadata" events are automatically parsed and the raw audio stream it output without the ICY bytes.

Also see the request() and get() convenience functions.

request()

request() convenience function. Similar to node core's http.request(), except it returns an icy.Client instance.

get()

get() convenience function. Similar to node core's http.get(), except it returns an icy.Client instance with .end() called on it and no request body written to it (the most common scenario).

Reader()

ICY stream reader. This is a duplex stream that emits "metadata" events in addition to stripping out the metadata itself from the output data. The result is clean (audio and/or video) data coming out of the stream.

Writer()

The Writer class is a duplex stream that accepts raw audio/video data and passes it through untouched. It also has a queue() function that will queue the Writer to inject the metadata into the stream at the next "metaint" interval.

Writer#queue(metadata)

Queues a piece of metadata to be sent along with the stream. metadata may be a String and be any title (up to 4066 chars), or may be an Object containing at least a "StreamTitle" key, with a String value. The serialized metadata payload must be <= 4080 bytes.

parse()

Parses a Buffer (or String) containing ICY metadata into an Object.

stringify()

Takes an Object and converts it into an ICY metadata string.

node-icy's People

Contributors

anunezde avatar ashtuchkin avatar atryfox avatar julien51 avatar tootallnate avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-icy's Issues

TypeError: Cannot read property 'blue' of undefined

This is the error thrown after git'ing your repository and running basic.js out of the box. I believe the use of the colors node module is incorrect. Stack trace below:

TypeError: Cannot read property 'blue' of undefined
at /Users/onyx/temp/node-icecast-stack/examples/basic/basic.js:31:37
at Array.forEach (native)
at Client. (/Users/onyx/temp/node-icecast-stack/examples/basic/basic.js:30:18)
at Client.emit (events.js:59:20)
at HttpRequestStack.emit (/usr/local/lib/node/.npm/stream-stack/1.1.0/package/stream-stack.js:196:23)
at HttpRequestStack._onHeadersComplete (/usr/local/lib/node/.npm/http-stack/0.0.2/package/lib/http-stack.js:92:8)
at HttpRequestStack._onData (/usr/local/lib/node/.npm/http-stack/0.0.2/package/lib/http-stack.js:63:12)
at Socket. (native)
at Socket._origEmit (events.js:59:20)
at Socket.emit (/usr/local/lib/node/.npm/stream-stack/1.1.0/package/stream-stack.js:191:28)

Example to html

Anyone able to give an example to send audio to an html audio tag, or point out what i'm doing wrong? I tried the example to Speaker and it works fine. Adapting that to do

var outputStream = new require('stream').PassThrough();

app.get('/listen', function (req, res) {
  icy.get(url, function (res) {
     res.pipe(new lame.Decoder())
      .pipe(outputStream);

     var callback = function (chunk) {
        // console.log(`Received ${chunk.length} bytes of data.`);
        res.write(chunk);
      };

      outputStream.on('data', callback);
  });
});

with the same input stream and lame quits, complaining about illegal headers.

Html is

<audio controls>
  <source src="/listen" type="audio/mpeg">
</audio>

metadata event not being fired

I've copy/pasted the example file into a working server and have confirmed the response headers are received (by the console.log(res.headers) line). However no metadata events are being triggered, even when I'm confident the stream is sending metadata. Below is the exact code I'm running

icy.get(url, function (res) {

  // log the HTTP response headers
  console.log(res.headers);

  // log any "metadata" events that happen
  res.on('metadata', function (metadata) {
    console.log(metadata);
    var parsed = icy.parse(metadata);
    console.log(parsed);
    io.sockets.emit("metadata", metadata);
  });
});

following redirect ?

My icecast loadbalancer is issuing a "moved temporarily" header and pointing the stream somewhere, it seems that this is breaking the ".get" method from node-icy.

Any ideas on how to "follow redirects" ?

Module Crash

Hello !

I've used your module to provide an real-time update of the music playing actually in our WebRadio.

But, after one night of running, the application crashes with this error which is from to your module :
http://s.drogrin.pw/14113719_.png

I've trying to solve the problem by update the package http-stack but i think he is up-to-date.

Best regard !
Drogrin.

Parsing buffer is wrong for IHeartRadio

Issue
*Note: I am using an IHeartRadio stream.
When parsing a buffer, if there is another quote in the text, it is not shown. But, instead, when doing buffer.toString(), it returns everything. Not sure if this has to do with Icy in general or IHeartRadio but it seems to be an issue.

Example
Not Working:

stream.on('metadata', function(md) {
  br.metadata = icy.parse(md);
}
// br.metadata = {"StreamTitle": "Taio Cruz - text"}

Working:

stream.on('metadata', function(md) {
  br.metadata = md.toString();
}
// br.metadata = "StreamTitle='Taio Cruz - text="Dynamite" song_spot="M" MediaBaseId="1734278" itunesTrackId="0" amgTrackId="-1" amgArtistId="0" TAID="43011" TPID="7610716" cartcutId="0708213001"';"

The URL
https://c3icy.prod.playlists.ihrhls.com/2385_icy

Anyway to increase metadata yield from stream?

Hi Nathan!

First of all thank you for taking the time to write this in node! Helpful, useful & awesome!

I wanted to understand why sometimes certain streams have empty metadata. I understand that there is a possibility that the stream itself failed to inject metadata, but I wonder if there is anything that could be done on the Node.js client-side to increase yield of metadata.

From ShoutCast Documentation:

Read the data stream as you normally would, keeping a byte count as you go. When the number of bytes equals the metadata interval, you will get a metadata block. The first part of the block is a length specifier, which is the next byte in the stream. This byte will equal the metadata length / 16. Multiply by 16 to get the actual metadata length. (Max byte size = 255 so metadata max length = 4080.) Now read that many bytes and you will have a string containing the metadata. Restart your byte count, and repeat. Success!

I see that the module:

  1. Keeps a byte count
  2. Fires a metadata event as soon as the response data reaches icy-metaint interval (in cURL case below at 2048)
  3. On that metadata event, uses a META_BLOCK_SIZE constant to figure out the length of the metadata, and once acquiring that length, parsing that data to get at the StreamTitle string

Yet anecdotally I get some value metadata a third or half the time. This could (and most likely is) the server itself failing to inject that metadata into the stream, but I wonder if there is anything else that could be done on the client side.

Here is an example request/response:

curl -v http://54.167.135.153/sbsystems-wpatfmaac-ib-64?session-id=2097886607 > tmp.aac

Request Headers

GET /sbsystems-wpatfmaac-ib-64?session-id=2097886607 HTTP/1.1
Host: 54.167.135.153
User-Agent: curl/7.43.0
Accept: */*

Response Headers

HTTP/1.1 200 OK
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Pragma: no-cache
icy-metaint: 2048
icy-br: 64
icy-name: 93.1 AMOR LA NUEVA
content-type: audio/aacp

Thank you again Nathan for building this module. Even if you don't get time to look into this question I still very much appreciate your work in developing such a helpful module.

Thanks,
Daniel

UTF8 Metadata

Currently when receiving accented characters, the metadata gets weird chars. Is this possible to fix?

Like "Cafรฉ Tacuba" gets parsed as "Caf๏ฟฝ Tacuba"

Error: Don't know what to do with this header

Hey Nate,

So, running the example you put on the homepage or the basic.js file on the examples folder prints out the same message, such as: Error: Don't know what to do with this header: 'Host: streaming23.radionomy.com'

Btw:
pedromtavares$ node -v
v0.5.3-pre

So how do we go by solving this? :)

MP3 ICY streams no longer seem to work?

Sample: https://radio.dripfeed.net/listen/monstromental/radio.mp3

HTTP/2 200 
server: nginx
date: Sun, 26 Feb 2023 19:57:38 GMT
content-type: audio/mpeg
pragma: no-cache
expires: Thu, 19 Nov 1981 08:52:00 GMT
cache-control: no-store, no-cache, private
vary: Origin
icy-br: 128
ice-audio-info: channels=2;samplerate=44100;bitrate=128
icy-description: Pulp Horror Instro Surf
icy-genre: Horror
icy-name: MONSTROMENTAL
icy-pub: 1
icy-url: https://www.monstromental.com
x-xss-protection: 1
x-content-type-options: nosniff
referrer-policy: no-referrer-when-downgrade

Also: https://stream.camfm.co.uk/camfm

HTTP/1.1 200 OK
Content-Type: audio/mpeg
Date: Sun, 26 Feb 2023 19:59:40 GMT
Server: Icecast 2.4.0-kh13
Cache-Control: no-cache, no-store
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type
Access-Control-Allow-Methods: GET, OPTIONS, HEAD
Connection: Close
Expires: Mon, 26 Jul 1997 05:00:00 GMT
        console.log(response.headers['content-type']);
                                    ^
TypeError: Cannot read properties of undefined (reading 'content-type')

But rawHeaders exist for these streams:

[
  'Content-Type',
  'audio/mpeg',
  'Date',
  'Sun, 26 Feb 2023 20:05:49 GMT',
  'Server',
  'Icecast 2.4.0-kh13',
  'Cache-Control',
  'no-cache, no-store',
  'Access-Control-Allow-Origin',
  '*',
  'Access-Control-Allow-Headers',
  'Origin, Accept, X-Requested-With, Content-Type',
  'Access-Control-Allow-Methods',
  'GET, OPTIONS, HEAD',
  'Connection',
  'Close',
  'Expires',
  'Mon, 26 Jul 1997 05:00:00 GMT',
  'icy-metaint',
  '16000'
]
[
  'Server',
  'nginx',
  'Date',
  'Sun, 26 Feb 2023 20:05:13 GMT',
  'Content-Type',
  'audio/mpeg',
  'Transfer-Encoding',
  'chunked',
  'Connection',
  'close',
  'Pragma',
  'no-cache',
  'Expires',
  'Thu, 19 Nov 1981 08:52:00 GMT',
  'Cache-Control',
  'no-store, no-cache, private',
  'Vary',
  'Origin',
  'icy-br',
  '128',
  'ice-audio-info',
  'channels=2;samplerate=44100;bitrate=128',
  'icy-description',
  'Pulp Horror Instro Surf',
  'icy-genre',
  'Horror',
  'icy-name',
  'MONSTROMENTAL',
  'icy-pub',
  '1',
  'icy-url',
  'https://www.monstromental.com',
  'icy-metaint',
  '16000',
  'X-XSS-Protection',
  '1',
  'X-Content-Type-Options',
  'nosniff',
  'Referrer-Policy',
  'no-referrer-when-downgrade'
]

The application/ogg stream I've been testing against works fine, it's specifically audio/mpeg streams that fail.

close stream

I'm using node-icy to scan streams, but all streams stay opened... how I can close/destroy a stream response ?

Getting Track Metadata ?

Hi,
this returns the current global stream metadata,

 console.error('stat:', res.headers);

how to retrive the song metadata please?
the metadata callback seams to get called never

Example of writer

An example of how Writer() works should be handy, from what I can find i am still not able to set it up.

Example doesn't work - error

Get this error when trying example:

http.js:1447
  var ret = parser.execute(d, start, end - start);
                   ^
TypeError: Cannot call method 'execute' of undefined
    at socketOnData (http.js:1447:20)
    at Socket.v.listener (/var/www/{"my directory"}/node_modules/icecast/node_modules/event-hijack/hijack.js:52:35)
    at Socket.EventEmitter.emit (events.js:96:17)
    at emit (/var/www/{"my directory"}/node_modules/icecast/node_modules/event-hijack/hijack.js:71:25)
    at Socket.<anonymous> (/var/www/{"my directory"}/node_modules/icecast/lib/preprocessor.js:51:5)
    at Socket.EventEmitter.emit (events.js:96:17)
    at TCP.onread (net.js:397:14)

Writing stream causes temporary speed up after pause

This may not be a node-icy bug, but I am using metadata to decide when I'd like to save the current stream to a file and when I'd like to ignore it. Playing back a recorded file, at the start of a section that was recorded immediately after a period of ignoring the stream, there's often a few seconds of content that replay at what seem to be at least 5x normal speed. Given that what's getting written is supposed to just be the straight passed-through data from the stream, it's especially strange that the content speeds up briefly. I would think that to cause a speed-up, the bitrate index for that section would need to change, but I have no idea what's temporarily causing that. Note that I'm not using the write function of node-icy (since there's not much documentation about it), but rather just using fs.createWriteStream to open a file once, and thereafter write all emits of data to it when the last metadata emit meets my desired conditions (otherwise that data is simply ignored). Any ideas?

No metadata event on OGG streams

Some Icecast OGG streams do not create icy metadata events.

Stream: https://next.fillyradio.com:8000/vbr_ogg

Whilst audio streaming works great, this particular block:

res.on("metadata", function(metadata) {
  console.log('Got Metadata')
}

never fires, unless the stream is MP3, and then we get all the nice happy console.logs!

I'm considering attaching a method to get metadata events externally (they're needed to build a FFMPEG Chapter file for the recorded data), but we liked the way that we got the metadata when we used MP3. Unfortunately, being able to write the file directly to OGG, without transcoding, is more important in our use case.

If you can figure out an alternative way to get the stream metadata, it'd be greatly appreciated. Otherwise, we'll likely be polling the Icecast with setTimeout().

Browser version

Want to share a browser version that uses the fetch readable stream api and MSE.
it uses a fraction of what a browser bundle of what this package uses... And it has no dependencies

Code
{
  const mozChunked = 'moz-chunked-arraybuffer';
  const supportChunked = (b => {
    try {
      return (b.responseType = mozChunked), b.responseType == mozChunked
    } catch (c) {
      return !1
    }
  })(new XMLHttpRequest);
  const supportsFetch = typeof Response !== 'undefined' &&
                        Response.prototype.hasOwnProperty('body')

  const trim = (arr, b = arr.length) => {
    for (;~--b && arr[b] === 0;);
    return arr.slice(0, b+1)
  }

  const parseMeta = str => {
    const pieces = str.split(';')
    const rtn = {}

    for (let peace of pieces) {
      peace = peace.trim();
      if (peace.length > 0) {
        const delimiter = /\=(['"])/.exec(peace)
        const name = peace.substring(0, delimiter.index)
        const value = peace.substring(delimiter.index + 2, peace.length - 1)
        rtn[name.trim()] = value.trim()
      }
    }
    return rtn
  }

  const attach = (req, player) => {
    if (!(req instanceof Request))
      req = new Request(req)

    req.headers.set('icy-metadata', '1')

    const decoder = new TextDecoder
    const ms = new MediaSource()
    const track = player.addTextTrack('metadata')
    const {cues} = track
    const segments = []

    /**********************
     0 = reading raw data
     1 = reading byteRange
     2 = reading meta data
    **********************/
    let state = 0
    let metaint;
    let byteLeftToRead = 0;
    let sourceBuffer;

    player.src = URL.createObjectURL(ms)
    player.controls = true

    const doAppend = () => {
      if (sourceBuffer.updating || !segments.length)
        return

      let chunk = segments.shift()

      // large buffer needs to be sliced so
      // metadata is not feed into the sourceBuffer
      if (chunk.length > byteLeftToRead) {
        const b = chunk.slice(0, byteLeftToRead)
        segments.unshift(chunk.slice(b.length))
        chunk = b
      }

      if (state === 2) {
        // small buffer needs to fill the remaining metadata
        if (chunk.length < byteLeftToRead) {
          if (segments.length) {
            const a = chunk
            const b = segments.shift()
            const c = new Int8Array(a.length + b.length)
            c.set(a)
            c.set(b, a.length)
            segments.unshift(c)
          }
          return
        }
      }

      byteLeftToRead -= chunk.length

      if (state === 0) {
        // Reading raw metaData
        if (byteLeftToRead === 0) {
          state = 1
          byteLeftToRead = 1
        }
        sourceBuffer.appendBuffer(chunk)
      } else if (state === 1) {
        byteLeftToRead = chunk[0] * 16
        if (byteLeftToRead > 0) {
          state = 2
        } else {
          state = 0
          byteLeftToRead = metaint
        }
      } else {
        chunk = trim(chunk)
        const {StreamTitle} = parseMeta(decoder.decode(chunk))
        const end = Number.MAX_SAFE_INTEGER
        let start = 0
        if (cues.length !== 0) {
          const last = cues[cues.length - 1]
          start = last.endTime = sourceBuffer.buffered.end(0)
        }

        track.addCue(new VTTCue(start, end, StreamTitle))
        state = 0
        byteLeftToRead = metaint
      }
    }

    req = supportsFetch ? fetch(req).then(res => {
      // Use fetch streaming technique
      const reader = res.body.getReader()
      const pump = () => reader.read().then(chunk => {
        window.e = new Blob([window.e || '', chunk.value], {type: 'application/wergfwe'})
        if (chunk.value) segments.push(chunk.value)
        doAppend()
        return chunk.done ? undefined : pump()
      })

      byteLeftToRead = metaint = ~~res.headers.get('icy-metaint') || 8192

      return pump
    }) : supportChunked ? new Promise(rs => {
      // Use Firefox XHR's moz-chunked-arraybuffer streaming technique
      const xhr = new XMLHttpRequest
      xhr.open('get', req.url)
      xhr.setRequestHeader('icy-metadata', '1')
      xhr.responseType = mozChunked
      xhr.onreadystatechange = () => {
        if (xhr.readyState === xhr.HEADERS_RECEIVED) {
          byteLeftToRead = metaint = ~~xhr.getResponseHeader('icy-metaint') || 8192
        }
        rs(() => {})
      }
      xhr.onprogress = () => {
        const bytes = new Uint8Array(xhr.response).slice()
        segments.push(bytes)
      }
      xhr.send()
    }) : Promise.reject(new Error('unable to stream audio'))

    ms.addEventListener('sourceopen', async evt => {
      sourceBuffer = evt.target.addSourceBuffer('audio/aac')
      sourceBuffer.mode = 'sequence'
      req.then(pump => pump())
      window.sb = sourceBuffer
      sourceBuffer.addEventListener('updateend', () => {
        if (!sourceBuffer.updating) ms.duration = sourceBuffer.buffered.end(0)
        doAppend()
      }, false)

      player.play()
    }, false);
  }


  const play = req => ({
    in(selector) {
      let player;

      // turn string into selector
      if (typeof selector === 'string') {
        selector = document.querySelector(selector)
      }

      const isNode = selector instanceof HTMLElement
      const isMedia = selector instanceof HTMLAudioElement ||
                      selector instanceof HTMLVideoElement

      if (isNode) {
        if (isMedia) {
          player = selector
        } else {
          player = new Audio
          player.controls = true
          selector.appendChild(player)
        }
      } else {
        player = new Audio
      }

      attach(req, player)
      return player
    }
  })

  window.play = play
}

Api

play(url).in(querySelector) // string or node elm (if it's not a video/audio element it will append a audio elm)
play(url).in(window) // plays without appending to body
const player = play(url).in('body') // append a new audio elm to body with controls set to true

// From here just use the media api.
// this will give you the current title of the auido that are being played
player.textTracks[0].oncuechange = evt => console.log(evt.target.activeCues[0].text)

update: included streaming for firefox alos, but it don't support the codec that i pass in addSourceBuffer... solution?

Detecting OGG stream and switching from node-lame decoder to node-ogg decoder?

Greetings,

I'd really like to build an app that can support both regular shoutcast / icy mp3 streams as well as the icy OGG streams that Traktor creates natively. Both of the libraries you've written, node-lame and node-ogg decode to raw PCM which is great for streaming one format into another, ultimately the stream I encode and send out will be from the node-lame encoder because it has greater browser support.

However, I've been looking for a way to glean some information from icy that will give me a heads up as to what the stream's file format is so I can use a conditional statement to switch out decoder instances. I've tried getting the information from icy.parse(metadata); and the URL method mentioned in the example (to get the headers) but I can't seem to find something that will let me determine the container for the format icy gets from the server.

I could try to use the lame decoder's .on('format', function(format) { in some sort of a try catch scenario but that seems kind of inefficient; also wondering if there are other resource based things that make this unfeasible.

I'd appreciate the input

Thanks~

Memory Leaks

The example code for piping to the lame decoder and speaker eventually uses up all my system memory. Is there a way to only keep a small buffer (1mb or so) around instead of the entire stream? This doesn't seem like it could be run for hours at a time without crashing due to the massive memory heap size overtime.

Disconnect from a stream?

How do I disconnect from a radio stream?
I've tried .end and .destroy from client but neither emits a 'close' or 'error' event - I think I'm still connected.

Thanks,
Shawn

Promise Support

Is it possible to add Promise support to this? As of right now, it's kind of annoying being forced to use a callback, when we could use a promise, and then use async/await to keep everything inline and error catchable. Specifically, promise support for icy.get.

stream changes not being triggered

After some time (hours) succesfully consuming the stream metadata, it suddenly stops bringing new events, no more new songs.

Any clues? Thanks

metadata event only called once

The 'metadata' event is only getting called once on app startup. If the song changes, a new event is never thrown, but if I restart the server, it does grab the new song title then.

Is my stream setup wrong, or am I missing something in the code?

My code:

icy.get(shoutcastConfig.serverUrl,function(res){
    console.error(res.headers);
    res.on('metadata',function(metadata){
      var parsed = icy.parse(metadata);
      console.log('Metadata event called');
      console.log(parsed);
    });

The stream url is:
http://ic2.christiannetcast.com/wytj-fm?type=.mp3

Please consider renaming your project, the name is misleading

Hi, Icecast maintainer here, just found out about your project.

I appreciate the work you've put into creating a streaming server on node.js.
Could you please consider renaming your project though, as it is not actually Icecast, but an implementation of the ICY streaming protocol as originally introduced in Shoutcast and as legacy protocol present in Icecast.
Icecast supports proper HTTP streaming of many formats, especially free formats using the Ogg container like Vorbis, Opus, Theora and WebM video, in addition ICY based streams are working too.

I think it would get you the recognition for your work and avoid us both the confusion of people looking for Icecast.

Thanks in advance.

How to catch timeout events?

I'm setting up my icy reader like:

 const opts: any = {};
    opts.hostname = myURL.hostname;
    opts.port = myURL.port;
    opts.timeout = 3000;
    opts.path = myURL.pathname;
    opts.protocol = myURL.protocol;
    // connect to the remote stream
    icy.get(opts, function(response) {
      const streamObj: any = {};

      if (response.headers['icy-name']) {
        streamObj.streamName = response.headers['icy-name'];
      }

      if (response.headers['icy-url']) {
        streamObj.streamUrl = response.headers['icy-url'];
      }

      // log any "metadata" events that happen
      response.once('metadata', (metadata: any) => {
        const parsed = icy.parse(metadata);
        if (parsed['StreamTitle']) {
          streamObj.streamTitle = parsed['StreamTitle'];
        }
        if (parsed['StreamUrl']) {
          streamObj.streamUrl = parsed['StreamUrl'];
        }
        //console.log(streamObj);
        res.status(200).send(streamObj);
        return;
      });

      response.once('timeout', (err: any) => {
        console.log('timeout');
        res.status(400).end(err);
        return;
      });

      response.once('end', (err: any) => {
        console.log('end');
        res.status(200).end('end');
        return;
      });
      response.resume();
    });

But if node-icy connects to a bad url, or one that doesn't respond, the connection never times out. Should I be listening for something other than 'timeout'?

Connection Limit?

Is there a connection limit to the number of icecast streams that you can connect to? I'm working on building a monitoring service where i connect to the icecast stream and then wait for the end event, which I then do some operations to alert the admins.

I'm able to trigger an end event message for about the first 5 streams on the list. The rest don't seem to be connecting.

Here is an example of what I'm trying to do:

// var sources is coming from the icecast stats
// go through the sources and set them up to be monitored
for(var $i = 0; $i < 50; $i++){
    var stream = sources.source[$i].listenurl;
    icecast.monitor(stream);
}

// my icecast.monitor function
// receives the streamurl / listenurl and tries to connect 
exports.monitor = function(url) {
    console.log('Monitoring: '+url); 
    icecast.get(url , function(res){

        // get the metadata
        res.on('metadata', function(metadata){
            var parsed = icecast.parse(metadata); 
            console.error(parsed); 
        }); 

        res.on('data', function(data){
            // console.log(data); 
        }); 

        // when something goes wrong
        res.on('end', function(data){
            console.log('oh shit something went wrong with'); 
        }); 
    }); 
};

Getting 'undefined' when reading data from icy.parse()

When running icy.parse() on metadata retrieved from the server, it looks fine when I read it from stdout, however, when I try to retrieve a value from the object, it only gives me undefined. The data looks like this when its parsed:

{ server: 'Icecast 2.4.0',
  date: 'Thu, 16 Jun 2016 20:12:39 GMT',
  'content-type': 'audio/mpeg',
  'cache-control': 'no-cache',
  expires: 'Mon, 26 Jul 1997 05:00:00 GMT',
  pragma: 'no-cache',
  'icy-br': '128',
  'ice-audio-info': 'bitrate=128',
  'icy-description': '###',
  'icy-genre': 'Variety',
  'icy-name': '###',
  'icy-pub': '0',
  'icy-url': '###',
  'icy-metaint': '16000' }

Some keys are enclosed with single quotations; if I try to read a value from there, it just throws an error:

/home/###/Desktop/###/node_modules/icy/lib/parse.js:21
  var pieces = metadata.replace(/\0*$/, '').split(';');
                        ^

TypeError: metadata.replace is not a function

timeout

how I can modify the request timeout for icy ?

Support for https

Currently, node-icy/node-icecast crashes If I try to handle a https url:

var icecast = require("icecast");
icecast.get(url, function (icyres) {
  //.. code
});

I think the underlying http class should switch to https when ssl is required?

Will the streaming be in sync?

If the port is open in two different tabs/browsers at different times, will the streaming of mp3 file go in sync in both the browsers?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.