From 47f1f901444e3a0d900c26fd4da0af2782974cbd Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 7 Dec 2022 13:50:16 +0100 Subject: [PATCH 1/9] add experimental method to simulate participants in a room without connection --- src/room/Room.ts | 86 +++++++++++++++++++++++++++++++++++++++++++++-- src/room/utils.ts | 43 +++++++++++++++++------- 2 files changed, 115 insertions(+), 14 deletions(-) diff --git a/src/room/Room.ts b/src/room/Room.ts index 67c9b1a963..1f4dd60017 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -17,6 +17,9 @@ import { Room as RoomModel, ServerInfo, SpeakerInfo, + TrackInfo, + TrackSource, + TrackType, UserPacket, } from '../proto/livekit_models'; import { @@ -42,7 +45,7 @@ import type { ConnectionQuality } from './participant/Participant'; import RemoteParticipant from './participant/RemoteParticipant'; import RTCEngine from './RTCEngine'; import LocalAudioTrack from './track/LocalAudioTrack'; -import type LocalTrackPublication from './track/LocalTrackPublication'; +import LocalTrackPublication from './track/LocalTrackPublication'; import LocalVideoTrack from './track/LocalVideoTrack'; import type RemoteTrack from './track/RemoteTrack'; import RemoteTrackPublication from './track/RemoteTrackPublication'; @@ -50,7 +53,16 @@ import { Track } from './track/Track'; import type { TrackPublication } from './track/TrackPublication'; import type { AdaptiveStreamSettings } from './track/types'; import { getNewAudioContext } from './track/utils'; -import { Future, isWeb, Mutex, supportsSetSinkId, unpackStreamId } from './utils'; +import { + Future, + getDummyVideoStreamTrack, + getEmptyAudioStreamTrack, + getEmptyVideoStreamTrack, + isWeb, + Mutex, + supportsSetSinkId, + unpackStreamId, +} from './utils'; export enum ConnectionState { Disconnected = 'disconnected', @@ -1242,6 +1254,76 @@ class Room extends (EventEmitter as new () => TypedEmitter) this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant); }; + /** + * @experimental + */ + simulateParticipants( + count: number, + options: { audio?: boolean; video?: boolean; aspectRatios: Array }, + ) { + this.emit(RoomEvent.SignalConnected); + this.emit(RoomEvent.Connected); + this.setAndEmitConnectionState(ConnectionState.Connected); + const pub = new LocalTrackPublication( + Track.Kind.Video, + TrackInfo.fromPartial({ + source: TrackSource.CAMERA, + sid: Math.floor(Math.random() * 10_000).toString(), + type: TrackType.AUDIO, + name: 'video-dummy', + }), + new LocalVideoTrack( + getDummyVideoStreamTrack(160 * options.aspectRatios[0] ?? 1, 160, true, true), + ), + ); + // @ts-ignore + this.localParticipant.addTrackPublication(pub); + this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, pub); + this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant); + for (let i = 0; i < count - 1; i += 1) { + let info: ParticipantInfo = ParticipantInfo.fromPartial({ + sid: Math.floor(Math.random() * 10_000).toString(), + identity: `simulated-${i}`, + state: ParticipantInfo_State.ACTIVE, + tracks: [], + metadata: '', + joinedAt: Date.now(), + region: 'cyberspace', + }); + const p = this.getOrCreateParticipant(info.identity, info); + if (options.video) { + console.log('aspect index for ', i, i % options.aspectRatios.length); + const dummyVideo = getDummyVideoStreamTrack( + 160 * options.aspectRatios[i % options.aspectRatios.length] ?? 1, + 160, + false, + true, + ); + const videoTrack = TrackInfo.fromPartial({ + source: TrackSource.CAMERA, + sid: Math.floor(Math.random() * 10_000).toString(), + type: TrackType.AUDIO, + name: 'video-dummy', + }); + p.addSubscribedMediaTrack(dummyVideo, videoTrack.sid, new MediaStream([dummyVideo])); + info.tracks = [...info.tracks, videoTrack]; + } + if (options.audio) { + const dummyTrack = getEmptyAudioStreamTrack(); + const audioTrack = TrackInfo.fromPartial({ + source: TrackSource.MICROPHONE, + sid: Math.floor(Math.random() * 10_000).toString(), + type: TrackType.AUDIO, + name: 'audio-dummy', + }); + p.addSubscribedMediaTrack(dummyTrack, audioTrack.sid, new MediaStream([dummyTrack])); + info.tracks = [...info.tracks, audioTrack]; + } + + p.updateInfo(info); + } + } + // /** @internal */ emit( event: E, diff --git a/src/room/utils.ts b/src/room/utils.ts index 03debee802..10d0903b04 100644 --- a/src/room/utils.ts +++ b/src/room/utils.ts @@ -181,22 +181,41 @@ let emptyVideoStreamTrack: MediaStreamTrack | undefined; export function getEmptyVideoStreamTrack() { if (!emptyVideoStreamTrack) { - const canvas = document.createElement('canvas'); - // the canvas size is set to 16, because electron apps seem to fail with smaller values - canvas.width = 16; - canvas.height = 16; - canvas.getContext('2d')?.fillRect(0, 0, canvas.width, canvas.height); - // @ts-ignore - const emptyStream = canvas.captureStream(); - [emptyVideoStreamTrack] = emptyStream.getTracks(); - if (!emptyVideoStreamTrack) { - throw Error('Could not get empty media stream video track'); - } - emptyVideoStreamTrack.enabled = false; + emptyVideoStreamTrack = getDummyVideoStreamTrack(); } return emptyVideoStreamTrack; } +export function getDummyVideoStreamTrack( + width: number = 16, + height: number = 16, + enabled: boolean = false, + paintContent: boolean = false, +) { + const canvas = document.createElement('canvas'); + // the canvas size is set to 16 by default, because electron apps seem to fail with smaller values + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + ctx?.fillRect(0, 0, canvas.width, canvas.height); + if (paintContent && ctx) { + ctx.beginPath(); + ctx.arc(width / 2, height / 2, 50, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = 'grey'; + ctx.fill(); + } + // @ts-ignore + const dummyStream = canvas.captureStream(); + const [dummyTrack] = dummyStream.getTracks(); + if (!dummyTrack) { + throw Error('Could not get empty media stream video track'); + } + dummyTrack.enabled = enabled; + + return dummyTrack; +} + let emptyAudioStreamTrack: MediaStreamTrack | undefined; export function getEmptyAudioStreamTrack() { From c525779ff9715e33a9f0b3c4cd1a0841a2a78570 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 7 Dec 2022 14:49:12 +0100 Subject: [PATCH 2/9] register events on local participant --- src/room/Room.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/room/Room.ts b/src/room/Room.ts index 1f4dd60017..0b7f198f10 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -1261,6 +1261,18 @@ class Room extends (EventEmitter as new () => TypedEmitter) count: number, options: { audio?: boolean; video?: boolean; aspectRatios: Array }, ) { + this.localParticipant + .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged) + .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted) + .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted) + .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished) + .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished) + .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged) + .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError) + .on( + ParticipantEvent.ParticipantPermissionsChanged, + this.onLocalParticipantPermissionsChanged, + ); this.emit(RoomEvent.SignalConnected); this.emit(RoomEvent.Connected); this.setAndEmitConnectionState(ConnectionState.Connected); @@ -1279,7 +1291,7 @@ class Room extends (EventEmitter as new () => TypedEmitter) // @ts-ignore this.localParticipant.addTrackPublication(pub); this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, pub); - this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant); + for (let i = 0; i < count - 1; i += 1) { let info: ParticipantInfo = ParticipantInfo.fromPartial({ sid: Math.floor(Math.random() * 10_000).toString(), From e5ef550024f64db9d7546240a81e587f2db408e2 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 7 Dec 2022 14:52:03 +0100 Subject: [PATCH 3/9] fix imports --- src/room/Room.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/room/Room.ts b/src/room/Room.ts index 0b7f198f10..fdc37f122a 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -57,7 +57,6 @@ import { Future, getDummyVideoStreamTrack, getEmptyAudioStreamTrack, - getEmptyVideoStreamTrack, isWeb, Mutex, supportsSetSinkId, From e4cb611138b35b3decdf7c9a7d5a58c6c2ed063a Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 7 Dec 2022 16:08:35 +0100 Subject: [PATCH 4/9] also publish local audio --- src/room/Room.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/room/Room.ts b/src/room/Room.ts index fdc37f122a..6869109243 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -1260,6 +1260,8 @@ class Room extends (EventEmitter as new () => TypedEmitter) count: number, options: { audio?: boolean; video?: boolean; aspectRatios: Array }, ) { + this.name = 'sim-room'; + this.localParticipant.identity = 'simulated-local'; this.localParticipant .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged) .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted) @@ -1289,7 +1291,19 @@ class Room extends (EventEmitter as new () => TypedEmitter) ); // @ts-ignore this.localParticipant.addTrackPublication(pub); - this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, pub); + const audioPub = new LocalTrackPublication( + Track.Kind.Audio, + TrackInfo.fromPartial({ + source: TrackSource.MICROPHONE, + sid: Math.floor(Math.random() * 10_000).toString(), + type: TrackType.AUDIO, + name: 'video-dummy', + }), + new LocalAudioTrack(getEmptyAudioStreamTrack()), + ); + // @ts-ignore + this.localParticipant.addTrackPublication(audioPub); + this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, audioPub); for (let i = 0; i < count - 1; i += 1) { let info: ParticipantInfo = ParticipantInfo.fromPartial({ From 16f58dae8add0fc1bc967bf070016348347db969 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 8 Dec 2022 09:21:35 +0100 Subject: [PATCH 5/9] set local name --- src/room/Room.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/room/Room.ts b/src/room/Room.ts index 6869109243..efd1d42d19 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -1260,8 +1260,10 @@ class Room extends (EventEmitter as new () => TypedEmitter) count: number, options: { audio?: boolean; video?: boolean; aspectRatios: Array }, ) { + this.handleDisconnect(); this.name = 'sim-room'; this.localParticipant.identity = 'simulated-local'; + this.localParticipant.name = 'simulated-local'; this.localParticipant .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged) .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted) From b0d6f4d07c89e7cd7b8e8bad6370c006afdd62dc Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 8 Dec 2022 13:23:35 +0100 Subject: [PATCH 6/9] cleanup --- src/room/Room.ts | 135 ++++++++++++++++++++++++---------------------- src/room/types.ts | 12 +++++ 2 files changed, 82 insertions(+), 65 deletions(-) create mode 100644 src/room/types.ts diff --git a/src/room/Room.ts b/src/room/Room.ts index efd1d42d19..e3eb8c751f 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -53,6 +53,7 @@ import { Track } from './track/Track'; import type { TrackPublication } from './track/TrackPublication'; import type { AdaptiveStreamSettings } from './track/types'; import { getNewAudioContext } from './track/utils'; +import type { SimulationOptions } from './types'; import { Future, getDummyVideoStreamTrack, @@ -318,18 +319,7 @@ class Room extends (EventEmitter as new () => TypedEmitter) this.localParticipant.updateInfo(pi); // forward metadata changed for the local participant - this.localParticipant - .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged) - .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted) - .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted) - .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished) - .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished) - .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged) - .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError) - .on( - ParticipantEvent.ParticipantPermissionsChanged, - this.onLocalParticipantPermissionsChanged, - ); + this.setupLocalParticipantEvents(); // populate remote participants, these should not trigger new events joinResponse.otherParticipants.forEach((info) => { @@ -646,6 +636,21 @@ class Room extends (EventEmitter as new () => TypedEmitter) } } + private setupLocalParticipantEvents() { + this.localParticipant + .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged) + .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted) + .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted) + .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished) + .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished) + .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged) + .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError) + .on( + ParticipantEvent.ParticipantPermissionsChanged, + this.onLocalParticipantPermissionsChanged, + ); + } + private recreateEngine() { this.engine?.close(); /* @ts-ignore */ @@ -1254,74 +1259,76 @@ class Room extends (EventEmitter as new () => TypedEmitter) }; /** + * Allows to populate a room with simulated participants. + * No actual connection to a server will be established, all state is local. * @experimental */ - simulateParticipants( - count: number, - options: { audio?: boolean; video?: boolean; aspectRatios: Array }, - ) { + simulateParticipants(options: SimulationOptions) { + const publishOptions = { + audio: true, + video: true, + ...options.publish, + }; + const participantOptions = { + count: 9, + audio: false, + video: true, + aspectRatios: [1.66, 1.7, 1.3], + ...options.participants, + }; this.handleDisconnect(); - this.name = 'sim-room'; + this.name = 'simulated-room'; this.localParticipant.identity = 'simulated-local'; this.localParticipant.name = 'simulated-local'; - this.localParticipant - .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged) - .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted) - .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted) - .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished) - .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished) - .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged) - .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError) - .on( - ParticipantEvent.ParticipantPermissionsChanged, - this.onLocalParticipantPermissionsChanged, - ); + this.setupLocalParticipantEvents(); this.emit(RoomEvent.SignalConnected); this.emit(RoomEvent.Connected); this.setAndEmitConnectionState(ConnectionState.Connected); - const pub = new LocalTrackPublication( - Track.Kind.Video, - TrackInfo.fromPartial({ - source: TrackSource.CAMERA, - sid: Math.floor(Math.random() * 10_000).toString(), - type: TrackType.AUDIO, - name: 'video-dummy', - }), - new LocalVideoTrack( - getDummyVideoStreamTrack(160 * options.aspectRatios[0] ?? 1, 160, true, true), - ), - ); - // @ts-ignore - this.localParticipant.addTrackPublication(pub); - const audioPub = new LocalTrackPublication( - Track.Kind.Audio, - TrackInfo.fromPartial({ - source: TrackSource.MICROPHONE, - sid: Math.floor(Math.random() * 10_000).toString(), - type: TrackType.AUDIO, - name: 'video-dummy', - }), - new LocalAudioTrack(getEmptyAudioStreamTrack()), - ); - // @ts-ignore - this.localParticipant.addTrackPublication(audioPub); - this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, audioPub); + if (publishOptions.video) { + const camPub = new LocalTrackPublication( + Track.Kind.Video, + TrackInfo.fromPartial({ + source: TrackSource.CAMERA, + sid: Math.floor(Math.random() * 10_000).toString(), + type: TrackType.AUDIO, + name: 'video-dummy', + }), + new LocalVideoTrack( + getDummyVideoStreamTrack(160 * participantOptions.aspectRatios[0] ?? 1, 160, true, true), + ), + ); + // @ts-ignore + this.localParticipant.addTrackPublication(camPub); + this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, camPub); + } + if (publishOptions.audio) { + const audioPub = new LocalTrackPublication( + Track.Kind.Audio, + TrackInfo.fromPartial({ + source: TrackSource.MICROPHONE, + sid: Math.floor(Math.random() * 10_000).toString(), + type: TrackType.AUDIO, + }), + new LocalAudioTrack(getEmptyAudioStreamTrack()), + ); + // @ts-ignore + this.localParticipant.addTrackPublication(audioPub); + this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, audioPub); + } - for (let i = 0; i < count - 1; i += 1) { + for (let i = 0; i < participantOptions.count - 1; i += 1) { let info: ParticipantInfo = ParticipantInfo.fromPartial({ sid: Math.floor(Math.random() * 10_000).toString(), identity: `simulated-${i}`, state: ParticipantInfo_State.ACTIVE, tracks: [], - metadata: '', joinedAt: Date.now(), - region: 'cyberspace', }); const p = this.getOrCreateParticipant(info.identity, info); - if (options.video) { - console.log('aspect index for ', i, i % options.aspectRatios.length); + if (participantOptions.video) { + console.log('aspect index for ', i, i % participantOptions.aspectRatios.length); const dummyVideo = getDummyVideoStreamTrack( - 160 * options.aspectRatios[i % options.aspectRatios.length] ?? 1, + 160 * participantOptions.aspectRatios[i % participantOptions.aspectRatios.length] ?? 1, 160, false, true, @@ -1330,18 +1337,16 @@ class Room extends (EventEmitter as new () => TypedEmitter) source: TrackSource.CAMERA, sid: Math.floor(Math.random() * 10_000).toString(), type: TrackType.AUDIO, - name: 'video-dummy', }); p.addSubscribedMediaTrack(dummyVideo, videoTrack.sid, new MediaStream([dummyVideo])); info.tracks = [...info.tracks, videoTrack]; } - if (options.audio) { + if (participantOptions.audio) { const dummyTrack = getEmptyAudioStreamTrack(); const audioTrack = TrackInfo.fromPartial({ source: TrackSource.MICROPHONE, sid: Math.floor(Math.random() * 10_000).toString(), type: TrackType.AUDIO, - name: 'audio-dummy', }); p.addSubscribedMediaTrack(dummyTrack, audioTrack.sid, new MediaStream([dummyTrack])); info.tracks = [...info.tracks, audioTrack]; diff --git a/src/room/types.ts b/src/room/types.ts new file mode 100644 index 0000000000..fc74cedb09 --- /dev/null +++ b/src/room/types.ts @@ -0,0 +1,12 @@ +export type SimulationOptions = { + publish?: { + audio?: boolean; + video?: boolean; + }; + participants?: { + count?: number; + aspectRatios?: Array; + audio?: boolean; + video?: boolean; + }; +}; From cf2f29bb57ebfdb53abb4b606a4e6963981f44e3 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 8 Dec 2022 13:28:03 +0100 Subject: [PATCH 7/9] remove log --- src/room/Room.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/room/Room.ts b/src/room/Room.ts index e3eb8c751f..d52cd04dab 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -1260,7 +1260,7 @@ class Room extends (EventEmitter as new () => TypedEmitter) /** * Allows to populate a room with simulated participants. - * No actual connection to a server will be established, all state is local. + * No actual connection to a server will be established, all state is * @experimental */ simulateParticipants(options: SimulationOptions) { @@ -1326,7 +1326,6 @@ class Room extends (EventEmitter as new () => TypedEmitter) }); const p = this.getOrCreateParticipant(info.identity, info); if (participantOptions.video) { - console.log('aspect index for ', i, i % participantOptions.aspectRatios.length); const dummyVideo = getDummyVideoStreamTrack( 160 * participantOptions.aspectRatios[i % participantOptions.aspectRatios.length] ?? 1, 160, From 33f708fb4bfcae39445228f495db3c8a64b5200a Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 9 Dec 2022 09:50:43 +0100 Subject: [PATCH 8/9] rename --- src/room/Room.ts | 11 ++++++++--- src/room/utils.ts | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/room/Room.ts b/src/room/Room.ts index d52cd04dab..c625e7d87a 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -56,7 +56,7 @@ import { getNewAudioContext } from './track/utils'; import type { SimulationOptions } from './types'; import { Future, - getDummyVideoStreamTrack, + createDummyVideoStreamTrack, getEmptyAudioStreamTrack, isWeb, Mutex, @@ -1294,7 +1294,12 @@ class Room extends (EventEmitter as new () => TypedEmitter) name: 'video-dummy', }), new LocalVideoTrack( - getDummyVideoStreamTrack(160 * participantOptions.aspectRatios[0] ?? 1, 160, true, true), + createDummyVideoStreamTrack( + 160 * participantOptions.aspectRatios[0] ?? 1, + 160, + true, + true, + ), ), ); // @ts-ignore @@ -1326,7 +1331,7 @@ class Room extends (EventEmitter as new () => TypedEmitter) }); const p = this.getOrCreateParticipant(info.identity, info); if (participantOptions.video) { - const dummyVideo = getDummyVideoStreamTrack( + const dummyVideo = createDummyVideoStreamTrack( 160 * participantOptions.aspectRatios[i % participantOptions.aspectRatios.length] ?? 1, 160, false, diff --git a/src/room/utils.ts b/src/room/utils.ts index 10d0903b04..fe65dcbdc2 100644 --- a/src/room/utils.ts +++ b/src/room/utils.ts @@ -181,12 +181,12 @@ let emptyVideoStreamTrack: MediaStreamTrack | undefined; export function getEmptyVideoStreamTrack() { if (!emptyVideoStreamTrack) { - emptyVideoStreamTrack = getDummyVideoStreamTrack(); + emptyVideoStreamTrack = createDummyVideoStreamTrack(); } return emptyVideoStreamTrack; } -export function getDummyVideoStreamTrack( +export function createDummyVideoStreamTrack( width: number = 16, height: number = 16, enabled: boolean = false, From 209af62509ca3198cb6e6d7042ab6cb0daa68ce8 Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 9 Dec 2022 09:51:42 +0100 Subject: [PATCH 9/9] changeset --- .changeset/eight-rice-lick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eight-rice-lick.md diff --git a/.changeset/eight-rice-lick.md b/.changeset/eight-rice-lick.md new file mode 100644 index 0000000000..26c3786128 --- /dev/null +++ b/.changeset/eight-rice-lick.md @@ -0,0 +1,5 @@ +--- +'livekit-client': patch +--- + +Add util function to simulate participants within a room