@livedigital/client 2.15.0 → 2.17.0

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.
@@ -113,12 +113,14 @@ export declare enum ConnectionQuality {
113
113
  }
114
114
  export declare type EncoderConfig = {};
115
115
  export declare type VideoCodec = 'h264' | 'vp8';
116
+ export declare type AudioCodec = 'opus';
116
117
  export declare type VideoEncoderConfig = EncoderConfig & {
117
118
  preferredCodec?: VideoCodec;
118
119
  encodings?: RtpEncodingParameters[];
119
120
  videoGoogleStartBitrate?: number;
120
121
  };
121
122
  export declare type AudioEncoderConfig = EncoderConfig & {
123
+ preferredCodec?: AudioCodec;
122
124
  enableFec?: boolean;
123
125
  };
124
126
  export declare type CreateTrackOptions = {
@@ -204,3 +206,9 @@ export declare type RemoteConsumerOptions = ConsumerOptions & {
204
206
  };
205
207
  export declare type LogMessageHandler = (msg: any, ...meta: any) => void;
206
208
  export declare type LogLevel = 3 | 4 | 6 | 7;
209
+ export declare type TransportConnectionTimeoutPayload = {
210
+ reason: 'ice' | 'dtls';
211
+ transportId: string;
212
+ direction: 'receive' | 'send';
213
+ timeout: number;
214
+ };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "2.15.0",
5
+ "version": "2.17.0",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -11,6 +11,7 @@ export const CLIENT_EVENTS = {
11
11
  peerLeft: 'peer-left',
12
12
  channelRejoinRequired: 'channel-rejoin-required',
13
13
  devicesListUpdated: 'devices-list-updated',
14
+ transportConnectionTimeout: 'transport-connection-timeout',
14
15
  };
15
16
 
16
17
  export const PEER_EVENTS = {
@@ -51,6 +52,7 @@ export const MEDIASOUP_EVENTS = {
51
52
  transportClose: 'transport.close',
52
53
  transportConnect: 'transport.connect',
53
54
  transportProduce: 'transport.produce',
55
+ transportConnectionTimeout: 'transport.connectionTimeout',
54
56
  transportStateChange: 'connectionstatechange',
55
57
  transportGetIceParameters: 'getIceParameters',
56
58
  };
@@ -4,10 +4,12 @@ import {
4
4
  ConsumerScoreChangedPayload,
5
5
  ProducerData,
6
6
  ProducerRequestMaxSpatialLayer,
7
- ProducerScoreChangedPayload, ProducerSetMaxSpatialLayer,
7
+ ProducerScoreChangedPayload,
8
+ ProducerSetMaxSpatialLayer,
9
+ TransportConnectionTimeoutPayload,
8
10
  } from '../../types/common';
9
11
  import Engine from '../index';
10
- import { MEDIASOUP_EVENTS } from '../../constants/events';
12
+ import { CLIENT_EVENTS, MEDIASOUP_EVENTS } from '../../constants/events';
11
13
  import Logger from '../Logger';
12
14
  import VideoTrack from '../media/tracks/VideoTrack';
13
15
 
@@ -138,6 +140,30 @@ class MediaSoupEventHandler {
138
140
 
139
141
  peer.observer.safeEmit(MEDIASOUP_EVENTS.producerSetMaxSpatialLayer, { peerId, producerId, spatialLayer });
140
142
  });
143
+
144
+ connection.on(MEDIASOUP_EVENTS.transportConnectionTimeout, async ({
145
+ reason,
146
+ transportId,
147
+ direction,
148
+ timeout,
149
+ }: TransportConnectionTimeoutPayload) => {
150
+ const transport = [
151
+ this.engine.network.receiveTransport,
152
+ this.engine.network.sendTransport,
153
+ ].find((item) => item?.id === transportId);
154
+
155
+ if (!transport || transport.connectionState === 'connected') {
156
+ return;
157
+ }
158
+
159
+ this.logger.warn(MEDIASOUP_EVENTS.transportConnectionTimeout, {
160
+ reason,
161
+ transportId,
162
+ direction,
163
+ timeout,
164
+ });
165
+ this.engine.clientEventEmitter.emit(CLIENT_EVENTS.transportConnectionTimeout, { direction, reason });
166
+ });
141
167
  }
142
168
 
