@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,165 @@
1
+ import { MediaEvent_SetTargetTrackVariant } from '@fishjam-cloud/protobufs/peer';
2
+ import { MediaEvent_VadNotification_Status } from '@fishjam-cloud/protobufs/server';
3
+ import { TrackContextImpl } from '../internal';
4
+ import { RemoteTrack } from './RemoteTrack';
5
+ export class Remote {
6
+ remoteTracks = {};
7
+ remoteEndpoints = {};
8
+ emit;
9
+ sendMediaEvent;
10
+ constructor(emit, sendMediaEvent) {
11
+ this.emit = emit;
12
+ this.sendMediaEvent = sendMediaEvent;
13
+ }
14
+ getTrackByMid = (mid) => {
15
+ const remoteTrack = Object.values(this.remoteTracks).find((remote) => remote.mLineId === mid);
16
+ if (!remoteTrack)
17
+ throw new Error(`Remote track with ${mid} not found`);
18
+ return remoteTrack;
19
+ };
20
+ addTracks = (endpointId, tracks) => {
21
+ const endpoint = this.remoteEndpoints[endpointId];
22
+ if (!endpoint)
23
+ throw new Error(`Endpoint ${endpointId} not found`);
24
+ Object.entries(tracks)
25
+ .map(([trackId, { metadataJson, simulcastConfig }]) => {
26
+ const parsedMetadata = metadataJson ? JSON.parse(metadataJson) : undefined;
27
+ const trackContext = new TrackContextImpl(endpoint, trackId, parsedMetadata, simulcastConfig);
28
+ return new RemoteTrack(trackId, trackContext);
29
+ })
30
+ .forEach((remoteTrack) => {
31
+ this.remoteTracks[remoteTrack.id] = remoteTrack;
32
+ endpoint.tracks.set(remoteTrack.id, remoteTrack.trackContext);
33
+ this.emit('trackAdded', remoteTrack.trackContext);
34
+ });
35
+ };
36
+ removeTracks = (trackIds) => {
37
+ trackIds.forEach((trackId) => {
38
+ this.removeRemoteTrack(trackId);
39
+ });
40
+ };
41
+ removeRemoteTrack = (trackId) => {
42
+ const remoteTrack = this.remoteTracks[trackId];
43
+ if (!remoteTrack)
44
+ throw new Error(`Track ${trackId} not found`);
45
+ const remoteEndpoint = this.remoteEndpoints[remoteTrack.trackContext.endpoint.id];
46
+ if (!remoteEndpoint)
47
+ throw new Error(`Endpoint ${remoteTrack.trackContext.endpoint.id} not found`);
48
+ remoteEndpoint.tracks.delete(trackId);
49
+ delete this.remoteTracks[trackId];
50
+ this.emit('trackRemoved', remoteTrack.trackContext);
51
+ };
52
+ addRemoteEndpoint = (endpointId, metadataJson, tracks, sendNotification = true) => {
53
+ const endpoint = {
54
+ id: endpointId,
55
+ type: 'exwebrtc',
56
+ metadata: metadataJson ? JSON.parse(metadataJson) : undefined,
57
+ tracks: new Map(),
58
+ };
59
+ this.addEndpoint(endpoint);
60
+ this.addTracks(endpoint.id, tracks ?? {});
61
+ if (sendNotification) {
62
+ this.emit('endpointAdded', endpoint);
63
+ }
64
+ };
65
+ addEndpoint = (endpoint) => {
66
+ this.remoteEndpoints[endpoint.id] = endpoint;
67
+ };
68
+ updateRemoteEndpoint = (endpointId, metadataJson) => {
69
+ const endpoint = this.remoteEndpoints[endpointId];
70
+ if (!endpoint)
71
+ throw new Error(`Endpoint ${endpointId} not found`);
72
+ endpoint.metadata = metadataJson ? JSON.parse(metadataJson) : undefined;
73
+ this.emit('endpointUpdated', endpoint);
74
+ };
75
+ removeRemoteEndpoint = (endpointId) => {
76
+ const endpoint = this.remoteEndpoints[endpointId];
77
+ if (!endpoint)
78
+ throw new Error(`Endpoint ${endpointId} not found`);
79
+ const trackIds = [...endpoint.tracks.values()].map(({ trackId }) => trackId);
80
+ this.removeTracks(trackIds);
81
+ delete this.remoteEndpoints[endpointId];
82
+ this.emit('endpointRemoved', endpoint);
83
+ };
84
+ updateRemoteTrack = (endpointId, trackId, metadataJson) => {
85
+ if (!this.remoteEndpoints[endpointId])
86
+ throw new Error(`Endpoint ${endpointId} not found`);
87
+ const remoteTrack = this.remoteTracks[trackId];
88
+ if (!remoteTrack)
89
+ throw new Error(`Track ${trackId} not found`);
90
+ remoteTrack.trackContext.metadata = metadataJson ? JSON.parse(metadataJson) : undefined;
91
+ this.emit('trackUpdated', remoteTrack.trackContext);
92
+ };
93
+ disableRemoteTrackEncoding = (trackId, encoding) => {
94
+ const remoteTrack = this.remoteTracks[trackId];
95
+ if (!remoteTrack)
96
+ throw new Error(`Track ${trackId} not found`);
97
+ remoteTrack.disableTrackEncoding(encoding);
98
+ this.emit('trackEncodingDisabled', remoteTrack.trackContext, encoding);
99
+ };
100
+ enableRemoteTrackEncoding = (trackId, encoding) => {
101
+ const remoteTrack = this.remoteTracks[trackId];
102
+ if (!remoteTrack)
103
+ throw new Error(`Track ${trackId} not found`);
104
+ remoteTrack.enableTrackEncoding(encoding);
105
+ this.emit('trackEncodingEnabled', remoteTrack.trackContext, encoding);
106
+ };
107
+ setRemoteTrackEncoding = (trackId, encoding, reason) => {
108
+ const remoteTrack = this.remoteTracks[trackId];
109
+ if (!remoteTrack)
110
+ throw new Error(`Track ${trackId} not found`);
111
+ remoteTrack.trackContext.encoding = encoding;
112
+ remoteTrack.trackContext.encodingReason = reason;
113
+ remoteTrack.trackContext.emit('encodingChanged', remoteTrack.trackContext);
114
+ };
115
+ setRemoteTrackVadStatus = (trackId, vadStatus) => {
116
+ const remoteTrack = this.remoteTracks[trackId];
117
+ if (!remoteTrack)
118
+ throw new Error(`Track ${trackId} not found`);
119
+ let nextStatus = null;
120
+ if (vadStatus === MediaEvent_VadNotification_Status.STATUS_SILENCE) {
121
+ nextStatus = 'silence';
122
+ }
123
+ else if (vadStatus === MediaEvent_VadNotification_Status.STATUS_SPEECH) {
124
+ nextStatus = 'speech';
125
+ }
126
+ if (nextStatus) {
127
+ remoteTrack.trackContext.vadStatus = nextStatus;
128
+ remoteTrack.trackContext.emit('voiceActivityChanged', remoteTrack.trackContext);
129
+ }
130
+ else {
131
+ console.warn('Received unknown vad status: ', vadStatus);
132
+ }
133
+ };
134
+ getRemoteTrackContexts = () => {
135
+ return Object.values(this.remoteTracks).reduce((acc, current) => ({
136
+ ...acc,
137
+ [current.trackContext.trackId]: current.trackContext,
138
+ }), {});
139
+ };
140
+ getRemoteEndpoints = () => {
141
+ return Object.values(this.remoteEndpoints).reduce((acc, current) => ({ ...acc, [current.id]: current }), {});
142
+ };
143
+ setTargetRemoteTrackEncoding = (trackId, variant) => {
144
+ const remoteTrack = this.remoteTracks[trackId];
145
+ if (!remoteTrack)
146
+ throw new Error(`Track ${trackId} not found`);
147
+ try {
148
+ remoteTrack.setTargetTrackEncoding(variant);
149
+ const setTargetTrackVariant = MediaEvent_SetTargetTrackVariant.create({ variant, trackId });
150
+ this.sendMediaEvent({ setTargetTrackVariant });
151
+ this.emit('targetTrackEncodingRequested', {
152
+ trackId,
153
+ variant,
154
+ });
155
+ }
156
+ catch (e) {
157
+ console.warn(e);
158
+ }
159
+ };
160
+ updateMLineIds = (midToTrackIds) => {
161
+ Object.entries(midToTrackIds).forEach(([mid, trackId]) => {
162
+ this.remoteTracks[trackId]?.setMLineId(mid);
163
+ });
164
+ };
165
+ }
@@ -0,0 +1,40 @@
1
+ import { Variant } from '@fishjam-cloud/protobufs/shared';
2
+ import type { TrackContextImpl } from '../internal';
3
+ import type { LocalTrackId, MLineId } from '../types';
4
+ import type { TrackCommon, TrackId } from './TrackCommon';
5
+ /**
6
+ * This is a wrapper over `TrackContext` that adds additional properties such as:
7
+ * - `MLineId`: required to generate sdpOffer
8
+ *
9
+ * # Lifecycle, state transitions
10
+ *
11
+ * I identified the following states
12
+ * - on `trackAdded`
13
+ * - trackContext.stream === null
14
+ * - trackContext.track === null
15
+ * - trackContext.trackKind === null
16
+ * - mLineId === null
17
+ * - on `onSdpAnswer`
18
+ * - trackContext.stream === null
19
+ * - trackContext.track === null
20
+ * - trackContext.trackKind === null
21
+ * - mLineId !== null
22
+ * - on `trackReady`
23
+ * - trackContext.stream !== null
24
+ * - trackContext.track !== null
25
+ * - trackContext.trackKind !== null
26
+ * - mLineId !== null
27
+ */
28
+ export declare class RemoteTrack implements TrackCommon {
29
+ id: TrackId;
30
+ mLineId: MLineId | null;
31
+ readonly trackContext: TrackContextImpl;
32
+ readonly encodings: Record<Variant, boolean>;
33
+ private targetEncoding;
34
+ constructor(id: LocalTrackId, trackContext: TrackContextImpl);
35
+ setMLineId: (mLineId: MLineId) => void;
36
+ setReady: (stream: MediaStream, track: MediaStreamTrack) => void;
37
+ disableTrackEncoding: (encoding: Variant) => void;
38
+ enableTrackEncoding: (encoding: Variant) => void;
39
+ setTargetTrackEncoding: (variant: Variant) => void;
40
+ }
@@ -0,0 +1,71 @@
1
+ import { Variant } from '@fishjam-cloud/protobufs/shared';
2
+ import { isTrackKind } from '../internal';
3
+ /**
4
+ * This is a wrapper over `TrackContext` that adds additional properties such as:
5
+ * - `MLineId`: required to generate sdpOffer
6
+ *
7
+ * # Lifecycle, state transitions
8
+ *
9
+ * I identified the following states
10
+ * - on `trackAdded`
11
+ * - trackContext.stream === null
12
+ * - trackContext.track === null
13
+ * - trackContext.trackKind === null
14
+ * - mLineId === null
15
+ * - on `onSdpAnswer`
16
+ * - trackContext.stream === null
17
+ * - trackContext.track === null
18
+ * - trackContext.trackKind === null
19
+ * - mLineId !== null
20
+ * - on `trackReady`
21
+ * - trackContext.stream !== null
22
+ * - trackContext.track !== null
23
+ * - trackContext.trackKind !== null
24
+ * - mLineId !== null
25
+ */
26
+ export class RemoteTrack {
27
+ id;
28
+ mLineId = null;
29
+ trackContext;
30
+ encodings = {
31
+ [Variant.UNRECOGNIZED]: false,
32
+ [Variant.VARIANT_UNSPECIFIED]: false,
33
+ [Variant.VARIANT_LOW]: false,
34
+ [Variant.VARIANT_MEDIUM]: false,
35
+ [Variant.VARIANT_HIGH]: false,
36
+ };
37
+ // todo this field is not exposed
38
+ targetEncoding = null;
39
+ constructor(id, trackContext) {
40
+ this.id = id;
41
+ this.trackContext = trackContext;
42
+ }
43
+ setMLineId = (mLineId) => {
44
+ this.mLineId = mLineId;
45
+ };
46
+ setReady = (stream, track) => {
47
+ if (!isTrackKind(track.kind))
48
+ throw new Error('Track has no kind');
49
+ this.trackContext.stream = stream;
50
+ this.trackContext.track = track;
51
+ this.trackContext.trackKind = track.kind;
52
+ };
53
+ disableTrackEncoding = (encoding) => {
54
+ this.encodings[encoding] = false;
55
+ };
56
+ enableTrackEncoding = (encoding) => {
57
+ this.encodings[encoding] = true;
58
+ };
59
+ setTargetTrackEncoding = (variant) => {
60
+ // todo implement validation
61
+ this.targetEncoding = variant;
62
+ const trackContext = this.trackContext;
63
+ if (!trackContext.simulcastConfig?.enabled) {
64
+ throw new Error('The track does not support changing its target variant');
65
+ }
66
+ const isValidTargetEncoding = !trackContext.simulcastConfig.enabledVariants.includes(variant);
67
+ if (isValidTargetEncoding) {
68
+ throw new Error(`The track does not support variant ${variant}`);
69
+ }
70
+ };
71
+ }
@@ -0,0 +1,9 @@
1
+ import type { Variant } from '@fishjam-cloud/protobufs/shared';
2
+ import type { MLineId } from '../types';
3
+ export type TrackId = string;
4
+ export type EndpointId = string;
5
+ export type TrackEncodings = Record<Variant, boolean>;
6
+ export interface TrackCommon {
7
+ mLineId: MLineId | null;
8
+ encodings: TrackEncodings;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const splitBandwidth: (rtcRtpEncodingParameters: RTCRtpEncodingParameters[], maxBandwidth: number) => RTCRtpEncodingParameters[];
@@ -0,0 +1,27 @@
1
+ export const splitBandwidth = (rtcRtpEncodingParameters, maxBandwidth) => {
2
+ const bandwidth = maxBandwidth * 1024;
3
+ if (bandwidth === 0) {
4
+ return rtcRtpEncodingParameters.map((encoding) => ({
5
+ ...encoding,
6
+ maxBitrate: undefined,
7
+ }));
8
+ }
9
+ if (rtcRtpEncodingParameters.length === 0) {
10
+ // This most likely is a race condition. Log an error and prevent catastrophic failure
11
+ console.error("Attempted to limit bandwidth of the track that doesn't have any encodings");
12
+ return rtcRtpEncodingParameters.map((encoding) => ({ ...encoding }));
13
+ }
14
+ if (!rtcRtpEncodingParameters[0])
15
+ throw new Error('RTCRtpEncodingParameters is in invalid state');
16
+ // We are solving the following equation:
17
+ // x + (k0/k1)^2 * x + (k0/k2)^2 * x + ... + (k0/kn)^2 * x = bandwidth
18
+ // where x is the bitrate for the first encoding, kn are scaleResolutionDownBy factors
19
+ // square is dictated by the fact that k0/kn is a scale factor, but we are interested in the total number of pixels in the image
20
+ const firstScaleDownBy = rtcRtpEncodingParameters[0].scaleResolutionDownBy || 1;
21
+ const bitrate_parts = rtcRtpEncodingParameters.reduce((acc, value) => acc + (firstScaleDownBy / (value.scaleResolutionDownBy || 1)) ** 2, 0);
22
+ const x = bandwidth / bitrate_parts;
23
+ return rtcRtpEncodingParameters.map((encoding) => ({
24
+ ...encoding,
25
+ maxBitrate: x * (firstScaleDownBy / (encoding.scaleResolutionDownBy || 1)) ** 2,
26
+ }));
27
+ };
@@ -0,0 +1,3 @@
1
+ import { Variant } from '@fishjam-cloud/protobufs/shared';
2
+ export declare const getEncodingParameters: (parameters: RTCRtpSendParameters, bandwidth: number) => RTCRtpEncodingParameters[];
3
+ export declare const encodingToVariantMap: Record<string, Variant>;
@@ -0,0 +1,11 @@
1
+ import { Variant } from '@fishjam-cloud/protobufs/shared';
2
+ import { splitBandwidth } from './bandwidth';
3
+ export const getEncodingParameters = (parameters, bandwidth) => {
4
+ const unlimitedEncodings = [{}];
5
+ return parameters.encodings.length === 0 ? unlimitedEncodings : splitBandwidth(parameters.encodings, bandwidth);
6
+ };
7
+ export const encodingToVariantMap = {
8
+ l: Variant.VARIANT_LOW,
9
+ m: Variant.VARIANT_MEDIUM,
10
+ h: Variant.VARIANT_HIGH,
11
+ };
@@ -0,0 +1,3 @@
1
+ import type { WebRTCEndpoint } from '../webRTCEndpoint';
2
+ export declare function emitMutableEvents(action: 'mute' | 'unmute', webrtc: WebRTCEndpoint, trackId: string): void;
3
+ export declare function getActionType(currentTrack: MediaStreamTrack | null, newTrack: MediaStreamTrack | null): 'mute' | 'unmute' | 'replace';
@@ -0,0 +1,18 @@
1
+ export function emitMutableEvents(action, webrtc, trackId) {
2
+ const localEventType = action === 'mute' ? 'localTrackMuted' : 'localTrackUnmuted';
3
+ // TODO add the mute/unmute event back if they're needed
4
+ // const mediaEvent = generateMediaEvent(mediaEventType, { trackId: trackId });
5
+ // webrtc.sendMediaEvent(mediaEvent);
6
+ webrtc.emit(localEventType, { trackId });
7
+ }
8
+ export function getActionType(currentTrack, newTrack) {
9
+ if (currentTrack && !newTrack) {
10
+ return 'mute';
11
+ }
12
+ else if (!currentTrack && newTrack) {
13
+ return 'unmute';
14
+ }
15
+ else {
16
+ return 'replace';
17
+ }
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { TrackContextImpl } from '../internal';
2
+ export declare const createTransceiverConfig: (trackContext: TrackContextImpl) => RTCRtpTransceiverInit;
@@ -0,0 +1,93 @@
1
+ import { Variant } from '@fishjam-cloud/protobufs/shared';
2
+ import { splitBandwidth } from './bandwidth';
3
+ import { encodingToVariantMap } from './encodings';
4
+ export const createTransceiverConfig = (trackContext) => {
5
+ if (!trackContext.track)
6
+ throw new Error(`Cannot create transceiver config for `);
7
+ if (trackContext.track.kind === 'audio') {
8
+ return createAudioTransceiverConfig(trackContext.stream);
9
+ }
10
+ return createVideoTransceiverConfig(trackContext, trackContext.maxBandwidth);
11
+ };
12
+ const createAudioTransceiverConfig = (stream) => {
13
+ return {
14
+ direction: 'sendonly',
15
+ streams: stream ? [stream] : [],
16
+ };
17
+ };
18
+ const createVideoTransceiverConfig = (trackContext, maxBandwidth) => {
19
+ if (!trackContext.simulcastConfig)
20
+ throw new Error(`Simulcast config for track ${trackContext.trackId} not found.`);
21
+ if (trackContext.simulcastConfig.enabled) {
22
+ let simulcastConfig;
23
+ if (maxBandwidth === 0) {
24
+ simulcastConfig = new Map([
25
+ [Variant.VARIANT_LOW, 0],
26
+ [Variant.VARIANT_MEDIUM, 0],
27
+ [Variant.VARIANT_HIGH, 0],
28
+ ]);
29
+ }
30
+ else if (typeof maxBandwidth === 'number') {
31
+ throw new Error('Invalid bandwidth limit for simulcast track.');
32
+ }
33
+ else {
34
+ simulcastConfig = maxBandwidth;
35
+ }
36
+ return createSimulcastTransceiverConfig(trackContext, simulcastConfig);
37
+ }
38
+ if (typeof maxBandwidth === 'number') {
39
+ return createNonSimulcastTransceiverConfig(trackContext, maxBandwidth);
40
+ }
41
+ throw new Error('LocalTrack is in invalid state!');
42
+ };
43
+ const createNonSimulcastTransceiverConfig = (trackContext, maxBandwidth) => {
44
+ return {
45
+ direction: 'sendonly',
46
+ sendEncodings: splitBandwidth([{ active: true }], maxBandwidth),
47
+ streams: trackContext.stream ? [trackContext.stream] : [],
48
+ };
49
+ };
50
+ const createSimulcastTransceiverConfig = (trackContext, maxBandwidth) => {
51
+ if (!trackContext.simulcastConfig)
52
+ throw new Error(`Simulcast config for track ${trackContext.trackId} not found.`);
53
+ const activeEncodings = trackContext.simulcastConfig.enabledVariants;
54
+ const encodings = [
55
+ {
56
+ rid: 'l',
57
+ active: activeEncodings.includes(Variant.VARIANT_LOW),
58
+ // maxBitrate: 4_000_000,
59
+ scaleResolutionDownBy: 4.0,
60
+ // scalabilityMode: "L1T" + TEMPORAL_LAYERS_COUNT,
61
+ },
62
+ {
63
+ rid: 'm',
64
+ active: activeEncodings.includes(Variant.VARIANT_MEDIUM),
65
+ scaleResolutionDownBy: 2.0,
66
+ },
67
+ {
68
+ rid: 'h',
69
+ active: activeEncodings.includes(Variant.VARIANT_HIGH),
70
+ // maxBitrate: 4_000_000,
71
+ // scalabilityMode: "L1T" + TEMPORAL_LAYERS_COUNT,
72
+ },
73
+ ];
74
+ return {
75
+ direction: 'sendonly',
76
+ // keep this array from low resolution to high resolution
77
+ // in other case lower resolution encoding can get
78
+ // higher max_bitrate
79
+ sendEncodings: calculateSimulcastEncodings(encodings, maxBandwidth),
80
+ };
81
+ };
82
+ const calculateSimulcastEncodings = (encodings, maxBandwidth) => {
83
+ return encodings
84
+ .filter((encoding) => encoding.rid)
85
+ .map((encoding) => {
86
+ const variant = (!!encoding.rid && encodingToVariantMap[encoding.rid]) || Variant.VARIANT_UNSPECIFIED;
87
+ const limit = maxBandwidth.get(variant) || 0;
88
+ return {
89
+ ...encoding,
90
+ maxBitrate: limit > 0 ? limit * 1024 : undefined,
91
+ };
92
+ });
93
+ };