livekit-client 1.14.4 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  4. package/dist/livekit-client.esm.mjs +5488 -5230
  5. package/dist/livekit-client.esm.mjs.map +1 -1
  6. package/dist/livekit-client.umd.js +1 -1
  7. package/dist/livekit-client.umd.js.map +1 -1
  8. package/dist/src/api/SignalClient.d.ts.map +1 -1
  9. package/dist/src/room/PCTransport.d.ts +10 -4
  10. package/dist/src/room/PCTransport.d.ts.map +1 -1
  11. package/dist/src/room/PCTransportManager.d.ts +51 -0
  12. package/dist/src/room/PCTransportManager.d.ts.map +1 -0
  13. package/dist/src/room/RTCEngine.d.ts +8 -5
  14. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  15. package/dist/src/room/Room.d.ts +9 -0
  16. package/dist/src/room/Room.d.ts.map +1 -1
  17. package/dist/src/room/events.d.ts +10 -0
  18. package/dist/src/room/events.d.ts.map +1 -1
  19. package/dist/src/room/participant/LocalParticipant.d.ts +0 -5
  20. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  21. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  22. package/dist/src/room/track/Track.d.ts +6 -2
  23. package/dist/src/room/track/Track.d.ts.map +1 -1
  24. package/dist/src/room/track/options.d.ts +2 -0
  25. package/dist/src/room/track/options.d.ts.map +1 -1
  26. package/dist/src/room/track/utils.d.ts +3 -0
  27. package/dist/src/room/track/utils.d.ts.map +1 -1
  28. package/dist/src/room/utils.d.ts.map +1 -1
  29. package/dist/src/test/mocks.d.ts +1 -1
  30. package/dist/src/test/mocks.d.ts.map +1 -1
  31. package/dist/ts4.2/src/room/PCTransport.d.ts +10 -4
  32. package/dist/ts4.2/src/room/PCTransportManager.d.ts +51 -0
  33. package/dist/ts4.2/src/room/RTCEngine.d.ts +8 -5
  34. package/dist/ts4.2/src/room/Room.d.ts +9 -0
  35. package/dist/ts4.2/src/room/events.d.ts +10 -0
  36. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +0 -5
  37. package/dist/ts4.2/src/room/track/Track.d.ts +6 -2
  38. package/dist/ts4.2/src/room/track/options.d.ts +2 -0
  39. package/dist/ts4.2/src/room/track/utils.d.ts +3 -0
  40. package/dist/ts4.2/src/test/mocks.d.ts +1 -1
  41. package/package.json +20 -19
  42. package/src/api/SignalClient.ts +7 -1
  43. package/src/connectionHelper/checks/webrtc.ts +2 -2
  44. package/src/room/PCTransport.ts +66 -29
  45. package/src/room/PCTransportManager.ts +336 -0
  46. package/src/room/RTCEngine.ts +178 -246
  47. package/src/room/Room.ts +49 -46
  48. package/src/room/defaults.ts +1 -1
  49. package/src/room/events.ts +11 -0
  50. package/src/room/participant/LocalParticipant.ts +9 -51
  51. package/src/room/track/LocalTrack.ts +2 -0
  52. package/src/room/track/Track.ts +30 -9
  53. package/src/room/track/options.ts +2 -0
  54. package/src/room/track/utils.ts +19 -0
  55. package/src/room/utils.ts +2 -1
  56. package/src/test/mocks.ts +5 -1
