Skip to content

Commit 3925461

Browse files
Merge pull request #2101 from Gauravjeetsingh/voice-search
Request for mic access when using voice search
2 parents 3cd48ce + 0e9da8a commit 3925461

File tree

6 files changed

+125
-8
lines changed

6 files changed

+125
-8
lines changed

app.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,17 @@ i18n.init({
6565

6666
expressApp.use(express.static(path.join(__dirname, 'www', 'obs')));
6767

68-
const { app, webContents, BrowserWindow, dialog, ipcMain, safeStorage, globalShortcut } = electron;
68+
const {
69+
app,
70+
webContents,
71+
BrowserWindow,
72+
dialog,
73+
ipcMain,
74+
safeStorage,
75+
globalShortcut,
76+
systemPreferences,
77+
shell,
78+
} = electron;
6979

7080
const store = new Store({
7181
configName: 'user-preferences',
@@ -172,6 +182,7 @@ function openSecondaryWindow(windowName) {
172182
webviewTag: true,
173183
nodeIntegrationInSubFrames: true,
174184
nodeIntegrationInWorker: true,
185+
media: true,
175186
},
176187
});
177188
remote.enable(window.obj.webContents);
@@ -240,7 +251,7 @@ autoUpdater.on('update-downloaded', () => {
240251
cancelId: 0,
241252
})
242253
.then(({ response }) => {
243-
if (response === 1) {
254+
if (response === 1 || response === '1') {
244255
autoUpdater.quitAndInstall();
245256
}
246257
global.analytics.trackEvent({
@@ -361,6 +372,7 @@ function createViewer(ipcData) {
361372
webviewTag: true,
362373
nodeIntegrationInSubFrames: true,
363374
nodeIntegrationInWorker: true,
375+
media: true,
364376
},
365377
});
366378
viewerWindow.loadURL(`file://${__dirname}/www/viewer.html`);
@@ -606,6 +618,7 @@ app.on('ready', () => {
606618
webviewTag: true,
607619
nodeIntegrationInSubFrames: true,
608620
nodeIntegrationInWorker: true,
621+
media: true,
609622
},
610623
});
611624
const splash = new BrowserWindow({
@@ -884,6 +897,22 @@ ipcMain.on('set-user-setting', (event, settingChanger) => {
884897
mainWindow.webContents.send('set-user-setting', settingChanger);
885898
});
886899

900+
ipcMain.on('get-media-access-status', async (event, mediaType) => {
901+
try {
902+
const isGranted = await systemPreferences.askForMediaAccess(mediaType);
903+
if (!isGranted) {
904+
if (mediaType === 'microphone') {
905+
shell.openExternal(
906+
'x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone',
907+
);
908+
}
909+
}
910+
event.reply('media-access-status', isGranted ? 'granted' : 'denied');
911+
} catch (error) {
912+
event.reply('media-access-status', 'error');
913+
}
914+
});
915+
887916
module.exports = {
888917
openSecondaryWindow,
889918
appVersion,

entitlements.mac.inherit.plist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@
1010
<true/>
1111
<key>com.apple.security.cs.disable-library-validation</key>
1212
<true/>
13+
<key>com.apple.security.device.microphone</key>
14+
<true/>
15+
<key>com.apple.security.device.audio-input</key>
16+
<true/>
1317
</dict>
1418
</plist>

package.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
"build": "npm run build-css & npm run build-js",
1616
"build:local": "npm run build-css & npm run build-js:sm",
1717
"pack:mac": "npm run build && electron-builder --mac --x64",
18-
"pack:macArm": "npm run build && electron-builder --mac --arm64",
18+
"pack:macArm": "npm run build && electron-builder -c packaging/electron-builder.macArm.yml --mac --arm64 --publish never",
1919
"pack:win": "npm run build && electron-builder --win --x64",
2020
"pack:win32": "npm run build && electron-builder --win --ia32",
2121
"pack:linux": "npm run build && electron-builder --linux --x64",
2222
"pack:berry": "export USE_SYSTEM_FPM=true && npm run build && electron-builder --linux --arm64",
2323
"dist:mac": "npm run build && electron-builder --mac --x64 --publish always",
24-
"dist:macArm": "npm run build && electron-builder --mac --arm64 --publish always",
24+
"dist:macArm": "npm run build && electron-builder -c packaging/electron-builder.macArm.yml --mac --arm64 --publish always",
2525
"dist:win": "npm run pack:win && electron-builder --win --x64 --publish always",
2626
"dist:win32": "npm run pack:win32 && electron-builder --win --ia32 --publish always",
2727
"build-css": "run-p build-css:*",
@@ -177,14 +177,29 @@
177177
"category": "public.app-category.reference",
178178
"icon": "assets/STTM.icns",
179179
"hardenedRuntime": true,
180+
"artifactName": "SikhiToTheMax-${version}-${os}-${arch}.${ext}",
181+
"extendInfo": {
182+
"NSMicrophoneUsageDescription": "Please give us access to your microphone for voice search",
183+
"NSCameraUsageDescription": "Please give us access to your camera",
184+
"com.apple.security.device.audio-input": true,
185+
"com.apple.security.device.camera": true
186+
},
180187
"entitlements": "./entitlements.mac.inherit.plist",
188+
"entitlementsInherit": "./entitlements.mac.inherit.plist",
181189
"target": [
182190
{
183191
"target": "dmg",
184192
"arch": [
185193
"x64",
186194
"arm64"
187195
]
196+
},
197+
{
198+
"target": "zip",
199+
"arch": [
200+
"x64",
201+
"arm64"
202+
]
188203
}
189204
],
190205
"notarize": {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
publish:
2+
provider: s3
3+
bucket: sttm-releases
4+
path: mac-${arch}
5+
region: us-west-2
6+
7+
mac:
8+
category: "public.app-category.reference"
9+
icon: "assets/STTM.icns"
10+
hardenedRuntime: true
11+
artifactName: "SikhiToTheMax-${version}-${os}-${arch}.${ext}"
12+
entitlements: "./entitlements.mac.inherit.plist"
13+
target:
14+
- target: dmg
15+
arch:
16+
- arm64
17+
- target: zip
18+
arch:
19+
- arm64
20+
notarize:
21+
teamId: "B3W82WVBU9"
22+
afterSign: "./notarize.js"

www/locales/en.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,13 @@
196196
"TRANSLATION": "Translation",
197197
"TRANSLITERATION": "Transliteration"
198198
},
199+
"MICROPHONE_ERROR": {
200+
"NotAllowedError": "Microphone access was denied. Please check your system permissions and allow microphone access for this application.",
201+
"NotFoundError": "No microphone found. Please connect a microphone and try again.",
202+
"NotReadableError": "Microphone is being used by another application. Please close other applications using the microphone and try again.",
203+
"OverconstrainedError": "Microphone constraints could not be satisfied. Please try again.",
204+
"default": "Please check your microphone permissions and try again."
205+
},
199206
"SEARCH": {
200207
"ANG": "Ang",
201208
"FIRST_LETTER_ANYWHERE": "First Letter (Anywhere)",

www/main/navigator/search/components/SearchContent.jsx

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const SearchContent = () => {
6565
const [audioStream, setAudioStream] = useState(null);
6666
const [isTranscriptLoading, setIsTranscriptLoading] = useState(false);
6767
const [searchPending, setSearchPending] = useState(true);
68+
const [microphonePermissionStatus, setMicrophonePermissionStatus] = useState('unknown');
6869

6970
const sourcesObj = banidb.SOURCE_TEXTS;
7071
const writersObj = banidb.WRITER_TEXTS;
@@ -199,6 +200,17 @@ const SearchContent = () => {
199200
});
200201
}, []);
201202

203+
const checkMicrophonePermission = async () => {
204+
try {
205+
ipcRenderer.send('get-media-access-status', 'microphone');
206+
const permission = await navigator.permissions.query({ name: 'microphone' });
207+
return permission.state;
208+
} catch (error) {
209+
console.error('Error checking microphone permission:', error);
210+
return 'unknown';
211+
}
212+
};
213+
202214
useEffect(() => {
203215
const checkOnlineStatus = async () => {
204216
try {
@@ -210,8 +222,20 @@ const SearchContent = () => {
210222
};
211223

212224
checkOnlineStatus();
225+
checkMicrophonePermission();
213226
}, []);
214227

228+
const requestMicrophonePermission = () =>
229+
new Promise((resolve) => {
230+
const handlePermissionResponse = (event, status) => {
231+
ipcRenderer.removeListener('media-access-status', handlePermissionResponse);
232+
resolve(status);
233+
};
234+
235+
ipcRenderer.on('media-access-status', handlePermissionResponse);
236+
ipcRenderer.send('get-media-access-status', 'microphone');
237+
});
238+
215239
const handleMicClick = async () => {
216240
if (isRecording) {
217241
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
@@ -226,7 +250,16 @@ const SearchContent = () => {
226250
});
227251
} else {
228252
try {
229-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
253+
const permissionStatus = await requestMicrophonePermission();
254+
setMicrophonePermissionStatus(permissionStatus);
255+
256+
const stream = await navigator.mediaDevices.getUserMedia({
257+
audio: {
258+
echoCancellation: true,
259+
noiseSuppression: true,
260+
autoGainControl: true,
261+
},
262+
});
230263
const recorder = new MediaRecorder(stream);
231264
const chunks = [];
232265

@@ -314,15 +347,18 @@ const SearchContent = () => {
314347
label: 'start-recording',
315348
});
316349
} catch (error) {
317-
console.error('Error accessing microphone:', error);
350+
const errorMessage =
351+
i18n.t(`MICROPHONE_ERROR.${error.name}`) || i18n.t('MICROPHONE_ERROR.default');
352+
318353
analytics.trackEvent({
319354
category: 'search',
320355
action: 'voice-search',
321-
label: 'microphone-error',
356+
label: error.name,
322357
value: error.message,
323358
});
324359

325-
alert('Unable to access microphone. Please check permissions and try again.');
360+
// eslint-disable-next-line no-alert
361+
alert(errorMessage);
326362
}
327363
}
328364
};
@@ -373,6 +409,10 @@ const SearchContent = () => {
373409
<IconButton
374410
icon={isRecording ? 'fa fa-stop' : 'fa fa-microphone'}
375411
onClick={handleMicClick}
412+
style={{
413+
opacity: microphonePermissionStatus === 'denied' ? 0.5 : 1,
414+
cursor: microphonePermissionStatus === 'denied' ? 'not-allowed' : 'pointer',
415+
}}
376416
/>
377417
)}
378418
{currentLanguage !== 'en' && (

0 commit comments

Comments
 (0)