├── .gitignore ├── README.md ├── config.json ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shaircast 2 | ==================== 3 | *So your iPhone can play music all around your house* 4 | 5 | AirPlay is cool if you want to stream music from your phone to one room in your house. But who has a house with one room? iTunes will let you stream to multiple speakers, but who wants to only listen to what they have in iTunes on their computer? 6 | 7 | This is a *major* first world problem. shaircast solves this problem. shaircast is a super simple Node.js application that combines [nodetunes](https://github.com/stephen/nodetunes) and [node-airtunes](https://github.com/lperrin/node_airtunes) to take a single AirPlay stream (from your iPhone or iPad) and broadcast it to multiple AirPlay speakers. Oh, and it keeps everything in sync too! Like magic! 8 | 9 | I use a couple of Raspberry Pis plugged into speakers, running [shairport-sync](https://github.com/mikebrady/shairport-sync). shaircast also runs great on a Raspberry Pi 2, side by side with shairport-sync, so you don't even have to have a huge server to run it! 10 | 11 | ##How do I get it? 12 | You will need Node.js. And a computer. Hopefully you have one of those. 13 | 14 | > Note: Because of a project dependency, for now shaircast only works on Node.js 0.10.x. Hopefully in the future it will also work on Node.js 0.12.x. 15 | 16 | First, get the code from GitHub: 17 | 18 | $ git clone https://github.com/nguyer/shaircast 19 | 20 | Then install the dependencies: 21 | 22 | $ cd shaircast 23 | $ npm install 24 | 25 | ### Configuration 26 | 27 | There is a file called `config.json` in the application directory. You should modify it to list the IP addresses of the AirPlay speakers you wish to broadcast to. You can also set the name of the group in the config file. 28 | 29 | The config file looks like this: 30 | 31 | ```json 32 | { 33 | "groupName": "All Speakers", 34 | "endpoints": [ 35 | "192.168.1.116", 36 | "192.168.1.117" 37 | ] 38 | } 39 | ``` 40 | 41 | ### Starting the server 42 | Just run: 43 | 44 | $ npm start 45 | 46 | ### Connect and play! 47 | If your server and iOS device are on the same network, you should see a new AirPlay target called "All Speakers", or whatever you named your group in the `config.json`. Select that AirPlay target and start playing! 48 | 49 | ##To-Do 50 | This is very early still, and there are a few enhancements I would like to make such as: 51 | - Optimizing network performance over Wi-Fi (reduce audio dropouts) 52 | - Enabling multiple groups 53 | - Installation via `npm` and command line 54 | 55 | Please feel free to provide other suggestions, or report bugs on GitHub. 56 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "groupName": "All Speakers", 3 | "endpoints": [ 4 | "192.168.1.116", 5 | "192.168.1.117" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shaircast", 3 | "version": "0.0.1", 4 | "description": "So your iPhone can play music all around your house", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Nicko Guyer", 10 | "license": "MIT", 11 | "dependencies": { 12 | "airtunes": "^0.1.7", 13 | "nodetunes": "^0.1.2", 14 | "speaker": "^0.2.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"); 4 | var path = require("path"); 5 | var AirTunesServer = require("nodetunes"); 6 | var airtunes = require("airtunes"); 7 | 8 | var config = JSON.parse(fs.readFileSync("config.json")); 9 | var server = new AirTunesServer({ serverName : config.groupName }); 10 | var endpoints = config.endpoints; 11 | var devices; 12 | var currentStream; 13 | 14 | server.on("clientConnected", function(stream) { 15 | console.log("clientConnected"); 16 | devices = []; 17 | endpoints.forEach(function(host) { 18 | devices.push(airtunes.add(host)); 19 | }); 20 | currentStream = stream; 21 | stream.pipe(airtunes); 22 | }); 23 | 24 | server.on("clientDisconnected", function() { 25 | console.log("clientDisconnected"); 26 | currentStream.unpipe(); 27 | airtunes.stopAll(function() { 28 | console.log("All devices stopped") 29 | }); 30 | }); 31 | 32 | server.on("error", function(err) { 33 | console.log("Error: " + err.message ); 34 | }); 35 | 36 | server.on("metadataChange", function(metadata) { 37 | console.log("Now playing \"" + metadata.minm + "\" by " + metadata.asar + " from \"" + metadata.asal + "\""); 38 | }); 39 | 40 | server.on("volumeChange", function(volume) { 41 | volume = (volume + 30) / 30 * 100 42 | console.log("volumeChange " + volume); 43 | devices.forEach(function(device) { 44 | device.setVolume(volume); 45 | }); 46 | }); 47 | 48 | server.start(); 49 | 50 | console.log(config.groupName + " started"); 51 | --------------------------------------------------------------------------------