@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.
Files changed (83) hide show
  1. package/LICENSE.txt +142 -0
  2. package/README.md +30 -0
  3. package/dist/index.d.mts +761 -0
  4. package/dist/index.mjs +4952 -0
  5. package/dist/protobufs/fishjam/media_events/peer/peer.d.ts +125 -0
  6. package/dist/protobufs/fishjam/media_events/peer/peer.js +1214 -0
  7. package/dist/protobufs/fishjam/media_events/server/server.d.ts +197 -0
  8. package/dist/protobufs/fishjam/media_events/server/server.js +2121 -0
  9. package/dist/protobufs/fishjam/media_events/shared.d.ts +38 -0
  10. package/dist/protobufs/fishjam/media_events/shared.js +152 -0
  11. package/dist/webrtc-client/src/CommandsQueue.d.ts +23 -0
  12. package/dist/webrtc-client/src/CommandsQueue.js +87 -0
  13. package/dist/webrtc-client/src/ConnectionManager.d.ts +16 -0
  14. package/dist/webrtc-client/src/ConnectionManager.js +46 -0
  15. package/dist/webrtc-client/src/bitrate.d.ts +12 -0
  16. package/dist/webrtc-client/src/bitrate.js +15 -0
  17. package/dist/webrtc-client/src/deferred.d.ts +8 -0
  18. package/dist/webrtc-client/src/deferred.js +17 -0
  19. package/dist/webrtc-client/src/index.d.ts +5 -0
  20. package/dist/webrtc-client/src/index.js +3 -0
  21. package/dist/webrtc-client/src/internal.d.ts +27 -0
  22. package/dist/webrtc-client/src/internal.js +18 -0
  23. package/dist/webrtc-client/src/mediaEvent.d.ts +13 -0
  24. package/dist/webrtc-client/src/mediaEvent.js +20 -0
  25. package/dist/webrtc-client/src/tracks/Local.d.ts +49 -0
  26. package/dist/webrtc-client/src/tracks/Local.js +243 -0
  27. package/dist/webrtc-client/src/tracks/LocalTrack.d.ts +61 -0
  28. package/dist/webrtc-client/src/tracks/LocalTrack.js +212 -0
  29. package/dist/webrtc-client/src/tracks/LocalTrackManager.d.ts +39 -0
  30. package/dist/webrtc-client/src/tracks/LocalTrackManager.js +76 -0
  31. package/dist/webrtc-client/src/tracks/Remote.d.ts +32 -0
  32. package/dist/webrtc-client/src/tracks/Remote.js +165 -0
  33. package/dist/webrtc-client/src/tracks/RemoteTrack.d.ts +40 -0
  34. package/dist/webrtc-client/src/tracks/RemoteTrack.js +71 -0
  35. package/dist/webrtc-client/src/tracks/TrackCommon.d.ts +9 -0
  36. package/dist/webrtc-client/src/tracks/TrackCommon.js +1 -0
  37. package/dist/webrtc-client/src/tracks/bandwidth.d.ts +1 -0
  38. package/dist/webrtc-client/src/tracks/bandwidth.js +27 -0
  39. package/dist/webrtc-client/src/tracks/encodings.d.ts +3 -0
  40. package/dist/webrtc-client/src/tracks/encodings.js +11 -0
  41. package/dist/webrtc-client/src/tracks/muteTrackUtils.d.ts +3 -0
  42. package/dist/webrtc-client/src/tracks/muteTrackUtils.js +18 -0
  43. package/dist/webrtc-client/src/tracks/transceivers.d.ts +2 -0
  44. package/dist/webrtc-client/src/tracks/transceivers.js +93 -0
  45. package/dist/webrtc-client/src/types.d.ts +259 -0
  46. package/dist/webrtc-client/src/types.js +1 -0
  47. package/dist/webrtc-client/src/webRTCEndpoint.d.ts +298 -0
  48. package/dist/webrtc-client/src/webRTCEndpoint.js +659 -0
  49. package/dist/webrtc-client/tests/events/bandwidthEstimationEvent.test.d.ts +1 -0
  50. package/dist/webrtc-client/tests/events/bandwidthEstimationEvent.test.js +21 -0
  51. package/dist/webrtc-client/tests/events/connectedEvent.test.d.ts +1 -0
  52. package/dist/webrtc-client/tests/events/connectedEvent.test.js +70 -0
  53. package/dist/webrtc-client/tests/events/encodingSwitchedEvent.test.d.ts +1 -0
  54. package/dist/webrtc-client/tests/events/encodingSwitchedEvent.test.js +60 -0
  55. package/dist/webrtc-client/tests/events/endpointAddedEvent.test.d.ts +1 -0
  56. package/dist/webrtc-client/tests/events/endpointAddedEvent.test.js +57 -0
  57. package/dist/webrtc-client/tests/events/endpointRemovedEvent.test.d.ts +1 -0
  58. package/dist/webrtc-client/tests/events/endpointRemovedEvent.test.js +64 -0
  59. package/dist/webrtc-client/tests/events/endpointUpdatedEvent.test.d.ts +1 -0
  60. package/dist/webrtc-client/tests/events/endpointUpdatedEvent.test.js +78 -0
  61. package/dist/webrtc-client/tests/events/trackAddedEvent.test.d.ts +1 -0
  62. package/dist/webrtc-client/tests/events/trackAddedEvent.test.js +84 -0
  63. package/dist/webrtc-client/tests/events/trackRemovedEvent.test.d.ts +1 -0
  64. package/dist/webrtc-client/tests/events/trackRemovedEvent.test.js +26 -0
  65. package/dist/webrtc-client/tests/events/trackUpdatedEvent.test.d.ts +1 -0
  66. package/dist/webrtc-client/tests/events/trackUpdatedEvent.test.js +77 -0
  67. package/dist/webrtc-client/tests/events/vadNotificationEvent.test.d.ts +1 -0
  68. package/dist/webrtc-client/tests/events/vadNotificationEvent.test.js +42 -0
  69. package/dist/webrtc-client/tests/fixtures.d.ts +27 -0
  70. package/dist/webrtc-client/tests/fixtures.js +172 -0
  71. package/dist/webrtc-client/tests/methods/addTrackMethod.test.d.ts +1 -0
  72. package/dist/webrtc-client/tests/methods/addTrackMethod.test.js +46 -0
  73. package/dist/webrtc-client/tests/methods/cleanUpMethod.test.d.ts +1 -0
  74. package/dist/webrtc-client/tests/methods/cleanUpMethod.test.js +14 -0
  75. package/dist/webrtc-client/tests/methods/connectMethod.test.d.ts +1 -0
  76. package/dist/webrtc-client/tests/methods/connectMethod.test.js +40 -0
  77. package/dist/webrtc-client/tests/methods/disconnectMethod.test.d.ts +1 -0
  78. package/dist/webrtc-client/tests/methods/disconnectMethod.test.js +29 -0
  79. package/dist/webrtc-client/tests/mocks.d.ts +5 -0
  80. package/dist/webrtc-client/tests/mocks.js +80 -0
  81. package/dist/webrtc-client/tests/utils.d.ts +3 -0
  82. package/dist/webrtc-client/tests/utils.js +56 -0
  83. package/package.json +80 -0
