Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show selected audio device indicator in header for mweb #2053

Merged
merged 12 commits into from
Oct 31, 2023
5 changes: 5 additions & 0 deletions packages/react-icons/assets/BluetoothIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions packages/react-icons/src/BluetoothIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';
import { SVGProps } from 'react';
const SvgBluetoothIcon = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24" fill="none" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.596 3.062a.818.818 0 0 1 .892.178l4.09 4.09c.32.32.32.838 0 1.158L11.066 12l3.512 3.512c.32.32.32.838 0 1.157l-4.09 4.091a.818.818 0 0 1-1.397-.578v-6.207L6.397 16.67a.818.818 0 1 1-1.157-1.157L8.752 12 5.24 8.488A.818.818 0 1 1 6.397 7.33l2.694 2.694V3.818c0-.33.2-.63.505-.756Zm1.131 10.913 2.116 2.116-2.116 2.115v-4.23Zm0-3.95V5.793l2.116 2.116-2.116 2.116Z"
fill="currentColor"
/>
<path
d="M16.555 9.106a.818.818 0 0 1 1.157 0 4.09 4.09 0 0 1 0 5.788.818.818 0 1 1-1.157-1.158 2.457 2.457 0 0 0 0-3.473.818.818 0 0 1 0-1.157ZM14.818 11.182a.818.818 0 1 0 0 1.636h.008a.818.818 0 1 0 0-1.636h-.008Z"
fill="currentColor"
/>
</svg>
);
export default SvgBluetoothIcon;
1 change: 1 addition & 0 deletions packages/react-icons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export { default as BarIcon } from './BarIcon';
export { default as BatteryFullIcon } from './BatteryFullIcon';
export { default as BatteryPowerIcon } from './BatteryPowerIcon';
export { default as BillIcon } from './BillIcon';
export { default as BluetoothIcon } from './BluetoothIcon';
export { default as BoltIcon } from './BoltIcon';
export { default as BookIcon } from './BookIcon';
export { default as BookmarkIcon } from './BookmarkIcon';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Logo, SpeakerTag } from './HeaderComponents';
// @ts-ignore: No implicit any
import { LiveStatus, RecordingStatus, StreamActions } from './StreamActions';
// @ts-ignore: No implicit any
import { AudioOutputActions, CamaraFlipActions } from './common';
import { AudioActions, CamaraFlipActions } from './common';

