From ecef222a309e53b62bb896aca1309c301ac354c7 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Tue, 16 Apr 2024 09:02:08 +0200 Subject: [PATCH] Support referencing components via refs (#827) --- .changeset/moody-steaks-drop.md | 5 + .github/workflows/size-limit.yaml | 2 +- packages/react/etc/components-react.api.md | 40 +++---- packages/react/src/components/ChatEntry.tsx | 78 ++++++------- .../react/src/components/ConnectionState.tsx | 13 ++- packages/react/src/components/LiveKitRoom.tsx | 9 +- packages/react/src/components/RoomName.tsx | 29 ++--- .../src/components/controls/ChatToggle.tsx | 14 ++- .../components/controls/ClearPinButton.tsx | 13 ++- .../components/controls/DisconnectButton.tsx | 13 ++- .../src/components/controls/FocusToggle.tsx | 50 +++++---- .../components/controls/MediaDeviceSelect.tsx | 34 +++--- .../controls/PaginationIndicator.tsx | 13 ++- .../controls/SettingsMenuToggle.tsx | 13 ++- .../src/components/controls/StartAudio.tsx | 13 ++- .../components/controls/StartMediaButton.tsx | 9 +- .../src/components/controls/TrackToggle.tsx | 14 ++- .../src/components/participant/AudioTrack.tsx | 84 +++++++------- .../participant/AudioVisualizer.tsx | 8 +- .../ConnectionQualityIndicator.tsx | 13 ++- .../participant/ParticipantAudioTile.tsx | 24 ++-- .../participant/ParticipantName.tsx | 9 +- .../participant/ParticipantTile.tsx | 24 ++-- .../participant/TrackMutedIndicator.tsx | 16 +-- .../src/components/participant/VideoTrack.tsx | 104 ++++++++++-------- .../src/hooks/useMediaTrackBySourceOrName.ts | 2 +- packages/react/src/utils.ts | 18 +++ 27 files changed, 398 insertions(+), 266 deletions(-) create mode 100644 .changeset/moody-steaks-drop.md diff --git a/.changeset/moody-steaks-drop.md b/.changeset/moody-steaks-drop.md new file mode 100644 index 000000000..180247cf0 --- /dev/null +++ b/.changeset/moody-steaks-drop.md @@ -0,0 +1,5 @@ +--- +"@livekit/components-react": minor +--- + +Support referencing components via refs diff --git a/.github/workflows/size-limit.yaml b/.github/workflows/size-limit.yaml index 0cc4ee7fe..0f06ff132 100644 --- a/.github/workflows/size-limit.yaml +++ b/.github/workflows/size-limit.yaml @@ -21,7 +21,7 @@ jobs: cache: 'pnpm' - name: Install dependencies run: pnpm install - - uses: andresz1/size-limit-action@35ea3c74377213d727b88532229d467a849345f4 # with pnpm support + - uses: andresz1/size-limit-action@v1.8.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} script: pnpm --filter @livekit/components-react exec size-limit --json diff --git a/packages/react/etc/components-react.api.md b/packages/react/etc/components-react.api.md index 76801f213..66d45225b 100644 --- a/packages/react/etc/components-react.api.md +++ b/packages/react/etc/components-react.api.md @@ -48,7 +48,7 @@ export interface AudioConferenceProps extends React_2.HTMLAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface AudioTrackProps extends React_2.AudioHTMLAttributes { @@ -61,7 +61,7 @@ export interface AudioTrackProps extends React_2.AudioHTMLAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface AudioVisualizerProps extends React_2.HTMLAttributes { @@ -100,7 +100,7 @@ export function Chat({ messageFormatter, messageDecoder, messageEncoder, channel export const ChatCloseIcon: (props: SVGProps) => React_2.JSX.Element; // @public -export function ChatEntry({ entry, hideName, hideTimestamp, messageFormatter, ...props }: ChatEntryProps): React_2.JSX.Element; +export const ChatEntry: (props: ChatEntryProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public export interface ChatEntryProps extends React_2.HTMLAttributes { @@ -134,7 +134,7 @@ export interface ChatProps extends React_2.HTMLAttributes, ChatO } // @public -export function ChatToggle(props: ChatToggleProps): React_2.JSX.Element; +export const ChatToggle: (props: ChatToggleProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface ChatToggleProps extends React_2.ButtonHTMLAttributes { @@ -146,14 +146,14 @@ export interface ChatToggleProps extends React_2.ButtonHTMLAttributes) => React_2.JSX.Element; // @public -export function ClearPinButton(props: ClearPinButtonProps): React_2.JSX.Element; +export const ClearPinButton: (props: ClearPinButtonProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface ClearPinButtonProps extends React_2.ButtonHTMLAttributes { } // @public -export function ConnectionQualityIndicator(props: ConnectionQualityIndicatorProps): React_2.JSX.Element; +export const ConnectionQualityIndicator: (props: ConnectionQualityIndicatorProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface ConnectionQualityIndicatorOptions { @@ -166,7 +166,7 @@ export interface ConnectionQualityIndicatorProps extends React_2.HTMLAttributes< } // @public -export function ConnectionState({ room, ...props }: ConnectionStatusProps): React_2.JSX.Element; +export const ConnectionState: (props: ConnectionStatusProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public export function ConnectionStateToast(props: ConnectionStateToastProps): React_2.JSX.Element; @@ -206,7 +206,7 @@ export interface ControlBarProps extends React_2.HTMLAttributes } // @public -export function DisconnectButton(props: DisconnectButtonProps): React_2.JSX.Element; +export const DisconnectButton: (props: DisconnectButtonProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface DisconnectButtonProps extends React_2.ButtonHTMLAttributes { @@ -242,7 +242,7 @@ export interface FocusLayoutProps extends React_2.HTMLAttributes { } // @public -export function FocusToggle({ trackRef, ...props }: FocusToggleProps): React_2.JSX.Element; +export const FocusToggle: (props: FocusToggleProps & React_2.RefAttributes) => React_2.ReactElement> | null; // Warning: (ae-internal-missing-underscore) The name "FocusToggleIcon" should be prefixed with an underscore because the declaration is marked as @internal // @@ -307,7 +307,9 @@ export type LayoutContextType = { export const LeaveIcon: (props: SVGProps) => React_2.JSX.Element; // @public -export function LiveKitRoom(props: React_2.PropsWithChildren): React_2.JSX.Element; +export const LiveKitRoom: (props: LiveKitRoomProps & { + children?: React_2.ReactNode; +} & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface LiveKitRoomProps extends Omit, 'onError'> { @@ -372,7 +374,7 @@ export interface MediaDeviceMenuProps extends React_2.ButtonHTMLAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface MediaDeviceSelectProps extends Omit, 'onError'> { @@ -425,7 +427,7 @@ export interface MultiBandTrackVolumeOptions { } // @public -export function ParticipantAudioTile({ children, disableSpeakingIndicator, onParticipantClick, trackRef, ...htmlProps }: ParticipantTileProps): React_2.JSX.Element; +export const ParticipantAudioTile: (props: ParticipantTileProps & React_2.RefAttributes) => React_2.ReactElement> | null; // Warning: (ae-internal-missing-underscore) The name "ParticipantClickEvent" should be prefixed with an underscore because the declaration is marked as @internal // @@ -455,7 +457,7 @@ export interface ParticipantLoopProps { } // @public -export function ParticipantName({ participant, ...props }: ParticipantNameProps): React_2.JSX.Element; +export const ParticipantName: (props: ParticipantNameProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface ParticipantNameProps extends React_2.HTMLAttributes, UseParticipantInfoOptions { @@ -467,7 +469,7 @@ export interface ParticipantNameProps extends React_2.HTMLAttributes) => React_2.JSX.Element; // @public -export function ParticipantTile({ trackRef, children, onParticipantClick, disableSpeakingIndicator, ...htmlProps }: ParticipantTileProps): React_2.JSX.Element; +export const ParticipantTile: (props: ParticipantTileProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface ParticipantTileProps extends React_2.HTMLAttributes { @@ -548,7 +550,7 @@ export interface RoomAudioRendererProps { export const RoomContext: React_2.Context; // @public -export function RoomName({ childrenPosition, children, ...htmlAttributes }: RoomNameProps): React_2.JSX.Element; +export const RoomName: (props: RoomNameProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface RoomNameProps extends React_2.HTMLAttributes { @@ -584,7 +586,7 @@ export function setLogLevel(level: LogLevel, options?: SetLogLevelOptions): void export const SpinnerIcon: (props: SVGProps) => React_2.JSX.Element; // @public -export function StartAudio({ label, ...props }: AllowAudioPlaybackProps): React_2.JSX.Element; +export const StartAudio: (props: AllowAudioPlaybackProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public export function Toast(props: React_2.HTMLAttributes): React_2.JSX.Element; @@ -599,7 +601,7 @@ export interface TrackLoopProps { } // @public -export function TrackMutedIndicator({ trackRef, show, ...props }: TrackMutedIndicatorProps): React_2.JSX.Element | null; +export const TrackMutedIndicator: (props: TrackMutedIndicatorProps & React_2.RefAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface TrackMutedIndicatorProps extends React_2.HTMLAttributes { @@ -627,7 +629,7 @@ export type TrackReferenceOrPlaceholder = TrackReference | TrackReferencePlaceho // Warning: (ae-forgotten-export) The symbol "ToggleSource" needs to be exported by the entry point index.d.ts // // @public -export function TrackToggle({ showIcon, ...props }: TrackToggleProps): React_2.JSX.Element; +export const TrackToggle: (props: TrackToggleProps & React_2.RefAttributes) => React_2.ReactElement | null; // @public (undocumented) export interface TrackToggleProps extends Omit, 'onChange'> { @@ -1124,7 +1126,7 @@ export interface VideoConferenceProps extends React_2.HTMLAttributes) => React_2.ReactElement> | null; // @public (undocumented) export interface VideoTrackProps extends React_2.VideoHTMLAttributes { diff --git a/packages/react/src/components/ChatEntry.tsx b/packages/react/src/components/ChatEntry.tsx index aa159a5bc..5857539e6 100644 --- a/packages/react/src/components/ChatEntry.tsx +++ b/packages/react/src/components/ChatEntry.tsx @@ -33,48 +33,48 @@ export interface ChatEntryProps extends React.HTMLAttributes { * @see `Chat` * @public */ -export function ChatEntry({ - entry, - hideName = false, - hideTimestamp = false, - messageFormatter, - ...props -}: ChatEntryProps) { - const formattedMessage = React.useMemo(() => { - return messageFormatter ? messageFormatter(entry.message) : entry.message; - }, [entry.message, messageFormatter]); - const hasBeenEdited = !!entry.editTimestamp; - const time = new Date(entry.timestamp); - const locale = navigator ? navigator.language : 'en-US'; +export const ChatEntry = /* @__PURE__ */ React.forwardRef( + function ChatEntry( + { entry, hideName = false, hideTimestamp = false, messageFormatter, ...props }: ChatEntryProps, + ref, + ) { + const formattedMessage = React.useMemo(() => { + return messageFormatter ? messageFormatter(entry.message) : entry.message; + }, [entry.message, messageFormatter]); + const hasBeenEdited = !!entry.editTimestamp; + const time = new Date(entry.timestamp); + const locale = navigator ? navigator.language : 'en-US'; - return ( -
  • - {(!hideTimestamp || !hideName || hasBeenEdited) && ( - - {!hideName && ( - - {entry.from?.name ?? entry.from?.identity} - - )} + return ( +
  • + {(!hideTimestamp || !hideName || hasBeenEdited) && ( + + {!hideName && ( + + {entry.from?.name ?? entry.from?.identity} + + )} - {(!hideTimestamp || hasBeenEdited) && ( - - {hasBeenEdited && 'edited '} - {time.toLocaleTimeString(locale, { timeStyle: 'short' })} - - )} - - )} + {(!hideTimestamp || hasBeenEdited) && ( + + {hasBeenEdited && 'edited '} + {time.toLocaleTimeString(locale, { timeStyle: 'short' })} + + )} + + )} - {formattedMessage} -
  • - ); -} + {formattedMessage} + + ); + }, +); /** @public */ export function formatChatMessageLinks(message: string): React.ReactNode { diff --git a/packages/react/src/components/ConnectionState.tsx b/packages/react/src/components/ConnectionState.tsx index e6791b1c7..22b5f6c67 100644 --- a/packages/react/src/components/ConnectionState.tsx +++ b/packages/react/src/components/ConnectionState.tsx @@ -22,7 +22,14 @@ export interface ConnectionStatusProps extends React.HTMLAttributes(function ConnectionState({ room, ...props }: ConnectionStatusProps, ref) { const connectionState = useConnectionState(room); - return
    {connectionState}
    ; -} + return ( +
    + {connectionState} +
    + ); +}); diff --git a/packages/react/src/components/LiveKitRoom.tsx b/packages/react/src/components/LiveKitRoom.tsx index 2410cacc3..f997e7ea7 100644 --- a/packages/react/src/components/LiveKitRoom.tsx +++ b/packages/react/src/components/LiveKitRoom.tsx @@ -100,10 +100,13 @@ export interface LiveKitRoomProps extends Omit) { +export const LiveKitRoom = /* @__PURE__ */ React.forwardRef< + HTMLDivElement, + React.PropsWithChildren +>(function LiveKitRoom(props: React.PropsWithChildren, ref) { const { room, htmlProps } = useLiveKitRoom(props); return ( -
    +
    {room && ( @@ -113,4 +116,4 @@ export function LiveKitRoom(props: React.PropsWithChildren) { )}
    ); -} +}); diff --git a/packages/react/src/components/RoomName.tsx b/packages/react/src/components/RoomName.tsx index 89edcb474..fdae07663 100644 --- a/packages/react/src/components/RoomName.tsx +++ b/packages/react/src/components/RoomName.tsx @@ -17,18 +17,19 @@ export interface RoomNameProps extends React.HTMLAttributes { * ``` * @public */ -export function RoomName({ - childrenPosition = 'before', - children, - ...htmlAttributes -}: RoomNameProps) { - const { name } = useRoomInfo(); +export const RoomName = /* @__PURE__ */ React.forwardRef( + function RoomName( + { childrenPosition = 'before', children, ...htmlAttributes }: RoomNameProps, + ref, + ) { + const { name } = useRoomInfo(); - return ( - - {childrenPosition === 'before' && children} - {name} - {childrenPosition === 'after' && children} - - ); -} + return ( + + {childrenPosition === 'before' && children} + {name} + {childrenPosition === 'after' && children} + + ); + }, +); diff --git a/packages/react/src/components/controls/ChatToggle.tsx b/packages/react/src/components/controls/ChatToggle.tsx index 4f3532aee..7aa5d875a 100644 --- a/packages/react/src/components/controls/ChatToggle.tsx +++ b/packages/react/src/components/controls/ChatToggle.tsx @@ -17,8 +17,14 @@ export interface ChatToggleProps extends React.ButtonHTMLAttributes( + function ChatToggle(props: ChatToggleProps, ref) { + const { mergedProps } = useChatToggle({ props }); - return ; -} + return ( + + ); + }, +); diff --git a/packages/react/src/components/controls/ClearPinButton.tsx b/packages/react/src/components/controls/ClearPinButton.tsx index 431f05b1f..a52f0d53f 100644 --- a/packages/react/src/components/controls/ClearPinButton.tsx +++ b/packages/react/src/components/controls/ClearPinButton.tsx @@ -18,7 +18,14 @@ export interface ClearPinButtonProps extends React.ButtonHTMLAttributes(function ClearPinButton(props: ClearPinButtonProps, ref) { const { buttonProps } = useClearPinButton(props); - return ; -} + return ( + + ); +}); diff --git a/packages/react/src/components/controls/DisconnectButton.tsx b/packages/react/src/components/controls/DisconnectButton.tsx index 0b24a2e64..a7aae05d7 100644 --- a/packages/react/src/components/controls/DisconnectButton.tsx +++ b/packages/react/src/components/controls/DisconnectButton.tsx @@ -18,7 +18,14 @@ export interface DisconnectButtonProps extends React.ButtonHTMLAttributes(function DisconnectButton(props: DisconnectButtonProps, ref) { const { buttonProps } = useDisconnectButton(props); - return ; -} + return ( + + ); +}); diff --git a/packages/react/src/components/controls/FocusToggle.tsx b/packages/react/src/components/controls/FocusToggle.tsx index 5ccd00a70..fc0a8fd16 100644 --- a/packages/react/src/components/controls/FocusToggle.tsx +++ b/packages/react/src/components/controls/FocusToggle.tsx @@ -22,29 +22,31 @@ export interface FocusToggleProps extends React.ButtonHTMLAttributes( + function FocusToggle({ trackRef, ...props }: FocusToggleProps, ref) { + const trackRefFromContext = useMaybeTrackRefContext(); - const { mergedProps, inFocus } = useFocusToggle({ - trackRef: trackRef ?? trackRefFromContext, - props, - }); + const { mergedProps, inFocus } = useFocusToggle({ + trackRef: trackRef ?? trackRefFromContext, + props, + }); - return ( - - {(layoutContext) => - layoutContext !== undefined && ( - - ) - } - - ); -} + return ( + + {(layoutContext) => + layoutContext !== undefined && ( + + ) + } + + ); + }, +); diff --git a/packages/react/src/components/controls/MediaDeviceSelect.tsx b/packages/react/src/components/controls/MediaDeviceSelect.tsx index d8d01fc4a..de2fc02de 100644 --- a/packages/react/src/components/controls/MediaDeviceSelect.tsx +++ b/packages/react/src/components/controls/MediaDeviceSelect.tsx @@ -42,18 +42,24 @@ export interface MediaDeviceSelectProps * ``` * @public */ -export function MediaDeviceSelect({ - kind, - initialSelection, - onActiveDeviceChange, - onDeviceListChange, - onDeviceSelectError, - exactMatch, - track, - requestPermissions, - onError, - ...props -}: MediaDeviceSelectProps) { +export const MediaDeviceSelect = /* @__PURE__ */ React.forwardRef< + HTMLUListElement, + MediaDeviceSelectProps +>(function MediaDeviceSelect( + { + kind, + initialSelection, + onActiveDeviceChange, + onDeviceListChange, + onDeviceSelectError, + exactMatch, + track, + requestPermissions, + onError, + ...props + }: MediaDeviceSelectProps, + ref, +) { const room = useMaybeRoomContext(); const handleError = React.useCallback( (e: Error) => { @@ -112,7 +118,7 @@ export function MediaDeviceSelect({ } return ( -
      +
        {devices.map((device, index) => (
      • ); -} +}); diff --git a/packages/react/src/components/controls/PaginationIndicator.tsx b/packages/react/src/components/controls/PaginationIndicator.tsx index a8cf2de3a..b07633b33 100644 --- a/packages/react/src/components/controls/PaginationIndicator.tsx +++ b/packages/react/src/components/controls/PaginationIndicator.tsx @@ -5,7 +5,10 @@ export interface PaginationIndicatorProps { currentPage: number; } -export function PaginationIndicator({ totalPageCount, currentPage }: PaginationIndicatorProps) { +export const PaginationIndicator = /* @__PURE__ */ React.forwardRef< + HTMLDivElement, + PaginationIndicatorProps +>(function PaginationIndicator({ totalPageCount, currentPage }: PaginationIndicatorProps, ref) { const bubbles = new Array(totalPageCount).fill('').map((_, index) => { if (index + 1 === currentPage) { return ; @@ -14,5 +17,9 @@ export function PaginationIndicator({ totalPageCount, currentPage }: PaginationI } }); - return
        {bubbles}
        ; -} + return ( +
        + {bubbles} +
        + ); +}); diff --git a/packages/react/src/components/controls/SettingsMenuToggle.tsx b/packages/react/src/components/controls/SettingsMenuToggle.tsx index 9f50987bc..94fa3f8ab 100644 --- a/packages/react/src/components/controls/SettingsMenuToggle.tsx +++ b/packages/react/src/components/controls/SettingsMenuToggle.tsx @@ -11,8 +11,15 @@ export interface SettingsMenuToggleProps extends React.ButtonHTMLAttributes(function SettingsMenuToggle(props: SettingsMenuToggleProps, ref) { const { mergedProps } = useSettingsToggle({ props }); - return ; -} + return ( + + ); +}); diff --git a/packages/react/src/components/controls/StartAudio.tsx b/packages/react/src/components/controls/StartAudio.tsx index 28f86b282..518adb530 100644 --- a/packages/react/src/components/controls/StartAudio.tsx +++ b/packages/react/src/components/controls/StartAudio.tsx @@ -22,9 +22,16 @@ export interface AllowAudioPlaybackProps extends React.ButtonHTMLAttributes(function StartAudio({ label = 'Allow Audio', ...props }: AllowAudioPlaybackProps, ref) { const room = useRoomContext(); const { mergedProps } = useStartAudio({ room, props }); - return ; -} + return ( + + ); +}); diff --git a/packages/react/src/components/controls/StartMediaButton.tsx b/packages/react/src/components/controls/StartMediaButton.tsx index f2d826cc2..7ab6cd0f2 100644 --- a/packages/react/src/components/controls/StartMediaButton.tsx +++ b/packages/react/src/components/controls/StartMediaButton.tsx @@ -22,7 +22,10 @@ export interface AllowMediaPlaybackProps extends React.ButtonHTMLAttributes(function StartMediaButton({ label, ...props }: AllowMediaPlaybackProps, ref) { const room = useRoomContext(); const { mergedProps: audioProps, canPlayAudio } = useStartAudio({ room, props }); const { mergedProps, canPlayVideo } = useStartVideo({ room, props: audioProps }); @@ -30,8 +33,8 @@ export function StartMediaButton({ label, ...props }: AllowMediaPlaybackProps) { style.display = canPlayAudio && canPlayVideo ? 'none' : 'block'; return ( - ); -} +}); diff --git a/packages/react/src/components/controls/TrackToggle.tsx b/packages/react/src/components/controls/TrackToggle.tsx index ed13ec8ac..4c4f67108 100644 --- a/packages/react/src/components/controls/TrackToggle.tsx +++ b/packages/react/src/components/controls/TrackToggle.tsx @@ -3,6 +3,12 @@ import * as React from 'react'; import { getSourceIcon } from '../../assets/icons/util'; import { useTrackToggle } from '../../hooks'; +declare module 'react' { + function forwardRef( + render: (props: P, ref: React.Ref) => React.ReactElement | null, + ): (props: P & React.RefAttributes) => React.ReactElement | null; +} + /** @public */ export interface TrackToggleProps extends Omit, 'onChange'> { @@ -30,12 +36,14 @@ export interface TrackToggleProps * ``` * @public */ -export function TrackToggle({ showIcon, ...props }: TrackToggleProps) { +export const TrackToggle = /* @__PURE__ */ React.forwardRef(function TrackToggle< + T extends ToggleSource, +>({ showIcon, ...props }: TrackToggleProps, ref: React.ForwardedRef) { const { buttonProps, enabled } = useTrackToggle(props); return ( - ); -} +}); diff --git a/packages/react/src/components/participant/AudioTrack.tsx b/packages/react/src/components/participant/AudioTrack.tsx index 84275ce4e..ff2740086 100644 --- a/packages/react/src/components/participant/AudioTrack.tsx +++ b/packages/react/src/components/participant/AudioTrack.tsx @@ -36,51 +36,51 @@ export interface AudioTrackProps extends React.AudioHTMLAttributes( + function AudioTrack( + { trackRef, onSubscriptionStatusChanged, volume, ...props }: AudioTrackProps, + ref, + ) { + const trackReference = useEnsureTrackRef(trackRef); - const mediaEl = React.useRef(null); + const mediaEl = React.useRef(null); - const { - elementProps, - isSubscribed, - track, - publication: pub, - } = useMediaTrackBySourceOrName(trackReference, { - element: mediaEl, - props, - }); + const { + elementProps, + isSubscribed, + track, + publication: pub, + } = useMediaTrackBySourceOrName(trackReference, { + element: mediaEl, + props, + }); - React.useEffect(() => { - onSubscriptionStatusChanged?.(!!isSubscribed); - }, [isSubscribed, onSubscriptionStatusChanged]); + React.useEffect(() => { + onSubscriptionStatusChanged?.(!!isSubscribed); + }, [isSubscribed, onSubscriptionStatusChanged]); - React.useEffect(() => { - if (track === undefined || volume === undefined) { - return; - } - if (track instanceof RemoteAudioTrack) { - track.setVolume(volume); - } else { - log.warn('Volume can only be set on remote audio tracks.'); - } - }, [volume, track]); + React.useEffect(() => { + if (track === undefined || volume === undefined) { + return; + } + if (track instanceof RemoteAudioTrack) { + track.setVolume(volume); + } else { + log.warn('Volume can only be set on remote audio tracks.'); + } + }, [volume, track]); - React.useEffect(() => { - if (pub === undefined || props.muted === undefined) { - return; - } - if (pub instanceof RemoteTrackPublication) { - pub.setEnabled(!props.muted); - } else { - log.warn('Can only call setEnabled on remote track publications.'); - } - }, [props.muted, pub, track]); + React.useEffect(() => { + if (pub === undefined || props.muted === undefined) { + return; + } + if (pub instanceof RemoteTrackPublication) { + pub.setEnabled(!props.muted); + } else { + log.warn('Can only call setEnabled on remote track publications.'); + } + }, [props.muted, pub, track]); - return