├── src ├── README.md ├── .nodemonignore ├── app │ ├── views │ │ ├── partials │ │ │ ├── log.ejs │ │ │ ├── users.ejs │ │ │ ├── socket-io.ejs │ │ │ ├── play-queue.ejs │ │ │ ├── banner.ejs │ │ │ ├── currently-playing.ejs │ │ │ ├── room.ejs │ │ │ ├── app-stats.ejs │ │ │ ├── footer.ejs │ │ │ ├── chat-form.ejs │ │ │ ├── noise-box-stats.ejs │ │ │ ├── play-mode-form.ejs │ │ │ └── username-form.ejs │ │ ├── home.ejs │ │ ├── host.ejs │ │ ├── layout.ejs │ │ └── user.ejs │ ├── model │ │ ├── NBUserCollection.js │ │ ├── NBHostModel.js │ │ ├── NBCollection.js │ │ ├── NBHomeCollection.js │ │ ├── NBHostCollection.js │ │ ├── NBTrackCollection.js │ │ ├── NBTrackModel.js │ │ ├── NBUserModel.js │ │ ├── NBHomeModel.js │ │ ├── NBLogModel.js │ │ ├── NBModel.js │ │ └── AppModel.js │ ├── middleware │ │ ├── cache-nuker.js │ │ ├── stats.js │ │ ├── template-options.js │ │ └── error.js │ ├── controllers │ │ ├── boot.js │ │ ├── test.js │ │ ├── home.js │ │ ├── user.js │ │ ├── host.js │ │ └── abstract.js │ └── lib │ │ ├── log.js │ │ ├── noise-box-reaper.js │ │ ├── sfx-metadata-parser.js │ │ └── built-in-sfx.js ├── config.js.sample ├── CBD1F10D3FD257184D2D96073BFF07E7.txt ├── public │ ├── noise │ │ └── alive.mp3 │ ├── img │ │ ├── bg-home │ │ │ ├── bg_home_01.jpg │ │ │ ├── bg_home_02.jpg │ │ │ ├── bg_home_03.jpg │ │ │ ├── bg_home_04.jpg │ │ │ ├── bg_home_05.jpg │ │ │ ├── bg_home_06.jpg │ │ │ ├── bg_home_07.jpg │ │ │ ├── bg_home_08.jpg │ │ │ ├── bg_home_09.jpg │ │ │ ├── bg_home_10.jpg │ │ │ ├── bg_home_11.jpg │ │ │ ├── bg_home_12.jpg │ │ │ ├── bg_home_13.jpg │ │ │ └── bg_home_14.jpg │ │ ├── logo_noisebox_banner_24.png │ │ └── logo_noisebox_large_24.png │ ├── font │ │ └── font-awesome │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ ├── less │ │ ├── style.less │ │ ├── _h5bp-top.less │ │ ├── _h5bp-bottom.less │ │ ├── _elements.less │ │ ├── _normalize.less │ │ ├── _font-awesome.less │ │ └── _noisebox.less │ ├── html │ │ └── error.html │ ├── js │ │ ├── HomeClient.js │ │ ├── main.js │ │ ├── nb.js │ │ ├── lib │ │ │ ├── sji.js │ │ │ ├── jquery.stickysectionheaders.js │ │ │ ├── timeago.js │ │ │ ├── bootstrap-tabs.js │ │ │ └── bootstrap-scrollspy.js │ │ ├── const.js │ │ ├── HostClient.js │ │ ├── UserClient.js │ │ └── AbstractClient.js │ └── connection-type │ │ └── index.html ├── index.js ├── package.json └── server.js ├── scripts ├── requirements.txt ├── print_tags.py ├── save_length.py └── set_length_all_files.py ├── .gitignore └── README.md /src/README.md: -------------------------------------------------------------------------------- 1 | :) -------------------------------------------------------------------------------- /src/.nodemonignore: -------------------------------------------------------------------------------- 1 | /public/ -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | mutagen==1.21 2 | -------------------------------------------------------------------------------- /src/app/views/partials/log.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/app/views/partials/users.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/app/views/partials/socket-io.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config.js.sample: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sfxDir: "./public/sfx" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/views/partials/play-queue.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules/ 3 | dist/ 4 | src/public/sfx 5 | src/config.js 6 | -------------------------------------------------------------------------------- /src/CBD1F10D3FD257184D2D96073BFF07E7.txt: -------------------------------------------------------------------------------- 1 | 0D1DEB79975A025F201A85D1A650913EF4E29D6C 2 | comodoca.com -------------------------------------------------------------------------------- /src/public/noise/alive.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/noise/alive.mp3 -------------------------------------------------------------------------------- /src/app/views/partials/banner.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_01.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_02.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_03.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_04.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_05.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_06.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_07.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_08.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_09.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_10.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_11.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_12.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_13.jpg -------------------------------------------------------------------------------- /src/public/img/bg-home/bg_home_14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/bg-home/bg_home_14.jpg -------------------------------------------------------------------------------- /src/public/img/logo_noisebox_banner_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/logo_noisebox_banner_24.png -------------------------------------------------------------------------------- /src/public/img/logo_noisebox_large_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/img/logo_noisebox_large_24.png -------------------------------------------------------------------------------- /src/public/font/font-awesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/font/font-awesome/FontAwesome.otf -------------------------------------------------------------------------------- /src/public/font/font-awesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/font/font-awesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/public/font/font-awesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/font/font-awesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/app/views/partials/currently-playing.ejs: -------------------------------------------------------------------------------- 1 |
2 |

Currently playing:

3 | 5 |
-------------------------------------------------------------------------------- /src/public/font/font-awesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/noise-box/dev/src/public/font/font-awesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/public/less/style.less: -------------------------------------------------------------------------------- 1 | @import "_font-awesome.less"; 2 | 3 | @import "_normalize.less"; 4 | 5 | @import "_h5bp-top.less"; 6 | 7 | @import "_noisebox.less"; 8 | 9 | @import "_h5bp-bottom.less"; -------------------------------------------------------------------------------- /src/app/model/NBUserCollection.js: -------------------------------------------------------------------------------- 1 | var Backbone = require("backbone"); 2 | var NBUserModel = require("./NBUserModel"); 3 | 4 | var NBUserCollection = module.exports = Backbone.Collection.extend({ 5 | 6 | model : NBUserModel 7 | }); -------------------------------------------------------------------------------- /src/app/views/partials/room.ejs: -------------------------------------------------------------------------------- 1 |
2 |

<%= id %>

3 |

Share this room

4 |

<%= userURL %>

5 |
-------------------------------------------------------------------------------- /src/app/model/NBHostModel.js: -------------------------------------------------------------------------------- 1 | var Backbone = require("backbone"); 2 | 3 | var NBHostModel = module.exports = Backbone.Model.extend({ 4 | 5 | defaults : { 6 | parentNoiseBoxID : "" 7 | }, 8 | 9 | initialize : function () { 10 | 11 | } 12 | }); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * index.js 4 | * 5 | * Starts up the server 6 | */ 7 | 8 | var NBApp = require("./server"); 9 | 10 | var port = process.env.PORT || process.argv[2]; 11 | var env = process.env.NODE_ENV || 'DEV'; 12 | 13 | NBApp.startApp(port, env); -------------------------------------------------------------------------------- /src/app/views/partials/app-stats.ejs: -------------------------------------------------------------------------------- 1 |
2 | 6 |
-------------------------------------------------------------------------------- /scripts/print_tags.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | 3 | from mutagen.easyid3 import EasyID3 4 | from mutagen.mp3 import MP3 5 | 6 | pp = pprint.PrettyPrinter(indent=4) 7 | audio = MP3("/Users/pxg/Sites/noisebox/src/public/sfx/_misc/hand.mp3") 8 | pp.pprint(EasyID3.valid_keys.keys()) 9 | pp.pprint(audio) 10 | -------------------------------------------------------------------------------- /src/app/views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/model/NBCollection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * NBCollection.js 4 | * 5 | * NBModel Backbone Collection. 6 | */ 7 | 8 | var Backbone = require("backbone"); 9 | var NBModel = require("./NBModel"); 10 | 11 | var NBCollection = module.exports = Backbone.Collection.extend({ 12 | 13 | model : NBModel 14 | }); -------------------------------------------------------------------------------- /src/app/views/partials/chat-form.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | Chat 4 | 6 |
7 |
-------------------------------------------------------------------------------- /src/public/html/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {title} 5 | 6 | 7 | 8 |
9 |

