Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace internal instanceof checks with typeguards #1378

Merged
merged 5 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fair-ladybugs-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"livekit-client": patch
---

Replace internal instanceof checks with typeguards
18 changes: 9 additions & 9 deletions examples/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
supportsAV1,
supportsVP9,
} from '../../src/index';
import { isSVCCodec } from '../../src/room/utils';
import { isLocalParticipant, isRemoteTrack, isSVCCodec } from '../../src/room/utils';

setLogLevel(LogLevel.debug);

Expand Down Expand Up @@ -165,7 +165,7 @@ const appActions = {
.on(RoomEvent.LocalTrackPublished, (pub) => {
const track = pub.track as LocalAudioTrack;

if (track instanceof LocalAudioTrack) {
if (isLocalAudioTrack(track)) {
const { calculateVolume } = createAudioAnalyser(track);

setInterval(() => {
Expand Down Expand Up @@ -495,7 +495,7 @@ window.appActions = appActions;

function handleChatMessage(msg: ChatMessage, participant?: LocalParticipant | RemoteParticipant) {
(<HTMLTextAreaElement>$('chat')).value +=
`${participant?.identity}${participant instanceof LocalParticipant ? ' (me)' : ''}: ${msg.message}\n`;
`${participant?.identity}${isLocalParticipant(participant) ? ' (me)' : ''}: ${msg.message}\n`;
}

function participantConnected(participant: Participant) {
Expand Down Expand Up @@ -596,7 +596,7 @@ function renderParticipant(participant: Participant, remove: boolean = false) {
</div>
</div>
${
participant instanceof RemoteParticipant
!isLocalParticipant(participant)
? `<div class="volume-control">
<input id="volume-${identity}" type="range" min="0" max="1" step="0.1" value="1" orient="vertical" />
</div>`
Expand Down Expand Up @@ -629,7 +629,7 @@ function renderParticipant(participant: Participant, remove: boolean = false) {

// update properties
container.querySelector(`#name-${identity}`)!.innerHTML = participant.identity;
if (participant instanceof LocalParticipant) {
if (isLocalParticipant(participant)) {
container.querySelector(`#name-${identity}`)!.innerHTML += ' (you)';
}
const micElm = container.querySelector(`#mic-${identity}`)!;
Expand All @@ -642,7 +642,7 @@ function renderParticipant(participant: Participant, remove: boolean = false) {
div!.classList.remove('speaking');
}

if (participant instanceof RemoteParticipant) {
if (!isLocalParticipant(participant)) {
const volumeSlider = <HTMLInputElement>container.querySelector(`#volume-${identity}`);
volumeSlider.addEventListener('input', (ev) => {
participant.setVolume(Number.parseFloat((ev.target as HTMLInputElement).value));
Expand All @@ -651,7 +651,7 @@ function renderParticipant(participant: Participant, remove: boolean = false) {

const cameraEnabled = cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
if (cameraEnabled) {
if (participant instanceof LocalParticipant) {
if (isLocalParticipant(participant)) {
// flip
videoElm.style.transform = 'scale(-1, 1)';
} else if (!cameraPub?.videoTrack?.attachedElements.includes(videoElm)) {
Expand Down Expand Up @@ -684,7 +684,7 @@ function renderParticipant(participant: Participant, remove: boolean = false) {

const micEnabled = micPub && micPub.isSubscribed && !micPub.isMuted;
if (micEnabled) {
if (!(participant instanceof LocalParticipant)) {
if (!isLocalParticipant(participant)) {
// don't attach local audio
audioELm.onloadeddata = () => {
if (participant.joinedAt && participant.joinedAt.getTime() < startTime) {
Expand Down Expand Up @@ -787,7 +787,7 @@ function renderBitrate() {
}

if (t.source === Track.Source.Camera) {
if (t.videoTrack instanceof RemoteVideoTrack) {
if (isRemoteTrack(t.videoTrack)) {
const codecElm = container.querySelector(`#codec-${p.identity}`)!;
codecElm.innerHTML = t.videoTrack.getDecoderImplementation() ?? '';
}
Expand Down
4 changes: 2 additions & 2 deletions src/e2ee/E2eeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import type Room from '../room/Room';
import { ConnectionState } from '../room/Room';
import { DeviceUnsupportedError } from '../room/errors';
import { EngineEvent, ParticipantEvent, RoomEvent } from '../room/events';
import LocalTrack from '../room/track/LocalTrack';
import type RemoteTrack from '../room/track/RemoteTrack';
import type { Track } from '../room/track/Track';
import type { VideoCodec } from '../room/track/options';
import { mimeTypeToVideoCodecString } from '../room/track/utils';
import { isLocalTrack } from '../room/utils';
import type { BaseKeyProvider } from './KeyProvider';
import { E2EE_FLAG } from './constants';
import { type E2EEManagerCallbacks, EncryptionEvent, KeyProviderEvent } from './events';
Expand Down Expand Up @@ -317,7 +317,7 @@ export class E2EEManager
}

private setupE2EESender(track: Track, sender: RTCRtpSender) {
if (!(track instanceof LocalTrack) || !sender) {
if (!isLocalTrack(track) || !sender) {
if (!sender) log.warn('early return because sender is not ready');
return;
}
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ import {
createAudioAnalyser,
getEmptyAudioStreamTrack,
getEmptyVideoStreamTrack,
isAudioTrack,
isBrowserSupported,
isLocalTrack,
isRemoteTrack,
isVideoTrack,
supportsAV1,
supportsAdaptiveStream,
supportsDynacast,
Expand Down Expand Up @@ -100,6 +104,10 @@ export {
supportsDynacast,
supportsVP9,
Mutex,
isAudioTrack,
isLocalTrack,
isRemoteTrack,
isVideoTrack,
};
export type {
AudioAnalyserOptions,
Expand Down
11 changes: 9 additions & 2 deletions src/room/RTCEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ import type { Track } from './track/Track';
import type { TrackPublishOptions, VideoCodec } from './track/options';
import { getTrackPublicationInfo } from './track/utils';
import type { LoggerOptions } from './types';
import { isVideoCodec, isWeb, sleep, supportsAddTrack, supportsTransceiver } from './utils';
import {
isVideoCodec,
isVideoTrack,
isWeb,
sleep,
supportsAddTrack,
supportsTransceiver,
} from './utils';

const lossyDataChannel = '_lossy';
const reliableDataChannel = '_reliable';
Expand Down Expand Up @@ -745,7 +752,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
streams.push(track.mediaStream);
}

if (track instanceof LocalVideoTrack) {
if (isVideoTrack(track)) {
track.codec = opts.videoCodec;
}

Expand Down
9 changes: 6 additions & 3 deletions src/room/Room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ import {
getEmptyAudioStreamTrack,
isBrowserSupported,
isCloud,
isLocalAudioTrack,
isLocalParticipant,
isReactNative,
isRemotePub,
isSafari,
isWeb,
supportsSetSinkId,
Expand Down Expand Up @@ -276,7 +279,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
this.e2eeManager.on(
EncryptionEvent.ParticipantEncryptionStatusChanged,
(enabled, participant) => {
if (participant instanceof LocalParticipant) {
if (isLocalParticipant(participant)) {
this.isE2EEEnabled = enabled;
}
this.emit(RoomEvent.ParticipantEncryptionStatusChanged, enabled, participant);
Expand Down Expand Up @@ -1950,7 +1953,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
private updateSubscriptions() {
for (const p of this.remoteParticipants.values()) {
for (const pub of p.videoTrackPublications.values()) {
if (pub.isSubscribed && pub instanceof RemoteTrackPublication) {
if (pub.isSubscribed && isRemotePub(pub)) {
pub.emitTrackUpdate();
}
}
Expand Down Expand Up @@ -2072,7 +2075,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)

this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);

if (pub.track instanceof LocalAudioTrack) {
if (isLocalAudioTrack(pub.track)) {
const trackIsSilent = await pub.track.checkForSilence();
if (trackIsSilent) {
this.emit(RoomEvent.LocalAudioSilenceDetected, pub);
Expand Down
43 changes: 23 additions & 20 deletions src/room/participant/LocalParticipant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,15 @@ import type { ChatMessage, DataPublishOptions } from '../types';
import {
Future,
compareVersions,
isAudioTrack,
isE2EESimulcastSupported,
isFireFox,
isLocalAudioTrack,
isLocalTrack,
isLocalVideoTrack,
isSVCCodec,
isSafari17,
isVideoTrack,
isWeb,
sleep,
supportsAV1,
Expand Down Expand Up @@ -640,9 +645,9 @@ export default class LocalParticipant extends Participant {
track.setAudioContext(this.audioContext);
}
track.mediaStream = stream;
if (track instanceof LocalAudioTrack && audioProcessor) {
if (isAudioTrack(track) && audioProcessor) {
await track.setProcessor(audioProcessor);
} else if (track instanceof LocalVideoTrack && videoProcessor) {
} else if (isVideoTrack(track) && videoProcessor) {
await track.setProcessor(videoProcessor);
}
return track;
Expand Down Expand Up @@ -717,15 +722,15 @@ export default class LocalParticipant extends Participant {
options?: TrackPublishOptions,
isRepublish = false,
): Promise<LocalTrackPublication> {
if (track instanceof LocalAudioTrack) {
if (isLocalAudioTrack(track)) {
track.setAudioContext(this.audioContext);
}

await this.reconnectFuture?.promise;
if (this.republishPromise && !isRepublish) {
await this.republishPromise;
}
if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
if (isLocalTrack(track) && this.pendingPublishPromises.has(track)) {
await this.pendingPublishPromises.get(track);
}
let defaultConstraints: MediaTrackConstraints | undefined;
Expand Down Expand Up @@ -857,15 +862,15 @@ export default class LocalParticipant extends Participant {

private async publish(track: LocalTrack, opts: TrackPublishOptions, isStereo: boolean) {
const existingTrackOfSource = Array.from(this.trackPublications.values()).find(
(publishedTrack) => track instanceof LocalTrack && publishedTrack.source === track.source,
(publishedTrack) => isLocalTrack(track) && publishedTrack.source === track.source,
);
if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
this.log.info(`publishing a second track with the same source: ${track.source}`, {
...this.logContext,
...getLogContextFromTrack(track),
});
}
if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
if (opts.stopMicTrackOnMute && isAudioTrack(track)) {
track.stopOnMute = true;
}

Expand Down Expand Up @@ -950,7 +955,7 @@ export default class LocalParticipant extends Participant {
req.width = dims.width;
req.height = dims.height;
// for svc codecs, disable simulcast and use vp8 for backup codec
if (track instanceof LocalVideoTrack) {
if (isLocalVideoTrack(track)) {
if (isSVCCodec(videoCodec)) {
if (track.source === Track.Source.ScreenShare) {
// vp9 svc with screenshare cannot encode multiple spatial layers
Expand Down Expand Up @@ -1036,7 +1041,7 @@ export default class LocalParticipant extends Participant {

track.sender = await this.engine.createSender(track, opts, encodings);

if (track instanceof LocalVideoTrack) {
if (isLocalVideoTrack(track)) {
opts.degradationPreference ??= getDefaultDegradationPreference(track);
track.setDegradationPreference(opts.degradationPreference);
}
Expand Down Expand Up @@ -1126,9 +1131,9 @@ export default class LocalParticipant extends Participant {
trackInfo: ti,
});

if (track instanceof LocalVideoTrack) {
if (isLocalVideoTrack(track)) {
track.startMonitor(this.engine.client);
} else if (track instanceof LocalAudioTrack) {
} else if (isLocalAudioTrack(track)) {
track.startMonitor();
}

Expand Down Expand Up @@ -1169,7 +1174,7 @@ export default class LocalParticipant extends Participant {
throw new TrackInvalidError('track is not published');
}

if (!(track instanceof LocalVideoTrack)) {
if (!isLocalVideoTrack(track)) {
throw new TrackInvalidError('track is not a video track');
}

Expand Down Expand Up @@ -1236,7 +1241,7 @@ export default class LocalParticipant extends Participant {
track: LocalTrack | MediaStreamTrack,
stopOnUnpublish?: boolean,
): Promise<LocalTrackPublication | undefined> {
if (track instanceof LocalTrack) {
if (isLocalTrack(track)) {
const publishPromise = this.pendingPublishPromises.get(track);
if (publishPromise) {
this.log.info('awaiting publish promise before attempting to unpublish', {
Expand Down Expand Up @@ -1303,7 +1308,7 @@ export default class LocalParticipant extends Participant {
if (this.engine.removeTrack(trackSender)) {
negotiationNeeded = true;
}
if (track instanceof LocalVideoTrack) {
if (isLocalVideoTrack(track)) {
for (const [, trackInfo] of track.simulcastCodecs) {
if (trackInfo.sender) {
if (this.engine.removeTrack(trackInfo.sender)) {
Expand Down Expand Up @@ -1349,9 +1354,7 @@ export default class LocalParticipant extends Participant {
tracks: LocalTrack[] | MediaStreamTrack[],
): Promise<LocalTrackPublication[]> {
const results = await Promise.all(tracks.map((track) => this.unpublishTrack(track)));
return results.filter(
(track) => track instanceof LocalTrackPublication,
) as LocalTrackPublication[];
return results.filter((track) => !!track);
}

async republishAllTracks(options?: TrackPublishOptions, restartTracks: boolean = true) {
Expand Down Expand Up @@ -1379,7 +1382,7 @@ export default class LocalParticipant extends Participant {
!track.isMuted &&
track.source !== Track.Source.ScreenShare &&
track.source !== Track.Source.ScreenShareAudio &&
(track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) &&
(isLocalAudioTrack(track) || isLocalVideoTrack(track)) &&
!track.isUserProvided
) {
// generally we need to restart the track before publishing, often a full reconnect
Expand Down Expand Up @@ -1962,7 +1965,7 @@ export default class LocalParticipant extends Participant {
this.unpublishTrack(track);
} else if (track.isUserProvided) {
await track.mute();
} else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
} else if (isLocalAudioTrack(track) || isLocalVideoTrack(track)) {
try {
if (isWeb()) {
try {
Expand Down Expand Up @@ -1997,7 +2000,7 @@ export default class LocalParticipant extends Participant {
...this.logContext,
...getLogContextFromTrack(track),
});
if (track instanceof LocalAudioTrack) {
if (isLocalAudioTrack(track)) {
// fall back to default device if available
await track.restartTrack({ deviceId: 'default' });
} else {
Expand Down Expand Up @@ -2026,7 +2029,7 @@ export default class LocalParticipant extends Participant {

// this looks overly complicated due to this object tree
if (track instanceof MediaStreamTrack) {
if (localTrack instanceof LocalAudioTrack || localTrack instanceof LocalVideoTrack) {
if (isLocalAudioTrack(localTrack) || isLocalVideoTrack(localTrack)) {
if (localTrack.mediaStreamTrack === track) {
publication = <LocalTrackPublication>pub;
}
Expand Down
7 changes: 2 additions & 5 deletions src/room/participant/Participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import { EventEmitter } from 'events';
import type TypedEmitter from 'typed-emitter';
import log, { LoggerNames, type StructuredLogger, getLogger } from '../../logger';
import { ParticipantEvent, TrackEvent } from '../events';
import LocalAudioTrack from '../track/LocalAudioTrack';
import type LocalTrackPublication from '../track/LocalTrackPublication';
import RemoteAudioTrack from '../track/RemoteAudioTrack';
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 { ChatMessage, LoggerOptions, TranscriptionSegment } from '../types';
import { isAudioTrack } from '../utils';

export enum ConnectionQuality {
Excellent = 'excellent',
Expand Down Expand Up @@ -317,9 +316,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
setAudioContext(ctx: AudioContext | undefined) {
this.audioContext = ctx;
this.audioTrackPublications.forEach(
(track) =>
(track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) &&
track.track.setAudioContext(ctx),
(track) => isAudioTrack(track.track) && track.track.setAudioContext(ctx),
);
}

Expand Down
Loading
Loading