@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,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,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,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
|
+
};
|