UNPKG

voicebot-react-native-expo

Version:

This is a voicebot-react-native package of Kipps AI voice bot for React Native Expo

1 lines 70.2 kB
{"version":3,"file":"prefabs.mjs","sources":["../src/prefabs/Chat.tsx","../src/prefabs/MediaDeviceMenu.tsx","../src/hooks/useWarnAboutMissingStyles.ts","../src/prefabs/PreJoin.tsx","../src/hooks/useSettingsToggle.ts","../src/components/controls/SettingsMenuToggle.tsx","../src/prefabs/ControlBar.tsx","../src/prefabs/VideoConference.tsx","../src/prefabs/AudioConference.tsx","../src/prefabs/VoiceAssistantControlBar.tsx"],"sourcesContent":["import type { ChatMessage, ChatOptions } from '@livekit/components-core';\nimport * as React from 'react';\nimport { useMaybeLayoutContext } from '../context';\nimport { cloneSingleChild } from '../utils';\nimport type { MessageFormatter } from '../components/ChatEntry';\nimport { ChatEntry } from '../components/ChatEntry';\nimport { useChat } from '../hooks/useChat';\nimport { ChatToggle } from '../components';\nimport { ChatCloseIcon } from '../assets/icons';\n\n/** @public */\nexport interface ChatProps extends React.HTMLAttributes<HTMLDivElement>, ChatOptions {\n messageFormatter?: MessageFormatter;\n}\n\n/**\n * The Chat component adds a basis chat functionality to the LiveKit room. The messages are distributed to all participants\n * in the room. Only users who are in the room at the time of dispatch will receive the message.\n *\n * @example\n * ```tsx\n * <LiveKitRoom>\n * <Chat />\n * </LiveKitRoom>\n * ```\n * @public\n */\nexport function Chat({\n messageFormatter,\n messageDecoder,\n messageEncoder,\n channelTopic,\n ...props\n}: ChatProps) {\n const inputRef = React.useRef<HTMLInputElement>(null);\n const ulRef = React.useRef<HTMLUListElement>(null);\n\n const chatOptions: ChatOptions = React.useMemo(() => {\n return { messageDecoder, messageEncoder, channelTopic };\n }, [messageDecoder, messageEncoder, channelTopic]);\n\n const { send, chatMessages, isSending } = useChat(chatOptions);\n\n const layoutContext = useMaybeLayoutContext();\n const lastReadMsgAt = React.useRef<ChatMessage['timestamp']>(0);\n\n async function handleSubmit(event: React.FormEvent) {\n event.preventDefault();\n if (inputRef.current && inputRef.current.value.trim() !== '') {\n if (send) {\n await send(inputRef.current.value);\n inputRef.current.value = '';\n inputRef.current.focus();\n }\n }\n }\n\n React.useEffect(() => {\n if (ulRef) {\n ulRef.current?.scrollTo({ top: ulRef.current.scrollHeight });\n }\n }, [ulRef, chatMessages]);\n\n React.useEffect(() => {\n if (!layoutContext || chatMessages.length === 0) {\n return;\n }\n\n if (\n layoutContext.widget.state?.showChat &&\n chatMessages.length > 0 &&\n lastReadMsgAt.current !== chatMessages[chatMessages.length - 1]?.timestamp\n ) {\n lastReadMsgAt.current = chatMessages[chatMessages.length - 1]?.timestamp;\n return;\n }\n\n const unreadMessageCount = chatMessages.filter(\n (msg) => !lastReadMsgAt.current || msg.timestamp > lastReadMsgAt.current,\n ).length;\n\n const { widget } = layoutContext;\n if (unreadMessageCount > 0 && widget.state?.unreadMessages !== unreadMessageCount) {\n widget.dispatch?.({ msg: 'unread_msg', count: unreadMessageCount });\n }\n }, [chatMessages, layoutContext?.widget]);\n\n return (\n <div {...props} className=\"lk-chat\">\n <div className=\"lk-chat-header\">\n Messages\n <ChatToggle className=\"lk-close-button\">\n <ChatCloseIcon />\n </ChatToggle>\n </div>\n\n <ul className=\"lk-list lk-chat-messages\" ref={ulRef}>\n {props.children\n ? chatMessages.map((msg, idx) =>\n cloneSingleChild(props.children, {\n entry: msg,\n key: msg.id ?? idx,\n messageFormatter,\n }),\n )\n : chatMessages.map((msg, idx, allMsg) => {\n const hideName = idx >= 1 && allMsg[idx - 1].from === msg.from;\n // If the time delta between two messages is bigger than 60s show timestamp.\n const hideTimestamp = idx >= 1 && msg.timestamp - allMsg[idx - 1].timestamp < 60_000;\n\n return (\n <ChatEntry\n key={msg.id ?? idx}\n hideName={hideName}\n hideTimestamp={hideName === false ? false : hideTimestamp} // If we show the name always show the timestamp as well.\n entry={msg}\n messageFormatter={messageFormatter}\n />\n );\n })}\n </ul>\n <form className=\"lk-chat-form\" onSubmit={handleSubmit}>\n <input\n className=\"lk-form-control lk-chat-form-input\"\n disabled={isSending}\n ref={inputRef}\n type=\"text\"\n placeholder=\"Enter a message...\"\n onInput={(ev) => ev.stopPropagation()}\n onKeyDown={(ev) => ev.stopPropagation()}\n onKeyUp={(ev) => ev.stopPropagation()}\n />\n <button type=\"submit\" className=\"lk-button lk-chat-form-button\" disabled={isSending}>\n Send\n </button>\n </form>\n </div>\n );\n}\n","import { computeMenuPosition, wasClickOutside } from '@livekit/components-core';\nimport * as React from 'react';\nimport { MediaDeviceSelect } from '../components/controls/MediaDeviceSelect';\nimport { log } from '@livekit/components-core';\nimport type { LocalAudioTrack, LocalVideoTrack } from 'livekit-client';\n\n/** @public */\nexport interface MediaDeviceMenuProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n kind?: MediaDeviceKind;\n initialSelection?: string;\n onActiveDeviceChange?: (kind: MediaDeviceKind, deviceId: string) => void;\n tracks?: Partial<Record<MediaDeviceKind, LocalAudioTrack | LocalVideoTrack | undefined>>;\n /**\n * this will call getUserMedia if the permissions are not yet given to enumerate the devices with device labels.\n * in some browsers multiple calls to getUserMedia result in multiple permission prompts.\n * It's generally advised only flip this to true, once a (preview) track has been acquired successfully with the\n * appropriate permissions.\n *\n * @see {@link PreJoin}\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices | MDN enumerateDevices}\n */\n requestPermissions?: boolean;\n}\n\n/**\n * The `MediaDeviceMenu` component is a button that opens a menu that lists\n * all media devices and allows the user to select them.\n *\n * @remarks\n * This component is implemented with the `MediaDeviceSelect` LiveKit components.\n *\n * @example\n * ```tsx\n * <LiveKitRoom>\n * <MediaDeviceMenu />\n * </LiveKitRoom>\n * ```\n * @public\n */\nexport function MediaDeviceMenu({\n kind,\n initialSelection,\n onActiveDeviceChange,\n tracks,\n requestPermissions = false,\n ...props\n}: MediaDeviceMenuProps) {\n const [isOpen, setIsOpen] = React.useState(false);\n const [devices, setDevices] = React.useState<MediaDeviceInfo[]>([]);\n const [updateRequired, setUpdateRequired] = React.useState<boolean>(true);\n const [needPermissions, setNeedPermissions] = React.useState(requestPermissions);\n\n const handleActiveDeviceChange = (kind: MediaDeviceKind, deviceId: string) => {\n log.debug('handle device change');\n setIsOpen(false);\n onActiveDeviceChange?.(kind, deviceId);\n };\n\n const button = React.useRef<HTMLButtonElement>(null);\n const tooltip = React.useRef<HTMLDivElement>(null);\n\n React.useLayoutEffect(() => {\n if (isOpen) {\n setNeedPermissions(true);\n }\n }, [isOpen]);\n\n React.useLayoutEffect(() => {\n if (button.current && tooltip.current && (devices || updateRequired)) {\n computeMenuPosition(button.current, tooltip.current).then(({ x, y }) => {\n if (tooltip.current) {\n Object.assign(tooltip.current.style, { left: `${x}px`, top: `${y}px` });\n }\n });\n }\n setUpdateRequired(false);\n }, [button, tooltip, devices, updateRequired]);\n\n const handleClickOutside = React.useCallback(\n (event: MouseEvent) => {\n if (!tooltip.current) {\n return;\n }\n if (event.target === button.current) {\n return;\n }\n if (isOpen && wasClickOutside(tooltip.current, event)) {\n setIsOpen(false);\n }\n },\n [isOpen, tooltip, button],\n );\n\n React.useEffect(() => {\n document.addEventListener<'click'>('click', handleClickOutside);\n window.addEventListener<'resize'>('resize', () => setUpdateRequired(true));\n return () => {\n document.removeEventListener<'click'>('click', handleClickOutside);\n window.removeEventListener<'resize'>('resize', () => setUpdateRequired(true));\n };\n }, [handleClickOutside, setUpdateRequired]);\n\n return (\n <>\n <button\n className=\"lk-button lk-button-menu\"\n aria-pressed={isOpen}\n {...props}\n onClick={() => setIsOpen(!isOpen)}\n ref={button}\n >\n {props.children}\n </button>\n {/** only render when enabled in order to make sure that the permissions are requested only if the menu is enabled */}\n {!props.disabled && (\n <div\n className=\"lk-device-menu\"\n ref={tooltip}\n style={{ visibility: isOpen ? 'visible' : 'hidden' }}\n >\n {kind ? (\n <MediaDeviceSelect\n initialSelection={initialSelection}\n onActiveDeviceChange={(deviceId) => handleActiveDeviceChange(kind, deviceId)}\n onDeviceListChange={setDevices}\n kind={kind}\n track={tracks?.[kind]}\n requestPermissions={needPermissions}\n />\n ) : (\n <>\n <div className=\"lk-device-menu-heading\">Audio inputs</div>\n <MediaDeviceSelect\n kind=\"audioinput\"\n onActiveDeviceChange={(deviceId) =>\n handleActiveDeviceChange('audioinput', deviceId)\n }\n onDeviceListChange={setDevices}\n track={tracks?.audioinput}\n requestPermissions={needPermissions}\n />\n <div className=\"lk-device-menu-heading\">Video inputs</div>\n <MediaDeviceSelect\n kind=\"videoinput\"\n onActiveDeviceChange={(deviceId) =>\n handleActiveDeviceChange('videoinput', deviceId)\n }\n onDeviceListChange={setDevices}\n track={tracks?.videoinput}\n requestPermissions={needPermissions}\n />\n </>\n )}\n </div>\n )}\n </>\n );\n}\n","import * as React from 'react';\nimport { warnAboutMissingStyles } from '../utils';\n\n/**\n * @internal\n */\nexport function useWarnAboutMissingStyles() {\n React.useEffect(() => {\n warnAboutMissingStyles();\n }, []);\n}\n","import type {\n CreateLocalTracksOptions,\n LocalAudioTrack,\n LocalTrack,\n LocalVideoTrack,\n} from 'livekit-client';\nimport {\n createLocalAudioTrack,\n createLocalTracks,\n createLocalVideoTrack,\n facingModeFromLocalTrack,\n Track,\n VideoPresets,\n Mutex,\n} from 'livekit-client';\nimport * as React from 'react';\nimport { MediaDeviceMenu } from './MediaDeviceMenu';\nimport { TrackToggle } from '../components/controls/TrackToggle';\nimport type { LocalUserChoices } from '@livekit/components-core';\nimport { log } from '@livekit/components-core';\nimport { ParticipantPlaceholder } from '../assets/images';\nimport { useMediaDevices, usePersistentUserChoices } from '../hooks';\nimport { useWarnAboutMissingStyles } from '../hooks/useWarnAboutMissingStyles';\nimport { defaultUserChoices } from '@livekit/components-core';\n\n/**\n * Props for the PreJoin component.\n * @public\n */\nexport interface PreJoinProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSubmit' | 'onError'> {\n /** This function is called with the `LocalUserChoices` if validation is passed. */\n onSubmit?: (values: LocalUserChoices) => void;\n /**\n * Provide your custom validation function. Only if validation is successful the user choices are past to the onSubmit callback.\n */\n onValidate?: (values: LocalUserChoices) => boolean;\n onError?: (error: Error) => void;\n /** Prefill the input form with initial values. */\n defaults?: Partial<LocalUserChoices>;\n /** Display a debug window for your convenience. */\n debug?: boolean;\n joinLabel?: string;\n micLabel?: string;\n camLabel?: string;\n userLabel?: string;\n /**\n * If true, user choices are persisted across sessions.\n * @defaultValue true\n * @alpha\n */\n persistUserChoices?: boolean;\n}\n\n/** @alpha */\nexport function usePreviewTracks(\n options: CreateLocalTracksOptions,\n onError?: (err: Error) => void,\n) {\n const [tracks, setTracks] = React.useState<LocalTrack[]>();\n\n const trackLock = React.useMemo(() => new Mutex(), []);\n\n React.useEffect(() => {\n let needsCleanup = false;\n let localTracks: Array<LocalTrack> = [];\n trackLock.lock().then(async (unlock) => {\n try {\n if (options.audio || options.video) {\n localTracks = await createLocalTracks(options);\n\n if (needsCleanup) {\n localTracks.forEach((tr) => tr.stop());\n } else {\n setTracks(localTracks);\n }\n }\n } catch (e: unknown) {\n if (onError && e instanceof Error) {\n onError(e);\n } else {\n log.error(e);\n }\n } finally {\n unlock();\n }\n });\n\n return () => {\n needsCleanup = true;\n localTracks.forEach((track) => {\n track.stop();\n });\n };\n }, [JSON.stringify(options), onError, trackLock]);\n\n return tracks;\n}\n\n/** @public */\nexport function usePreviewDevice<T extends LocalVideoTrack | LocalAudioTrack>(\n enabled: boolean,\n deviceId: string,\n kind: 'videoinput' | 'audioinput',\n) {\n const [deviceError, setDeviceError] = React.useState<Error | null>(null);\n const [isCreatingTrack, setIsCreatingTrack] = React.useState<boolean>(false);\n\n const devices = useMediaDevices({ kind });\n const [selectedDevice, setSelectedDevice] = React.useState<MediaDeviceInfo | undefined>(\n undefined,\n );\n\n const [localTrack, setLocalTrack] = React.useState<T>();\n const [localDeviceId, setLocalDeviceId] = React.useState<string>(deviceId);\n\n React.useEffect(() => {\n setLocalDeviceId(deviceId);\n }, [deviceId]);\n\n const createTrack = async (deviceId: string, kind: 'videoinput' | 'audioinput') => {\n try {\n const track =\n kind === 'videoinput'\n ? await createLocalVideoTrack({\n deviceId: deviceId,\n resolution: VideoPresets.h720.resolution,\n })\n : await createLocalAudioTrack({ deviceId });\n\n const newDeviceId = await track.getDeviceId();\n if (newDeviceId && deviceId !== newDeviceId) {\n prevDeviceId.current = newDeviceId;\n setLocalDeviceId(newDeviceId);\n }\n setLocalTrack(track as T);\n } catch (e) {\n if (e instanceof Error) {\n setDeviceError(e);\n }\n }\n };\n\n const switchDevice = async (track: LocalVideoTrack | LocalAudioTrack, id: string) => {\n await track.setDeviceId(id);\n prevDeviceId.current = id;\n };\n\n const prevDeviceId = React.useRef(localDeviceId);\n\n React.useEffect(() => {\n if (enabled && !localTrack && !deviceError && !isCreatingTrack) {\n log.debug('creating track', kind);\n setIsCreatingTrack(true);\n createTrack(localDeviceId, kind).finally(() => {\n setIsCreatingTrack(false);\n });\n }\n }, [enabled, localTrack, deviceError, isCreatingTrack]);\n\n // switch camera device\n React.useEffect(() => {\n if (!localTrack) {\n return;\n }\n if (!enabled) {\n log.debug(`muting ${kind} track`);\n localTrack.mute().then(() => log.debug(localTrack.mediaStreamTrack));\n } else if (selectedDevice?.deviceId && prevDeviceId.current !== selectedDevice?.deviceId) {\n log.debug(`switching ${kind} device from`, prevDeviceId.current, selectedDevice.deviceId);\n switchDevice(localTrack, selectedDevice.deviceId);\n } else {\n log.debug(`unmuting local ${kind} track`);\n localTrack.unmute();\n }\n }, [localTrack, selectedDevice, enabled, kind]);\n\n React.useEffect(() => {\n return () => {\n if (localTrack) {\n log.debug(`stopping local ${kind} track`);\n localTrack.stop();\n localTrack.mute();\n }\n };\n }, []);\n\n React.useEffect(() => {\n setSelectedDevice(devices?.find((dev) => dev.deviceId === localDeviceId));\n }, [localDeviceId, devices]);\n\n return {\n selectedDevice,\n localTrack,\n deviceError,\n };\n}\n\n/**\n * The `PreJoin` prefab component is normally presented to the user before he enters a room.\n * This component allows the user to check and select the preferred media device (camera und microphone).\n * On submit the user decisions are returned, which can then be passed on to the `LiveKitRoom` so that the user enters the room with the correct media devices.\n *\n * @remarks\n * This component is independent of the `LiveKitRoom` component and should not be nested within it.\n * Because it only accesses the local media tracks this component is self-contained and works without connection to the LiveKit server.\n *\n * @example\n * ```tsx\n * <PreJoin />\n * ```\n * @public\n */\nexport function PreJoin({\n defaults = {},\n onValidate,\n onSubmit,\n onError,\n debug,\n joinLabel = 'Join Room',\n micLabel = 'Microphone',\n camLabel = 'Camera',\n userLabel = 'Username',\n persistUserChoices = true,\n ...htmlProps\n}: PreJoinProps) {\n const [userChoices, setUserChoices] = React.useState(defaultUserChoices);\n\n // TODO: Remove and pipe `defaults` object directly into `usePersistentUserChoices` once we fully switch from type `LocalUserChoices` to `UserChoices`.\n const partialDefaults: Partial<LocalUserChoices> = {\n ...(defaults.audioDeviceId !== undefined && { audioDeviceId: defaults.audioDeviceId }),\n ...(defaults.videoDeviceId !== undefined && { videoDeviceId: defaults.videoDeviceId }),\n ...(defaults.audioEnabled !== undefined && { audioEnabled: defaults.audioEnabled }),\n ...(defaults.videoEnabled !== undefined && { videoEnabled: defaults.videoEnabled }),\n ...(defaults.username !== undefined && { username: defaults.username }),\n };\n\n const {\n userChoices: initialUserChoices,\n saveAudioInputDeviceId,\n saveAudioInputEnabled,\n saveVideoInputDeviceId,\n saveVideoInputEnabled,\n saveUsername,\n } = usePersistentUserChoices({\n defaults: partialDefaults,\n preventSave: !persistUserChoices,\n preventLoad: !persistUserChoices,\n });\n\n // Initialize device settings\n const [audioEnabled, setAudioEnabled] = React.useState<boolean>(initialUserChoices.audioEnabled);\n const [videoEnabled, setVideoEnabled] = React.useState<boolean>(initialUserChoices.videoEnabled);\n const [audioDeviceId, setAudioDeviceId] = React.useState<string>(\n initialUserChoices.audioDeviceId,\n );\n const [videoDeviceId, setVideoDeviceId] = React.useState<string>(\n initialUserChoices.videoDeviceId,\n );\n const [username, setUsername] = React.useState(initialUserChoices.username);\n\n // Save user choices to persistent storage.\n React.useEffect(() => {\n saveAudioInputEnabled(audioEnabled);\n }, [audioEnabled, saveAudioInputEnabled]);\n React.useEffect(() => {\n saveVideoInputEnabled(videoEnabled);\n }, [videoEnabled, saveVideoInputEnabled]);\n React.useEffect(() => {\n saveAudioInputDeviceId(audioDeviceId);\n }, [audioDeviceId, saveAudioInputDeviceId]);\n React.useEffect(() => {\n saveVideoInputDeviceId(videoDeviceId);\n }, [videoDeviceId, saveVideoInputDeviceId]);\n React.useEffect(() => {\n saveUsername(username);\n }, [username, saveUsername]);\n\n const tracks = usePreviewTracks(\n {\n audio: audioEnabled ? { deviceId: initialUserChoices.audioDeviceId } : false,\n video: videoEnabled ? { deviceId: initialUserChoices.videoDeviceId } : false,\n },\n onError,\n );\n\n const videoEl = React.useRef(null);\n\n const videoTrack = React.useMemo(\n () => tracks?.filter((track) => track.kind === Track.Kind.Video)[0] as LocalVideoTrack,\n [tracks],\n );\n\n const facingMode = React.useMemo(() => {\n if (videoTrack) {\n const { facingMode } = facingModeFromLocalTrack(videoTrack);\n return facingMode;\n } else {\n return 'undefined';\n }\n }, [videoTrack]);\n\n const audioTrack = React.useMemo(\n () => tracks?.filter((track) => track.kind === Track.Kind.Audio)[0] as LocalAudioTrack,\n [tracks],\n );\n\n React.useEffect(() => {\n if (videoEl.current && videoTrack) {\n videoTrack.unmute();\n videoTrack.attach(videoEl.current);\n }\n\n return () => {\n videoTrack?.detach();\n };\n }, [videoTrack]);\n\n const [isValid, setIsValid] = React.useState<boolean>();\n\n const handleValidation = React.useCallback(\n (values: LocalUserChoices) => {\n if (typeof onValidate === 'function') {\n return onValidate(values);\n } else {\n return values.username !== '';\n }\n },\n [onValidate],\n );\n\n React.useEffect(() => {\n const newUserChoices = {\n username,\n videoEnabled,\n videoDeviceId,\n audioEnabled,\n audioDeviceId,\n };\n setUserChoices(newUserChoices);\n setIsValid(handleValidation(newUserChoices));\n }, [username, videoEnabled, handleValidation, audioEnabled, audioDeviceId, videoDeviceId]);\n\n function handleSubmit(event: React.FormEvent) {\n event.preventDefault();\n if (handleValidation(userChoices)) {\n if (typeof onSubmit === 'function') {\n onSubmit(userChoices);\n }\n } else {\n log.warn('Validation failed with: ', userChoices);\n }\n }\n\n useWarnAboutMissingStyles();\n\n return (\n <div className=\"lk-prejoin\" {...htmlProps}>\n <div className=\"lk-video-container\">\n {videoTrack && (\n <video ref={videoEl} width=\"1280\" height=\"720\" data-lk-facing-mode={facingMode} />\n )}\n {(!videoTrack || !videoEnabled) && (\n <div className=\"lk-camera-off-note\">\n <ParticipantPlaceholder />\n </div>\n )}\n </div>\n <div className=\"lk-button-group-container\">\n <div className=\"lk-button-group audio\">\n <TrackToggle\n initialState={audioEnabled}\n source={Track.Source.Microphone}\n onChange={(enabled) => setAudioEnabled(enabled)}\n >\n {micLabel}\n </TrackToggle>\n <div className=\"lk-button-group-menu\">\n <MediaDeviceMenu\n initialSelection={audioDeviceId}\n kind=\"audioinput\"\n disabled={!audioTrack}\n tracks={{ audioinput: audioTrack }}\n onActiveDeviceChange={(_, id) => setAudioDeviceId(id)}\n />\n </div>\n </div>\n <div className=\"lk-button-group video\">\n <TrackToggle\n initialState={videoEnabled}\n source={Track.Source.Camera}\n onChange={(enabled) => setVideoEnabled(enabled)}\n >\n {camLabel}\n </TrackToggle>\n <div className=\"lk-button-group-menu\">\n <MediaDeviceMenu\n initialSelection={videoDeviceId}\n kind=\"videoinput\"\n disabled={!videoTrack}\n tracks={{ videoinput: videoTrack }}\n onActiveDeviceChange={(_, id) => setVideoDeviceId(id)}\n />\n </div>\n </div>\n </div>\n\n <form className=\"lk-username-container\">\n <input\n className=\"lk-form-control\"\n id=\"username\"\n name=\"username\"\n type=\"text\"\n defaultValue={username}\n placeholder={userLabel}\n onChange={(inputEl) => setUsername(inputEl.target.value)}\n autoComplete=\"off\"\n />\n <button\n className=\"lk-button lk-join-button\"\n type=\"submit\"\n onClick={handleSubmit}\n disabled={!isValid}\n >\n {joinLabel}\n </button>\n </form>\n\n {debug && (\n <>\n <strong>User Choices:</strong>\n <ul className=\"lk-list\" style={{ overflow: 'hidden', maxWidth: '15rem' }}>\n <li>Username: {`${userChoices.username}`}</li>\n <li>Video Enabled: {`${userChoices.videoEnabled}`}</li>\n <li>Audio Enabled: {`${userChoices.audioEnabled}`}</li>\n <li>Video Device: {`${userChoices.videoDeviceId}`}</li>\n <li>Audio Device: {`${userChoices.audioDeviceId}`}</li>\n </ul>\n </>\n )}\n </div>\n );\n}\n","import { useLayoutContext } from '../context';\nimport { mergeProps } from '../mergeProps';\nimport * as React from 'react';\n\n/** @alpha */\nexport interface UseSettingsToggleProps {\n props: React.ButtonHTMLAttributes<HTMLButtonElement>;\n}\n\n/**\n * The `useSettingsToggle` hook provides state and functions for toggling the settings menu.\n * @remarks\n * Depends on the `LayoutContext` to work properly.\n * @see {@link SettingsMenu}\n * @alpha\n */\nexport function useSettingsToggle({ props }: UseSettingsToggleProps) {\n const { dispatch, state } = useLayoutContext().widget;\n const className = 'lk-button lk-settings-toggle';\n\n const mergedProps = React.useMemo(() => {\n return mergeProps(props, {\n className,\n onClick: () => {\n if (dispatch) dispatch({ msg: 'toggle_settings' });\n },\n 'aria-pressed': state?.showSettings ? 'true' : 'false',\n });\n }, [props, className, dispatch, state]);\n\n return { mergedProps };\n}\n","import * as React from 'react';\nimport { useSettingsToggle } from '../../hooks/useSettingsToggle';\n\n/** @alpha */\nexport interface SettingsMenuToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}\n\n/**\n * The `SettingsMenuToggle` component is a button that toggles the visibility of the `SettingsMenu` component.\n * @remarks\n * For the component to have any effect it has to live inside a `LayoutContext` context.\n *\n * @alpha\n */\nexport const SettingsMenuToggle: (\n props: SettingsMenuToggleProps & React.RefAttributes<HTMLButtonElement>,\n) => React.ReactNode = /* @__PURE__ */ React.forwardRef<HTMLButtonElement, SettingsMenuToggleProps>(\n function SettingsMenuToggle(props: SettingsMenuToggleProps, ref) {\n const { mergedProps } = useSettingsToggle({ props });\n\n return (\n <button ref={ref} {...mergedProps}>\n {props.children}\n </button>\n );\n },\n);\n","import { Track } from 'livekit-client';\nimport * as React from 'react';\nimport { MediaDeviceMenu } from './MediaDeviceMenu';\nimport { DisconnectButton } from '../components/controls/DisconnectButton';\nimport { TrackToggle } from '../components/controls/TrackToggle';\nimport { ChatIcon, GearIcon, LeaveIcon } from '../assets/icons';\nimport { ChatToggle } from '../components/controls/ChatToggle';\nimport { useLocalParticipantPermissions, usePersistentUserChoices } from '../hooks';\nimport { useMediaQuery } from '../hooks/internal';\nimport { useMaybeLayoutContext } from '../context';\nimport { supportsScreenSharing } from '@livekit/components-core';\nimport { mergeProps } from '../utils';\nimport { StartMediaButton } from '../components/controls/StartMediaButton';\nimport { SettingsMenuToggle } from '../components/controls/SettingsMenuToggle';\n\n/** @public */\nexport type ControlBarControls = {\n microphone?: boolean;\n camera?: boolean;\n chat?: boolean;\n screenShare?: boolean;\n leave?: boolean;\n settings?: boolean;\n};\n\n/** @public */\nexport interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {\n onDeviceError?: (error: { source: Track.Source; error: Error }) => void;\n variation?: 'minimal' | 'verbose' | 'textOnly';\n controls?: ControlBarControls;\n /**\n * If `true`, the user's device choices will be persisted.\n * This will enable the user to have the same device choices when they rejoin the room.\n * @defaultValue true\n * @alpha\n */\n saveUserChoices?: boolean;\n}\n\n/**\n * The `ControlBar` prefab gives the user the basic user interface to control their\n * media devices (camera, microphone and screen share), open the `Chat` and leave the room.\n *\n * @remarks\n * This component is build with other LiveKit components like `TrackToggle`,\n * `DeviceSelectorButton`, `DisconnectButton` and `StartAudio`.\n *\n * @example\n * ```tsx\n * <LiveKitRoom>\n * <ControlBar />\n * </LiveKitRoom>\n * ```\n * @public\n */\nexport function ControlBar({\n variation,\n controls,\n saveUserChoices = true,\n onDeviceError,\n ...props\n}: ControlBarProps) {\n const [isChatOpen, setIsChatOpen] = React.useState(false);\n const layoutContext = useMaybeLayoutContext();\n React.useEffect(() => {\n if (layoutContext?.widget.state?.showChat !== undefined) {\n setIsChatOpen(layoutContext?.widget.state?.showChat);\n }\n }, [layoutContext?.widget.state?.showChat]);\n const isTooLittleSpace = useMediaQuery(`(max-width: ${isChatOpen ? 1000 : 760}px)`);\n\n const defaultVariation = isTooLittleSpace ? 'minimal' : 'verbose';\n variation ??= defaultVariation;\n\n const visibleControls = { leave: true, ...controls };\n\n const localPermissions = useLocalParticipantPermissions();\n\n if (!localPermissions) {\n visibleControls.camera = false;\n visibleControls.chat = false;\n visibleControls.microphone = false;\n visibleControls.screenShare = false;\n } else {\n visibleControls.camera ??= localPermissions.canPublish;\n visibleControls.microphone ??= localPermissions.canPublish;\n visibleControls.screenShare ??= localPermissions.canPublish;\n visibleControls.chat ??= localPermissions.canPublishData && controls?.chat;\n }\n\n const showIcon = React.useMemo(\n () => variation === 'minimal' || variation === 'verbose',\n [variation],\n );\n const showText = React.useMemo(\n () => variation === 'textOnly' || variation === 'verbose',\n [variation],\n );\n\n const browserSupportsScreenSharing = supportsScreenSharing();\n\n const [isScreenShareEnabled, setIsScreenShareEnabled] = React.useState(false);\n\n const onScreenShareChange = React.useCallback(\n (enabled: boolean) => {\n setIsScreenShareEnabled(enabled);\n },\n [setIsScreenShareEnabled],\n );\n\n const htmlProps = mergeProps({ className: 'lk-control-bar' }, props);\n\n const {\n saveAudioInputEnabled,\n saveVideoInputEnabled,\n saveAudioInputDeviceId,\n saveVideoInputDeviceId,\n } = usePersistentUserChoices({ preventSave: !saveUserChoices });\n\n const microphoneOnChange = React.useCallback(\n (enabled: boolean, isUserInitiated: boolean) =>\n isUserInitiated ? saveAudioInputEnabled(enabled) : null,\n [saveAudioInputEnabled],\n );\n\n const cameraOnChange = React.useCallback(\n (enabled: boolean, isUserInitiated: boolean) =>\n isUserInitiated ? saveVideoInputEnabled(enabled) : null,\n [saveVideoInputEnabled],\n );\n\n return (\n <div {...htmlProps}>\n {visibleControls.microphone && (\n <div className=\"lk-button-group\">\n <TrackToggle\n source={Track.Source.Microphone}\n showIcon={showIcon}\n onChange={microphoneOnChange}\n onDeviceError={(error) => onDeviceError?.({ source: Track.Source.Microphone, error })}\n >\n {showText && 'Microphone'}\n </TrackToggle>\n <div className=\"lk-button-group-menu\">\n <MediaDeviceMenu\n kind=\"audioinput\"\n onActiveDeviceChange={(_kind, deviceId) => saveAudioInputDeviceId(deviceId ?? '')}\n />\n </div>\n </div>\n )}\n {visibleControls.camera && (\n <div className=\"lk-button-group\">\n <TrackToggle\n source={Track.Source.Camera}\n showIcon={showIcon}\n onChange={cameraOnChange}\n onDeviceError={(error) => onDeviceError?.({ source: Track.Source.Camera, error })}\n >\n {showText && 'Camera'}\n </TrackToggle>\n <div className=\"lk-button-group-menu\">\n <MediaDeviceMenu\n kind=\"videoinput\"\n onActiveDeviceChange={(_kind, deviceId) => saveVideoInputDeviceId(deviceId ?? '')}\n />\n </div>\n </div>\n )}\n {visibleControls.screenShare && browserSupportsScreenSharing && (\n <TrackToggle\n source={Track.Source.ScreenShare}\n captureOptions={{ audio: true, selfBrowserSurface: 'include' }}\n showIcon={showIcon}\n onChange={onScreenShareChange}\n onDeviceError={(error) => onDeviceError?.({ source: Track.Source.ScreenShare, error })}\n >\n {showText && (isScreenShareEnabled ? 'Stop screen share' : 'Share screen')}\n </TrackToggle>\n )}\n {visibleControls.chat && (\n <ChatToggle>\n {showIcon && <ChatIcon />}\n {showText && 'Chat'}\n </ChatToggle>\n )}\n {visibleControls.settings && (\n <SettingsMenuToggle>\n {showIcon && <GearIcon />}\n {showText && 'Settings'}\n </SettingsMenuToggle>\n )}\n {visibleControls.leave && (\n <DisconnectButton>\n {showIcon && <LeaveIcon />}\n {showText && 'Leave'}\n </DisconnectButton>\n )}\n <StartMediaButton />\n </div>\n );\n}\n","import type {\n MessageDecoder,\n MessageEncoder,\n TrackReferenceOrPlaceholder,\n WidgetState,\n} from '@livekit/components-core';\nimport { isEqualTrackRef, isTrackReference, isWeb, log } from '@livekit/components-core';\nimport { RoomEvent, Track } from 'livekit-client';\nimport * as React from 'react';\nimport type { MessageFormatter } from '../components';\nimport {\n CarouselLayout,\n ConnectionStateToast,\n FocusLayout,\n FocusLayoutContainer,\n GridLayout,\n LayoutContextProvider,\n ParticipantTile,\n RoomAudioRenderer,\n} from '../components';\nimport { useCreateLayoutContext } from '../context';\nimport { usePinnedTracks, useTracks } from '../hooks';\nimport { Chat } from './Chat';\nimport { ControlBar } from './ControlBar';\nimport { useWarnAboutMissingStyles } from '../hooks/useWarnAboutMissingStyles';\n\n/**\n * @public\n */\nexport interface VideoConferenceProps extends React.HTMLAttributes<HTMLDivElement> {\n chatMessageFormatter?: MessageFormatter;\n chatMessageEncoder?: MessageEncoder;\n chatMessageDecoder?: MessageDecoder;\n /** @alpha */\n SettingsComponent?: React.ComponentType;\n}\n\n/**\n * The `VideoConference` ready-made component is your drop-in solution for a classic video conferencing application.\n * It provides functionality such as focusing on one participant, grid view with pagination to handle large numbers\n * of participants, basic non-persistent chat, screen sharing, and more.\n *\n * @remarks\n * The component is implemented with other LiveKit components like `FocusContextProvider`,\n * `GridLayout`, `ControlBar`, `FocusLayoutContainer` and `FocusLayout`.\n * You can use these components as a starting point for your own custom video conferencing application.\n *\n * @example\n * ```tsx\n * <LiveKitRoom>\n * <VideoConference />\n * <LiveKitRoom>\n * ```\n * @public\n */\nexport function VideoConference({\n chatMessageFormatter,\n chatMessageDecoder,\n chatMessageEncoder,\n SettingsComponent,\n ...props\n}: VideoConferenceProps) {\n const [widgetState, setWidgetState] = React.useState<WidgetState>({\n showChat: false,\n unreadMessages: 0,\n showSettings: false,\n });\n const lastAutoFocusedScreenShareTrack = React.useRef<TrackReferenceOrPlaceholder | null>(null);\n\n const tracks = useTracks(\n [\n { source: Track.Source.Camera, withPlaceholder: true },\n { source: Track.Source.ScreenShare, withPlaceholder: false },\n ],\n { updateOnlyOn: [RoomEvent.ActiveSpeakersChanged], onlySubscribed: false },\n );\n\n const widgetUpdate = (state: WidgetState) => {\n log.debug('updating widget state', state);\n setWidgetState(state);\n };\n\n const layoutContext = useCreateLayoutContext();\n\n const screenShareTracks = tracks\n .filter(isTrackReference)\n .filter((track) => track.publication.source === Track.Source.ScreenShare);\n\n const focusTrack = usePinnedTracks(layoutContext)?.[0];\n const carouselTracks = tracks.filter((track) => !isEqualTrackRef(track, focusTrack));\n\n React.useEffect(() => {\n // If screen share tracks are published, and no pin is set explicitly, auto set the screen share.\n if (\n screenShareTracks.some((track) => track.publication.isSubscribed) &&\n lastAutoFocusedScreenShareTrack.current === null\n ) {\n log.debug('Auto set screen share focus:', { newScreenShareTrack: screenShareTracks[0] });\n layoutContext.pin.dispatch?.({ msg: 'set_pin', trackReference: screenShareTracks[0] });\n lastAutoFocusedScreenShareTrack.current = screenShareTracks[0];\n } else if (\n lastAutoFocusedScreenShareTrack.current &&\n !screenShareTracks.some(\n (track) =>\n track.publication.trackSid ===\n lastAutoFocusedScreenShareTrack.current?.publication?.trackSid,\n )\n ) {\n log.debug('Auto clearing screen share focus.');\n layoutContext.pin.dispatch?.({ msg: 'clear_pin' });\n lastAutoFocusedScreenShareTrack.current = null;\n }\n if (focusTrack && !isTrackReference(focusTrack)) {\n const updatedFocusTrack = tracks.find(\n (tr) =>\n tr.participant.identity === focusTrack.participant.identity &&\n tr.source === focusTrack.source,\n );\n if (updatedFocusTrack !== focusTrack && isTrackReference(updatedFocusTrack)) {\n layoutContext.pin.dispatch?.({ msg: 'set_pin', trackReference: updatedFocusTrack });\n }\n }\n }, [\n screenShareTracks\n .map((ref) => `${ref.publication.trackSid}_${ref.publication.isSubscribed}`)\n .join(),\n focusTrack?.publication?.trackSid,\n tracks,\n ]);\n\n useWarnAboutMissingStyles();\n\n return (\n <div className=\"lk-video-conference\" {...props}>\n {isWeb() && (\n <LayoutContextProvider\n value={layoutContext}\n // onPinChange={handleFocusStateChange}\n onWidgetChange={widgetUpdate}\n >\n <div className=\"lk-video-conference-inner\">\n {!focusTrack ? (\n <div className=\"lk-grid-layout-wrapper\">\n <GridLayout tracks={tracks}>\n <ParticipantTile />\n </GridLayout>\n </div>\n ) : (\n <div className=\"lk-focus-layout-wrapper\">\n <FocusLayoutContainer>\n <CarouselLayout tracks={carouselTracks}>\n <ParticipantTile />\n </CarouselLayout>\n {focusTrack && <FocusLayout trackRef={focusTrack} />}\n </FocusLayoutContainer>\n </div>\n )}\n <ControlBar controls={{ chat: true, settings: !!SettingsComponent }} />\n </div>\n <Chat\n style={{ display: widgetState.showChat ? 'grid' : 'none' }}\n messageFormatter={chatMessageFormatter}\n messageEncoder={chatMessageEncoder}\n messageDecoder={chatMessageDecoder}\n />\n {SettingsComponent && (\n <div\n className=\"lk-settings-menu-modal\"\n style={{ display: widgetState.showSettings ? 'block' : 'none' }}\n >\n <SettingsComponent />\n </div>\n )}\n </LayoutContextProvider>\n )}\n <RoomAudioRenderer />\n <ConnectionStateToast />\n </div>\n );\n}\n","import * as React from 'react';\nimport { ControlBar } from './ControlBar';\n\nimport { ParticipantAudioTile } from '../components/participant/ParticipantAudioTile';\nimport { LayoutContextProvider } from '../components/layout/LayoutContextProvider';\nimport type { WidgetState } from '@livekit/components-core';\nimport { Chat } from './Chat';\nimport { TrackLoop } from '../components';\nimport { useTracks } from '../hooks';\nimport { useWarnAboutMissingStyles } from '../hooks/useWarnAboutMissingStyles';\nimport { Track } from 'livekit-client';\n\n/** @public */\nexport interface AudioConferenceProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * This component is the default setup of a classic LiveKit audio conferencing app.\n * It provides functionality like switching between participant grid view and focus view.\n *\n * @remarks\n * The component is implemented with other LiveKit components like `FocusContextProvider`,\n * `GridLayout`, `ControlBar`, `FocusLayoutContainer` and `FocusLayout`.\n *\n * @example\n * ```tsx\n * <LiveKitRoom>\n * <AudioConference />\n * <LiveKitRoom>\n * ```\n * @public\n */\nexport function AudioConference({ ...props }: AudioConferenceProps) {\n const [widgetState, setWidgetState] = React.useState<WidgetState>({\n showChat: false,\n unreadMessages: 0,\n });\n\n const audioTracks = useTracks([Track.Source.Microphone]);\n\n useWarnAboutMissingStyles();\n\n return (\n <LayoutContextProvider onWidgetChange={setWidgetState}>\n <div className=\"lk-audio-conference\" {...props}>\n <div className=\"lk-audio-conference-stage\">\n <TrackLoop tracks={audioTracks}>\n <ParticipantAudioTile />\n </TrackLoop>\n </div>\n <ControlBar\n controls={{ microphone: true, screenShare: false, camera: false, chat: true }}\n />\n {widgetState.showChat && <Chat />}\n </div>\n </LayoutContextProvider>\n );\n}\n","import { Track } from 'livekit-client';\nimport * as React from 'react';\nimport { MediaDeviceMenu } from './MediaDeviceMenu';\nimport { DisconnectButton } from '../components/controls/DisconnectButton';\nimport { TrackToggle } from '../components/controls/TrackToggle';\nimport {\n useLocalParticipant,\n useLocalParticipantPermissions,\n usePersistentUserChoices,\n} from '../hooks';\nimport { mergeProps } from '../utils';\nimport { StartMediaButton } from '../components/controls/StartMediaButton';\nimport { BarVisualizer } from '../components';\nimport type { TrackReferenceOrPlaceholder } from '@livekit/components-core';\n\n/** @beta */\nexport type VoiceAssistantControlBarControls = {\n microphone?: boolean;\n leave?: boolean;\n};\n\n/** @beta */\nexport interface VoiceAssistantControlBarProps extends React.HTMLAttributes<HTMLDivElement> {\n onDeviceError?: (error: { source: Track.Source; error: Error }) => void;\n controls?: VoiceAssistantControlBarControls;\n /**\n * If `true`, the user's device choices will be persisted.\n * This will enables the user to have the same device choices when they rejoin the room.\n * @defaultValue true\n */\n saveUserChoices?: boolean;\n}\n\n/**\n * @example\n * ```tsx\n * <LiveKitRoom ... >\n * <VoiceAssistantControlBar />\n * </LiveKitRoom>\n * ```\n * @beta\n */\nexport function VoiceAssistantControlBar({\n controls,\n saveUserChoices = true,\n onDeviceError,\n ...props\n}: VoiceAssistantControlBarProps) {\n const visibleControls = { leave: true, microphone: true, ...controls };\n\n const localPermissions = useLocalParticipantPermissions();\n const { microphoneTrack, localParticipant } = useLocalParticipant();\n\n const micTrackRef: TrackReferenceOrPlaceholder = React.useMemo(() => {\n return {\n participant: localParticipant,\n source: Track.Source.Microphone,\n publication: microphoneTrack,\n };\n }, [localParticipant, microphoneTrack]);\n\n if (!localPermissions) {\n visibleControls.microphone = false;\n } else {\n visibleControls.microphone ??= localPermissions.canPublish;\n }\n\n const htmlProps = mergeProps({ className: 'lk-agent-control-bar' }, props);\n\n const { saveAudioInputEnabled, saveAudioInputDeviceId } = usePersistentUserChoices({\n preventSave: !saveUserChoices,\n });\n\n const microphoneOnChange = React.useCallback(\n (enabled: boolean, isUserInitiated: boolean) => {\n if (isUserInitiated) {\n saveAudioInputEnabled(enabled);\n }\n },\n [saveAudioInputEnabled],\n );\n\n return (\n <div {...htmlProps}>\n {visibleControls.microphone && (\n <div className=\"lk-button-group\">\n <TrackToggle\n source={Track.Source.Microphone}\n showIcon={true}\n onChange={microphoneOnChange}\n onDeviceError={(error) => onDeviceError?.({ source: Track.Source.Microphone, error })}\n >\n <BarVisualizer trackRef={micTrackRef} barCount={7} options={{ minHeight: 5 }} />\n </TrackToggle>\n <div className=\"lk-button-group-menu\">\n <MediaDeviceMenu\n kind=\"audioinput\"\n onActiveDeviceChange={(_kind, deviceId) => saveAudioInputDeviceId(deviceId ?? '')}\n />\n </div>\n </div>\n )}\n\n {visibleControls.leave && <DisconnectButton>{'Disconnect'}</DisconnectButton>}\n <StartMediaButton />\n </div>\n );\n}\n"],"names":["Chat","messageFormatter","messageDecoder","messageEncoder","channelTopic","props","inputRef","React","ulRef","chatOptions","send","chatMessages","isSending","useChat","layoutContext","useMaybeLayoutContext","lastReadMsgAt","handleSubmit","event","_a","_b","_c","unreadMessageCount","msg","widget","_d","_e","ChatToggle","ChatCloseIcon","idx","cloneSingleChild","allMsg","hideName","hideTimestamp","ChatEntry","ev","MediaDeviceMenu","kind","initialSelection","onActiveDeviceChange","tracks","requestPermissions","isOpen","setIsOpen","devices","setDevices","updateRequired","setUpdateRequired","needPermissions","setNeedPermissions","handleActiveDeviceChange","deviceId","log","button","tooltip","computeMenuPosition","x","y","handleClickOutside","wasClickOutside","MediaDeviceSelect","useWarnAboutMissingStyles","warnAboutMissingStyles","usePreviewTracks","options","onError","setTracks","trackLock","Mutex","needsCleanup","localTracks","unlock","createLocalTracks","tr","e","track","usePreviewDevice","enabled","deviceError","setDeviceError","isCreatingTrack","setIsCreatingTrack","useMediaDevices","selectedDevice","setSelectedDevice","localTrack","setLocalTrack","localDeviceId","setLocalDeviceId","createTrack","createLocalVideoTrack","VideoPresets","createLocalAudioTrack","newDeviceId","prevDeviceId","switchDevice","id","dev","PreJoin","defaults","onValidate","onSubmit","debug","joinLabel","micLabel","camLabel","userLabel","persistUserChoices","htmlProps","userChoices","setUserChoices","defaultUserChoices","partialDefaults","initialUserChoices","saveAudioInputDeviceId","saveAudioInputEnabled","saveVideoInputDeviceId","saveVideoInputEnabled","saveUsername","usePersistentUserChoices","audioEnabled","setAudioEnabled","videoEnabled","setVideoEnabled","audioDeviceId","setAudioDeviceId","videoDeviceId","setVideoDeviceId","username","setUsername","videoEl","videoTrack","Track","facingMode","facingModeFromLocalTrack","audioTrack","isValid","setIsValid","handleValidation","values","newUserChoices","ParticipantPlaceholder","TrackToggle","_","inputEl","useSettingsToggle","dispatch","state","useLayoutContext","className","mergeProps","SettingsMenuToggle","ref","mergedProps","ControlBar","variation","controls","saveUserChoices","onDeviceError","isChatOpen","setIsChatOpen","defaultVariation","useMediaQuery","visibleControls","localPermissions","useLocalParticipantPermissions","showIcon","showText","browserSupportsScreenSharing","supportsScreenSharing","isScreenShareEnabled","setIsScreenShareEnabled","onScreenShareChange","microphoneOnChange","isUserInitiated","cameraOnChange","error","_kind","ChatIcon","GearIcon","DisconnectButton","LeaveIcon","StartMediaButton","VideoConference","chatMessageFormatter","chatMessageDecoder","chatMessageEncoder","SettingsComponent","widgetState","setWidgetState","lastAutoFocusedScreenShareTrack","useTracks","RoomEvent","widgetUpdate","useCreateLayoutContext","screenShareTracks","isTrackReference","focusTrack","usePinnedTracks","carouselTracks","isEqualTrackRef","updatedFocusTrack","_f","isWeb","LayoutContextProvider","FocusLayoutContainer","CarouselLayout","ParticipantTile","FocusLayout","GridLayout","RoomAudioRenderer","ConnectionStateToast","AudioConference","audioTracks","TrackLoop","ParticipantAudioTile","VoiceAssistantControlBar","microphoneTrack","localParticipant","useLocalParticipant","micTrackRef","BarVisualizer"],"mappings":";;;;;;AA2BO,SAASA,GAAK;AAAA,EACnB,kBAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,cAAAC;AAAA,EACA,GAAGC;AACL,GAAc;AACN,QAAAC,IAAWC,EAAM,OAAyB,IAAI,GAC9CC,IAAQD,EAAM,OAAyB,IAAI,GAE3CE,IAA2BF,EAAM,QAAQ,OACtC,EAAE,gBAAAL,GAAgB,gBAAAC,GAAgB,cAAAC,MACxC,CAACF,GAAgBC,GAAgBC,CAAY,CAAC,GAE3C,EAAE,MAAAM,GAAM,cAAAC,GAAc,WAAAC,EAAU,IAAIC,GAAQJ,CAAW,GAEvDK,IAAgBC,KAChBC,IAAgBT,EAAM,OAAiC,CAAC;AAE9D,iBAAeU,EAAaC,GAAwB;AAClD,IAAAA,EAAM,eAAe,GACjBZ,EAAS,WAAWA,EAAS,QAAQ,MAAM,WAAW,MACpDI,MACI,MAAAA,EAAKJ,EAAS,QAAQ,KAAK,GACjCA,EAAS,QAAQ,QAAQ,IACzBA,EAAS,QAAQ;EAGvB;AAEA,SAAAC,EAAM,UAAU,MAAM;;AACpB,IAAIC,OACFW,IAAAX,EAAM,YAAN,QAAAW,EAAe,SAAS,EAAE,KAAKX,EAAM,QAAQ;EAC/C,GACC,CAACA,GAAOG