livekit-client 1.4.4 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/livekit-client.esm.mjs +510 -38
- 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/connectionHelper/ConnectionCheck.d.ts +25 -0
- package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -0
- package/dist/src/connectionHelper/checks/Checker.d.ts +59 -0
- package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -0
- package/dist/src/connectionHelper/checks/publishAudio.d.ts +6 -0
- package/dist/src/connectionHelper/checks/publishAudio.d.ts.map +1 -0
- package/dist/src/connectionHelper/checks/publishVideo.d.ts +6 -0
- package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -0
- package/dist/src/connectionHelper/checks/reconnect.d.ts +6 -0
- package/dist/src/connectionHelper/checks/reconnect.d.ts.map +1 -0
- package/dist/src/connectionHelper/checks/turn.d.ts +6 -0
- package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -0
- package/dist/src/connectionHelper/checks/webrtc.d.ts +6 -0
- package/dist/src/connectionHelper/checks/webrtc.d.ts.map +1 -0
- package/dist/src/connectionHelper/checks/websocket.d.ts +6 -0
- package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc.d.ts +8 -0
- package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
- package/dist/src/room/DeviceManager.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +6 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +5 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +2 -0
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +85 -0
- package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +25 -0
- package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +59 -0
- package/dist/ts4.2/src/connectionHelper/checks/publishAudio.d.ts +6 -0
- package/dist/ts4.2/src/connectionHelper/checks/publishVideo.d.ts +6 -0
- package/dist/ts4.2/src/connectionHelper/checks/reconnect.d.ts +6 -0
- package/dist/ts4.2/src/connectionHelper/checks/turn.d.ts +6 -0
- package/dist/ts4.2/src/connectionHelper/checks/webrtc.d.ts +6 -0
- package/dist/ts4.2/src/connectionHelper/checks/websocket.d.ts +6 -0
- package/dist/ts4.2/src/index.d.ts +30 -0
- package/dist/ts4.2/src/logger.d.ts +26 -0
- package/dist/ts4.2/src/options.d.ts +91 -0
- package/dist/ts4.2/src/proto/google/protobuf/timestamp.d.ts +141 -0
- package/dist/ts4.2/src/proto/livekit_models.d.ts +1421 -0
- package/dist/ts4.2/src/proto/livekit_rtc.d.ts +7122 -0
- package/dist/ts4.2/src/room/DefaultReconnectPolicy.d.ts +8 -0
- package/dist/ts4.2/src/room/DeviceManager.d.ts +9 -0
- package/dist/ts4.2/src/room/PCTransport.d.ts +33 -0
- package/dist/ts4.2/src/room/RTCEngine.d.ts +96 -0
- package/dist/ts4.2/src/room/ReconnectPolicy.d.ts +23 -0
- package/dist/ts4.2/src/room/Room.d.ts +203 -0
- package/dist/ts4.2/src/room/defaults.d.ts +8 -0
- package/dist/ts4.2/src/room/errors.d.ts +39 -0
- package/dist/ts4.2/src/room/events.d.ts +422 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +141 -0
- package/dist/ts4.2/src/room/participant/Participant.d.ts +92 -0
- package/dist/ts4.2/src/room/participant/ParticipantTrackPermission.d.ts +26 -0
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +52 -0
- package/dist/ts4.2/src/room/participant/publishUtils.d.ts +19 -0
- package/dist/ts4.2/src/room/stats.d.ts +67 -0
- package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +25 -0
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +42 -0
- package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +38 -0
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +53 -0
- package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +53 -0
- package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +15 -0
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +61 -0
- package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +52 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +121 -0
- package/dist/ts4.2/src/room/track/TrackPublication.d.ts +68 -0
- package/dist/ts4.2/src/room/track/create.d.ts +24 -0
- package/dist/ts4.2/src/room/track/options.d.ts +241 -0
- package/dist/ts4.2/src/room/track/types.d.ts +23 -0
- package/dist/ts4.2/src/room/track/utils.d.ts +14 -0
- package/dist/ts4.2/src/room/utils.d.ts +35 -0
- package/dist/ts4.2/src/test/MockMediaStreamTrack.d.ts +26 -0
- package/dist/ts4.2/src/test/mocks.d.ts +11 -0
- package/dist/ts4.2/src/version.d.ts +3 -0
- package/package.json +13 -3
- package/src/api/SignalClient.ts +2 -2
- package/src/connectionHelper/ConnectionCheck.ts +90 -0
- package/src/connectionHelper/checks/Checker.ts +164 -0
- package/src/connectionHelper/checks/publishAudio.ts +33 -0
- package/src/connectionHelper/checks/publishVideo.ts +33 -0
- package/src/connectionHelper/checks/reconnect.ts +45 -0
- package/src/connectionHelper/checks/turn.ts +53 -0
- package/src/connectionHelper/checks/webrtc.ts +18 -0
- package/src/connectionHelper/checks/websocket.ts +22 -0
- package/src/index.ts +3 -1
- package/src/proto/livekit_rtc.ts +12 -1
- package/src/room/DeviceManager.ts +0 -17
- package/src/room/Room.ts +22 -2
- package/src/room/events.ts +5 -0
- package/src/room/participant/LocalParticipant.ts +15 -8
- package/src/room/track/LocalTrack.ts +3 -0
- package/src/room/track/RemoteTrackPublication.ts +20 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
import { EventEmitter } from 'events';
|
2
|
+
import type TypedEmitter from 'typed-emitter';
|
3
|
+
import type { RoomConnectOptions, RoomOptions } from '../../options';
|
4
|
+
import Room, { ConnectionState } from '../../room/Room';
|
5
|
+
import type RTCEngine from '../../room/RTCEngine';
|
6
|
+
|
7
|
+
type LogMessage = {
|
8
|
+
level: 'info' | 'warning' | 'error';
|
9
|
+
message: string;
|
10
|
+
};
|
11
|
+
|
12
|
+
export enum CheckStatus {
|
13
|
+
IDLE,
|
14
|
+
RUNNING,
|
15
|
+
SKIPPED,
|
16
|
+
SUCCESS,
|
17
|
+
FAILED,
|
18
|
+
}
|
19
|
+
|
20
|
+
export type CheckInfo = {
|
21
|
+
name: string;
|
22
|
+
logs: Array<LogMessage>;
|
23
|
+
status: CheckStatus;
|
24
|
+
description: string;
|
25
|
+
};
|
26
|
+
|
27
|
+
export interface CheckerOptions {
|
28
|
+
errorsAsWarnings?: boolean;
|
29
|
+
roomOptions?: RoomOptions;
|
30
|
+
connectOptions?: RoomConnectOptions;
|
31
|
+
}
|
32
|
+
|
33
|
+
export abstract class Checker extends (EventEmitter as new () => TypedEmitter<CheckerCallbacks>) {
|
34
|
+
protected url: string;
|
35
|
+
|
36
|
+
protected token: string;
|
37
|
+
|
38
|
+
room: Room;
|
39
|
+
|
40
|
+
connectOptions?: RoomConnectOptions;
|
41
|
+
|
42
|
+
status: CheckStatus = CheckStatus.IDLE;
|
43
|
+
|
44
|
+
logs: Array<LogMessage> = [];
|
45
|
+
|
46
|
+
errorsAsWarnings: boolean = false;
|
47
|
+
|
48
|
+
name: string;
|
49
|
+
|
50
|
+
constructor(url: string, token: string, options: CheckerOptions = {}) {
|
51
|
+
super();
|
52
|
+
this.url = url;
|
53
|
+
this.token = token;
|
54
|
+
this.name = this.constructor.name;
|
55
|
+
this.room = new Room(options.roomOptions);
|
56
|
+
this.connectOptions = options.connectOptions;
|
57
|
+
if (options.errorsAsWarnings) {
|
58
|
+
this.errorsAsWarnings = options.errorsAsWarnings;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
abstract get description(): string;
|
63
|
+
|
64
|
+
protected abstract perform(): Promise<void>;
|
65
|
+
|
66
|
+
async run(onComplete?: () => void) {
|
67
|
+
if (this.status !== CheckStatus.IDLE) {
|
68
|
+
throw Error('check is running already');
|
69
|
+
}
|
70
|
+
this.setStatus(CheckStatus.RUNNING);
|
71
|
+
this.appendMessage(`${this.name} started.`);
|
72
|
+
|
73
|
+
try {
|
74
|
+
await this.perform();
|
75
|
+
} catch (err) {
|
76
|
+
if (err instanceof Error) {
|
77
|
+
if (this.errorsAsWarnings) {
|
78
|
+
this.appendWarning(err.message);
|
79
|
+
} else {
|
80
|
+
this.appendError(err.message);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
await this.disconnect();
|
86
|
+
|
87
|
+
// sleep for a bit to ensure disconnect
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
89
|
+
|
90
|
+
// @ts-ignore
|
91
|
+
if (this.status !== CheckStatus.SKIPPED) {
|
92
|
+
this.setStatus(this.isSuccess() ? CheckStatus.SUCCESS : CheckStatus.FAILED);
|
93
|
+
}
|
94
|
+
|
95
|
+
if (onComplete) {
|
96
|
+
onComplete();
|
97
|
+
}
|
98
|
+
return this.getInfo();
|
99
|
+
}
|
100
|
+
|
101
|
+
protected isSuccess(): boolean {
|
102
|
+
return !this.logs.some((l) => l.level === 'error');
|
103
|
+
}
|
104
|
+
|
105
|
+
protected async connect(): Promise<Room> {
|
106
|
+
if (this.room.state === ConnectionState.Connected) {
|
107
|
+
return this.room;
|
108
|
+
}
|
109
|
+
await this.room.connect(this.url, this.token);
|
110
|
+
return this.room;
|
111
|
+
}
|
112
|
+
|
113
|
+
protected async disconnect() {
|
114
|
+
if (this.room && this.room.state !== ConnectionState.Disconnected) {
|
115
|
+
await this.room.disconnect();
|
116
|
+
// wait for it to go through
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
protected skip() {
|
122
|
+
this.setStatus(CheckStatus.SKIPPED);
|
123
|
+
}
|
124
|
+
|
125
|
+
protected appendMessage(message: string) {
|
126
|
+
this.logs.push({ level: 'info', message });
|
127
|
+
this.emit('update', this.getInfo());
|
128
|
+
}
|
129
|
+
|
130
|
+
protected appendWarning(message: string) {
|
131
|
+
this.logs.push({ level: 'warning', message });
|
132
|
+
this.emit('update', this.getInfo());
|
133
|
+
}
|
134
|
+
|
135
|
+
protected appendError(message: string) {
|
136
|
+
this.logs.push({ level: 'error', message });
|
137
|
+
this.emit('update', this.getInfo());
|
138
|
+
}
|
139
|
+
|
140
|
+
protected setStatus(status: CheckStatus) {
|
141
|
+
this.status = status;
|
142
|
+
this.emit('update', this.getInfo());
|
143
|
+
}
|
144
|
+
|
145
|
+
protected get engine(): RTCEngine | undefined {
|
146
|
+
return this.room?.engine;
|
147
|
+
}
|
148
|
+
|
149
|
+
getInfo(): CheckInfo {
|
150
|
+
return {
|
151
|
+
logs: this.logs,
|
152
|
+
name: this.name,
|
153
|
+
status: this.status,
|
154
|
+
description: this.description,
|
155
|
+
};
|
156
|
+
}
|
157
|
+
}
|
158
|
+
export type InstantiableCheck<T extends Checker> = {
|
159
|
+
new (url: string, token: string, options?: CheckerOptions): T;
|
160
|
+
};
|
161
|
+
|
162
|
+
type CheckerCallbacks = {
|
163
|
+
update: (info: CheckInfo) => void;
|
164
|
+
};
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { createLocalAudioTrack } from '../../room/track/create';
|
2
|
+
import { Checker } from './Checker';
|
3
|
+
|
4
|
+
export class PublishAudioCheck extends Checker {
|
5
|
+
get description(): string {
|
6
|
+
return 'Can publish audio';
|
7
|
+
}
|
8
|
+
|
9
|
+
async perform(): Promise<void> {
|
10
|
+
const room = await this.connect();
|
11
|
+
|
12
|
+
const track = await createLocalAudioTrack();
|
13
|
+
room.localParticipant.publishTrack(track);
|
14
|
+
// wait for a few seconds to publish
|
15
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
16
|
+
|
17
|
+
// verify RTC stats that it's publishing
|
18
|
+
const stats = await track.sender?.getStats();
|
19
|
+
if (!stats) {
|
20
|
+
throw new Error('Could not get RTCStats');
|
21
|
+
}
|
22
|
+
let numPackets = 0;
|
23
|
+
stats.forEach((stat) => {
|
24
|
+
if (stat.type === 'outbound-rtp' && stat.mediaType === 'audio') {
|
25
|
+
numPackets = stat.packetsSent;
|
26
|
+
}
|
27
|
+
});
|
28
|
+
if (numPackets === 0) {
|
29
|
+
throw new Error('Could not determine packets are sent');
|
30
|
+
}
|
31
|
+
this.appendMessage(`published ${numPackets} audio packets`);
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { createLocalVideoTrack } from '../../room/track/create';
|
2
|
+
import { Checker } from './Checker';
|
3
|
+
|
4
|
+
export class PublishVideoCheck extends Checker {
|
5
|
+
get description(): string {
|
6
|
+
return 'Can publish video';
|
7
|
+
}
|
8
|
+
|
9
|
+
async perform(): Promise<void> {
|
10
|
+
const room = await this.connect();
|
11
|
+
|
12
|
+
const track = await createLocalVideoTrack();
|
13
|
+
room.localParticipant.publishTrack(track);
|
14
|
+
// wait for a few seconds to publish
|
15
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
16
|
+
|
17
|
+
// verify RTC stats that it's publishing
|
18
|
+
const stats = await track.sender?.getStats();
|
19
|
+
if (!stats) {
|
20
|
+
throw new Error('Could not get RTCStats');
|
21
|
+
}
|
22
|
+
let numPackets = 0;
|
23
|
+
stats.forEach((stat) => {
|
24
|
+
if (stat.type === 'outbound-rtp' && stat.mediaType === 'video') {
|
25
|
+
numPackets = stat.packetsSent;
|
26
|
+
}
|
27
|
+
});
|
28
|
+
if (numPackets === 0) {
|
29
|
+
throw new Error('Could not determine packets are sent');
|
30
|
+
}
|
31
|
+
this.appendMessage(`published ${numPackets} video packets`);
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { RoomEvent } from '../../room/events';
|
2
|
+
import { ConnectionState } from '../../room/Room';
|
3
|
+
import { Checker } from './Checker';
|
4
|
+
|
5
|
+
export class ReconnectCheck extends Checker {
|
6
|
+
get description(): string {
|
7
|
+
return 'Resuming connection after interruption';
|
8
|
+
}
|
9
|
+
|
10
|
+
async perform(): Promise<void> {
|
11
|
+
const room = await this.connect();
|
12
|
+
let reconnectingTriggered = false;
|
13
|
+
let reconnected = false;
|
14
|
+
|
15
|
+
let reconnectResolver: (value: unknown) => void;
|
16
|
+
const reconnectTimeout = new Promise((resolve) => {
|
17
|
+
setTimeout(resolve, 5000);
|
18
|
+
reconnectResolver = resolve;
|
19
|
+
});
|
20
|
+
|
21
|
+
room
|
22
|
+
.on(RoomEvent.Reconnecting, () => {
|
23
|
+
reconnectingTriggered = true;
|
24
|
+
})
|
25
|
+
.on(RoomEvent.Reconnected, () => {
|
26
|
+
reconnected = true;
|
27
|
+
reconnectResolver(true);
|
28
|
+
});
|
29
|
+
|
30
|
+
room.engine.client.ws?.close();
|
31
|
+
const onClose = room.engine.client.onClose;
|
32
|
+
if (onClose) {
|
33
|
+
onClose('');
|
34
|
+
}
|
35
|
+
|
36
|
+
await reconnectTimeout;
|
37
|
+
|
38
|
+
if (!reconnectingTriggered) {
|
39
|
+
throw new Error('Did not attempt to reconnect');
|
40
|
+
} else if (!reconnected || room.state !== ConnectionState.Connected) {
|
41
|
+
this.appendWarning('reconnection is only possible in Redis-based configurations');
|
42
|
+
throw new Error('Not able to reconnect');
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { SignalClient } from '../../api/SignalClient';
|
2
|
+
import { Checker } from './Checker';
|
3
|
+
|
4
|
+
export class TURNCheck extends Checker {
|
5
|
+
get description(): string {
|
6
|
+
return 'Can connect via TURN';
|
7
|
+
}
|
8
|
+
|
9
|
+
async perform(): Promise<void> {
|
10
|
+
const signalClient = new SignalClient();
|
11
|
+
const joinRes = await signalClient.join(this.url, this.token, {
|
12
|
+
autoSubscribe: true,
|
13
|
+
maxRetries: 0,
|
14
|
+
});
|
15
|
+
|
16
|
+
let hasTLS = false;
|
17
|
+
let hasTURN = false;
|
18
|
+
let hasSTUN = false;
|
19
|
+
|
20
|
+
for (let iceServer of joinRes.iceServers) {
|
21
|
+
for (let url of iceServer.urls) {
|
22
|
+
if (url.startsWith('turn:')) {
|
23
|
+
hasTURN = true;
|
24
|
+
hasSTUN = true;
|
25
|
+
} else if (url.startsWith('turns:')) {
|
26
|
+
hasTURN = true;
|
27
|
+
hasSTUN = true;
|
28
|
+
hasTLS = true;
|
29
|
+
}
|
30
|
+
if (url.startsWith('stun:')) {
|
31
|
+
hasSTUN = true;
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
if (!hasSTUN) {
|
36
|
+
this.appendWarning('No STUN servers configured on server side.');
|
37
|
+
} else if (hasTURN && !hasTLS) {
|
38
|
+
this.appendWarning('TURN is configured server side, but TURN/TLS is unavailable.');
|
39
|
+
}
|
40
|
+
signalClient.close();
|
41
|
+
if (this.connectOptions?.rtcConfig?.iceServers || hasTURN) {
|
42
|
+
await this.room!.connect(this.url, this.token, {
|
43
|
+
rtcConfig: {
|
44
|
+
iceTransportPolicy: 'relay',
|
45
|
+
},
|
46
|
+
});
|
47
|
+
} else {
|
48
|
+
this.appendWarning('No TURN servers configured.');
|
49
|
+
this.skip();
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Checker } from './Checker';
|
2
|
+
|
3
|
+
export class WebRTCCheck extends Checker {
|
4
|
+
get description(): string {
|
5
|
+
return 'Establishing WebRTC connection';
|
6
|
+
}
|
7
|
+
|
8
|
+
protected async perform(): Promise<void> {
|
9
|
+
try {
|
10
|
+
console.log('initiating room connection');
|
11
|
+
this.room = await this.connect();
|
12
|
+
console.log('now the room is connected');
|
13
|
+
} catch (err) {
|
14
|
+
this.appendWarning('ports need to be open on firewall in order to connect.');
|
15
|
+
throw err;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { SignalClient } from '../../api/SignalClient';
|
2
|
+
import { Checker } from './Checker';
|
3
|
+
|
4
|
+
export class WebSocketCheck extends Checker {
|
5
|
+
get description(): string {
|
6
|
+
return 'Connecting to signal connection via WebSocket';
|
7
|
+
}
|
8
|
+
|
9
|
+
protected async perform(): Promise<void> {
|
10
|
+
if (this.url.startsWith('ws:') || this.url.startsWith('http:')) {
|
11
|
+
this.appendWarning('Server is insecure, clients may block connections to it');
|
12
|
+
}
|
13
|
+
|
14
|
+
let signalClient = new SignalClient();
|
15
|
+
const joinRes = await signalClient.join(this.url, this.token, {
|
16
|
+
autoSubscribe: true,
|
17
|
+
maxRetries: 0,
|
18
|
+
});
|
19
|
+
this.appendMessage(`Connected to server, version ${joinRes.serverVersion}.`);
|
20
|
+
signalClient.close();
|
21
|
+
}
|
22
|
+
}
|
package/src/index.ts
CHANGED
@@ -13,7 +13,8 @@ import LocalVideoTrack from './room/track/LocalVideoTrack';
|
|
13
13
|
import RemoteAudioTrack from './room/track/RemoteAudioTrack';
|
14
14
|
import RemoteTrack from './room/track/RemoteTrack';
|
15
15
|
import RemoteTrackPublication from './room/track/RemoteTrackPublication';
|
16
|
-
import RemoteVideoTrack
|
16
|
+
import RemoteVideoTrack from './room/track/RemoteVideoTrack';
|
17
|
+
import type { ElementInfo } from './room/track/RemoteVideoTrack';
|
17
18
|
import { TrackPublication } from './room/track/TrackPublication';
|
18
19
|
import {
|
19
20
|
getEmptyAudioStreamTrack,
|
@@ -32,6 +33,7 @@ export * from './room/track/options';
|
|
32
33
|
export * from './room/track/Track';
|
33
34
|
export * from './room/track/types';
|
34
35
|
export * from './version';
|
36
|
+
export * from './connectionHelper/ConnectionCheck';
|
35
37
|
export {
|
36
38
|
setLogLevel,
|
37
39
|
setLogExtension,
|
package/src/proto/livekit_rtc.ts
CHANGED
@@ -267,6 +267,8 @@ export interface UpdateTrackSettings {
|
|
267
267
|
width: number;
|
268
268
|
/** for video, height to receive */
|
269
269
|
height: number;
|
270
|
+
/** for video, frame rate to receive */
|
271
|
+
fps: number;
|
270
272
|
}
|
271
273
|
|
272
274
|
export interface LeaveRequest {
|
@@ -1843,7 +1845,7 @@ export const UpdateSubscription = {
|
|
1843
1845
|
};
|
1844
1846
|
|
1845
1847
|
function createBaseUpdateTrackSettings(): UpdateTrackSettings {
|
1846
|
-
return { trackSids: [], disabled: false, quality: 0, width: 0, height: 0 };
|
1848
|
+
return { trackSids: [], disabled: false, quality: 0, width: 0, height: 0, fps: 0 };
|
1847
1849
|
}
|
1848
1850
|
|
1849
1851
|
export const UpdateTrackSettings = {
|
@@ -1863,6 +1865,9 @@ export const UpdateTrackSettings = {
|
|
1863
1865
|
if (message.height !== 0) {
|
1864
1866
|
writer.uint32(48).uint32(message.height);
|
1865
1867
|
}
|
1868
|
+
if (message.fps !== 0) {
|
1869
|
+
writer.uint32(56).uint32(message.fps);
|
1870
|
+
}
|
1866
1871
|
return writer;
|
1867
1872
|
},
|
1868
1873
|
|
@@ -1888,6 +1893,9 @@ export const UpdateTrackSettings = {
|
|
1888
1893
|
case 6:
|
1889
1894
|
message.height = reader.uint32();
|
1890
1895
|
break;
|
1896
|
+
case 7:
|
1897
|
+
message.fps = reader.uint32();
|
1898
|
+
break;
|
1891
1899
|
default:
|
1892
1900
|
reader.skipType(tag & 7);
|
1893
1901
|
break;
|
@@ -1903,6 +1911,7 @@ export const UpdateTrackSettings = {
|
|
1903
1911
|
quality: isSet(object.quality) ? videoQualityFromJSON(object.quality) : 0,
|
1904
1912
|
width: isSet(object.width) ? Number(object.width) : 0,
|
1905
1913
|
height: isSet(object.height) ? Number(object.height) : 0,
|
1914
|
+
fps: isSet(object.fps) ? Number(object.fps) : 0,
|
1906
1915
|
};
|
1907
1916
|
},
|
1908
1917
|
|
@@ -1917,6 +1926,7 @@ export const UpdateTrackSettings = {
|
|
1917
1926
|
message.quality !== undefined && (obj.quality = videoQualityToJSON(message.quality));
|
1918
1927
|
message.width !== undefined && (obj.width = Math.round(message.width));
|
1919
1928
|
message.height !== undefined && (obj.height = Math.round(message.height));
|
1929
|
+
message.fps !== undefined && (obj.fps = Math.round(message.fps));
|
1920
1930
|
return obj;
|
1921
1931
|
},
|
1922
1932
|
|
@@ -1927,6 +1937,7 @@ export const UpdateTrackSettings = {
|
|
1927
1937
|
message.quality = object.quality ?? 0;
|
1928
1938
|
message.width = object.width ?? 0;
|
1929
1939
|
message.height = object.height ?? 0;
|
1940
|
+
message.fps = object.fps ?? 0;
|
1930
1941
|
return message;
|
1931
1942
|
},
|
1932
1943
|
};
|
@@ -65,23 +65,6 @@ export default class DeviceManager {
|
|
65
65
|
devices = devices.filter((device) => device.kind === kind);
|
66
66
|
}
|
67
67
|
|
68
|
-
// Chrome returns 'default' devices, we would filter them out, but put the default
|
69
|
-
// device at first
|
70
|
-
// we would only do this if there are more than 1 device though
|
71
|
-
if (devices.length > 1 && devices[0].deviceId === defaultId) {
|
72
|
-
// find another device with matching group id, and move that to 0
|
73
|
-
const defaultDevice = devices[0];
|
74
|
-
for (let i = 1; i < devices.length; i += 1) {
|
75
|
-
if (devices[i].groupId === defaultDevice.groupId) {
|
76
|
-
const temp = devices[0];
|
77
|
-
devices[0] = devices[i];
|
78
|
-
devices[i] = temp;
|
79
|
-
break;
|
80
|
-
}
|
81
|
-
}
|
82
|
-
return devices.filter((device) => device !== defaultDevice);
|
83
|
-
}
|
84
|
-
|
85
68
|
return devices;
|
86
69
|
}
|
87
70
|
|
package/src/room/Room.ts
CHANGED
@@ -101,6 +101,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
101
101
|
/** options of room */
|
102
102
|
options: InternalRoomOptions;
|
103
103
|
|
104
|
+
private _isRecording: boolean = false;
|
105
|
+
|
104
106
|
private identityToSid: Map<string, string>;
|
105
107
|
|
106
108
|
/** connect options of room */
|
@@ -332,6 +334,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
332
334
|
this.name = joinResponse.room!.name;
|
333
335
|
this.sid = joinResponse.room!.sid;
|
334
336
|
this.metadata = joinResponse.room!.metadata;
|
337
|
+
if (this._isRecording !== joinResponse.room!.activeRecording) {
|
338
|
+
this._isRecording = joinResponse.room!.activeRecording;
|
339
|
+
this.emit(RoomEvent.RecordingStatusChanged, joinResponse.room!.activeRecording);
|
340
|
+
}
|
335
341
|
this.emit(RoomEvent.SignalConnected);
|
336
342
|
} catch (err) {
|
337
343
|
this.recreateEngine();
|
@@ -424,6 +430,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
424
430
|
this.connectFuture = undefined;
|
425
431
|
}
|
426
432
|
|
433
|
+
/**
|
434
|
+
* if the current room has a participant with `recorder: true` in its JWT grant
|
435
|
+
**/
|
436
|
+
get isRecording() {
|
437
|
+
return this._isRecording;
|
438
|
+
}
|
439
|
+
|
427
440
|
/**
|
428
441
|
* @internal for testing
|
429
442
|
*/
|
@@ -950,8 +963,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
950
963
|
};
|
951
964
|
|
952
965
|
private handleRoomUpdate = (r: RoomModel) => {
|
953
|
-
this.
|
954
|
-
|
966
|
+
if (this._isRecording !== r.activeRecording) {
|
967
|
+
this._isRecording = r.activeRecording;
|
968
|
+
this.emit(RoomEvent.RecordingStatusChanged, r.activeRecording);
|
969
|
+
}
|
970
|
+
if (this.metadata !== r.metadata) {
|
971
|
+
this.metadata = r.metadata;
|
972
|
+
this.emitWhenConnected(RoomEvent.RoomMetadataChanged, r.metadata);
|
973
|
+
}
|
955
974
|
};
|
956
975
|
|
957
976
|
private handleConnectionQualityUpdate = (update: ConnectionQualityUpdate) => {
|
@@ -1273,4 +1292,5 @@ export type RoomEventCallbacks = {
|
|
1273
1292
|
) => void;
|
1274
1293
|
audioPlaybackChanged: (playing: boolean) => void;
|
1275
1294
|
signalConnected: () => void;
|
1295
|
+
recordingStatusChanged: (recording: boolean) => void;
|
1276
1296
|
};
|
package/src/room/events.ts
CHANGED
@@ -250,6 +250,11 @@ export enum RoomEvent {
|
|
250
250
|
* Signal connected, can publish tracks.
|
251
251
|
*/
|
252
252
|
SignalConnected = 'signalConnected',
|
253
|
+
|
254
|
+
/**
|
255
|
+
* Recording of a room has started/stopped.
|
256
|
+
*/
|
257
|
+
RecordingStatusChanged = 'recordingStatusChanged',
|
253
258
|
}
|
254
259
|
|
255
260
|
export enum ParticipantEvent {
|
@@ -34,7 +34,7 @@ import {
|
|
34
34
|
} from '../track/options';
|
35
35
|
import { Track } from '../track/Track';
|
36
36
|
import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
|
37
|
-
import { isFireFox, isWeb, supportsAV1 } from '../utils';
|
37
|
+
import { isFireFox, isSafari, isWeb, supportsAV1 } from '../utils';
|
38
38
|
import Participant from './Participant';
|
39
39
|
import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
|
40
40
|
import {
|
@@ -243,6 +243,7 @@ export default class LocalParticipant extends Participant {
|
|
243
243
|
}
|
244
244
|
const publishPromises: Array<Promise<LocalTrackPublication>> = [];
|
245
245
|
for (const localTrack of localTracks) {
|
246
|
+
log.info('publishing track', { localTrack });
|
246
247
|
publishPromises.push(this.publishTrack(localTrack, publishOptions));
|
247
248
|
}
|
248
249
|
const publishedTracks = await Promise.all(publishPromises);
|
@@ -374,14 +375,20 @@ export default class LocalParticipant extends Participant {
|
|
374
375
|
|
375
376
|
let videoConstraints: MediaTrackConstraints | boolean = true;
|
376
377
|
if (options.resolution) {
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
378
|
+
if (isSafari()) {
|
379
|
+
videoConstraints = {
|
380
|
+
width: { max: options.resolution.width },
|
381
|
+
height: { max: options.resolution.height },
|
382
|
+
frameRate: options.resolution.frameRate,
|
383
|
+
};
|
384
|
+
} else {
|
385
|
+
videoConstraints = {
|
386
|
+
width: { ideal: options.resolution.width },
|
387
|
+
height: { ideal: options.resolution.height },
|
388
|
+
frameRate: options.resolution.frameRate,
|
389
|
+
};
|
390
|
+
}
|
382
391
|
}
|
383
|
-
// typescript definition is missing getDisplayMedia: https://github.com/microsoft/TypeScript/issues/33232
|
384
|
-
// @ts-ignore
|
385
392
|
const stream: MediaStream = await navigator.mediaDevices.getDisplayMedia({
|
386
393
|
audio: options.audio ?? false,
|
387
394
|
video: videoConstraints,
|
@@ -121,6 +121,9 @@ export default abstract class LocalTrack extends Track {
|
|
121
121
|
}
|
122
122
|
this._mediaStreamTrack = track;
|
123
123
|
|
124
|
+
// sync muted state with the enabled state of the newly provided track
|
125
|
+
this._mediaStreamTrack.enabled = !this.isMuted;
|
126
|
+
|
124
127
|
await this.resumeUpstream();
|
125
128
|
|
126
129
|
this.attachedElements.forEach((el) => {
|
@@ -22,6 +22,8 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
22
22
|
|
23
23
|
protected videoDimensions?: Track.Dimensions;
|
24
24
|
|
25
|
+
protected fps?: number;
|
26
|
+
|
25
27
|
constructor(kind: Track.Kind, id: string, name: string, autoSubscribe: boolean | undefined) {
|
26
28
|
super(kind, id, name);
|
27
29
|
this.subscribed = autoSubscribe;
|
@@ -143,6 +145,23 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
143
145
|
this.emitTrackUpdate();
|
144
146
|
}
|
145
147
|
|
148
|
+
setVideoFPS(fps: number) {
|
149
|
+
if (!this.isManualOperationAllowed()) {
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
|
153
|
+
if (!(this.track instanceof RemoteVideoTrack)) {
|
154
|
+
return;
|
155
|
+
}
|
156
|
+
|
157
|
+
if (this.fps === fps) {
|
158
|
+
return;
|
159
|
+
}
|
160
|
+
|
161
|
+
this.fps = fps;
|
162
|
+
this.emitTrackUpdate();
|
163
|
+
}
|
164
|
+
|
146
165
|
get videoQuality(): VideoQuality | undefined {
|
147
166
|
return this.currentVideoQuality;
|
148
167
|
}
|
@@ -256,6 +275,7 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
256
275
|
const settings: UpdateTrackSettings = UpdateTrackSettings.fromPartial({
|
257
276
|
trackSids: [this.trackSid],
|
258
277
|
disabled: this.disabled,
|
278
|
+
fps: this.fps,
|
259
279
|
});
|
260
280
|
if (this.videoDimensions) {
|
261
281
|
settings.width = this.videoDimensions.width;
|