Skip to content

Commit

Permalink
initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
fardog committed Sep 20, 2014
0 parents commit 9113426
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .editorconfig
@@ -0,0 +1,14 @@
# http://editorconfig.org
root = true

[*]
indent_style = tab
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true

[*.js]
indent_style = space
indent_size = 2
26 changes: 26 additions & 0 deletions .gitignore
@@ -0,0 +1,26 @@
.DS_*
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
.sass-cache
.tmp
.env

pids
logs
results

npm-debug.log
node_modules
/working/
config.json

bower_components
www
/datasets/
/build/
22 changes: 22 additions & 0 deletions LICENSE
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2014 Nathan Wittstock

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

70 changes: 70 additions & 0 deletions README.md
@@ -0,0 +1,70 @@
# chromecast-osx-audio v0.0.1

Streams Mac OS X audio input to a local Chromecast device.

## Installation

To install the module for use in your projects:

```bash
npm install -g chromecast-osx-audio
```

## Usage

Pipe input to a writeable file stream:

```js
var fs = require('fs');
var audio = require('osx-audio');

var input = audio.createReadStream();

var writable = fs.createWriteStream('output.txt');
input.pipe(writable);
```

### Options

None yet.

## Environment Variables

None yet.

## Known Issues

Too early to know.

## Contributing

Feel free to send pull requests! I'm not picky, but would like the following:

1. Write tests for any new features, and do not break existing tests.
2. Be sure to point out any changes that break API.

## History

- **v0.0.1**
Initial Release.

## The MIT License (MIT)

Copyright (c) 2014 Nathan Wittstock

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 changes: 28 additions & 0 deletions bin/chromecast.js
@@ -0,0 +1,28 @@
#!/usr/bin/env node
/**
* chromecast commandline utility.
*
* @since 0.0.1
*/
'use strict';

var fs = require('fs');
var chalk = require('chalk');
var error = chalk.bold.red;
var Cli = require('../lib/cli.js');

var cli = new Cli().parse(process.argv.slice(2), function(err, message, options) {
if (err) {
console.error(error('\nYou had errors in your syntax. Use --help for further information.'));
err.forEach(function (e) {
console.error(e.message);
});
}
else if (message) {
console.log(message);
}
else {
var chromecast = require('../')(options);
}
});

111 changes: 111 additions & 0 deletions index.js
@@ -0,0 +1,111 @@
var Chromecast = function(options) {

var lame = require('lame');
var audio = require('osx-audio');
var fs = require('fs');

// we need to get the address of the local interface
var ip = null;
var interfaces = require('os').networkInterfaces();
for (dev in interfaces) {
interfaces[dev].forEach(function(a) {
if (a.family === 'IPv4' && a.internal === false) {
ip = a.address;
}
});
}

// create the Encoder instance
var encoder = new lame.Encoder({
// input
channels: 2, // 2 channels (left and right)
bitDepth: 16, // 16-bit samples
sampleRate: 44100, // 44,100 Hz sample rate

// output
bitRate: options.bitrate,
outSampleRate: options.samplerate,
mode: (options.mono ? lame.MONO : lame.STEREO) // STEREO (default), JOINTSTEREO, DUALCHANNEL or MONO
});

var input = audio.createReadStream();
input.pipe(encoder);

// set up an express app
var express = require('express')
var app = express()

app.get('/', function(req, res) {
res.send('Nope.');
});

app.get('/stream.mp3', function (req, res) {
res.set({
'Content-Type': 'audio/mpeg3',
'Transfer-Encoding': 'chunked'
});
encoder.pipe(res);
});

var server = app.listen(options.port);


var Client = require('castv2-client').Client;
var DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver;
var mdns = require('mdns');

var browser = mdns.createBrowser(mdns.tcp('googlecast'));

browser.on('serviceUp', function(service) {
console.log('found device "%s" at %s:%d', service.name, service.addresses[0], service.port);
ondeviceup(service.addresses[0]);
browser.stop();
});

browser.start();

function ondeviceup(host) {

var client = new Client();

client.connect(host, function() {
console.log('connected, launching app ...');

client.launch(DefaultMediaReceiver, function(err, player) {
var media = {

// Here you can plug an URL to any mp4, webm, mp3 or jpg file with the proper contentType.
contentId: 'http://' + ip + ':' + server.address().port + '/stream.mp3',
contentType: 'audio/mpeg3',
streamType: 'LIVE', // or LIVE

// Title and cover displayed while buffering
metadata: {
type: 0,
metadataType: 0,
title: options.name
}
};

player.on('status', function(status) {
console.log('status broadcast playerState=%s', status.playerState);
});

console.log('app "%s" launched, loading media %s ...', player.session.displayName, media.contentId);

player.load(media, { autoplay: true }, function(err, status) {
console.log('media loaded playerState=%s', status.playerState);
});
});

});

client.on('error', function(err) {
console.log('Error: %s', err.message);
client.close();
});

}
}

module.exports = Chromecast;
101 changes: 101 additions & 0 deletions lib/cli.js
@@ -0,0 +1,101 @@
'use strict';

var fs = require('fs');
var chalk = require('chalk');
var parseArgs = require('minimist');

// Number.isInteger() polyfill ::
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
if (!Number.isInteger) {
Number.isInteger = function isInteger (nVal) {
return typeof nVal === "number" && isFinite(nVal) && nVal > -9007199254740992 && nVal < 9007199254740992 && Math.floor(nVal) === nVal;
};
}

var cli = function(options) {
this.options = {
alias: {
port: 'p',
bitrate: 'b',
mono: 'm',
samplerate: 's',
name: 'n',
version: 'v',
help: 'h'
},
default: {
port: 3000,
bitrate: 192,
samplerate: 44100,
name: 'Chrome OSX Audio Stream'
},
'boolean': ['version', 'help', 'mono'],
'string': ['name'],
'integer': ['port', 'bitrate', 'samplerate']
};

this.errors = [];
this.message = null;

this.helpMessage = [
chalk.bold.blue("Usage: chromecast [options]"),
"",
"Options:",
" -p, --port The port that the streaming server will listen on. [3000]",
" -b, --bitrate The bitrate for the mp3 encoded stream. [192]",
" -m, --mono The stream defaults to stereo. Set to mono with this flag.",
" -s, --samplerate The sample rate for the mp3 encoded stream [44100]",
" -n, --name A name for the server to report itself as. [Chrome OSX Audio Stream]",
" --version print version and exit"
];

return this;
};

cli.prototype.parse = function(argv, next) {
var options = parseArgs(argv, this.options);

if (options.version) {
var pkg = require('../package.json');
this.message = "version " + pkg.version;
}
else if (options.help) {
this.message = this.helpMessage.join('\n');
}
else {
/*
* Options are processed in a significant order; we only save the last error
* message, so we'll want to make sure the most significant are last
*/

// ensure that parameter-expecting options have parameters
this.options['string'].forEach(function(i) {
if(typeof options[i] !== 'undefined') {
if (typeof options[i] !== 'string' || options[i].length < 1) {
this.errors.push(new Error(i + " expects a value."));
}
}
}.bind(this));

// ensure that number-expecting options have parameters
this.options['integer'].forEach(function(i) {
if(typeof options[i] !== 'undefined') {
if (!Number.isInteger(options[i])) {
this.errors.push(new Error(i + " expects an integer value."));
}
}
}.bind(this));
}

this.parsedOptions = options;

if (typeof next === 'function') {
// we return the array of errors if there are any, otherwise null
next(this.errors.length > 0 ? this.errors : null, this.message, options);
}

return this;
};


module.exports = cli;

0 comments on commit 9113426

Please sign in to comment.