Skip to content

Commit

Permalink
Add support for participant attributes (#1184)
Browse files Browse the repository at this point in the history
This reverts commit c9141cd.
lukasIO authored Jul 9, 2024
1 parent abb38fc commit a9fd4d3
Showing 9 changed files with 104 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-buses-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"livekit-client": minor
---

Add support for participant attributes
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/api/SignalClient.ts
Original file line number Diff line number Diff line change
@@ -511,12 +511,13 @@ export class SignalClient {
});
}

sendUpdateLocalMetadata(metadata: string, name: string) {
sendUpdateLocalMetadata(metadata: string, name: string, attributes: Record<string, string> = {}) {
return this.sendRequest({
case: 'updateMetadata',
value: new UpdateParticipantMetadata({
metadata,
name,
attributes,
}),
});
}
4 changes: 4 additions & 0 deletions src/room/Room.ts
Original file line number Diff line number Diff line change
@@ -2131,6 +2131,10 @@ export type RoomEventCallbacks = {
prevPermissions: ParticipantPermission | undefined,
participant: RemoteParticipant | LocalParticipant,
) => void;
participantAttributesChanged: (
changedAttributes: Record<string, string>,
participant: RemoteParticipant | LocalParticipant,
) => void;
activeSpeakersChanged: (speakers: Array<Participant>) => void;
roomMetadataChanged: (metadata: string) => void;
dataReceived: (
14 changes: 14 additions & 0 deletions src/room/events.ts
Original file line number Diff line number Diff line change
@@ -185,6 +185,13 @@ export enum RoomEvent {
*/
ParticipantNameChanged = 'participantNameChanged',

/**
* Participant attributes is an app-specific key value state to be pushed to
* all users.
* When a participant's attributes changed, this event will be emitted with the changed attributes and the participant
*/
ParticipantAttributesChanged = 'participantAttributesChanged',

/**
* Room metadata is a simple way for app-specific state to be pushed to
* all users.
@@ -495,6 +502,13 @@ export enum ParticipantEvent {

/** @internal */
PCTrackAdded = 'pcTrackAdded',

/**
* Participant attributes is an app-specific key value state to be pushed to
* all users.
* When a participant's attributes changed, this event will be emitted with the changed attributes
*/
AttributesChanged = 'attributesChanged',
}

/** @internal */
8 changes: 8 additions & 0 deletions src/room/participant/LocalParticipant.ts
Original file line number Diff line number Diff line change
@@ -203,6 +203,14 @@ export default class LocalParticipant extends Participant {
this.engine.client.sendUpdateLocalMetadata(this.metadata ?? '', name);
}

async setAttributes(attributes: Record<string, string>) {
await this.engine.client.sendUpdateLocalMetadata(
this.metadata ?? '',
this.name ?? '',
attributes,
);
}

/**
* Enable or disable a participant's camera track.
*
23 changes: 23 additions & 0 deletions src/room/participant/Participant.ts
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import type RemoteTrack from '../track/RemoteTrack';
import type RemoteTrackPublication from '../track/RemoteTrackPublication';
import { Track } from '../track/Track';
import type { TrackPublication } from '../track/TrackPublication';
import { diffAttributes } from '../track/utils';
import type { LoggerOptions, TranscriptionSegment } from '../types';

export enum ConnectionQuality {
@@ -77,6 +78,8 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
/** client metadata, opaque to livekit */
metadata?: string;

private _attributes: Record<string, string>;

lastSpokeAt?: Date | undefined;

permissions?: ParticipantPermission;
@@ -112,6 +115,11 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
return this._kind;
}

/** participant attributes, similar to metadata, but as a key/value map */
get attributes(): Readonly<Record<string, string>> {
return Object.freeze({ ...this._attributes });
}

/** @internal */
constructor(
sid: string,
@@ -135,6 +143,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
this.videoTrackPublications = new Map();
this.trackPublications = new Map();
this._kind = kind;
this._attributes = {};
}

getTrackPublications(): TrackPublication[] {
@@ -214,6 +223,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
this.sid = info.sid;
this._setName(info.name);
this._setMetadata(info.metadata);
this._setAttributes(info.attributes);
if (info.permission) {
this.setPermissions(info.permission);
}
@@ -245,6 +255,18 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
}
}

/**
* Updates metadata from server
**/
private _setAttributes(attributes: Record<string, string>) {
const diff = diffAttributes(attributes, this.attributes);
this._attributes = attributes;

if (Object.keys(diff).length > 0) {
this.emit(ParticipantEvent.AttributesChanged, diff);
}
}

/** @internal */
setPermissions(permissions: ParticipantPermission): boolean {
const prevPermissions = this.permissions;
@@ -363,4 +385,5 @@ export type ParticipantEventCallbacks = {
publication: RemoteTrackPublication,
status: TrackPublication.SubscriptionStatus,
) => void;
attributesChanged: (changedAttributes: Record<string, string>) => void;
};
29 changes: 28 additions & 1 deletion src/room/track/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { AudioCaptureOptions, VideoCaptureOptions, VideoPresets } from './options';
import { constraintsForOptions, mergeDefaultOptions } from './utils';
import { constraintsForOptions, diffAttributes, mergeDefaultOptions } from './utils';

describe('mergeDefaultOptions', () => {
const audioDefaults: AudioCaptureOptions = {
@@ -109,3 +109,30 @@ describe('constraintsForOptions', () => {
expect(videoOpts.aspectRatio).toEqual(VideoPresets.h720.resolution.aspectRatio);
});
});

describe('diffAttributes', () => {
it('detects changed values', () => {
const oldValues: Record<string, string> = { a: 'value', b: 'initial', c: 'value' };
const newValues: Record<string, string> = { a: 'value', b: 'updated', c: 'value' };

const diff = diffAttributes(oldValues, newValues);
expect(Object.keys(diff).length).toBe(1);
expect(diff.b).toBe('updated');
});
it('detects new values', () => {
const newValues: Record<string, string> = { a: 'value', b: 'value', c: 'value' };
const oldValues: Record<string, string> = { a: 'value', b: 'value' };

const diff = diffAttributes(oldValues, newValues);
expect(Object.keys(diff).length).toBe(1);
expect(diff.c).toBe('value');
});
it('detects deleted values as empty strings', () => {
const newValues: Record<string, string> = { a: 'value', b: 'value' };
const oldValues: Record<string, string> = { a: 'value', b: 'value', c: 'value' };

const diff = diffAttributes(oldValues, newValues);
expect(Object.keys(diff).length).toBe(1);
expect(diff.c).toBe('');
});
});
16 changes: 16 additions & 0 deletions src/room/track/utils.ts
Original file line number Diff line number Diff line change
@@ -243,3 +243,19 @@ export function getLogContextFromTrack(track: Track | TrackPublication): Record<
export function supportsSynchronizationSources(): boolean {
return typeof RTCRtpReceiver !== 'undefined' && 'getSynchronizationSources' in RTCRtpReceiver;
}

export function diffAttributes(
oldValues: Record<string, string>,
newValues: Record<string, string>,
) {
const allKeys = [...Object.keys(newValues), ...Object.keys(oldValues)];
const diff: Record<string, string> = {};

for (const key of allKeys) {
if (oldValues[key] !== newValues[key]) {
diff[key] = newValues[key] ?? '';
}
}

return diff;
}

0 comments on commit a9fd4d3

Please sign in to comment.