livekit-client 2.1.2 → 2.1.4
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.
- package/dist/livekit-client.esm.mjs +153 -49
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts +1 -0
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +1 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +5 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +3 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +4 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +4 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +1 -0
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/src/room/RegionUrlProvider.d.ts +1 -0
- package/dist/ts4.2/src/room/participant/publishUtils.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +5 -1
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +4 -1
- package/dist/ts4.2/src/room/track/options.d.ts +4 -0
- package/dist/ts4.2/src/room/track/utils.d.ts +1 -0
- package/dist/ts4.2/src/room/types.d.ts +1 -1
- package/dist/ts4.2/src/room/utils.d.ts +1 -0
- package/dist/ts4.2/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/api/SignalClient.ts +3 -1
- package/src/room/PCTransport.ts +10 -7
- package/src/room/RTCEngine.ts +47 -10
- package/src/room/RegionUrlProvider.ts +5 -0
- package/src/room/Room.ts +9 -2
- package/src/room/participant/LocalParticipant.ts +7 -16
- package/src/room/participant/publishUtils.ts +20 -0
- package/src/room/track/LocalTrack.ts +21 -2
- package/src/room/track/LocalVideoTrack.ts +23 -0
- package/src/room/track/RemoteTrack.ts +11 -5
- package/src/room/track/Track.ts +1 -1
- package/src/room/track/options.ts +5 -0
- package/src/room/track/utils.ts +4 -0
- package/src/room/types.ts +3 -1
- package/src/room/utils.ts +4 -2
- package/src/version.ts +1 -1
package/src/room/RTCEngine.ts
CHANGED
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
DisconnectReason,
|
10
10
|
type JoinResponse,
|
11
11
|
type LeaveRequest,
|
12
|
+
LeaveRequest_Action,
|
12
13
|
ParticipantInfo,
|
13
14
|
ReconnectReason,
|
14
15
|
type ReconnectResponse,
|
@@ -435,7 +436,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
435
436
|
this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
|
436
437
|
};
|
437
438
|
|
438
|
-
|
439
|
+
if (!supportOptionalDatachannel(joinResponse.serverInfo?.protocol)) {
|
440
|
+
this.createDataChannels();
|
441
|
+
}
|
439
442
|
}
|
440
443
|
|
441
444
|
private setupSignalClientCallbacks() {
|
@@ -504,16 +507,28 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
504
507
|
this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
|
505
508
|
};
|
506
509
|
|
507
|
-
this.client.onLeave = (leave
|
508
|
-
if (leave?.canReconnect) {
|
509
|
-
this.fullReconnectOnNext = true;
|
510
|
-
// reconnect immediately instead of waiting for next attempt
|
511
|
-
this.handleDisconnect(leaveReconnect);
|
512
|
-
} else {
|
513
|
-
this.emit(EngineEvent.Disconnected, leave?.reason);
|
514
|
-
this.close();
|
515
|
-
}
|
510
|
+
this.client.onLeave = (leave: LeaveRequest) => {
|
516
511
|
this.log.debug('client leave request', { ...this.logContext, reason: leave?.reason });
|
512
|
+
if (leave.regions && this.regionUrlProvider) {
|
513
|
+
this.log.debug('updating regions', this.logContext);
|
514
|
+
this.regionUrlProvider.setServerReportedRegions(leave.regions);
|
515
|
+
}
|
516
|
+
switch (leave.action) {
|
517
|
+
case LeaveRequest_Action.DISCONNECT:
|
518
|
+
this.emit(EngineEvent.Disconnected, leave?.reason);
|
519
|
+
this.close();
|
520
|
+
break;
|
521
|
+
case LeaveRequest_Action.RECONNECT:
|
522
|
+
this.fullReconnectOnNext = true;
|
523
|
+
// reconnect immediately instead of waiting for next attempt
|
524
|
+
this.handleDisconnect(leaveReconnect);
|
525
|
+
break;
|
526
|
+
case LeaveRequest_Action.RESUME:
|
527
|
+
// reconnect immediately instead of waiting for next attempt
|
528
|
+
this.handleDisconnect(leaveReconnect);
|
529
|
+
default:
|
530
|
+
break;
|
531
|
+
}
|
517
532
|
};
|
518
533
|
}
|
519
534
|
|
@@ -1103,11 +1118,21 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1103
1118
|
throw new ConnectionError(`${transportName} connection not set`);
|
1104
1119
|
}
|
1105
1120
|
|
1121
|
+
let needNegotiation = false;
|
1122
|
+
if (!subscriber && !this.dataChannelForKind(kind, subscriber)) {
|
1123
|
+
this.createDataChannels();
|
1124
|
+
needNegotiation = true;
|
1125
|
+
}
|
1126
|
+
|
1106
1127
|
if (
|
1128
|
+
!needNegotiation &&
|
1107
1129
|
!subscriber &&
|
1108
1130
|
!this.pcManager.publisher.isICEConnected &&
|
1109
1131
|
this.pcManager.publisher.getICEConnectionState() !== 'checking'
|
1110
1132
|
) {
|
1133
|
+
needNegotiation = true;
|
1134
|
+
}
|
1135
|
+
if (needNegotiation) {
|
1111
1136
|
// start negotiation
|
1112
1137
|
this.negotiate();
|
1113
1138
|
}
|
@@ -1168,6 +1193,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1168
1193
|
}
|
1169
1194
|
|
1170
1195
|
this.pcManager.requirePublisher();
|
1196
|
+
// don't negotiate without any transceivers or data channel, it will generate sdp without ice frag then negotiate failed
|
1197
|
+
if (
|
1198
|
+
this.pcManager.publisher.getTransceivers().length == 0 &&
|
1199
|
+
!this.lossyDC &&
|
1200
|
+
!this.reliableDC
|
1201
|
+
) {
|
1202
|
+
this.createDataChannels();
|
1203
|
+
}
|
1171
1204
|
|
1172
1205
|
const abortController = new AbortController();
|
1173
1206
|
|
@@ -1378,3 +1411,7 @@ export type EngineEventCallbacks = {
|
|
1378
1411
|
remoteMute: (trackSid: string, muted: boolean) => void;
|
1379
1412
|
offline: () => void;
|
1380
1413
|
};
|
1414
|
+
|
1415
|
+
function supportOptionalDatachannel(protocol: number | undefined): boolean {
|
1416
|
+
return protocol !== undefined && protocol > 13;
|
1417
|
+
}
|
package/src/room/Room.ts
CHANGED
@@ -4,6 +4,7 @@ import {
|
|
4
4
|
DisconnectReason,
|
5
5
|
JoinResponse,
|
6
6
|
LeaveRequest,
|
7
|
+
LeaveRequest_Action,
|
7
8
|
ParticipantInfo,
|
8
9
|
ParticipantInfo_State,
|
9
10
|
ParticipantPermission,
|
@@ -859,7 +860,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
859
860
|
onLeave(
|
860
861
|
new LeaveRequest({
|
861
862
|
reason: DisconnectReason.CLIENT_INITIATED,
|
862
|
-
|
863
|
+
action: LeaveRequest_Action.RECONNECT,
|
863
864
|
}),
|
864
865
|
);
|
865
866
|
}
|
@@ -876,7 +877,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
876
877
|
},
|
877
878
|
});
|
878
879
|
break;
|
879
|
-
|
880
|
+
case 'leave-full-reconnect':
|
881
|
+
req = new SimulateScenario({
|
882
|
+
scenario: {
|
883
|
+
case: 'leaveRequestFullReconnect',
|
884
|
+
value: true,
|
885
|
+
},
|
886
|
+
});
|
880
887
|
default:
|
881
888
|
}
|
882
889
|
if (req) {
|
@@ -54,6 +54,7 @@ import { trackPermissionToProto } from './ParticipantTrackPermission';
|
|
54
54
|
import {
|
55
55
|
computeTrackBackupEncodings,
|
56
56
|
computeVideoEncodings,
|
57
|
+
getDefaultDegradationPreference,
|
57
58
|
mediaTrackToLocalTrack,
|
58
59
|
} from './publishUtils';
|
59
60
|
|
@@ -857,6 +858,11 @@ export default class LocalParticipant extends Participant {
|
|
857
858
|
|
858
859
|
track.sender = await this.engine.createSender(track, opts, encodings);
|
859
860
|
|
861
|
+
if (track instanceof LocalVideoTrack) {
|
862
|
+
opts.degradationPreference ??= getDefaultDegradationPreference(track);
|
863
|
+
track.setDegradationPreference(opts.degradationPreference);
|
864
|
+
}
|
865
|
+
|
860
866
|
if (encodings) {
|
861
867
|
if (isFireFox() && track.kind === Track.Kind.Audio) {
|
862
868
|
/* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
|
@@ -880,8 +886,7 @@ export default class LocalParticipant extends Participant {
|
|
880
886
|
maxbr: encodings[0]?.maxBitrate ? encodings[0].maxBitrate / 1000 : 0,
|
881
887
|
});
|
882
888
|
}
|
883
|
-
} else if (track.codec && track.codec
|
884
|
-
// AV1 requires setting x-start-bitrate in SDP
|
889
|
+
} else if (track.codec && isSVCCodec(track.codec) && encodings[0]?.maxBitrate) {
|
885
890
|
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
886
891
|
cid: req.cid,
|
887
892
|
codec: track.codec,
|
@@ -890,20 +895,6 @@ export default class LocalParticipant extends Participant {
|
|
890
895
|
}
|
891
896
|
}
|
892
897
|
|
893
|
-
if (track.kind === Track.Kind.Video && track.source === Track.Source.ScreenShare) {
|
894
|
-
// a few of reasons we are forcing this setting without allowing overrides:
|
895
|
-
// 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue
|
896
|
-
// 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced`
|
897
|
-
try {
|
898
|
-
this.log.debug(`setting degradationPreference to maintain-resolution`);
|
899
|
-
const params = track.sender.getParameters();
|
900
|
-
params.degradationPreference = 'maintain-resolution';
|
901
|
-
await track.sender.setParameters(params);
|
902
|
-
} catch (e) {
|
903
|
-
this.log.warn(`failed to set degradationPreference: ${e}`);
|
904
|
-
}
|
905
|
-
}
|
906
|
-
|
907
898
|
await this.engine.negotiate();
|
908
899
|
|
909
900
|
if (track instanceof LocalVideoTrack) {
|
@@ -19,6 +19,7 @@ import {
|
|
19
19
|
isReactNative,
|
20
20
|
isSVCCodec,
|
21
21
|
isSafari,
|
22
|
+
unwrapConstraint,
|
22
23
|
} from '../utils';
|
23
24
|
|
24
25
|
/** @internal */
|
@@ -171,6 +172,11 @@ export function computeVideoEncodings(
|
|
171
172
|
});
|
172
173
|
}
|
173
174
|
|
175
|
+
if (original.encoding.priority) {
|
176
|
+
encodings[0].priority = original.encoding.priority;
|
177
|
+
encodings[0].networkPriority = original.encoding.priority;
|
178
|
+
}
|
179
|
+
|
174
180
|
log.debug(`using svc encoding`, { encodings });
|
175
181
|
return encodings;
|
176
182
|
}
|
@@ -435,3 +441,17 @@ export class ScalabilityMode {
|
|
435
441
|
return `L${this.spatial}T${this.temporal}${this.suffix ?? ''}`;
|
436
442
|
}
|
437
443
|
}
|
444
|
+
|
445
|
+
export function getDefaultDegradationPreference(track: LocalVideoTrack): RTCDegradationPreference {
|
446
|
+
// a few of reasons we have different default paths:
|
447
|
+
// 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue
|
448
|
+
// 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced`
|
449
|
+
if (
|
450
|
+
track.source === Track.Source.ScreenShare ||
|
451
|
+
(track.constraints.height && unwrapConstraint(track.constraints.height) >= 1080)
|
452
|
+
) {
|
453
|
+
return 'maintain-resolution';
|
454
|
+
} else {
|
455
|
+
return 'balanced';
|
456
|
+
}
|
457
|
+
}
|
@@ -15,8 +15,17 @@ const defaultDimensionsTimeout = 1000;
|
|
15
15
|
export default abstract class LocalTrack<
|
16
16
|
TrackKind extends Track.Kind = Track.Kind,
|
17
17
|
> extends Track<TrackKind> {
|
18
|
+
protected _sender?: RTCRtpSender;
|
19
|
+
|
18
20
|
/** @internal */
|
19
|
-
sender
|
21
|
+
get sender(): RTCRtpSender | undefined {
|
22
|
+
return this._sender;
|
23
|
+
}
|
24
|
+
|
25
|
+
/** @internal */
|
26
|
+
set sender(sender: RTCRtpSender | undefined) {
|
27
|
+
this._sender = sender;
|
28
|
+
}
|
20
29
|
|
21
30
|
/** @internal */
|
22
31
|
codec?: VideoCodec;
|
@@ -43,6 +52,8 @@ export default abstract class LocalTrack<
|
|
43
52
|
|
44
53
|
protected audioContext?: AudioContext;
|
45
54
|
|
55
|
+
protected manuallyStopped: boolean = false;
|
56
|
+
|
46
57
|
private restartLock: Mutex;
|
47
58
|
|
48
59
|
/**
|
@@ -259,6 +270,7 @@ export default abstract class LocalTrack<
|
|
259
270
|
}
|
260
271
|
|
261
272
|
protected async restart(constraints?: MediaTrackConstraints) {
|
273
|
+
this.manuallyStopped = false;
|
262
274
|
const unlock = await this.restartLock.lock();
|
263
275
|
try {
|
264
276
|
if (!constraints) {
|
@@ -296,8 +308,14 @@ export default abstract class LocalTrack<
|
|
296
308
|
|
297
309
|
await this.setMediaStreamTrack(newTrack);
|
298
310
|
this._constraints = constraints;
|
299
|
-
|
300
311
|
this.emit(TrackEvent.Restarted, this);
|
312
|
+
if (this.manuallyStopped) {
|
313
|
+
this.log.warn(
|
314
|
+
'track was stopped during a restart, stopping restarted track',
|
315
|
+
this.logContext,
|
316
|
+
);
|
317
|
+
this.stop();
|
318
|
+
}
|
301
319
|
return this;
|
302
320
|
} finally {
|
303
321
|
unlock();
|
@@ -361,6 +379,7 @@ export default abstract class LocalTrack<
|
|
361
379
|
};
|
362
380
|
|
363
381
|
stop() {
|
382
|
+
this.manuallyStopped = true;
|
364
383
|
super.stop();
|
365
384
|
|
366
385
|
this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
|
@@ -53,6 +53,15 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
53
53
|
// a missing `getParameter` call.
|
54
54
|
private senderLock: Mutex;
|
55
55
|
|
56
|
+
private degradationPreference: RTCDegradationPreference = 'balanced';
|
57
|
+
|
58
|
+
override set sender(sender: RTCRtpSender | undefined) {
|
59
|
+
this._sender = sender;
|
60
|
+
if (this.degradationPreference) {
|
61
|
+
this.setDegradationPreference(this.degradationPreference);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
56
65
|
/**
|
57
66
|
*
|
58
67
|
* @param mediaTrack
|
@@ -273,6 +282,20 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
273
282
|
}
|
274
283
|
}
|
275
284
|
|
285
|
+
async setDegradationPreference(preference: RTCDegradationPreference) {
|
286
|
+
this.degradationPreference = preference;
|
287
|
+
if (this.sender) {
|
288
|
+
try {
|
289
|
+
this.log.debug(`setting degradationPreference to ${preference}`, this.logContext);
|
290
|
+
const params = this.sender.getParameters();
|
291
|
+
params.degradationPreference = preference;
|
292
|
+
this.sender.setParameters(params);
|
293
|
+
} catch (e: any) {
|
294
|
+
this.log.warn(`failed to set degradationPreference`, { error: e, ...this.logContext });
|
295
|
+
}
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
276
299
|
addSimulcastTrack(
|
277
300
|
codec: VideoCodec,
|
278
301
|
encodings?: RTCRtpEncodingParameters[],
|
@@ -2,6 +2,7 @@ import { TrackEvent } from '../events';
|
|
2
2
|
import { monitorFrequency } from '../stats';
|
3
3
|
import type { LoggerOptions } from '../types';
|
4
4
|
import { Track } from './Track';
|
5
|
+
import { supportsSynchronizationSources } from './utils';
|
5
6
|
|
6
7
|
export default abstract class RemoteTrack<
|
7
8
|
TrackKind extends Track.Kind = Track.Kind,
|
@@ -77,7 +78,9 @@ export default abstract class RemoteTrack<
|
|
77
78
|
if (!this.monitorInterval) {
|
78
79
|
this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency);
|
79
80
|
}
|
80
|
-
|
81
|
+
if (supportsSynchronizationSources()) {
|
82
|
+
this.registerTimeSyncUpdate();
|
83
|
+
}
|
81
84
|
}
|
82
85
|
|
83
86
|
protected abstract monitorReceiver(): void;
|
@@ -85,10 +88,13 @@ export default abstract class RemoteTrack<
|
|
85
88
|
registerTimeSyncUpdate() {
|
86
89
|
const loop = () => {
|
87
90
|
this.timeSyncHandle = requestAnimationFrame(() => loop());
|
88
|
-
const
|
89
|
-
if (
|
90
|
-
|
91
|
-
this.rtpTimestamp
|
91
|
+
const sources = this.receiver?.getSynchronizationSources()[0];
|
92
|
+
if (sources) {
|
93
|
+
const { timestamp, rtpTimestamp } = sources;
|
94
|
+
if (rtpTimestamp && this.rtpTimestamp !== rtpTimestamp) {
|
95
|
+
this.emit(TrackEvent.TimeSyncUpdate, { timestamp, rtpTimestamp });
|
96
|
+
this.rtpTimestamp = rtpTimestamp;
|
97
|
+
}
|
92
98
|
}
|
93
99
|
};
|
94
100
|
loop();
|
package/src/room/track/Track.ts
CHANGED
@@ -525,5 +525,5 @@ export type TrackEventCallbacks = {
|
|
525
525
|
upstreamResumed: (track: any) => void;
|
526
526
|
trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
|
527
527
|
audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
|
528
|
-
timeSyncUpdate: (timestamp: number) => void;
|
528
|
+
timeSyncUpdate: (update: { timestamp: number; rtpTimestamp: number }) => void;
|
529
529
|
};
|
@@ -64,6 +64,11 @@ export interface TrackPublishDefaults {
|
|
64
64
|
*/
|
65
65
|
scalabilityMode?: ScalabilityMode;
|
66
66
|
|
67
|
+
/**
|
68
|
+
* degradation preference
|
69
|
+
*/
|
70
|
+
degradationPreference?: RTCDegradationPreference;
|
71
|
+
|
67
72
|
/**
|
68
73
|
* Up to two additional simulcast layers to publish in addition to the original
|
69
74
|
* Track.
|
package/src/room/track/utils.ts
CHANGED
@@ -239,3 +239,7 @@ export function getLogContextFromTrack(track: Track | TrackPublication): Record<
|
|
239
239
|
};
|
240
240
|
}
|
241
241
|
}
|
242
|
+
|
243
|
+
export function supportsSynchronizationSources(): boolean {
|
244
|
+
return typeof RTCRtpReceiver !== 'undefined' && 'getSynchronizationSources' in RTCRtpReceiver;
|
245
|
+
}
|
package/src/room/types.ts
CHANGED
@@ -49,7 +49,9 @@ export type SimulationScenario =
|
|
49
49
|
// to disable congestion control entirely (by setting bandwidth to 100Mbps)
|
50
50
|
| 'subscriber-bandwidth'
|
51
51
|
| 'disconnect-signal-on-resume'
|
52
|
-
| 'disconnect-signal-on-resume-no-messages'
|
52
|
+
| 'disconnect-signal-on-resume-no-messages'
|
53
|
+
// instructs the server to send a full reconnect reconnect action to the client
|
54
|
+
| 'leave-full-reconnect';
|
53
55
|
|
54
56
|
export type LoggerOptions = {
|
55
57
|
loggerName?: string;
|
package/src/room/utils.ts
CHANGED
@@ -491,8 +491,10 @@ export function isVideoCodec(maybeCodec: string): maybeCodec is VideoCodec {
|
|
491
491
|
return videoCodecs.includes(maybeCodec as VideoCodec);
|
492
492
|
}
|
493
493
|
|
494
|
-
export function unwrapConstraint(constraint: ConstrainDOMString): string
|
495
|
-
|
494
|
+
export function unwrapConstraint(constraint: ConstrainDOMString): string;
|
495
|
+
export function unwrapConstraint(constraint: ConstrainULong): number;
|
496
|
+
export function unwrapConstraint(constraint: ConstrainDOMString | ConstrainULong): string | number {
|
497
|
+
if (typeof constraint === 'string' || typeof constraint === 'number') {
|
496
498
|
return constraint;
|
497
499
|
}
|
498
500
|
|
package/src/version.ts
CHANGED