143
169
  private async handleProducerSetMaxSpatialLayer({
@@ -69,7 +69,7 @@ class Engine {
69
69
 
70
70
  private channel?: string;
71
71
 
72
- private isRoomJoining = false;
72
+ private isChannelJoining = false;
73
73
 
74
74
  private readonly logger: Logger;
75
75
 
@@ -187,14 +187,14 @@ class Engine {
187
187
  public async join(params: JoinChannelParams): Promise<void> {
188
188
  try {
189
189
  this.logger.debug('join()', { params });
190
- this.isRoomJoining = true;
190
+ this.isChannelJoining = true;
191
191
  await this.connectToSocketServerWithRetry(params);
192
192
  await this.performJoin(params);
193
193
  } catch (error) {
194
194
  this.logger.error('join()', { error });
195
195
  throw error;
196
196
  } finally {
197
- this.isRoomJoining = false;
197
+ this.isChannelJoining = false;
198
198
  }
199
199
  }
200
200
 
@@ -512,13 +512,13 @@ class Engine {
512
512
  const stopListening = () => this.network.socket.observer.removeListener('state', onSocketStateChange);
513
513
  const isStateNotExpected = [SocketIOEvents.Disconnected, SocketIOEvents.Reconnecting].includes(state);
514
514
 
515
- if (error || (this.isRoomJoining && isStateNotExpected)) {
515
+ if (error || (this.isChannelJoining && isStateNotExpected)) {
516
516
  stopListening();
517
517
  reject(error || 'Not expected socket state for new connection');
518
518
  return;
519
519
  }
520
520
 
521
- if (this.isRoomJoining && [SocketIOEvents.Connected, SocketIOEvents.Reconnected].includes(state)) {
521
+ if (this.isChannelJoining && [SocketIOEvents.Connected, SocketIOEvents.Reconnected].includes(state)) {
522
522
  stopListening();
523
523
  resolve();
524
524
  }
@@ -45,18 +45,13 @@ class Media {
45
45
  }
46
46
 
47
47
  getTrackCodec(track: Track): RtpCodecCapability | undefined {
48
- if (!(track instanceof VideoTrack)) {
48
+ const { rtpCapabilities, rtpCapabilities: { codecs } } = this.mediasoupDevice;
49
+ if (!codecs) {
50
+ this.#logger.error('getTrackCodec()', { track, rtpCapabilities });
49
51
  return undefined;
50
52
  }
51
53
 
52
- if (!this.mediasoupDevice.rtpCapabilities?.codecs) {
53
- return undefined;
54
- }
55
-
56
- return this.mediasoupDevice
57
- .rtpCapabilities
58
- .codecs
59
- .find((c) => c.mimeType.toLowerCase() === `video/${track.getPreferredCodec()}`);
54
+ return codecs.find((c) => c.mimeType.toLowerCase() === `${track.kind}/${track.getPreferredCodec()}`);
60
55
  }
61
56
 
62
57
  private createTracks(stream: MediaStream): Track[] {
@@ -1,5 +1,5 @@
1
1
  import { ProducerCodecOptions } from 'mediasoup-client/lib/Producer';
2
- import { AudioEncoderConfig } from '../../../types/common';
2
+ import { AudioCodec, AudioEncoderConfig } from '../../../types/common';
3
3
  import BaseTrack from './BaseTrack';
4
4
  import TrackWithCodecOptions from './TrackWithCodecOptions';
5
5
 
@@ -13,6 +13,15 @@ class AudioTrack extends BaseTrack implements TrackWithCodecOptions {
13
13
  opusFec: this.getEncoderConfig().enableFec || true,
14
14
  };
15
15
  }
16
+
17
+ getPreferredCodec(): AudioCodec {
18
+ const { preferredCodec } = this.getEncoderConfig();
19
+ if (preferredCodec) {
20
+ return preferredCodec;
21
+ }
22
+
23
+ return 'opus';
24
+ }
16
25
  }
17
26
 
18
27
  export default AudioTrack;
@@ -1,7 +1,7 @@
1
1
  import { ProducerCodecOptions } from 'mediasoup-client/lib/Producer';
2
2
  import { RtpEncodingParameters } from 'mediasoup-client/lib/RtpParameters';
3
3
  import { WEBCAM_SIMULCAST_ENCODINGS } from '../../../constants/simulcastEncodings';
4
- import { VideoCodec, VideoEncoderConfig } from '../../../types/common';
4
+ import { TrackLabel, VideoCodec, VideoEncoderConfig } from '../../../types/common';
5
5
  import BaseTrack from './BaseTrack';
6
6
  import TrackWithCodecOptions from './TrackWithCodecOptions';
7
7
  import TrackWithEncodings from './TrackWithEncodings';
@@ -20,7 +20,16 @@ class VideoTrack extends BaseTrack implements TrackWithCodecOptions, TrackWithEn
20
20
  }
21
21
 
22
22
  getPreferredCodec(): VideoCodec {
23
- return this.getEncoderConfig().preferredCodec || 'h264';
23
+ const { preferredCodec } = this.getEncoderConfig();
24
+ if (preferredCodec) {
25
+ return preferredCodec;
26
+ }
27
+
28
+ if (this.getLabel() === TrackLabel.ScreenVideo) {
29
+ return 'vp8';
30
+ }
31
+
32
+ return 'h264';
24
33
  }
25
34
 
26
35
  getEncodings(): RtpEncodingParameters[] {
@@ -49,7 +49,11 @@ class SocketIO {
49
49
  }
50
50
 
51
51
  connect(serverUrl: string): void {
52
- const connection = io(serverUrl);
52
+ const connection = io(serverUrl, {
53
+ transports: ['polling', 'websocket'],
54
+ upgrade: true,
55
+ rememberUpgrade: false,
56
+ });
53
57
 
54
58
  this.connection = connection;
55
59
  this.serverUrl = serverUrl;
@@ -92,7 +92,7 @@ class Network {
92
92
 
93
93
  const sendTransportOptions = await this.socket.request(MEDIASOUP_EVENTS.transportCreate, {
94
94
  sctpCapabilities: mediasoupDevice.sctpCapabilities,
95
- localDirection: 'out',
95
+ localDirection: 'send',
96
96
  }) as TransportOptions;
97
97
 
98
98
  this.sendTransport = mediasoupDevice.createSendTransport(sendTransportOptions);
@@ -118,7 +118,6 @@ class Network {
118
118
  }
119
119
  });
120
120
 
121
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
122
121
  this.sendTransport.on(MEDIASOUP_TRANSPORT_EVENTS.produce, async (
123
122
  parameters: ProduceParams,
124
123
  callback: (a: unknown) => void,
@@ -158,8 +157,7 @@ class Network {
158
157
  }
159
158
 
160
159
  const recvTransportOptions = await this.socket.request(MEDIASOUP_EVENTS.transportCreate, {
161
- sctpCapabilities: mediasoupDevice.sctpCapabilities,
162
- localDirection: 'in',
160
+ localDirection: 'receive',
163
161
  }) as TransportOptions;
164
162
  this.receiveTransport = mediasoupDevice.createRecvTransport(recvTransportOptions);
165
163
  this.logger.debug('createRecvTransport()', { transport: this.receiveTransport });
@@ -10,9 +10,11 @@ import {
10
10
  import QualityLimitationsIssueDetector from './detectors/QualityLimitationsIssueDetector';
11
11
  import FramesDroppedIssueDetector from './detectors/FramesDroppedIssueDetector';
12
12
  import FramesEncodedSentIssueDetector from './detectors/FramesEncodedSentIssueDetector';
13
- import NetworkIssueDetector from './detectors/NetworkIssueDetector';
13
+ import InboundNetworkIssueDetector from './detectors/InboundNetworkIssueDetector';
14
+ import OutboundNetworkIssueDetector from './detectors/OutboundNetworkIssueDetector';
14
15
  import NetworkMediaSyncIssueDetector from './detectors/NetworkMediaSyncIssueDetector';
15
16
  import AvailableOutgoingBitrateIssueDetector from './detectors/AvailableOutgoingBitrateIssueDetector';
17
+ import VideoCodecMismatchDetector from './detectors/VideoCodecMismatchDetector';
16
18
 
17
19
  class WebRTCIssueDetector {
18
20
  private readonly webrtcStats: WebRTCStats;
@@ -35,6 +37,7 @@ class WebRTCIssueDetector {
35
37
  // Move instantiation from the constructor
36
38
  this.webrtcStats = new WebRTCStats({
37
39
  getStatsInterval: this.getStatsInterval,
40
+ remote: true,
38
41
  });
39
42
 
40
43
  (window as unknown as WIDWindow).wid = this;
@@ -44,9 +47,11 @@ class WebRTCIssueDetector {
44
47
  new QualityLimitationsIssueDetector(),
45
48
  new FramesDroppedIssueDetector(),
46
49
  new FramesEncodedSentIssueDetector(),
47
- new NetworkIssueDetector(),
50
+ new InboundNetworkIssueDetector(),
51
+ new OutboundNetworkIssueDetector(),
48
52
  new NetworkMediaSyncIssueDetector(),
49
53
  new AvailableOutgoingBitrateIssueDetector(),
54
+ new VideoCodecMismatchDetector(),
50
55
  ];
51
56
 
52
57
  this.webrtcStats.on('stats', (event) => {
@@ -6,7 +6,7 @@ import {
6
6
  IssueType,
7
7
  } from '../types';
8
8
 
9
- class NetworkIssueDetector implements IssueDetector {
9
+ class InboundNetworkIssueDetector implements IssueDetector {
10
10
  #lastProcessedStats: { [connectionId: string]: WebRTCStatsEventData } = {};
11
11
 
12
12
  detect(data: WebRTCStatsEventData): IssueDetectorResult {
@@ -89,16 +89,17 @@ class NetworkIssueDetector implements IssueDetector {
89
89
  if (isPoorConnectionQuality) {
90
90
  issues.push({
91
91
  type: IssueType.Network,
92
- reason: IssueReason.LowMOS,
92
+ reason: IssueReason.LowInboundMOS,
93
93
  iceCandidate: data.connection.local.id,
94
94
  debug,
95
+ data: mos,
95
96
  });
96
97
  }
97
98
 
98
99
  if (isNetworkIssue) {
99
100
  issues.push({
100
101
  type: IssueType.Network,
101
- reason: IssueReason.NetworkQuality,
102
+ reason: IssueReason.InboundNetworkQuality,
102
103
  iceCandidate: data.connection.local.id,
103
104
  debug,
104
105
  });
@@ -116,7 +117,7 @@ class NetworkIssueDetector implements IssueDetector {
116
117
  if (isNetworkMediaLatencyIssue) {
117
118
  issues.push({
118
119
  type: IssueType.Network,
119
- reason: IssueReason.NetworkMediaLatency,
120
+ reason: IssueReason.InboundNetworkMediaLatency,
120
121
  iceCandidate: data.connection.local.id,
121
122
  debug,
122
123
  });
@@ -135,4 +136,4 @@ class NetworkIssueDetector implements IssueDetector {
135
136
  }
136
137
  }
137
138
 
138
- export default NetworkIssueDetector;
139
+ export default InboundNetworkIssueDetector;
@@ -0,0 +1,115 @@
1
+ import { WebRTCStatsEventData } from '@peermetrics/webrtc-stats';
2
+ import {
3
+ IssueDetector,
4
+ IssueDetectorResult,
5
+ IssueReason,
6
+ IssueType,
7
+ } from '../types';
8
+
9
+ class OutboundNetworkIssueDetector implements IssueDetector {
10
+ #lastProcessedStats: { [connectionId: string]: WebRTCStatsEventData } = {};
11
+
12
+ detect(data: WebRTCStatsEventData): IssueDetectorResult {
13
+ const issues = this.processData(data);
14
+ this.#lastProcessedStats[data.connection.id] = data;
15
+ return issues;
16
+ }
17
+
18
+ private processData(data: WebRTCStatsEventData): IssueDetectorResult {
19
+ const issues: IssueDetectorResult = [];
20
+ const remoteInboundRTPStreamsStats = [
21
+ ...data.remote?.audio.inbound || [],
22
+ ...data.remote?.video.inbound || [],
23
+ ];
24
+
25
+ if (!remoteInboundRTPStreamsStats.length) {
26
+ return issues;
27
+ }
28
+
29
+ const previousStats = this.#lastProcessedStats[data.connection.id];
30
+ if (!previousStats) {
31
+ return issues;
32
+ }
33
+
34
+ const previousRemoteInboundRTPStreamsStats = [
35
+ ...previousStats.remote?.audio.inbound || [],
36
+ ...previousStats.remote?.video.inbound || [],
37
+ ];
38
+
39
+ const { packetsSent } = data.connection;
40
+ const lastPacketsSent = previousStats.connection.packetsSent;
41
+
42
+ const rtpNetworkStats = remoteInboundRTPStreamsStats.reduce((stats, currentStreamStats) => {
43
+ const previousStreamStats = previousRemoteInboundRTPStreamsStats
44
+ .find((stream) => stream.ssrc === currentStreamStats.ssrc);
45
+
46
+ return {
47
+ sumJitter: stats.sumJitter + currentStreamStats.jitter,
48
+ packetsLost: stats.packetsLost + currentStreamStats.packetsLost,
49
+ lastPacketsLost: stats.lastPacketsLost + (previousStreamStats?.packetsLost || 0),
50
+ };
51
+ }, {
52
+ sumJitter: 0,
53
+ packetsLost: 0,
54
+ lastPacketsLost: 0,
55
+ });
56
+
57
+ const rtt = (1e3 * data.connection.currentRoundTripTime) || 0;
58
+ const { sumJitter } = rtpNetworkStats;
59
+ const avgJitter = sumJitter / remoteInboundRTPStreamsStats.length;
60
+
61
+ const deltaPacketSent = packetsSent - lastPacketsSent;
62
+ const deltaPacketLost = rtpNetworkStats.packetsLost - rtpNetworkStats.lastPacketsLost;
63
+
64
+ const packetsLoss = deltaPacketSent && deltaPacketLost
65
+ ? Math.round((deltaPacketLost * 100) / (deltaPacketSent + deltaPacketLost))
66
+ : 0;
67
+
68
+ const effectiveLatency = rtt + (avgJitter * 2) + 10;
69
+ let rFactor = effectiveLatency < 160
70
+ ? 93.2 - (effectiveLatency / 40)
71
+ : 93.2 - (effectiveLatency / 120) - 10;
72
+ rFactor -= (packetsLoss * 2.5);
73
+ const mos = 1 + (0.035) * rFactor + (0.000007) * rFactor * (rFactor - 60) * (100 - rFactor);
74
+
75
+ const isHighPacketsLoss = packetsLoss > 5;
76
+ const isHighJitter = avgJitter >= 200;
77
+ const isPoorConnectionQuality = mos < 3.5;
78
+ const isNetworkMediaLatencyIssue = isHighPacketsLoss && isHighJitter;
79
+ const isNetworkIssue = (!isHighPacketsLoss && isHighJitter) || isHighJitter || isHighPacketsLoss;
80
+ const debug = `packetLoss: ${packetsLoss}%, jitter: ${avgJitter}, rtt: ${rtt},`
81
+ + ` MOS: ${mos}`;
82
+
83
+ if (isNetworkMediaLatencyIssue) {
84
+ issues.push({
85
+ type: IssueType.Network,
86
+ reason: IssueReason.OutboundNetworkMediaLatency,
87
+ iceCandidate: data.connection.local.id,
88
+ debug,
89
+ });
90
+ }
91
+
92
+ if (isNetworkIssue) {
93
+ issues.push({
94
+ type: IssueType.Network,
95
+ reason: IssueReason.OutboundNetworkQuality,
96
+ iceCandidate: data.connection.local.id,
97
+ debug,
98
+ });
99
+ }
100
+
101
+ if (isPoorConnectionQuality) {
102
+ issues.push({
103
+ type: IssueType.Network,
104
+ reason: IssueReason.LowOutboundMOS,
105
+ iceCandidate: data.connection.local.id,
106
+ debug,
107
+ data: mos,
108
+ });
109
+ }
110
+
111
+ return issues;
112
+ }
113
+ }
114
+
115
+ export default OutboundNetworkIssueDetector;
@@ -0,0 +1,39 @@
1
+ import { WebRTCStatsEventData } from '@peermetrics/webrtc-stats';
2
+ import {
3
+ IssueDetector,
4
+ IssueDetectorResult,
5
+ IssueReason,
6
+ IssueType,
7
+ } from '../types';
8
+
9
+ class VideoCodecMismatchDetector implements IssueDetector {
10
+ #lastProcessedStats: { [connectionId: string]: WebRTCStatsEventData } = {};
11
+
12
+ detect(data: WebRTCStatsEventData): IssueDetectorResult {
13
+ const issues = this.processData(data);
14
+ this.#lastProcessedStats[data.connection.id] = data;
15
+ return issues;
16
+ }
17
+
18
+ private processData(data: WebRTCStatsEventData): IssueDetectorResult {
19
+ const issues: IssueDetectorResult = [];
20
+ const previousInboundRTPVideoStreamsStats = this.#lastProcessedStats[data.connection.id]?.video.inbound;
21
+
22
+ data.video.inbound.forEach((streamStats) => {
23
+ const previousStreamStats = previousInboundRTPVideoStreamsStats?.find((item) => item.ssrc === streamStats.ssrc);
24
+ if (streamStats.decoderImplementation === 'unknown' && previousStreamStats?.decoderImplementation !== 'unknown') {
25
+ issues.push({
26
+ type: IssueType.Stream,
27
+ reason: IssueReason.VideoCodecMismatchIssue,
28
+ ssrc: streamStats.ssrc,
29
+ trackIdentifier: streamStats.track.trackIdentifier,
30
+ debug: `mimeType: ${streamStats.mimeType}, decoderImplementation: ${streamStats.decoderImplementation}`,
31
+ });
32
+ }
33
+ });
34
+
35
+ return issues;
36
+ }
37
+ }
38
+
39
+ export default VideoCodecMismatchDetector;
@@ -26,18 +26,23 @@ export enum IssueType {
26
26
  Network = 'network',
27
27
  CPU = 'cpu',
28
28
  Server = 'server',
29
+ Stream = 'stream',
29
30
  }
30
31
 
31
32
  export enum IssueReason {
32
- NetworkQuality = 'network-quality',
33
- NetworkMediaLatency = 'network-media-latency',
33
+ OutboundNetworkQuality = 'outbound-network-quality',
34
+ InboundNetworkQuality = 'inbound-network-quality',
35
+ OutboundNetworkMediaLatency = 'outbound-network-media-latency',
36
+ InboundNetworkMediaLatency = 'inbound-network-media-latency',
34
37
  NetworkMediaSyncFailure = 'network-media-sync-failure',
35
38
  OutboundNetworkThroughput = 'outbound-network-throughput',
36
39
  InboundNetworkThroughput = 'inbound-network-throughput',
37
40
  EncoderCPUThrottling = 'encoder-cpu-throttling',
38
41
  DecoderCPUThrottling = 'decoder-cpu-throttling',
39
42
  ServerIssue = 'server-issue',
40
- LowMOS = 'low-mean-opinion-score',
43
+ VideoCodecMismatchIssue = 'codec-mismatch',
44
+ LowInboundMOS = 'low-inbound-mean-opinion-score',
45
+ LowOutboundMOS = 'low-outbound-mean-opinion-score',
41
46
  }
42
47
 
43
48
  export type IssuePayload = {
@@ -45,5 +50,7 @@ export type IssuePayload = {
45
50
  reason: IssueReason,
46
51
  ssrc?: number,
47
52
  iceCandidate?: string,
53
+ data?: number;
48
54
  debug?: string,
55
+ trackIdentifier?: string,
49
56
  };
@@ -135,6 +135,8 @@ export type EncoderConfig = {};
135
135
 
136
136
  export type VideoCodec = 'h264' | 'vp8';
137
137
 
138
+ export type AudioCodec = 'opus';
139
+
138
140
  export type VideoEncoderConfig = EncoderConfig & {
139
141
  preferredCodec?: VideoCodec,
140
142
  encodings?: RtpEncodingParameters[],
@@ -142,6 +144,7 @@ export type VideoEncoderConfig = EncoderConfig & {
142
144
  };
143
145
 
144
146
  export type AudioEncoderConfig = EncoderConfig & {
147
+ preferredCodec?: AudioCodec,
145
148
  enableFec?: boolean;
146
149
  };
147
150
 
@@ -235,3 +238,10 @@ export type RemoteConsumerOptions = ConsumerOptions & {
235
238
  export type LogMessageHandler = (msg: any, ...meta: any) => void;
236
239
 
237
240
  export type LogLevel = 3 | 4 | 6 | 7;
241
+
242
+ export type TransportConnectionTimeoutPayload = {
243
+ reason: 'ice' | 'dtls',
244
+ transportId: string,
245
+ direction: 'receive' | 'send',
246
+ timeout: number,
247
+ };