{title}

10 |

{error}

11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /src/app/views/partials/noise-box-stats.ejs: -------------------------------------------------------------------------------- 1 |
2 |

3 | <%= id %> stats: 4 | <%= numHosts %> hosts, 5 | <%= numUsers %> users 6 |

7 |
-------------------------------------------------------------------------------- /src/app/model/NBHomeCollection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * NBHomeCollection.js 4 | * 5 | * NBHomeModel Backbone Collection. 6 | */ 7 | 8 | var Backbone = require("backbone"); 9 | var NBHomeModel = require("./NBHomeModel"); 10 | 11 | var NBHomeCollection = module.exports = Backbone.Collection.extend({ 12 | 13 | model : NBHomeModel 14 | }); -------------------------------------------------------------------------------- /src/app/model/NBHostCollection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * NBHostCollection.js 4 | * 5 | * NBHostModel Backbone Collection. 6 | */ 7 | 8 | var Backbone = require("backbone"); 9 | var NBHostModel = require("./NBHostModel"); 10 | 11 | var NBHostCollection = module.exports = Backbone.Collection.extend({ 12 | 13 | model : NBHostModel 14 | }); -------------------------------------------------------------------------------- /src/app/model/NBTrackCollection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * NBTrackCollection.js 4 | * 5 | * NBTrackModel Backbone Collection. 6 | */ 7 | 8 | var Backbone = require("backbone"); 9 | var NBTrackModel = require("./NBTrackModel"); 10 | 11 | var NBTrackCollection = module.exports = Backbone.Collection.extend({ 12 | 13 | model : NBTrackModel 14 | }); -------------------------------------------------------------------------------- /src/app/middleware/cache-nuker.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | 3 | return function (req,res,next) { 4 | res.header("Cache-Control","no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0"); 5 | res.header("Expires","0"); 6 | res.header("Pragma","no-cache"); 7 | next(); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /scripts/save_length.py: -------------------------------------------------------------------------------- 1 | from mutagen.mp3 import MP3 2 | from mutagen.easyid3 import EasyID3 3 | import pprint 4 | 5 | file_path = '/Users/pxg/Sites/noisebox/node-musicmetadata/tracks/beast/1.mp3' 6 | pp = pprint.PrettyPrinter(indent=4) 7 | audio = MP3(file_path) 8 | length = audio.info.length 9 | 10 | audio2 = EasyID3(file_path) 11 | audio2["length"] = str(length) 12 | audio2.save() 13 | -------------------------------------------------------------------------------- /src/app/model/NBTrackModel.js: -------------------------------------------------------------------------------- 1 | var Backbone = require("backbone"); 2 | 3 | var NBTrackModel = module.exports = Backbone.Model.extend({ 4 | 5 | defaults : { 6 | track: "", 7 | trackName: "", 8 | trackId: "", 9 | album: "", 10 | user: "", 11 | datetime: "", 12 | played:false 13 | }, 14 | 15 | initialize : function (data) { 16 | } 17 | }); -------------------------------------------------------------------------------- /src/public/js/HomeClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * HomeClient.js 4 | * 5 | * Home page. 6 | */ 7 | 8 | define(function (require) { 9 | 10 | var $ = require("jquery"); 11 | 12 | return Class.extend({ 13 | init : function () { 14 | $(".flash-message p:parent").parent().slideDown(250).delay(5000).slideUp(250); 15 | $("#id").focus(); 16 | } 17 | }); 18 | }); -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noise-box", 3 | "author": "", 4 | "version": "0.0.2", 5 | "private": true, 6 | "dependencies": { 7 | "express": "~3.1.0", 8 | "express-partials": "~0.0.6", 9 | "ejs": "~0.8.3", 10 | "socket.io": "~0.9.13", 11 | "underscore": "~1.4.3", 12 | "backbone": "~0.9.2", 13 | "winston": "~0.6.2", 14 | "dive": "~0.3.0", 15 | "async": "~0.2.5", 16 | "musicmetadata": "~0.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/controllers/boot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * boot.js 4 | * 5 | * Boot route controller, allows the client side code to redirect with a 6 | * flash message displayed after the redirect 7 | */ 8 | 9 | var server = require("./../../server"); 10 | var app = server.app; 11 | var templateOptions = require("./../middleware/template-options"); 12 | 13 | app.get('/boot', templateOptions(), function(req, res, next){ 14 | req.session.flashMessage = req.query.m; 15 | res.redirect("/"); 16 | return; 17 | }); 18 | -------------------------------------------------------------------------------- /src/app/model/NBUserModel.js: -------------------------------------------------------------------------------- 1 | var Backbone = require("backbone"); 2 | 3 | var NBUserModel = module.exports = Backbone.Model.extend({ 4 | 5 | defaults : { 6 | id : "", 7 | parentNoiseBoxID : "", 8 | socket : "", 9 | username : "" 10 | }, 11 | 12 | 13 | initialize : function () { 14 | this.updateUsername(); 15 | }, 16 | 17 | 18 | updateUsername : function(username) { 19 | if (!username) { 20 | username = this.cid.replace("c", "user_"); 21 | } 22 | this.set("username", username); 23 | } 24 | }); -------------------------------------------------------------------------------- /src/app/views/partials/play-mode-form.ejs: -------------------------------------------------------------------------------- 1 |
2 | 6 | 10 |
-------------------------------------------------------------------------------- /src/app/model/NBHomeModel.js: -------------------------------------------------------------------------------- 1 | var Backbone = require("backbone"), 2 | fs = require("fs"); 3 | 4 | var NBHomeModel = module.exports = Backbone.Model.extend({ 5 | 6 | defaults : { 7 | }, 8 | 9 | initialize : function () { 10 | }, 11 | 12 | 13 | selectRandomBg : function () { 14 | var bgCount, rand, list; 15 | 16 | list = fs.readdirSync("./public/img/bg-home"); 17 | bgCount = list.length; 18 | rand = this.getRandomInt(1,bgCount); 19 | return list[rand]; 20 | }, 21 | 22 | 23 | getRandomInt : function(min, max) { 24 | return Math.floor(Math.random() * (max - min + 1)) + min; 25 | } 26 | }); -------------------------------------------------------------------------------- /src/app/views/home.ejs: -------------------------------------------------------------------------------- 1 |

<%= heading %>

2 |

Broadcasting noise across the internet since 1972

3 |

NoiseBox allows anyone to play sound clips through another computer...

4 |
5 |
6 | Create a room 7 |

Create a room

8 | 9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /src/app/controllers/test.js: -------------------------------------------------------------------------------- 1 | /*global process*/ 2 | /** 3 | * NoiseBox 4 | * home.js 5 | * 6 | * Home route controller. 7 | */ 8 | 9 | var server = require("./../../server"); 10 | var app = server.app; 11 | var io = server.io; 12 | var model = server.model; 13 | var constants = server.constants; 14 | var templateOptions = require("./../middleware/template-options"); 15 | var stats = require("./../middleware/stats"); 16 | var AbstractController = require("./abstract.js"); 17 | var _ = require("underscore"); 18 | 19 | module.exports = function () { 20 | // Only active in the test environment 21 | console.log('test env is killing the server'); 22 | console.log('goodbye...'); 23 | process.exit(); 24 | }; -------------------------------------------------------------------------------- /src/public/connection-type/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Connection Type 6 | 7 | 8 | 9 | 21 | 22 | 23 |

Connection type

24 |

Loading...

25 | 26 | -------------------------------------------------------------------------------- /src/app/lib/log.js: -------------------------------------------------------------------------------- 1 | var winston = require("winston"); 2 | var config = require("../../config"); 3 | var server = require("../../server.js"); 4 | 5 | /** 6 | * Usage: 7 | * 8 | * var log = require("./app/lib/log"); 9 | * 10 | * var meta = {}; 11 | * 12 | * log.info("Some informational message",meta); 13 | * log.warn("Some warning message",meta); 14 | * log.error("Some error message",meta); 15 | * 16 | * "meta" parameter is any object you'd like to inspect/print out along with the 17 | * log message string (uses util.inspect). 18 | */ 19 | 20 | var consoleTransport = new (winston.transports.Console)({ 21 | level: "info", 22 | timestamp: true 23 | }); 24 | 25 | var transports = [consoleTransport]; 26 | 27 | module.exports = new (winston.Logger)({transports:transports}); 28 | -------------------------------------------------------------------------------- /src/app/lib/noise-box-reaper.js: -------------------------------------------------------------------------------- 1 | var server = require("../../server"); 2 | var log = require("./log"); 3 | 4 | var reapInterval = 1000*60*60*12; // Reap empty boxes every 12 hours 5 | var staleness = 1000*60*60*6; // Spare a box from a reaping if its had activity within the last 6 hours 6 | 7 | module.exports = function () { 8 | setInterval(function () { 9 | var toRemove = []; 10 | server.model.noiseBoxes.each(function (noiseBox) { 11 | if ( Date.now() > (noiseBox.activityTimestamp+staleness) ) { 12 | toRemove.push(noiseBox); 13 | } 14 | }); 15 | if ( toRemove.length > 0 ) { 16 | log.info("now reaping "+toRemove.length+" stale boxes"); 17 | } 18 | toRemove.forEach(function (noiseBox) { 19 | server.model.noiseBoxes.remove(noiseBox); 20 | }); 21 | },reapInterval); 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/views/partials/username-form.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Update username <%= username %>

4 |
5 | 6 |
7 |
8 | Update username 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
-------------------------------------------------------------------------------- /src/public/js/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * main.js 4 | * 5 | * Clientside JS entry point. 6 | */ 7 | 8 | (function () { 9 | 10 | "use strict"; 11 | 12 | require.config({ 13 | 14 | paths : { 15 | jquery : "lib/jquery", 16 | underscore : "lib/underscore", 17 | sji : "lib/sji", 18 | constants : "const", 19 | scrollspy: "lib/bootstrap-scrollspy", 20 | tabs: "lib/bootstrap-tabs", 21 | stickyheaders: "lib/jquery.stickysectionheaders", 22 | timeago: "lib/timeago" 23 | }, 24 | shim : { 25 | jquery : { 26 | exports : "$" 27 | }, 28 | sji : { 29 | exports : "Class" 30 | }, 31 | underscore : { 32 | exports : "_" 33 | }, 34 | constants : { 35 | exports : "Const" 36 | } 37 | } 38 | }); 39 | 40 | require(["nb","sji"],function (nb) { 41 | nb.init(); 42 | }); 43 | }()); -------------------------------------------------------------------------------- /src/app/middleware/stats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * stats.js 4 | * 5 | * Middleware for adding app stats to the res object. 6 | */ 7 | 8 | var server = require("./../../server"); 9 | var model = server.model; 10 | 11 | module.exports = function () { 12 | 13 | return function (req,res,next) { 14 | 15 | var numNoiseBoxes = model.noiseBoxes.length; 16 | var numClients = model.getNumConnectedClients(); 17 | 18 | var numHosts = 0; 19 | var numUsers = 0; 20 | 21 | if ( typeof req.params.id !== "undefined") { 22 | 23 | model.noiseBoxes.each(function (nb) { 24 | 25 | if ( req.params.id === nb.get("id") ) { 26 | 27 | numHosts = nb.hosts.length; 28 | numUsers = nb.users.length; 29 | } 30 | }); 31 | } 32 | 33 | res.extendTemplateOptions({ 34 | 35 | numNoiseBoxes : numNoiseBoxes, 36 | numClients : numClients, 37 | numHosts : numHosts, 38 | numUsers : numUsers 39 | }); 40 | 41 | next(); 42 | }; 43 | }; -------------------------------------------------------------------------------- /src/app/middleware/template-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * templateOptions.js 4 | * 5 | * Middleware for adding a default template options object to the res object. Also exposes a 6 | * flashMessage property which can be used to pass one-time messages to the next request via 7 | * sessions. 8 | */ 9 | 10 | var _ = require("underscore"); 11 | var server = require("./../../server"); 12 | var constants = server.constants; 13 | var config = require("./../../config"); 14 | 15 | 16 | module.exports = function () { 17 | 18 | return function (req,res,next) { 19 | 20 | res.templateOptions = { 21 | 22 | clientType : "", 23 | title : constants.APP_TITLE, 24 | heading : constants.APP_TITLE, 25 | host : 'http://' + req.headers.host, 26 | env : server.env, 27 | id : "", 28 | flashMessage : typeof req.session.flashMessage === "undefined" ? "" : req.session.flashMessage 29 | }; 30 | 31 | req.session.flashMessage = undefined; 32 | 33 | res.extendTemplateOptions = function (o) { 34 | 35 | this.templateOptions = _.extend(this.templateOptions,o); 36 | }; 37 | 38 | next(); 39 | }; 40 | }; -------------------------------------------------------------------------------- /src/public/js/nb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * nb.js 4 | * 5 | * Main NoiseBox client module. Creates an appropriate client class instance to handle the 6 | * different types of client. 7 | */ 8 | 9 | define(["HomeClient","HostClient","UserClient","constants","jquery"], function (HomeClient,HostClient,UserClient,Const) { 10 | 11 | var init = function () { 12 | $('.popup').click(function(){ 13 | window.open(this.href, 'window name', 'window settings'); 14 | return false; 15 | }); 16 | 17 | $(function () { 18 | 19 | var client; 20 | var clientType = $("body").attr("id"); 21 | 22 | switch ( clientType ) { 23 | 24 | case Const.TYPE_HOME: 25 | client = new HomeClient(); 26 | break; 27 | case Const.TYPE_HOST: 28 | client = new HostClient(); 29 | break; 30 | case Const.TYPE_USER: 31 | client = new UserClient(); 32 | break; 33 | default: 34 | throw(new Error("Client type '"+clientType+"' not recognised")); 35 | } 36 | }); 37 | }; 38 | 39 | return { 40 | 41 | init : init 42 | }; 43 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #noise-box 2 | ========= 3 | 4 | Allow anyone to start their own noise box 'room', with 'hosts' that'll play the noise, and 'clients' that can select tracks, which will then be played via their specific host(s). 5 | 6 | Once a client logs in to the room, they see a list of tracks, can preview or broadcast a clip, that'll then get queued and played on the host machine. The noise will be played via the html5 audio api. 7 | 8 | ###Requirements 9 | 10 | * Node v0.8+ 11 | * npm 12 | 13 | ###Installation 14 | 15 | ``` 16 | git clone git@github.com:GuntLondon/noise-box.git 17 | cd noise-box/src 18 | npm install 19 | ``` 20 | 21 | A `config.js` file also needs to be present in the `src` folder. There is a sample version called `config.js.sample`. 22 | 23 | ###sfx 24 | 25 | Create (or symlink) a directory in /src/public and call it 'sfx' - place mp3 files in here to have them listed. 26 | 27 | ###less 28 | 29 | To update the styles, make changes to the style.less file 30 | 31 | To recompile the CSS file, first install less: 32 | 33 | ``` 34 | npm install less 35 | ``` 36 | 37 | Then from the src directory, run the following command: 38 | 39 | ``` 40 | ./node_modules/less/bin/lessc public/less/style.less public/css/style.css -x 41 | ``` 42 | 43 | 44 | ###Usage 45 | 46 | Fire up the noise box server by switching to the ```src``` directory and running ```node server.js``` 47 | 48 | Browse to http://localhost:7001 49 | 50 | Enjoy! -------------------------------------------------------------------------------- /src/public/js/lib/sji.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple JavaScript Inheritance 3 | * By John Resig 4 | * MIT Licensed. 5 | * 6 | * http://ejohn.org/blog/simple-javascript-inheritance/ 7 | */ 8 | (function(){ 9 | var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 10 | this.Class = function(){}; 11 | Class.extend = function(prop) { 12 | var _super = this.prototype; 13 | initializing = true; 14 | var prototype = new this(); 15 | initializing = false; 16 | for (var name in prop) { 17 | prototype[name] = typeof prop[name] == "function" && 18 | typeof _super[name] == "function" && fnTest.test(prop[name]) ? 19 | (function(name, fn){ 20 | return function() { 21 | var tmp = this._super; 22 | this._super = _super[name]; 23 | var ret = fn.apply(this, arguments); 24 | this._super = tmp; 25 | return ret; 26 | }; 27 | })(name, prop[name]) : 28 | prop[name]; 29 | } 30 | function Class() { 31 | if ( !initializing && this.init ) 32 | this.init.apply(this, arguments); 33 | } 34 | Class.prototype = prototype; 35 | Class.prototype.constructor = Class; 36 | Class.extend = arguments.callee; 37 | return Class; 38 | }; 39 | })(); -------------------------------------------------------------------------------- /src/app/views/host.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% include partials/banner %> 3 | <% include partials/room %> 4 | <% include partials/currently-playing %> 5 |
6 | 7 |
8 |
9 | 10 |
11 | 12 | 17 | 18 |
19 |
20 |
21 | <% include partials/log %> 22 |
23 |
24 | 25 |
26 |
27 | <% include partials/play-queue %> 28 |
29 |
30 | 31 |
32 |
33 | <% include partials/users %> 34 |
35 |
36 |
37 |
38 |
39 |
40 | <% include partials/socket-io %> 41 | <% include partials/footer %> -------------------------------------------------------------------------------- /src/app/controllers/home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * home.js 4 | * 5 | * Home route controller. 6 | */ 7 | 8 | var server = require("./../../server"); 9 | var app = server.app; 10 | var io = server.io; 11 | var model = server.model; 12 | var constants = server.constants; 13 | var templateOptions = require("./../middleware/template-options"); 14 | var stats = require("./../middleware/stats"); 15 | var log = require("../lib/log"); 16 | var fs = require("fs"); 17 | 18 | module.exports = function () { 19 | 20 | // Map route to middleware and rendering function: 21 | 22 | app.get("/",templateOptions(),stats(),function (req,res) { 23 | 24 | res.extendTemplateOptions({ 25 | 26 | clientType:constants.TYPE_HOME, 27 | bg: selectRandomBg() 28 | }); 29 | 30 | res.render(constants.TYPE_HOME,res.templateOptions); 31 | }); 32 | 33 | // prove we own the domain for Gandi SSL cert 34 | var ssl_confirm_file = 'CBD1F10D3FD257184D2D96073BFF07E7.txt'; 35 | app.get('/' + ssl_confirm_file, function (req,res) { 36 | fs.readFile('./' + ssl_confirm_file, function(err, data) { 37 | if (err) throw err; 38 | res.writeHead(200, {'Content-Type': 'text/plain'}); 39 | res.end(data); 40 | }); 41 | }); 42 | 43 | 44 | /* 45 | * Select a bg image from those available in the folder 46 | */ 47 | function selectRandomBg () { 48 | var homeModel = model.getHomeModel(); 49 | return homeModel.selectRandomBg(); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/app/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= title %> 6 | 7 | 8 | 9 | <% if (locals.bg) { %> 10 | 15 | <% } %> 16 | 17 | 18 | <% if (flashMessage.length > 0) { %> 19 |
20 |

<%= flashMessage %>

21 |
22 | <% } %> 23 | 24 |
25 |
26 | <%- body %> 27 |
28 |
29 | 30 | 31 | 32 | 42 | 43 | -------------------------------------------------------------------------------- /src/public/js/lib/jquery.stickysectionheaders.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Sticky Section Headers 3 | * 4 | * M O D I F I E D 5 | * 6 | * Copyright (c) 2012 Florian Plank (http://www.polarblau.com/) 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * and GPL (GPL-LICENSE.txt) licenses. 9 | * 10 | */ 11 | 12 | (function($){ 13 | $.fn.stickySectionHeaders = function(options) { 14 | 15 | var settings = $.extend({ 16 | stickyClass : 'sticky', 17 | headlineSelector: 'strong', 18 | childEl : 'li' 19 | }, options); 20 | 21 | return $(this).each(function() { 22 | var $this = $(this); 23 | $(this).bind('scroll.sticky', function(e) { 24 | var $scrollable = $(this); 25 | $(this).find(settings.childEl).each(function() { 26 | var $this = $(this), 27 | top = $this.position().top, 28 | height = $this.outerHeight(), 29 | $head = $this.find(settings.headlineSelector), 30 | headHeight = $head.outerHeight(); 31 | if (top < 0) { 32 | $this.addClass(settings.stickyClass).css('paddingTop', headHeight); 33 | $head.css({ 34 | 'top' : (height + top < headHeight) ? (headHeight - (top + height)) * -1 : '' 35 | }); 36 | } else { 37 | $this.removeClass(settings.stickyClass).css('paddingTop', ''); 38 | } 39 | }); 40 | }); 41 | }); 42 | }; 43 | 44 | /* A little helper to calculate the sum of different 45 | * CSS properties 46 | * 47 | * EXAMPLE: 48 | * $('#my-div').cssSum('paddingLeft', 'paddingRight'); 49 | */ 50 | $.fn.cssSum = function() { 51 | var $self = $(this), sum = 0; 52 | $(arguments).each(function(i, e) { 53 | sum += parseInt($self.css(e) || 0, 10); 54 | }); 55 | return sum; 56 | }; 57 | 58 | })(jQuery); 59 | -------------------------------------------------------------------------------- /src/public/less/_h5bp-top.less: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 Boilerplate 3 | * 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html, 14 | button, 15 | input, 16 | select, 17 | textarea { 18 | color: #222; 19 | } 20 | 21 | body { 22 | font-size: 1em; 23 | line-height: 1.4; 24 | } 25 | 26 | /* 27 | * Remove text-shadow in selection highlight: h5bp.com/i 28 | * These selection rule sets have to be separate. 29 | * Customize the background color to match your design. 30 | */ 31 | 32 | ::-moz-selection { 33 | background: #b3d4fc; 34 | text-shadow: none; 35 | } 36 | 37 | ::selection { 38 | background: #b3d4fc; 39 | text-shadow: none; 40 | } 41 | 42 | /* 43 | * A better looking default horizontal rule 44 | */ 45 | 46 | hr { 47 | display: block; 48 | height: 1px; 49 | border: 0; 50 | border-top: 1px solid #ccc; 51 | margin: 1em 0; 52 | padding: 0; 53 | } 54 | 55 | /* 56 | * Remove the gap between images and the bottom of their containers: h5bp.com/i/440 57 | */ 58 | 59 | img { 60 | vertical-align: middle; 61 | } 62 | 63 | /* 64 | * Remove default fieldset styles. 65 | */ 66 | 67 | fieldset { 68 | border: 0; 69 | margin: 0; 70 | padding: 0; 71 | } 72 | 73 | /* 74 | * Allow only vertical resizing of textareas. 75 | */ 76 | 77 | textarea { 78 | resize: vertical; 79 | } 80 | 81 | /* ========================================================================== 82 | Chrome Frame prompt 83 | ========================================================================== */ 84 | 85 | .chromeframe { 86 | margin: 0.2em 0; 87 | background: #ccc; 88 | color: #000; 89 | padding: 0.2em 0; 90 | } -------------------------------------------------------------------------------- /src/public/js/const.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * const.js 4 | * 5 | * App constants. This JS file is used both on the client and the server, so we 6 | * conditionally export for either RequireJS or Node/CommonJS after detecting 7 | * the environment. 8 | */ 9 | 10 | var Const = { 11 | 12 | // Environments 13 | 14 | PROD : "production", 15 | DEV : "development", 16 | TEST : "testing", 17 | 18 | // Default values 19 | 20 | DEFAULT_PORT : 7001, 21 | APP_TITLE : "NoiseBox", 22 | 23 | // NoiseBox client types 24 | 25 | TYPE_HOME : "home", 26 | TYPE_HOST : "host", 27 | TYPE_USER : "user", 28 | 29 | // Model events 30 | 31 | NOISEBOX_ADDED : "noiseBoxAdded", 32 | NOISEBOX_REMOVED : "noiseBoxRemoved", 33 | HOME_ADDED : "homeAdded", 34 | HOME_REMOVED : "homeRemoved", 35 | HOST_ADDED : "hostAdded", 36 | HOST_REMOVED : "hostRemoved", 37 | USER_ADDED : "userAdded", 38 | USER_UPDATED : "userUpdated", 39 | USER_REMOVED : "userRemoved", 40 | TRACK_ADDED : "trackAdded", 41 | TRACK_REMOVED : "trackRemoved", 42 | LOG_UPDATED : "logUpdated", 43 | 44 | // Socket.io events 45 | 46 | SERVER_SOCKET_CONNECT : "connect", 47 | CLIENT_SOCKET_CONNECTION : "connection", 48 | SOCKET_DISCONNECT : "disconnect", 49 | 50 | // Custom server > client socket events 51 | 52 | SERVER_APP_STATS_UPDATED : "appStatsUpdated", 53 | SERVER_NOISE_BOX_STATS_UPDATED : "noiseBoxStatsUpdated", 54 | SERVER_ADD_TRACK : "addTrack", 55 | SERVER_REMOVE_TRACK : "removeTrack", 56 | SERVER_BOOT_CLIENT : 'bootClient', 57 | 58 | // Custom client > server socket events 59 | 60 | HOME_CONNECT : "homeConnect", 61 | HOST_CONNECT : "hostConnect", 62 | USER_CONNECT : "userConnect", 63 | USER_CHANGED : "userChanged", 64 | USER_CLICKED_TRACK : "userClickedTrack", 65 | USER_NAME_UPDATE : "userNameUpdate", 66 | HOST_TRACK_PLAYING : "trackPlaying", 67 | HOST_TRACK_COMPLETE : "trackComplete", 68 | CHAT_MESSAGE_SENT : "chatMessageSent" 69 | 70 | }; 71 | 72 | if ( typeof module !== "undefined" && module.exports ) { 73 | module.exports = Const; 74 | } else if ( typeof define === "function" ) { 75 | define(Const); 76 | } 77 | -------------------------------------------------------------------------------- /scripts/set_length_all_files.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from mutagen.easyid3 import EasyID3 3 | from mutagen.id3 import ID3, TIT2 4 | from mutagen.mp3 import MP3 5 | import datetime 6 | import math 7 | import os 8 | 9 | 10 | def set_length_all_files(path): 11 | for root, dirs, files in os.walk(path, topdown=False): 12 | for name in files: 13 | file_path = os.path.join(root, name) 14 | fileExtension = os.path.splitext(file_path)[1] 15 | if fileExtension == '.mp3': 16 | set_id3_length(file_path) 17 | 18 | 19 | def format_length(length): 20 | # Round up so we don't have tracks with 0 seconds 21 | length = int(math.ceil(length)) 22 | # format the length hh:mm:ss 23 | if length >= 60: 24 | length = str(datetime.timedelta(seconds=length)) 25 | # format time further 26 | if length[0:3] == '0:0': 27 | length = length[3:] 28 | elif length[0:2] == '0:': 29 | length = length[2:] 30 | return length 31 | 32 | 33 | def set_id3_length(file_path): 34 | audio = MP3(file_path) 35 | length = format_length(audio.info.length) 36 | 37 | # Exception will be thrown if tags don't exist 38 | try: 39 | audio_id3 = EasyID3(file_path) 40 | except Exception as e: 41 | # Create ID3 tags 42 | print 'EXCEPTION %s %s' % (file_path, e) 43 | tags = ID3() 44 | # below line is needed for tags to be created 45 | tags['TIT2'] = TIT2(encoding=3, text='') 46 | tags.save(file_path) 47 | audio_id3 = EasyID3(file_path) 48 | 49 | audio_id3['length'] = str(length) 50 | # title is filename (remove .mp3 remove underscores) 51 | dir, file = os.path.split(file_path) 52 | file = os.path.splitext(file)[0] 53 | audio_id3['title'] = file.replace('_', ' ').replace('-', ' ') 54 | # album is folder name 55 | album = os.path.split(dir)[1] 56 | audio_id3['album'] = album.replace('_', ' ').replace('-', ' ') 57 | # remove artist 58 | audio_id3['artist'] = '' 59 | # remove genre 60 | audio_id3['genre'] = '' 61 | audio_id3.save() 62 | 63 | #TODO: change this path so it's relative 64 | set_length_all_files('/Users/pxg/Sites/noisebox/src/public/sfx/') 65 | -------------------------------------------------------------------------------- /src/app/lib/sfx-metadata-parser.js: -------------------------------------------------------------------------------- 1 | var MusicMetaData = require("musicmetadata"); 2 | var config = require("../../config"); 3 | var fs = require("fs"); 4 | var async = require("async"); 5 | var log = require("./log"); 6 | 7 | var sfxDir = config.sfxDir || "./public/sfx"; 8 | 9 | /** 10 | * Loops through an sfx data structure and parses each file for MP3 metadata. If 11 | * it is found its added to the data for each file. 12 | */ 13 | module.exports = function (sfx,cb) { 14 | if ( sfx[0].parsed ) { 15 | cb(null,sfx); 16 | return; 17 | } 18 | var tasks = []; 19 | sfx.forEach(function (dir) { 20 | dir.files.forEach(function (file) { 21 | tasks.push((function (file,dir) { 22 | return function (cb) { 23 | 24 | // uncomment if checking files for length 25 | parseFile(file,dir,cb); 26 | 27 | // comment if checking files for length 28 | //cb(); 29 | }; 30 | }(file,dir))); 31 | }); 32 | }); 33 | async.series(tasks,function (err) { 34 | cb(err,sfx); 35 | }); 36 | console.log("Parsing metadata"); 37 | }; 38 | 39 | function parseFile (file,dir,cb) { 40 | try{ 41 | dir.parsed = true; 42 | var filePath = sfxDir+"/"+dir.name+"/"+file.filename; 43 | //console.log("Checking metadata for file:", filePath); 44 | var parser = new MusicMetaData(fs.createReadStream(filePath)); 45 | parser.once("metadata",function (res) { 46 | file.title = res.title; 47 | file.artist = res.artist.toString(); 48 | file.album = res.album; 49 | }); 50 | parser.once("TLEN",function (res) { 51 | file.duration = res; 52 | }); 53 | parser.once("done",function (err) { 54 | //console.log("Done checking:", filePath); 55 | //if ( err ) log.warn(file.filename+" "+err.toString()); 56 | /* 57 | NOTE: uncommenting the below line crashes the app on pxg's machine: 58 | events.js:48 59 | throw arguments[1]; // Unhandled 'error' event 60 | ^ 61 | Error: EBADF, bad file descriptor 62 | */ 63 | //parser.stream.destroy(); 64 | cb(); 65 | }); 66 | }catch(e){ 67 | console.log(e); 68 | cb(); 69 | return; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app/lib/built-in-sfx.js: -------------------------------------------------------------------------------- 1 | var dive = require("dive"); 2 | var config = require("../../config"); 3 | var fs = require("fs"); 4 | 5 | var sfxDir = config.sfxDir || "./public/sfx"; 6 | var cacheTime = 1000*60*5; 7 | var sfx; 8 | var lastAccess; 9 | 10 | /** 11 | * Async walks the sfx dir and builds a data structure. Data structure is 12 | * rebuilt at most once every cacheTime (currently 5mins). Format: 13 | * 14 | * [ 15 | * { 16 | * name: "dir", 17 | * files: [ 18 | * filename: "example.mp3", 19 | * path: "/sfx/dir/example.mp3", 20 | * duration: 0, 21 | * title: "", 22 | * artist: "", 23 | * album: "" 24 | * ] 25 | * } 26 | * ] 27 | */ 28 | 29 | var sortPropertyAlphabetical = function(property){ 30 | return function(a,b) { 31 | if( a[property] < b[property] ){ 32 | return -1; 33 | } 34 | if( a[property] > b[property] ){ 35 | return 1; 36 | } 37 | return 0; 38 | }; 39 | }; 40 | 41 | module.exports = function (cb) { 42 | if ( sfx && lastAccess && (Date.now()<(lastAccess+cacheTime)) ) { 43 | cb(null,sfx); 44 | } else { 45 | sfx = []; 46 | lastAccess = Date.now(); 47 | dive(sfxDir,{directories:true},function (err,path) { 48 | var pathParts = path.split("/"); 49 | var name = pathParts.pop(); 50 | if ( fs.lstatSync(path).isDirectory() ) { 51 | var dir = { 52 | name: name, 53 | files: [] 54 | }; 55 | sfx.push(dir); 56 | // This will sort the Albums, needed on live server 57 | sfx.sort(sortPropertyAlphabetical('name')); 58 | } else { 59 | var dirName = pathParts.pop(); 60 | sfx.forEach(function (dir) { 61 | if ( dir.name === dirName ) { 62 | dir.files.push({ 63 | name: name.split(".").shift(), 64 | filename: name, 65 | path: "/sfx/"+dirName+"/"+name, 66 | duration: 0, 67 | title: "", 68 | artist: "", 69 | album: "" 70 | }); 71 | // This will sort the Tracks, needed on live server 72 | dir.files.sort(sortPropertyAlphabetical('name')); 73 | } 74 | }); 75 | } 76 | },function () { 77 | cb(null,sfx); 78 | }); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * server.js 4 | * 5 | * Server module. 6 | * Main application entry point. 7 | */ 8 | 9 | var express = require("express"); 10 | var expressPartials = require("express-partials"); 11 | var socketIO = require("socket.io"); 12 | var constants = require("./public/js/const"); 13 | var error = require("./app/middleware/error"); 14 | var AppModel = require("./app/model/AppModel"); 15 | var log = require("./app/lib/log"); 16 | var noiseBoxReaper = require("./app/lib/noise-box-reaper"); 17 | 18 | // Detect environment: 19 | 20 | var port = process.env.PORT || ( process.argv[2] || constants.DEFAULT_PORT ); 21 | var env = process.env.NODE_ENV || constants.DEV; 22 | 23 | // Create and configure an Express app instance: 24 | 25 | var app = express(); 26 | 27 | app.set("port",port); 28 | app.set("views",__dirname+"/app/views"); 29 | app.set("view engine","ejs"); 30 | 31 | // Middleware: 32 | 33 | app.use(express.favicon()); 34 | app.use(express.static(__dirname + "/public")); 35 | app.use(expressPartials()); 36 | app.use(express.cookieParser("N01ZEBOXX")); 37 | app.use(express.cookieSession({key:"sid"})); 38 | app.use(express.bodyParser()); 39 | app.use(app.router); 40 | app.use(error()); 41 | 42 | // Get file list 43 | require("./app/lib/built-in-sfx")(function (err,sfx) { 44 | if ( err ) { 45 | console.log("Error walking SFX dir"); 46 | } 47 | require("./app/lib/sfx-metadata-parser")(sfx,function (err,sfx) { 48 | if ( err ) { 49 | console.log("Error parsing SFX metadata"); 50 | } 51 | }); 52 | }); 53 | 54 | // Start the Express server: 55 | 56 | var server = app.listen(port,function () { 57 | log.info("noise-box server started",{port:port,env:env}); 58 | }); 59 | 60 | // Start socket.io: 61 | 62 | var io = socketIO.listen(server,{"log level":0}); 63 | 64 | // test over long-polling (disable websockets) 65 | //io.set('transports', [ 'xhr-polling' ]); 66 | 67 | // Expose app actors: 68 | 69 | module.exports.constants = constants; 70 | module.exports.app = app; 71 | module.exports.io = io; 72 | module.exports.env = env; 73 | module.exports.port = port; 74 | module.exports.model = new AppModel(); 75 | 76 | // Test controller 77 | 78 | if (env === 'testing') { 79 | var TestController = require("./app/controllers/test"); 80 | app.get("/testing/killme",TestController); 81 | } 82 | 83 | 84 | // Init controllers: 85 | 86 | var HomeController = require("./app/controllers/home"); 87 | var HostController = require("./app/controllers/host"); 88 | var UserController = require("./app/controllers/user"); 89 | var BootController = require("./app/controllers/boot"); 90 | 91 | HomeController(); 92 | HostController.init(); 93 | UserController.init(); 94 | 95 | noiseBoxReaper(); 96 | -------------------------------------------------------------------------------- /src/public/js/lib/timeago.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Calculate relative times 3 | */ 4 | var timeAgo = (function() { 5 | 6 | var 7 | timeout, 8 | interval = 1000, 9 | els = [], 10 | init, count, add, parseTime, calculateTimeDifferenceInSeconds, calculateTimeDifference, now; 11 | 12 | init = function(){ 13 | if (!!timeout) return; 14 | count(); 15 | }; 16 | 17 | add = function($el) { 18 | $el.data('time', parseTime($el.data('datetime'))); 19 | els.push($el); 20 | }; 21 | 22 | count = function() { 23 | var counter, len, $el, time, difference; 24 | 25 | for (counter = 0, len = els.length; counter < len; counter++) { 26 | $el = els[counter]; 27 | time = $el.data('time'); 28 | difference = calculateTimeDifference(time); 29 | $el.text(difference); 30 | } 31 | 32 | timeout = setTimeout(count, interval); 33 | }; 34 | 35 | parseTime = function(time) { 36 | time = $.trim(time); 37 | time = time.replace(/\.\d\d\d+/, ""); 38 | time = time.replace(/-/, "/").replace(/-/, "/"); 39 | time = time.replace(/T/, " ").replace(/Z/, " UTC"); 40 | time = time.replace(/([\+\-]\d\d)\:?(\d\d)/, " $1$2"); 41 | return new Date(time); 42 | }; 43 | 44 | calculateTimeDifferenceInSeconds = function(time) { 45 | var timeDifference; 46 | timeDifference = new Date().getTime() - time.getTime(); 47 | return Math.round((Math.abs(timeDifference) / 1000)); 48 | }; 49 | 50 | calculateTimeDifference = function(time) { 51 | var sec, min; 52 | sec = calculateTimeDifferenceInSeconds(time); 53 | min = Math.round(sec / 60); 54 | 55 | if (sec < 60) { 56 | return sec + "s"; 57 | } else if (min <= 60) { 58 | return min + "m"; 59 | } else if (min <= 1439) { 60 | return (Math.round(min / 60)) + "h"; 61 | } else if (min <= 43199) { 62 | return (Math.round(min / 1440)) + "d"; 63 | } else if (min <= 525599) { 64 | return (Math.round(min / 43200)) + "months"; 65 | } else { 66 | return (Math.round(min / 525600)) + " years"; 67 | } 68 | }; 69 | 70 | now = function() { 71 | if (!Date.prototype.toISOString) { 72 | Date.prototype.toISOString = function() { 73 | function pad(n) { return n < 10 ? '0' + n : n; } 74 | return this.getUTCFullYear() + '-' + 75 | pad(this.getUTCMonth() + 1) + '-' + 76 | pad(this.getUTCDate()) + 'T' + 77 | pad(this.getUTCHours()) + ':' + 78 | pad(this.getUTCMinutes()) + ':' + 79 | pad(this.getUTCSeconds()) + 'Z'; 80 | }; 81 | } 82 | return new Date().toISOString(); 83 | }; 84 | 85 | return { 86 | init : init, 87 | add : add, 88 | now : now 89 | }; 90 | })(); 91 | -------------------------------------------------------------------------------- /src/app/model/NBLogModel.js: -------------------------------------------------------------------------------- 1 | var Backbone = require("backbone"); 2 | var moment = require("./../lib/moment"); 3 | 4 | var NBLogModel = module.exports = Backbone.Model.extend({ 5 | 6 | defaults : { 7 | log : [] 8 | }, 9 | 10 | 11 | initialize : function (nb) { 12 | this.add({ 13 | eventType:"noisebox-created" 14 | }); 15 | 16 | nb.users.on("add",function (nbUserModel) { 17 | this.add({ 18 | eventType:"user-added", 19 | user: nbUserModel.get("username") 20 | }); 21 | },this); 22 | 23 | nb.users.on("change",function (nbUserModel) { 24 | this.add({ 25 | user: nbUserModel.previous("username"), 26 | detail: nbUserModel.get("username"), 27 | eventType: "username-updated" 28 | }); 29 | },this); 30 | 31 | nb.users.on("remove",function (nbUserModel) { 32 | this.add({ 33 | eventType:"user-removed", 34 | user: nbUserModel.get("username") 35 | }); 36 | },this); 37 | 38 | nb.hosts.on("add",function (nbHostModel) { 39 | this.add({ 40 | eventType:"host-added" 41 | }); 42 | },this); 43 | 44 | nb.hosts.on("remove",function (nbHostModel) { 45 | this.add({ 46 | eventType:"host-removed" 47 | }); 48 | 49 | },this); 50 | 51 | nb.tracks.on("add",function (nbTrackModel) { 52 | this.add({ 53 | user: nbTrackModel.get("user"), 54 | detail: nbTrackModel.get("album") + " - " + nbTrackModel.get("trackName"), 55 | datetime: nbTrackModel.get("datetime"), 56 | eventType: "track-added" 57 | }); 58 | },this); 59 | 60 | nb.tracks.on("change:played",function (nbTrackModel) { 61 | this.add({ 62 | detail: nbTrackModel.get("album") + " - " + nbTrackModel.get("trackName"), 63 | eventType: "track-complete" 64 | }); 65 | },this); 66 | 67 | nb.tracks.on("remove",function (nbTrackModel) { 68 | this.add({ 69 | eventType:"track-removed" 70 | }); 71 | },this); 72 | }, 73 | 74 | 75 | // log an event on the noise box 76 | // getting and setting in this manner should fire a change event 77 | // http://stackoverflow.com/a/7325167 78 | add : function (event) { 79 | 80 | var log = this.get("log"), 81 | item = { 82 | user : event && event.user || undefined, 83 | detail: event && event.detail || undefined, 84 | eventType: event && event.eventType || undefined, 85 | datetime: event && event.datetime || moment().format("YYYY-MM-DD HH:mm:ss") 86 | }; 87 | 88 | log.push(item); 89 | 90 | this.set("log", log); 91 | 92 | // TODO: manually firing a change event 93 | this.trigger("change", item, this); 94 | this.trigger("change:log", item, this); 95 | } 96 | }); -------------------------------------------------------------------------------- /src/app/middleware/error.js: -------------------------------------------------------------------------------- 1 | /*global process __dirname */ 2 | /** 3 | * NoiseBox 4 | * error.js 5 | * 6 | * Custom error handling middleware. 7 | */ 8 | 9 | var fs = require("fs"); 10 | var constants = require("./../../public/js/const"); 11 | var log = require("../lib/log"); 12 | 13 | module.exports = function () { 14 | 15 | return function (err,req,res,next) { 16 | 17 | // Sanity check the supplied error object: 18 | 19 | err = typeof err !== "undefined" ? err : {}; 20 | err = typeof err !== "object" ? {name:err.toString()} : err; 21 | 22 | // Set default properties in case of missing information: 23 | 24 | err.statusCode = typeof err.status !== "undefined" ? err.status : err.statusCode; 25 | err.statusCode = typeof err.statusCode !== "undefined" ? err.statusCode : 500; 26 | err.stack = typeof err.stack !== "undefined" ? err.stack : ""; 27 | err.name = typeof err.name !== "undefined" ? err.name : "NoiseBoxError"; 28 | err.message = typeof err.message !== "undefined" ? err.message : "No details"; 29 | 30 | // Set information to be rendered depending on the environment. 31 | // A production environment just cases a non-specific error message to be displayed 32 | // while any other environment (testing/development). 33 | 34 | var env = process.env.NODE_ENV || constants.DEV; 35 | 36 | var error; 37 | var stack; 38 | 39 | if ( env === constants.PROD ) { 40 | 41 | error = "Something went wrong."; 42 | stack = ""; 43 | 44 | } else { 45 | 46 | error = err.name+" ("+err.statusCode+")"; 47 | 48 | // Clean up and format the stack output: 49 | 50 | stack = err.stack.split("\n").map(function (element) { 51 | if ( element.indexOf("|") > -1 || element === "" ) 52 | { 53 | return ""; 54 | } else { 55 | return "
  • "+htmlEscape(element)+"
  • "; 56 | } 57 | }).join(""); 58 | } 59 | 60 | // Implement light-weight template rendering. The reason we can't use res.render() here is 61 | // because we can't be sure where the error is bubbling up from (it could be in the template 62 | // code) so we have to have as few depandancies on app code as possible for robust error 63 | // rendering. 64 | 65 | fs.readFile(__dirname + "/../../public/html/error.html", "utf8", function(e,html) { 66 | 67 | html = html.replace(/\{title\}/g,constants.APP_TITLE); 68 | html = html.replace("{error}",htmlEscape(error)); 69 | html = html.replace("{stack}",stack); 70 | 71 | res.writeHead(err.statusCode,{"Content-Type":"text/html; charset=utf-8"}); 72 | res.end(html); 73 | }); 74 | 75 | log.error("express error",{statusCode:err.statusCode,stack:err.stack}); 76 | }; 77 | 78 | function htmlEscape (text) { 79 | 80 | return text 81 | .replace(/&/g, '&') 82 | .replace(/ 2 | <% include partials/banner %> 3 | <% include partials/room %> 4 | <% include partials/username-form %> 5 | <% include partials/currently-playing %> 6 | 7 | 8 |
    9 | 10 |
    11 |

    Albums

    12 |
    13 | 19 |
    20 |
    21 | 22 |
    23 |

    Tracks

    24 |
    25 | 26 |
    27 |
    28 |
    29 | <% files.forEach(function(dir){ %> 30 |
    31 | <% dirname = dir.name.replace(/-/g, " ") %> 32 |

    <%= dirname %>

    33 | 45 |
    46 | <% }); %> 47 |
    48 |
    49 |
    50 | 51 |
    52 | 53 |

    Stats

    54 | 55 |
    56 | 57 | 62 | 63 |
    64 |
    65 |
    66 | <% include partials/log %> 67 |
    68 | <% include partials/chat-form %> 69 |
    70 | 71 |
    72 |
    73 | <% include partials/play-queue %> 74 |
    75 |
    76 | 77 |
    78 |
    79 | <% include partials/users %> 80 |
    81 |
    82 |
    83 |
    84 | 85 |
    86 | <% include partials/play-mode-form %> 87 |
    88 |
    89 | 90 |
    91 | <% include partials/socket-io %> 92 | <% include partials/footer %> -------------------------------------------------------------------------------- /src/app/model/AppModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NoiseBox 3 | * AppModel.js 4 | * 5 | * Root Backbone Model for the whole app. 6 | */ 7 | 8 | var Backbone = require("backbone"); 9 | var NBCollection = require("./NBCollection"); 10 | var NBHomeModel = require("./NBHomeModel"); 11 | var server = require("./../../server"); 12 | var constants; 13 | 14 | var AppModel = module.exports = Backbone.Model.extend({ 15 | 16 | defaults : { 17 | 18 | }, 19 | 20 | initialize : function () { 21 | 22 | constants = server.constants; 23 | 24 | this.homeModel = new NBHomeModel(); 25 | this.noiseBoxes = new NBCollection(); 26 | 27 | this.noiseBoxes.on("add",function (nbModel) { 28 | this.trigger(constants.NOISEBOX_ADDED,nbModel); 29 | },this); 30 | 31 | this.noiseBoxes.on("remove",function (nbModel) { 32 | this.trigger(constants.NOISEBOX_REMOVED,nbModel); 33 | },this); 34 | }, 35 | 36 | getHomeModel : function () { 37 | return this.homeModel; 38 | }, 39 | 40 | getFiles : function () { 41 | return this.files; 42 | }, 43 | 44 | addNoiseBox : function (id) { 45 | 46 | this.noiseBoxes.add({id:id}); 47 | 48 | var nb = this.getNoiseBox(id); 49 | 50 | nb.users.on("add",function (nbUserModel) { 51 | this.trigger(constants.USER_ADDED,nbUserModel, constants.USER_ADDED); 52 | },this); 53 | 54 | nb.users.on("change",function (nbUserModel) { 55 | this.trigger(constants.USER_UPDATED,nbUserModel, constants.USER_UPDATED); 56 | },this); 57 | 58 | nb.users.on("remove",function (nbUserModel) { 59 | this.trigger(constants.USER_REMOVED,nbUserModel, constants.USER_REMOVED); 60 | },this); 61 | 62 | nb.hosts.on("add",function (nbHostModel) { 63 | this.trigger(constants.HOST_ADDED,nbHostModel); 64 | },this); 65 | 66 | nb.hosts.on("remove",function (nbHostModel) { 67 | this.trigger(constants.HOST_REMOVED,nbHostModel); 68 | },this); 69 | 70 | nb.tracks.on("add",function (nbTrackModel) { 71 | this.trigger(constants.TRACK_ADDED,nbTrackModel, nb); 72 | },this); 73 | 74 | nb.tracks.on("remove",function (nbTrackModel) { 75 | this.trigger(constants.TRACK_REMOVED,nbTrackModel, nb); 76 | },this); 77 | 78 | nb.log.on("change",function (item, nbLog) { 79 | this.trigger(constants.LOG_UPDATED, item, nbLog, nb); 80 | }, this); 81 | 82 | return nb; 83 | }, 84 | 85 | removeNoiseBox : function (id) { 86 | 87 | var nb = this.getNoiseBox(id); 88 | 89 | nb.off(); 90 | nb.users.off(); 91 | nb.hosts.off(); 92 | 93 | this.noiseBoxes.remove(nb); 94 | }, 95 | 96 | noiseBoxExists : function (id) { 97 | 98 | return this.getNoiseBox(id) !== undefined; 99 | }, 100 | 101 | getNoiseBox : function (id) { 102 | 103 | return this.noiseBoxes.get(id); 104 | }, 105 | 106 | getNoiseBoxByClientSocketID : function (id) { 107 | 108 | var found; 109 | 110 | this.noiseBoxes.each(function (nb) { 111 | 112 | if ( nb.clientExists(id) ) { 113 | 114 | found = nb; 115 | } 116 | }); 117 | 118 | return found; 119 | }, 120 | 121 | getNumConnectedClients : function () { 122 | 123 | var numClients = 0; 124 | var numHosts = 0; 125 | var numUsers = 0; 126 | 127 | this.noiseBoxes.each(function (nb) { 128 | 129 | numClients = numClients + nb.hosts.length; 130 | numClients = numClients + nb.users.length; 131 | }); 132 | 133 | return numClients; 134 | } 135 | }); 136 | -------------------------------------------------------------------------------- /src/public/js/lib/bootstrap-tabs.js: -------------------------------------------------------------------------------- 1 | /* ======================================================== 2 | * bootstrap-tab.js v2.3.1 3 | * http://twitter.github.com/bootstrap/javascript.html#tabs 4 | * ======================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ======================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* TAB CLASS DEFINITION 27 | * ==================== */ 28 | 29 | var Tab = function (element) { 30 | this.element = $(element) 31 | } 32 | 33 | Tab.prototype = { 34 | 35 | constructor: Tab 36 | 37 | , show: function () { 38 | var $this = this.element 39 | , $ul = $this.closest('ul:not(.dropdown-menu)') 40 | , selector = $this.attr('data-target') 41 | , previous 42 | , $target 43 | , e 44 | 45 | if (!selector) { 46 | selector = $this.attr('href') 47 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 48 | } 49 | 50 | if ( $this.parent('li').hasClass('active') ) return 51 | 52 | previous = $ul.find('.active:last a')[0] 53 | 54 | e = $.Event('show', { 55 | relatedTarget: previous 56 | }) 57 | 58 | $this.trigger(e) 59 | 60 | if (e.isDefaultPrevented()) return 61 | 62 | $target = $(selector) 63 | 64 | this.activate($this.parent('li'), $ul) 65 | this.activate($target, $target.parent(), function () { 66 | $this.trigger({ 67 | type: 'shown' 68 | , relatedTarget: previous 69 | }) 70 | }) 71 | } 72 | 73 | , activate: function ( element, container, callback) { 74 | var $active = container.find('> .active') 75 | , transition = callback 76 | && $.support.transition 77 | && $active.hasClass('fade') 78 | 79 | function next() { 80 | $active 81 | .removeClass('active') 82 | .find('> .dropdown-menu > .active') 83 | .removeClass('active') 84 | 85 | element.addClass('active') 86 | 87 | if (transition) { 88 | element[0].offsetWidth // reflow for transition 89 | element.addClass('in') 90 | } else { 91 | element.removeClass('fade') 92 | } 93 | 94 | if ( element.parent('.dropdown-menu') ) { 95 | element.closest('li.dropdown').addClass('active') 96 | } 97 | 98 | callback && callback() 99 | } 100 | 101 | transition ? 102 | $active.one($.support.transition.end, next) : 103 | next() 104 | 105 | $active.removeClass('in') 106 | } 107 | } 108 | 109 | 110 | /* TAB PLUGIN DEFINITION 111 | * ===================== */ 112 | 113 | var old = $.fn.tab 114 | 115 | $.fn.tab = function ( option ) { 116 | return this.each(function () { 117 | var $this = $(this) 118 | , data = $this.data('tab') 119 | if (!data) $this.data('tab', (data = new Tab(this))) 120 | if (typeof option == 'string') data[option]() 121 | }) 122 | } 123 | 124 | $.fn.tab.Constructor = Tab 125 | 126 | 127 | /* TAB NO CONFLICT 128 | * =============== */ 129 | 130 | $.fn.tab.noConflict = function () { 131 | $.fn.tab = old 132 | return this 133 | } 134 | 135 | 136 | /* TAB DATA-API 137 | * ============ */ 138 | 139 | $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { 140 | e.preventDefault() 141 | $(this).tab('show') 142 | }) 143 | 144 | }(window.jQuery); -------------------------------------------------------------------------------- /src/public/js/HostClient.js: -------------------------------------------------------------------------------- 1 | /*global _gaq*/ 2 | /** 3 | * NoiseBox 4 | * HomeClient.js 5 | * 6 | * Host page. 7 | */ 8 | 9 | define(["constants","AbstractClient","jquery"], function (Const,AbstractClient) { 10 | 11 | return AbstractClient.extend({ 12 | 13 | currentTrack : null, 14 | audioElement : null, 15 | 16 | init : function () { 17 | 18 | if (this.canPlayMP3()) { 19 | this._super(); 20 | } else { 21 | window.location = "/boot?m=Sorry your browser doesn't support playback of MP3s, try Google Chrome"; 22 | } 23 | }, 24 | 25 | onConnect : function () { 26 | 27 | this._super(); 28 | 29 | this.log({ eventType: "room-created" }); 30 | this.log({ eventType: "share-link" }); 31 | 32 | // some mobile/touch devices require the user 33 | // to add an audio track via touch interaction 34 | if ('ontouchstart' in document.documentElement) { 35 | this.log({ eventType: "audio-start-note" }); 36 | $('.audio-start').one('click', _.bind(this.addAudioStartNote, this)); 37 | } 38 | 39 | this.emit(Const.HOST_CONNECT); 40 | }, 41 | 42 | 43 | addAudioStartNote : function(e) { 44 | e.preventDefault(); 45 | var $a = $(e.target), 46 | $li = $a.closest('li'); 47 | 48 | $li.slideUp(); 49 | 50 | this.audioElement = $("