export const Header = () => {
const roomState = useHMSStore(selectRoomState);
Expand Down Expand Up @@ -40,7 +40,7 @@ export const Header = () => {
{isMobile ? (
<>
<CamaraFlipActions />
<AudioOutputActions />
<AudioActions />
</>
) : null}
</Flex>
Expand Down
69 changes: 46 additions & 23 deletions packages/roomkit-react/src/Prebuilt/components/Header/common.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import {
useHMSActions,
useHMSStore,
} from '@100mslive/react-sdk';
import { CameraFlipIcon, CheckIcon, CrossIcon, SpeakerIcon } from '@100mslive/react-icons';
import {
BluetoothIcon,
CameraFlipIcon,
CheckIcon,
CrossIcon,
HeadphonesIcon,
SpeakerIcon,
} from '@100mslive/react-icons';
import { HorizontalDivider } from '../../../Divider';
import { Label } from '../../../Label';
import { Box, Flex } from '../../../Layout';
Expand Down Expand Up @@ -49,56 +56,72 @@ export const CamaraFlipActions = () => {
);
};

export const AudioOutputActions = () => {
// It will handle and show audio input devices in Mweb while audio output devices in desktop
export const AudioActions = () => {
const { allDevices, selectedDeviceIDs, updateDevice } = useDevices();
const { audioOutput } = allDevices;

// don't show speaker selector where the API is not supported, and use
// a generic word("Audio") for Mic. In some cases(Chrome Android for e.g.) this changes both mic and speaker keeping them in sync.
const shouldShowAudioOutput = 'setSinkId' in HTMLMediaElement.prototype;
const { audioInput, audioOutput } = allDevices;
let availableAudioDevices = audioInput;
let selectedAudio = selectedDeviceIDs.audioInput;
if (shouldShowAudioOutput) {
availableAudioDevices = audioOutput;
selectedAudio = selectedDeviceIDs.audioOutput;
}
const hmsActions = useHMSActions();
const audioFiltered = availableAudioDevices?.find(item => !!item.label);
const currentSelection = availableAudioDevices?.find(item => item.deviceId === selectedAudio);

/**
* Chromium browsers return an audioOutput with empty label when no permissions are given
*/
const audioOutputFiltered = audioOutput?.filter(item => !!item.label) ?? [];
if (!shouldShowAudioOutput || !audioOutputFiltered?.length > 0) {
if (!audioFiltered) {
return null;
}
let AudioIcon = <SpeakerIcon />;
if (currentSelection && currentSelection.label.toLowerCase().includes('bluetooth')) {
AudioIcon = <BluetoothIcon />;
} else if (currentSelection && currentSelection.label.toLowerCase().includes('wired')) {
AudioIcon = <HeadphonesIcon />;
}
return (
<AudioOutputSelectionSheet
outputDevices={audioOutput}
outputSelected={selectedDeviceIDs.outputDevices}
<AudioSelectionSheet
audioDevices={availableAudioDevices}
audioSelected={selectedAudio}
onChange={async deviceId => {
try {
await updateDevice({
deviceId,
deviceType: DeviceType.audioOutput,
deviceType: shouldShowAudioOutput ? DeviceType.audioOutput : DeviceType.audioInput,
});
} catch (e) {
ToastManager.addToast({
title: `Error while changing audio output ${e.message || ''}`,
title: `Error while changing audio device ${e.message || ''}`,
variant: 'error',
});
}
}}
>
<Box>
<IconButton>
<SpeakerIcon />
</IconButton>
<Box
onClick={async () => {
// refresh device as `devicechange` listener won't work in mobile device
await hmsActions.refreshDevices();
}}
>
<IconButton>{AudioIcon} </IconButton>
</Box>
</AudioOutputSelectionSheet>
</AudioSelectionSheet>
);
};

const AudioOutputSelectionSheet = ({ outputDevices, outputSelected, onChange, children }) => {
const AudioSelectionSheet = ({ audioDevices, audioSelected, onChange, children }) => {
return (
<Sheet.Root>
<Sheet.Trigger asChild>{children}</Sheet.Trigger>
<Sheet.Content>
<Sheet.Title css={{ py: '$10', px: '$8', alignItems: 'center' }}>
<Flex direction="row" justify="between" css={{ w: '100%' }}>
<Text variant="h6" css={{ display: 'flex' }}>
Audio Output
Audio
</Text>
<Sheet.Close>
<IconButton as="div" data-testid="dialog_cross_icon">
Expand All @@ -113,16 +136,16 @@ const AudioOutputSelectionSheet = ({ outputDevices, outputSelected, onChange, ch
css={{
px: '$8',
maxHeight: '80vh',
overflowY: 'scroll',
overflowY: 'auto',
}}
>
{outputDevices.map(audioDevice => {
{audioDevices.map(audioDevice => {
return (
<SelectWithLabel
key={audioDevice.deviceId}
label={audioDevice.label}
id={audioDevice.deviceId}
checked={audioDevice.deviceId === outputSelected}
checked={audioDevice.deviceId === audioSelected}
onChange={() => onChange(audioDevice.deviceId)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React, { Fragment, useEffect, useRef, useState } from 'react';
import { useMedia } from 'react-use';
import {
DeviceType,
selectIsLocalVideoEnabled,
selectLocalVideoTrackID,
selectVideoTrackByID,
useDevices,
useHMSActions,
useHMSStore,
} from '@100mslive/react-sdk';
import { MicOnIcon, SpeakerIcon, VideoOnIcon } from '@100mslive/react-icons';
import { Box, Button, Dropdown, Flex, StyledVideoTile, Text, Video } from '../../../';
import { config as cssConfig } from '../../../Theme';
import { DialogDropdownTrigger } from '../../primitives/DropdownTrigger';
import { useUISettings } from '../AppData/useUISettings';
import { useDropdownSelection } from '../hooks/useDropdownSelection';
Expand All @@ -30,7 +33,15 @@ const Settings = ({ setHide }) => {
const shouldShowAudioOutput = 'setSinkId' in HTMLMediaElement.prototype;
const mirrorLocalVideo = useUISettings(UI_SETTINGS.mirrorLocalVideo);
const trackSelector = selectVideoTrackByID(videoTrackId);
const hmsActions = useHMSActions();
const track = useHMSStore(trackSelector);
const isMobile = useMedia(cssConfig.media.md);

useEffect(() => {
if (isMobile) {
hmsActions.refreshDevices();
}
}, [hmsActions, isMobile]);

/**
* Chromium browsers return an audioOutput with empty label when no permissions are given
Expand Down
Loading