diff options
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | app.js | 80 | ||||
| -rw-r--r-- | package.json | 13 | ||||
| -rw-r--r-- | public/css/style.css | 16 | ||||
| -rw-r--r-- | public/js/chat.js | 93 | ||||
| -rw-r--r-- | public/js/sample.js | 94 | ||||
| -rw-r--r-- | routes/index.js | 9 | ||||
| -rw-r--r-- | views/chat.jade | 14 | ||||
| -rw-r--r-- | views/index.jade | 7 | ||||
| -rw-r--r-- | views/layout.jade | 7 |
10 files changed, 337 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..a07dc4c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +nodeaudio +========= + +node audio chat @@ -0,0 +1,80 @@ +/* -*- mode:javascript; coding:utf-8; -*- */ + +'use strict'; + +var PORT = 3000; +var REDISKEY_URL_QUEUE = 'url.queue'; +var REDISKEY_URL_SEQ = 'url.seq'; +var URL_QUEUE_MAXLEN = 10; + +var express = require('express'); +var routes = require('./routes'); + +var app = express(); +var redis = require('redis').createClient(); +var server = require('http').createServer(app); +var io = require('socket.io').listen(server); + +app.set('view engine', 'jade'); +app.set('views', __dirname + '/views'); + +app.engine('jade', require('jade').__express); + +//app.use(express.bodyParser()); +//app.use(express.methodOverride()); +app.use(app.router); +app.use(express.static(__dirname + '/public')); + +switch (app.get('env')) { +case 'development': + app.use(express.errorHandler({ + dumpExceptions: true, + showStack: true + })); + break; +case 'production': + app.use(express.errorHandler()); + break +} + +app.get('/', routes.index); +app.get('/chat', routes.chat); + +server.listen(PORT); + +io.sockets.on('connection', function (socket) { + socket.emit('message', { message: 'ready' }); + socket.on('send', function (data) { + var m = data.message.match(/(?:https?:\/\/)(?:[\da-z\.-]+)(?:\.(?:[a-z\.]{2,6}))?(?:\:\d+)?(?:[\/\w\.-]*)*\/?/ig); + + if (m) { + var length = m.length; + + redis.incrby(REDISKEY_URL_SEQ, length, function (e, v) { + var timestamp = Date.now(); + var xs = m.map(function (url, i) { + return JSON.stringify({ + seq: v - length + i + 1, + timestamp: timestamp, + url: url + }); + }); + + xs.unshift(REDISKEY_URL_QUEUE); + redis.send_command('rpush', xs); + redis.ltrim(REDISKEY_URL_QUEUE, -URL_QUEUE_MAXLEN, -1); + redis.lrange(REDISKEY_URL_QUEUE, -URL_QUEUE_MAXLEN, -1, function (e, queue) { + if (queue) { + data.queue = queue; + } + io.sockets.emit('message', data); + }); + }); + } + else { + io.sockets.emit('message', data); + } + }); +}); + +console.log("listening on port %d (env=%s)", server.address().port, app.settings.env); diff --git a/package.json b/package.json new file mode 100644 index 0000000..7a649a5 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "nodeaudio", + "version": "0.0.0", + "description": "nodeaudio", + "dependencies": { + "express": "latest", + "jade": "latest", + "hiredis": "latest", + "redis": "latest", + "socket.io": "latest" + }, + "author": "akonsu" +} diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..45c8cbf --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,16 @@ +/*-*- coding:utf-8; mode:css; -*-*/ + +* { + margin: 0; + padding: 0; +} +#chat #history { + border: solid 1px #ccc; + height: 300px; + overflow-y: scroll; + width: 500px; +} +#chat .controls input:not([type]) { + border: solid 1px #ccc; + width: 500px; +} diff --git a/public/js/chat.js b/public/js/chat.js new file mode 100644 index 0000000..27b0c8e --- /dev/null +++ b/public/js/chat.js @@ -0,0 +1,93 @@ +/* -*- mode:javascript; coding:utf-8; -*- */ + +'use strict'; + +(function (window) { + function on_ended(context) { + if (this.link && this.link.buffer) { + console.log('playing next sound'); + queue.link = this.link; + this.link.onended = _.partial(on_ended, context); + this.link.play(context); + } + else { + console.log('playing again'); + this.play(context); + } + }; + + function Queue() { + this.last = this; + } + + Queue.prototype.append = function (x) { + if (!this.seen || x.gt(this.seen)) { + this.last.link = x; + this.last = x; + this.seen = { + seq: x.seq, + timestamp: x.timestamp + }; + return x; + } + else { + return null; + } + }; + + var queue = new Queue(); + + window.AudioContext = window.AudioContext || window.webkitAudioContext; + + window.onload = function () { + var context = new AudioContext(); + var field = document.getElementById("field"); + var history = document.getElementById("history"); + var send = document.getElementById('send'); + + var messages = []; + var socket = io.connect('/'); + + socket.on('message', function (data) { + if (data.message) { + messages.push(data.message); + + var html = ''; + + for (var i = 0; i < messages.length; i++) { + html += messages[i] + '<br />'; + } + history.innerHTML = html; + + if (data.queue) { + _.each(data.queue, function (s) { + try { + var sample = new Sample(s); + + if (queue.append(sample)) { + if (queue.link === sample) { + sample.onended = _.partial(on_ended, context); + sample.onload = function () { + this.play(context); + }; + } + sample.load(context); + } + } + catch (e) { + console.error((e.message || 'error') + ' in "' + s + '"'); + } + }); + } + } + else { + console.error('error: ', data); + } + }); + + send.onclick = function () { + var text = field.value; + socket.emit('send', { message: text }); + }; + }; +})(window); diff --git a/public/js/sample.js b/public/js/sample.js new file mode 100644 index 0000000..d54be71 --- /dev/null +++ b/public/js/sample.js @@ -0,0 +1,94 @@ +/* -*- mode:javascript; coding:utf-8; -*- */ + +'use strict'; + +(function (window) { + var LOOP_COUNT = 4; + + function sample(json) { + var x = JSON.parse(json); + + this.seq = x.seq || 0; + this.timestamp = x.timestamp || 0; + this.url = x.url; + } + + sample.prototype.gt = function (x) { + return this.timestamp > x.timestamp || this.timestamp === x.timestamp && this.seq > x.seq; + }; + + sample.prototype.load = function (context) { + var self = this; + + if (self.url) { + var request = new XMLHttpRequest(); + + request.open('GET', self.url, true); + request.responseType = 'arraybuffer'; + + request.onload = function (v) { + if (v && v.target && v.target.status == 200) { + context.decodeAudioData(request.response, function (buffer) { + if (buffer) { + self.buffer = buffer; + + if (self.onload) { + self.onload(); + } + console.log('sample.load: finished ', self.url); + } + else { + console.error('sample.load: no decoded data', self); + } + }, function (e) { + console.error('sample.load: decodeAudioData error', e, self); + }); + } + }; + + request.onerror = function (e) { + var s = (e && e.target && e.target.status && e.target.statusText + ? (e.target.status + ' ' + e.target.statusText) + : 'error'); + console.error('sample.load: ' + s + ' while loading', self); + }; + + request.send(); + } + else { + console.error('sample.load: no URL'); + } + }; + + sample.prototype.play = function (context) { + var self = this; + + if (self.buffer) { + var delay = self.buffer.duration * LOOP_COUNT + var source = context.createBufferSource(); + + source.buffer = self.buffer; + source.loop = true; + + source.connect(context.destination); + source.start(context.currentTime); + source.stop(context.currentTime + delay); + + window.setTimeout(function () { + if (self.onended) { + self.onended(); + } + }, delay * 1000); + + // does not work + //source.onended = function (e) { + // console.log(e); + //}; + } + else { + console.error('sample.play: no buffer', self); + } + }; + + window['Sample'] = sample; +})(window); diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..22f27f7 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,9 @@ +/* -*- mode:javascript; coding:utf-8; -*- */ + +exports.chat = function (req, res) { + res.render('chat'); +}; + +exports.index = function (req, res) { + res.render('index') +}; diff --git a/views/chat.jade b/views/chat.jade new file mode 100644 index 0000000..8d02e49 --- /dev/null +++ b/views/chat.jade @@ -0,0 +1,14 @@ +extends layout + +block head + script(src='//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.1/underscore-min.js') + script(src='/socket.io/socket.io.js') + script(src='js/chat.js') + script(src='js/sample.js') + +block content + #chat + #history + .controls + input#field(value='http://localhost:3000/samples/amen_brother.wav http://localhost:3000/samples/yamaha-SY-35-clarinet-c5.wav') + input#send(type='button', value='send') diff --git a/views/index.jade b/views/index.jade new file mode 100644 index 0000000..cd207eb --- /dev/null +++ b/views/index.jade @@ -0,0 +1,7 @@ +extends layout + +block head + script(src="myfile.js") + +block content + a(href="chat") chat diff --git a/views/layout.jade b/views/layout.jade new file mode 100644 index 0000000..8c065a0 --- /dev/null +++ b/views/layout.jade @@ -0,0 +1,7 @@ +doctype 5 +html + head + link(rel='stylesheet', href='/css/style.css') + block head + body + block content |
