Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Major rewrite. Shoutcast server that streams 128kb/s. Some things are…

… hard coded and others are not configurable. This was really just a proof of concept of the queuestream module (but I'll likely keep going with it). Still needs LOADS of work.

Signed-off-by: Nick Campbell <nicholas.j.campbell@gmail.com>
  • Loading branch information...
commit da56b25207584417d2527506216889d67a36d7a0 1 parent 3fe26d3
Nick Campbell authored
2  .gitignore
... ... @@ -0,0 +1,2 @@
  1 +*.sw[a-z]
  2 +node_modules
7 examples/html5audio.html
... ... @@ -0,0 +1,7 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 + <head>HTML5 Audio Stream</head>
  4 + <body>
  5 + <audio src="http://localhost:7000/" controls preload="auto" autobuffer autoplay="true"></audio>
  6 + </body>
  7 +</html>
96 lib/shoutcast.js
... ... @@ -0,0 +1,96 @@
  1 +var QueueStream = require('queuestream').QueueStream,
  2 + util = require('util'),
  3 + fs = require('fs'),
  4 + Stream = require('stream').Stream;
  5 +
  6 +function ShoutStream(opts) {
  7 + QueueStream.call(this);
  8 +
  9 + this.setMaxListeners(100);
  10 +
  11 + var self = this;
  12 + setInterval(function() {
  13 + self.resume();
  14 + }, 1000);
  15 +}
  16 +util.inherits(ShoutStream, QueueStream);
  17 +
  18 +ShoutStream.prototype.currentData = function(data) {
  19 + // throttle bitrate...(atm hard coded)
  20 + this.pause();
  21 + this.emit('data', data);
  22 +}
  23 +
  24 +function Station(opts) {
  25 + if (!opts) opts = {};
  26 +
  27 + this.name = opts.name;
  28 + this.genre = opts.genre;
  29 + this.url = opts.url;
  30 + this.notice = opts.notice;
  31 + this.notice2 = "NodeJS Streaming Shoutcast Server/v0.1";
  32 +
  33 + this.stream = new ShoutStream();
  34 +
  35 + this.playlist = {
  36 + };
  37 +
  38 + this.stats = {
  39 + bytesWritten: 0,
  40 + numPlayed: 0
  41 + };
  42 +
  43 + var self = this;
  44 + this.stream.on('data', function(data) {
  45 + self.stats.bytesWritten += data.length;
  46 + });
  47 + this.stream.on('next', function(nextTrack) {
  48 + self.stats.numPlayed++;
  49 + console.log('Next: ');
  50 + console.log(self.playlist[nextTrack.path]);
  51 + });
  52 +}
  53 +
  54 +Station.prototype.start = function() {
  55 + this.stream.startNext();
  56 +}
  57 +
  58 +Station.prototype.connect = function(res, cb) {
  59 + var headers = {
  60 + 'icy-notice1': this.notice,
  61 + 'icy-notice2': this.notice2,
  62 + 'icy-name': this.name,
  63 + 'icy-genre': this.genre,
  64 + 'icy-url': this.url,
  65 + 'Content-Type': 'audio/mpegurl',
  66 + 'icy-pub':'0',
  67 + 'icy-br':'56',
  68 + 'icy-metaint': '0' //32*1024
  69 + };
  70 +
  71 + res.writeHead(200, headers);
  72 + util.pump(this.stream, res, function() {
  73 + console.log('Shhhart');
  74 + res.end();
  75 + });
  76 +}
  77 +
  78 +Station.prototype.addTrack = function(track) {
  79 + var self = this;
  80 + function add(f) {
  81 + if (f instanceof Stream) {
  82 + self.stream.queue(f);
  83 + } else {
  84 + add(fs.createReadStream(f, {bufferSize: 128*1024}));
  85 + }
  86 + }
  87 +
  88 + this.playlist[(track.file || track.stream.path)] = track;
  89 + add(track.stream || track.file);
  90 +}
  91 +
  92 +module.exports = {
  93 + Station: function(opts) {
  94 + return new Station(opts);
  95 + }
  96 +};
23 package.json
... ... @@ -0,0 +1,23 @@
  1 +{
  2 + "name": "shoutcast",
  3 + "version": "0.0.1",
  4 + "author": "Nick Campbell (http://github.com/ncb000gt)",
  5 + "description": "Shoutcast Streaming Server",
  6 + "keywords": ["music", "streaming music", "shoutcast", "streaming"],
  7 + "repository": {
  8 + "type": "git",
  9 + "url": "http://github.com/ncb000gt/node-shoutcast.git"
  10 + },
  11 + "licenses": [{"type": "MIT"}],
  12 + "bugs": {"web" : "http://github.com/ncb000gt/node-shoutcast/issues"},
  13 + "scripts": {
  14 + "test": "make test"
  15 + },
  16 + "dependencies": {
  17 + "queuestream": ">=0.0.1",
  18 + "eventemitter2": ">=0.4.0"
  19 + },
  20 + "devDependencies": {
  21 + "nodeunit": ">=0.5.4"
  22 + }
  23 +}
174 shoutcast.js
... ... @@ -1,144 +1,58 @@
1   -var net = require("net")
2   -,fs = require("fs")
3   -,sys = require("sys")
4   -,http = require('http')
5   -,file = require('./lib/file');
6   -
7   -var song_queue = {
8   - songs: [],
9   - idx: 0
  1 +var net = require("net"),
  2 + http = require('http'),
  3 + fs = require('fs'),
  4 + file = require('./lib/file'),
  5 + shoutcast = require('./lib/shoutcast');
  6 +
  7 +var config = {
  8 + name: 'Node Shoutcasts R Us',
  9 + genre: 'Musak',
  10 + url: 'http://github.com/ncb000gt/node-shoutcast/',
  11 + notice: 'I\'m a little teapot...'
10 12 };
11 13
12   -var STATION = {
13   - NAME: 'Node Shoutcasts R Us',
14   - GENRE: 'Musak',
15   - URL: 'http://github.com/ncb000gt/node-shoutcast/',
16   - NOTICE: 'I\'m a little teapot...'
17   -};
  14 +var stations = [];
18 15
19   -var config = require('/usr/local/etc/node-shoutcast/config');
20   -sys.debug('config: ' + JSON.stringify(config));
21   -if ('STATION' in config) {
22   - var config_station = config.STATION;
23   - if ('NAME' in config_station) {
24   - STATION.NAME = config_station.NAME;
25   - } else if ('GENRE' in config_station) {
26   - STATION.GENRE = config_station.GENRE;
27   - } else if ('URL' in config_station) {
28   - STATION.URL = config_station.URL;
29   - } else if ('NOTICE' in config_station) {
30   - STATION.NOTICE = config_station.NOTICE;
31   - }
32   -}
  16 +var first = shoutcast.Station(config);
  17 +stations.push(first);
33 18
34   -var PLAYLIST = config.PLAYLIST;
35   -var QUEUE_SIZE = config.QUEUE_SIZE || 10;
  19 +var dir = process.argv[2];
36 20
37   -sys.debug('PLAYLIST: ' + PLAYLIST);
38   -var stats = fs.statSync(PLAYLIST);
  21 +console.log('dir: ' + dir);
  22 +var stats = fs.statSync(dir);
39 23 if (stats.isDirectory()) {
40   - file.walkSync(PLAYLIST, function(p, dirs, files) {
41   - var len = files.length;
42   - for (var i = 0; i < len; i++) {
43   - var file = files[i];
44   - if (file.indexOf('.mp3') >= 0) {
45   - song_queue.songs.push(p+'/'+file);
46   - }
47   - if (song_queue.songs.length > QUEUE_SIZE) {
48   - break;
49   - }
50   - }
51   - });
  24 + file.walkSync(dir, function(p, dirs, files) {
  25 + var len = files.length;
  26 + for (var i = 0; i < len; i++) {
  27 + var f = files[i];
  28 +
  29 + if (f.indexOf('.mp3') >= 0) {
  30 + first.addTrack({file: p+'/'+f});
  31 + }
  32 + }
  33 + });
52 34 }
53 35
54   -var headers = {
55   - 'icy-notice1': STATION.NOTICE,
56   - 'icy-notice2': "NodeJS Streaming Shoutcast Server/v0.1",
57   - 'icy-name': STATION.NAME,
58   - 'icy-genre': STATION.GENRE,
59   - 'icy-url': STATION.URL,
60   - 'Content-Type': 'audio/mpegurl',
61   - 'icy-pub':'0',
62   - 'icy-br':'56',
63   - 'icy-metaint': '0'//1024'
64   -};
65   -
66   -var currentSong = null;
67   -var filetoread = "/home/ncampbell/Music/Amazon MP3/David Guetta/One Love (Deluxe Version)/Memories (Feat Kid Cudi).mp3";
68   -
69   -var bytesOut = 0;
70   -
71 36 http.createServer(
72   - function (req, res) {
73   - var o = req.headers;
74   - for (var p in o) {
75   - sys.puts(p + ': ' + o[p]);
76   - }
77   -
78   - sys.puts('Starting stream...');
79   -
80   - res.writeHead(200, headers);
81   -
82   - //TODO: pop song and make read song as a stream separate from the request, requests should just tie into the already running stream
83   - var song = song_queue.songs[song_queue.idx];
84   - sys.debug('song: ' + song);
85   - var fStream = fs.createReadStream(song, {bufferSize:1024});
86   -
87   - pump(fStream, res, function() { sys.puts("No more data. Closing."); });
88   -
89   - }).listen(7000);
  37 + function (req, res) {
  38 + first.connect(res, function() {
  39 + console.log("Stream ended?");
  40 + });
  41 + }).listen(7000);
  42 +first.start();
90 43
91 44 setInterval(function() {
92   - sys.debug('Total Bytes Written: ' + bytesOut);
93   - }, 3000);
94   -
95   -sys.puts('Server running at http://0.0.0.0:7000/');
96   -
97   -function pump(readStream, writeStream, callback) {
98   - var callbackCalled = false;
99   -
100   - function call (a, b, c) {
101   - if (callback && !callbackCalled) {
102   - callback(a, b, c);
103   - callbackCalled = true;
104   - }
  45 + var total = 0;
  46 + var played = 0;
  47 +
  48 + var len = stations.length;
  49 + for (var idx = 0; idx < len; idx++) {
  50 + total += stations[idx].stats.bytesWritten;
  51 + played += stations[idx].stats.numPlayed;
105 52 }
106 53
107   - if (!readStream.pause) readStream.pause = function () {readStream.emit("pause")};
108   - if (!readStream.resume) readStream.resume = function () {readStream.emit("resume")};
109   -
110   - readStream.addListener("data", function (chunk) {
111   - bytesOut+=chunk.length;
112   - if (writeStream.write(chunk) === false) readStream.pause();
113   - });
114   -
115   - writeStream.addListener("pause", function () {
116   - readStream.pause();
117   - });
118   -
119   - writeStream.addListener("drain", function () {
120   - readStream.resume();
121   - });
122   -
123   - writeStream.addListener("resume", function () {
124   - readStream.resume();
125   - });
126   -
127   - readStream.addListener("end", function () {
128   - writeStream.end();
129   - });
130   -
131   - readStream.addListener("close", function () {
132   - call();
133   - });
134   -
135   - readStream.addListener("error", function (err) {
136   - writeStream.end();
137   - call(err);
138   - });
  54 + console.log('Total Bytes Written: ' + total);
  55 + console.log('Total Tracks Played: ' + played);
  56 +}, 20000);
139 57
140   - writeStream.addListener("error", function (err) {
141   - readStream.destroy();
142   - call(err);
143   - });
144   -};
  58 +console.log('Server running at http://0.0.0.0:7000/');

0 comments on commit da56b25

Please sign in to comment.
Something went wrong with that request. Please try again.