Skip to content

Commit

Permalink
Adding stream switcher
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesmorgan committed Jan 27, 2016
1 parent 6232e1d commit 8e06c6b
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 16 deletions.
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Primarily driven from the need to stream audio from my Mac Book to a set of rece
After having issues with bluetooth connectivity and problems with the range bluetooth offers, I created this small application.
This applications aims to eliminate this restrictions of bluetooth's range by using UPNP over WIFI for better performance, namely distance from source to speaker.

![V0.1.0](/screenshots/screenshot-v0.1.0-menu.png "V0.1.0")
![0.3.0 Device Menu](/screenshots/screenshot-v0.3.0-device-menu.png "0.3.0 Device Menu")
![0.3.0 Stream Menu](/screenshots/screenshot-v0.3.0-stream-menu.png "0.3.0 Stream Menu")

### Installation

Expand Down Expand Up @@ -65,25 +66,27 @@ lsof -i :3000
* Manually install `npm install webcast-osx-audio -save` and re-run.

#### Release Notes
* [2016-01-08] - `v0.1.0`
* [2016-01-08] - `0.1.0`
* initial release - basic support for redirection of Mac OSX Audio to UPNP Jongo speaker

* [2016-01-13] - `_v0.2.0_`
* [2016-01-13] - `_0.2.0_`
* Look at adjusting stream quality - providing options
* Allow start/stop of casting & switching to other devices once stopped
* Notifications on start/stop streaming

* [2016-01-xx] - `_v0.3.0_`
* [2016-01-27] - `_0.3.0_`
* Enable logging to file - remove console.log()
* Verbose mode - enabled logging of unknown devices e.g. logging of device
* Allow streaming over microphone over speakers (very doable but is a required feature?)

* [2016-XX-XX] - `_0.4.0_`
* Allow casting to Chromecast
* Allow casting to Chromecast Audio

#### TODO

_Future_
* Allow casting to Chromecast
* Allow casting to Chromecast Audio
* Allow general discovery mode where every device is logged out e.g. [-d]
* Allow streaming over microphone over speakers (very doable but is a required feature?)
* Ability to 'Refresh Devices'
* Allow casting to Sonos speakers - https://github.com/bencevans/node-sonos
* Allow casting to Ruko - https://github.com/TheThingSystem/node-roku
Expand Down
26 changes: 26 additions & 0 deletions lib/native/MenuFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ var about = function () {
});
};

var aboutStreamFeature = function () {
logger.debug('Adding About Stream Feature Item');
return new MenuItem({
id: 'about-stream',
label: 'About This Feature',
click: function () {
dialog.showMessageBox({
title: 'Stream Selection',
message: 'You can cast OSX Audio Output (default) or you\'re Internal Microphone.',
detail: 'Casting the Internal Microphone turns the speaker into a mega-phone!',
buttons: ["OK"]
});
}
});
};

var quit = function (cb) {
logger.debug('Adding Quit Menu Item');
return new MenuItem({
Expand Down Expand Up @@ -92,6 +108,14 @@ var castToDeviceMenu = function (menu) {
})
};

var steamMenu = function (menu) {
logger.debug('Adding stream menu');
return new MenuItem({
label: 'Stream',
submenu: menu
});
};

var scanningForDevices = function () {
logger.debug('Adding Scanning for Devices...');
return new MenuItem({
Expand All @@ -103,11 +127,13 @@ module.exports = {
setSpeaker: setSpeaker,
removeSpeaker: removeSpeaker,
about: about,
aboutStreamFeature: aboutStreamFeature,
quit: quit,
scanningForDevices: scanningForDevices,
separator: separator,
sonosDeviceItem: sonosDeviceItem,
castToDeviceMenu: castToDeviceMenu,
steamMenu: steamMenu,
jongoDeviceItem: jongoDeviceItem,
chromeCastItem: chromeCastItem,
chromeCastAudioItem: chromeCastAudioItem
Expand Down
14 changes: 14 additions & 0 deletions lib/native/NotificationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,21 @@ var notifyCastingStopped = function (device) {
});
};

var notify = function (options) {
notifier.notify({
title: options.title,
message: options.message,
icon: path.join(__dirname, 'not-castingTemplate.png'),
//appIcon: path.join(__dirname, 'not-castingTemplate.png'),
//contentImage: path.join(__dirname, 'not-castingTemplate.png'),
//sender: path.join(__dirname, 'not-castingTemplate.png'),
wait: false,
sticky: false
});
};

module.exports = {
notify: notify,
notifyCastingStarted: notifyCastingStarted,
notifyCastingStopped: notifyCastingStopped
};
58 changes: 49 additions & 9 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ mb.on('ready', function ready() {

switch (device.type) {
case DeviceMatcher.TYPES.CHROMECAST:

if (DeviceMatcher.isChromecast(device) || DeviceMatcher.isChromecastAudio(device)) {
devicesAdded.push(device);
deviceListMenu.append(MenuFactory.chromeCastItem(device, function onClicked() {
Expand Down Expand Up @@ -129,22 +130,50 @@ mb.on('ready', function ready() {
mb.tray.setContextMenu(menu);
});

// Stream Options
var streamMenu = new Menu();
streamMenu.append(new MenuItem({
label: 'OSX Output (default)',
click: function () {
LocalSourceSwitcher.switchSource({
output: 'Soundflower (2ch)',
input: 'Soundflower (2ch)'
});
NotificationService.notify({
title: 'Audio Source Switched',
message: 'OSX Audio via Soundflower'
});
}
}));
streamMenu.append(new MenuItem({
label: 'Internal Microphone',
click: function () {
LocalSourceSwitcher.switchSource({
output: 'Soundflower (2ch)',
input: 'Internal Microphone'
});
NotificationService.notify({
title: 'Audio Source Switched',
message: 'Internal Microphone via Soundflower'
});
}
}));
streamMenu.append(MenuFactory.separator());
streamMenu.append(MenuFactory.aboutStreamFeature());

// Streaming Menu
menu.append(MenuFactory.separator());
menu.append(MenuFactory.steamMenu(streamMenu));
menu.append(MenuFactory.separator());

//Clicking this option stops casting audio to Chromecast
menu.append(new MenuItem({
label: 'Stop casting',
enabled: true, // default disabled as not initially playing
click: function () {

// Attempt to stop all controls
devicesAdded.forEach(function (device) {
if (device && device.controls && _.isFunction(device.controls.stop)) {
device.controls.stop(function (err, result) {
// do something...
});
} else {
logger.debug('Unknown handled device', _.keys(device))
}
});
attemptToStopAllDevices();

// Clean up playing speaker icon
deviceListMenu.items.forEach(MenuFactory.removeSpeaker);
Expand All @@ -168,6 +197,16 @@ mb.on('ready', function ready() {
item.enabled = false
};

var attemptToStopAllDevices = function () {
devicesAdded.forEach(function (device) {
if (_.has(device, 'controls') && _.isFunction(device.controls.stop)) {
device.controls.stop(function (err, result) {
// do something...
});
}
});
};

var setSpeakIcon = function (item) {
if (item.label === this.device.name) {
MenuFactory.setSpeaker(item);
Expand All @@ -178,6 +217,7 @@ mb.on('ready', function ready() {

var onQuitHandler = function () {
mb.tray.setImage(path.join(__dirname, 'not-castingTemplate.png'));
attemptToStopAllDevices();
LocalSoundStreamer.stopStream();
LocalSourceSwitcher.resetOriginSource();
mb.app.quit();
Expand Down
Binary file added screenshots/screenshot-v0.3.0-device-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/screenshot-v0.3.0-stream-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8e06c6b

Please sign in to comment.