| "use client" | |
| import * as React from "react" | |
| import { useMultibandTrackVolume } from "@/common" | |
| import AudioVisualizer from "@/components/Agent/AudioVisualizer" | |
| import AgoraRTC, { IMicrophoneAudioTrack } from "agora-rtc-sdk-ng" | |
| import { | |
| Select, | |
| SelectContent, | |
| SelectItem, | |
| SelectTrigger, | |
| SelectValue, | |
| } from "@/components/ui/select" | |
| import { Button } from "@/components/ui/button" | |
| import { MicIconByStatus } from "@/components/Icon" | |
| export default function MicrophoneBlock(props: { | |
| audioTrack?: IMicrophoneAudioTrack | |
| }) { | |
| const { audioTrack } = props | |
| const [audioMute, setAudioMute] = React.useState(false) | |
| const [mediaStreamTrack, setMediaStreamTrack] = | |
| React.useState<MediaStreamTrack>() | |
| React.useEffect(() => { | |
| audioTrack?.on("track-updated", onAudioTrackupdated) | |
| if (audioTrack) { | |
| setMediaStreamTrack(audioTrack.getMediaStreamTrack()) | |
| } | |
| return () => { | |
| audioTrack?.off("track-updated", onAudioTrackupdated) | |
| } | |
| }, [audioTrack]) | |
| React.useEffect(() => { | |
| audioTrack?.setMuted(audioMute) | |
| }, [audioTrack, audioMute]) | |
| const subscribedVolumes = useMultibandTrackVolume(mediaStreamTrack, 20) | |
| const onAudioTrackupdated = (track: MediaStreamTrack) => { | |
| console.log("[test] audio track updated", track) | |
| setMediaStreamTrack(track) | |
| } | |
| const onClickMute = () => { | |
| setAudioMute(!audioMute) | |
| } | |
| return ( | |
| <CommonDeviceWrapper | |
| title="MICROPHONE" | |
| Icon={MicIconByStatus} | |
| onIconClick={onClickMute} | |
| isActive={!audioMute} | |
| select={<MicrophoneSelect audioTrack={audioTrack} />} | |
| > | |
| <div className="mt-3 flex h-28 flex-col items-center justify-center gap-2.5 self-stretch rounded-md border border-[#272A2F] bg-[#1E2024] p-6 shadow-[0px_2px_2px_0px_rgba(0,0,0,0.25)]"> | |
| <AudioVisualizer | |
| type="user" | |
| barWidth={4} | |
| minBarHeight={2} | |
| maxBarHeight={50} | |
| frequencies={subscribedVolumes} | |
| borderRadius={2} | |
| gap={4} | |
| /> | |
| </div> | |
| </CommonDeviceWrapper> | |
| ) | |
| } | |
| export function CommonDeviceWrapper(props: { | |
| children: React.ReactNode | |
| title: string | |
| Icon: ( | |
| props: React.SVGProps<SVGSVGElement> & { active?: boolean }, | |
| ) => React.ReactNode | |
| onIconClick: () => void | |
| isActive: boolean | |
| select?: React.ReactNode | |
| }) { | |
| const { title, Icon, onIconClick, isActive, select, children } = props | |
| return ( | |
| <div className="flex flex-col"> | |
| <div className="flex items-center justify-between"> | |
| <div className="text-sm font-medium">{title}</div> | |
| <div className="flex items-center gap-2"> | |
| <Button | |
| variant="outline" | |
| size="icon" | |
| className="border-secondary bg-transparent" | |
| onClick={onIconClick} | |
| > | |
| <Icon className="h-5 w-5" active={isActive} /> | |
| </Button> | |
| {select} | |
| </div> | |
| </div> | |
| {children} | |
| </div> | |
| ) | |
| } | |
| export type TDeviceSelectItem = { | |
| label: string | |
| value: string | |
| deviceId: string | |
| } | |
| export const DEFAULT_DEVICE_ITEM: TDeviceSelectItem = { | |
| label: "Default", | |
| value: "default", | |
| deviceId: "", | |
| } | |
| export const DeviceSelect = (props: { | |
| items: TDeviceSelectItem[] | |
| value: string | |
| onChange: (value: string) => void | |
| placeholder?: string | |
| }) => { | |
| const { items, value, onChange, placeholder } = props | |
| return ( | |
| <Select value={value} onValueChange={onChange}> | |
| <SelectTrigger className="w-[180px]"> | |
| <SelectValue placeholder={placeholder} /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| {items.map((item) => ( | |
| <SelectItem key={item.value} value={item.value}> | |
| {item.label} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| ) | |
| } | |
| export const MicrophoneSelect = (props: { | |
| audioTrack?: IMicrophoneAudioTrack | |
| }) => { | |
| const { audioTrack } = props | |
| const [items, setItems] = React.useState<TDeviceSelectItem[]>([ | |
| DEFAULT_DEVICE_ITEM, | |
| ]) | |
| const [value, setValue] = React.useState("default") | |
| React.useEffect(() => { | |
| if (audioTrack) { | |
| const label = audioTrack?.getTrackLabel() | |
| setValue(label) | |
| AgoraRTC.getMicrophones().then((arr) => { | |
| setItems( | |
| arr.map((item) => ({ | |
| label: item.label, | |
| value: item.label, | |
| deviceId: item.deviceId, | |
| })), | |
| ) | |
| }) | |
| } | |
| }, [audioTrack]) | |
| const onChange = async (value: string) => { | |
| const target = items.find((item) => item.value === value) | |
| if (target) { | |
| setValue(target.value) | |
| if (audioTrack) { | |
| await audioTrack.setDevice(target.deviceId) | |
| } | |
| } | |
| } | |
| return <DeviceSelect items={items} value={value} onChange={onChange} /> | |
| } | |