-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add packages for browser input audio device selection (#77)
Add two new packages based on the microphone selection flow implemented in the portal - `@speechmatics/browser-audio-input`: - Contains just the store/singleton class managing audio devices. - Also provides a minified build in case someone wants to use this in an inline script tag - `@speechmatics/browser-audio-input-react`: - React bindings for the above package - Constrain the types of the main exported hook - Updated the NextJS example to use the react package **Note**: Both these packages export only ESM (apart from the raw client which also provides a minified build). My thinking was there are practically 0 CommonJS use cases for browser microphone selection, so I omitted it from the build.
- Loading branch information
Showing
13 changed files
with
497 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Browser audio input (React) | ||
|
||
React bindings for the `@speechmatics/browser-audio-input` package, letting you manage audio input devices and permissions across browsers. | ||
|
||
## Installation | ||
|
||
``` | ||
npm i @speechmatics/browser-audio-input-react | ||
``` | ||
|
||
## Usage | ||
|
||
Below is an example of a Microphone selection component. | ||
|
||
```TSX | ||
import { useAudioDevices } from "@speechmatics/browser-audio-input-react"; | ||
|
||
function MicrophoneSelect({ | ||
setDeviceId, | ||
}: { setDeviceId: (deviceId: string) => void }) { | ||
const devices = useAudioDevices(); | ||
|
||
switch (devices.permissionState) { | ||
case 'prompt': | ||
return ( | ||
<label> | ||
Enable mic permissions | ||
<select | ||
onClick={devices.promptPermissions} | ||
onKeyDown={devices.promptPermissions} | ||
/> | ||
</label> | ||
); | ||
case 'prompting': | ||
return ( | ||
<label> | ||
Enable mic permissions | ||
<select aria-busy="true" /> | ||
</label> | ||
); | ||
case 'granted': { | ||
const onChange = (e: ChangeEvent<HTMLSelectElement>) => { | ||
setDeviceId(e.target.value); | ||
}; | ||
return ( | ||
<label> | ||
Select audio device | ||
<select onChange={onChange}> | ||
{devices.deviceList.map((d) => ( | ||
<option key={d.deviceId} value={d.deviceId}> | ||
{d.label} | ||
</option> | ||
))} | ||
</select> | ||
</label> | ||
); | ||
} | ||
case 'denied': | ||
return ( | ||
<label> | ||
Microphone permission disabled | ||
<select disabled /> | ||
</label> | ||
); | ||
default: | ||
devices satisfies never; | ||
return null; | ||
} | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"name": "@speechmatics/browser-audio-input-react", | ||
"version": "0.0.1", | ||
"description": "React hooks for managing audio inputs and permissions across browsers", | ||
"exports": ["./dist/index.js"], | ||
"module": "./dist/index.js", | ||
"typings": "./dist/index.d.ts", | ||
"files": ["dist/", "README.md"], | ||
"scripts": { | ||
"build": "rm -rf dist/ && pnpm -C ../browser-audio-input build && pnpm rollup -c", | ||
"prepare": "pnpm build", | ||
"format": "biome format --write .", | ||
"lint": "biome lint --write ." | ||
}, | ||
"keywords": [ | ||
"Flow", | ||
"API", | ||
"React", | ||
"hooks", | ||
"transcription", | ||
"speech", | ||
"intelligence" | ||
], | ||
"dependencies": { | ||
"@speechmatics/browser-audio-input": "workspace:*" | ||
}, | ||
"author": "", | ||
"license": "MIT", | ||
"peerDependencies": { | ||
"react": "^18 || ^19" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.3.12", | ||
"typescript-event-target": "^1.1.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import esbuild from 'rollup-plugin-esbuild'; | ||
import dts from 'rollup-plugin-dts'; | ||
|
||
import packageJSON from './package.json' assert { type: 'json' }; | ||
|
||
// Based on gist | ||
//https://gist.github.com/aleclarson/9900ed2a9a3119d865286b218e14d226 | ||
|
||
/** @returns {import("rollup").RollupOptions[]} */ | ||
export default function rollup() { | ||
return [ | ||
{ | ||
plugins: [esbuild()], | ||
input: 'src/index.ts', | ||
output: [ | ||
{ | ||
file: packageJSON.module, | ||
format: 'es', | ||
sourcemap: true, | ||
strict: false, | ||
}, | ||
], | ||
}, | ||
|
||
{ | ||
plugins: [ | ||
dts({ | ||
compilerOptions: { | ||
removeComments: true, | ||
}, | ||
}), | ||
], | ||
input: 'src/index.ts', | ||
output: { | ||
file: `${packageJSON.module.replace('.js', '')}.d.ts`, | ||
}, | ||
}, | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { useCallback, useSyncExternalStore } from 'react'; | ||
import { getAudioDevicesStore } from '@speechmatics/browser-audio-input'; | ||
|
||
// Here we subscribe to the device state browser event | ||
// When devices change, the getDevices callback is invoked | ||
function subscribeDevices(callback: () => void) { | ||
const audioDevices = getAudioDevicesStore(); | ||
audioDevices.addEventListener('changeDevices', callback); | ||
return () => { | ||
audioDevices.removeEventListener('changeDevices', callback); | ||
}; | ||
} | ||
const getDevices = () => getAudioDevicesStore().devices; | ||
|
||
function useAudioDeviceList() { | ||
return useSyncExternalStore(subscribeDevices, getDevices, getDevices); | ||
} | ||
|
||
// Here we subscribe to the user's provided permissions | ||
// When the permission state changes, the useAudioDevices hook is called | ||
function subscribePermissionState(callback: () => void) { | ||
const audioDevices = getAudioDevicesStore(); | ||
audioDevices.addEventListener('changePermissions', callback); | ||
return () => { | ||
audioDevices.removeEventListener('changePermissions', callback); | ||
}; | ||
} | ||
const getPermissionState = () => getAudioDevicesStore().permissionState; | ||
function useAudioPermissionState() { | ||
return useSyncExternalStore( | ||
subscribePermissionState, | ||
getPermissionState, | ||
getPermissionState, | ||
); | ||
} | ||
|
||
function usePromptAudioPermission() { | ||
return useCallback(async () => { | ||
await getAudioDevicesStore().promptPermissions(); | ||
}, []); | ||
} | ||
|
||
export type AudioDevices = | ||
| { permissionState: 'prompt'; promptPermissions: () => void } | ||
| { permissionState: 'prompting' } | ||
| { | ||
permissionState: 'granted'; | ||
deviceList: ReadonlyArray<MediaDeviceInfo>; | ||
} | ||
| { permissionState: 'denied' }; | ||
|
||
export function useAudioDevices(): AudioDevices { | ||
const permissionState = useAudioPermissionState(); | ||
const promptPermissions = usePromptAudioPermission(); | ||
const deviceList = useAudioDeviceList(); | ||
|
||
switch (permissionState) { | ||
case 'prompt': | ||
return { | ||
permissionState, | ||
promptPermissions, | ||
}; | ||
case 'granted': | ||
return { | ||
permissionState, | ||
deviceList, | ||
}; | ||
case 'prompting': | ||
case 'denied': | ||
return { | ||
permissionState, | ||
}; | ||
default: | ||
permissionState satisfies never; | ||
throw new Error(`Unexpected permission state: ${permissionState}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../tsconfig.json" | ||
} |
Oops, something went wrong.