From 803ce484a50963f205c5d327ec7ea74cf6d254c5 Mon Sep 17 00:00:00 2001 From: huanghuang358 <178640467+huanghuang358@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:21:48 +0800 Subject: [PATCH 1/5] feat(webapp) add setting page --- webapp/components/device.tsx | 29 ++- webapp/components/join.tsx | 5 + webapp/components/settings.tsx | 292 ++++++++++++++++++++++++++++++ webapp/components/svg/setting.tsx | 49 +++++ webapp/lib/util.ts | 3 + webapp/store/atom.ts | 13 ++ 6 files changed, 386 insertions(+), 5 deletions(-) create mode 100644 webapp/components/settings.tsx create mode 100644 webapp/components/svg/setting.tsx diff --git a/webapp/components/device.tsx b/webapp/components/device.tsx index 2d26ed6..7684cef 100644 --- a/webapp/components/device.tsx +++ b/webapp/components/device.tsx @@ -6,7 +6,13 @@ import { deviceNone, deviceScreen, } from '../lib/device' -import { deviceSpeakerAtom, speakerStatusAtom, settingsEnabledScreenAtom } from './../store/atom' +import { isScreenShareSupported } from '../lib/util' +import { + deviceSpeakerAtom, + speakerStatusAtom, + settingsEnabledScreenAtom, + screenShareResolutionAtom, +} from './../store/atom' import Loading from './svg/loading' import SvgSpeaker from './svg/speaker' @@ -14,6 +20,8 @@ import SvgAudio from './svg/audio' import SvgVideo from './svg/video' import { SvgPresentCancel, SvgPresentToAll } from './svg/present' import { SvgBackgroundCancel, SvgBackground } from './svg/background' +import { SvgSetting } from './svg/setting' +import Settings from './settings' function toDevice(info: MediaDeviceInfo): Device { const deviceId = info.deviceId @@ -55,6 +63,8 @@ export default function DeviceBar(props: { streamId: string }) { const [deviceAudio, setDeviceAudio] = useState([deviceNone]) const [deviceVideo, setDeviceVideo] = useState([deviceNone]) + const [isSetting, setIsSetting] = useState(false) + const permissionsQuery = async () => (await Promise.all(['camera', 'microphone'].map( // NOTE: @@ -117,7 +127,7 @@ export default function DeviceBar(props: { streamId: string }) { setDeviceSpeaker([...speakers]) setDeviceAudio([...audios]) - setDeviceVideo(settingsEnabledScreen ? [...videos] : [...videos, deviceScreen]) + setDeviceVideo([...videos]) } const init = async () => { @@ -177,11 +187,13 @@ export default function DeviceBar(props: { streamId: string }) { { label: '1080p', value: '1080' }, { label: 'Native', value: 'native' } ] - const [currentScreenResolution, setCurrentScreenResolution] = useState('720') + const [currentScreenResolution, setCurrentScreenResolution] = useAtom(screenShareResolutionAtom) const toggleEnableScreen = async () => { + const height = Number.parseInt(currentScreenResolution) + const constraints = Number.isNaN(height) ? {} : { height } setLoadingScreen(true) - await onChangedDeviceVideo(userStatus.screen ? deviceNone.deviceId : deviceScreen.deviceId) + await onChangedDeviceVideo(userStatus.screen ? deviceNone.deviceId : deviceScreen.deviceId, constraints) setLoadingScreen(false) } @@ -318,7 +330,7 @@ export default function DeviceBar(props: { streamId: string }) { {!settingsEnabledScreen && (
-
)} +
+ +
+ {isSetting && setIsSetting(false)} onChangedDeviceVideo={onChangedDeviceVideo} isScreenSharing={userStatus.screen}/>} ) } diff --git a/webapp/components/join.tsx b/webapp/components/join.tsx index ff972f4..8b0898c 100644 --- a/webapp/components/join.tsx +++ b/webapp/components/join.tsx @@ -6,11 +6,14 @@ import { } from '../store/atom' import { getStorage, setStorage, delStorage, setStorageStream, setStorageMeeting } from '../lib/storage' import { newRoom, newUser, setApiToken, setRoomId } from '../lib/api' +import { SvgSetting } from './svg/setting' +import Settings from './settings' export default function Join() { const [loc, setLoc] = useAtom(locationAtom) const [__, setAtomMeetingId] = useAtom(meetingIdAtom) const [tmpId, setTmpId] = useState('') + const [isSetting, setIsSetting] = useState(false) const getLoginStatus = async () => { const user = getStorage() @@ -67,11 +70,13 @@ export default function Join() { maxLength={11} /> +

If have some problems, Please click this:

Reset
+ {isSetting && setIsSetting(false)} onChangedDeviceVideo={() => {}} isScreenSharing={false}/>} ) } diff --git a/webapp/components/settings.tsx b/webapp/components/settings.tsx new file mode 100644 index 0000000..82f0853 --- /dev/null +++ b/webapp/components/settings.tsx @@ -0,0 +1,292 @@ +import { useAtom } from 'jotai' +import { useState } from 'react' +import { deviceNone } from '../lib/device' +import { + languageAtom, + videoResolutionAtom, + screenShareResolutionAtom, + meetingIdAtom, + settingsEnabledScreenAtom, +} from './../store/atom' + +import { + SvgClose, + SvgGeneral, + SvgMedia, + SvgAdvanced, + SvgTip, +} from './svg/setting' +import { delStorage } from '../lib/storage' + +function SettingGeneral() { + const [language, setLanguage] = useAtom(languageAtom) + const languageOptions = ['English'] + const onChangeLanguage = (language: string) => { + setLanguage(language) + + // todo: add support of more languages + } + return ( +
+

+ Language +

+ +
+ ) +} + +function SettingMedia(props: { isScreenSharing: boolean, onChangedDeviceVideo: (current: string, constraints?: MediaTrackConstraints) => void }) { + const [videoResolution, setVideoResolution] = useAtom(videoResolutionAtom) + const [screenShareResolution, setScreenShareResolution] = useAtom(screenShareResolutionAtom) + const [isTipShowed, setIsTipShowed] = useState(false) + const videoResolutionOptions = [ + { label: '480p (Default)', value: '480' }, + ] + const screenShareResolutionOptions = [ + { label: '720p (Default)', value: '720' }, + { label: '1080p', value: '1080' }, + { label: 'Native', value: 'native' } + ] + const onChangeVideoResolution = (resolution: string) => { + setVideoResolution(resolution) + + // todo: add support of more video resolutions + } + const onChangeScreenShareResolution = async (resolution: string) => { + setScreenShareResolution(resolution) + if (props.isScreenSharing) props.onChangedDeviceVideo(deviceNone.deviceId) + } + return ( +
+
+

+ Video Resolution +

+ +
+
+
+
+

+ Screen Share Resolution +

+
setIsTipShowed(true)} + onMouseLeave={() => setIsTipShowed(false)} + > + + {isTipShowed && ( +
+
+
+ )} +
+
+ +
+
+ {isTipShowed && ( +
+

+ 1. Meaningless for mobile devices; +

+

+ 2. If screen is sharing, any changes on this property will stop sharing. +

+
+ )} +
+
+
+ ) +} + +function SettingAdvanced() { + const [isTipShowed, setIsTipShowed] = useState(false) + const [isTipShowed2, setIsTipShowed2] = useState(false) + const [meetingId] = useAtom(meetingIdAtom) + const [settingsEnabledScreen, setsettingsEnabledScreen] = useAtom(settingsEnabledScreenAtom) + const [isReset, setIsReset] = useState(false) + + return ( +
+
+
+
+

+ Local Storage +

+
setIsTipShowed(true)} + onMouseLeave={() => setIsTipShowed(false)} + > + + {isTipShowed && ( +
+
+
+ )} +
+
+ +
+
+ {isTipShowed && ( +
+

+ 1. Local storage only can be reset on the homepage. +

+

+ 2. Single use only. +

+
+ )} +
+
+
+
+
+ +
setIsTipShowed2(true)} + onMouseLeave={() => setIsTipShowed2(false)} + > + + {isTipShowed2 && ( +
+
+
+ )} +
+
+
+
+ {isTipShowed2 && ( +
+

+ 1. Defaults to checked on mobile devices; +

+

+ 2. Changable only on the homepage. +

+
+ )} +
+
+
+ ) +} + +export default function Settings(props: { onClose: () => void, onChangedDeviceVideo: (current: string, constraints?: MediaTrackConstraints) => void, isScreenSharing: boolean }) { + const [activeTab, setActiveTab] = useState('General') + const settingsOptions = [ + { label: 'General', icon: SvgGeneral }, + { label: 'Media', icon: SvgMedia }, + { label: 'Advanced', icon: SvgAdvanced } + ] + return ( +
+
+
+

Settings

+ +
+
+ +
+

{activeTab}

+ {activeTab === 'General' && } + {activeTab === 'Media' && } + {activeTab === 'Advanced' && } +
+
+
+
+ ) +} diff --git a/webapp/components/svg/setting.tsx b/webapp/components/svg/setting.tsx new file mode 100644 index 0000000..393f0c4 --- /dev/null +++ b/webapp/components/svg/setting.tsx @@ -0,0 +1,49 @@ +export function SvgSetting() { + return ( + + + + ) +} + +export function SvgClose() { + return ( + + + + ) +} + +export function SvgGeneral() { + return ( + + + + + ) +} + +export function SvgMedia() { + return ( + + + + + ) +} + +export function SvgAdvanced() { + return ( + + + + ) + +} +export function SvgTip() { + return ( + + + + ) +} \ No newline at end of file diff --git a/webapp/lib/util.ts b/webapp/lib/util.ts index 24fb300..a42bcf3 100644 --- a/webapp/lib/util.ts +++ b/webapp/lib/util.ts @@ -24,10 +24,13 @@ const isFullscreenSupported = typeof document.exitFullscreen === 'function' && const isPictureInPictureSupported = typeof document.exitPictureInPicture === 'function' && typeof HTMLVideoElement.prototype.requestPictureInPicture === 'function' +const isScreenShareSupported = !(/Mobi|Android|iPhone|iPad|HarmonyOS|HMSCore/i.test(navigator.userAgent)) + export { addSplitSymbol, delSplitSymbol, isWechat, isFullscreenSupported, isPictureInPictureSupported, + isScreenShareSupported, } diff --git a/webapp/store/atom.ts b/webapp/store/atom.ts index 4014bb6..1b06f12 100644 --- a/webapp/store/atom.ts +++ b/webapp/store/atom.ts @@ -21,6 +21,9 @@ interface UserStatus { screen: boolean } +const languageAtom = atom('English') +languageAtom.debugLabel = 'languageAtom' + const meetingIdAtom = atom('') meetingIdAtom.debugLabel = 'meetingIdAtom' const meetingJoinedAtom = atom(false) @@ -44,7 +47,14 @@ speakerStatusAtom.debugLabel = 'speakerStatus' const settingsEnabledScreenAtom = atom(/Mobi|Android|iPhone|iPad|HarmonyOS|HMSCore/i.test(navigator.userAgent)) settingsEnabledScreenAtom.debugLabel = 'settingsEnabledScreen' +const videoResolutionAtom = atom('480') +videoResolutionAtom.debugLabel = 'videoResolutionAtom' +const screenShareResolutionAtom = atom('720') +screenShareResolutionAtom.debugLabel = 'screenShareResolutionAtom' + export { + languageAtom, + locationAtom, presentationStreamAtom, @@ -55,6 +65,9 @@ export { speakerStatusAtom, settingsEnabledScreenAtom, + + videoResolutionAtom, + screenShareResolutionAtom, } export type { From c9449879b8c76b7c05f8fceb28f9f045ef3cf2ab Mon Sep 17 00:00:00 2001 From: huanghuang358 <178640467+huanghuang358@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:37:41 +0800 Subject: [PATCH 2/5] refactor(webapp): extract componet of setting items & simplify type declaration of props --- webapp/components/settings.tsx | 299 +++++++++++++++------------------ 1 file changed, 134 insertions(+), 165 deletions(-) diff --git a/webapp/components/settings.tsx b/webapp/components/settings.tsx index 82f0853..2457316 100644 --- a/webapp/components/settings.tsx +++ b/webapp/components/settings.tsx @@ -18,6 +18,69 @@ import { } from './svg/setting' import { delStorage } from '../lib/storage' +interface SettingsProps { + onClose: () => void + onChangedDeviceVideo: (current: string, constraints?: MediaTrackConstraints) => void + isScreenSharing: boolean +} + +interface SettingItemProps { + children?: React.ReactNode; + label: string; + tooltips?: string[]; +} + +interface SettingMediaProps { + onChangedDeviceVideo: (current: string, constraints?: MediaTrackConstraints) => void + isScreenSharing: boolean +} + +function SettingItem(props: SettingItemProps) { + const [tooltipVisible, setTooltipVisible] = useState(false) + return ( +
+
+
+ + {props.tooltips && ( +
setTooltipVisible(true)} + onMouseLeave={() => setTooltipVisible(false)} + > + + {tooltipVisible && ( +
+
+
+ )} +
+ )} +
+ {props.children} +
+ {props.tooltips && tooltipVisible && ( +
+
+ {props.tooltips.map((tip, index) => ( +

+ {tip} +

+ ))} +
+
+ )} +
+ ) +} + function SettingGeneral() { const [language, setLanguage] = useAtom(languageAtom) const languageOptions = ['English'] @@ -27,32 +90,30 @@ function SettingGeneral() { // todo: add support of more languages } return ( -
-

- Language -

- +
+ + +
) } -function SettingMedia(props: { isScreenSharing: boolean, onChangedDeviceVideo: (current: string, constraints?: MediaTrackConstraints) => void }) { +function SettingMedia(props: SettingMediaProps) { const [videoResolution, setVideoResolution] = useAtom(videoResolutionAtom) const [screenShareResolution, setScreenShareResolution] = useAtom(screenShareResolutionAtom) - const [isTipShowed, setIsTipShowed] = useState(false) const videoResolutionOptions = [ { label: '480p (Default)', value: '480' }, ] @@ -72,10 +133,7 @@ function SettingMedia(props: { isScreenSharing: boolean, onChangedDeviceVideo: ( } return (
-
-

- Video Resolution -

+ -
-
-
-
-

- Screen Share Resolution -

-
setIsTipShowed(true)} - onMouseLeave={() => setIsTipShowed(false)} + + + onChangeScreenShareResolution(e.target.value)} - > - {screenShareResolutionOptions.map(option => ( - - ))} - -
-
- {isTipShowed && ( -
-

- 1. Meaningless for mobile devices; -

-

- 2. If screen is sharing, any changes on this property will stop sharing. -

-
- )} -
-
+ {option.label} + + ))} + +
) } function SettingAdvanced() { - const [isTipShowed, setIsTipShowed] = useState(false) - const [isTipShowed2, setIsTipShowed2] = useState(false) const [meetingId] = useAtom(meetingIdAtom) const [settingsEnabledScreen, setsettingsEnabledScreen] = useAtom(settingsEnabledScreenAtom) const [isReset, setIsReset] = useState(false) return (
-
-
-
-

- Local Storage -

-
setIsTipShowed(true)} - onMouseLeave={() => setIsTipShowed(false)} - > - - {isTipShowed && ( -
-
-
- )} -
-
- -
-
- {isTipShowed && ( -
-

- 1. Local storage only can be reset on the homepage. -

-

- 2. Single use only. -

-
- )} -
-
-
-
-
- -
setIsTipShowed2(true)} - onMouseLeave={() => setIsTipShowed2(false)} - > - - {isTipShowed2 && ( -
-
-
- )} -
-
-
-
- {isTipShowed2 && ( -
-

- 1. Defaults to checked on mobile devices; -

-

- 2. Changable only on the homepage. -

-
- )} -
-
+ + + + + +
) } -export default function Settings(props: { onClose: () => void, onChangedDeviceVideo: (current: string, constraints?: MediaTrackConstraints) => void, isScreenSharing: boolean }) { +export default function Settings(props: SettingsProps) { const [activeTab, setActiveTab] = useState('General') const settingsOptions = [ { label: 'General', icon: SvgGeneral }, From a0d6e01949fcb42a69f813b2097497e4fb1ff0dc Mon Sep 17 00:00:00 2001 From: huanghuang358 <178640467+huanghuang358@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:31:10 +0800 Subject: [PATCH 3/5] feat(webapp): persist setting values through zustand --- package-lock.json | 260 +++++++++++++++++++-------------- package.json | 3 +- webapp/components/device.tsx | 17 ++- webapp/components/settings.tsx | 27 ++-- webapp/store/atom.ts | 19 --- webapp/store/settingStore.ts | 37 +++++ 6 files changed, 210 insertions(+), 153 deletions(-) create mode 100644 webapp/store/settingStore.ts diff --git a/package-lock.json b/package-lock.json index ca52358..6c42f35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "react-dom": "^18.3.1", "swr": "^2.2.5", "wavesurfer.js": "7.8.2", - "whip-whep": "1.2.0" + "whip-whep": "1.2.0", + "zustand": "^5.0.6" }, "devDependencies": { "@eslint/js": "^9.15.0", @@ -93,9 +94,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", "dev": true, "license": "MIT", "engines": { @@ -103,22 +104,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -134,14 +135,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -151,14 +152,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -168,29 +169,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -200,9 +201,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -230,9 +231,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -240,27 +241,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -270,13 +271,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", - "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -286,13 +287,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", - "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -326,17 +327,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -345,9 +346,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "dev": true, "license": "MIT", "dependencies": { @@ -1246,6 +1247,13 @@ "redux": "^3.1.0 || ^4.0.0 || ^5.0.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/pluginutils": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", @@ -1808,9 +1816,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2249,23 +2257,24 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", - "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", + "integrity": "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/core": "^7.27.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "node_modules/@vue/compiler-core": { @@ -2489,9 +2498,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2513,9 +2522,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -2533,10 +2542,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -2582,9 +2591,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001677", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", - "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, "funding": [ { @@ -2868,9 +2877,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.51", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.51.tgz", - "integrity": "sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==", + "version": "1.5.178", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz", + "integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==", "dev": true, "license": "ISC" }, @@ -3608,9 +3617,9 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -3897,9 +3906,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, "license": "MIT" }, @@ -4209,9 +4218,9 @@ } }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", "engines": { @@ -5258,9 +5267,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -5279,7 +5288,7 @@ "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -5970,6 +5979,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.6.tgz", + "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 9522404..e538f3a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "react-dom": "^18.3.1", "swr": "^2.2.5", "wavesurfer.js": "7.8.2", - "whip-whep": "1.2.0" + "whip-whep": "1.2.0", + "zustand": "^5.0.6" }, "devDependencies": { "@eslint/js": "^9.15.0", diff --git a/webapp/components/device.tsx b/webapp/components/device.tsx index 7684cef..d713089 100644 --- a/webapp/components/device.tsx +++ b/webapp/components/device.tsx @@ -10,8 +10,6 @@ import { isScreenShareSupported } from '../lib/util' import { deviceSpeakerAtom, speakerStatusAtom, - settingsEnabledScreenAtom, - screenShareResolutionAtom, } from './../store/atom' import Loading from './svg/loading' @@ -23,6 +21,8 @@ import { SvgBackgroundCancel, SvgBackground } from './svg/background' import { SvgSetting } from './svg/setting' import Settings from './settings' +import {useSettingStore} from '../store/settingStore' + function toDevice(info: MediaDeviceInfo): Device { const deviceId = info.deviceId let label = info.label @@ -45,7 +45,7 @@ export default function DeviceBar(props: { streamId: string }) { const [currentDeviceSpeaker, setCurrentDeviceSpeaker] = useAtom(deviceSpeakerAtom) const [speakerStatus, setSpeakerStatus] = useAtom(speakerStatusAtom) - const [settingsEnabledScreen] = useAtom(settingsEnabledScreenAtom) + const screenShareButtonShowed = useSettingStore(state => state.screenShareButtonShowed) const [virtualBackgroundEnabled, setVirtualBackgroundEnabled] = useState(false) const { @@ -187,10 +187,11 @@ export default function DeviceBar(props: { streamId: string }) { { label: '1080p', value: '1080' }, { label: 'Native', value: 'native' } ] - const [currentScreenResolution, setCurrentScreenResolution] = useAtom(screenShareResolutionAtom) + const screenShareResolution = useSettingStore(state => state.screenShareResolution) + const setScreenShareResolution = useSettingStore(state => state.setScreenShareResolution) const toggleEnableScreen = async () => { - const height = Number.parseInt(currentScreenResolution) + const height = Number.parseInt(screenShareResolution) const constraints = Number.isNaN(height) ? {} : { height } setLoadingScreen(true) await onChangedDeviceVideo(userStatus.screen ? deviceNone.deviceId : deviceScreen.deviceId, constraints) @@ -198,7 +199,7 @@ export default function DeviceBar(props: { streamId: string }) { } const onChangeScreenShareResolution = async (resolution: string) => { - setCurrentScreenResolution(resolution) + setScreenShareResolution(resolution) const height = Number.parseInt(resolution) const constraints = Number.isNaN(height) ? {} : { height } setLoadingScreen(true) @@ -327,7 +328,7 @@ export default function DeviceBar(props: { streamId: string }) { - {!settingsEnabledScreen && ( + {!screenShareButtonShowed && (