New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

nodetunes

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nodetunes - npm Package Compare versions

Comparing version

to
0.0.5

6

examples/decrypt.js

@@ -15,6 +15,6 @@ "use strict";

var privkey = ursa.createPrivateKey(fs.readFileSync('private.key'));
var response = privkey.publicDecrypt(data, 'base64', 'hex');
var privkey = ursa.createPrivateKey(fs.readFileSync('private.key'));
var response = privkey.publicDecrypt(data, 'base64', 'hex');
console.log(response);
console.log(response);
});

@@ -6,14 +6,9 @@ "use strict";

var server = new AirTunesServer(new Speaker({
var speaker = new Speaker({
channels: 2,
bitDepth: 16,
sampleRate: 44100
}), {
});
var server = new AirTunesServer(speaker);
server.start();
server.on('volumeChange', function(vol) {
console.log(vol);
});
server.start();

@@ -6,33 +6,34 @@ "use strict";

var audioProcessor = function(rtspServer) {
var self = this;
var self = this;
var state = 'buffering';
var state = 'buffering';
var bufferQueue = new PriorityQueue(function(a, b) {
return b.sequenceNumber - a.sequenceNumber;
});
var bufferQueue = new PriorityQueue(function(a, b) {
return b.sequenceNumber - a.sequenceNumber;
});
self.process = function(audio, sequenceNumber) {
var swapBuf = new Buffer(audio.length);
self.process = function(audio, sequenceNumber) {
var swapBuf = new Buffer(audio.length);
for (var i = 0; i < audio.length; i += 2) {
swapBuf[i] = audio[i + 1];
swapBuf[i + 1] = audio[i];
}
// endian hack
for (var i = 0; i < audio.length; i += 2) {
swapBuf[i] = audio[i + 1];
swapBuf[i + 1] = audio[i];
}
if (bufferQueue.length < 5) {
state = 'buffering';
}
if (bufferQueue.length < 5) {
state = 'buffering';
}
bufferQueue.enq({ buffer: swapBuf, sequenceNumber: sequenceNumber });
bufferQueue.enq({ buffer: swapBuf, sequenceNumber: sequenceNumber });
if (state == 'active') {
while (bufferQueue.size() >= 5) {
rtspServer.options.outStream.write(bufferQueue.deq().buffer);
}
} else if (bufferQueue.size() >= 100) {
state = 'active';
}
if (state == 'active') {
while (bufferQueue.size() >= 5) {
rtspServer.options.outStream.write(bufferQueue.deq().buffer);
}
} else if (bufferQueue.size() >= 100) {
state = 'active';
}
};
};

@@ -39,0 +40,0 @@ };

@@ -7,46 +7,45 @@ "use strict";

var rtpServer = function(rtspServer) {
var self = this;
var crypto = require('crypto');
var RtpServer = function(rtspServer) {
var self = this;
var crypto = require('crypto');
self.start = function() {
self.baseServer = dgram.createSocket('udp4');
self.controlServer = dgram.createSocket('udp4');
self.timingServer = dgram.createSocket('udp4');
RtpServer.prototype.start = function() {
self.baseServer = dgram.createSocket('udp4');
self.controlServer = dgram.createSocket('udp4');
self.timingServer = dgram.createSocket('udp4');
self.baseServer.bind(rtspServer.ports[0]);
self.controlServer.bind(rtspServer.ports[1]);
self.timingServer.bind(rtspServer.ports[2]);
self.baseServer.bind(rtspServer.ports[0]);
self.controlServer.bind(rtspServer.ports[1]);
self.timingServer.bind(rtspServer.ports[2]);
self.baseServer.on('message', function(msg) {
var meta = msg.slice(0, 12);
var sequenceNumber = meta.slice(2, 4).readUInt16BE(0);
self.baseServer.on('message', function(msg) {
var meta = msg.slice(0, 12);
var sequenceNumber = meta.slice(2, 4).readUInt16BE(0);
var encryptedAudio = msg.slice(12);
var encryptedAudio = msg.slice(12);
var decipher = crypto.createDecipheriv('aes-128-cbc', rtspServer.audioAesKey, rtspServer.audioAesIv);
decipher.setAutoPadding(false);
var decipher = crypto.createDecipheriv('aes-128-cbc', rtspServer.audioAesKey, rtspServer.audioAesIv);
decipher.setAutoPadding(false);
var audio = decipher.update(encryptedAudio);
var audio = decipher.update(encryptedAudio);
rtspServer.audioProcessor.process(audio, sequenceNumber);
rtspServer.audioProcessor.process(audio, sequenceNumber);
});
});
self.controlServer.on('message', function(msg) {
var timestamp = msg.readUInt32BE(4);
});
self.controlServer.on('message', function(msg) {
var timestamp = msg.readUInt32BE(4);
});
self.timingServer.on('message', function(msg) {
//console.log(msg.length + ' BYTES SENT TO TIMING PORT');
});
};
self.timingServer.on('message', function(msg) {
//console.log(msg.length + ' BYTES SENT TO TIMING PORT');
});
};
self.stop = function() {
self.baseServer.close();
self.controlServer.close();
self.timingServer.close();
}
RtpServer.prototype.stop = function() {
self.baseServer.close();
self.controlServer.close();
self.timingServer.close();
}
}
module.exports = rtpServer;
module.exports = RtpServer;

@@ -9,33 +9,41 @@ "use strict";

var server = function(options, external) {
var self = this;
var Server = function(options, external) {
var self = this;
self.external = external;
self.options = options;
self.external = external;
self.options = options;
self.ports = [];
self.ports = [];
self.rtp = new RtpServer(self);
self.audioProcessor = new AudioProcessor(self);
self.macAddress = '5F513885F785';
self.metadata = {};
// pull method processors
var methodMapping = require('./rtspmethods')(self);
self.rtp = new RtpServer(self);
self.audioProcessor = new AudioProcessor(self);
self.macAddress = options.macAddress;
self.metadata = {};
var handler = function(socket) {
self.clientConnected = false;
socket.id = new Date();
self.external.emit('clientConnected');
// pull method processors
var methodMapping = require('./rtspmethods')(self);
var parser = new Parser(socket);
parser.on('message', function(m) {
var response = new tools.MessageBuilder(socket);
methodMapping[m.method](response, m.headers, m.content);
});
Server.prototype.connectHandler = function(socket) {
};
socket.id = new Date();
self.external.emit('clientConnected');
self.handler = handler;
var parser = new Parser(socket);
parser.on('message', function(m) {
var response = new tools.MessageBuilder(socket);
methodMapping[m.method](response, m.headers, m.content);
});
socket.on('close', self.disconnectHandler);
};
Server.prototype.disconnectHandler = function() {
self.clientConnected = false;
self.external.emit('clientDisconnected');
};
};
module.exports = server;
module.exports = Server;

@@ -8,4 +8,5 @@ "use strict";

var statusMessages = {
200: "OK",
401: "Unauthorized"
200: 'OK',
401: 'Unauthorized',
453: 'Not Enough Bandwidth'
};

@@ -15,92 +16,92 @@

var buffer = '';
var socket = socket;
var self = this;
var buffer = '';
var socket = socket;
var self = this;
MessageBuilder.prototype.addHeader = function(header, data) {
buffer += header + ": " + data + "\r\n";
};
MessageBuilder.prototype.addHeader = function(header, data) {
buffer += header + ": " + data + "\r\n";
};
MessageBuilder.prototype.setStatus = function(statusCode, cseq) {
buffer += "RTSP/1.0 " + statusCode + " " + statusMessages[statusCode] + '\r\n';
self.addHeader('Server', 'AirTunes/105.1');
self.addHeader('CSeq', cseq);
}
MessageBuilder.prototype.setStatus = function(statusCode, cseq) {
buffer += "RTSP/1.0 " + statusCode + " " + statusMessages[statusCode] + '\r\n';
self.addHeader('Server', 'AirTunes/105.1');
self.addHeader('CSeq', cseq);
}
MessageBuilder.prototype.setOK = function(cseq) {
self.setStatus(200, cseq);
};
MessageBuilder.prototype.setOK = function(cseq) {
self.setStatus(200, cseq);
};
MessageBuilder.prototype.send = function() {
//console.log('SENDING DATA @ ' + socket.id.getTime());
//console.log('`--: ' + buffer.replace(/\r\n/g, '\r\n`--: '));
//console.log('END SEND\n')
socket.write(buffer + '\r\n');
};
MessageBuilder.prototype.send = function() {
//console.log('SENDING DATA @ ' + socket.id.getTime());
//console.log('`--: ' + buffer.replace(/\r\n/g, '\r\n`--: '));
//console.log('END SEND\n')
socket.write(buffer + '\r\n');
};
MessageBuilder.prototype.sendError = function(err) {
//console.log('SENDING ERROR ' + err + ': ' + errorList[err]);
socket.end("RTSP/1.0 " + err + ' ' + statusMessages[err] + '\r\n');
};
MessageBuilder.prototype.sendError = function(err) {
//console.log('SENDING ERROR ' + err + ': ' + errorList[err]);
socket.end("RTSP/1.0 " + err + ' ' + statusMessages[err] + '\r\n');
};
};
var parseSdp = function(msg) {
var multi = [ 'a', 'p', 'b' ];
var multi = [ 'a', 'p', 'b' ];
var lines = msg.split('\r\n');
var output = {};
for (var i = 0; i < lines.length; i++) {
var lines = msg.split('\r\n');
var output = {};
for (var i = 0; i < lines.length; i++) {
var sp = lines[i].split(/=(.+)?/);
if (sp.length == 3) { // for some reason there's an empty item?
if (multi.indexOf(sp[0]) != -1) { // some attributes are multiline...
if (!output[sp[0]])
output[sp[0]] = new Array();
var sp = lines[i].split(/=(.+)?/);
if (sp.length == 3) { // for some reason there's an empty item?
if (multi.indexOf(sp[0]) != -1) { // some attributes are multiline...
if (!output[sp[0]])
output[sp[0]] = new Array();
output[sp[0]].push(sp[1]);
} else {
output[sp[0]] = sp[1];
}
}
}
return output;
output[sp[0]].push(sp[1]);
} else {
output[sp[0]] = sp[1];
}
}
}
return output;
};
var dmapTypes = {
mper: 8,
asal: 'str',
asar: 'str',
ascp: 'str',
asgn: 'str',
minm: 'str',
astn: 2,
asdk: 1,
caps: 1,
astm: 4
mper: 8,
asal: 'str',
asar: 'str',
ascp: 'str',
asgn: 'str',
minm: 'str',
astn: 2,
asdk: 1,
caps: 1,
astm: 4
};
var parseDmap = function(buffer) {
var output = {};
var output = {};
for (var i = 8; i < buffer.length;) {
var itemType = buffer.slice(i, i + 4);
var itemLength = buffer.slice(i + 4, i + 8).readUInt32BE(0);
if (itemLength != 0) {
var data = buffer.slice(i + 8, i + 8 + itemLength);
if (dmapTypes[itemType] == 'str') {
output[itemType.toString()] = data.toString();
} else if (dmapTypes[itemType] == 1) {
output[itemType.toString()] = data.readUInt8(0);
} else if (dmapTypes[itemType] == 2) {
output[itemType.toString()] = data.readUInt16BE(0);
} else if (dmapTypes[itemType] == 4) {
output[itemType.toString()] = data.readUInt32BE(0);
} else if (dmapTypes[itemType] == 8) {
output[itemType.toString()] = (data.readUInt32BE(0) << 8) + data.readUInt32BE(4);
}
}
i += 8 + itemLength;
}
for (var i = 8; i < buffer.length;) {
var itemType = buffer.slice(i, i + 4);
var itemLength = buffer.slice(i + 4, i + 8).readUInt32BE(0);
if (itemLength != 0) {
var data = buffer.slice(i + 8, i + 8 + itemLength);
if (dmapTypes[itemType] == 'str') {
output[itemType.toString()] = data.toString();
} else if (dmapTypes[itemType] == 1) {
output[itemType.toString()] = data.readUInt8(0);
} else if (dmapTypes[itemType] == 2) {
output[itemType.toString()] = data.readUInt16BE(0);
} else if (dmapTypes[itemType] == 4) {
output[itemType.toString()] = data.readUInt32BE(0);
} else if (dmapTypes[itemType] == 8) {
output[itemType.toString()] = (data.readUInt32BE(0) << 8) + data.readUInt32BE(4);
}
}
i += 8 + itemLength;
}
return output;
return output;
}

@@ -111,14 +112,14 @@

var generateAppleResponse = function(challengeBuf, ipAddr, macAddr) {
var fullChallenge = Buffer.concat([ challengeBuf, ipAddr, macAddr ]);
var fullChallenge = Buffer.concat([ challengeBuf, ipAddr, macAddr ]);
// im sure there's an easier way to pad this buffer
var padding = new Array();
for (var i = fullChallenge.length; i < 32; i++) {
padding.push(0);
}
fullChallenge = Buffer.concat([ fullChallenge, new Buffer(padding) ]);
// im sure there's an easier way to pad this buffer
var padding = new Array();
for (var i = fullChallenge.length; i < 32; i++) {
padding.push(0);
}
fullChallenge = Buffer.concat([ fullChallenge, new Buffer(padding) ]);
var response = privkey.privateEncrypt(fullChallenge, 'base64', 'base64');
var response = privkey.privateEncrypt(fullChallenge, 'base64', 'base64');
return response;
return response;
};

@@ -128,7 +129,7 @@

var ha1 = crypto.createHash('md5').update(username + ':' + realm + ':' + password).digest().toString('hex');
var ha2 = crypto.createHash('md5').update(method + ':' + uri).digest().toString('hex');
var response = crypto.createHash('md5').update(ha1 + ':' + nonce + ':' + ha2).digest().toString('hex');
var ha1 = crypto.createHash('md5').update(username + ':' + realm + ':' + password).digest().toString('hex');
var ha2 = crypto.createHash('md5').update(method + ':' + uri).digest().toString('hex');
var response = crypto.createHash('md5').update(ha1 + ':' + nonce + ':' + ha2).digest().toString('hex');
return response;
return response;
}

@@ -135,0 +136,0 @@

@@ -11,180 +11,187 @@ "use strict";

var rtspServer = rtspServer;
var rtspServer = rtspServer;
var nonce = '';
var nonce = '';
var options = function(response, headers) {
var options = function(response, headers) {
response.setOK(headers['CSeq']);
response.addHeader('Public', 'ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET');
response.setOK(headers['CSeq']);
response.addHeader('Public', 'ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET');
if (headers.hasOwnProperty('Apple-Challenge')) {
if (headers.hasOwnProperty('Apple-Challenge')) {
// challenge response consists of challenge + ip address + mac address + padding to 32 bytes,
// encrypted with the ApEx private key (private encryption mode w/ PKCS1 padding)
// challenge response consists of challenge + ip address + mac address + padding to 32 bytes,
// encrypted with the ApEx private key (private encryption mode w/ PKCS1 padding)
var challengeBuf = new Buffer(headers['Apple-Challenge'], 'base64');
var ipAddr = new Buffer(ip.toBuffer(ip.address()), 'hex');
var macAddr = new Buffer(rtspServer.macAddress.replace(/:/g, ''), 'hex');
response.addHeader('Apple-Response', tools.generateAppleResponse(challengeBuf, ipAddr, macAddr));
}
var challengeBuf = new Buffer(headers['Apple-Challenge'], 'base64');
var ipAddr = new Buffer(ip.toBuffer(ip.address()), 'hex');
var macAddr = new Buffer(rtspServer.macAddress.replace(/:/g, ''), 'hex');
response.addHeader('Apple-Response', tools.generateAppleResponse(challengeBuf, ipAddr, macAddr));
}
response.send();
};
response.send();
};
var announce = function(response, headers, content) {
if (rtspServer.options.password && !headers['Authorization']) {
var announce = function(response, headers, content) {
if (rtspServer.clientConnected) {
response.setStatus(453);
response.send();
} else if (rtspServer.options.password && !headers['Authorization']) {
var md5sum = crypto.createHash('md5');
md5sum.update = randomstring.generate();
response.setStatus(401, headers['CSeq']);
nonce = md5sum.digest('hex').toString('hex');
var md5sum = crypto.createHash('md5');
md5sum.update = randomstring.generate();
response.setStatus(401, headers['CSeq']);
nonce = md5sum.digest('hex').toString('hex');
response.addHeader('WWW-Authenticate', 'Digest realm="roap", nonce="' + nonce + '"');
response.send();
response.addHeader('WWW-Authenticate', 'Digest realm="roap", nonce="' + nonce + '"');
response.send();
} else if (rtspServer.options.password && headers['Authorization']) {
} else if (rtspServer.options.password && headers['Authorization']) {
var auth = headers['Authorization'];
var auth = headers['Authorization'];
var params = auth.split(/, /g);
var map = {};
params.forEach(function(param) {
var pair = param.replace(/["]/g, '').split('=');
map[pair[0]] = pair[1];
});
var params = auth.split(/, /g);
var map = {};
params.forEach(function(param) {
var pair = param.replace(/["]/g, '').split('=');
map[pair[0]] = pair[1];
});
var expectedResponse = tools.generateRfc2617Response('iTunes', 'roap', rtspServer.options.password, nonce, map['uri'], 'ANNOUNCE');
var receivedResponse = map['response'];
var expectedResponse = tools.generateRfc2617Response('iTunes', 'roap', rtspServer.options.password, nonce, map['uri'], 'ANNOUNCE');
var receivedResponse = map['response'];
if (expectedResponse == receivedResponse) {
announceParse(response, headers, content);
} else {
response.sendError(401);
}
if (expectedResponse == receivedResponse) {
announceParse(response, headers, content);
} else {
response.sendError(401);
}
} else {
announceParse(response, headers, content);
}
};
var announceParse = function(response, headers, content) {
var sdp = tools.parseSdp(content.toString());
for (var i = 0; i < sdp.a.length; i++) {
var sp = sdp.a[i].split(':');
if (sp.length == 2) {
if (sp[0] == 'rsaaeskey') {
rtspServer.audioAesKey = tools.rsaOperations.decrypt(new Buffer(sp[1], 'base64'));
} else if (sp[0] == 'aesiv') {
rtspServer.audioAesIv = new Buffer(sp[1], 'base64');
} else if (sp[0] == 'rtpmap') {
rtspServer.audioCodec = sp[1];
} else if (sp[0] == 'fmtp') {
rtspServer.audioOptions = sp[1].split(' ');
}
}
}
rtspServer.clientName = sdp.i;
} else {
announceParse(response, headers, content);
}
};
response.setOK(headers['CSeq']);
response.send();
};
var announceParse = function(response, headers, content) {
var setup = function(response, headers) {
rtspServer.ports = [];
rtspServer.clientConnected = true;
portastic.find({
min : 50000,
max : 50020,
retrieve: 3
}, function(err, port){
if (err) throw err;
var sdp = tools.parseSdp(content.toString());
for (var i = 0; i < sdp.a.length; i++) {
var sp = sdp.a[i].split(':');
if (sp.length == 2) {
if (sp[0] == 'rsaaeskey') {
rtspServer.audioAesKey = tools.rsaOperations.decrypt(new Buffer(sp[1], 'base64'));
} else if (sp[0] == 'aesiv') {
rtspServer.audioAesIv = new Buffer(sp[1], 'base64');
} else if (sp[0] == 'rtpmap') {
rtspServer.audioCodec = sp[1];
} else if (sp[0] == 'fmtp') {
rtspServer.audioOptions = sp[1].split(' ');
}
}
}
rtspServer.clientName = sdp.i;
rtspServer.ports = port;
response.setOK(headers['CSeq']);
response.send();
};
if (rtspServer.ports.length >= 3) {
var setup = function(response, headers) {
rtspServer.ports = [];
rtspServer.rtp.start();
portastic.find({
min : 50000,
max : 50020,
retrieve: 3
}, function(err, port){
if (err) throw err;
response.setOK(headers['CSeq']);
response.addHeader('Transport', 'RTP/AVP/UDP;unicast;mode=record;server_port=' + rtspServer.ports[0] + ';control_port=' + rtspServer.ports[1] + ';timing_port=' + rtspServer.ports[2]);
response.addHeader('Session', '1');
response.addHeader('Audio-Jack-Status', 'connected');
response.send();
rtspServer.ports = port;
}
});
};
if (rtspServer.ports.length >= 3) {
var record = function(response, headers) {
response.setOK(headers['CSeq']);
if (!headers['RTP-Info']) {
// it seems like iOS airplay does something else
} else {
var rtpInfo = headers['RTP-Info'].split(';');
var initSeq = rtpInfo[0].split('=')[1];
var initRtpTime = rtpInfo[1].split('=')[1];
if (!initSeq || !initRtpTime) {
response.sendError(400);
} else {
response.addHeader('Audio-Latency', '1000');
}
}
response.send();
};
rtspServer.rtp.start();
var flush = function(response, headers) {
response.setOK(headers['CSeq']);
response.addHeader('RTP-Info', 'rtptime=1147914212');
response.send();
};
response.setOK(headers['CSeq']);
response.addHeader('Transport', 'RTP/AVP/UDP;unicast;mode=record;server_port=' + rtspServer.ports[0] + ';control_port=' + rtspServer.ports[1] + ';timing_port=' + rtspServer.ports[2]);
response.addHeader('Session', '1');
response.addHeader('Audio-Jack-Status', 'connected');
response.send();
var teardown = function(response, headers) {
rtspServer.rtp.stop();
response.setOK(headers['CSeq']);
response.send();
};
}
});
};
var setParameter = function(response, headers, content) {
if (headers['Content-Type'] == 'application/x-dmap-tagged') {
// metadata dmap/daap format
var record = function(response, headers) {
response.setOK(headers['CSeq']);
if (!headers['RTP-Info']) {
// it seems like iOS airplay does something else
} else {
var rtpInfo = headers['RTP-Info'].split(';');
var initSeq = rtpInfo[0].split('=')[1];
var initRtpTime = rtpInfo[1].split('=')[1];
if (!initSeq || !initRtpTime) {
response.sendError(400);
} else {
response.addHeader('Audio-Latency', '1000');
}
}
response.send();
};
var dmapData = tools.parseDmap(content);
rtspServer.metadata = dmapData;
rtspServer.external.emit('metadataChange', rtspServer.metadata);
} else if (headers['Content-Type'] == 'image/jpeg') {
rtspServer.metadata.artwork = content;
rtspServer.external.emit('artworkChange', content);
} else if (headers['Content-Type'] == 'text/parameters') {
var data = content.toString().split(': ');
rtspServer.metadata = rtspServer.metadata || {};
var flush = function(response, headers) {
response.setOK(headers['CSeq']);
response.addHeader('RTP-Info', 'rtptime=1147914212');
response.send();
};
if (data[0] == 'volume') {
rtspServer.metadata['volume'] = parseFloat(data[1]);
rtspServer.external.emit('volumeChange', rtspServer.metadata['volume']);
} else if (data[0] == 'progress') {
rtspServer.metadata['progress'] = data[1];
rtspServer.external.emit('progressChange', rtspServer.metadata['progress']);
}
} else {
var teardown = function(response, headers) {
rtspServer.rtp.stop();
response.setOK(headers['CSeq']);
response.send();
};
}
response.setOK(headers['CSeq']);
response.send();
};
var setParameter = function(response, headers, content) {
if (headers['Content-Type'] == 'application/x-dmap-tagged') {
// metadata dmap/daap format
var getParameter = function(response, headers, content) {
response.setOK(headers['CSeq']);
response.send();
}
var dmapData = tools.parseDmap(content);
rtspServer.metadata = dmapData;
rtspServer.external.emit('metadataChange', rtspServer.metadata);
} else if (headers['Content-Type'] == 'image/jpeg') {
rtspServer.metadata.artwork = content;
rtspServer.external.emit('artworkChange', content);
} else if (headers['Content-Type'] == 'text/parameters') {
var data = content.toString().split(': ');
rtspServer.metadata = rtspServer.metadata || {};
return {
"OPTIONS" : options,
"ANNOUNCE" : announce,
"SETUP" : setup,
"RECORD" : record,
"FLUSH" : flush,
"TEARDOWN" : teardown,
"SET_PARAMETER" : setParameter, // metadata, volume control
"GET_PARAMETER" : getParameter // asked for by iOS?
};
if (data[0] == 'volume') {
rtspServer.metadata['volume'] = parseFloat(data[1]);
rtspServer.external.emit('volumeChange', parseInt(rtspServer.metadata['volume']));
} else if (data[0] == 'progress') {
rtspServer.metadata['progress'] = data[1];
rtspServer.external.emit('progressChange', rtspServer.metadata['progress']);
}
} else {
}
response.setOK(headers['CSeq']);
response.send();
};
var getParameter = function(response, headers, content) {
response.setOK(headers['CSeq']);
response.send();
}
return {
"OPTIONS" : options,
"ANNOUNCE" : announce,
"SETUP" : setup,
"RECORD" : record,
"FLUSH" : flush,
"TEARDOWN" : teardown,
"SET_PARAMETER" : setParameter, // metadata, volume control
"GET_PARAMETER" : getParameter // asked for by iOS?
};
};

@@ -5,2 +5,4 @@ "use strict";

var net = require('net');
var portastic = require('portastic');
var randomMac = require('random-mac');
var RtspServer = require('./rtsp');

@@ -10,53 +12,61 @@ var EventEmitter = require('events').EventEmitter;

var NodeTunes = function(outStream, options) {
var self = this;
var options = options || {};
var self = this;
var options = options || {};
options.outStream = outStream;
options.outStream = outStream;
if (!options.serverName) {
options.serverName = 'NodeTunes';
}
if (!options.serverName) {
options.serverName = 'NodeTunes';
}
if (!options.macAddress) {
options.macAddress = '5F513885F785';
}
if (!options.macAddress) {
options.macAddress = randomMac().toUpperCase().replace(/:/g, '');
}
var txtSetup = {
txtvers: '1', // txt record version?
tx: '1', // ?
ch: '2', // # channels
cn: '0', // codec; 0=pcm, 1=alac, 2=aac, 3=aac elc; fwiw Sonos supports aac; pcm required for iPad+Spotify; OS X works with pcm
et: '0,1', // encryption; 0=none, 1=rsa, 3=fairplay, 4=mfisap, 5=fairplay2.5; need rsa for os x
md: '0', // metadata; 0=text, 1=artwork, 2=progress
pw: (options.password ? 'true' : 'false'), // password enabled
sr: '44100', // sampling rate (e.g. 44.1KHz)
ss: '16', // sample size (e.g. 16 bit?)
tp: 'TCP,UDP', // transport protocol
vs: '130.14', // server version?
am: options.serverName, // device model
ek: '1', // ? from ApEx; setting to 1 enables iTunes; seems to use ALAC regardless of 'cn' setting
//sv: 'false', // ? from ApEx
//da: 'true', // ? from ApEx
//vn: '65537', // ? from ApEx; maybe rsa key modulus? happens to be the same value
//fv: '76400.10', // ? from ApEx; maybe AirPort software version (7.6.4)
//sf: '0x5' // ? from ApEx
};
var txtSetup = {
txtvers: '1', // txt record version?
tx: '1', // ?
ch: '2', // # channels
cn: '0', // codec; 0=pcm, 1=alac, 2=aac, 3=aac elc; fwiw Sonos supports aac; pcm required for iPad+Spotify; OS X works with pcm
et: '0,1', // encryption; 0=none, 1=rsa, 3=fairplay, 4=mfisap, 5=fairplay2.5; need rsa for os x
md: '0', // metadata; 0=text, 1=artwork, 2=progress
pw: (options.password ? 'true' : 'false'), // password enabled
sr: '44100', // sampling rate (e.g. 44.1KHz)
ss: '16', // sample size (e.g. 16 bit?)
tp: 'TCP,UDP', // transport protocol
vs: '130.14', // server version?
am: options.serverName, // device model
ek: '1', // ? from ApEx; setting to 1 enables iTunes; seems to use ALAC regardless of 'cn' setting
//sv: 'false', // ? from ApEx
//da: 'true', // ? from ApEx
//vn: '65537', // ? from ApEx; maybe rsa key modulus? happens to be the same value
//fv: '76400.10', // ? from ApEx; maybe AirPort software version (7.6.4)
//sf: '0x5' // ? from ApEx
};
var netServer = null;
var rtspServer = new RtspServer(options, self);
var netServer = null;
var rtspServer = new RtspServer(options, self);
NodeTunes.prototype.start = function() {
NodeTunes.prototype.start = function() {
portastic.find({
min: 5000,
max: 5050,
retrieve: 1
}, function(err, port) {
if (err) throw err;
netServer = net.createServer(rtspServer.connectHandler).listen(port, function() {
netServer = net.createServer(rtspServer.handler).listen(5000, function() {
var ad = mdns.createAdvertisement(mdns.tcp('raop'), 5000, {
name: options.macAddress + '@' + options.serverName,
txtRecord: txtSetup
});
});
// advertise on bonjour
var ad = mdns.createAdvertisement(mdns.tcp('raop'), port, {
name: options.macAddress + '@' + options.serverName,
txtRecord: txtSetup
});
});
});
};
};
NodeTunes.prototype.stop = function() {
netServer.close();
};
NodeTunes.prototype.stop = function() {
netServer.close();
};
};

@@ -63,0 +73,0 @@ NodeTunes.prototype.__proto__ = EventEmitter.prototype;

{
"name": "nodetunes",
"version": "0.0.4",
"version": "0.0.5",
"author": "Stephen Wan <stephen@stephenwan.net>",
"description": "AirTunes v2 Music Server",
"contributors": [
"contributors": [
{
"name": "Stephen Wan",
"email": "stephen@stephenwan.net"
}
}
],

@@ -19,15 +19,16 @@ "scripts": {

},
"dependencies" : {
"mdns2" : "*",
"ursa" : "*",
"ip" : "*",
"priorityqueuejs" : "*",
"portastic" : "*",
"randomstring" : "*",
"httplike" : "0.0.1"
"dependencies": {
"mdns2": "*",
"ursa": "*",
"ip": "*",
"priorityqueuejs": "*",
"portastic": "*",
"randomstring": "*",
"httplike": "0.0.1",
"random-mac": "0.0.4"
},
"devDependencies" : {
"speaker" : "*"
"devDependencies": {
"speaker": "*"
},
"license": "MIT"
}