airtunes2
Version:
an AirTunes v2 implementation: stream wirelessly to audio devices.
126 lines (97 loc) • 3.24 kB
JavaScript
var events = require('events'),
util = require('util'),
config = require('./config.js'),
bindings = require('../build/Release/airtunes');
function CoreAudioDevice(hasAirTunes, audioOut, options) {
events.EventEmitter.call(this);
this.audioOut = audioOut;
this.type = 'coreaudio';
this.key = 'coreaudio';
this.coreAudio = null;
this.latency = 0;
this.started = false;
this.audioCallback = null;
this.setHasAirTunes(hasAirTunes);
this.volume = options.volume || 50;
this.bufferLevelTimer = null;
this.status = 'init';
}
util.inherits(CoreAudioDevice, events.EventEmitter);
CoreAudioDevice.prototype.start = function(hideStatus) {
if(this.started)
return;
var self = this;
this.started = true;
var elapsed = new Date().getTime() - config.rtp_time_ref;
var elapsedFrames = Math.floor(elapsed*config.sampling_rate/1000);
var caTimeRef = this.latency + this.audioOut.lastSeq*config.frames_per_packet - elapsedFrames;
this.coreAudio = bindings.newCoreAudio();
/*
* Since the AudioQueue consumes data as fast as we send it, the internal buffer never
* has a chance to fill. We add this margin to avoid ever draining the buffer.
* This is done in a separate scope to avoid retaining the buffer in the closure.
*/
function checkBufferLevel() {
if(bindings.getBufferLevel(self.coreAudio) <= config.coreaudio_min_level) {
var silence = new Buffer(config.coreaudio_preload);
silence.fill(0);
bindings.enqueuePacket(self.coreAudio, silence, silence.length);
}
self.bufferLevelTimer = setTimeout(checkBufferLevel, config.coreaudio_check_period);
}
checkBufferLevel();
bindings.play(this.coreAudio, caTimeRef);
this.setVolume(this.volume);
this.status = 'ready';
if(!hideStatus)
this.emit('status', 'ready');
this.audioCallback = function(packet) {
bindings.enqueuePacket(self.coreAudio, packet.pcm, packet.pcm.length);
};
this.audioOut.on('packet', this.audioCallback);
}
CoreAudioDevice.prototype.reportStatus = function(){
this.emit('status', this.status);
};
CoreAudioDevice.prototype.setHasAirTunes = function(hasAirTunes) {
this.latency = hasAirTunes ?
11025 + 2*config.sampling_rate :
11025;
// restart to update audio latency
if(this.started) {
this.cleanup();
this.start(true);
}
};
CoreAudioDevice.prototype.stop = function(cb) {
this.cleanup();
this.status = 'stopped';
this.emit('status', 'stopped');
this.removeAllListeners();
if(cb)
cb();
};
CoreAudioDevice.prototype.setVolume = function(volume) {
if(this.coreAudio)
bindings.setVolume(this.coreAudio, volume);
};
CoreAudioDevice.prototype.getInternalBufferLevel = function() {
if(this.coreAudio)
return bindings.getBufferLevel(this.coreAudio);
};
CoreAudioDevice.prototype.cleanup = function() {
if(!this.started)
return;
this.started = false;
if(this.audioCallback) {
this.audioOut.removeListener('packet', this.audioCallback);
this.audioCallback = null;
}
if(this.bufferLevelTimer) {
clearTimeout(this.bufferLevelTimer);
this.bufferLevelTimer = null;
}
bindings.stop(this.coreAudio);
this.coreAudio = null;
};
module.exports = CoreAudioDevice;