@@ -0,0 +1,51 @@
1
+ import { SignalTarget } from '../proto/livekit_rtc_pb';
2
+ import PCTransport from './PCTransport';
3
+ export declare enum PCTransportState {
4
+ NEW = 0,
5
+ CONNECTING = 1,
6
+ CONNECTED = 2,
7
+ FAILED = 3,
8
+ CLOSING = 4,
9
+ CLOSED = 5
10
+ }
11
+ export declare class PCTransportManager {
12
+ publisher: PCTransport;
13
+ subscriber: PCTransport;
14
+ peerConnectionTimeout: number;
15
+ get needsPublisher(): boolean;
16
+ get needsSubscriber(): boolean;
17
+ get currentState(): PCTransportState;
18
+ onStateChange?: (state: PCTransportState, pubState: RTCPeerConnectionState, subState: RTCPeerConnectionState) => void;
19
+ onIceCandidate?: (ev: RTCIceCandidate, target: SignalTarget) => void;
20
+ onDataChannel?: (ev: RTCDataChannelEvent) => void;
21
+ onTrack?: (ev: RTCTrackEvent) => void;
22
+ onPublisherOffer?: (offer: RTCSessionDescriptionInit) => void;
23
+ private isPublisherConnectionRequired;
24
+ private isSubscriberConnectionRequired;
25
+ private state;
26
+ private connectionLock;
27
+ constructor(rtcConfig: RTCConfiguration, subscriberPrimary: boolean);
28
+ requirePublisher(require?: boolean): void;
29
+ requireSubscriber(require?: boolean): void;
30
+ createAndSendPublisherOffer(options?: RTCOfferOptions): Promise<void>;
31
+ setPublisherAnswer(sd: RTCSessionDescriptionInit): Promise<void>;
32
+ removeTrack(sender: RTCRtpSender): void | undefined;
33
+ close(): Promise<void>;
34
+ triggerIceRestart(): Promise<void>;
35
+ addIceCandidate(candidate: RTCIceCandidateInit, target: SignalTarget): Promise<void>;
36
+ createSubscriberAnswerFromOffer(sd: RTCSessionDescriptionInit): Promise<RTCSessionDescriptionInit>;
37
+ updateConfiguration(config: RTCConfiguration, iceRestart?: boolean): void;
38
+ ensurePCTransportConnection(abortController?: AbortController, timeout?: number): Promise<void>;
39
+ negotiate(abortController: AbortController): Promise<void>;
40
+ addPublisherTransceiver(track: MediaStreamTrack, transceiverInit: RTCRtpTransceiverInit): RTCRtpTransceiver;
41
+ addPublisherTrack(track: MediaStreamTrack): RTCRtpSender;
42
+ createPublisherDataChannel(label: string, dataChannelDict: RTCDataChannelInit): RTCDataChannel;
43
+ /**
44
+ * Returns the first required transport's address if no explicit target is specified
45
+ */
46
+ getConnectedAddress(target?: SignalTarget): Promise<string | undefined>;
47
+ private get requiredTransports();
48
+ private updateState;
49
+ private ensureTransportConnected;
50
+ }
51
+ //# sourceMappingURL=PCTransportManager.d.ts.map
@@ -3,24 +3,26 @@ import type { SignalOptions } from '../api/SignalClient';
3
3
  import { SignalClient } from '../api/SignalClient';
4
4
  import type { InternalRoomOptions } from '../options';
5
5
  import { DataPacket, DataPacket_Kind, DisconnectReason, ParticipantInfo, Room as RoomModel, SpeakerInfo, TrackInfo, UserPacket } from '../proto/livekit_models_pb';
6
- import { AddTrackRequest, ConnectionQualityUpdate, JoinResponse, StreamStateUpdate, SubscriptionPermissionUpdate, SubscriptionResponse } from '../proto/livekit_rtc_pb';
6
+ import type { AddTrackRequest, ConnectionQualityUpdate, JoinResponse, StreamStateUpdate, SubscriptionPermissionUpdate, SubscriptionResponse } from '../proto/livekit_rtc_pb';
7
7
  import PCTransport from './PCTransport';
8
+ import { PCTransportManager } from './PCTransportManager';
8
9
  import type { RegionUrlProvider } from './RegionUrlProvider';
9
10
  import type LocalTrack from './track/LocalTrack';
11
+ import type LocalTrackPublication from './track/LocalTrackPublication';
10
12
  import type LocalVideoTrack from './track/LocalVideoTrack';
11
13
  import type { SimulcastTrackInfo } from './track/LocalVideoTrack';
14
+ import type RemoteTrackPublication from './track/RemoteTrackPublication';
12
15
  import { Track } from './track/Track';
