@fishjam-cloud/ts-client 0.5.1 → 0.5.5
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/README.md +20 -24
- package/dist/src/FishjamClient.d.ts +5 -22
- package/dist/src/FishjamClient.js +13 -14
- package/dist/src/index.d.ts +1 -1
- package/dist/src/reconnection.js +2 -4
- package/dist/src/webrtc/CommandsQueue.js +8 -24
- package/dist/src/webrtc/ConnectionManager.js +3 -9
- package/dist/src/webrtc/tracks/Local.d.ts +1 -1
- package/dist/src/webrtc/tracks/Local.js +3 -8
- package/dist/src/webrtc/tracks/LocalTrack.d.ts +1 -1
- package/dist/src/webrtc/tracks/LocalTrack.js +5 -17
- package/dist/src/webrtc/tracks/LocalTrackManager.d.ts +1 -1
- package/dist/src/webrtc/tracks/LocalTrackManager.js +3 -8
- package/dist/src/webrtc/tracks/encodings.js +1 -3
- package/dist/src/webrtc/tracks/transceivers.js +1 -2
- package/dist/src/webrtc/types.d.ts +0 -1
- package/dist/src/webrtc/webRTCEndpoint.d.ts +1 -3
- package/dist/src/webrtc/webRTCEndpoint.js +27 -63
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
[](https://www.npmjs.com/package/@fishjam-cloud/ts-client)
|
|
2
2
|
[](https://www.typescriptlang.org)
|
|
3
|
-
[](https://fishjam-cloud.github.io/web-client-sdk/)
|
|
3
|
+
[](https://fishjam-cloud.github.io/web-client-sdk/modules/_fishjam_dev_ts_client.html)
|
|
4
4
|
|
|
5
5
|
# Fishjam TS Client
|
|
6
6
|
|
|
7
7
|
TypeScript client library for [Fishjam Cloud](https://cloud.fishjam.stream).
|
|
8
8
|
|
|
9
|
+
## Documentation
|
|
10
|
+
|
|
11
|
+
Documentation is available [here](https://fishjam-cloud.github.io/web-client-sdk/modules/_fishjam_dev_ts_client.html) or
|
|
12
|
+
you can generate it locally:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
yarn run docs
|
|
16
|
+
```
|
|
17
|
+
|
|
9
18
|
## Installation
|
|
10
19
|
|
|
11
20
|
You can install this package using `npm`:
|
|
@@ -17,27 +26,17 @@ npm install @fishjam-cloud/ts-client
|
|
|
17
26
|
or `yarn`:
|
|
18
27
|
|
|
19
28
|
```bash
|
|
20
|
-
yarn @fishjam-cloud/
|
|
29
|
+
yarn @fishjam-cloud/ts-client
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
## Documentation
|
|
24
|
-
|
|
25
|
-
Documentation is available
|
|
26
|
-
[here](https://fishjam-cloud.github.io/web-client-sdk/modules/_fishjam_dev_ts_client.html/).
|
|
27
|
-
|
|
28
|
-
For a more comprehensive tutorial on Fishjam, its capabilities and usage in
|
|
29
|
-
production, refer to the [Fishjam Cloud Docs](https://cloud.fishjam.stream/).
|
|
30
|
-
|
|
31
32
|
## Usage
|
|
32
33
|
|
|
33
34
|
Prerequisites:
|
|
34
35
|
|
|
35
|
-
- Account on Fishjam Cloud with App configured.
|
|
36
|
-
- Created room and token of peer in that room. You can use Room Manager to
|
|
37
|
-
create room and peer token.
|
|
36
|
+
- Account on [Fishjam Cloud](https://cloud.fishjam.stream) with App configured.
|
|
37
|
+
- Created room and token of peer in that room. You can use Room Manager to create room and peer token.
|
|
38
38
|
|
|
39
|
-
The following code snippet is based on the
|
|
40
|
-
[minimal](../../examples/ts-client//minimal/) example.
|
|
39
|
+
The following code snippet is based on the [minimal](../../examples/ts-client/minimal/) example.
|
|
41
40
|
|
|
42
41
|
```ts
|
|
43
42
|
import { FishjamClient, WebRTCEndpoint } from '@fishjam-cloud/ts-client';
|
|
@@ -65,11 +64,13 @@ const client = new FishjamClient<PeerMetadata, TrackMetadata>();
|
|
|
65
64
|
|
|
66
65
|
const peerToken = prompt('Enter peer token') ?? 'YOUR_PEER_TOKEN';
|
|
67
66
|
|
|
67
|
+
const FISHJAM_URL = 'ws://localhost:5002';
|
|
68
|
+
|
|
68
69
|
// Start the peer connection
|
|
69
70
|
client.connect({
|
|
70
71
|
peerMetadata: { name: 'peer' },
|
|
71
72
|
token: peerToken,
|
|
72
|
-
|
|
73
|
+
url: FISHJAM_URL,
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
// You can listen to events emitted by the client
|
|
@@ -109,14 +110,10 @@ client.on('trackRemoved', (ctx) => {
|
|
|
109
110
|
|
|
110
111
|
async function startScreenSharing(webrtc: WebRTCEndpoint) {
|
|
111
112
|
// Get screen sharing MediaStream
|
|
112
|
-
const screenStream = await navigator.mediaDevices.getDisplayMedia(
|
|
113
|
-
SCREEN_SHARING_MEDIA_CONSTRAINTS,
|
|
114
|
-
);
|
|
113
|
+
const screenStream = await navigator.mediaDevices.getDisplayMedia(SCREEN_SHARING_MEDIA_CONSTRAINTS);
|
|
115
114
|
|
|
116
115
|
// Add local MediaStream to webrtc
|
|
117
|
-
screenStream
|
|
118
|
-
.getTracks()
|
|
119
|
-
.forEach((track) => webrtc.addTrack(track, { type: 'screen' }));
|
|
116
|
+
screenStream.getTracks().forEach((track) => webrtc.addTrack(track, { type: 'screen' }));
|
|
120
117
|
}
|
|
121
118
|
```
|
|
122
119
|
|
|
@@ -126,8 +123,7 @@ For more examples, see the [examples](../../examples/ts-client/) folder.
|
|
|
126
123
|
|
|
127
124
|
## Copyright and License
|
|
128
125
|
|
|
129
|
-
Copyright 2024,
|
|
130
|
-
[Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=fishjam-ts)
|
|
126
|
+
Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=fishjam-ts)
|
|
131
127
|
|
|
132
128
|
[](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=fishjam-ts)
|
|
133
129
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BandwidthLimit, Endpoint, SimulcastConfig, TrackBandwidthLimit, TrackContext,
|
|
1
|
+
import type { BandwidthLimit, Encoding, Endpoint, MetadataParser, SimulcastConfig, TrackBandwidthLimit, TrackContext, WebRTCEndpointEvents } from './webrtc';
|
|
2
2
|
import type TypedEmitter from 'typed-emitter';
|
|
3
3
|
import type { ReconnectConfig } from './reconnection';
|
|
4
4
|
import type { AuthErrorReason } from './auth';
|
|
@@ -134,30 +134,14 @@ export interface MessageEvents<PeerMetadata, TrackMetadata> {
|
|
|
134
134
|
localTrackMetadataChanged: (event: Parameters<WebRTCEndpointEvents<PeerMetadata, TrackMetadata>['localTrackMetadataChanged']>[0]) => void;
|
|
135
135
|
disconnectRequested: (event: Parameters<WebRTCEndpointEvents<PeerMetadata, TrackMetadata>['disconnectRequested']>[0]) => void;
|
|
136
136
|
}
|
|
137
|
-
export type SignalingUrl = {
|
|
138
|
-
/**
|
|
139
|
-
* Protocol of the websocket server
|
|
140
|
-
* Default is `"ws"`
|
|
141
|
-
*/
|
|
142
|
-
protocol?: string;
|
|
143
|
-
/**
|
|
144
|
-
* Host of the websocket server
|
|
145
|
-
* Default is `"localhost:5002"`
|
|
146
|
-
*/
|
|
147
|
-
host?: string;
|
|
148
|
-
/**
|
|
149
|
-
* Path of the websocket server
|
|
150
|
-
* Default is `"/socket/peer/websocket"`
|
|
151
|
-
*/
|
|
152
|
-
path?: string;
|
|
153
|
-
};
|
|
154
137
|
/** Configuration object for the client */
|
|
155
138
|
export interface ConnectConfig<PeerMetadata> {
|
|
156
139
|
/** Metadata for the peer */
|
|
157
140
|
peerMetadata: PeerMetadata;
|
|
158
141
|
/** Token for authentication */
|
|
159
142
|
token: string;
|
|
160
|
-
|
|
143
|
+
/** Fishjam url */
|
|
144
|
+
url: string;
|
|
161
145
|
}
|
|
162
146
|
export type CreateConfig<PeerMetadata, TrackMetadata> = {
|
|
163
147
|
peerMetadataParser?: MetadataParser<PeerMetadata>;
|
|
@@ -228,6 +212,7 @@ export declare class FishjamClient<PeerMetadata, TrackMetadata> extends FishjamC
|
|
|
228
212
|
*/
|
|
229
213
|
connect(config: ConnectConfig<PeerMetadata>): void;
|
|
230
214
|
private initConnection;
|
|
215
|
+
private getUrl;
|
|
231
216
|
private initWebsocket;
|
|
232
217
|
/**
|
|
233
218
|
* Retrieves statistics related to the RTCPeerConnection.
|
|
@@ -380,11 +365,9 @@ export declare class FishjamClient<PeerMetadata, TrackMetadata> extends FishjamC
|
|
|
380
365
|
*
|
|
381
366
|
* @param {string} trackId - Id of audio or video track to replace.
|
|
382
367
|
* @param {MediaStreamTrack} newTrack - New audio or video track.
|
|
383
|
-
* @param {TrackMetadata} [newTrackMetadata] - Optional track metadata to apply to the new track. If no track metadata is passed, the
|
|
384
|
-
* old track metadata is retained.
|
|
385
368
|
* @returns {Promise<boolean>} Success
|
|
386
369
|
*/
|
|
387
|
-
replaceTrack(trackId: string, newTrack: MediaStreamTrack | null
|
|
370
|
+
replaceTrack(trackId: string, newTrack: MediaStreamTrack | null): Promise<void>;
|
|
388
371
|
/**
|
|
389
372
|
* Updates maximum bandwidth for the track identified by trackId. This value directly translates to quality of the
|
|
390
373
|
* stream and, in case of video, to the amount of RTP packets being sent. In case trackId points at the simulcast
|
|
@@ -10,6 +10,7 @@ const isComponent = (endpoint) => endpoint.type === 'recording' ||
|
|
|
10
10
|
endpoint.type === 'file' ||
|
|
11
11
|
endpoint.type === 'rtsp' ||
|
|
12
12
|
endpoint.type === 'sip';
|
|
13
|
+
const WEBSOCKET_PATH = 'socket/peer/websocket';
|
|
13
14
|
/**
|
|
14
15
|
* FishjamClient is the main class to interact with Fishjam.
|
|
15
16
|
*
|
|
@@ -56,10 +57,8 @@ export class FishjamClient extends EventEmitter {
|
|
|
56
57
|
trackMetadataParser;
|
|
57
58
|
constructor(config) {
|
|
58
59
|
super();
|
|
59
|
-
this.peerMetadataParser =
|
|
60
|
-
|
|
61
|
-
this.trackMetadataParser =
|
|
62
|
-
config?.trackMetadataParser ?? ((x) => x);
|
|
60
|
+
this.peerMetadataParser = config?.peerMetadataParser ?? ((x) => x);
|
|
61
|
+
this.trackMetadataParser = config?.trackMetadataParser ?? ((x) => x);
|
|
63
62
|
this.reconnectManager = new ReconnectManager(this, (peerMetadata) => this.initConnection(peerMetadata), config?.reconnect);
|
|
64
63
|
}
|
|
65
64
|
/**
|
|
@@ -95,14 +94,16 @@ export class FishjamClient extends EventEmitter {
|
|
|
95
94
|
this.setupCallbacks();
|
|
96
95
|
this.status = 'initialized';
|
|
97
96
|
}
|
|
97
|
+
getUrl(url) {
|
|
98
|
+
if (url.endsWith('/'))
|
|
99
|
+
return `${url}${WEBSOCKET_PATH}`;
|
|
100
|
+
return `${url}/${WEBSOCKET_PATH}`;
|
|
101
|
+
}
|
|
98
102
|
initWebsocket(peerMetadata) {
|
|
99
103
|
if (!this.connectConfig)
|
|
100
104
|
throw Error('ConnectConfig is null');
|
|
101
|
-
const { token,
|
|
102
|
-
const
|
|
103
|
-
const host = signaling?.host ?? 'localhost:5002';
|
|
104
|
-
const path = signaling?.path ?? '/socket/peer/websocket';
|
|
105
|
-
const websocketUrl = protocol + '://' + host + path;
|
|
105
|
+
const { token, url } = this.connectConfig;
|
|
106
|
+
const websocketUrl = this.getUrl(url);
|
|
106
107
|
this.websocket = new WebSocket(websocketUrl);
|
|
107
108
|
this.websocket.binaryType = 'arraybuffer';
|
|
108
109
|
const socketOpenHandler = (event) => {
|
|
@@ -180,7 +181,7 @@ export class FishjamClient extends EventEmitter {
|
|
|
180
181
|
return Object.entries(this.webrtc?.getRemoteEndpoints() ?? {}).reduce((acc, [id, peer]) => (isPeer(peer) ? { ...acc, [id]: peer } : acc), {});
|
|
181
182
|
}
|
|
182
183
|
getRemoteComponents() {
|
|
183
|
-
return Object.entries(this.webrtc?.getRemoteEndpoints() ?? {}).reduce((acc, [id, component]) => isComponent(component) ? { ...acc, [id]: component } : acc, {});
|
|
184
|
+
return Object.entries(this.webrtc?.getRemoteEndpoints() ?? {}).reduce((acc, [id, component]) => (isComponent(component) ? { ...acc, [id]: component } : acc), {});
|
|
184
185
|
}
|
|
185
186
|
getLocalPeer() {
|
|
186
187
|
return this.webrtc?.getLocalEndpoint() || null;
|
|
@@ -458,14 +459,12 @@ export class FishjamClient extends EventEmitter {
|
|
|
458
459
|
*
|
|
459
460
|
* @param {string} trackId - Id of audio or video track to replace.
|
|
460
461
|
* @param {MediaStreamTrack} newTrack - New audio or video track.
|
|
461
|
-
* @param {TrackMetadata} [newTrackMetadata] - Optional track metadata to apply to the new track. If no track metadata is passed, the
|
|
462
|
-
* old track metadata is retained.
|
|
463
462
|
* @returns {Promise<boolean>} Success
|
|
464
463
|
*/
|
|
465
|
-
async replaceTrack(trackId, newTrack
|
|
464
|
+
async replaceTrack(trackId, newTrack) {
|
|
466
465
|
if (!this.webrtc)
|
|
467
466
|
throw this.handleWebRTCNotInitialized();
|
|
468
|
-
return this.webrtc.replaceTrack(trackId, newTrack
|
|
467
|
+
return this.webrtc.replaceTrack(trackId, newTrack);
|
|
469
468
|
}
|
|
470
469
|
/**
|
|
471
470
|
* Updates maximum bandwidth for the track identified by trackId. This value directly translates to quality of the
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Peer, Component, ConnectConfig, CreateConfig, MessageEvents
|
|
1
|
+
export type { Peer, Component, ConnectConfig, CreateConfig, MessageEvents } from './FishjamClient';
|
|
2
2
|
export type { ReconnectConfig, ReconnectionStatus } from './reconnection';
|
|
3
3
|
export type { AuthErrorReason } from './auth.js';
|
|
4
4
|
export { isAuthError, AUTH_ERROR_REASONS } from './auth.js';
|
package/dist/src/reconnection.js
CHANGED
|
@@ -78,8 +78,7 @@ export class ReconnectManager {
|
|
|
78
78
|
this.client.emit('reconnectionStarted');
|
|
79
79
|
this.lastLocalEndpoint = this.client.getLocalPeer() || null;
|
|
80
80
|
}
|
|
81
|
-
const timeout = this.reconnectConfig.initialDelay +
|
|
82
|
-
this.reconnectAttempt * this.reconnectConfig.delay;
|
|
81
|
+
const timeout = this.reconnectConfig.initialDelay + this.reconnectAttempt * this.reconnectConfig.delay;
|
|
83
82
|
this.reconnectAttempt += 1;
|
|
84
83
|
this.reconnectTimeoutId = setTimeout(() => {
|
|
85
84
|
this.reconnectTimeoutId = null;
|
|
@@ -115,7 +114,6 @@ export const createReconnectConfig = (config) => {
|
|
|
115
114
|
maxAttempts: config?.maxAttempts ?? DEFAULT_RECONNECT_CONFIG.maxAttempts,
|
|
116
115
|
initialDelay: config?.initialDelay ?? DEFAULT_RECONNECT_CONFIG.initialDelay,
|
|
117
116
|
delay: config?.delay ?? DEFAULT_RECONNECT_CONFIG.delay,
|
|
118
|
-
addTracksOnReconnect: config?.addTracksOnReconnect ??
|
|
119
|
-
DEFAULT_RECONNECT_CONFIG.addTracksOnReconnect,
|
|
117
|
+
addTracksOnReconnect: config?.addTracksOnReconnect ?? DEFAULT_RECONNECT_CONFIG.addTracksOnReconnect,
|
|
120
118
|
};
|
|
121
119
|
};
|
|
@@ -28,31 +28,15 @@ export class CommandsQueue {
|
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
this.clearConnectionCallbacks = () => {
|
|
31
|
-
connection
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
connection
|
|
35
|
-
.getConnection()
|
|
36
|
-
.removeEventListener('icegatheringstatechange', onIceGatheringStateChange);
|
|
37
|
-
connection
|
|
38
|
-
.getConnection()
|
|
39
|
-
.removeEventListener('connectionstatechange', onConnectionStateChange);
|
|
40
|
-
connection
|
|
41
|
-
.getConnection()
|
|
42
|
-
.removeEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
31
|
+
connection.getConnection().removeEventListener('signalingstatechange', onSignalingStateChange);
|
|
32
|
+
connection.getConnection().removeEventListener('icegatheringstatechange', onIceGatheringStateChange);
|
|
33
|
+
connection.getConnection().removeEventListener('connectionstatechange', onConnectionStateChange);
|
|
34
|
+
connection.getConnection().removeEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
43
35
|
};
|
|
44
|
-
connection
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
connection
|
|
48
|
-
.getConnection()
|
|
49
|
-
.addEventListener('connectionstatechange', onConnectionStateChange);
|
|
50
|
-
connection
|
|
51
|
-
.getConnection()
|
|
52
|
-
.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
53
|
-
connection
|
|
54
|
-
.getConnection()
|
|
55
|
-
.addEventListener('signalingstatechange', onSignalingStateChange);
|
|
36
|
+
connection.getConnection().addEventListener('icegatheringstatechange', onIceConnectionStateChange);
|
|
37
|
+
connection.getConnection().addEventListener('connectionstatechange', onConnectionStateChange);
|
|
38
|
+
connection.getConnection().addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
39
|
+
connection.getConnection().addEventListener('signalingstatechange', onSignalingStateChange);
|
|
56
40
|
};
|
|
57
41
|
commandsQueue = [];
|
|
58
42
|
commandResolutionNotifier = null;
|
|
@@ -35,14 +35,10 @@ export class ConnectionManager {
|
|
|
35
35
|
return this.connection;
|
|
36
36
|
};
|
|
37
37
|
setTransceiversToReadOnly = () => {
|
|
38
|
-
this.connection
|
|
39
|
-
.getTransceivers()
|
|
40
|
-
.forEach((transceiver) => (transceiver.direction = 'sendonly'));
|
|
38
|
+
this.connection.getTransceivers().forEach((transceiver) => (transceiver.direction = 'sendonly'));
|
|
41
39
|
};
|
|
42
40
|
addTransceiversIfNeeded = (serverTracks) => {
|
|
43
|
-
const recvTransceivers = this.connection
|
|
44
|
-
.getTransceivers()
|
|
45
|
-
.filter((elem) => elem.direction === 'recvonly');
|
|
41
|
+
const recvTransceivers = this.connection.getTransceivers().filter((elem) => elem.direction === 'recvonly');
|
|
46
42
|
['audio', 'video']
|
|
47
43
|
.flatMap((type) => this.getNeededTransceiversTypes(type, recvTransceivers, serverTracks))
|
|
48
44
|
.forEach((kind) => this.connection.addTransceiver(kind, { direction: 'recvonly' }));
|
|
@@ -73,9 +69,7 @@ export class ConnectionManager {
|
|
|
73
69
|
removeTrack = (sender) => {
|
|
74
70
|
this.connection.removeTrack(sender);
|
|
75
71
|
};
|
|
76
|
-
findSender = (mediaStreamTrackId) => this.connection
|
|
77
|
-
.getSenders()
|
|
78
|
-
.find((sender) => sender.track && sender.track.id === mediaStreamTrackId);
|
|
72
|
+
findSender = (mediaStreamTrackId) => this.connection.getSenders().find((sender) => sender.track && sender.track.id === mediaStreamTrackId);
|
|
79
73
|
addIceCandidate = async (iceCandidate) => {
|
|
80
74
|
await this.connection.addIceCandidate(iceCandidate);
|
|
81
75
|
};
|
|
@@ -31,7 +31,7 @@ export declare class Local<EndpointMetadata, TrackMetadata> {
|
|
|
31
31
|
disableAllLocalTrackEncodings: () => Promise<void>;
|
|
32
32
|
getTrackByMidOrNull: (mid: string) => LocalTrack<EndpointMetadata, TrackMetadata> | null;
|
|
33
33
|
removeTrack: (trackId: TrackId) => void;
|
|
34
|
-
replaceTrack: (webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>, trackId: TrackId, newTrack: MediaStreamTrack | null
|
|
34
|
+
replaceTrack: (webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>, trackId: TrackId, newTrack: MediaStreamTrack | null) => Promise<void>;
|
|
35
35
|
setEndpointMetadata: (metadata: EndpointMetadata) => void;
|
|
36
36
|
getEndpoint: () => EndpointWithTrackContext<EndpointMetadata, TrackMetadata>;
|
|
37
37
|
setTrackBandwidth: (trackId: string, bandwidth: BandwidthLimit) => Promise<void>;
|
|
@@ -85,8 +85,7 @@ export class Local {
|
|
|
85
85
|
}
|
|
86
86
|
};
|
|
87
87
|
getTrackByMidOrNull = (mid) => {
|
|
88
|
-
return
|
|
89
|
-
null);
|
|
88
|
+
return Object.values(this.localTracks).find((track) => track.mLineId === mid) ?? null;
|
|
90
89
|
};
|
|
91
90
|
removeTrack = (trackId) => {
|
|
92
91
|
const trackManager = this.localTracks[trackId];
|
|
@@ -98,16 +97,12 @@ export class Local {
|
|
|
98
97
|
this.localEndpoint.tracks.delete(trackId);
|
|
99
98
|
delete this.localTracks[trackId];
|
|
100
99
|
};
|
|
101
|
-
replaceTrack = async (
|
|
102
|
-
// todo remove webrtc with newTrackMetadata
|
|
103
|
-
webrtc, trackId, newTrack,
|
|
104
|
-
// todo remove webrtc with newTrackMetadata
|
|
105
|
-
newTrackMetadata) => {
|
|
100
|
+
replaceTrack = async (webrtc, trackId, newTrack) => {
|
|
106
101
|
// TODO add validation to track.kind, you cannot replace video with audio
|
|
107
102
|
const trackManager = this.localTracks[trackId];
|
|
108
103
|
if (!trackManager)
|
|
109
104
|
throw new Error(`Cannot find ${trackId}`);
|
|
110
|
-
await trackManager.replaceTrack(newTrack,
|
|
105
|
+
await trackManager.replaceTrack(newTrack, webrtc);
|
|
111
106
|
};
|
|
112
107
|
setEndpointMetadata = (metadata) => {
|
|
113
108
|
try {
|
|
@@ -49,7 +49,7 @@ export declare class LocalTrack<EndpointMetadata, TrackMetadata> implements Trac
|
|
|
49
49
|
addTrackToConnection: () => void;
|
|
50
50
|
private updateEncodings;
|
|
51
51
|
removeFromConnection: () => void;
|
|
52
|
-
replaceTrack: (newTrack: MediaStreamTrack | null,
|
|
52
|
+
replaceTrack: (newTrack: MediaStreamTrack | null, webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>) => Promise<void>;
|
|
53
53
|
setTrackBandwidth: (bandwidth: BandwidthLimit) => Promise<void>;
|
|
54
54
|
setEncodingBandwidth(rid: Encoding, bandwidth: BandwidthLimit): Promise<void>;
|
|
55
55
|
updateTrackMetadata: (metadata: unknown) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { generateCustomEvent, generateMediaEvent } from '../mediaEvent';
|
|
2
|
-
import { defaultBitrates, defaultSimulcastBitrates, UNLIMITED_BANDWIDTH
|
|
2
|
+
import { defaultBitrates, defaultSimulcastBitrates, UNLIMITED_BANDWIDTH } from '../bitrate';
|
|
3
3
|
import { getEncodingParameters } from './encodings';
|
|
4
4
|
import { createTransceiverConfig } from './transceivers';
|
|
5
5
|
/**
|
|
@@ -82,8 +82,7 @@ export class LocalTrack {
|
|
|
82
82
|
};
|
|
83
83
|
// 1
|
|
84
84
|
updateEncodings = () => {
|
|
85
|
-
if (this.trackContext?.track?.kind === 'video' &&
|
|
86
|
-
this.trackContext.simulcastConfig?.activeEncodings) {
|
|
85
|
+
if (this.trackContext?.track?.kind === 'video' && this.trackContext.simulcastConfig?.activeEncodings) {
|
|
87
86
|
const activeEncodings = this.trackContext.simulcastConfig.activeEncodings;
|
|
88
87
|
this.encodings.l = activeEncodings.some((e) => e === 'l');
|
|
89
88
|
this.encodings.m = activeEncodings.some((e) => e === 'm');
|
|
@@ -97,14 +96,7 @@ export class LocalTrack {
|
|
|
97
96
|
throw new Error(`There is no active RTCPeerConnection`);
|
|
98
97
|
this.connection.removeTrack(this.sender);
|
|
99
98
|
};
|
|
100
|
-
|
|
101
|
-
// Metadata are updated after `await this.sender.replaceTrack(newTrack)`,
|
|
102
|
-
// so it could be chained by the user.
|
|
103
|
-
replaceTrack = async (newTrack,
|
|
104
|
-
// todo remove webrtc with newTrackMetadata
|
|
105
|
-
newTrackMetadata,
|
|
106
|
-
// todo remove webrtc with newTrackMetadata
|
|
107
|
-
webrtc) => {
|
|
99
|
+
replaceTrack = async (newTrack, webrtc) => {
|
|
108
100
|
const trackId = this.id;
|
|
109
101
|
const stream = this.trackContext.stream;
|
|
110
102
|
if (!this.sender)
|
|
@@ -131,11 +123,8 @@ export class LocalTrack {
|
|
|
131
123
|
try {
|
|
132
124
|
await this.sender.replaceTrack(newTrack);
|
|
133
125
|
this.trackContext.track = newTrack;
|
|
134
|
-
if (newTrackMetadata) {
|
|
135
|
-
webrtc.updateTrackMetadata(trackId, newTrackMetadata);
|
|
136
|
-
}
|
|
137
126
|
}
|
|
138
|
-
catch (
|
|
127
|
+
catch (_error) {
|
|
139
128
|
// ignore
|
|
140
129
|
}
|
|
141
130
|
};
|
|
@@ -209,8 +198,7 @@ export class LocalTrack {
|
|
|
209
198
|
throw new Error(`RTCRtpSender for track ${this.id} not found`);
|
|
210
199
|
const encodings = this.sender.getParameters().encodings;
|
|
211
200
|
if (this.isNotSimulcastTrack(encodings)) {
|
|
212
|
-
return
|
|
213
|
-
(kind ? defaultBitrates[kind] : UNLIMITED_BANDWIDTH));
|
|
201
|
+
return encodings[0].maxBitrate || (kind ? defaultBitrates[kind] : UNLIMITED_BANDWIDTH);
|
|
214
202
|
}
|
|
215
203
|
else if (kind === 'audio') {
|
|
216
204
|
throw 'Audio track cannot have multiple encodings';
|
|
@@ -31,7 +31,7 @@ export declare class LocalTrackManager<EndpointMetadata, TrackMetadata> {
|
|
|
31
31
|
parseAddTrack: (track: MediaStreamTrack, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit) => void;
|
|
32
32
|
addTrackHandler: (trackId: string, track: MediaStreamTrack, stream: MediaStream, trackMetadata: TrackMetadata | undefined, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit) => void;
|
|
33
33
|
removeTrackHandler: (trackId: string) => void;
|
|
34
|
-
replaceTrackHandler: (webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>, trackId: string, newTrack: MediaStreamTrack | null
|
|
34
|
+
replaceTrackHandler: (webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>, trackId: string, newTrack: MediaStreamTrack | null) => Promise<void>;
|
|
35
35
|
getEndpointId: () => string;
|
|
36
36
|
updateConnection: (connection: ConnectionManager) => void;
|
|
37
37
|
updateSenders: () => void;
|
|
@@ -59,19 +59,14 @@ export class LocalTrackManager {
|
|
|
59
59
|
this.ongoingRenegotiation = true;
|
|
60
60
|
this.local.removeTrack(trackId);
|
|
61
61
|
};
|
|
62
|
-
replaceTrackHandler = async (
|
|
63
|
-
// todo remove webrtc with newTrackMetadata
|
|
64
|
-
webrtc, trackId, newTrack,
|
|
65
|
-
// todo remove webrtc with newTrackMetadata
|
|
66
|
-
newTrackMetadata) => {
|
|
62
|
+
replaceTrackHandler = async (webrtc, trackId, newTrack) => {
|
|
67
63
|
this.ongoingTrackReplacement = true;
|
|
68
64
|
try {
|
|
69
|
-
await this.local.replaceTrack(webrtc, trackId, newTrack
|
|
65
|
+
await this.local.replaceTrack(webrtc, trackId, newTrack);
|
|
70
66
|
}
|
|
71
|
-
|
|
67
|
+
finally {
|
|
72
68
|
this.ongoingTrackReplacement = false;
|
|
73
69
|
}
|
|
74
|
-
this.ongoingTrackReplacement = false;
|
|
75
70
|
};
|
|
76
71
|
getEndpointId = () => this.local.getEndpoint().id;
|
|
77
72
|
updateConnection = (connection) => {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { splitBandwidth } from './bandwidth';
|
|
2
2
|
export const getEncodingParameters = (parameters, bandwidth) => {
|
|
3
3
|
const unlimitedEncodings = [{}];
|
|
4
|
-
return parameters.encodings.length === 0
|
|
5
|
-
? unlimitedEncodings
|
|
6
|
-
: splitBandwidth(parameters.encodings, bandwidth);
|
|
4
|
+
return parameters.encodings.length === 0 ? unlimitedEncodings : splitBandwidth(parameters.encodings, bandwidth);
|
|
7
5
|
};
|
|
@@ -16,8 +16,7 @@ const createAudioTransceiverConfig = (stream) => {
|
|
|
16
16
|
const createVideoTransceiverConfig = (trackContext, maxBandwidth) => {
|
|
17
17
|
if (!trackContext.simulcastConfig)
|
|
18
18
|
throw new Error(`Simulcast config for track ${trackContext.trackId} not found.`);
|
|
19
|
-
if (typeof maxBandwidth !== 'number' &&
|
|
20
|
-
trackContext.simulcastConfig.enabled) {
|
|
19
|
+
if (typeof maxBandwidth !== 'number' && trackContext.simulcastConfig.enabled) {
|
|
21
20
|
return createSimulcastTransceiverConfig(trackContext, maxBandwidth);
|
|
22
21
|
}
|
|
23
22
|
if (typeof maxBandwidth === 'number') {
|
|
@@ -243,7 +243,6 @@ export interface WebRTCEndpointEvents<EndpointMetadata, TrackMetadata> {
|
|
|
243
243
|
localTrackReplaced: (event: {
|
|
244
244
|
trackId: string;
|
|
245
245
|
track: MediaStreamTrack | null;
|
|
246
|
-
metadata?: TrackMetadata;
|
|
247
246
|
}) => void;
|
|
248
247
|
localTrackMuted: (event: {
|
|
249
248
|
trackId: string;
|
|
@@ -127,8 +127,6 @@ export declare class WebRTCEndpoint<EndpointMetadata = any, TrackMetadata = any>
|
|
|
127
127
|
* @param trackId - Audio or video track.
|
|
128
128
|
* @param {string} trackId - Id of audio or video track to replace.
|
|
129
129
|
* @param {MediaStreamTrack} newTrack
|
|
130
|
-
* @param {any} [newTrackMetadata] - Optional track metadata to apply to the new track. If no
|
|
131
|
-
* track metadata is passed, the old track metadata is retained.
|
|
132
130
|
* @returns {Promise<boolean>} success
|
|
133
131
|
* @example
|
|
134
132
|
* ```ts
|
|
@@ -169,7 +167,7 @@ export declare class WebRTCEndpoint<EndpointMetadata = any, TrackMetadata = any>
|
|
|
169
167
|
* })
|
|
170
168
|
* ```
|
|
171
169
|
*/
|
|
172
|
-
replaceTrack(trackId: string, newTrack: MediaStreamTrack | null
|
|
170
|
+
replaceTrack(trackId: string, newTrack: MediaStreamTrack | null): Promise<void>;
|
|
173
171
|
/**
|
|
174
172
|
* Updates maximum bandwidth for the track identified by trackId.
|
|
175
173
|
* This value directly translates to quality of the stream and, in case of video, to the amount of RTP packets being sent.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deserializeMediaEvent, generateCustomEvent, generateMediaEvent, serializeMediaEvent
|
|
1
|
+
import { deserializeMediaEvent, generateCustomEvent, generateMediaEvent, serializeMediaEvent } from './mediaEvent';
|
|
2
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
import { Deferred } from './deferred';
|
|
@@ -23,10 +23,8 @@ export class WebRTCEndpoint extends EventEmitter {
|
|
|
23
23
|
clearConnectionCallbacks = null;
|
|
24
24
|
constructor(config) {
|
|
25
25
|
super();
|
|
26
|
-
this.endpointMetadataParser =
|
|
27
|
-
|
|
28
|
-
this.trackMetadataParser =
|
|
29
|
-
config?.trackMetadataParser ?? ((x) => x);
|
|
26
|
+
this.endpointMetadataParser = config?.endpointMetadataParser ?? ((x) => x);
|
|
27
|
+
this.trackMetadataParser = config?.trackMetadataParser ?? ((x) => x);
|
|
30
28
|
const sendEvent = (mediaEvent) => this.sendMediaEvent(mediaEvent);
|
|
31
29
|
const emit = (events, ...args) => {
|
|
32
30
|
this.emit(events, ...args);
|
|
@@ -77,8 +75,7 @@ export class WebRTCEndpoint extends EventEmitter {
|
|
|
77
75
|
switch (deserializedMediaEvent.type) {
|
|
78
76
|
case 'connected': {
|
|
79
77
|
this.local.setLocalEndpointId(deserializedMediaEvent.data.id);
|
|
80
|
-
const endpoints = deserializedMediaEvent.data
|
|
81
|
-
.otherEndpoints;
|
|
78
|
+
const endpoints = deserializedMediaEvent.data.otherEndpoints;
|
|
82
79
|
// todo implement track mapping (+ validate metadata)
|
|
83
80
|
// todo implement endpoint metadata mapping
|
|
84
81
|
endpoints.forEach((endpoint) => {
|
|
@@ -113,8 +110,7 @@ export class WebRTCEndpoint extends EventEmitter {
|
|
|
113
110
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/getStats | MDN Web Docs: RTCPeerConnection.getStats()}
|
|
114
111
|
*/
|
|
115
112
|
async getStatistics(selector) {
|
|
116
|
-
return (
|
|
117
|
-
new Map());
|
|
113
|
+
return (await this.connectionManager?.getConnection().getStats(selector)) ?? new Map();
|
|
118
114
|
}
|
|
119
115
|
/**
|
|
120
116
|
* Returns a snapshot of currently received remote tracks.
|
|
@@ -241,15 +237,13 @@ export class WebRTCEndpoint extends EventEmitter {
|
|
|
241
237
|
onSdpAnswer = async (data) => {
|
|
242
238
|
this.remote.updateMLineIds(data.midToTrackId);
|
|
243
239
|
this.local.updateMLineIds(data.midToTrackId);
|
|
244
|
-
Object.
|
|
245
|
-
.map((
|
|
246
|
-
if (!
|
|
240
|
+
Object.keys(data.midToTrackId)
|
|
241
|
+
.map((mid) => {
|
|
242
|
+
if (!mid)
|
|
247
243
|
throw new Error('TrackId is not defined');
|
|
248
|
-
|
|
249
|
-
throw new Error('TrackId is not a string');
|
|
250
|
-
return trackId;
|
|
244
|
+
return mid;
|
|
251
245
|
})
|
|
252
|
-
.map((
|
|
246
|
+
.map((mid) => this.local.getTrackByMidOrNull(mid))
|
|
253
247
|
.filter((localTrack) => localTrack !== null)
|
|
254
248
|
.forEach((localTrack) => {
|
|
255
249
|
const trackContext = localTrack.trackContext;
|
|
@@ -361,8 +355,6 @@ export class WebRTCEndpoint extends EventEmitter {
|
|
|
361
355
|
* @param trackId - Audio or video track.
|
|
362
356
|
* @param {string} trackId - Id of audio or video track to replace.
|
|
363
357
|
* @param {MediaStreamTrack} newTrack
|
|
364
|
-
* @param {any} [newTrackMetadata] - Optional track metadata to apply to the new track. If no
|
|
365
|
-
* track metadata is passed, the old track metadata is retained.
|
|
366
358
|
* @returns {Promise<boolean>} success
|
|
367
359
|
* @example
|
|
368
360
|
* ```ts
|
|
@@ -403,29 +395,17 @@ export class WebRTCEndpoint extends EventEmitter {
|
|
|
403
395
|
* })
|
|
404
396
|
* ```
|
|
405
397
|
*/
|
|
406
|
-
async replaceTrack(trackId, newTrack
|
|
398
|
+
async replaceTrack(trackId, newTrack) {
|
|
407
399
|
const resolutionNotifier = new Deferred();
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
},
|
|
416
|
-
resolutionNotifier,
|
|
417
|
-
resolve: 'immediately',
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
resolutionNotifier.reject(error);
|
|
422
|
-
}
|
|
400
|
+
this.commandsQueue.pushCommand({
|
|
401
|
+
handler: () => {
|
|
402
|
+
this.localTrackManager.replaceTrackHandler(this, trackId, newTrack);
|
|
403
|
+
},
|
|
404
|
+
resolutionNotifier,
|
|
405
|
+
resolve: 'immediately',
|
|
406
|
+
});
|
|
423
407
|
return resolutionNotifier.promise.then(() => {
|
|
424
|
-
this.emit('localTrackReplaced', {
|
|
425
|
-
trackId,
|
|
426
|
-
track: newTrack,
|
|
427
|
-
metadata: newTrackMetadata,
|
|
428
|
-
});
|
|
408
|
+
this.emit('localTrackReplaced', { trackId, track: newTrack });
|
|
429
409
|
});
|
|
430
410
|
}
|
|
431
411
|
/**
|
|
@@ -635,31 +615,15 @@ export class WebRTCEndpoint extends EventEmitter {
|
|
|
635
615
|
if (!connection)
|
|
636
616
|
throw new Error(`There is no active RTCPeerConnection`);
|
|
637
617
|
this.clearConnectionCallbacks = () => {
|
|
638
|
-
connection
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
connection
|
|
642
|
-
?.getConnection()
|
|
643
|
-
?.removeEventListener('icecandidateerror', onIceCandidateError);
|
|
644
|
-
connection
|
|
645
|
-
?.getConnection()
|
|
646
|
-
?.removeEventListener('connectionstatechange', onConnectionStateChange);
|
|
647
|
-
connection
|
|
648
|
-
?.getConnection()
|
|
649
|
-
?.removeEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
618
|
+
connection?.getConnection()?.removeEventListener('icecandidate', onIceCandidate);
|
|
619
|
+
connection?.getConnection()?.removeEventListener('icecandidateerror', onIceCandidateError);
|
|
620
|
+
connection?.getConnection()?.removeEventListener('connectionstatechange', onConnectionStateChange);
|
|
621
|
+
connection?.getConnection()?.removeEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
650
622
|
};
|
|
651
|
-
connection
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
connection
|
|
655
|
-
.getConnection()
|
|
656
|
-
.addEventListener('icecandidateerror', onIceCandidateError);
|
|
657
|
-
connection
|
|
658
|
-
.getConnection()
|
|
659
|
-
.addEventListener('connectionstatechange', onConnectionStateChange);
|
|
660
|
-
connection
|
|
661
|
-
.getConnection()
|
|
662
|
-
.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
623
|
+
connection.getConnection().addEventListener('icecandidate', onIceCandidate);
|
|
624
|
+
connection.getConnection().addEventListener('icecandidateerror', onIceCandidateError);
|
|
625
|
+
connection.getConnection().addEventListener('connectionstatechange', onConnectionStateChange);
|
|
626
|
+
connection.getConnection().addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
663
627
|
this.commandsQueue.initConnection(connection);
|
|
664
628
|
this.local.addAllTracksToConnection();
|
|
665
629
|
connection.setTransceiversToReadOnly();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishjam-cloud/ts-client",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Typescript client library for Fishjam Cloud",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Fishjam Cloud Team",
|
|
@@ -52,22 +52,22 @@
|
|
|
52
52
|
"uuid": "^10.0.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@playwright/test": "^1.
|
|
55
|
+
"@playwright/test": "^1.46.1",
|
|
56
56
|
"@types/events": "^3.0.3",
|
|
57
|
-
"@types/node": "^22.
|
|
57
|
+
"@types/node": "^22.4.1",
|
|
58
58
|
"@types/uuid": "^10.0.0",
|
|
59
|
-
"@vitest/coverage-v8": "^2.0.
|
|
59
|
+
"@vitest/coverage-v8": "^2.0.5",
|
|
60
60
|
"fake-mediastreamtrack": "^1.2.0",
|
|
61
61
|
"husky": "^9.1.4",
|
|
62
|
-
"lint-staged": "^15.2.
|
|
62
|
+
"lint-staged": "^15.2.9",
|
|
63
63
|
"react": "^18.2.0",
|
|
64
|
-
"ts-proto": "^
|
|
64
|
+
"ts-proto": "^2.0.2",
|
|
65
65
|
"typed-emitter": "^2.1.0",
|
|
66
|
-
"typedoc": "^0.26.
|
|
66
|
+
"typedoc": "^0.26.6",
|
|
67
67
|
"typedoc-plugin-external-resolver": "^1.0.3",
|
|
68
|
-
"typedoc-plugin-mdn-links": "^3.2.
|
|
68
|
+
"typedoc-plugin-mdn-links": "^3.2.10",
|
|
69
69
|
"typescript": "^5.5.4",
|
|
70
|
-
"vitest": "^2.0.
|
|
70
|
+
"vitest": "^2.0.5",
|
|
71
71
|
"zod": "^3.23.6"
|
|
72
72
|
},
|
|
73
73
|
"lint-staged": {
|