livekit-client 2.15.7 → 2.15.9
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.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +253 -118
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2442 -323
- 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 +31 -2
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/api/WebSocketStream.d.ts +29 -0
- package/dist/src/api/WebSocketStream.d.ts.map +1 -0
- package/dist/src/api/utils.d.ts +2 -0
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +16 -2
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +35 -1
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +2 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +15 -0
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -0
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +3 -2
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +6 -6
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/index.d.ts +5 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +1 -0
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/options.d.ts +10 -2
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +6 -4
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +6 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -2
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -2
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +2 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +2 -2
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/token-source/TokenSource.d.ts +70 -0
- package/dist/src/room/token-source/TokenSource.d.ts.map +1 -0
- package/dist/src/room/token-source/types.d.ts +68 -0
- package/dist/src/room/token-source/types.d.ts.map +1 -0
- package/dist/src/room/token-source/utils.d.ts +5 -0
- package/dist/src/room/token-source/utils.d.ts.map +1 -0
- package/dist/src/room/track/LocalTrack.d.ts +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +7 -3
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +8 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/camelToSnakeCase.d.ts +8 -0
- package/dist/src/utils/camelToSnakeCase.d.ts.map +1 -0
- package/dist/ts4.2/{src/api → api}/SignalClient.d.ts +31 -2
- package/dist/ts4.2/api/WebSocketStream.d.ts +29 -0
- package/dist/ts4.2/{src/api → api}/utils.d.ts +2 -0
- package/dist/ts4.2/{src/e2ee → e2ee}/E2eeManager.d.ts +16 -2
- package/dist/ts4.2/{src/e2ee → e2ee}/types.d.ts +35 -1
- package/dist/ts4.2/{src/e2ee → e2ee}/utils.d.ts +3 -0
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +15 -0
- package/dist/ts4.2/{src/e2ee → e2ee}/worker/ParticipantKeyHandler.d.ts +3 -2
- package/dist/ts4.2/{src/e2ee → e2ee}/worker/sifPayload.d.ts +6 -6
- package/dist/ts4.2/{src/index.d.ts → index.d.ts} +5 -3
- package/dist/ts4.2/{src/logger.d.ts → logger.d.ts} +1 -0
- package/dist/ts4.2/{src/options.d.ts → options.d.ts} +10 -2
- package/dist/ts4.2/{src/room → room}/PCTransport.d.ts +1 -0
- package/dist/ts4.2/{src/room → room}/PCTransportManager.d.ts +6 -4
- package/dist/ts4.2/{src/room → room}/RTCEngine.d.ts +6 -3
- package/dist/ts4.2/{src/room → room}/Room.d.ts +3 -2
- package/dist/ts4.2/{src/room → room}/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -1
- package/dist/ts4.2/{src/room → room}/errors.d.ts +2 -1
- package/dist/ts4.2/{src/room → room}/participant/Participant.d.ts +2 -2
- package/dist/ts4.2/room/token-source/TokenSource.d.ts +71 -0
- package/dist/ts4.2/room/token-source/types.d.ts +68 -0
- package/dist/ts4.2/room/token-source/utils.d.ts +5 -0
- package/dist/ts4.2/{src/room → room}/track/LocalTrack.d.ts +1 -1
- package/dist/ts4.2/{src/room → room}/track/options.d.ts +10 -3
- package/dist/ts4.2/{src/room → room}/types.d.ts +1 -0
- package/dist/ts4.2/{src/room → room}/utils.d.ts +8 -1
- package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -0
- package/package.json +11 -10
- package/src/api/SignalClient.test.ts +688 -0
- package/src/api/SignalClient.ts +308 -161
- package/src/api/WebSocketStream.test.ts +625 -0
- package/src/api/WebSocketStream.ts +118 -0
- package/src/api/utils.ts +10 -0
- package/src/connectionHelper/checks/publishVideo.ts +5 -0
- package/src/connectionHelper/checks/turn.ts +1 -0
- package/src/connectionHelper/checks/webrtc.ts +1 -1
- package/src/connectionHelper/checks/websocket.ts +1 -0
- package/src/e2ee/E2eeManager.ts +94 -2
- package/src/e2ee/types.ts +44 -1
- package/src/e2ee/utils.ts +16 -0
- package/src/e2ee/worker/DataCryptor.test.ts +271 -0
- package/src/e2ee/worker/DataCryptor.ts +147 -0
- package/src/e2ee/worker/ParticipantKeyHandler.ts +4 -3
- package/src/e2ee/worker/e2ee.worker.ts +47 -0
- package/src/e2ee/worker/sifPayload.ts +10 -6
- package/src/index.ts +16 -1
- package/src/logger.ts +1 -0
- package/src/options.ts +15 -2
- package/src/room/PCTransport.ts +7 -3
- package/src/room/PCTransportManager.ts +39 -35
- package/src/room/RTCEngine.ts +109 -22
- package/src/room/Room.ts +43 -18
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +64 -17
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -0
- package/src/room/defaults.ts +1 -0
- package/src/room/errors.ts +3 -0
- package/src/room/participant/LocalParticipant.ts +8 -6
- package/src/room/participant/Participant.ts +6 -1
- package/src/room/token-source/TokenSource.ts +285 -0
- package/src/room/token-source/types.ts +84 -0
- package/src/room/token-source/utils.test.ts +63 -0
- package/src/room/token-source/utils.ts +40 -0
- package/src/room/track/LocalAudioTrack.ts +1 -1
- package/src/room/track/LocalTrack.ts +1 -1
- package/src/room/track/options.ts +12 -4
- package/src/room/track/utils.ts +10 -2
- package/src/room/types.ts +1 -0
- package/src/room/utils.ts +37 -4
- package/src/utils/camelToSnakeCase.ts +16 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/ConnectionCheck.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/Checker.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/cloudRegion.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/connectionProtocol.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishAudio.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishVideo.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/reconnect.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/turn.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/webrtc.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/websocket.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/KeyProvider.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/constants.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/errors.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/events.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/index.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/worker/FrameCryptor.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/worker/e2ee.worker.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/worker/naluUtils.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/DefaultReconnectPolicy.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/DeviceManager.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/ReconnectPolicy.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/RegionUrlProvider.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/attribute-typings.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/data-stream/incoming/StreamReader.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/StreamWriter.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/defaults.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/events.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/LocalParticipant.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/ParticipantTrackPermission.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/RemoteParticipant.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/publishUtils.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/rpc.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/stats.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/timers.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/LocalAudioTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/LocalTrackPublication.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/LocalVideoTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteAudioTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteTrackPublication.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteVideoTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/Track.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/TrackPublication.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/create.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/facingMode.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/processor/types.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/record.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/types.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/utils.d.ts +0 -0
- /package/dist/ts4.2/{src/test → test}/MockMediaStreamTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/test → test}/mocks.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/AsyncQueue.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/browserParser.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/cloneDeep.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/dataPacketBuffer.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/ttlmap.d.ts +0 -0
- /package/dist/ts4.2/{src/version.d.ts → version.d.ts} +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Mutex } from '@livekit/mutex';
|
|
2
|
+
import {
|
|
3
|
+
RoomAgentDispatch,
|
|
4
|
+
RoomConfiguration,
|
|
5
|
+
TokenSourceRequest,
|
|
6
|
+
TokenSourceResponse,
|
|
7
|
+
} from '@livekit/protocol';
|
|
8
|
+
import {
|
|
9
|
+
TokenSourceConfigurable,
|
|
10
|
+
type TokenSourceFetchOptions,
|
|
11
|
+
TokenSourceFixed,
|
|
12
|
+
type TokenSourceResponseObject,
|
|
13
|
+
} from './types';
|
|
14
|
+
import { decodeTokenPayload, isResponseTokenValid } from './utils';
|
|
15
|
+
|
|
16
|
+
/** A TokenSourceCached is a TokenSource which caches the last {@link TokenSourceResponseObject} value and returns it
|
|
17
|
+
* until a) it expires or b) the {@link TokenSourceFetchOptions} provided to .fetch(...) change. */
|
|
18
|
+
abstract class TokenSourceCached extends TokenSourceConfigurable {
|
|
19
|
+
private cachedFetchOptions: TokenSourceFetchOptions | null = null;
|
|
20
|
+
|
|
21
|
+
private cachedResponse: TokenSourceResponse | null = null;
|
|
22
|
+
|
|
23
|
+
private fetchMutex = new Mutex();
|
|
24
|
+
|
|
25
|
+
private isSameAsCachedFetchOptions(options: TokenSourceFetchOptions) {
|
|
26
|
+
if (!this.cachedFetchOptions) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const key of Object.keys(this.cachedFetchOptions) as Array<
|
|
31
|
+
keyof TokenSourceFetchOptions
|
|
32
|
+
>) {
|
|
33
|
+
switch (key) {
|
|
34
|
+
case 'roomName':
|
|
35
|
+
case 'participantName':
|
|
36
|
+
case 'participantIdentity':
|
|
37
|
+
case 'participantMetadata':
|
|
38
|
+
case 'participantAttributes':
|
|
39
|
+
case 'agentName':
|
|
40
|
+
case 'agentMetadata':
|
|
41
|
+
if (this.cachedFetchOptions[key] !== options[key]) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
// ref: https://stackoverflow.com/a/58009992
|
|
47
|
+
const exhaustiveCheckedKey: never = key;
|
|
48
|
+
throw new Error(`Options key ${exhaustiveCheckedKey} not being checked for equality!`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private shouldReturnCachedValueFromFetch(fetchOptions: TokenSourceFetchOptions) {
|
|
56
|
+
if (!this.cachedResponse) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (!isResponseTokenValid(this.cachedResponse)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (this.isSameAsCachedFetchOptions(fetchOptions)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getCachedResponseJwtPayload() {
|
|
69
|
+
if (!this.cachedResponse) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return decodeTokenPayload(this.cachedResponse.participantToken);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async fetch(options: TokenSourceFetchOptions): Promise<TokenSourceResponseObject> {
|
|
76
|
+
const unlock = await this.fetchMutex.lock();
|
|
77
|
+
try {
|
|
78
|
+
if (this.shouldReturnCachedValueFromFetch(options)) {
|
|
79
|
+
return this.cachedResponse!.toJson() as TokenSourceResponseObject;
|
|
80
|
+
}
|
|
81
|
+
this.cachedFetchOptions = options;
|
|
82
|
+
|
|
83
|
+
const tokenResponse = await this.update(options);
|
|
84
|
+
this.cachedResponse = tokenResponse;
|
|
85
|
+
return tokenResponse.toJson() as TokenSourceResponseObject;
|
|
86
|
+
} finally {
|
|
87
|
+
unlock();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected abstract update(options: TokenSourceFetchOptions): Promise<TokenSourceResponse>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type LiteralOrFn =
|
|
95
|
+
| TokenSourceResponseObject
|
|
96
|
+
| (() => TokenSourceResponseObject | Promise<TokenSourceResponseObject>);
|
|
97
|
+
export class TokenSourceLiteral extends TokenSourceFixed {
|
|
98
|
+
private literalOrFn: LiteralOrFn;
|
|
99
|
+
|
|
100
|
+
constructor(literalOrFn: LiteralOrFn) {
|
|
101
|
+
super();
|
|
102
|
+
this.literalOrFn = literalOrFn;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async fetch(): Promise<TokenSourceResponseObject> {
|
|
106
|
+
if (typeof this.literalOrFn === 'function') {
|
|
107
|
+
return this.literalOrFn();
|
|
108
|
+
} else {
|
|
109
|
+
return this.literalOrFn;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type CustomFn = (
|
|
115
|
+
options: TokenSourceFetchOptions,
|
|
116
|
+
) => TokenSourceResponseObject | Promise<TokenSourceResponseObject>;
|
|
117
|
+
export class TokenSourceCustom extends TokenSourceCached {
|
|
118
|
+
private customFn: CustomFn;
|
|
119
|
+
|
|
120
|
+
constructor(customFn: CustomFn) {
|
|
121
|
+
super();
|
|
122
|
+
this.customFn = customFn;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
protected async update(options: TokenSourceFetchOptions) {
|
|
126
|
+
const resultMaybePromise = this.customFn(options);
|
|
127
|
+
|
|
128
|
+
let result;
|
|
129
|
+
if (resultMaybePromise instanceof Promise) {
|
|
130
|
+
result = await resultMaybePromise;
|
|
131
|
+
} else {
|
|
132
|
+
result = resultMaybePromise;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return TokenSourceResponse.fromJson(result, {
|
|
136
|
+
// NOTE: it could be possible that the response body could contain more fields than just
|
|
137
|
+
// what's in TokenSourceResponse depending on the implementation
|
|
138
|
+
ignoreUnknownFields: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type EndpointOptions = Omit<RequestInit, 'body'>;
|
|
144
|
+
|
|
145
|
+
export class TokenSourceEndpoint extends TokenSourceCached {
|
|
146
|
+
private url: string;
|
|
147
|
+
|
|
148
|
+
private endpointOptions: EndpointOptions;
|
|
149
|
+
|
|
150
|
+
constructor(url: string, options: EndpointOptions = {}) {
|
|
151
|
+
super();
|
|
152
|
+
this.url = url;
|
|
153
|
+
this.endpointOptions = options;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private createRequestFromOptions(options: TokenSourceFetchOptions) {
|
|
157
|
+
const request = new TokenSourceRequest();
|
|
158
|
+
|
|
159
|
+
for (const key of Object.keys(options) as Array<keyof TokenSourceFetchOptions>) {
|
|
160
|
+
switch (key) {
|
|
161
|
+
case 'roomName':
|
|
162
|
+
case 'participantName':
|
|
163
|
+
case 'participantIdentity':
|
|
164
|
+
case 'participantMetadata':
|
|
165
|
+
request[key] = options[key];
|
|
166
|
+
break;
|
|
167
|
+
|
|
168
|
+
case 'participantAttributes':
|
|
169
|
+
request.participantAttributes = options.participantAttributes ?? {};
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case 'agentName':
|
|
173
|
+
request.roomConfig = request.roomConfig ?? new RoomConfiguration();
|
|
174
|
+
if (request.roomConfig.agents.length === 0) {
|
|
175
|
+
request.roomConfig.agents.push(new RoomAgentDispatch());
|
|
176
|
+
}
|
|
177
|
+
request.roomConfig.agents[0].agentName = options.agentName!;
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case 'agentMetadata':
|
|
181
|
+
request.roomConfig = request.roomConfig ?? new RoomConfiguration();
|
|
182
|
+
if (request.roomConfig.agents.length === 0) {
|
|
183
|
+
request.roomConfig.agents.push(new RoomAgentDispatch());
|
|
184
|
+
}
|
|
185
|
+
request.roomConfig.agents[0].metadata = options.agentMetadata!;
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
default:
|
|
189
|
+
// ref: https://stackoverflow.com/a/58009992
|
|
190
|
+
const exhaustiveCheckedKey: never = key;
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Options key ${exhaustiveCheckedKey} not being included in forming request!`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return request;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected async update(options: TokenSourceFetchOptions) {
|
|
201
|
+
const request = this.createRequestFromOptions(options);
|
|
202
|
+
|
|
203
|
+
const response = await fetch(this.url, {
|
|
204
|
+
...this.endpointOptions,
|
|
205
|
+
method: this.endpointOptions.method ?? 'POST',
|
|
206
|
+
headers: {
|
|
207
|
+
'Content-Type': 'application/json',
|
|
208
|
+
...this.endpointOptions.headers,
|
|
209
|
+
},
|
|
210
|
+
body: request.toJsonString({
|
|
211
|
+
useProtoFieldName: true,
|
|
212
|
+
}),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Error generating token from endpoint ${this.url}: received ${response.status} / ${await response.text()}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const body = await response.json();
|
|
222
|
+
return TokenSourceResponse.fromJson(body, {
|
|
223
|
+
// NOTE: it could be possible that the response body could contain more fields than just
|
|
224
|
+
// what's in TokenSourceResponse depending on the implementation (ie, SandboxTokenServer)
|
|
225
|
+
ignoreUnknownFields: true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export type SandboxTokenServerOptions = {
|
|
231
|
+
baseUrl?: string;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export class TokenSourceSandboxTokenServer extends TokenSourceEndpoint {
|
|
235
|
+
constructor(sandboxId: string, options: SandboxTokenServerOptions) {
|
|
236
|
+
const { baseUrl = 'https://cloud-api.livekit.io', ...rest } = options;
|
|
237
|
+
|
|
238
|
+
super(`${baseUrl}/api/v2/sandbox/connection-details`, {
|
|
239
|
+
...rest,
|
|
240
|
+
headers: {
|
|
241
|
+
'X-Sandbox-ID': sandboxId,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const TokenSource = {
|
|
248
|
+
/** TokenSource.literal contains a single, literal set of {@link TokenSourceResponseObject}
|
|
249
|
+
* credentials, either provided directly or returned from a provided function. */
|
|
250
|
+
literal(literalOrFn: LiteralOrFn) {
|
|
251
|
+
return new TokenSourceLiteral(literalOrFn);
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* TokenSource.custom allows a user to define a manual function which generates new
|
|
256
|
+
* {@link TokenSourceResponseObject} values on demand.
|
|
257
|
+
*
|
|
258
|
+
* Use this to get credentials from custom backends / etc.
|
|
259
|
+
*/
|
|
260
|
+
custom(customFn: CustomFn) {
|
|
261
|
+
return new TokenSourceCustom(customFn);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* TokenSource.endpoint creates a token source that fetches credentials from a given URL using
|
|
266
|
+
* the standard endpoint format:
|
|
267
|
+
* FIXME: add docs link here in the future!
|
|
268
|
+
*/
|
|
269
|
+
endpoint(url: string, options: EndpointOptions = {}) {
|
|
270
|
+
return new TokenSourceEndpoint(url, options);
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* TokenSource.sandboxTokenServer queries a sandbox token server for credentials,
|
|
275
|
+
* which supports quick prototyping / getting started types of use cases.
|
|
276
|
+
*
|
|
277
|
+
* This token provider is INSECURE and should NOT be used in production.
|
|
278
|
+
*
|
|
279
|
+
* For more info:
|
|
280
|
+
* @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server
|
|
281
|
+
*/
|
|
282
|
+
sandboxTokenServer(sandboxId: string, options: SandboxTokenServerOptions = {}) {
|
|
283
|
+
return new TokenSourceSandboxTokenServer(sandboxId, options);
|
|
284
|
+
},
|
|
285
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { RoomConfiguration, TokenSourceRequest, TokenSourceResponse } from '@livekit/protocol';
|
|
2
|
+
import type { JWTPayload } from 'jose';
|
|
3
|
+
import type { ValueToSnakeCase } from '../../utils/camelToSnakeCase';
|
|
4
|
+
// The below imports are being linked in tsdoc comments, so they have to be imported even if they
|
|
5
|
+
// aren't being used.
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
|
+
import type { TokenSourceCustom, TokenSourceEndpoint, TokenSourceLiteral } from './TokenSource';
|
|
8
|
+
|
|
9
|
+
export type TokenSourceRequestObject = Required<
|
|
10
|
+
NonNullable<ConstructorParameters<typeof TokenSourceRequest>[0]>
|
|
11
|
+
>;
|
|
12
|
+
export type TokenSourceResponseObject = Required<
|
|
13
|
+
NonNullable<ConstructorParameters<typeof TokenSourceResponse>[0]>
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
/** The `TokenSource` request object sent to the server as part of fetching a configurable
|
|
17
|
+
* `TokenSource` like {@link TokenSourceEndpoint}.
|
|
18
|
+
*
|
|
19
|
+
* Use this as a type for your request body if implementing a server endpoint in node.js.
|
|
20
|
+
*/
|
|
21
|
+
export type TokenSourceRequestPayload = ValueToSnakeCase<TokenSourceRequestObject>;
|
|
22
|
+
|
|
23
|
+
/** The `TokenSource` response object sent from the server as part of fetching a configurable
|
|
24
|
+
* `TokenSource` like {@link TokenSourceEndpoint}.
|
|
25
|
+
*
|
|
26
|
+
* Use this as a type for your response body if implementing a server endpoint in node.js.
|
|
27
|
+
*/
|
|
28
|
+
export type TokenSourceResponsePayload = ValueToSnakeCase<TokenSourceResponseObject>;
|
|
29
|
+
|
|
30
|
+
/** The payload of a LiveKit JWT token. */
|
|
31
|
+
export type TokenPayload = JWTPayload & {
|
|
32
|
+
name?: string;
|
|
33
|
+
metadata?: string;
|
|
34
|
+
attributes?: Record<string, string>;
|
|
35
|
+
video?: {
|
|
36
|
+
room?: string;
|
|
37
|
+
roomJoin?: boolean;
|
|
38
|
+
canPublish?: boolean;
|
|
39
|
+
canPublishData?: boolean;
|
|
40
|
+
canSubscribe?: boolean;
|
|
41
|
+
};
|
|
42
|
+
roomConfig?: RoomConfigurationObject;
|
|
43
|
+
};
|
|
44
|
+
export type RoomConfigurationObject = NonNullable<
|
|
45
|
+
ConstructorParameters<typeof RoomConfiguration>[0]
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
/** A Fixed TokenSource is a token source that takes no parameters and returns a completely
|
|
49
|
+
* independently derived value on each fetch() call.
|
|
50
|
+
*
|
|
51
|
+
* The most common downstream implementer is {@link TokenSourceLiteral}.
|
|
52
|
+
*/
|
|
53
|
+
export abstract class TokenSourceFixed {
|
|
54
|
+
abstract fetch(): Promise<TokenSourceResponseObject>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type TokenSourceFetchOptions = {
|
|
58
|
+
roomName?: string;
|
|
59
|
+
participantName?: string;
|
|
60
|
+
participantIdentity?: string;
|
|
61
|
+
participantMetadata?: string;
|
|
62
|
+
participantAttributes?: { [key: string]: string };
|
|
63
|
+
|
|
64
|
+
agentName?: string;
|
|
65
|
+
agentMetadata?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** A Configurable TokenSource is a token source that takes a
|
|
69
|
+
* {@link TokenSourceFetchOptions} object as input and returns a deterministic
|
|
70
|
+
* {@link TokenSourceResponseObject} output based on the options specified.
|
|
71
|
+
*
|
|
72
|
+
* For example, if options.participantName is set, it should be expected that
|
|
73
|
+
* all tokens that are generated will have participant name field set to the
|
|
74
|
+
* provided value.
|
|
75
|
+
*
|
|
76
|
+
* A few common downstream implementers are {@link TokenSourceEndpoint}
|
|
77
|
+
* and {@link TokenSourceCustom}.
|
|
78
|
+
*/
|
|
79
|
+
export abstract class TokenSourceConfigurable {
|
|
80
|
+
abstract fetch(options: TokenSourceFetchOptions): Promise<TokenSourceResponseObject>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A TokenSource is a mechanism for fetching credentials required to connect to a LiveKit Room. */
|
|
84
|
+
export type TokenSourceBase = TokenSourceFixed | TokenSourceConfigurable;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { TokenSourceResponse } from '@livekit/protocol';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { decodeTokenPayload, isResponseTokenValid } from './utils';
|
|
4
|
+
|
|
5
|
+
// Test JWTs created for test purposes only.
|
|
6
|
+
// None of these actually auth against anything.
|
|
7
|
+
const TOKENS = {
|
|
8
|
+
// Nbf date set at 1234567890 seconds (Fri Feb 13 2009 23:31:30 GMT+0000)
|
|
9
|
+
// Exp date set at 9876543210 seconds (Fri Dec 22 2282 20:13:30 GMT+0000)
|
|
10
|
+
// A dummy roomConfig value is also set, with room_config.name = "test room name", and room_config.agents = [{"agentName": "test agent name","metadata":"test agent metadata"}]
|
|
11
|
+
VALID:
|
|
12
|
+
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjo5ODc2NTQzMjEwLCJuYmYiOjEyMzQ1Njc4OTAsImlhdCI6MTIzNDU2Nzg5MCwicm9vbUNvbmZpZyI6eyJuYW1lIjoidGVzdCByb29tIG5hbWUiLCJlbXB0eVRpbWVvdXQiOjAsImRlcGFydHVyZVRpbWVvdXQiOjAsIm1heFBhcnRpY2lwYW50cyI6MCwibWluUGxheW91dERlbGF5IjowLCJtYXhQbGF5b3V0RGVsYXkiOjAsInN5bmNTdHJlYW1zIjpmYWxzZSwiYWdlbnRzIjpbeyJhZ2VudE5hbWUiOiJ0ZXN0IGFnZW50IG5hbWUiLCJtZXRhZGF0YSI6InRlc3QgYWdlbnQgbWV0YWRhdGEifV0sIm1ldGFkYXRhIjoiIn19.EDetpHG8cSubaApzgWJaQrpCiSy9KDBlfCfVdIydbQ-_CHiNnXOK_f_mCJbTf9A-duT1jmvPOkLrkkWFT60XPQ',
|
|
13
|
+
|
|
14
|
+
// Nbf date set at 9876543210 seconds (Fri Dec 22 2282 20:13:30 GMT+0000)
|
|
15
|
+
// Exp date set at 9876543211 seconds (Fri Dec 22 2282 20:13:31 GMT+0000)
|
|
16
|
+
NBF_IN_FUTURE:
|
|
17
|
+
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjo5ODc2NTQzMjExLCJuYmYiOjk4NzY1NDMyMTAsImlhdCI6MTIzNDU2Nzg5MH0.DcMmdKrD76eJg7IUBZqoTRDvBaXtCcwtuE5h7IwVXhG_6nvgxN_ix30_AmLgnYhvhkN-x9dTRPoHg-CME72AbQ',
|
|
18
|
+
|
|
19
|
+
// Nbf date set at 1234567890 seconds (Fri Feb 13 2009 23:31:30 GMT+0000)
|
|
20
|
+
// Exp date set at 1234567891 seconds (Fri Feb 13 2009 23:31:31 GMT+0000)
|
|
21
|
+
EXP_IN_PAST:
|
|
22
|
+
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxMjM0NTY3ODkxLCJuYmYiOjEyMzQ1Njc4OTAsImlhdCI6MTIzNDU2Nzg5MH0.OYP1NITayotBYt0mioInLJmaIM0bHyyR-yG6iwKyQDzhoGha15qbsc7dOJlzz4za1iW5EzCgjc2_xGxqaSu5XA',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('isResponseTokenValid', () => {
|
|
26
|
+
it('should find a valid jwt not expired', () => {
|
|
27
|
+
const isValid = isResponseTokenValid(
|
|
28
|
+
TokenSourceResponse.fromJson({
|
|
29
|
+
serverUrl: 'ws://localhost:7800',
|
|
30
|
+
participantToken: TOKENS.VALID,
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
expect(isValid).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('should find a long ago expired jwt as expired', () => {
|
|
36
|
+
const isValid = isResponseTokenValid(
|
|
37
|
+
TokenSourceResponse.fromJson({
|
|
38
|
+
serverUrl: 'ws://localhost:7800',
|
|
39
|
+
participantToken: TOKENS.EXP_IN_PAST,
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
expect(isValid).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it('should find a jwt that has not become active yet as expired', () => {
|
|
45
|
+
const isValid = isResponseTokenValid(
|
|
46
|
+
TokenSourceResponse.fromJson({
|
|
47
|
+
serverUrl: 'ws://localhost:7800',
|
|
48
|
+
participantToken: TOKENS.NBF_IN_FUTURE,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
expect(isValid).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('decodeTokenPayload', () => {
|
|
56
|
+
it('should extract roomconfig metadata from a token', () => {
|
|
57
|
+
const payload = decodeTokenPayload(TOKENS.VALID);
|
|
58
|
+
expect(payload.roomConfig?.name).toBe('test room name');
|
|
59
|
+
expect(payload.roomConfig?.agents).toHaveLength(1);
|
|
60
|
+
expect(payload.roomConfig?.agents![0].agentName).toBe('test agent name');
|
|
61
|
+
expect(payload.roomConfig?.agents![0].metadata).toBe('test agent metadata');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { RoomConfiguration, type TokenSourceResponse } from '@livekit/protocol';
|
|
2
|
+
import { decodeJwt } from 'jose';
|
|
3
|
+
import type { RoomConfigurationObject, TokenPayload } from './types';
|
|
4
|
+
|
|
5
|
+
const ONE_SECOND_IN_MILLISECONDS = 1000;
|
|
6
|
+
const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS;
|
|
7
|
+
|
|
8
|
+
export function isResponseTokenValid(response: TokenSourceResponse) {
|
|
9
|
+
const jwtPayload = decodeTokenPayload(response.participantToken);
|
|
10
|
+
if (!jwtPayload?.nbf || !jwtPayload?.exp) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const now = new Date();
|
|
15
|
+
|
|
16
|
+
const nbfInMilliseconds = jwtPayload.nbf * ONE_SECOND_IN_MILLISECONDS;
|
|
17
|
+
const nbfDate = new Date(nbfInMilliseconds);
|
|
18
|
+
|
|
19
|
+
const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS;
|
|
20
|
+
const expDate = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS);
|
|
21
|
+
|
|
22
|
+
return nbfDate <= now && expDate > now;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function decodeTokenPayload(token: string) {
|
|
26
|
+
const payload = decodeJwt<Omit<TokenPayload, 'roomConfig'>>(token);
|
|
27
|
+
|
|
28
|
+
const { roomConfig, ...rest } = payload;
|
|
29
|
+
|
|
30
|
+
const mappedPayload: TokenPayload = {
|
|
31
|
+
...rest,
|
|
32
|
+
roomConfig: payload.roomConfig
|
|
33
|
+
? (RoomConfiguration.fromJson(
|
|
34
|
+
payload.roomConfig as Record<string, any>,
|
|
35
|
+
) as RoomConfigurationObject)
|
|
36
|
+
: undefined,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return mappedPayload;
|
|
40
|
+
}
|
|
@@ -244,7 +244,7 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
|
244
244
|
const trackIsSilent = await detectSilence(this);
|
|
245
245
|
if (trackIsSilent) {
|
|
246
246
|
if (!this.isMuted) {
|
|
247
|
-
this.log.
|
|
247
|
+
this.log.debug('silence detected on local audio track', this.logContext);
|
|
248
248
|
}
|
|
249
249
|
this.emit(TrackEvent.AudioSilenceDetected);
|
|
250
250
|
}
|
|
@@ -390,18 +390,26 @@ export interface AudioPreset {
|
|
|
390
390
|
priority?: RTCPriorityType;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
// `red` is not technically a codec, but treated as one in signalling protocol
|
|
394
|
+
export const audioCodecs = ['opus', 'red'] as const;
|
|
395
|
+
|
|
396
|
+
export type AudioCodec = (typeof audioCodecs)[number];
|
|
397
|
+
|
|
398
|
+
const backupVideoCodecs = ['vp8', 'h264'] as const;
|
|
394
399
|
|
|
395
400
|
export const videoCodecs = ['vp8', 'h264', 'vp9', 'av1', 'h265'] as const;
|
|
396
401
|
|
|
397
402
|
export type VideoCodec = (typeof videoCodecs)[number];
|
|
398
403
|
|
|
399
|
-
export type BackupVideoCodec = (typeof
|
|
404
|
+
export type BackupVideoCodec = (typeof backupVideoCodecs)[number];
|
|
400
405
|
|
|
401
|
-
export function
|
|
402
|
-
return !!
|
|
406
|
+
export function isBackupVideoCodec(codec: string): codec is BackupVideoCodec {
|
|
407
|
+
return !!backupVideoCodecs.find((backup) => backup === codec);
|
|
403
408
|
}
|
|
404
409
|
|
|
410
|
+
/** @deprecated Use {@link isBackupVideoCodec} instead */
|
|
411
|
+
export const isBackupCodec = isBackupVideoCodec;
|
|
412
|
+
|
|
405
413
|
export enum BackupCodecPolicy {
|
|
406
414
|
// codec regression is preferred, the sfu will try to regress codec if possible but not guaranteed
|
|
407
415
|
PREFER_REGRESSION = 0,
|
package/src/room/track/utils.ts
CHANGED
|
@@ -147,10 +147,18 @@ export function getNewAudioContext(): AudioContext | void {
|
|
|
147
147
|
}
|
|
148
148
|
} catch (e) {
|
|
149
149
|
console.warn('Error trying to auto-resume audio context', e);
|
|
150
|
+
} finally {
|
|
151
|
+
window.document.body?.removeEventListener('click', handleResume);
|
|
150
152
|
}
|
|
151
|
-
|
|
152
|
-
window.document.body?.removeEventListener('click', handleResume);
|
|
153
153
|
};
|
|
154
|
+
|
|
155
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/statechange_event
|
|
156
|
+
audioContext.addEventListener('statechange', () => {
|
|
157
|
+
if (audioContext.state === 'closed') {
|
|
158
|
+
window.document.body?.removeEventListener('click', handleResume);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
154
162
|
window.document.body.addEventListener('click', handleResume);
|
|
155
163
|
}
|
|
156
164
|
return audioContext;
|
package/src/room/types.ts
CHANGED
|
@@ -136,6 +136,7 @@ export interface BaseStreamInfo {
|
|
|
136
136
|
/** total size in bytes for finite streams and undefined for streams of unknown size */
|
|
137
137
|
size?: number;
|
|
138
138
|
attributes?: Record<string, string>;
|
|
139
|
+
encryptionType: Encryption_Type;
|
|
139
140
|
}
|
|
140
141
|
export interface ByteStreamInfo extends BaseStreamInfo {
|
|
141
142
|
name: string;
|
package/src/room/utils.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type RemoteTrackPublication from './track/RemoteTrackPublication';
|
|
|
22
22
|
import type RemoteVideoTrack from './track/RemoteVideoTrack';
|
|
23
23
|
import { Track } from './track/Track';
|
|
24
24
|
import type { TrackPublication } from './track/TrackPublication';
|
|
25
|
-
import { type VideoCodec, videoCodecs } from './track/options';
|
|
25
|
+
import { type AudioCodec, type VideoCodec, audioCodecs, videoCodecs } from './track/options';
|
|
26
26
|
import { getNewAudioContext } from './track/utils';
|
|
27
27
|
import type { ChatMessage, LiveKitReactNativeInfo, TranscriptionSegment } from './types';
|
|
28
28
|
|
|
@@ -73,7 +73,7 @@ export function supportsAV1(): boolean {
|
|
|
73
73
|
let hasAV1 = false;
|
|
74
74
|
if (capabilities) {
|
|
75
75
|
for (const codec of capabilities.codecs) {
|
|
76
|
-
if (codec.mimeType === 'video/
|
|
76
|
+
if (codec.mimeType.toLowerCase() === 'video/av1') {
|
|
77
77
|
hasAV1 = true;
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
@@ -110,7 +110,7 @@ export function supportsVP9(): boolean {
|
|
|
110
110
|
let hasVP9 = false;
|
|
111
111
|
if (capabilities) {
|
|
112
112
|
for (const codec of capabilities.codecs) {
|
|
113
|
-
if (codec.mimeType === 'video/
|
|
113
|
+
if (codec.mimeType.toLowerCase() === 'video/vp9') {
|
|
114
114
|
hasVP9 = true;
|
|
115
115
|
break;
|
|
116
116
|
}
|
|
@@ -128,7 +128,7 @@ export function supportsH265(): boolean {
|
|
|
128
128
|
let hasH265 = false;
|
|
129
129
|
if (capabilities) {
|
|
130
130
|
for (const codec of capabilities.codecs) {
|
|
131
|
-
if (codec.mimeType === 'video/
|
|
131
|
+
if (codec.mimeType.toLowerCase() === 'video/h265') {
|
|
132
132
|
hasH265 = true;
|
|
133
133
|
break;
|
|
134
134
|
}
|
|
@@ -151,6 +151,15 @@ export function supportsSetSinkId(elm?: HTMLMediaElement): boolean {
|
|
|
151
151
|
return 'setSinkId' in elm;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Checks whether or not setting an audio output via {@link Room#setActiveDevice}
|
|
156
|
+
* is supported for the current browser.
|
|
157
|
+
*/
|
|
158
|
+
export function supportsAudioOutputSelection(): boolean {
|
|
159
|
+
// Note: this is method publicly exported under a user friendly name and currently only proxying `supportsSetSinkId`
|
|
160
|
+
return supportsSetSinkId();
|
|
161
|
+
}
|
|
162
|
+
|
|
154
163
|
export function isBrowserSupported() {
|
|
155
164
|
if (typeof RTCPeerConnection === 'undefined') {
|
|
156
165
|
return false;
|
|
@@ -418,6 +427,26 @@ export function getEmptyAudioStreamTrack() {
|
|
|
418
427
|
return emptyAudioStreamTrack.clone();
|
|
419
428
|
}
|
|
420
429
|
|
|
430
|
+
export function getStereoAudioStreamTrack() {
|
|
431
|
+
const ctx = new AudioContext();
|
|
432
|
+
const oscLeft = ctx.createOscillator();
|
|
433
|
+
const oscRight = ctx.createOscillator();
|
|
434
|
+
oscLeft.frequency.value = 440;
|
|
435
|
+
oscRight.frequency.value = 220;
|
|
436
|
+
const merger = ctx.createChannelMerger(2);
|
|
437
|
+
oscLeft.connect(merger, 0, 0); // left channel
|
|
438
|
+
oscRight.connect(merger, 0, 1); // right channel
|
|
439
|
+
const dst = ctx.createMediaStreamDestination();
|
|
440
|
+
merger.connect(dst);
|
|
441
|
+
oscLeft.start();
|
|
442
|
+
oscRight.start();
|
|
443
|
+
const [stereoTrack] = dst.stream.getAudioTracks();
|
|
444
|
+
if (!stereoTrack) {
|
|
445
|
+
throw Error('Could not get stereo media stream audio track');
|
|
446
|
+
}
|
|
447
|
+
return stereoTrack;
|
|
448
|
+
}
|
|
449
|
+
|
|
421
450
|
export class Future<T> {
|
|
422
451
|
promise: Promise<T>;
|
|
423
452
|
|
|
@@ -533,6 +562,10 @@ export function createAudioAnalyser(
|
|
|
533
562
|
return { calculateVolume, analyser, cleanup };
|
|
534
563
|
}
|
|
535
564
|
|
|
565
|
+
export function isAudioCodec(maybeCodec: string): maybeCodec is AudioCodec {
|
|
566
|
+
return audioCodecs.includes(maybeCodec as AudioCodec);
|
|
567
|
+
}
|
|
568
|
+
|
|
536
569
|
export function isVideoCodec(maybeCodec: string): maybeCodec is VideoCodec {
|
|
537
570
|
return videoCodecs.includes(maybeCodec as VideoCodec);
|
|
538
571
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type CamelToSnakeCase<Str extends string> = Str extends `${infer First}${infer Rest}`
|
|
2
|
+
? `${First extends Capitalize<First> ? '_' : ''}${Lowercase<First>}${CamelToSnakeCase<Rest>}`
|
|
3
|
+
: Str;
|
|
4
|
+
|
|
5
|
+
type ArrayValuesToSnakeCase<Item> = Array<ValueToSnakeCase<Item>>;
|
|
6
|
+
|
|
7
|
+
type ObjectKeysToSnakeCase<Obj> = {
|
|
8
|
+
[Key in keyof Obj as CamelToSnakeCase<string & Key>]: NonNullable<ValueToSnakeCase<Obj[Key]>>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ValueToSnakeCase<Value> =
|
|
12
|
+
Value extends Array<infer Item>
|
|
13
|
+
? ArrayValuesToSnakeCase<Item>
|
|
14
|
+
: Value extends object
|
|
15
|
+
? ObjectKeysToSnakeCase<Value>
|
|
16
|
+
: Value;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/connectionProtocol.d.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|