From fcc23749ebd9209f29cbe47ec93e643eeb057a26 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 17 Jun 2024 17:09:08 +0200 Subject: [PATCH] Add overload for kind + identity in `useRemoteParticipant` hook (#893) --- .changeset/weak-glasses-sparkle.md | 6 +++ docs/storybook/package.json | 2 +- examples/nextjs/package.json | 2 +- packages/core/etc/components-core.api.md | 16 ++++++- packages/core/package.json | 2 +- packages/core/src/observables/participant.ts | 40 ++++++++++++++++- packages/core/src/types.ts | 10 ++++- packages/react/etc/components-react.api.md | 14 ++++++ packages/react/package.json | 2 +- .../react/src/hooks/useRemoteParticipant.ts | 44 ++++++++++++++++--- packages/react/src/index.ts | 1 + pnpm-lock.yaml | 22 +++++----- 12 files changed, 135 insertions(+), 26 deletions(-) create mode 100644 .changeset/weak-glasses-sparkle.md diff --git a/.changeset/weak-glasses-sparkle.md b/.changeset/weak-glasses-sparkle.md new file mode 100644 index 000000000..156b44b2c --- /dev/null +++ b/.changeset/weak-glasses-sparkle.md @@ -0,0 +1,6 @@ +--- +'@livekit/components-core': patch +'@livekit/components-react': patch +--- + +Add overload for kind + identity in useRemoteParticipant hook diff --git a/docs/storybook/package.json b/docs/storybook/package.json index 822e15c81..ff0484e4b 100644 --- a/docs/storybook/package.json +++ b/docs/storybook/package.json @@ -13,7 +13,7 @@ "dependencies": { "@livekit/components-react": "workspace:*", "@livekit/components-styles": "workspace:*", - "livekit-client": "^2.1.5", + "livekit-client": "^2.2.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index b188db37d..42937b733 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -11,7 +11,7 @@ "dependencies": { "@livekit/components-react": "workspace:*", "@livekit/components-styles": "workspace:*", - "livekit-client": "^2.1.5", + "livekit-client": "^2.2.0", "livekit-server-sdk": "^1.2.7", "next": "^12.3.4", "react": "^18.2.0", diff --git a/packages/core/etc/components-core.api.md b/packages/core/etc/components-core.api.md index 3bcdc926b..1491cd6cc 100644 --- a/packages/core/etc/components-core.api.md +++ b/packages/core/etc/components-core.api.md @@ -19,6 +19,7 @@ import { Observable } from 'rxjs'; import type { Participant } from 'livekit-client'; import { ParticipantEvent } from 'livekit-client'; import type { ParticipantEventCallbacks } from 'livekit-client/dist/src/room/participant/Participant'; +import type { ParticipantKind } from 'livekit-client'; import type { ParticipantPermission } from '@livekit/protocol'; import type { PublicationEventCallbacks } from 'livekit-client/dist/src/room/track/TrackPublication'; import { RemoteParticipant } from 'livekit-client'; @@ -296,6 +297,11 @@ export function observeRoomEvents(room: Room, ...events: RoomEvent[]): Observabl // @public (undocumented) export function observeTrackEvents(track: TrackPublication, ...events: TrackEvent_2[]): Observable; +// Warning: (ae-incompatible-release-tags) The symbol "participantByIdentifierObserver" is marked as @public, but its signature references "ParticipantIdentifier" which is marked as @beta +// +// @public (undocumented) +export function participantByIdentifierObserver(room: Room, { kind, identity }: ParticipantIdentifier, options?: ConnectedParticipantObserverOptions): Observable; + // Warning: (ae-internal-missing-underscore) The name "ParticipantClickEvent" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) @@ -312,6 +318,14 @@ export function participantEventSelector(participant // @public (undocumented) export type ParticipantFilter = Parameters['0']; +// Warning: (ae-forgotten-export) The symbol "RequireAtLeastOne" needs to be exported by the entry point index.d.ts +// +// @beta (undocumented) +export type ParticipantIdentifier = RequireAtLeastOne<{ + kind: ParticipantKind; + identity: string; +}, 'identity' | 'kind'>; + // @public (undocumented) export function participantInfoObserver(participant: Participant): Observable<{ name: string | undefined; @@ -345,8 +359,6 @@ export function participantPermissionObserver(participant: Participant): Observa // @public (undocumented) export const participantTrackEvents: ParticipantEvent[]; -// Warning: (ae-forgotten-export) The symbol "RequireAtLeastOne" needs to be exported by the entry point index.d.ts -// // @public (undocumented) export type ParticipantTrackIdentifier = RequireAtLeastOne<{ sources: Track.Source[]; diff --git a/packages/core/package.json b/packages/core/package.json index 810915c29..a33ed9c87 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,7 +40,7 @@ "rxjs": "7.8.1" }, "peerDependencies": { - "livekit-client": "^2.1.5", + "livekit-client": "^2.2.0", "@livekit/protocol": "^1.16.0", "tslib": "^2.6.2" }, diff --git a/packages/core/src/observables/participant.ts b/packages/core/src/observables/participant.ts index 7b69e13a5..685b114af 100644 --- a/packages/core/src/observables/participant.ts +++ b/packages/core/src/observables/participant.ts @@ -7,7 +7,7 @@ import { Observable, map, startWith, switchMap } from 'rxjs'; import { getTrackByIdentifier } from '../components/mediaTrack'; import { allParticipantEvents, allParticipantRoomEvents } from '../helper/eventGroups'; import type { TrackReferenceOrPlaceholder } from '../track-reference'; -import type { TrackIdentifier } from '../types'; +import type { ParticipantIdentifier, TrackIdentifier } from '../types'; import { observeRoomEvents } from './room'; export function observeParticipantEvents( @@ -248,3 +248,41 @@ export function participantPermissionObserver( ); return observer; } + +export function participantByIdentifierObserver( + room: Room, + { kind, identity }: ParticipantIdentifier, + options: ConnectedParticipantObserverOptions = {}, +): Observable { + const additionalEvents = options.additionalEvents ?? allParticipantEvents; + const matchesIdentifier = (participant: RemoteParticipant) => { + let isMatch = true; + if (kind) { + isMatch = isMatch && participant.kind === kind; + } + if (identity) { + isMatch = isMatch && participant.identity === identity; + } + return isMatch; + }; + const observable = observeRoomEvents( + room, + RoomEvent.ParticipantConnected, + RoomEvent.ParticipantDisconnected, + RoomEvent.ConnectionStateChanged, + ).pipe( + switchMap((r) => { + const participant = Array.from(r.remoteParticipants.values()).find((p) => + matchesIdentifier(p), + ); + if (participant) { + return observeParticipantEvents(participant, ...additionalEvents); + } else { + return new Observable((subscribe) => subscribe.next(undefined)); + } + }), + startWith(Array.from(room.remoteParticipants.values()).find((p) => matchesIdentifier(p))), + ); + + return observable; +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a9d2d18a2..cdbf36aa4 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,4 +1,4 @@ -import type { Participant, Track, TrackPublication } from 'livekit-client'; +import type { Participant, ParticipantKind, Track, TrackPublication } from 'livekit-client'; import type { TrackReference, TrackReferenceOrPlaceholder } from './track-reference'; // ## PinState Type @@ -57,6 +57,14 @@ export type ParticipantTrackIdentifier = RequireAtLeastOne< 'sources' | 'name' | 'kind' >; +/** + * @beta + */ +export type ParticipantIdentifier = RequireAtLeastOne< + { kind: ParticipantKind; identity: string }, + 'identity' | 'kind' +>; + /** * The TrackIdentifier type is used to select Tracks either based on * - Track.Source and/or name of the track, e.g. `{source: Track.Source.Camera}` or `{name: "my-track"}` diff --git a/packages/react/etc/components-react.api.md b/packages/react/etc/components-react.api.md index 7cb653e26..bbc16b422 100644 --- a/packages/react/etc/components-react.api.md +++ b/packages/react/etc/components-react.api.md @@ -19,6 +19,7 @@ import { LocalVideoTrack } from 'livekit-client'; import type { MediaDeviceFailure } from 'livekit-client'; import { Participant } from 'livekit-client'; import type { ParticipantEvent } from 'livekit-client'; +import type { ParticipantKind } from 'livekit-client'; import type { ParticipantPermission } from '@livekit/protocol'; import * as React_2 from 'react'; import type { RemoteAudioTrack } from 'livekit-client'; @@ -449,6 +450,14 @@ export function ParticipantContextIfNeeded(props: React_2.PropsWithChildren<{ participant?: Participant; }>): React_2.JSX.Element; +// Warning: (ae-forgotten-export) The symbol "RequireAtLeastOne" needs to be exported by the entry point index.d.ts +// +// @beta (undocumented) +export type ParticipantIdentifier = RequireAtLeastOne<{ + kind: ParticipantKind; + identity: string; +}, 'identity' | 'kind'>; + // @public export function ParticipantLoop({ participants, ...props }: ParticipantLoopProps): React_2.JSX.Element; @@ -967,6 +976,11 @@ export function usePreviewDevice(en // @alpha (undocumented) export function usePreviewTracks(options: CreateLocalTracksOptions, onError?: (err: Error) => void): LocalTrack[] | undefined; +// Warning: (ae-incompatible-release-tags) The symbol "useRemoteParticipant" is marked as @public, but its signature references "ParticipantIdentifier" which is marked as @beta +// +// @public +export function useRemoteParticipant(identifier: ParticipantIdentifier, options?: UseRemoteParticipantOptions): RemoteParticipant | undefined; + // @public export function useRemoteParticipant(identity: string, options?: UseRemoteParticipantOptions): RemoteParticipant | undefined; diff --git a/packages/react/package.json b/packages/react/package.json index 17fbc97d3..62d24b8af 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -54,7 +54,7 @@ }, "peerDependencies": { "@livekit/protocol": "^1.16.0", - "livekit-client": "^2.1.5", + "livekit-client": "^2.2.0", "react": ">=18", "react-dom": ">=18", "tslib": "^2.6.2" diff --git a/packages/react/src/hooks/useRemoteParticipant.ts b/packages/react/src/hooks/useRemoteParticipant.ts index 315769d82..795919fa6 100644 --- a/packages/react/src/hooks/useRemoteParticipant.ts +++ b/packages/react/src/hooks/useRemoteParticipant.ts @@ -1,4 +1,8 @@ -import { connectedParticipantObserver } from '@livekit/components-core'; +import { + type ParticipantIdentifier, + connectedParticipantObserver, + participantByIdentifierObserver, +} from '@livekit/components-core'; import type { ParticipantEvent, RemoteParticipant } from 'livekit-client'; import * as React from 'react'; import { useRoomContext } from '../context'; @@ -13,7 +17,22 @@ export interface UseRemoteParticipantOptions { } /** - * The `useRemoteParticipant` hook returns the RemoteParticipant with the given `identity`. + * The `useRemoteParticipant` hook returns the first RemoteParticipant by either identity and/or based on the participant kind. + * @remarks + * To optimize performance, you can use the `updateOnlyOn` property to decide on what `ParticipantEvents` the hook updates. + * + * @example + * ```tsx + * const participant = useRemoteParticipant({kind: ParticipantKind.Agent, identity: 'myAgent'}); + * ``` + * @public + */ +export function useRemoteParticipant( + identifier: ParticipantIdentifier, + options?: UseRemoteParticipantOptions, +): RemoteParticipant | undefined; +/** + * The `useRemoteParticipant` hook returns the first RemoteParticipant by either identity or based on the participant kind. * @remarks * To optimize performance, you can use the `updateOnlyOn` property to decide on what `ParticipantEvents` the hook updates. * @@ -25,20 +44,31 @@ export interface UseRemoteParticipantOptions { */ export function useRemoteParticipant( identity: string, + options?: UseRemoteParticipantOptions, +): RemoteParticipant | undefined; +export function useRemoteParticipant( + identityOrIdentifier: string | ParticipantIdentifier, options: UseRemoteParticipantOptions = {}, ): RemoteParticipant | undefined { const room = useRoomContext(); const [updateOnlyOn] = React.useState(options.updateOnlyOn); - const observable = React.useMemo( - () => connectedParticipantObserver(room, identity, { additionalEvents: updateOnlyOn }), - [room, identity, updateOnlyOn], - ); + const observable = React.useMemo(() => { + if (typeof identityOrIdentifier === 'string') { + return connectedParticipantObserver(room, identityOrIdentifier, { + additionalEvents: updateOnlyOn, + }); + } else { + return participantByIdentifierObserver(room, identityOrIdentifier, { + additionalEvents: updateOnlyOn, + }); + } + }, [room, JSON.stringify(identityOrIdentifier), updateOnlyOn]); // Using `wrapperParticipant` to ensure a new object reference, // triggering a re-render when the participant events fire. const [participantWrapper, setParticipantWrapper] = React.useState({ - p: room.getParticipantByIdentity(identity) as RemoteParticipant | undefined, + p: undefined as RemoteParticipant | undefined, }); React.useEffect(() => { const listener = observable.subscribe((p) => setParticipantWrapper({ p })); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 5d5bc4c16..4f9f94ee3 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -21,6 +21,7 @@ export type { TrackReference, TrackReferenceOrPlaceholder, ParticipantClickEvent, + ParticipantIdentifier, PinState, WidgetState, } from '@livekit/components-core'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a5437c1e..edb55d0ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: specifier: workspace:* version: link:../../packages/styles livekit-client: - specifier: ^2.1.5 - version: 2.1.5 + specifier: ^2.2.0 + version: 2.2.0 react: specifier: ^18.2.0 version: 18.3.1 @@ -115,8 +115,8 @@ importers: specifier: workspace:* version: link:../../packages/styles livekit-client: - specifier: ^2.1.5 - version: 2.1.5 + specifier: ^2.2.0 + version: 2.2.0 livekit-server-sdk: specifier: ^1.2.7 version: 1.2.7 @@ -158,8 +158,8 @@ importers: specifier: ^1.16.0 version: 1.16.0 livekit-client: - specifier: ^2.1.5 - version: 2.1.5 + specifier: ^2.2.0 + version: 2.2.0 loglevel: specifier: 1.9.1 version: 1.9.1 @@ -210,8 +210,8 @@ importers: specifier: 2.1.1 version: 2.1.1 livekit-client: - specifier: ^2.1.5 - version: 2.1.5 + specifier: ^2.2.0 + version: 2.2.0 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -5531,8 +5531,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - livekit-client@2.1.5: - resolution: {integrity: sha512-8sc1ltfKRjy51Q/V/SaDpjptXBamm9LXhixKBYdXdQFZdew4hgqTGYlHIyee/IM9QSqAXk1W+uCtVfkmxPD1EA==} + livekit-client@2.2.0: + resolution: {integrity: sha512-Iuk17l269P7SxvE3Gs3ExwoW3+lrz3BfiXOU7gTf/GqansxPx7Y+rfkLHWSBmvmAXOt+Rld4EFEpk8ARSagfnQ==} livekit-server-sdk@1.2.7: resolution: {integrity: sha512-tOhRb0vz1wBzMpTkP4ixptlC9MFME24PvG8Z/R7vBbQ1VGd6EdNr56voBSr+RCalYxaQqx0E9Gg4l+57m/Nlmw==} @@ -14285,7 +14285,7 @@ snapshots: lines-and-columns@1.2.4: {} - livekit-client@2.1.5: + livekit-client@2.2.0: dependencies: '@livekit/protocol': 1.16.0 events: 3.3.0