Skip to content

Commit

Permalink
Add Microphone option in cog menu (#859)
Browse files Browse the repository at this point in the history
  • Loading branch information
karaggeorge authored Jun 26, 2020
1 parent bd0ff3f commit 3788a42
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 146 deletions.
30 changes: 4 additions & 26 deletions main/common/aperture.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@ const {closePrefsWindow} = require('../preferences');
const {setRecordingTray, disableTray, resetTray} = require('../tray');
const {disableCroppers, setRecordingCroppers, closeAllCroppers} = require('../cropper');
const {setCropperShortcutAction} = require('../global-accelerators');
const {getInputDevices} = require('macos-audio-devices');

// eslint-disable-next-line no-unused-vars
const {convertToH264} = require('../utils/encoding');

const {hasMicrophoneAccess} = require('./system-permissions');

const settings = require('./settings');
const {track} = require('./analytics');
const plugins = require('./plugins');
const {getAudioDevices} = require('../utils/devices');
const {showError} = require('../utils/errors');
const {RecordServiceContext} = require('../service-context');

const aperture = createAperture();
const {audioDevices, videoCodecs} = createAperture;
const {videoCodecs} = createAperture;

// eslint-disable-next-line no-unused-vars
const recordHevc = videoCodecs.has('hevc');
Expand Down Expand Up @@ -85,8 +83,7 @@ const startRecording = async options => {
record60fps,
showCursor,
highlightClicks,
recordAudio,
audioInputDeviceId
recordAudio
} = settings.store;

apertureOptions = {
Expand All @@ -104,6 +101,7 @@ const startRecording = async options => {
if (recordAudio === true) {
// In case for some reason the default audio device is not set
// use the first available device for recording
const audioInputDeviceId = settings.getSelectedInputDeviceId();
if (audioInputDeviceId) {
apertureOptions.audioDeviceId = audioInputDeviceId;
} else {
Expand Down Expand Up @@ -208,26 +206,6 @@ const stopRecording = async () => {
}
};

const getAudioDevices = async () => {
if (hasMicrophoneAccess()) {
try {
let devices = await audioDevices();

if (Array.isArray(devices)) {
return devices;
}

devices = await getInputDevices();
return devices.map(({uid, name}) => ({id: uid, name}));
} catch (error) {
showError(error, {reportToSentry: true});
return [];
}
}

return [];
};

module.exports = {
startRecording,
stopRecording,
Expand Down
5 changes: 4 additions & 1 deletion main/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@

const supportedVideoExtensions = ['mp4', 'mov', 'm4v'];

module.exports = {supportedVideoExtensions};
module.exports = {
supportedVideoExtensions,
defaultInputDeviceId: 'SYSTEM_DEFAULT'
};
2 changes: 1 addition & 1 deletion main/common/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Plugins {
plugin => plugin.recordServices.map(service => ({
...service,
isEnabled: recordPluginServiceState.get(service.title) || false,
toggleEnbaled: () => this.enableService(service)
toggleEnabled: () => this.enableService(service)
}))
)
);
Expand Down
31 changes: 16 additions & 15 deletions main/common/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
const {homedir} = require('os');
const Store = require('electron-store');

const {getInputDevices} = require('macos-audio-devices');
const {defaultInputDeviceId} = require('./constants');
const {hasMicrophoneAccess} = require('./system-permissions');
const {audioDevices} = require('aperture');
const {getAudioDevices, getDefaultInputDevice} = require('../utils/devices');

const store = new Store({
schema: {
Expand Down Expand Up @@ -46,7 +46,7 @@ const store = new Store({
'string',
'null'
],
default: null
default: defaultInputDeviceId
},
cropperShortcut: {
type: 'object',
Expand Down Expand Up @@ -85,20 +85,21 @@ const audioInputDeviceId = store.get('audioInputDeviceId');

if (hasMicrophoneAccess()) {
(async () => {
let devices = await audioDevices();

if (!Array.isArray(devices)) {
const Sentry = require('../utils/sentry');
Sentry.captureException(new Error(`devices is not an array: ${JSON.stringify(devices)}`));

devices = (await getInputDevices()).map(device => ({id: device.uid}));
}
const devices = await getAudioDevices();

if (!devices.some(device => device.id === audioInputDeviceId)) {
const [device] = devices;
if (device) {
store.set('audioInputDeviceId', device.id);
}
store.set('audioInputDeviceId', defaultInputDeviceId);
}
})();
}

module.exports.getSelectedInputDeviceId = () => {
const audioInputDeviceId = store.get('audioInputDeviceId', defaultInputDeviceId);

if (audioInputDeviceId === defaultInputDeviceId) {
const device = getDefaultInputDevice();
return device.id;
}

return audioInputDeviceId;
};
61 changes: 50 additions & 11 deletions main/menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ const {openNewGitHubIssue, appMenu} = require('electron-util');
const {ipcMain: ipc} = require('electron-better-ipc');
const delay = require('delay');

const {supportedVideoExtensions} = require('./common/constants');
const {supportedVideoExtensions, defaultInputDeviceId} = require('./common/constants');
const settings = require('./common/settings');
const {hasMicrophoneAccess} = require('./common/system-permissions');
const {getAudioDevices, getDefaultInputDevice} = require('./utils/devices');
const {ensureDockIsShowing} = require('./utils/dock');
const {openPrefsWindow} = require('./preferences');
const {openExportsWindow} = require('./exports');
Expand Down Expand Up @@ -82,10 +85,12 @@ const aboutItem = {
}
};

let isExportsItemEnabled = false;

const exportHistoryItem = {
label: 'Export History',
click: openExportsWindow,
enabled: false,
enabled: isExportsItemEnabled,
id: 'exports'
};

Expand All @@ -104,7 +109,45 @@ const getPluginsItem = () => ({
visible: pluginsItems.length > 0
});

const getCogMenuTemplate = () => [
const getMicrophoneItem = async () => {
const devices = await getAudioDevices();
const isRecordAudioEnabled = settings.get('recordAudio');
const currentDefaultDevice = getDefaultInputDevice();

let audioInputDeviceId = settings.get('audioInputDeviceId');
if (!devices.some(device => device.id === audioInputDeviceId)) {
settings.set('audioInputDeviceId', defaultInputDeviceId);
audioInputDeviceId = defaultInputDeviceId;
}

return {
id: 'devices',
label: 'Microphone',
submenu: [
{
label: 'None',
type: 'checkbox',
checked: !isRecordAudioEnabled,
click: () => settings.set('recordAudio', false)
},
...[
{name: `System Default (${currentDefaultDevice.name})`, id: defaultInputDeviceId},
...devices
].map(device => ({
label: device.name,
type: 'checkbox',
checked: isRecordAudioEnabled && audioInputDeviceId === device.id,
click: () => {
settings.set('recordAudio', true);
settings.set('audioInputDeviceId', device.id);
}
}))
],
visible: hasMicrophoneAccess()
};
};

const getCogMenuTemplate = async () => [
aboutItem,
{
type: 'separator'
Expand All @@ -114,6 +157,7 @@ const getCogMenuTemplate = () => [
type: 'separator'
},
getPluginsItem(),
await getMicrophoneItem(),
{
type: 'separator'
},
Expand Down Expand Up @@ -192,26 +236,21 @@ const appMenuTemplate = [
}
];

let cogMenu = Menu.buildFromTemplate(getCogMenuTemplate());
const cogExportsItem = cogMenu.getMenuItemById('exports');

const refreshRecordPluginItems = services => {
pluginsItems = services.map(service => ({
label: service.title,
type: 'checkbox',
checked: service.isEnabled,
click: service.toggleEnbaled
click: service.toggleEnabled
}));

cogMenu = Menu.buildFromTemplate(getCogMenuTemplate());
};

const appMenu_ = Menu.buildFromTemplate(appMenuTemplate);
const appExportsItem = appMenu_.getMenuItemById('exports');
const appSaveOriginalItem = appMenu_.getMenuItemById('saveOriginal');

const toggleExportMenuItem = enabled => {
cogExportsItem.enabled = enabled;
isExportsItemEnabled = enabled;
appExportsItem.enabled = enabled;
};

Expand All @@ -227,7 +266,7 @@ editorEmitter.on('focus', () => {
appSaveOriginalItem.visible = true;
});

const getCogMenu = () => cogMenu;
const getCogMenu = async () => Menu.buildFromTemplate(await getCogMenuTemplate());

module.exports = {
getCogMenu,
Expand Down
4 changes: 2 additions & 2 deletions main/tray.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const openFiles = require('./utils/open-files');
let tray = null;
let trayAnimation = null;

const openContextMenu = () => {
tray.popUpContextMenu(getCogMenu());
const openContextMenu = async () => {
tray.popUpContextMenu(await getCogMenu());
};

const initializeTray = () => {
Expand Down
52 changes: 52 additions & 0 deletions main/utils/devices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

const audioDevices = require('macos-audio-devices');
const aperture = require('aperture');

const {showError} = require('./errors');
const {hasMicrophoneAccess} = require('../common/system-permissions');

const getAudioDevices = async () => {
if (!hasMicrophoneAccess()) {
return [];
}

try {
const devices = await audioDevices.getInputDevices();

return devices.sort((a, b) => {
if (a.transportType === b.transportType) {
return a.name.localeCompare(b.name);
}

if (a.transportType === 'builtin') {
return -1;
}

if (b.transportType === 'builtin') {
return 1;
}

return 0;
}).map(device => ({id: device.uid, name: device.name}));
} catch (error) {
const devices = await aperture.audioDevices();

if (!Array.isArray(devices)) {
const Sentry = require('./sentry');
Sentry.captureException(new Error(`devices is not an array: ${JSON.stringify(devices)}`));
showError(error, {reportToSentry: true});
return [];
}
}
};

const getDefaultInputDevice = () => {
const device = audioDevices.getDefaultInputDevice.sync();
return {
id: device.uid,
name: device.name
};
};

module.exports = {getAudioDevices, getDefaultInputDevice};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"mac-open-with": "^1.2.3",
"mac-screen-capture-permissions": "^1.1.0",
"mac-windows": "^0.7.1",
"macos-audio-devices": "^1.3.1",
"macos-audio-devices": "^1.3.2",
"macos-version": "^5.2.0",
"make-dir": "^3.1.0",
"moment": "^2.24.0",
Expand Down
7 changes: 6 additions & 1 deletion renderer/components/action-bar/controls/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ MainControls.Left = connect(
)(Left);

class Right extends React.Component {
onCogMenuClick = async () => {
const cogMenu = await electron.remote.require('./menus').getCogMenu();
cogMenu.popup();
}

render() {
const {enterFullscreen, exitFullscreen, isFullscreen, advanced} = this.props;

Expand All @@ -97,7 +102,7 @@ class Right extends React.Component {
<FullscreenIcon tabIndex={advanced ? -1 : 0} onClick={enterFullscreen}/>
}
</div>
<IconMenu isMenu icon={MoreIcon} tabIndex={advanced ? -1 : 0} onOpen={electron.remote.require('./menus').getCogMenu().popup}/>
<IconMenu isMenu icon={MoreIcon} tabIndex={advanced ? -1 : 0} onOpen={this.onCogMenuClick}/>
<style jsx>{mainStyle}</style>
<style jsx>{`
.fullscreen {
Expand Down
Loading

0 comments on commit 3788a42

Please sign in to comment.