@@ -0,0 +1,70 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import { expect, it, vi } from 'vitest';
3
+ import { WebRTCEndpoint } from '../../src';
4
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
5
+ import { createConnectedEvent, createEmptyEndpoint, createTrackWithSimulcast, exampleTrackId } from '../fixtures';
6
+ it('Connecting to empty room produce event', () => new Promise((done) => {
7
+ const webRTCEndpoint = new WebRTCEndpoint();
8
+ const connected = createConnectedEvent();
9
+ webRTCEndpoint.on('connected', (peerId, _peersInRoom) => {
10
+ expect(connected.endpointId).toBe(peerId);
11
+ expect(Object.keys(connected.endpointIdToEndpoint).length).toBe(1);
12
+ done('');
13
+ });
14
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
15
+ }));
16
+ it('Connecting to empty room set internal state', () => () => {
17
+ // Given
18
+ const webRTCEndpoint = new WebRTCEndpoint();
19
+ // When
20
+ const connected = createConnectedEvent();
21
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
22
+ // Then
23
+ const localEndpointId = webRTCEndpoint['getEndpointId']();
24
+ expect(localEndpointId).toBe(connected.endpointId);
25
+ });
26
+ it('Connecting to room with one peer', () => new Promise((done) => {
27
+ const webRTCEndpoint = new WebRTCEndpoint();
28
+ const connected = createConnectedEvent();
29
+ connected.endpointIdToEndpoint = {
30
+ ...connected.endpointIdToEndpoint,
31
+ [faker.string.uuid()]: createEmptyEndpoint(),
32
+ };
33
+ webRTCEndpoint.on('connected', (peerId, _peersInRoom) => {
34
+ expect(connected.endpointId).toBe(peerId);
35
+ expect(Object.keys(connected.endpointIdToEndpoint).length).toBe(2);
36
+ done('');
37
+ });
38
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
39
+ }));
40
+ it('Connecting to room with one peer with one track', () => new Promise((done) => {
41
+ // Given
42
+ const webRTCEndpoint = new WebRTCEndpoint();
43
+ const trackAddedCallback = vi.fn((_x) => null);
44
+ const connectedCallback = vi.fn((_peerId, _peersInRoom) => null);
45
+ const connected = createConnectedEvent();
46
+ const otherEndpoint = createEmptyEndpoint();
47
+ otherEndpoint.trackIdToTrack = { [exampleTrackId]: createTrackWithSimulcast() };
48
+ connected.endpointIdToEndpoint = {
49
+ ...connected.endpointIdToEndpoint,
50
+ [faker.string.uuid()]: otherEndpoint,
51
+ };
52
+ webRTCEndpoint.on('connected', (peerId, remotePeersInRoom) => {
53
+ connectedCallback(peerId, remotePeersInRoom);
54
+ expect(peerId).toBe(connected.endpointId);
55
+ expect(remotePeersInRoom.length).toBe(1);
56
+ });
57
+ webRTCEndpoint.on('trackAdded', (ctx) => {
58
+ trackAddedCallback(ctx);
59
+ expect(ctx.trackId).toBe(exampleTrackId);
60
+ expect(ctx.simulcastConfig?.enabled).toBe(otherEndpoint.trackIdToTrack[exampleTrackId].simulcastConfig?.enabled);
61
+ done('');
62
+ });
63
+ // When
64
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
65
+ // Then
66
+ const remoteTracks = webRTCEndpoint.getRemoteTracks();
67
+ expect(Object.values(remoteTracks).length).toBe(1);
68
+ expect(trackAddedCallback.mock.calls).toHaveLength(1);
69
+ expect(connectedCallback.mock.calls).toHaveLength(1);
70
+ }));
@@ -0,0 +1,60 @@
1
+ import { expect, it } from 'vitest';
2
+ import { Variant, WebRTCEndpoint } from '../../src';
3
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
4
+ import { createEncodingSwitchedEvent, exampleEndpointId, exampleTrackId, notExistingEndpointId, notExistingTrackId, } from '../fixtures';
5
+ import { setupRoom } from '../utils';
6
+ it('Change existing track encoding', () => {
7
+ // Given
8
+ const webRTCEndpoint = new WebRTCEndpoint();
9
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
10
+ const initialTrackEncoding = webRTCEndpoint.getRemoteTracks()[exampleTrackId].encoding;
11
+ expect(initialTrackEncoding).toBe(undefined);
12
+ // When
13
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
14
+ trackVariantSwitched: createEncodingSwitchedEvent(exampleEndpointId, exampleTrackId, Variant.VARIANT_MEDIUM),
15
+ }));
16
+ // Then
17
+ const finalTrackEncoding = webRTCEndpoint.getRemoteTracks()[exampleTrackId].encoding;
18
+ expect(finalTrackEncoding).toBe(Variant.VARIANT_MEDIUM);
19
+ });
20
+ it('Changing track encoding when endpoint exist but track does not exist', () => {
21
+ // Given
22
+ const webRTCEndpoint = new WebRTCEndpoint();
23
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
24
+ const initialTrackEncoding = webRTCEndpoint.getRemoteTracks()[exampleTrackId].encoding;
25
+ expect(initialTrackEncoding).toBe(undefined);
26
+ // When
27
+ expect(() => webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
28
+ trackVariantSwitched: createEncodingSwitchedEvent(exampleEndpointId, notExistingTrackId, Variant.VARIANT_MEDIUM),
29
+ }))).rejects.toThrow(`Track ${notExistingTrackId} not found`);
30
+ });
31
+ it('Changing track encoding when endpoint does not exist but track exist in other endpoint', () => {
32
+ // Given
33
+ const webRTCEndpoint = new WebRTCEndpoint();
34
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
35
+ const initialTrackEncoding = webRTCEndpoint.getRemoteTracks()[exampleTrackId].encoding;
36
+ expect(initialTrackEncoding).toBe(undefined);
37
+ // When
38
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
39
+ trackVariantSwitched: createEncodingSwitchedEvent(notExistingEndpointId, exampleTrackId, Variant.VARIANT_MEDIUM),
40
+ }));
41
+ // Then
42
+ const finalTrackEncoding = webRTCEndpoint.getRemoteTracks()[exampleTrackId].encoding;
43
+ expect(finalTrackEncoding).toBe(Variant.VARIANT_MEDIUM);
44
+ });
45
+ it('Change existing track encoding produces event', () => new Promise((done) => {
46
+ // Given
47
+ const webRTCEndpoint = new WebRTCEndpoint();
48
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
49
+ const initialTrackEncoding = webRTCEndpoint.getRemoteTracks()[exampleTrackId].encoding;
50
+ expect(initialTrackEncoding).toBe(undefined);
51
+ webRTCEndpoint.getRemoteTracks()[exampleTrackId].on('encodingChanged', (context) => {
52
+ // Then
53
+ expect(context.encoding).toBe(Variant.VARIANT_MEDIUM);
54
+ done('');
55
+ });
56
+ // When
57
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
58
+ trackVariantSwitched: createEncodingSwitchedEvent(exampleEndpointId, exampleTrackId, Variant.VARIANT_MEDIUM),
59
+ }));
60
+ }));
@@ -0,0 +1,57 @@
1
+ import { expect, it } from 'vitest';
2
+ import { WebRTCEndpoint } from '../../src';
3
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
4
+ import { createConnectedEvent, createConnectedEventWithOneEndpoint, createEndpointAdded, exampleEndpointId, } from '../fixtures';
5
+ import { mockRTCPeerConnection } from '../mocks';
6
+ it('Add endpoint to empty state', () => {
7
+ // Given
8
+ mockRTCPeerConnection();
9
+ const webRTCEndpoint = new WebRTCEndpoint();
10
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEvent() }));
11
+ // When
12
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointAdded: createEndpointAdded(exampleEndpointId) }));
13
+ // Then
14
+ const endpoints = webRTCEndpoint.getRemoteEndpoints();
15
+ expect(Object.values(endpoints).length).toBe(1);
16
+ });
17
+ it('Add another endpoint', () => {
18
+ // Given
19
+ mockRTCPeerConnection();
20
+ const webRTCEndpoint = new WebRTCEndpoint();
21
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint() }));
22
+ // When
23
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointAdded: createEndpointAdded(exampleEndpointId) }));
24
+ // Then
25
+ const endpoints = webRTCEndpoint.getRemoteEndpoints();
26
+ expect(Object.values(endpoints).length).toBe(2);
27
+ });
28
+ it('Add endpoint produces event', () => new Promise((done) => {
29
+ // Given
30
+ mockRTCPeerConnection();
31
+ const webRTCEndpoint = new WebRTCEndpoint();
32
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint() }));
33
+ const endpointAdded = createEndpointAdded(exampleEndpointId);
34
+ webRTCEndpoint.on('endpointAdded', (endpoint) => {
35
+ // Then
36
+ expect(endpoint.id).toBe(endpointAdded.endpointId);
37
+ expect(endpoint.metadata).toBe(undefined);
38
+ done('');
39
+ });
40
+ // When
41
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointAdded }));
42
+ }));
43
+ it('Parses the metadata', () => {
44
+ // Given
45
+ mockRTCPeerConnection();
46
+ const webRTCEndpoint = new WebRTCEndpoint();
47
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEvent() }));
48
+ const metadata = {
49
+ goodStuff: 'ye',
50
+ };
51
+ // When
52
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointAdded: createEndpointAdded(exampleEndpointId, metadata) }));
53
+ // Then
54
+ const endpoints = webRTCEndpoint.getRemoteEndpoints();
55
+ const addedEndpoint = endpoints[exampleEndpointId];
56
+ expect(addedEndpoint.metadata).toEqual(metadata);
57
+ });
@@ -0,0 +1,64 @@
1
+ import { expect, it } from 'vitest';
2
+ import { WebRTCEndpoint } from '../../src';
3
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
4
+ import { createConnectedEventWithOneEndpoint, createEndpointRemoved, exampleEndpointId, exampleTrackId, notExistingEndpointId, } from '../fixtures';
5
+ import { mockRTCPeerConnection } from '../mocks';
6
+ import { setupRoom } from '../utils';
7
+ it('Remove the endpoint that does not exist', () => {
8
+ // Given
9
+ mockRTCPeerConnection();
10
+ const webRTCEndpoint = new WebRTCEndpoint();
11
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint(exampleEndpointId) }));
12
+ // When
13
+ expect(() => webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointRemoved: createEndpointRemoved(notExistingEndpointId) })))
14
+ // Then
15
+ .rejects.toThrow(`Endpoint ${notExistingEndpointId} not found`);
16
+ });
17
+ it('Remove current peer', () => new Promise((done) => {
18
+ // Given
19
+ mockRTCPeerConnection();
20
+ const webRTCEndpoint = new WebRTCEndpoint();
21
+ const currentPeerId = 'currentPeerId';
22
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint(exampleEndpointId, currentPeerId) }));
23
+ webRTCEndpoint.on('disconnected', () => {
24
+ // Then
25
+ done('');
26
+ });
27
+ // When
28
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointRemoved: createEndpointRemoved(currentPeerId) }));
29
+ }));
30
+ it('Remove existing endpoint should remove it from remote endpoints', () => {
31
+ // Given
32
+ mockRTCPeerConnection();
33
+ const webRTCEndpoint = new WebRTCEndpoint();
34
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint(exampleEndpointId) }));
35
+ // When
36
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointRemoved: createEndpointRemoved(exampleEndpointId) }));
37
+ // Then
38
+ const endpoints = webRTCEndpoint.getRemoteEndpoints();
39
+ expect(Object.values(endpoints).length).toBe(0);
40
+ });
41
+ it('Remove existing endpoint should remove all tracks', () => {
42
+ // Given
43
+ mockRTCPeerConnection();
44
+ const webRTCEndpoint = new WebRTCEndpoint();
45
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
46
+ // When
47
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointRemoved: createEndpointRemoved(exampleEndpointId) }));
48
+ // Then
49
+ const tracks = webRTCEndpoint.getRemoteTracks();
50
+ expect(Object.values(tracks).length).toBe(0);
51
+ });
52
+ it('Remove existing endpoint should emit trackRemoved event', () => new Promise((done) => {
53
+ // Given
54
+ mockRTCPeerConnection();
55
+ const webRTCEndpoint = new WebRTCEndpoint();
56
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
57
+ webRTCEndpoint.on('trackRemoved', (trackContext) => {
58
+ // Then
59
+ expect(trackContext.trackId).toBe(exampleTrackId);
60
+ done('');
61
+ });
62
+ // When
63
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointRemoved: createEndpointRemoved(exampleEndpointId) }));
64
+ }));
@@ -0,0 +1,78 @@
1
+ import { expect, it } from 'vitest';
2
+ import { WebRTCEndpoint } from '../../src';
3
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
4
+ import { createConnectedEvent, createConnectedEventWithOneEndpoint, createEndpointUpdatedPeerMetadata, exampleEndpointId, notExistingEndpointId, } from '../fixtures';
5
+ import { mockRTCPeerConnection } from '../mocks';
6
+ it('Update existing endpoint metadata', () => {
7
+ // Given
8
+ mockRTCPeerConnection();
9
+ const webRTCEndpoint = new WebRTCEndpoint();
10
+ const connected = createConnectedEventWithOneEndpoint(exampleEndpointId);
11
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
12
+ // When
13
+ const metadata = {
14
+ newField: 'new field value',
15
+ };
16
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointUpdated: createEndpointUpdatedPeerMetadata(exampleEndpointId, metadata) }));
17
+ // Then
18
+ const endpoint = webRTCEndpoint.getRemoteEndpoints()[exampleEndpointId];
19
+ expect(endpoint.metadata).toMatchObject(metadata);
20
+ });
21
+ it('Update existing endpoint produce event', () => new Promise((done) => {
22
+ // Given
23
+ mockRTCPeerConnection();
24
+ const webRTCEndpoint = new WebRTCEndpoint();
25
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEventWithOneEndpoint(exampleEndpointId) }));
26
+ const metadata = {
27
+ newField: 'new field value',
28
+ };
29
+ webRTCEndpoint.on('endpointUpdated', (endpoint) => {
30
+ // Then
31
+ expect(endpoint.metadata).toMatchObject(metadata);
32
+ done('');
33
+ });
34
+ // When
35
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointUpdated: createEndpointUpdatedPeerMetadata(exampleEndpointId, metadata) }));
36
+ }));
37
+ it('Update existing endpoint with undefined metadata', () => {
38
+ // Given
39
+ mockRTCPeerConnection();
40
+ const webRTCEndpoint = new WebRTCEndpoint();
41
+ const connectedMediaEvent = createConnectedEventWithOneEndpoint(exampleEndpointId);
42
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: connectedMediaEvent }));
43
+ // When
44
+ const metadata = undefined;
45
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointUpdated: createEndpointUpdatedPeerMetadata(exampleEndpointId, metadata) }));
46
+ // Then
47
+ const endpoint = webRTCEndpoint.getRemoteEndpoints()[exampleEndpointId];
48
+ expect(endpoint.metadata).toBe(undefined);
49
+ });
50
+ it('Update endpoint that not exist', () => {
51
+ // Given
52
+ mockRTCPeerConnection();
53
+ const webRTCEndpoint = new WebRTCEndpoint();
54
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: createConnectedEvent() }));
55
+ // When
56
+ const metadata = {
57
+ newField: 'new field value',
58
+ };
59
+ expect(() => webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
60
+ endpointUpdated: createEndpointUpdatedPeerMetadata(notExistingEndpointId, metadata),
61
+ }))).rejects.toThrow(`Endpoint ${notExistingEndpointId} not found`);
62
+ });
63
+ it('Parse metadata on endpoint update', () => {
64
+ // Given
65
+ mockRTCPeerConnection();
66
+ const webRTCEndpoint = new WebRTCEndpoint();
67
+ const connectedMediaEvent = createConnectedEventWithOneEndpoint(exampleEndpointId);
68
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: connectedMediaEvent }));
69
+ // When
70
+ const metadata = {
71
+ goodStuff: 'ye',
72
+ };
73
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ endpointUpdated: createEndpointUpdatedPeerMetadata(exampleEndpointId, metadata) }));
74
+ // Then
75
+ const endpoints = webRTCEndpoint.getRemoteEndpoints();
76
+ const addedEndpoint = Object.values(endpoints)[0];
77
+ expect(addedEndpoint.metadata).toEqual({ goodStuff: 'ye' });
78
+ });
@@ -0,0 +1,84 @@
1
+ import { expect, it } from 'vitest';
2
+ import { WebRTCEndpoint } from '../../src';
3
+ import { deserializePeerMediaEvent, serializeServerMediaEvent } from '../../src/mediaEvent';
4
+ import { createAddTrackMediaEvent, createAnswerData, createConnectedEventWithOneEndpoint, createCustomOfferDataEventWithOneVideoTrack, exampleTrackId, } from '../fixtures';
5
+ import { mockRTCPeerConnection } from '../mocks';
6
+ it('Connect to room with one endpoint then addTrack produce event', () => new Promise((done) => {
7
+ // Given
8
+ const webRTCEndpoint = new WebRTCEndpoint();
9
+ const eventWithOneEndpoint = createConnectedEventWithOneEndpoint();
10
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected: eventWithOneEndpoint }));
11
+ const [otherEndpointId] = Object.entries(eventWithOneEndpoint.endpointIdToEndpoint).find(([id]) => id !== eventWithOneEndpoint.endpointId);
12
+ const tracksAdded = createAddTrackMediaEvent(otherEndpointId, exampleTrackId);
13
+ webRTCEndpoint.on('trackAdded', (ctx) => {
14
+ expect(ctx.trackId).toBe(exampleTrackId);
15
+ expect(ctx.endpoint.id).toBe(tracksAdded.endpointId);
16
+ expect(ctx.simulcastConfig?.enabled).toBe(tracksAdded.trackIdToTrack[exampleTrackId].simulcastConfig?.enabled);
17
+ done('');
18
+ });
19
+ // When
20
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ tracksAdded }));
21
+ // Then
22
+ const remoteTracks = webRTCEndpoint.getRemoteTracks();
23
+ expect(Object.values(remoteTracks).length).toBe(1);
24
+ }));
25
+ it('Correctly parses track metadata', () => new Promise((done) => {
26
+ // Given
27
+ const webRTCEndpoint = new WebRTCEndpoint();
28
+ const connected = createConnectedEventWithOneEndpoint();
29
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
30
+ const otherEndpointId = Object.keys(connected.endpointIdToEndpoint).find((id) => id !== connected.endpointId);
31
+ const tracksAdded = createAddTrackMediaEvent(otherEndpointId, exampleTrackId, {
32
+ peer: { goodStuff: 'ye', extraFluff: 'nah' },
33
+ });
34
+ webRTCEndpoint.on('trackAdded', (ctx) => {
35
+ // Then
36
+ expect(ctx.metadata).toEqual({ peer: { goodStuff: 'ye', extraFluff: 'nah' } });
37
+ done('');
38
+ });
39
+ // When
40
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ tracksAdded }));
41
+ }));
42
+ it('tracksAdded -> handle offerData with one video track from server', () => new Promise((done) => {
43
+ // Given
44
+ const { addTransceiverCallback } = mockRTCPeerConnection();
45
+ const webRTCEndpoint = new WebRTCEndpoint();
46
+ const connected = createConnectedEventWithOneEndpoint();
47
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
48
+ const otherEndpointId = Object.keys(connected.endpointIdToEndpoint).find((id) => id !== connected.endpointId);
49
+ const trackAddedEvent = createAddTrackMediaEvent(otherEndpointId, exampleTrackId);
50
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ tracksAdded: trackAddedEvent }));
51
+ const offerData = createCustomOfferDataEventWithOneVideoTrack();
52
+ webRTCEndpoint.on('sendMediaEvent', (mediaEvent) => {
53
+ const event = deserializePeerMediaEvent(mediaEvent);
54
+ expect(event.sdpOffer).toBeDefined();
55
+ done('');
56
+ });
57
+ // When
58
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ offerData }));
59
+ // Then
60
+ // todo
61
+ // if there is no connection: Setup callbacks else restartIce
62
+ expect(addTransceiverCallback.mock.calls).toHaveLength(1);
63
+ expect(addTransceiverCallback.mock.calls[0][0]).toBe('video');
64
+ const transceivers = webRTCEndpoint['connectionManager'].getConnection().getTransceivers();
65
+ expect(transceivers.length).toBe(1);
66
+ expect(transceivers[0].direction).toBe('recvonly');
67
+ }));
68
+ it('tracksAdded -> offerData with one track -> handle sdpAnswer data with one video track from server', () => {
69
+ // Given
70
+ mockRTCPeerConnection();
71
+ const webRTCEndpoint = new WebRTCEndpoint();
72
+ const connected = createConnectedEventWithOneEndpoint();
73
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ connected }));
74
+ const otherEndpointId = Object.keys(connected.endpointIdToEndpoint).find((id) => id !== connected.endpointId);
75
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ tracksAdded: createAddTrackMediaEvent(otherEndpointId, exampleTrackId) }));
76
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ offerData: createCustomOfferDataEventWithOneVideoTrack() }));
77
+ // When
78
+ const sdpAnswer = createAnswerData(exampleTrackId);
79
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ sdpAnswer }));
80
+ // Then
81
+ const midToTrackId = webRTCEndpoint['local']['getMidToTrackId']();
82
+ // midToTrackId?.size should be undefined because the local peer doesn't offer anything
83
+ expect(midToTrackId?.size).toBe(undefined);
84
+ });
@@ -0,0 +1,26 @@
1
+ import { expect, it } from 'vitest';
2
+ import { WebRTCEndpoint } from '../../src';
3
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
4
+ import { createTracksRemovedEvent, exampleEndpointId, exampleTrackId } from '../fixtures';
5
+ import { setupRoomWithMocks } from '../utils';
6
+ it('Remove tracks event should emit event', () => new Promise((done) => {
7
+ // Given
8
+ const webRTCEndpoint = new WebRTCEndpoint();
9
+ setupRoomWithMocks(webRTCEndpoint, exampleEndpointId, exampleTrackId);
10
+ webRTCEndpoint.on('trackRemoved', (trackContext) => {
11
+ // Then
12
+ expect(trackContext.trackId).toBe(exampleTrackId);
13
+ done('');
14
+ });
15
+ // When
16
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ tracksRemoved: createTracksRemovedEvent(exampleEndpointId, [exampleTrackId]) }));
17
+ }));
18
+ it('Remove tracks event should remove from local state', () => {
19
+ // Given
20
+ const webRTCEndpoint = new WebRTCEndpoint();
21
+ setupRoomWithMocks(webRTCEndpoint, exampleEndpointId, exampleTrackId);
22
+ // When
23
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ tracksRemoved: createTracksRemovedEvent(exampleEndpointId, [exampleTrackId]) }));
24
+ const tracks = webRTCEndpoint.getRemoteTracks();
25
+ expect(Object.values(tracks).length).toBe(0);
26
+ });
@@ -0,0 +1,77 @@
1
+ import { expect, it } from 'vitest';
2
+ import { WebRTCEndpoint } from '../../src';
3
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
4
+ import { createTrackUpdatedEvent, exampleEndpointId, exampleTrackId, notExistingEndpointId } from '../fixtures';
5
+ import { setupRoom } from '../utils';
6
+ it(`Updating existing track emits events`, () => new Promise((done) => {
7
+ // Given
8
+ const webRTCEndpoint = new WebRTCEndpoint();
9
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
10
+ webRTCEndpoint.on('trackUpdated', (context) => {
11
+ // Then
12
+ expect(context.metadata).toEqual(metadata);
13
+ done('');
14
+ });
15
+ const metadata = {
16
+ name: 'New name',
17
+ };
18
+ // When
19
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ trackUpdated: createTrackUpdatedEvent(exampleTrackId, exampleEndpointId, metadata) }));
20
+ }));
21
+ it(`Updating existing track changes track metadata`, () => {
22
+ // Given
23
+ const webRTCEndpoint = new WebRTCEndpoint();
24
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
25
+ const metadata = {
26
+ name: 'New name',
27
+ };
28
+ // When
29
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ trackUpdated: createTrackUpdatedEvent(exampleTrackId, exampleEndpointId, metadata) }));
30
+ // Then
31
+ const track = webRTCEndpoint.getRemoteTracks()[exampleTrackId];
32
+ expect(track.metadata).toEqual(metadata);
33
+ });
34
+ it('Correctly parses track metadata', () => {
35
+ // Given
36
+ const webRTCEndpoint = new WebRTCEndpoint();
37
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
38
+ const metadata = {
39
+ goodStuff: 'ye',
40
+ };
41
+ // When
42
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ trackUpdated: createTrackUpdatedEvent(exampleTrackId, exampleEndpointId, metadata) }));
43
+ // Then
44
+ const track = webRTCEndpoint.getRemoteTracks()[exampleTrackId];
45
+ expect(track.metadata).toEqual({ goodStuff: 'ye' });
46
+ });
47
+ it.todo(`Webrtc endpoint skips updating local endpoint metadata`, () => {
48
+ // Given
49
+ const webRTCEndpoint = new WebRTCEndpoint();
50
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
51
+ const metadata = {
52
+ name: 'New name',
53
+ };
54
+ // When
55
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ trackUpdated: createTrackUpdatedEvent(exampleTrackId, exampleEndpointId, metadata) }));
56
+ // Then
57
+ // todo How should empty metadata be handled?
58
+ // - empty object {}
59
+ // - null
60
+ // - undefined
61
+ // expect(track.metadata).toBe(value.data.otherEndpoints[0].metadata as any)
62
+ // TODO: write the rest of the test once we expose webrtc.getLocalEndpoints() function
63
+ });
64
+ it(`Updating track with invalid endpoint id throws error`, () => {
65
+ // Given
66
+ const webRTCEndpoint = new WebRTCEndpoint();
67
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
68
+ const metadata = {
69
+ name: 'New name',
70
+ };
71
+ // When
72
+ expect(() => webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
73
+ trackUpdated: createTrackUpdatedEvent(exampleTrackId, notExistingEndpointId, metadata),
74
+ })))
75
+ // Then
76
+ .rejects.toThrow(`Endpoint ${notExistingEndpointId} not found`);
77
+ });
@@ -0,0 +1,42 @@
1
+ import { MediaEvent_VadNotification_Status } from '@fishjam-cloud/protobufs/server';
2
+ import { expect, it } from 'vitest';
3
+ import { WebRTCEndpoint } from '../../src';
4
+ import { serializeServerMediaEvent } from '../../src/mediaEvent';
5
+ import { createCustomVadNotificationEvent, exampleEndpointId, exampleTrackId } from '../fixtures';
6
+ import { setupRoom } from '../utils';
7
+ it(`Changing VAD notification to "speech" on existing track id`, () => {
8
+ // Given
9
+ const webRTCEndpoint = new WebRTCEndpoint();
10
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
11
+ // When
12
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
13
+ vadNotification: createCustomVadNotificationEvent(exampleTrackId, MediaEvent_VadNotification_Status.STATUS_SPEECH),
14
+ }));
15
+ // Then
16
+ const track = webRTCEndpoint.getRemoteTracks()[exampleTrackId];
17
+ expect(track.vadStatus).toBe('speech');
18
+ });
19
+ it(`Changing VAD notification to "silence" on existing track id`, () => {
20
+ // Given
21
+ const webRTCEndpoint = new WebRTCEndpoint();
22
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
23
+ // When
24
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({
25
+ vadNotification: createCustomVadNotificationEvent(exampleTrackId, MediaEvent_VadNotification_Status.STATUS_SILENCE),
26
+ }));
27
+ // Then
28
+ const track = webRTCEndpoint.getRemoteTracks()[exampleTrackId];
29
+ expect(track.vadStatus).toBe('silence');
30
+ });
31
+ it(`Changing VAD notification emits event`, () => new Promise((done) => {
32
+ // Given
33
+ const webRTCEndpoint = new WebRTCEndpoint();
34
+ setupRoom(webRTCEndpoint, exampleEndpointId, exampleTrackId);
35
+ webRTCEndpoint.getRemoteTracks()[exampleTrackId].on('voiceActivityChanged', (context) => {
36
+ expect(context.vadStatus).toBe('speech');
37
+ done('');
38
+ });
39
+ // When
40
+ const vadNotification = createCustomVadNotificationEvent(exampleTrackId, MediaEvent_VadNotification_Status.STATUS_SPEECH);
41
+ webRTCEndpoint.receiveMediaEvent(serializeServerMediaEvent({ vadNotification }));
42
+ }));
@@ -0,0 +1,27 @@
1
+ import type { MediaEvent_Track, MediaEvent_VadNotification_Status } from '@fishjam-cloud/protobufs/server';
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 { Variant } from '../src';
5
+ export declare const exampleEndpointId = "exampleEndpointId";
6
+ export declare const notExistingEndpointId = "notExistingEndpointId";
7
+ export declare const exampleTrackId = "exampleTrackId";
8
+ export declare const notExistingTrackId = "notExistingTrackId";
9
+ export declare const mockTrack: FakeMediaStreamTrack;
10
+ export declare const stream: any;
11
+ export declare const createTrackWithSimulcast: (metadata?: unknown) => MediaEvent_Track;
12
+ export declare const createEmptyEndpoint: () => MediaEvent_Endpoint;
13
+ export declare const createConnectedEvent: (endpointId?: string) => MediaEvent_Connected;
14
+ export declare const createEncodingSwitchedEvent: (endpointId: string, trackId: string, variant: Variant) => MediaEvent_TrackVariantSwitched;
15
+ export declare const createCustomVadNotificationEvent: (trackId: string, status: MediaEvent_VadNotification_Status) => MediaEvent_VadNotification;
16
+ export declare const createTrackUpdatedEvent: (trackId: string, endpointId: string, metadata: unknown) => MediaEvent_TrackUpdated;
17
+ export declare const createEndpointAdded: (endpointId: string, metadata?: unknown) => MediaEvent_EndpointAdded;
18
+ export declare const createEndpointRemoved: (endpointId: string) => MediaEvent_EndpointRemoved;
19
+ export declare const createConnectedEventWithOneEndpoint: (endpointId?: string, localEndpointId?: string) => MediaEvent_Connected;
20
+ export declare const createConnectedEventWithOneEndpointWithOneTrack: (remoteEndpointId: string, trackId: string, localEndpointId?: string) => MediaEvent_Connected;
21
+ export declare const createAddTrackMediaEvent: (endpointId: string, trackId: string, metadata?: any) => MediaEvent_TracksAdded;
22
+ export declare const createTracksRemovedEvent: (endpointId: string, trackIds: string[]) => MediaEvent_TracksRemoved;
23
+ export declare const createCustomOfferDataEventWithOneVideoTrack: () => MediaEvent_OfferData;
24
+ export declare const createAddLocalTrackSDPOffer: () => MediaEvent_OfferData;
25
+ export declare const createAnswerData: (trackId: string) => MediaEvent_SdpAnswer;
26
+ export declare const createAddLocalTrackAnswerData: (trackId: string) => MediaEvent_SdpAnswer;
27
+ export declare const createEndpointUpdatedPeerMetadata: (endpointId: string, metadata: unknown) => MediaEvent_EndpointUpdated;