summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoryo mama <pepper@scannerjammer.com>2015-04-04 01:13:50 -0700
committeryo mama <pepper@scannerjammer.com>2015-04-04 01:13:50 -0700
commit1a3c90165cb6bac4a2e57d9897d6829de0ccc6fc (patch)
tree604f4b5b02b1e24c7c5aad5c4560b0cf53cf9289
-rw-r--r--README.md4
-rw-r--r--app.js80
-rw-r--r--package.json13
-rw-r--r--public/css/style.css16
-rw-r--r--public/js/chat.js93
-rw-r--r--public/js/sample.js94
-rw-r--r--routes/index.js9
-rw-r--r--views/chat.jade14
-rw-r--r--views/index.jade7
-rw-r--r--views/layout.jade7
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
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..cc9c6d7
--- /dev/null
+++ b/app.js
@@ -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