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

Sprint 1-2 #315

Merged
merged 45 commits into from
Jan 28, 2025
Merged
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
79611dc
Feat new UI manage prod page (#233)
Saelmala Dec 13, 2024
08e4b92
Feat/multiple sessions one window (#225)
malmen237 Dec 13, 2024
7a51cb5
Feat/change device during call (#212)
malmen237 Dec 16, 2024
878a680
Feat: [non-ios] control volume of line (#243)
Saelmala Dec 16, 2024
4f706d5
Refactor/UI (#241)
LucasMaupin Dec 16, 2024
17a04d9
fix: remove unused var (#248)
LucasMaupin Dec 16, 2024
c29680d
fix: show mute button on now volume slider value (#247)
Saelmala Dec 16, 2024
5cb862b
fix: only show volume slider on desktop (#256)
Saelmala Dec 18, 2024
dbb153b
fix: add back volume controls on mobile
Saelmala Dec 18, 2024
fd2cd38
fix: only keep volume controls on desktop (#257)
Saelmala Dec 19, 2024
4e11917
Fix/modal and button fixes on callpage (#255)
malmen237 Jan 7, 2025
a229fb6
fix: show volume control on supported devices (#259)
Saelmala Jan 7, 2025
a0c80c0
Feat/global hotkeys (#260)
malmen237 Jan 7, 2025
8c807be
feat: device handling & user settings (#258)
LucasMaupin Jan 9, 2025
adf129c
Feat/mute participant (#265)
malmen237 Jan 13, 2025
1fc24fa
Fix/uniform icons (#268)
malmen237 Jan 13, 2025
e606d48
Feat ifb functionality (#266)
Saelmala Jan 13, 2025
5077ec0
fix: problems with icon-colors solved (#270)
malmen237 Jan 13, 2025
bab02cc
fix: disable mute for program line (#276)
Saelmala Jan 13, 2025
a6ad292
fix: added color for disabled buttons when actionbutton is used (#275)
malmen237 Jan 13, 2025
c7a646b
feat: new manage productions page (#267)
LucasMaupin Jan 13, 2025
c3797c6
fix: mute issues on program-line (#278)
malmen237 Jan 14, 2025
158afef
fix: even volume increase (#277)
Saelmala Jan 14, 2025
2ab7509
feat: new calls-page ui (#279)
LucasMaupin Jan 14, 2025
4fae9fc
fix: added margin to warning-component (#288)
malmen237 Jan 15, 2025
212857c
feat: collapsablecalls (#300)
LucasMaupin Jan 16, 2025
1a0f082
fix: joinform bugfix (#301)
malmen237 Jan 16, 2025
5eefa1d
fix: improvements for program lines (#302)
Saelmala Jan 16, 2025
d784c7f
Fix/user settings (#304)
LucasMaupin Jan 17, 2025
a40c661
Bug logs volume and value (#308)
Saelmala Jan 17, 2025
e5bab79
UI/call buttons (#309)
LucasMaupin Jan 17, 2025
ae1c86a
Fix/bugfixes and cleanup (#305)
malmen237 Jan 17, 2025
ab93ff6
fix: centered the icons (#313)
malmen237 Jan 20, 2025
a2b06e8
fix: speaker-icon is red when audio-output is 0 (#312)
malmen237 Jan 20, 2025
aa17539
fix: change volume increase time (#311)
Saelmala Jan 20, 2025
fbd9f15
fix: ui fixes program calls (#310)
Saelmala Jan 20, 2025
3e33fbb
feat: production-line devices uses user settings (#314)
LucasMaupin Jan 20, 2025
517b2a8
fix: updated icon to match rest of project (#316)
malmen237 Jan 20, 2025
2a7b9f7
Update src/components/production-list/production-list-components.ts
malmen237 Jan 24, 2025
e6ff46f
chore: update readme
martinstark Jan 24, 2025
c42b179
Feat/visual production (#324)
malmen237 Jan 27, 2025
74d55cc
fix: clear timeout (#323)
Saelmala Jan 27, 2025
884aeb7
Feat/pr fixes (#321)
LucasMaupin Jan 28, 2025
0dd2805
fix: solved issue with missing dependency (#318)
malmen237 Jan 28, 2025
ad99d05
fix: update form and make warnings and errors work (#326)
malmen237 Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Feat/change device during call (#212)
Co-authored-by: Lucas Maupin <lucas.maupin@gmail.com>
malmen237 and LucasMaupin authored Dec 16, 2024
commit 7a51cb5dae6f892eb79dfd80b396c01c41c13d42
3 changes: 3 additions & 0 deletions src/assets/icons/icon.tsx
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import UserSvg from "./user.svg?react";
import ConfirmSvg from "./done.svg?react";
import StepLeftSvg from "./chevron_left.svg?react";
import StepRightSvg from "./navigate_next.svg?react";
import RefreshSvg from "./refresh.svg?react";
import Settings from "./settings.svg?react";
import ChevronDown from "./chevron_down.svg?react";
import ChevronUp from "./chevron_up.svg?react";
@@ -33,6 +34,8 @@ export const StepLeftIcon = () => <StepLeftSvg />;

export const StepRightIcon = () => <StepRightSvg />;

export const RefreshIcon = () => <RefreshSvg />;

export const SettingsIcon = () => <Settings />;

export const ChevronDownIcon = () => <ChevronDown />;
5 changes: 5 additions & 0 deletions src/assets/icons/refresh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/bowser.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import Bowser from "bowser";

const deviceInfo = Bowser.parse(window.navigator.userAgent);
const browser = Bowser.getParser(window.navigator.userAgent);
const browserName = browser.getBrowserName();

// platform type, can be either "desktop", "tablet" or "mobile"
export const isMobile = deviceInfo.platform.type === "mobile";
@@ -13,3 +14,5 @@ export const isValidBrowser = browser.satisfies({
safari: ">=16.4",
samsung: ">=21",
});

export const isBrowserFirefox = browserName.toLowerCase() === "firefox";
106 changes: 76 additions & 30 deletions src/components/landing-page/join-production.tsx
Original file line number Diff line number Diff line change
@@ -20,6 +20,10 @@ import { uniqBy } from "../../helpers.ts";
import { FormInputWithLoader } from "./form-input-with-loader.tsx";
import { useStorage } from "../accessing-local-storage/access-local-storage.ts";
import { useNavigateToProduction } from "./use-navigate-to-production.ts";
import { useFetchDevices } from "../../use-fetch-devices.ts";
import { useDevicePermissions } from "../../use-device-permission.ts";
import { Modal } from "../modal/modal.tsx";
import { ReloadDevicesButton } from "../reload-devices-button.tsx/reload-devices-button.tsx";

type FormValues = TJoinProductionOptions;

@@ -35,6 +39,11 @@ const ButtonWrapper = styled.div`
margin: 2rem 0 2rem 0;
`;

const FormWithBtn = styled.div`
display: flex;
justify-content: space-between;
`;

type TProps = {
preSelected?: {
preSelectedProductionId: string;
@@ -53,6 +62,9 @@ export const JoinProduction = ({
const [joinProductionOptions, setJoinProductionOptions] =
useState<TJoinProductionOptions | null>(null);
const { readFromStorage, writeToStorage } = useStorage("username");
const [refresh, setRefresh] = useState<number>(0);
const [firefoxWarningModalOpen, setFirefoxWarningModalOpen] = useState(false);

const {
formState: { errors, isValid },
register,
@@ -71,9 +83,18 @@ export const JoinProduction = ({
keepErrors: true, // input errors will be retained with value update
},
});
const { permission } = useDevicePermissions({
continueToApp: true,
});

const [{ devices, selectedProductionId }, dispatch] = useGlobalState();

useFetchDevices({
dispatch,
permission,
refresh,
});

const {
error: productionFetchError,
production,
@@ -150,8 +171,6 @@ export const JoinProduction = ({
payload: {
id: uuid,
callState: {
production: null,
reloadProductionList: false,
devices: null,
joinProductionOptions: payload,
mediaStreamInput: null,
@@ -236,38 +255,65 @@ export const JoinProduction = ({
/>
<FormLabel>
<DecorativeLabel>Input</DecorativeLabel>
<FormSelect
// eslint-disable-next-line
{...register(`audioinput`)}
>
{inputDevices.length > 0 ? (
inputDevices.map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{device.label}
</option>
))
) : (
<option value="no-device">No device available</option>
)}
</FormSelect>
</FormLabel>
<FormLabel>
<DecorativeLabel>Output</DecorativeLabel>
{outputDevices.length > 0 ? (
<FormWithBtn>
<FormSelect
// eslint-disable-next-line
{...register(`audiooutput`)}
{...register(`audioinput`)}
>
{outputDevices.map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{device.label}
</option>
))}
{inputDevices.length > 0 ? (
inputDevices.map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{device.label}
</option>
))
) : (
<option value="no-device">No device available</option>
)}
</FormSelect>
) : (
<StyledWarningMessage>
Controlled by operating system
</StyledWarningMessage>
<ReloadDevicesButton
handleReloadDevices={() => setRefresh((prev) => prev + 1)}
devices={devices}
isDummy
/>
</FormWithBtn>
</FormLabel>
<FormLabel>
<DecorativeLabel>Output</DecorativeLabel>
<FormWithBtn>
{outputDevices.length > 0 ? (
<FormSelect
// eslint-disable-next-line
{...register(`audiooutput`)}
>
{outputDevices.map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{device.label}
</option>
))}
</FormSelect>
) : (
<StyledWarningMessage>
Controlled by operating system
</StyledWarningMessage>
)}
<ReloadDevicesButton
handleReloadDevices={() => setRefresh((prev) => prev + 1)}
setFirefoxWarningModalOpen={() =>
setFirefoxWarningModalOpen(true)
}
devices={devices}
/>
</FormWithBtn>
{firefoxWarningModalOpen && (
<Modal onClose={() => setFirefoxWarningModalOpen(false)}>
<DisplayContainerHeader>
Reset permissions
</DisplayContainerHeader>
<p>
To reload devices Firefox needs the permission to be manually
reset, please remove permission and reload page instead.
</p>
</Modal>
)}
</FormLabel>
{!preSelected && (
10 changes: 10 additions & 0 deletions src/components/loader/loader.tsx
Original file line number Diff line number Diff line change
@@ -22,6 +22,16 @@ const Loading = styled.div`
left: 30%;
}

&.refresh-devices {
position: absolute;
top: 0.5rem;
left: 0.5rem;
padding: 0;
margin: 0;
width: 2.5rem;
height: 2.5rem;
}

&.join-production {
border: 0.4rem solid rgba(201, 201, 201, 0.1);
border-top: 0.4rem solid #e2e2e2;
201 changes: 192 additions & 9 deletions src/components/production-line/production-line.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import styled from "@emotion/styled";
import { useCallback, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { SubmitHandler, useForm } from "react-hook-form";
import { useGlobalState } from "../../global-state/context-provider.tsx";
import { useAudioInput } from "./use-audio-input.ts";
import { ActionButton } from "../landing-page/form-elements.tsx";
import { UserList } from "./user-list.tsx";
import {
MicMuted,
@@ -12,11 +12,21 @@ import {
SpeakerOn,
SettingsIcon,
} from "../../assets/icons/icon.tsx";
import {
ActionButton,
DecorativeLabel,
FormLabel,
FormContainer,
FormSelect,
PrimaryButton,
StyledWarningMessage,
} from "../landing-page/form-elements.tsx";
import { uniqBy } from "../../helpers.ts";
import { Spinner } from "../loader/loader.tsx";
import { DisplayContainerHeader } from "../landing-page/display-container-header.tsx";
import { DisplayContainer, FlexContainer } from "../generic-components.ts";
import { useDeviceLabels } from "./use-device-labels.ts";
import { isMobile } from "../../bowser.ts";
import { isBrowserFirefox, isMobile } from "../../bowser.ts";
import { useLineHotkeys, useSpeakerHotkeys } from "./use-line-hotkeys.ts";
import { LongPressToTalkButton } from "./long-press-to-talk-button.tsx";
import { useLinePolling } from "./use-line-polling.ts";
@@ -25,13 +35,18 @@ import { useIsLoading } from "./use-is-loading.ts";
import { useCheckBadLineData } from "./use-check-bad-line-data.ts";
import { useAudioCue } from "./use-audio-cue.ts";
import { DisplayWarning } from "../display-box.tsx";
import { useFetchDevices } from "../../use-fetch-devices.ts";
import { TJoinProductionOptions } from "./types.ts";
import { SettingsModal, Hotkeys } from "./settings-modal.tsx";
import { CallState } from "../../global-state/types.ts";
import { ExitCallButton } from "./exit-call-button.tsx";
import { Modal } from "../modal/modal.tsx";
import { VerifyDecision } from "../verify-decision/verify-decision.tsx";
import { ModalConfirmationText } from "../modal/modal-confirmation-text.ts";
import { SymphonyRtcConnectionComponent } from "./symphony-rtc-connection-component.tsx";
import { ReloadDevicesButton } from "../reload-devices-button.tsx/reload-devices-button.tsx";

type FormValues = TJoinProductionOptions;

const TempDiv = styled.div`
padding: 0 0 2rem 0;
@@ -54,6 +69,10 @@ const SmallText = styled.span`
font-size: 1.6rem;
`;

const LargeText = styled.span`
word-break: break-all;
`;

const ButtonIcon = styled.div`
width: 3rem;
display: inline-block;
@@ -94,8 +113,10 @@ const LongPressWrapper = styled.div`
touch-action: none;
`;

const ButtonWrapper = styled.span`
margin: 0 2rem 0 0;
const ButtonWrapper = styled.div`
display: flex;
justify-content: space-between;
margin: 0 2rem 2rem 1rem;
`;

const ListWrapper = styled(DisplayContainer)`
@@ -135,10 +156,12 @@ export const ProductionLine = ({
isSingleCall,
}: TProductionLine) => {
const { productionId: paramProductionId, lineId: paramLineId } = useParams();
const [, dispatch] = useGlobalState();
const [{ devices }, dispatch] = useGlobalState();
const [connectionActive, setConnectionActive] = useState(true);
const [isInputMuted, setIsInputMuted] = useState(true);
const [isOutputMuted, setIsOutputMuted] = useState(false);
const [showDeviceSettings, setShowDeviceSettings] = useState(false);
const [refresh, setRefresh] = useState<number>(0);
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
const [confirmExitModalOpen, setConfirmExitModalOpen] = useState(false);
const [hotkeys, setHotkeys] = useState<Hotkeys>({
@@ -160,8 +183,9 @@ export const ProductionLine = ({
sessionId,
} = callState;

const inputAudioStream = useAudioInput({
inputId: joinProductionOptions?.audioinput ?? null,
const [inputAudioStream, resetAudioInput] = useAudioInput({
audioInputId: joinProductionOptions?.audioinput ?? null,
audioOutputId: joinProductionOptions?.audiooutput ?? null,
});

const muteInput = useCallback(
@@ -195,6 +219,18 @@ export const ProductionLine = ({
customKeyPress: savedHotkeys.pressToTalkHotkey,
});

useFetchDevices({
dispatch,
permission: true,
refresh,
});

useEffect(() => {
if (joinProductionOptions) {
setConnectionActive(true);
}
}, [joinProductionOptions]);

useEffect(() => {
if (connectionState === "connected") {
playEnterSound();
@@ -251,6 +287,79 @@ export const ProductionLine = ({
dispatch,
});

const {
formState: { isValid, isDirty },
register,
handleSubmit,
} = useForm<FormValues>({
defaultValues: {
username: "",
productionId: paramProductionId || "",
lineId: paramLineId || undefined,
},
resetOptions: {
keepDirtyValues: true, // user-interacted input will be retained
keepErrors: true, // input errors will be retained with value update
},
});

const outputDevices = devices
? uniqBy(
devices.filter((d) => d.kind === "audiooutput"),
(item) => item.deviceId
)
: [];

const inputDevices = devices
? uniqBy(
devices.filter((d) => d.kind === "audioinput"),
(item) => item.deviceId
)
: [];

const settingsButtonPressed = () => {
setRefresh((prev) => prev + 1);
setShowDeviceSettings(!showDeviceSettings);
};

// Reset connection and re-connect to production-line
const onSubmit: SubmitHandler<FormValues> = async (payload) => {
const unchangedPayload =
payload.audioinput === joinProductionOptions?.audioinput &&
payload.audiooutput === joinProductionOptions?.audiooutput;
if (joinProductionOptions && !unchangedPayload) {
setConnectionActive(false);
resetAudioInput();
muteInput(true);

const newJoinProductionOptions = {
...payload,
productionId: joinProductionOptions.productionId,
lineId: joinProductionOptions.lineId,
username: joinProductionOptions.username,
};

dispatch({
type: "UPDATE_CALL",
payload: {
id,
updates: {
devices: null,
joinProductionOptions: newJoinProductionOptions,
mediaStreamInput: null,
dominantSpeaker: null,
audioLevelAboveThreshold: false,
connectionState: null,
audioElements: null,
sessionId: null,
},
},
});

setShowDeviceSettings(false);
}
};

const handleSettingsClick = () => {
setIsSettingsModalOpen(!isSettingsModalOpen);
};
@@ -285,8 +394,10 @@ export const ProductionLine = ({

{!loading && production && line && (
<DisplayContainerHeader>
<SmallText>Production:</SmallText> {production.name}{" "}
<SmallText>Line:</SmallText> {line.name}
<SmallText>Production:</SmallText>
<LargeText>{production.name} </LargeText>
<SmallText>Line:</SmallText>
<LargeText>{line.name}</LargeText>
</DisplayContainerHeader>
)}
</HeaderWrapper>
@@ -374,6 +485,78 @@ export const ProductionLine = ({
<strong>Audio Output:</strong> {deviceLabels.outputLabel}
</TempDiv>
)}
<FlexButtonWrapper>
<PrimaryButton
type="button"
onClick={() => settingsButtonPressed()}
>
{!showDeviceSettings ? "Change device" : "Close"}
</PrimaryButton>
</FlexButtonWrapper>
{showDeviceSettings && devices && (
<FormContainer>
<FormLabel>
<DecorativeLabel>Input</DecorativeLabel>
<FormSelect
// eslint-disable-next-line
{...register(`audioinput`)}
>
{inputDevices.length > 0 ? (
inputDevices.map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{device.label}
</option>
))
) : (
<option value="no-device">No device available</option>
)}
</FormSelect>
</FormLabel>
<FormLabel>
<DecorativeLabel>Output</DecorativeLabel>
{outputDevices.length > 0 ? (
<FormSelect
// eslint-disable-next-line
{...register(`audiooutput`)}
>
{outputDevices.map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{device.label}
</option>
))}
</FormSelect>
) : (
<StyledWarningMessage>
Controlled by operating system
</StyledWarningMessage>
)}
</FormLabel>
{isBrowserFirefox && !isMobile && (
<StyledWarningMessage>
If a new device has been added Firefox needs the
permission to be manually reset. If your device is
missing, please remove the permission and reload page.
</StyledWarningMessage>
)}
<ButtonWrapper>
<PrimaryButton
type="submit"
disabled={!isValid || !isDirty}
onClick={handleSubmit(onSubmit)}
>
Save
</PrimaryButton>
{!(isBrowserFirefox && !isMobile) && (
<ReloadDevicesButton
handleReloadDevices={() =>
setRefresh((prev) => prev + 1)
}
devices={devices}
/>
)}
</ButtonWrapper>
</FormContainer>
)}

{inputAudioStream &&
inputAudioStream !== "no-device" &&
70 changes: 44 additions & 26 deletions src/components/production-line/use-audio-input.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,71 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { noop } from "../../helpers";
import { TJoinProductionOptions } from "./types.ts";

type TGetMediaDevicesOptions = {
inputId: TJoinProductionOptions["audioinput"] | null;
audioInputId: TJoinProductionOptions["audioinput"] | null;
audioOutputId: TJoinProductionOptions["audiooutput"] | null;
};

export type TUseAudioInputValues = MediaStream | "no-device" | null;

type TUseAudioInput = (
options: TGetMediaDevicesOptions
) => TUseAudioInputValues;
) => [TUseAudioInputValues, () => void];

// A hook for fetching the user selected audio input as a MediaStream
export const useAudioInput: TUseAudioInput = ({ inputId }) => {
export const useAudioInput: TUseAudioInput = ({
audioInputId,
audioOutputId,
}) => {
const [audioInput, setAudioInput] = useState<TUseAudioInputValues>(null);

useEffect(() => {
let aborted = false;

if (!inputId) return noop;
if (!audioInputId) return noop;

if (inputId === "no-device") return setAudioInput("no-device");
if (audioInputId === "no-device") return setAudioInput("no-device");

navigator.mediaDevices
.getUserMedia({
audio: {
deviceId: {
exact: inputId,
// First request a generic audio stream to "reset" permissions
navigator.mediaDevices.getUserMedia({ audio: true }).then(() => {
// Then request the specific audio input the user has selected
navigator.mediaDevices
.getUserMedia({
audio: {
deviceId: {
exact: audioInputId,
},
noiseSuppression: true,
},
noiseSuppression: true,
},
})
.then((stream) => {
if (aborted) return;

// Default to muted input
stream.getTracks().forEach((t) => {
// eslint-disable-next-line no-param-reassign
t.enabled = false;
});
})
.then((stream) => {
if (aborted) return;

// Default to muted input
stream.getTracks().forEach((t) => {
// eslint-disable-next-line no-param-reassign
t.enabled = false;
});

setAudioInput(stream);
});
setAudioInput(stream);
});
});

return () => {
aborted = true;
};
}, [inputId]);
// audioOutputId is needed as a dependency to trigger restart of
// useEffect if only output has been updated during line-call
}, [audioInputId, audioOutputId]);

// Reset function to set audioInput to null
const reset = useCallback(() => {
if (audioInput && audioInput !== "no-device") {
audioInput.getTracks().forEach((t) => t.stop());
}
setAudioInput(null);
}, [audioInput]);

return audioInput;
return [audioInput, reset];
};
1 change: 0 additions & 1 deletion src/components/production-line/use-establish-session.ts
Original file line number Diff line number Diff line change
@@ -61,7 +61,6 @@ export const useEstablishSession = ({
useEffect(
() => () => {
if (!joinProductionOptions) return;

if (sessionId) {
API.deleteAudioSession({
sessionId,
1 change: 1 addition & 0 deletions src/components/production-line/use-rtc-connection.ts
Original file line number Diff line number Diff line change
@@ -357,6 +357,7 @@ export const useRtcConnection = ({
sessionId,
joinProductionOptions,
rtcPeerConnection,
cleanUpAudio,
dispatch,
noStreamError,
callId,
71 changes: 71 additions & 0 deletions src/components/reload-devices-button.tsx/reload-devices-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import styled from "@emotion/styled";
import { useEffect, useState } from "react";
import { RefreshIcon } from "../../assets/icons/icon";
import { PrimaryButton } from "../landing-page/form-elements";
import { Spinner } from "../loader/loader";
import { isBrowserFirefox, isMobile } from "../../bowser";

const StyledRefreshBtn = styled(PrimaryButton)`
padding: 0;
margin: 0;
width: 3.5rem;
height: 3.5rem;
margin-left: 1.5rem;
flex-shrink: 0; /* Prevent shrinking */
flex-basis: auto; /* Prevent shrinking */
&.dummy {
background-color: #242424;
pointer-events: none;
}
`;

export const ReloadDevicesButton = ({
handleReloadDevices,
setFirefoxWarningModalOpen,
devices,
isDummy,
}: {
handleReloadDevices: () => void;
setFirefoxWarningModalOpen?: () => void;
devices: MediaDeviceInfo[];
isDummy?: boolean;
}) => {
const [deviceRefresh, setDeviceRefresh] = useState(false);

useEffect(() => {
let timeout: number | null = null;

timeout = window.setTimeout(() => {
setDeviceRefresh(false);
}, 800);

return () => {
if (timeout !== null) {
window.clearTimeout(timeout);
}
};
}, [devices]);

const reloadDevices = () => {
if (isBrowserFirefox && !isMobile && setFirefoxWarningModalOpen) {
setFirefoxWarningModalOpen();
} else {
setDeviceRefresh(true);
handleReloadDevices();
}
};

return (
<StyledRefreshBtn
type="button"
title={isDummy ? "" : "Refresh devices"}
className={isDummy ? "dummy" : ""}
disabled={isDummy}
onClick={() => reloadDevices()}
>
{!deviceRefresh && <RefreshIcon />}
{deviceRefresh && <Spinner className="refresh-devices" />}
</StyledRefreshBtn>
);
};
2 changes: 0 additions & 2 deletions src/global-state/types.ts
Original file line number Diff line number Diff line change
@@ -9,8 +9,6 @@ export interface ErrorState {
}

export interface CallState {
production: TProduction | null;
reloadProductionList: boolean;
devices: MediaDeviceInfo[] | null;
joinProductionOptions: TJoinProductionOptions | null;
mediaStreamInput: MediaStream | null;
9 changes: 7 additions & 2 deletions src/use-fetch-devices.ts
Original file line number Diff line number Diff line change
@@ -4,9 +4,14 @@ import { TGlobalStateAction } from "./global-state/global-state-actions";
type TUseFetchDevices = {
permission: boolean;
dispatch: Dispatch<TGlobalStateAction>;
refresh?: number;
};

export const useFetchDevices = ({ permission, dispatch }: TUseFetchDevices) => {
export const useFetchDevices = ({
permission,
dispatch,
refresh,
}: TUseFetchDevices) => {
useEffect(() => {
if (permission) {
window.navigator.mediaDevices
@@ -26,5 +31,5 @@ export const useFetchDevices = ({ permission, dispatch }: TUseFetchDevices) => {
}

return () => {};
}, [dispatch, permission]);
}, [dispatch, permission, refresh]);
};