@fishjam-cloud/webrtc-client 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +142 -0
- package/README.md +30 -0
- package/dist/index.d.mts +761 -0
- package/dist/index.mjs +4952 -0
- package/dist/protobufs/fishjam/media_events/peer/peer.d.ts +125 -0
- package/dist/protobufs/fishjam/media_events/peer/peer.js +1214 -0
- package/dist/protobufs/fishjam/media_events/server/server.d.ts +197 -0
- package/dist/protobufs/fishjam/media_events/server/server.js +2121 -0
- package/dist/protobufs/fishjam/media_events/shared.d.ts +38 -0
- package/dist/protobufs/fishjam/media_events/shared.js +152 -0
- package/dist/webrtc-client/src/CommandsQueue.d.ts +23 -0
- package/dist/webrtc-client/src/CommandsQueue.js +87 -0
- package/dist/webrtc-client/src/ConnectionManager.d.ts +16 -0
- package/dist/webrtc-client/src/ConnectionManager.js +46 -0
- package/dist/webrtc-client/src/bitrate.d.ts +12 -0
- package/dist/webrtc-client/src/bitrate.js +15 -0
- package/dist/webrtc-client/src/deferred.d.ts +8 -0
- package/dist/webrtc-client/src/deferred.js +17 -0
- package/dist/webrtc-client/src/index.d.ts +5 -0
- package/dist/webrtc-client/src/index.js +3 -0
- package/dist/webrtc-client/src/internal.d.ts +27 -0
- package/dist/webrtc-client/src/internal.js +18 -0
- package/dist/webrtc-client/src/mediaEvent.d.ts +13 -0
- package/dist/webrtc-client/src/mediaEvent.js +20 -0
- package/dist/webrtc-client/src/tracks/Local.d.ts +49 -0
- package/dist/webrtc-client/src/tracks/Local.js +243 -0
- package/dist/webrtc-client/src/tracks/LocalTrack.d.ts +61 -0
- package/dist/webrtc-client/src/tracks/LocalTrack.js +212 -0
- package/dist/webrtc-client/src/tracks/LocalTrackManager.d.ts +39 -0
- package/dist/webrtc-client/src/tracks/LocalTrackManager.js +76 -0
- package/dist/webrtc-client/src/tracks/Remote.d.ts +32 -0
- package/dist/webrtc-client/src/tracks/Remote.js +165 -0
- package/dist/webrtc-client/src/tracks/RemoteTrack.d.ts +40 -0
- package/dist/webrtc-client/src/tracks/RemoteTrack.js +71 -0
- package/dist/webrtc-client/src/tracks/TrackCommon.d.ts +9 -0
- package/dist/webrtc-client/src/tracks/TrackCommon.js +1 -0
- package/dist/webrtc-client/src/tracks/bandwidth.d.ts +1 -0
- package/dist/webrtc-client/src/tracks/bandwidth.js +27 -0
- package/dist/webrtc-client/src/tracks/encodings.d.ts +3 -0
- package/dist/webrtc-client/src/tracks/encodings.js +11 -0
- package/dist/webrtc-client/src/tracks/muteTrackUtils.d.ts +3 -0
- package/dist/webrtc-client/src/tracks/muteTrackUtils.js +18 -0
- package/dist/webrtc-client/src/tracks/transceivers.d.ts +2 -0
- package/dist/webrtc-client/src/tracks/transceivers.js +93 -0
- package/dist/webrtc-client/src/types.d.ts +259 -0
- package/dist/webrtc-client/src/types.js +1 -0
- package/dist/webrtc-client/src/webRTCEndpoint.d.ts +298 -0
- package/dist/webrtc-client/src/webRTCEndpoint.js +659 -0
- package/dist/webrtc-client/tests/events/bandwidthEstimationEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/bandwidthEstimationEvent.test.js +21 -0
- package/dist/webrtc-client/tests/events/connectedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/connectedEvent.test.js +70 -0
- package/dist/webrtc-client/tests/events/encodingSwitchedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/encodingSwitchedEvent.test.js +60 -0
- package/dist/webrtc-client/tests/events/endpointAddedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/endpointAddedEvent.test.js +57 -0
- package/dist/webrtc-client/tests/events/endpointRemovedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/endpointRemovedEvent.test.js +64 -0
- package/dist/webrtc-client/tests/events/endpointUpdatedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/endpointUpdatedEvent.test.js +78 -0
- package/dist/webrtc-client/tests/events/trackAddedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/trackAddedEvent.test.js +84 -0
- package/dist/webrtc-client/tests/events/trackRemovedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/trackRemovedEvent.test.js +26 -0
- package/dist/webrtc-client/tests/events/trackUpdatedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/trackUpdatedEvent.test.js +77 -0
- package/dist/webrtc-client/tests/events/vadNotificationEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/vadNotificationEvent.test.js +42 -0
- package/dist/webrtc-client/tests/fixtures.d.ts +27 -0
- package/dist/webrtc-client/tests/fixtures.js +172 -0
- package/dist/webrtc-client/tests/methods/addTrackMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/addTrackMethod.test.js +46 -0
- package/dist/webrtc-client/tests/methods/cleanUpMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/cleanUpMethod.test.js +14 -0
- package/dist/webrtc-client/tests/methods/connectMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/connectMethod.test.js +40 -0
- package/dist/webrtc-client/tests/methods/disconnectMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/disconnectMethod.test.js +29 -0
- package/dist/webrtc-client/tests/mocks.d.ts +5 -0
- package/dist/webrtc-client/tests/mocks.js +80 -0
- package/dist/webrtc-client/tests/utils.d.ts +3 -0
- package/dist/webrtc-client/tests/utils.js +56 -0
- package/package.json +80 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import { MediaEvent_Connected, MediaEvent_Endpoint, MediaEvent_EndpointAdded, MediaEvent_EndpointRemoved, MediaEvent_EndpointUpdated, MediaEvent_OfferData, MediaEvent_SdpAnswer, MediaEvent_TracksAdded, MediaEvent_TracksRemoved, MediaEvent_TrackUpdated, MediaEvent_TrackVariantSwitched, MediaEvent_VadNotification, } from '@fishjam-cloud/protobufs/server';
|
|
3
|
+
import { FakeMediaStreamTrack } from 'fake-mediastreamtrack';
|
|
4
|
+
import { vi } from 'vitest';
|
|
5
|
+
import { Variant } from '../src';
|
|
6
|
+
export const exampleEndpointId = 'exampleEndpointId';
|
|
7
|
+
export const notExistingEndpointId = 'notExistingEndpointId';
|
|
8
|
+
export const exampleTrackId = 'exampleTrackId';
|
|
9
|
+
export const notExistingTrackId = 'notExistingTrackId';
|
|
10
|
+
export const mockTrack = new FakeMediaStreamTrack({ kind: 'video' });
|
|
11
|
+
const MediaStreamMock = vi.fn().mockImplementation(() => { });
|
|
12
|
+
export const stream = new MediaStreamMock();
|
|
13
|
+
export const createTrackWithSimulcast = (metadata = undefined) => ({
|
|
14
|
+
metadataJson: JSON.stringify(metadata),
|
|
15
|
+
simulcastConfig: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
enabledVariants: [Variant.VARIANT_LOW, Variant.VARIANT_MEDIUM, Variant.VARIANT_HIGH],
|
|
18
|
+
disabledVariants: [],
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
export const createEmptyEndpoint = () => MediaEvent_Endpoint.create({ trackIdToTrack: {}, endpointType: 'webrtc' });
|
|
22
|
+
export const createConnectedEvent = (endpointId = faker.string.uuid()) => {
|
|
23
|
+
return MediaEvent_Connected.create({ endpointId, endpointIdToEndpoint: { [endpointId]: createEmptyEndpoint() } });
|
|
24
|
+
};
|
|
25
|
+
export const createEncodingSwitchedEvent = (endpointId, trackId, variant) => MediaEvent_TrackVariantSwitched.create({
|
|
26
|
+
variant,
|
|
27
|
+
endpointId,
|
|
28
|
+
trackId,
|
|
29
|
+
});
|
|
30
|
+
// export const createBandwidthEstimationEvent = (): CustomBandwidthEstimationEvent =>
|
|
31
|
+
// CustomBandwidthEstimationEventSchema.parse({
|
|
32
|
+
// data: {
|
|
33
|
+
// data: {
|
|
34
|
+
// estimation: 261506.7264961106,
|
|
35
|
+
// },
|
|
36
|
+
// type: 'bandwidthEstimation',
|
|
37
|
+
// },
|
|
38
|
+
// type: 'custom',
|
|
39
|
+
// });
|
|
40
|
+
export const createCustomVadNotificationEvent = (trackId, status) => MediaEvent_VadNotification.create({
|
|
41
|
+
trackId,
|
|
42
|
+
status,
|
|
43
|
+
});
|
|
44
|
+
export const createTrackUpdatedEvent = (trackId, endpointId, metadata) => MediaEvent_TrackUpdated.create({
|
|
45
|
+
endpointId: endpointId,
|
|
46
|
+
metadataJson: JSON.stringify(metadata),
|
|
47
|
+
trackId: trackId,
|
|
48
|
+
});
|
|
49
|
+
export const createEndpointAdded = (endpointId, metadata = undefined) => MediaEvent_EndpointAdded.create({
|
|
50
|
+
endpointId,
|
|
51
|
+
metadataJson: JSON.stringify(metadata),
|
|
52
|
+
});
|
|
53
|
+
export const createEndpointRemoved = (endpointId) => MediaEvent_EndpointRemoved.create({
|
|
54
|
+
endpointId,
|
|
55
|
+
});
|
|
56
|
+
export const createConnectedEventWithOneEndpoint = (endpointId = faker.string.uuid(), localEndpointId) => {
|
|
57
|
+
const connectedEvent = createConnectedEvent(localEndpointId);
|
|
58
|
+
const endpoint = createEmptyEndpoint();
|
|
59
|
+
connectedEvent.endpointIdToEndpoint = { ...connectedEvent.endpointIdToEndpoint, [endpointId]: endpoint };
|
|
60
|
+
return MediaEvent_Connected.create(connectedEvent);
|
|
61
|
+
};
|
|
62
|
+
export const createConnectedEventWithOneEndpointWithOneTrack = (remoteEndpointId, trackId, localEndpointId) => {
|
|
63
|
+
const connectedEvent = createConnectedEvent(localEndpointId);
|
|
64
|
+
const remoteEndpoint = createEmptyEndpoint();
|
|
65
|
+
remoteEndpoint.trackIdToTrack[trackId] = createTrackWithSimulcast();
|
|
66
|
+
connectedEvent.endpointIdToEndpoint = {
|
|
67
|
+
...connectedEvent.endpointIdToEndpoint,
|
|
68
|
+
[remoteEndpointId]: remoteEndpoint,
|
|
69
|
+
};
|
|
70
|
+
return MediaEvent_Connected.create(connectedEvent);
|
|
71
|
+
};
|
|
72
|
+
export const createAddTrackMediaEvent = (endpointId, trackId, metadata = undefined) => MediaEvent_TracksAdded.create({
|
|
73
|
+
endpointId: endpointId,
|
|
74
|
+
trackIdToTrack: {
|
|
75
|
+
[trackId]: createTrackWithSimulcast(metadata),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
export const createTracksRemovedEvent = (endpointId, trackIds) => MediaEvent_TracksRemoved.create({
|
|
79
|
+
endpointId: endpointId,
|
|
80
|
+
trackIds,
|
|
81
|
+
});
|
|
82
|
+
export const createCustomOfferDataEventWithOneVideoTrack = () => MediaEvent_OfferData.create({
|
|
83
|
+
tracksTypes: {
|
|
84
|
+
audio: 0,
|
|
85
|
+
video: 1,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
export const createAddLocalTrackSDPOffer = () => MediaEvent_OfferData.create({
|
|
89
|
+
tracksTypes: {
|
|
90
|
+
audio: 0,
|
|
91
|
+
video: 0,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
export const createAnswerData = (trackId) => MediaEvent_SdpAnswer.create({
|
|
95
|
+
sdp: `v=0\r
|
|
96
|
+
o=- 39483584182226872 0 IN IP4 127.0.0.1\r
|
|
97
|
+
s=-\r
|
|
98
|
+
t=0 0\r
|
|
99
|
+
a=group:BUNDLE 0\r
|
|
100
|
+
a=extmap-allow-mixed\r
|
|
101
|
+
a=ice-lite\r
|
|
102
|
+
m=video 9 UDP/TLS/RTP/SAVPF 102 103\r
|
|
103
|
+
c=IN IP4 0.0.0.0\r
|
|
104
|
+
a=sendonly\r
|
|
105
|
+
a=ice-ufrag:fXa4\r
|
|
106
|
+
a=ice-pwd:mC2wFgKGsN3cXnxadEhVaa\r
|
|
107
|
+
a=ice-options:trickle\r
|
|
108
|
+
a=fingerprint:sha-256 50:65:CB:9F:2B:B5:62:7F:20:59:79:C6:7B:49:D8:DF:C2:B5:59:1F:E2:7D:68:F8:C3:07:73:8B:16:70:FB:DD\r
|
|
109
|
+
a=setup:passive\r
|
|
110
|
+
a=mid:0\r
|
|
111
|
+
a=msid:60ff1fb2-6868-42be-8c92-311733034415 ea1339b9-54ce-445b-9cff-2568f9ac504b\r
|
|
112
|
+
a=rtcp-mux\r
|
|
113
|
+
a=rtpmap:102 H264/90000\r
|
|
114
|
+
a=fmtp:102 profile-level-id=42001f;level-asymmetry-allowed=1;packetization-mode=1\r
|
|
115
|
+
a=rtpmap:103 rtx/90000\r
|
|
116
|
+
a=fmtp:103 apt=102\r
|
|
117
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r
|
|
118
|
+
a=rtcp-fb:102 transport-cc\r
|
|
119
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r
|
|
120
|
+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r
|
|
121
|
+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r
|
|
122
|
+
a=rtcp-fb:102 ccm fir\r
|
|
123
|
+
a=rtcp-fb:102 nack\r
|
|
124
|
+
a=rtcp-fb:102 nack pli\r
|
|
125
|
+
a=rtcp-rsize\r
|
|
126
|
+
a=ssrc:663086196 cname:${trackId}-video-60ff1fb2-6868-42be-8c92-311733034415\r
|
|
127
|
+
`,
|
|
128
|
+
midToTrackId: {
|
|
129
|
+
'0': '9afe80ce-1964-4958-a386-d7a9e3097ca7:5c74b6b3-cb72-49f1-a76b-0df4895a3d32',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
export const createAddLocalTrackAnswerData = (trackId) => MediaEvent_SdpAnswer.create({
|
|
133
|
+
midToTrackId: {
|
|
134
|
+
'0': trackId,
|
|
135
|
+
},
|
|
136
|
+
sdp: `v=0\r
|
|
137
|
+
o=- 63903156084304368 0 IN IP4 127.0.0.1\r
|
|
138
|
+
s=-\r
|
|
139
|
+
t=0 0\r
|
|
140
|
+
a=group:BUNDLE 0\r
|
|
141
|
+
a=extmap-allow-mixed\r
|
|
142
|
+
a=ice-lite\r
|
|
143
|
+
m=video 9 UDP/TLS/RTP/SAVPF 106 107\r
|
|
144
|
+
c=IN IP4 0.0.0.0\r
|
|
145
|
+
a=recvonly\r
|
|
146
|
+
a=ice-ufrag:dHiY\r
|
|
147
|
+
a=ice-pwd:IAPCE68QAQ8AxSF0OQIEZp\r
|
|
148
|
+
a=ice-options:trickle\r
|
|
149
|
+
a=fingerprint:sha-256 C1:50:4C:EC:98:1D:62:C8:DA:AE:F8:5B:44:4F:76:BB:4E:FF:5E:51:3E:A7:62:9B:58:38:A5:13:D0:B1:50:67\r
|
|
150
|
+
a=setup:passive\r
|
|
151
|
+
a=mid:0\r
|
|
152
|
+
a=msid:7bf8bef4-be67-456c-8635-ba58339c29e9 ad3deb09-60a6-4bfc-aa14-482ed4f60667\r
|
|
153
|
+
a=rtcp-mux\r
|
|
154
|
+
a=rtpmap:106 H264/90000\r
|
|
155
|
+
a=fmtp:106 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\r
|
|
156
|
+
a=rtpmap:107 rtx/90000\r
|
|
157
|
+
a=fmtp:107 apt=106\r
|
|
158
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r
|
|
159
|
+
a=rtcp-fb:106 transport-cc\r
|
|
160
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r
|
|
161
|
+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r
|
|
162
|
+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r
|
|
163
|
+
a=rtcp-fb:106 ccm fir\r
|
|
164
|
+
a=rtcp-fb:106 nack\r
|
|
165
|
+
a=rtcp-fb:106 nack pli\r
|
|
166
|
+
a=rtcp-rsize\r
|
|
167
|
+
`,
|
|
168
|
+
});
|
|
169
|
+
export const createEndpointUpdatedPeerMetadata = (endpointId, metadata) => MediaEvent_EndpointUpdated.create({
|
|
170
|
+
endpointId,
|
|
171
|
+
metadataJson: JSON.stringify(metadata),
|
|
172
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { WebRTCEndpoint } from '../../src';
|
|
3
|
+
import { deserializePeerMediaEvent, serializeServerMediaEvent } from '../../src/mediaEvent';
|
|
4
|
+
import { createConnectedEventWithOneEndpoint, mockTrack } from '../fixtures';
|
|
5
|
+
import { mockMediaStream, mockRTCPeerConnection } from '../mocks';
|
|
6
|
+
it('Adding track invokes renegotiation', () => new Promise((done) => {
|
|
7
|
+
// Given
|
|
8
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
9
|
+
mockMediaStream();
|
|
10
|
+
const serializedEvent = serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint() });
|
|
11
|
+
webRTCEndpoint.receiveMediaEvent(serializedEvent);
|
|
12
|
+
webRTCEndpoint.on('sendMediaEvent', (mediaEvent) => {
|
|
13
|
+
// Then
|
|
14
|
+
const event = deserializePeerMediaEvent(mediaEvent);
|
|
15
|
+
expect(event.renegotiateTracks).toBeTruthy();
|
|
16
|
+
done('');
|
|
17
|
+
// now it's time to create offer and answer
|
|
18
|
+
// webRTCEndpoint.receiveMediaEvent(JSON.stringify(createOfferData()))
|
|
19
|
+
// webRTCEndpoint.receiveMediaEvent(JSON.stringify(createAnswerData("9bf0cc85-c795-43b2-baf1-2c974cd770b9:1b6d99d1-3630-4e01-b386-15cbbfe5a41f")))
|
|
20
|
+
});
|
|
21
|
+
// When
|
|
22
|
+
webRTCEndpoint.addTrack(mockTrack);
|
|
23
|
+
}));
|
|
24
|
+
it('Adding track updates internal state', () => {
|
|
25
|
+
// Given
|
|
26
|
+
mockRTCPeerConnection();
|
|
27
|
+
mockMediaStream();
|
|
28
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
29
|
+
const serializedEvent = serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint() });
|
|
30
|
+
webRTCEndpoint.receiveMediaEvent(serializedEvent);
|
|
31
|
+
// When
|
|
32
|
+
webRTCEndpoint.addTrack(mockTrack);
|
|
33
|
+
// Then
|
|
34
|
+
const localTrackIdToTrack = webRTCEndpoint['local'].getTrackIdToTrack();
|
|
35
|
+
expect(localTrackIdToTrack.size).toBe(1);
|
|
36
|
+
const localEndpoint = webRTCEndpoint['local'].getEndpoint();
|
|
37
|
+
expect(localEndpoint.tracks.size).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
it('Adding track before being accepted by the server throws error', async () => {
|
|
40
|
+
// Given
|
|
41
|
+
mockRTCPeerConnection();
|
|
42
|
+
mockMediaStream();
|
|
43
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
44
|
+
// When
|
|
45
|
+
await expect(() => webRTCEndpoint.addTrack(mockTrack)).rejects.toThrow('Cannot add tracks before being accepted by the server');
|
|
46
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { WebRTCEndpoint } from '../../src';
|
|
3
|
+
import { exampleEndpointId, exampleTrackId } from '../fixtures';
|
|
4
|
+
import { setupRoomWithMocks } from '../utils';
|
|
5
|
+
it('CleanUp sets connection to undefined', async () => {
|
|
6
|
+
// Given
|
|
7
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
8
|
+
await setupRoomWithMocks(webRTCEndpoint, exampleEndpointId, exampleTrackId);
|
|
9
|
+
// When
|
|
10
|
+
webRTCEndpoint.cleanUp();
|
|
11
|
+
// Then
|
|
12
|
+
const connection = webRTCEndpoint['connectionManager'];
|
|
13
|
+
expect(connection).toBe(undefined);
|
|
14
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { WebRTCEndpoint } from '../../src';
|
|
3
|
+
import { deserializePeerMediaEvent } from '../../src/mediaEvent';
|
|
4
|
+
it('Method connect sends mediaEvent to backend', () => new Promise((done) => {
|
|
5
|
+
// Given
|
|
6
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
7
|
+
const peerMetadata = {};
|
|
8
|
+
webRTCEndpoint.on('sendMediaEvent', (mediaEvent) => {
|
|
9
|
+
// Then
|
|
10
|
+
const event = deserializePeerMediaEvent(mediaEvent);
|
|
11
|
+
expect(event.connect).toBeTruthy();
|
|
12
|
+
done('');
|
|
13
|
+
});
|
|
14
|
+
// When
|
|
15
|
+
webRTCEndpoint.connect(peerMetadata);
|
|
16
|
+
}));
|
|
17
|
+
it("Method 'connect' sends metadata in event", () => new Promise((done) => {
|
|
18
|
+
// Given
|
|
19
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
20
|
+
const peerMetadata = { exampleField: 'exampleValue' };
|
|
21
|
+
webRTCEndpoint.on('sendMediaEvent', (mediaEvent) => {
|
|
22
|
+
// Then
|
|
23
|
+
const event = deserializePeerMediaEvent(mediaEvent);
|
|
24
|
+
const metadataJson = event.connect?.metadataJson;
|
|
25
|
+
expect(metadataJson).toBeDefined();
|
|
26
|
+
expect(JSON.parse(metadataJson)).toMatchObject(peerMetadata);
|
|
27
|
+
done('');
|
|
28
|
+
});
|
|
29
|
+
// When
|
|
30
|
+
webRTCEndpoint.connect(peerMetadata);
|
|
31
|
+
}));
|
|
32
|
+
it("Method 'connect' sets metadata in local field", () => {
|
|
33
|
+
// Given
|
|
34
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
35
|
+
const peerMetadata = { exampleField: 'exampleValue' };
|
|
36
|
+
// When
|
|
37
|
+
webRTCEndpoint.connect(peerMetadata);
|
|
38
|
+
// Then
|
|
39
|
+
expect(webRTCEndpoint['local'].getEndpoint().metadata).toMatchObject(peerMetadata);
|
|
40
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { WebRTCEndpoint } from '../../src';
|
|
3
|
+
import { deserializePeerMediaEvent } from '../../src/mediaEvent';
|
|
4
|
+
import { exampleEndpointId, exampleTrackId } from '../fixtures';
|
|
5
|
+
import { setupRoomWithMocks } from '../utils';
|
|
6
|
+
it('Disconnect sets connection to undefined', async () => {
|
|
7
|
+
// Given
|
|
8
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
9
|
+
await setupRoomWithMocks(webRTCEndpoint, exampleEndpointId, exampleTrackId);
|
|
10
|
+
// When
|
|
11
|
+
webRTCEndpoint.disconnect();
|
|
12
|
+
// Then
|
|
13
|
+
const connection = webRTCEndpoint['connectionManager'];
|
|
14
|
+
expect(connection).toBe(undefined);
|
|
15
|
+
});
|
|
16
|
+
it('Disconnect invokes disconnected event', () => new Promise((done) => {
|
|
17
|
+
(async () => {
|
|
18
|
+
// Given
|
|
19
|
+
const webRTCEndpoint = new WebRTCEndpoint();
|
|
20
|
+
await setupRoomWithMocks(webRTCEndpoint, exampleEndpointId, exampleTrackId);
|
|
21
|
+
webRTCEndpoint.on('sendMediaEvent', (mediaEvent) => {
|
|
22
|
+
const event = deserializePeerMediaEvent(mediaEvent);
|
|
23
|
+
expect(event.disconnect).toBeTruthy();
|
|
24
|
+
done('');
|
|
25
|
+
});
|
|
26
|
+
// When
|
|
27
|
+
webRTCEndpoint.disconnect();
|
|
28
|
+
})();
|
|
29
|
+
}));
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { undefined } from 'zod';
|
|
3
|
+
export const mockMediaStream = () => {
|
|
4
|
+
global.MediaStream = vi.fn().mockImplementation(() => {
|
|
5
|
+
return {
|
|
6
|
+
addTrack: () => { },
|
|
7
|
+
};
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export const mockRTCPeerConnection = () => {
|
|
11
|
+
const addTransceiverCallback = vi.fn((_trackOrKind, _init) => null);
|
|
12
|
+
global.RTCPeerConnection = vi.fn().mockImplementation(() => {
|
|
13
|
+
const transceivers = [];
|
|
14
|
+
const senders = [];
|
|
15
|
+
const newVar = {
|
|
16
|
+
getTransceivers: () => {
|
|
17
|
+
return transceivers;
|
|
18
|
+
},
|
|
19
|
+
addTransceiver: (trackOrKind, init) => {
|
|
20
|
+
addTransceiverCallback(trackOrKind, init);
|
|
21
|
+
const sender = {};
|
|
22
|
+
sender.getParameters = () => {
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
const encodings = [{}];
|
|
25
|
+
return { encodings: encodings };
|
|
26
|
+
};
|
|
27
|
+
if (init?.direction === 'sendonly') {
|
|
28
|
+
sender.track = typeof trackOrKind !== 'string' ? trackOrKind : { id: 'someTrackId' };
|
|
29
|
+
}
|
|
30
|
+
senders.push(sender);
|
|
31
|
+
// maybe move to callback declaration
|
|
32
|
+
const transceiver = {
|
|
33
|
+
currentDirection: null,
|
|
34
|
+
direction: init?.direction ?? 'inactive',
|
|
35
|
+
mid: 'someTransceiverMid',
|
|
36
|
+
receiver: {},
|
|
37
|
+
sender: sender,
|
|
38
|
+
setCodecPreferences: (_codecs) => { },
|
|
39
|
+
stop: () => { },
|
|
40
|
+
};
|
|
41
|
+
transceivers.push(transceiver);
|
|
42
|
+
return transceiver;
|
|
43
|
+
},
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
createOffer: (_options) => {
|
|
46
|
+
// const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
return new Promise((resolve, _reject) => {
|
|
48
|
+
resolve({ sdp: '', type: 'offer' });
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
setLocalDescription: async (_description) => { },
|
|
52
|
+
setRemoteDescription: async (_description) => { },
|
|
53
|
+
getSenders: () => {
|
|
54
|
+
return senders;
|
|
55
|
+
},
|
|
56
|
+
close: () => { },
|
|
57
|
+
addEventListener: () => { },
|
|
58
|
+
removeEventListener: () => { },
|
|
59
|
+
};
|
|
60
|
+
return newVar;
|
|
61
|
+
});
|
|
62
|
+
global.RTCRtpTransceiver = vi.fn().mockImplementation(() => {
|
|
63
|
+
const newVar = {
|
|
64
|
+
currentDirection: null,
|
|
65
|
+
direction: 'stopped',
|
|
66
|
+
mid: '0',
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
receiver: undefined,
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
sender: undefined,
|
|
71
|
+
setCodecPreferences(_codecs) { },
|
|
72
|
+
stop() { },
|
|
73
|
+
};
|
|
74
|
+
return newVar;
|
|
75
|
+
});
|
|
76
|
+
const runOnTrack = (ev) => {
|
|
77
|
+
global.RTCPeerConnection.ontrack(ev);
|
|
78
|
+
};
|
|
79
|
+
return { addTransceiverCallback, runOnTrack };
|
|
80
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WebRTCEndpoint } from '../src';
|
|
2
|
+
export declare const setupRoom: (webRTCEndpoint: WebRTCEndpoint, endpointId: string, trackId: string) => void;
|
|
3
|
+
export declare const setupRoomWithMocks: (webRTCEndpoint: WebRTCEndpoint, endpointId: string, trackId: string) => Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { MediaEvent } from '@fishjam-cloud/protobufs/server';
|
|
2
|
+
import { createAddLocalTrackAnswerData, createAddLocalTrackSDPOffer, createAddTrackMediaEvent, createConnectedEventWithOneEndpointWithOneTrack, stream, } from './fixtures';
|
|
3
|
+
import { mockRTCPeerConnection } from './mocks';
|
|
4
|
+
export const setupRoom = (webRTCEndpoint, endpointId, trackId) => {
|
|
5
|
+
const connected = createConnectedEventWithOneEndpointWithOneTrack(endpointId, trackId);
|
|
6
|
+
const connectedEvent = MediaEvent.encode({ connected }).finish();
|
|
7
|
+
webRTCEndpoint.receiveMediaEvent(connectedEvent);
|
|
8
|
+
// Right now info about tracks from connectedEvent is ignored
|
|
9
|
+
// We need to fix it but not in this commit
|
|
10
|
+
// We need more tests (e2e) to introduce this change
|
|
11
|
+
const tracksAdded = createAddTrackMediaEvent(endpointId, trackId);
|
|
12
|
+
const tracksAddedEvent = MediaEvent.encode({ tracksAdded }).finish();
|
|
13
|
+
webRTCEndpoint.receiveMediaEvent(tracksAddedEvent);
|
|
14
|
+
};
|
|
15
|
+
// Fix proposal:
|
|
16
|
+
//
|
|
17
|
+
// "connected" handler
|
|
18
|
+
//
|
|
19
|
+
// const otherEndpoints: Endpoint[] = endpoints.map((endpoint) => {
|
|
20
|
+
// const endpointTracks: [string, any][] = Object.entries(endpoint.tracks || {});
|
|
21
|
+
// const tracks = this.mapMediaEventTracksToTrackContextImpl(endpointTracks, endpoint);
|
|
22
|
+
// return { ...endpoint, tracks };
|
|
23
|
+
// });
|
|
24
|
+
//
|
|
25
|
+
// otherEndpoints.forEach((endpoint) => {
|
|
26
|
+
// this.addEndpoint(endpoint);
|
|
27
|
+
// });
|
|
28
|
+
// otherEndpoints.forEach((endpoint) => {
|
|
29
|
+
// endpoint.tracks.forEach((ctx, trackId) => {
|
|
30
|
+
//
|
|
31
|
+
// "endpointAdded"
|
|
32
|
+
// this.addEndpoint({ ...endpoint, tracks: new Map() });
|
|
33
|
+
//
|
|
34
|
+
// remove todo if-else statement from addEndpoint function
|
|
35
|
+
export const setupRoomWithMocks = async (webRTCEndpoint, endpointId, trackId) => {
|
|
36
|
+
mockRTCPeerConnection();
|
|
37
|
+
setupRoom(webRTCEndpoint, endpointId, trackId);
|
|
38
|
+
const offerData = createAddLocalTrackSDPOffer();
|
|
39
|
+
webRTCEndpoint.receiveMediaEvent(MediaEvent.encode({ offerData }).finish());
|
|
40
|
+
const sdpAnswer = createAddLocalTrackAnswerData(trackId);
|
|
41
|
+
webRTCEndpoint.receiveMediaEvent(MediaEvent.encode({ sdpAnswer }).finish());
|
|
42
|
+
const connection = webRTCEndpoint['connectionManager'];
|
|
43
|
+
const transciever = new RTCRtpTransceiver();
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
const rtcTrackEvent = {
|
|
46
|
+
streams: [stream],
|
|
47
|
+
transceiver: transciever,
|
|
48
|
+
// @ts-expect-error
|
|
49
|
+
track: {
|
|
50
|
+
kind: 'video',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
connection.getConnection().ontrack(rtcTrackEvent);
|
|
55
|
+
return new Promise((resolve) => resolve());
|
|
56
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fishjam-cloud/webrtc-client",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "Typescript client library for ExWebRTC/WebRTC endpoint in Membrane RTC Engine",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Fishjam Team",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/fishjam-cloud/web-client-sdk.git",
|
|
10
|
+
"directory": "packages/webrtc-client"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/fishjam-cloud/web-client-sdk#readme",
|
|
13
|
+
"bugs": "https://github.com/fishjam-cloud/web-client-sdk/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"webrtc",
|
|
16
|
+
"fishjam"
|
|
17
|
+
],
|
|
18
|
+
"main": "./dist/index.mjs",
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/**"
|
|
22
|
+
],
|
|
23
|
+
"tsup": {
|
|
24
|
+
"entry": [
|
|
25
|
+
"src/index.ts"
|
|
26
|
+
],
|
|
27
|
+
"format": [
|
|
28
|
+
"esm"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "yarn build:check && tsup --dts-resolve",
|
|
33
|
+
"build:check": "tsc --noEmit",
|
|
34
|
+
"format": "prettier --write . --ignore-path ./.eslintignore",
|
|
35
|
+
"format:check": "prettier --check . --ignore-path ./.eslintignore",
|
|
36
|
+
"lint": "eslint . --ext .ts,.tsx --fix",
|
|
37
|
+
"lint:check": "eslint . --ext .ts,.tsx",
|
|
38
|
+
"gen:proto": "sh scripts/protobuf.sh",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:e2e": "NODE_OPTIONS=--dns-result-order=ipv4first playwright test",
|
|
41
|
+
"test:coverage": "vitest run tests/** --coverage",
|
|
42
|
+
"prepare": "tsc",
|
|
43
|
+
"prepack": "yarn build"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@bufbuild/protobuf": "^2.2.3",
|
|
47
|
+
"events": "^3.3.0",
|
|
48
|
+
"typed-emitter": "^2.1.0",
|
|
49
|
+
"uuid": "^11.1.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@faker-js/faker": "^9.5.0",
|
|
53
|
+
"@fishjam-cloud/protobufs": "workspace:*",
|
|
54
|
+
"@playwright/test": "^1.50.1",
|
|
55
|
+
"@types/events": "^3.0.3",
|
|
56
|
+
"@types/node": "^22.13.4",
|
|
57
|
+
"@types/uuid": "^10.0.0",
|
|
58
|
+
"@vitest/coverage-v8": "^3.0.5",
|
|
59
|
+
"fake-mediastreamtrack": "^1.2.0",
|
|
60
|
+
"husky": "^9.1.7",
|
|
61
|
+
"lint-staged": "^15.4.3",
|
|
62
|
+
"tsup": "^8.3.6",
|
|
63
|
+
"typed-emitter": "^2.1.0",
|
|
64
|
+
"typedoc": "^0.27.7",
|
|
65
|
+
"typedoc-plugin-external-resolver": "^1.0.3",
|
|
66
|
+
"typedoc-plugin-mdn-links": "^4.0.13",
|
|
67
|
+
"typescript": "^5.7.3",
|
|
68
|
+
"vitest": "^3.0.7",
|
|
69
|
+
"zod": "^3.24.1"
|
|
70
|
+
},
|
|
71
|
+
"lint-staged": {
|
|
72
|
+
"*": [
|
|
73
|
+
"yarn format:check"
|
|
74
|
+
],
|
|
75
|
+
"*.(js|ts|tsx)": [
|
|
76
|
+
"yarn lint:check"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"packageManager": "yarn@4.3.0"
|
|
80
|
+
}
|