13
16
  import type { TrackPublishOptions, VideoCodec } from './track/options';
14
17
  declare const RTCEngine_base: new () => TypedEventEmitter<EngineEventCallbacks>;
15
18
  /** @internal */
16
19
  export default class RTCEngine extends RTCEngine_base {
17
20
  private options;
18
- publisher?: PCTransport;
19
- subscriber?: PCTransport;
20
21
  client: SignalClient;
21
22
  rtcConfig: RTCConfiguration;
22
23
  peerConnectionTimeout: number;
23
24
  fullReconnectOnNext: boolean;
25
+ pcManager?: PCTransportManager;
24
26
  /**
25
27
  * @internal
26
28
  */
@@ -32,11 +34,9 @@ export default class RTCEngine extends RTCEngine_base {
32
34
  private dcBufferStatus;
33
35
  private reliableDCSub?;
34
36
  private subscriberPrimary;
35
- private primaryTransport?;
36
37
  private pcState;
37
38
  private _isClosed;
38
39
  private pendingTrackResolvers;
39
- private hasPublished;
40
40
  private url?;
41
41
  private token?;
42
42
  private signalOpts?;
@@ -106,7 +106,10 @@ export default class RTCEngine extends RTCEngine_base {
106
106
  /** @internal */
107
107
  negotiate(): Promise<void>;
108
108
  dataChannelForKind(kind: DataPacket_Kind, sub?: boolean): RTCDataChannel | undefined;
109
+ /** @internal */
110
+ sendSyncState(remoteTracks: RemoteTrackPublication[], localTracks: LocalTrackPublication[]): void;
109
111
  failNext(): void;
112
+ private dataChannelsInfo;
110
113
  private clearReconnectTimeout;
111
114
  private clearPendingReconnect;
112
115
  private handleBrowserOnLine;
@@ -63,6 +63,7 @@ declare class Room extends Room_base {
63
63
  private connectionReconcileInterval?;
64
64
  private regionUrlProvider?;
65
65
  private regionUrl?;
66
+ private isVideoPlaybackBlocked;
66
67
  /**
67
68
  * Creates a new Room, the primary construct for a LiveKit session.
68
69
  * @param options
@@ -133,10 +134,15 @@ declare class Room extends Room_base {
133
134
  * - `getUserMedia`
134
135
  */
135
136
  startAudio: () => Promise<void>;
137
+ startVideo: () => Promise<void>;
136
138
  /**
137
139
  * Returns true if audio playback is enabled
138
140
  */
139
141
  get canPlaybackAudio(): boolean;
142
+ /**
143
+ * Returns true if video playback is enabled
144
+ */
145
+ get canPlaybackVideo(): boolean;
140
146
  /**
141
147
  * Returns the active audio output device used in this room.
142
148
  * @return the previously successfully set audio output device ID or an empty string if the default device is used.
@@ -171,6 +177,8 @@ declare class Room extends Room_base {
171
177
  private handleDataPacket;
172
178
  private handleAudioPlaybackStarted;
173
179
  private handleAudioPlaybackFailed;
180
+ private handleVideoPlaybackStarted;
181
+ private handleVideoPlaybackFailed;
174
182
  private handleDeviceChange;
175
183
  private handleRoomUpdate;
176
184
  private handleConnectionQualityUpdate;
@@ -238,6 +246,7 @@ export type RoomEventCallbacks = {
238
246
  trackSubscriptionPermissionChanged: (publication: RemoteTrackPublication, status: TrackPublication.PermissionStatus, participant: RemoteParticipant) => void;
239
247
  trackSubscriptionStatusChanged: (publication: RemoteTrackPublication, status: TrackPublication.SubscriptionStatus, participant: RemoteParticipant) => void;
240
248
  audioPlaybackChanged: (playing: boolean) => void;
249
+ videoPlaybackChanged: (playing: boolean) => void;
241
250
  signalConnected: () => void;
242
251
  recordingStatusChanged: (recording: boolean) => void;
243
252
  participantEncryptionStatusChanged: (encrypted: boolean, participant?: Participant) => void;
@@ -215,6 +215,12 @@ export declare enum RoomEvent {
215
215
  * `Room.canPlaybackAudio` will indicate if audio playback is permitted.
216
216
  */
217
217
  AudioPlaybackStatusChanged = "audioPlaybackChanged",
218
+ /**
219
+ * LiveKit will attempt to autoplay all video tracks when you attach them to
220
+ * a video element. However, if that fails, we'll notify you via VideoPlaybackStatusChanged.
221
+ * Calling `room.startVideo()` in a user gesture event handler will resume the video playback.
222
+ */
223
+ VideoPlaybackStatusChanged = "videoPlaybackChanged",
218
224
  /**
219
225
  * When we have encountered an error while attempting to create a track.
220
226
  * The errors take place in getUserMedia().
@@ -449,6 +455,10 @@ export declare enum TrackEvent {
449
455
  /** @internal */
450
456
  VideoDimensionsChanged = "videoDimensionsChanged",
451
457
  /** @internal */
458
+ VideoPlaybackStarted = "videoPlaybackStarted",
459
+ /** @internal */
460
+ VideoPlaybackFailed = "videoPlaybackFailed",
461
+ /** @internal */
452
462
  ElementAttached = "elementAttached",
453
463
  /** @internal */
454
464
  ElementDetached = "elementDetached",
@@ -1,6 +1,5 @@
1
1
  import type { InternalRoomOptions } from '../../options';
2
2
  import { DataPacket_Kind, ParticipantInfo, ParticipantPermission } from '../../proto/livekit_models_pb';
3
- import { DataChannelInfo, TrackPublishedResponse } from '../../proto/livekit_rtc_pb';
4
3
  import type RTCEngine from '../RTCEngine';
5
4
  import LocalTrack from '../track/LocalTrack';
6
5
  import LocalTrackPublication from '../track/LocalTrackPublication';
@@ -174,9 +173,5 @@ export default class LocalParticipant extends Participant {
174
173
  private handleLocalTrackUnpublished;
175
174
  private handleTrackEnded;
176
175
  private getPublicationForTrack;
177
- /** @internal */
178
- publishedTracksInfo(): TrackPublishedResponse[];
179
- /** @internal */
180
- dataChannelsInfo(): DataChannelInfo[];
181
176
  }
182
177
  //# sourceMappingURL=LocalParticipant.d.ts.map
@@ -64,8 +64,10 @@ export declare abstract class Track extends Track_base {
64
64
  protected handleAppVisibilityChanged(): Promise<void>;
65
65
  protected addAppVisibilityListener(): void;
66
66
  protected removeAppVisibilityListener(): void;
67
+ private handleElementSuspended;
68
+ private handleElementPlay;
69
+ private debouncedPlaybackStateChange;
67
70
  }
68
- /** @internal */
69
71
  export declare function attachToElement(track: MediaStreamTrack, element: HTMLMediaElement): void;
70
72
  /** @internal */
71
73
  export declare function detachTrack(track: MediaStreamTrack, element: HTMLMediaElement): void;
@@ -112,10 +114,12 @@ export type TrackEventCallbacks = {
112
114
  updateSettings: () => void;
113
115
  updateSubscription: () => void;
114
116
  audioPlaybackStarted: () => void;
115
- audioPlaybackFailed: (error: Error) => void;
117
+ audioPlaybackFailed: (error?: Error) => void;
116
118
  audioSilenceDetected: () => void;
117
119
  visibilityChanged: (visible: boolean, track?: any) => void;
118
120
  videoDimensionsChanged: (dimensions: Track.Dimensions, track?: any) => void;
121
+ videoPlaybackStarted: () => void;
122
+ videoPlaybackFailed: (error?: Error) => void;
119
123
  elementAttached: (element: HTMLMediaElement) => void;
120
124
  elementDetached: (element: HTMLMediaElement) => void;
121
125
  upstreamPaused: (track: any) => void;
@@ -12,6 +12,8 @@ export interface TrackPublishDefaults {
12
12
  *
13
13
  * You could customize specific encoding parameters of the backup track by
14
14
  * explicitly setting codec and encoding fields.
15
+ *
16
+ * Defaults to `true`
15
17
  */
16
18
  backupCodec?: true | false | {
17
19
  codec: BackupVideoCodec;
@@ -1,4 +1,6 @@
1
+ import { TrackPublishedResponse } from '../../proto/livekit_rtc_pb';
1
2
  import { Track } from './Track';
3
+ import type { TrackPublication } from './TrackPublication';
2
4
  import type { AudioCaptureOptions, CreateLocalTracksOptions, ScreenShareCaptureOptions, VideoCaptureOptions } from './options';
3
5
  import type { AudioTrack } from './types';
4
6
  export declare function mergeDefaultOptions(options?: CreateLocalTracksOptions, audioDefaults?: AudioCaptureOptions, videoDefaults?: VideoCaptureOptions): CreateLocalTracksOptions;
@@ -25,4 +27,5 @@ export declare function sourceToKind(source: Track.Source): MediaDeviceKind | un
25
27
  */
26
28
  export declare function screenCaptureToDisplayMediaStreamOptions(options: ScreenShareCaptureOptions): DisplayMediaStreamOptions;
27
29
  export declare function mimeTypeToVideoCodecString(mimeType: string): "vp8" | "h264" | "vp9" | "av1";
30
+ export declare function getTrackPublicationInfo<T extends TrackPublication>(tracks: T[]): TrackPublishedResponse[];
28
31
  //# sourceMappingURL=utils.d.ts.map
@@ -5,7 +5,7 @@ declare const mocks: {
5
5
  SignalClient: MockedClass<typeof SignalClient>;
6
6
  RTCEngine: MockedClass<typeof RTCEngine>;
7
7
  MockLocalVideoTrack: {
8
- stop: import("@vitest/spy").Mock<any, any>;
8
+ stop: () => void;
9
9
  };
10
10
  };
11
11
  export default mocks;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "1.14.4",
3
+ "version": "1.15.1",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -28,35 +28,20 @@
28
28
  "./dist/ts4.2/src/index.d.ts"
29
29
  ],
30
30
  "./dist/src/e2ee/worker/e2ee.worker.d.ts": [
31
- "./dist/ts4.2//dist/src/e2ee/worker/e2ee.worker.d.ts"
31
+ "./dist/ts4.2/dist/src/e2ee/worker/e2ee.worker.d.ts"
32
32
  ]
33
33
  }
34
34
  },
35
35
  "repository": "git@github.com:livekit/client-sdk-js.git",
36
36
  "author": "David Zhao <david@davidzhao.com>",
37
37
  "license": "Apache-2.0",
38
- "scripts": {
39
- "build": "rollup --config --bundleConfigAsCjs && rollup --config rollup.config.worker.js --bundleConfigAsCjs && yarn downlevel-dts",
40
- "build:watch": "rollup --watch --config rollup.config.js",
41
- "build-docs": "typedoc",
42
- "proto": "protoc --es_out src/proto --es_opt target=ts -I./protocol ./protocol/livekit_rtc.proto ./protocol/livekit_models.proto",
43
- "sample": "vite example -c vite.config.js",
44
- "lint": "eslint src",
45
- "test": "vitest run src",
46
- "deploy": "gh-pages -d example/dist",
47
- "format": "prettier --write src example/sample.ts",
48
- "format:check": "prettier --check src",
49
- "release": "yarn build && yarn compat && changeset publish",
50
- "downlevel-dts": "downlevel-dts ./dist/ ./dist/ts4.2 --to=4.2",
51
- "compat": "eslint --no-eslintrc --config ./.eslintrc.dist.cjs ./dist/livekit-client.umd.js",
52
- "size-limit": "size-limit"
53
- },
54
38
  "dependencies": {
55
39
  "@bufbuild/protobuf": "^1.3.0",
56
40
  "events": "^3.3.0",
57
41
  "loglevel": "^1.8.0",
58
42
  "sdp-transform": "^2.14.1",
59
43
  "ts-debounce": "^4.0.0",
44
+ "tslib": "2.6.2",
60
45
  "typed-emitter": "^2.1.0",
61
46
  "webrtc-adapter": "^8.1.1"
62
47
  },
@@ -98,5 +83,21 @@
98
83
  "typescript": "5.2.2",
99
84
  "vite": "4.5.0",
100
85
  "vitest": "^0.34.0"
86
+ },
87
+ "scripts": {
88
+ "build": "rollup --config --bundleConfigAsCjs && rollup --config rollup.config.worker.js --bundleConfigAsCjs && pnpm downlevel-dts",
89
+ "build:watch": "rollup --watch --config rollup.config.js",
90
+ "build-docs": "typedoc",
91
+ "proto": "protoc --es_out src/proto --es_opt target=ts -I./protocol ./protocol/livekit_rtc.proto ./protocol/livekit_models.proto",
92
+ "sample": "vite example -c vite.config.js",
93
+ "lint": "eslint src",
94
+ "test": "vitest run src",
95
+ "deploy": "gh-pages -d example/dist",
96
+ "format": "prettier --write src example/sample.ts",
97
+ "format:check": "prettier --check src",
98
+ "ci:publish": "pnpm build && pnpm compat && changeset publish",
99
+ "downlevel-dts": "downlevel-dts ./dist/ ./dist/ts4.2 --to=4.2",
100
+ "compat": "eslint --no-eslintrc --config ./.eslintrc.dist.cjs ./dist/livekit-client.umd.js",
101
+ "size-limit": "size-limit"
101
102
  }
102
- }
103
+ }
@@ -558,6 +558,8 @@ export class SignalClient {
558
558
  log.debug('received unsupported message');
559
559
  return;
560
560
  }
561
+
562
+ let pingHandled = false;
561
563
  if (msg.case === 'answer') {
562
564
  const sd = fromProtoSessionDescription(msg.value);
563
565
  if (this.onAnswer) {
@@ -626,13 +628,17 @@ export class SignalClient {
626
628
  this.onSubscriptionError(msg.value);
627
629
  }
628
630
  } else if (msg.case === 'pong') {
629
- this.resetPingTimeout();
630
631
  } else if (msg.case === 'pongResp') {
631
632
  this.rtt = Date.now() - Number.parseInt(msg.value.lastPingTimestamp.toString());
632
633
  this.resetPingTimeout();
634
+ pingHandled = true;
633
635
  } else {
634
636
  log.debug('unsupported message', msg);
635
637
  }
638
+
639
+ if (!pingHandled) {
640
+ this.resetPingTimeout();
641
+ }
636
642
  }
637
643
 
638
644
  setReconnected() {
@@ -38,8 +38,8 @@ export class WebRTCCheck extends Checker {
38
38
  }
39
39
  };
40
40
 
41
- if (this.room.engine.subscriber) {
42
- this.room.engine.subscriber.onIceCandidateError = (ev) => {
41
+ if (this.room.engine.pcManager) {
42
+ this.room.engine.pcManager.subscriber.onIceCandidateError = (ev) => {
43
43
  if (ev instanceof RTCPeerConnectionIceErrorEvent) {
44
44
  this.appendWarning(
45
45
  `error with ICE candidate: ${ev.errorCode} ${ev.errorText} ${ev.url}`,
@@ -33,10 +33,16 @@ export default class PCTransport extends EventEmitter {
33
33
  private _pc: RTCPeerConnection | null;
34
34
 
35
35
  private get pc() {
36
- if (this._pc) return this._pc;
37
- throw new UnexpectedConnectionState('Expected peer connection to be available');
36
+ if (!this._pc) {
37
+ this._pc = this.createPC();
38
+ }
39
+ return this._pc;
38
40
  }
39
41
 
42
+ private config?: RTCConfiguration;
43
+
44
+ private mediaConstraints: Record<string, unknown>;
45
+
40
46
  pendingCandidates: RTCIceCandidateInit[] = [];
41
47
 
42
48
  restartingIce: boolean = false;
@@ -57,32 +63,53 @@ export default class PCTransport extends EventEmitter {
57
63
 
58
64
  onConnectionStateChange?: (state: RTCPeerConnectionState) => void;
59
65
 
66
+ onIceConnectionStateChange?: (state: RTCIceConnectionState) => void;
67
+
68
+ onSignalingStatechange?: (state: RTCSignalingState) => void;
69
+
60
70
  onDataChannel?: (ev: RTCDataChannelEvent) => void;
61
71
 
62
72
  onTrack?: (ev: RTCTrackEvent) => void;
63
73
 
64
74
  constructor(config?: RTCConfiguration, mediaConstraints: Record<string, unknown> = {}) {
65
75
  super();
66
- this._pc = isChromiumBased()
76
+ this.config = config;
77
+ this.mediaConstraints = mediaConstraints;
78
+ this._pc = this.createPC();
79
+ }
80
+
81
+ private createPC() {
82
+ const pc = isChromiumBased()
67
83
  ? // @ts-expect-error chrome allows additional media constraints to be passed into the RTCPeerConnection constructor
68
- new RTCPeerConnection(config, mediaConstraints)
69
- : new RTCPeerConnection(config);
70
- this._pc.onicecandidate = (ev) => {
84
+ new RTCPeerConnection(this.config, this.mediaConstraints)
85
+ : new RTCPeerConnection(this.config);
86
+
87
+ pc.onicecandidate = (ev) => {
71
88
  if (!ev.candidate) return;
72
89
  this.onIceCandidate?.(ev.candidate);
73
90
  };
74
- this._pc.onicecandidateerror = (ev) => {
91
+ pc.onicecandidateerror = (ev) => {
75
92
  this.onIceCandidateError?.(ev);
76
93
  };
77
- this._pc.onconnectionstatechange = () => {
78
- this.onConnectionStateChange?.(this._pc?.connectionState ?? 'closed');
94
+
95
+ pc.oniceconnectionstatechange = () => {
96
+ this.onIceConnectionStateChange?.(pc.iceConnectionState);
79
97
  };
80
- this._pc.ondatachannel = (ev) => {
98
+
99
+ pc.onsignalingstatechange = () => {
100
+ this.onSignalingStatechange?.(pc.signalingState);
101
+ };
102
+
103
+ pc.onconnectionstatechange = () => {
104
+ this.onConnectionStateChange?.(pc.connectionState);
105
+ };
106
+ pc.ondatachannel = (ev) => {
81
107
  this.onDataChannel?.(ev);
82
108
  };
83
- this._pc.ontrack = (ev) => {
109
+ pc.ontrack = (ev) => {
84
110
  this.onTrack?.(ev);
85
111
  };
112
+ return pc;
86
113
  }
87
114
 
88
115
  get isICEConnected(): boolean {
@@ -168,7 +195,7 @@ export default class PCTransport extends EventEmitter {
168
195
 
169
196
  if (this.renegotiate) {
170
197
  this.renegotiate = false;
171
- this.createAndSendOffer();
198
+ await this.createAndSendOffer();
172
199
  } else if (sd.type === 'answer') {
173
200
  this.emit(PCEvents.NegotiationComplete);
174
201
  if (sd.sdp) {
@@ -183,10 +210,10 @@ export default class PCTransport extends EventEmitter {
183
210
  }
184
211
 
185
212
  // debounced negotiate interface
186
- negotiate = debounce((onError?: (e: Error) => void) => {
213
+ negotiate = debounce(async (onError?: (e: Error) => void) => {
187
214
  this.emit(PCEvents.NegotiationStarted);
188
215
  try {
189
- this.createAndSendOffer();
216
+ await this.createAndSendOffer();
190
217
  } catch (e) {
191
218
  if (onError) {
192
219
  onError(e as Error);
@@ -209,11 +236,11 @@ export default class PCTransport extends EventEmitter {
209
236
  if (this._pc && this._pc.signalingState === 'have-local-offer') {
210
237
  // we're waiting for the peer to accept our offer, so we'll just wait
211
238
  // the only exception to this is when ICE restart is needed
212
- const currentSD = this.pc.remoteDescription;
239
+ const currentSD = this._pc.remoteDescription;
213
240
  if (options?.iceRestart && currentSD) {
214
241
  // TODO: handle when ICE restart is needed but we don't have a remote description
215
242
  // the best thing to do is to recreate the peerconnection
216
- await this.pc.setRemoteDescription(currentSD);
243
+ await this._pc.setRemoteDescription(currentSD);
217
244
  } else {
218
245
  this.renegotiate = true;
219
246
  return;
@@ -307,7 +334,10 @@ export default class PCTransport extends EventEmitter {
307
334
  }
308
335
 
309
336
  addTrack(track: MediaStreamTrack) {
310
- return this.pc.addTrack(track);
337
+ if (!this._pc) {
338
+ throw new UnexpectedConnectionState('PC closed, cannot add track');
339
+ }
340
+ return this._pc.addTrack(track);
311
341
  }
312
342
 
313
343
  setTrackCodecBitrate(info: TrackBitrateInfo) {
@@ -315,43 +345,50 @@ export default class PCTransport extends EventEmitter {
315
345
  }
316
346
 
317
347
  setConfiguration(rtcConfig: RTCConfiguration) {
318
- return this.pc.setConfiguration(rtcConfig);
348
+ if (!this._pc) {
349
+ throw new UnexpectedConnectionState('PC closed, cannot configure');
350
+ }
351
+ return this._pc?.setConfiguration(rtcConfig);
319
352
  }
320
353
 
321
354
  canRemoveTrack(): boolean {
322
- return !!this.pc.removeTrack;
355
+ return !!this._pc?.removeTrack;
323
356
  }
324
357
 
325
358
  removeTrack(sender: RTCRtpSender) {
326
- return this.pc.removeTrack(sender);
359
+ return this._pc?.removeTrack(sender);
327
360
  }
328
361
 
329
362
  getConnectionState() {
330
- return this.pc.connectionState;
363
+ return this._pc?.connectionState ?? 'closed';
331
364
  }
332
365
 
333
366
  getICEConnectionState() {
334
- return this.pc.iceConnectionState;
367
+ return this._pc?.iceConnectionState ?? 'closed';
335
368
  }
336
369
 
337
370
  getSignallingState() {
338
- return this.pc.signalingState;
371
+ return this._pc?.signalingState ?? 'closed';
339
372
  }
340
373
 
341
374
  getTransceivers() {
342
- return this.pc.getTransceivers();
375
+ return this._pc?.getTransceivers() ?? [];
343
376
  }
344
377
 
345
378
  getSenders() {
346
- return this.pc.getSenders();
379
+ return this._pc?.getSenders() ?? [];
347
380
  }
348
381
 
349
382
  getLocalDescription() {
350
- return this.pc.localDescription;
383
+ return this._pc?.localDescription;
351
384
  }
352
385
 
353
386
  getRemoteDescription() {
354
- return this.pc.remoteDescription;
387
+ return this.pc?.remoteDescription;
388
+ }
389
+
390
+ getStats() {
391
+ return this.pc.getStats();
355
392
  }
356
393
 
357
394
  async getConnectedAddress(): Promise<string | undefined> {
@@ -391,7 +428,7 @@ export default class PCTransport extends EventEmitter {
391
428
  return candidates.get(selectedID);
392
429
  }
393
430
 
394
- close() {
431
+ close = () => {
395
432
  if (!this._pc) {
396
433
  return;
397
434
  }
@@ -408,7 +445,7 @@ export default class PCTransport extends EventEmitter {
408
445
  this._pc.onconnectionstatechange = null;
409
446
  this._pc.oniceconnectionstatechange = null;
410
447
  this._pc = null;
411
- }
448
+ };
412
449
 
413
450
  private async setMungedSDP(sd: RTCSessionDescriptionInit, munged?: string, remote?: boolean) {
414
451
  if (munged) {