Skip to content

Commit 57f4da4

Browse files
committed
Handle live microphone permission changes in settings dialog
1 parent b4cc466 commit 57f4da4

File tree

1 file changed

+84
-6
lines changed

1 file changed

+84
-6
lines changed

react/features/device-selection/components/AudioDevicesSelection.web.tsx

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,15 @@ interface IProps extends AbstractDialogTabProps, WithTranslation {
134134
*/
135135
selectedAudioOutputId: string;
136136
}
137-
137+
/**
138+
* The type of the React {@code Component} state of {@link AudioDeviceSelection}.
139+
*/
140+
interface IState{
141+
/**
142+
* Whether microphone permission is granted locally.
143+
*/
144+
localHasAudioPermission: boolean | null;
145+
}
138146

139147
const styles = (theme: Theme) => {
140148
return {
@@ -182,7 +190,7 @@ const styles = (theme: Theme) => {
182190
*
183191
* @augments Component
184192
*/
185-
class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
193+
class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
186194

187195
/**
188196
* Whether current component is mounted or not.
@@ -195,6 +203,15 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
195203
*/
196204
_unMounted: boolean;
197205

206+
/**
207+
* Stores the current microphone permission status.
208+
*
209+
* This is used to track whether audio permissions are granted, denied,
210+
* or still unknown, so that the component can react correctly when the
211+
* permission state changes.
212+
*/
213+
_permissionStatus: PermissionStatus | null = null;
214+
198215
/**
199216
* Initializes a new DeviceSelection instance.
200217
*
@@ -204,7 +221,13 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
204221
constructor(props: IProps) {
205222
super(props);
206223

224+
this.state = {
225+
localHasAudioPermission : null
226+
}
227+
207228
this._unMounted = true;
229+
this._onPermissionChange = this._onPermissionChange.bind(this);
230+
208231
}
209232

210233
/**
@@ -214,6 +237,9 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
214237
*/
215238
override componentDidMount() {
216239
this._unMounted = false;
240+
241+
this._setupPermissionListener();
242+
217243
Promise.all([
218244
this._createAudioInputTrack(this.props.selectedAudioInputId)
219245
])
@@ -245,6 +271,48 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
245271
override componentWillUnmount() {
246272
this._unMounted = true;
247273
disposeTrack(this.props.previewAudioTrack);
274+
275+
if (this._permissionStatus) {
276+
this._permissionStatus.removeEventListener('change', this._onPermissionChange);
277+
}
278+
}
279+
280+
/**
281+
* Sets up listener for microphone permission changes.
282+
*/
283+
async _setupPermissionListener() {
284+
try {
285+
this._permissionStatus = await navigator.permissions.query({
286+
name: 'microphone' as PermissionName
287+
});
288+
289+
// This is the initial permission state
290+
this.setState({
291+
localHasAudioPermission: this._permissionStatus.state === 'granted'
292+
});
293+
294+
this._permissionStatus.addEventListener('change', this._onPermissionChange);
295+
} catch (error) {
296+
console.warn(' Permissions API not supported for AudioDeviceSelection ', error);
297+
}
298+
}
299+
300+
301+
/**
302+
* Called when microphone permission changes.
303+
*/
304+
_onPermissionChange() {
305+
if (this._unMounted || !this._permissionStatus) {
306+
return;
307+
}
308+
309+
const isGranted = this._permissionStatus.state === 'granted';
310+
this.setState({ localHasAudioPermission: isGranted });
311+
312+
if (isGranted) {
313+
this._createAudioInputTrack(this.props.selectedAudioInputId);
314+
this.props.dispatch(getAvailableDevices());
315+
}
248316
}
249317

250318
/**
@@ -310,6 +378,11 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
310378
t
311379
} = this.props;
312380
const { audioInput, audioOutput } = this._getSelectors();
381+
const { localHasAudioPermission } = this.state;
382+
383+
const effectivePermission = localHasAudioPermission !== null
384+
? localHasAudioPermission
385+
: hasAudioPermission;
313386

314387
const classes = withStyles.getClasses(this.props);
315388

@@ -328,7 +401,7 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
328401
{this._renderSelector(audioInput)}
329402
</div>}
330403

331-
{!hideAudioInputPreview && hasAudioPermission && !iAmVisitor
404+
{!hideAudioInputPreview && effectivePermission && !iAmVisitor
332405
&& <AudioInputPreview
333406
track = { this.props.previewAudioTrack } />}
334407

@@ -385,7 +458,7 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
385458
aria-live = 'polite'
386459
className = { classes.outputContainer }>
387460
{this._renderSelector(audioOutput)}
388-
{!hideAudioOutputPreview && hasAudioPermission
461+
{!hideAudioOutputPreview && effectivePermission
389462
&& <AudioOutputPreview
390463
className = { classes.outputButton }
391464
deviceId = { selectedAudioOutputId } />}
@@ -450,10 +523,15 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
450523
*/
451524
_getSelectors() {
452525
const { availableDevices, hasAudioPermission } = this.props;
526+
const { localHasAudioPermission } = this.state;
527+
528+
const effectivePermission = localHasAudioPermission !== null
529+
? localHasAudioPermission
530+
: hasAudioPermission;
453531

454532
const audioInput = {
455533
devices: availableDevices.audioInput,
456-
hasPermission: hasAudioPermission,
534+
hasPermission: effectivePermission,
457535
icon: 'icon-microphone',
458536
isDisabled: this.props.disableAudioInputChange || this.props.disableDeviceChange,
459537
key: 'audioInput',
@@ -468,7 +546,7 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
468546
if (!this.props.hideAudioOutputSelect) {
469547
audioOutput = {
470548
devices: availableDevices.audioOutput,
471-
hasPermission: hasAudioPermission,
549+
hasPermission: effectivePermission,
472550
icon: 'icon-speaker',
473551
isDisabled: this.props.disableDeviceChange,
474552
key: 'audioOutput',

0 commit comments

Comments
 (0)