livekit-client 1.14.4 → 1.15.1

Sign up to get free protection for your applications and to get access to all the features.
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) {