@fishjam-cloud/ts-client 0.5.1

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 (52) hide show
  1. package/README.md +134 -0
  2. package/dist/src/FishjamClient.d.ts +523 -0
  3. package/dist/src/FishjamClient.js +666 -0
  4. package/dist/src/auth.d.ts +3 -0
  5. package/dist/src/auth.js +8 -0
  6. package/dist/src/index.d.ts +7 -0
  7. package/dist/src/index.js +3 -0
  8. package/dist/src/protos/fishjam/peer_notifications.d.ts +78 -0
  9. package/dist/src/protos/fishjam/peer_notifications.js +302 -0
  10. package/dist/src/protos/index.d.ts +1 -0
  11. package/dist/src/protos/index.js +1 -0
  12. package/dist/src/reconnection.d.ts +27 -0
  13. package/dist/src/reconnection.js +121 -0
  14. package/dist/src/webrtc/CommandsQueue.d.ts +23 -0
  15. package/dist/src/webrtc/CommandsQueue.js +102 -0
  16. package/dist/src/webrtc/ConnectionManager.d.ts +29 -0
  17. package/dist/src/webrtc/ConnectionManager.js +82 -0
  18. package/dist/src/webrtc/bitrate.d.ts +12 -0
  19. package/dist/src/webrtc/bitrate.js +12 -0
  20. package/dist/src/webrtc/deferred.d.ts +8 -0
  21. package/dist/src/webrtc/deferred.js +17 -0
  22. package/dist/src/webrtc/index.d.ts +3 -0
  23. package/dist/src/webrtc/index.js +1 -0
  24. package/dist/src/webrtc/internal.d.ts +26 -0
  25. package/dist/src/webrtc/internal.js +34 -0
  26. package/dist/src/webrtc/mediaEvent.d.ts +10 -0
  27. package/dist/src/webrtc/mediaEvent.js +16 -0
  28. package/dist/src/webrtc/tracks/Local.d.ts +51 -0
  29. package/dist/src/webrtc/tracks/Local.js +289 -0
  30. package/dist/src/webrtc/tracks/LocalTrack.d.ts +62 -0
  31. package/dist/src/webrtc/tracks/LocalTrack.js +235 -0
  32. package/dist/src/webrtc/tracks/LocalTrackManager.d.ts +38 -0
  33. package/dist/src/webrtc/tracks/LocalTrackManager.js +83 -0
  34. package/dist/src/webrtc/tracks/Remote.d.ts +38 -0
  35. package/dist/src/webrtc/tracks/Remote.js +200 -0
  36. package/dist/src/webrtc/tracks/RemoteTrack.d.ts +39 -0
  37. package/dist/src/webrtc/tracks/RemoteTrack.js +64 -0
  38. package/dist/src/webrtc/tracks/TrackCommon.d.ts +8 -0
  39. package/dist/src/webrtc/tracks/TrackCommon.js +1 -0
  40. package/dist/src/webrtc/tracks/bandwidth.d.ts +1 -0
  41. package/dist/src/webrtc/tracks/bandwidth.js +27 -0
  42. package/dist/src/webrtc/tracks/encodings.d.ts +1 -0
  43. package/dist/src/webrtc/tracks/encodings.js +7 -0
  44. package/dist/src/webrtc/tracks/transceivers.d.ts +2 -0
  45. package/dist/src/webrtc/tracks/transceivers.js +78 -0
  46. package/dist/src/webrtc/types.d.ts +306 -0
  47. package/dist/src/webrtc/types.js +2 -0
  48. package/dist/src/webrtc/voiceActivityDetection.d.ts +2 -0
  49. package/dist/src/webrtc/voiceActivityDetection.js +2 -0
  50. package/dist/src/webrtc/webRTCEndpoint.d.ts +299 -0
  51. package/dist/src/webrtc/webRTCEndpoint.js +730 -0
  52. package/package.json +82 -0
@@ -0,0 +1,82 @@
1
+ export class ConnectionManager {
2
+ connection;
3
+ constructor(turnServers) {
4
+ this.connection = new RTCPeerConnection({
5
+ bundlePolicy: 'max-bundle',
6
+ iceServers: this.getIceServers(turnServers),
7
+ iceTransportPolicy: 'relay',
8
+ });
9
+ }
10
+ isConnectionUnstable = () => {
11
+ if (!this.connection)
12
+ return false;
13
+ const isSignalingUnstable = this.connection.signalingState !== 'stable';
14
+ const isConnectionNotConnected = this.connection.connectionState !== 'connected';
15
+ const isIceNotConnected = this.connection.iceConnectionState !== 'connected';
16
+ return isSignalingUnstable && isConnectionNotConnected && isIceNotConnected;
17
+ };
18
+ /**
19
+ * Configures TURN servers for WebRTC connections by adding them to the provided RTCConfiguration object.
20
+ */
21
+ getIceServers = (turnServers) => {
22
+ return turnServers.map((turnServer) => {
23
+ const transport = turnServer.transport === 'tls' ? 'tcp' : turnServer.transport;
24
+ const uri = turnServer.transport === 'tls' ? 'turns' : 'turn';
25
+ const address = turnServer.serverAddr;
26
+ const port = turnServer.serverPort;
27
+ return {
28
+ credential: turnServer.password,
29
+ urls: uri.concat(':', address, ':', port, '?transport=', transport),
30
+ username: turnServer.username,
31
+ };
32
+ });
33
+ };
34
+ getConnection = () => {
35
+ return this.connection;
36
+ };
37
+ setTransceiversToReadOnly = () => {
38
+ this.connection
39
+ .getTransceivers()
40
+ .forEach((transceiver) => (transceiver.direction = 'sendonly'));
41
+ };
42
+ addTransceiversIfNeeded = (serverTracks) => {
43
+ const recvTransceivers = this.connection
44
+ .getTransceivers()
45
+ .filter((elem) => elem.direction === 'recvonly');
46
+ ['audio', 'video']
47
+ .flatMap((type) => this.getNeededTransceiversTypes(type, recvTransceivers, serverTracks))
48
+ .forEach((kind) => this.connection.addTransceiver(kind, { direction: 'recvonly' }));
49
+ };
50
+ getNeededTransceiversTypes = (type, recvTransceivers, serverTracks) => {
51
+ const typeNumber = serverTracks.get(type) ?? 0;
52
+ const typeTransceiversNumber = recvTransceivers.filter((elem) => elem.receiver.track.kind === type).length;
53
+ return Array(typeNumber - typeTransceiversNumber).fill(type);
54
+ };
55
+ addTransceiver = (track, transceiverConfig) => {
56
+ this.connection.addTransceiver(track, transceiverConfig);
57
+ };
58
+ setOnTrackReady = (onTrackReady) => {
59
+ this.connection.ontrack = onTrackReady;
60
+ };
61
+ setRemoteDescription = async (data) => {
62
+ await this.connection.setRemoteDescription(data);
63
+ };
64
+ isTrackInUse = (track) => this.connection.getSenders().some((val) => val.track === track);
65
+ setTransceiverDirection = () => {
66
+ this.connection
67
+ .getTransceivers()
68
+ .filter((transceiver) => transceiver.direction === 'sendrecv')
69
+ .forEach((transceiver) => {
70
+ transceiver.direction = 'sendonly';
71
+ });
72
+ };
73
+ removeTrack = (sender) => {
74
+ this.connection.removeTrack(sender);
75
+ };
76
+ findSender = (mediaStreamTrackId) => this.connection
77
+ .getSenders()
78
+ .find((sender) => sender.track && sender.track.id === mediaStreamTrackId);
79
+ addIceCandidate = async (iceCandidate) => {
80
+ await this.connection.addIceCandidate(iceCandidate);
81
+ };
82
+ }
@@ -0,0 +1,12 @@
1
+ import type { Encoding } from './types';
2
+ import { type BandwidthLimit } from './types';
3
+ export type Bitrate = number;
4
+ export type Bitrates = Record<Encoding, Bitrate> | Bitrate;
5
+ export declare const defaultBitrates: {
6
+ readonly audio: Bitrate;
7
+ readonly video: Bitrate;
8
+ };
9
+ export declare const UNLIMITED_BANDWIDTH: Bitrate;
10
+ export declare const defaultSimulcastBitrates: {
11
+ [key in Encoding]: BandwidthLimit;
12
+ };
@@ -0,0 +1,12 @@
1
+ // The suggested bitrate values are based on our internal tests.
2
+ export const defaultBitrates = {
3
+ audio: 50_000,
4
+ video: 2_500_000,
5
+ };
6
+ export const UNLIMITED_BANDWIDTH = 0;
7
+ // The suggested bitrate values are based on our internal tests.
8
+ export const defaultSimulcastBitrates = {
9
+ h: 2_500_000,
10
+ m: 500_000,
11
+ l: 150_000,
12
+ };
@@ -0,0 +1,8 @@
1
+ export declare class Deferred<T, E = unknown> {
2
+ promise: Promise<T>;
3
+ private resolveFn;
4
+ private rejectFn;
5
+ constructor();
6
+ resolve(value: T): void;
7
+ reject(reason?: E): void;
8
+ }
@@ -0,0 +1,17 @@
1
+ export class Deferred {
2
+ promise;
3
+ resolveFn = () => null;
4
+ rejectFn = () => null;
5
+ constructor() {
6
+ this.promise = new Promise((resolve, reject) => {
7
+ this.resolveFn = resolve;
8
+ this.rejectFn = reject;
9
+ });
10
+ }
11
+ resolve(value) {
12
+ this.resolveFn(value);
13
+ }
14
+ reject(reason) {
15
+ this.rejectFn(reason);
16
+ }
17
+ }
@@ -0,0 +1,3 @@
1
+ export type { TrackBandwidthLimit, SimulcastBandwidthLimit, BandwidthLimit, WebRTCEndpointEvents, TrackContextEvents, Endpoint, SimulcastConfig, TrackContext, Encoding, VadStatus, EncodingReason, Config, MetadataParser, TrackKind, } from './types';
2
+ export { WebRTCEndpoint } from './webRTCEndpoint';
3
+ export type { SerializedMediaEvent, MediaEvent } from './mediaEvent';
@@ -0,0 +1 @@
1
+ export { WebRTCEndpoint } from './webRTCEndpoint';
@@ -0,0 +1,26 @@
1
+ import type TypedEmitter from 'typed-emitter';
2
+ import type { EncodingReason, Endpoint, MetadataParser, SimulcastConfig, TrackBandwidthLimit, TrackContext, TrackContextEvents, Encoding, TrackKind, TrackNegotiationStatus, VadStatus } from './types';
3
+ export declare const isTrackKind: (kind: string) => kind is TrackKind;
4
+ declare const TrackContextImpl_base: new <EndpointMetadata_1, ParsedMetadata_1>() => TypedEmitter<Required<TrackContextEvents<EndpointMetadata_1, ParsedMetadata_1>>>;
5
+ export declare class TrackContextImpl<EndpointMetadata, ParsedMetadata> extends TrackContextImpl_base<EndpointMetadata, ParsedMetadata> implements TrackContext<EndpointMetadata, ParsedMetadata> {
6
+ endpoint: Endpoint<EndpointMetadata, ParsedMetadata>;
7
+ trackId: string;
8
+ track: MediaStreamTrack | null;
9
+ trackKind: TrackKind | null;
10
+ stream: MediaStream | null;
11
+ metadata?: ParsedMetadata;
12
+ rawMetadata: any;
13
+ metadataParsingError?: any;
14
+ simulcastConfig?: SimulcastConfig;
15
+ maxBandwidth: TrackBandwidthLimit;
16
+ encoding?: Encoding;
17
+ encodingReason?: EncodingReason;
18
+ vadStatus: VadStatus;
19
+ negotiationStatus: TrackNegotiationStatus;
20
+ pendingMetadataUpdate: boolean;
21
+ constructor(endpoint: Endpoint<EndpointMetadata, ParsedMetadata>, trackId: string, metadata: any, simulcastConfig: SimulcastConfig, metadataParser: MetadataParser<ParsedMetadata>);
22
+ }
23
+ export type EndpointWithTrackContext<EndpointMetadata, TrackMetadata> = Omit<Endpoint<EndpointMetadata, TrackMetadata>, 'tracks'> & {
24
+ tracks: Map<string, TrackContextImpl<EndpointMetadata, TrackMetadata>>;
25
+ };
26
+ export {};
@@ -0,0 +1,34 @@
1
+ import EventEmitter from 'events';
2
+ export const isTrackKind = (kind) => kind === 'audio' || kind === 'video';
3
+ export class TrackContextImpl extends EventEmitter {
4
+ endpoint;
5
+ trackId;
6
+ track = null;
7
+ trackKind = null;
8
+ stream = null;
9
+ metadata;
10
+ rawMetadata;
11
+ metadataParsingError;
12
+ simulcastConfig;
13
+ maxBandwidth = 0;
14
+ encoding;
15
+ encodingReason;
16
+ vadStatus = 'silence';
17
+ negotiationStatus = 'awaiting';
18
+ // Indicates that metadata were changed when in "offered" negotiationStatus
19
+ // and `updateTrackMetadata` Media Event should be sent after the transition to "done"
20
+ pendingMetadataUpdate = false;
21
+ constructor(endpoint, trackId, metadata, simulcastConfig, metadataParser) {
22
+ super();
23
+ this.endpoint = endpoint;
24
+ this.trackId = trackId;
25
+ try {
26
+ this.metadata = metadataParser(metadata);
27
+ }
28
+ catch (error) {
29
+ this.metadataParsingError = error;
30
+ }
31
+ this.rawMetadata = metadata;
32
+ this.simulcastConfig = simulcastConfig;
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ export type SerializedMediaEvent = string;
2
+ export interface MediaEvent {
3
+ type: string;
4
+ key?: string;
5
+ data?: any;
6
+ }
7
+ export declare function serializeMediaEvent(mediaEvent: MediaEvent): SerializedMediaEvent;
8
+ export declare function deserializeMediaEvent(serializedMediaEvent: SerializedMediaEvent): MediaEvent;
9
+ export declare function generateMediaEvent(type: string, data?: any): MediaEvent;
10
+ export declare function generateCustomEvent(data?: any): MediaEvent;
@@ -0,0 +1,16 @@
1
+ export function serializeMediaEvent(mediaEvent) {
2
+ return JSON.stringify(mediaEvent);
3
+ }
4
+ export function deserializeMediaEvent(serializedMediaEvent) {
5
+ return JSON.parse(serializedMediaEvent);
6
+ }
7
+ export function generateMediaEvent(type, data) {
8
+ let event = { type };
9
+ if (data) {
10
+ event = { ...event, data };
11
+ }
12
+ return event;
13
+ }
14
+ export function generateCustomEvent(data) {
15
+ return generateMediaEvent('custom', data);
16
+ }
@@ -0,0 +1,51 @@
1
+ import { LocalTrack } from './LocalTrack';
2
+ import type { BandwidthLimit, Encoding, MetadataParser, MLineId, RemoteTrackId, SimulcastConfig, TrackBandwidthLimit, WebRTCEndpointEvents } from '../types';
3
+ import type { EndpointWithTrackContext } from '../internal';
4
+ import { TrackContextImpl } from '../internal';
5
+ import type { MediaEvent } from '../mediaEvent';
6
+ import type { ConnectionManager } from '../ConnectionManager';
7
+ import type { EndpointId, TrackId } from './TrackCommon';
8
+ import type { WebRTCEndpoint } from '../webRTCEndpoint';
9
+ export type MidToTrackId = Record<MLineId, TrackId>;
10
+ /**
11
+ * This class encapsulates methods related to handling the list of local tracks and local endpoint.
12
+ * It stores and mutates part of the client state for this local peer.
13
+ * It emits local events for local tracks and endpoints,
14
+ * and delegates mutation logic to the appropriate `LocalTrack` objects.
15
+ * It's responsible for creating `MidToTrackId` record which is required in `sdpOffer`
16
+ */
17
+ export declare class Local<EndpointMetadata, TrackMetadata> {
18
+ private readonly localTracks;
19
+ private readonly localEndpoint;
20
+ private readonly endpointMetadataParser;
21
+ private readonly trackMetadataParser;
22
+ private readonly emit;
23
+ private readonly sendMediaEvent;
24
+ private connection;
25
+ constructor(emit: <E extends keyof Required<WebRTCEndpointEvents<EndpointMetadata, TrackMetadata>>>(event: E, ...args: Parameters<Required<WebRTCEndpointEvents<EndpointMetadata, TrackMetadata>>[E]>) => void, sendMediaEvent: (mediaEvent: MediaEvent) => void, endpointMetadataParser: MetadataParser<EndpointMetadata>, trackMetadataParser: MetadataParser<TrackMetadata>);
26
+ updateSenders: () => void;
27
+ updateMLineIds: (midToTrackId: MidToTrackId) => void;
28
+ updateConnection: (connection: ConnectionManager) => void;
29
+ createSdpOfferEvent: (offer: RTCSessionDescriptionInit) => MediaEvent;
30
+ addTrack: (connection: ConnectionManager | undefined, trackId: string, track: MediaStreamTrack, stream: MediaStream, trackMetadata: TrackMetadata | undefined, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit) => LocalTrack<EndpointMetadata, TrackMetadata>;
31
+ disableAllLocalTrackEncodings: () => Promise<void>;
32
+ getTrackByMidOrNull: (mid: string) => LocalTrack<EndpointMetadata, TrackMetadata> | null;
33
+ removeTrack: (trackId: TrackId) => void;
34
+ replaceTrack: (webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>, trackId: TrackId, newTrack: MediaStreamTrack | null, newTrackMetadata: TrackMetadata | undefined) => Promise<void>;
35
+ setEndpointMetadata: (metadata: EndpointMetadata) => void;
36
+ getEndpoint: () => EndpointWithTrackContext<EndpointMetadata, TrackMetadata>;
37
+ setTrackBandwidth: (trackId: string, bandwidth: BandwidthLimit) => Promise<void>;
38
+ getTrackIdToTrack: () => Map<RemoteTrackId, TrackContextImpl<EndpointMetadata, TrackMetadata>>;
39
+ setLocalEndpointId: (endpointId: EndpointId) => void;
40
+ setEncodingBandwidth: (trackId: TrackId, rid: Encoding, bandwidth: BandwidthLimit) => Promise<void>;
41
+ updateEndpointMetadata: (metadata: unknown) => void;
42
+ updateLocalTrackMetadata: (trackId: TrackId, metadata: unknown) => void;
43
+ disableLocalTrackEncoding: (trackId: string, encoding: Encoding) => Promise<void>;
44
+ enableLocalTrackEncoding: (trackId: TrackId, encoding: Encoding) => Promise<void>;
45
+ private getTrackIdToMetadata;
46
+ private getTrackIdToTrackBitrates;
47
+ private getMidToTrackId;
48
+ private getTransceiverMapping;
49
+ setLocalTrackStatusToOffered: () => void;
50
+ addAllTracksToConnection: () => void;
51
+ }
@@ -0,0 +1,289 @@
1
+ import { LocalTrack } from './LocalTrack';
2
+ import { isTrackKind, TrackContextImpl } from '../internal';
3
+ import { generateCustomEvent, generateMediaEvent } from '../mediaEvent';
4
+ /**
5
+ * This class encapsulates methods related to handling the list of local tracks and local endpoint.
6
+ * It stores and mutates part of the client state for this local peer.
7
+ * It emits local events for local tracks and endpoints,
8
+ * and delegates mutation logic to the appropriate `LocalTrack` objects.
9
+ * It's responsible for creating `MidToTrackId` record which is required in `sdpOffer`
10
+ */
11
+ export class Local {
12
+ localTracks = {};
13
+ localEndpoint = {
14
+ id: '',
15
+ type: 'webrtc',
16
+ metadata: undefined,
17
+ rawMetadata: undefined,
18
+ tracks: new Map(),
19
+ };
20
+ endpointMetadataParser;
21
+ trackMetadataParser;
22
+ emit;
23
+ sendMediaEvent;
24
+ connection = null;
25
+ constructor(emit, sendMediaEvent, endpointMetadataParser, trackMetadataParser) {
26
+ this.emit = emit;
27
+ this.sendMediaEvent = sendMediaEvent;
28
+ this.endpointMetadataParser = endpointMetadataParser;
29
+ this.trackMetadataParser = trackMetadataParser;
30
+ }
31
+ updateSenders = () => {
32
+ Object.values(this.localTracks).forEach((localTrack) => {
33
+ localTrack.updateSender();
34
+ });
35
+ };
36
+ updateMLineIds = (midToTrackId) => {
37
+ Object.entries(midToTrackId).forEach(([mLineId, trackId]) => {
38
+ const localTrack = this.localTracks[trackId];
39
+ if (localTrack) {
40
+ localTrack.setMLineId(mLineId);
41
+ }
42
+ });
43
+ };
44
+ updateConnection = (connection) => {
45
+ this.connection = connection;
46
+ Object.values(this.localTracks).forEach((track) => {
47
+ track.updateConnection(connection);
48
+ });
49
+ };
50
+ createSdpOfferEvent = (offer) => {
51
+ const trackIdToTrackMetadata = this.getTrackIdToMetadata();
52
+ const trackIdToTrackBitrates = this.getTrackIdToTrackBitrates();
53
+ const midToTrackId = this.getMidToTrackId();
54
+ return generateCustomEvent({
55
+ type: 'sdpOffer',
56
+ data: {
57
+ sdpOffer: offer,
58
+ trackIdToTrackMetadata,
59
+ trackIdToTrackBitrates,
60
+ midToTrackId,
61
+ },
62
+ });
63
+ };
64
+ addTrack = (connection, trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth) => {
65
+ const trackContext = new TrackContextImpl(this.localEndpoint, trackId, trackMetadata, simulcastConfig, this.trackMetadataParser);
66
+ trackContext.track = track;
67
+ trackContext.stream = stream;
68
+ trackContext.maxBandwidth = maxBandwidth;
69
+ if (!isTrackKind(track.kind))
70
+ throw new Error('Track has no kind');
71
+ trackContext.trackKind = track.kind;
72
+ this.localEndpoint.tracks.set(trackId, trackContext);
73
+ const trackManager = new LocalTrack(connection, trackId, trackContext, this.trackMetadataParser);
74
+ this.localTracks[trackId] = trackManager;
75
+ return trackManager;
76
+ };
77
+ disableAllLocalTrackEncodings = async () => {
78
+ // TODO: Why are we disabling already disabled encodings?
79
+ // I think this part of the code is invoked to mutate RTCRtpSender.getParameters().encodings.active.
80
+ // We should probably implement a separate method for that, because disabling already disabled tracks seems weird.
81
+ for (const [trackId, trackManager] of Object.entries(this.localTracks)) {
82
+ for (const encoding of trackManager.getDisabledEncodings()) {
83
+ await this.disableLocalTrackEncoding(trackId, encoding);
84
+ }
85
+ }
86
+ };
87
+ getTrackByMidOrNull = (mid) => {
88
+ return (Object.values(this.localTracks).find((track) => track.mLineId === mid) ??
89
+ null);
90
+ };
91
+ removeTrack = (trackId) => {
92
+ const trackManager = this.localTracks[trackId];
93
+ if (!trackManager)
94
+ throw new Error(`Cannot find ${trackId}`);
95
+ trackManager.removeFromConnection();
96
+ const mediaEvent = generateCustomEvent({ type: 'renegotiateTracks' });
97
+ this.sendMediaEvent(mediaEvent);
98
+ this.localEndpoint.tracks.delete(trackId);
99
+ delete this.localTracks[trackId];
100
+ };
101
+ replaceTrack = async (
102
+ // todo remove webrtc with newTrackMetadata
103
+ webrtc, trackId, newTrack,
104
+ // todo remove webrtc with newTrackMetadata
105
+ newTrackMetadata) => {
106
+ // TODO add validation to track.kind, you cannot replace video with audio
107
+ const trackManager = this.localTracks[trackId];
108
+ if (!trackManager)
109
+ throw new Error(`Cannot find ${trackId}`);
110
+ await trackManager.replaceTrack(newTrack, newTrackMetadata, webrtc);
111
+ };
112
+ setEndpointMetadata = (metadata) => {
113
+ try {
114
+ this.localEndpoint.metadata = this.endpointMetadataParser(metadata);
115
+ this.localEndpoint.metadataParsingError = undefined;
116
+ }
117
+ catch (error) {
118
+ this.localEndpoint.metadata = undefined;
119
+ this.localEndpoint.metadataParsingError = error;
120
+ throw error;
121
+ }
122
+ this.localEndpoint.rawMetadata = metadata;
123
+ };
124
+ getEndpoint = () => {
125
+ return this.localEndpoint;
126
+ };
127
+ setTrackBandwidth = async (trackId, bandwidth) => {
128
+ // FIXME: maxBandwidth in TrackContext is not updated
129
+ const trackManager = this.localTracks[trackId];
130
+ if (!trackManager)
131
+ throw new Error(`Cannot find ${trackId}`);
132
+ await trackManager.setTrackBandwidth(bandwidth);
133
+ const mediaEvent = trackManager.createTrackVariantBitratesEvent();
134
+ this.sendMediaEvent(mediaEvent);
135
+ this.emit('localTrackBandwidthSet', {
136
+ trackId,
137
+ bandwidth,
138
+ });
139
+ };
140
+ getTrackIdToTrack = () => {
141
+ const entries = Object.values(this.localTracks).map((track) => [track.id, track.trackContext]);
142
+ return new Map(entries);
143
+ };
144
+ setLocalEndpointId = (endpointId) => {
145
+ this.localEndpoint.id = endpointId;
146
+ };
147
+ setEncodingBandwidth = async (trackId, rid, bandwidth) => {
148
+ const trackManager = this.localTracks[trackId];
149
+ if (!trackManager)
150
+ throw new Error(`Cannot find ${trackId}`);
151
+ await trackManager.setEncodingBandwidth(rid, bandwidth);
152
+ const mediaEvent = generateCustomEvent({
153
+ type: 'trackVariantBitrates',
154
+ data: {
155
+ trackId: trackId,
156
+ variantBitrates: trackManager.getTrackBitrates(),
157
+ },
158
+ });
159
+ this.sendMediaEvent(mediaEvent);
160
+ this.emit('localTrackEncodingBandwidthSet', {
161
+ trackId,
162
+ rid,
163
+ bandwidth,
164
+ });
165
+ };
166
+ updateEndpointMetadata = (metadata) => {
167
+ this.localEndpoint.metadata = this.endpointMetadataParser(metadata);
168
+ this.localEndpoint.rawMetadata = this.localEndpoint.metadata;
169
+ this.localEndpoint.metadataParsingError = undefined;
170
+ const mediaEvent = generateMediaEvent('updateEndpointMetadata', {
171
+ metadata: this.localEndpoint.metadata,
172
+ });
173
+ this.sendMediaEvent(mediaEvent);
174
+ this.emit('localEndpointMetadataChanged', {
175
+ metadata: this.localEndpoint.metadata,
176
+ });
177
+ };
178
+ updateLocalTrackMetadata = (trackId, metadata) => {
179
+ const trackManager = this.localTracks[trackId];
180
+ if (!trackManager)
181
+ throw new Error(`Cannot find ${trackId}`);
182
+ trackManager.updateTrackMetadata(metadata);
183
+ const trackContext = trackManager.trackContext;
184
+ const mediaEvent = generateMediaEvent('updateTrackMetadata', {
185
+ trackId,
186
+ trackMetadata: metadata,
187
+ });
188
+ switch (trackContext.negotiationStatus) {
189
+ case 'done':
190
+ this.sendMediaEvent(mediaEvent);
191
+ this.emit('localTrackMetadataChanged', {
192
+ trackId,
193
+ metadata: trackContext.metadata,
194
+ });
195
+ break;
196
+ case 'offered':
197
+ trackContext.pendingMetadataUpdate = true;
198
+ break;
199
+ case 'awaiting':
200
+ // We don't need to do anything
201
+ break;
202
+ }
203
+ };
204
+ disableLocalTrackEncoding = async (trackId, encoding) => {
205
+ const localTrack = this.localTracks[trackId];
206
+ if (!localTrack)
207
+ throw new Error(`Track ${trackId} not found`);
208
+ await localTrack.disableTrackEncoding(encoding);
209
+ const mediaEvent = generateMediaEvent('disableTrackEncoding', {
210
+ trackId: trackId,
211
+ encoding: encoding,
212
+ });
213
+ this.sendMediaEvent(mediaEvent);
214
+ this.emit('localTrackEncodingEnabled', {
215
+ trackId,
216
+ encoding,
217
+ });
218
+ };
219
+ enableLocalTrackEncoding = async (trackId, encoding) => {
220
+ const trackManager = this.localTracks[trackId];
221
+ if (!trackManager)
222
+ throw new Error(`Cannot find ${trackId}`);
223
+ await trackManager.enableTrackEncoding(encoding);
224
+ const mediaEvent = generateMediaEvent('enableTrackEncoding', {
225
+ trackId: trackId,
226
+ encoding: encoding,
227
+ });
228
+ this.sendMediaEvent(mediaEvent);
229
+ this.emit('localTrackEncodingEnabled', {
230
+ trackId,
231
+ encoding,
232
+ });
233
+ };
234
+ getTrackIdToMetadata = () => {
235
+ return Object.values(this.localTracks).reduce((previousValue, localTrack) => ({
236
+ ...previousValue,
237
+ [localTrack.id]: localTrack.trackContext.metadata,
238
+ }), {});
239
+ };
240
+ getTrackIdToTrackBitrates = () => {
241
+ return Object.values(this.localTracks).reduce((previousValue, localTrack) => ({
242
+ ...previousValue,
243
+ [localTrack.id]: localTrack.getTrackBitrates(),
244
+ }), {});
245
+ };
246
+ getMidToTrackId = () => {
247
+ if (!this.connection)
248
+ return null;
249
+ // - negotiated unmuted tracks: tracks added in previous negotiation, data is being transmitted
250
+ // - not yet negotiated tracks: tracks added in this negotiation, data will be transmitted after successful negotiation
251
+ const mappingFromTransceivers = this.getTransceiverMapping();
252
+ // - negotiated unmuted tracks: tracks added in previous negotiation, data is being transmitted
253
+ // - negotiated muted tracks: tracks added in previous negotiation, data is not being transmitted but can be transmitted in the future
254
+ const mappingFromLocalNegotiatedTracks = Object.values(this.localTracks).reduce((acc, curr) => {
255
+ if (curr.mLineId) {
256
+ acc[curr.mLineId] = curr.id;
257
+ }
258
+ return acc;
259
+ }, {});
260
+ return { ...mappingFromTransceivers, ...mappingFromLocalNegotiatedTracks };
261
+ };
262
+ getTransceiverMapping = () => {
263
+ if (!this.connection)
264
+ return {};
265
+ return this.connection
266
+ .getConnection()
267
+ .getTransceivers()
268
+ .filter((transceiver) => transceiver.sender.track?.id && transceiver.mid)
269
+ .reduce((acc, transceiver) => {
270
+ const localTrackId = transceiver.sender.track.id;
271
+ const mid = transceiver.mid;
272
+ const localTrack = Object.values(this.localTracks).find((track) => track.mediaStreamTrackId === localTrackId);
273
+ if (!localTrack)
274
+ throw new Error('Local track not found');
275
+ acc[mid] = localTrack.trackContext.trackId;
276
+ return acc;
277
+ }, {});
278
+ };
279
+ setLocalTrackStatusToOffered = () => {
280
+ Object.values(this.localTracks).forEach((localTrack) => {
281
+ localTrack.trackContext.negotiationStatus = 'offered';
282
+ });
283
+ };
284
+ addAllTracksToConnection = () => {
285
+ Object.values(this.localTracks).forEach((localTrack) => {
286
+ localTrack.addTrackToConnection();
287
+ });
288
+ };
289
+ }
@@ -0,0 +1,62 @@
1
+ import type { TrackContextImpl } from '../internal';
2
+ import type { BandwidthLimit, Encoding, LocalTrackId, MediaStreamTrackId, MetadataParser, MLineId } from '../types';
3
+ import type { TrackCommon, TrackEncodings, TrackId } from './TrackCommon';
4
+ import type { WebRTCEndpoint } from '../webRTCEndpoint';
5
+ import type { Bitrates } from '../bitrate';
6
+ import type { ConnectionManager } from '../ConnectionManager';
7
+ /**
8
+ * This is a wrapper over `TrackContext` that adds additional properties such as:
9
+ * - `MLineId`: required to generate sdpOffer
10
+ * - `MediaStreamTrackId`: required to generate sdpOffer and match RTCRtpSender
11
+ * - `RTCRtpSender`: required to manage RTCPeerConnection track, e.g.:
12
+ * - enable/disable encoding
13
+ * - remove from connection
14
+ * - replace track
15
+ * - set track bandwidth
16
+ *
17
+ * In the future, this object could potentially be merged with `TrackContextImpl`.
18
+ *
19
+ * # Lifecycle, state transitions
20
+ *
21
+ * I identified the following states
22
+ * - Before creating `Connection` object
23
+ * - connection === null
24
+ * - sender === null
25
+ * - mLineId === null
26
+ * - After creating `Connection` object, during `onOfferData` handler
27
+ * // TODO: Verify if the track acquires a sender at this point or if it's only for past tracks
28
+ * - connection !== null
29
+ * - sender !== null
30
+ * - mLineId === null
31
+ * - After establishing connection, during `onSdpAnswer`, track is being sent
32
+ * - connection !== null
33
+ * - sender !== null
34
+ * - mLineId !== null
35
+ */
36
+ export declare class LocalTrack<EndpointMetadata, TrackMetadata> implements TrackCommon {
37
+ readonly id: TrackId;
38
+ mediaStreamTrackId: MediaStreamTrackId | null;
39
+ mLineId: MLineId | null;
40
+ readonly trackContext: TrackContextImpl<EndpointMetadata, TrackMetadata>;
41
+ private sender;
42
+ readonly encodings: TrackEncodings;
43
+ private readonly metadataParser;
44
+ connection: ConnectionManager | undefined;
45
+ constructor(connection: ConnectionManager | undefined, id: LocalTrackId, trackContext: TrackContextImpl<EndpointMetadata, TrackMetadata>, metadataParser: MetadataParser<TrackMetadata>);
46
+ updateSender: () => void;
47
+ updateConnection: (connection: ConnectionManager) => void;
48
+ disableTrackEncoding: (encoding: Encoding) => Promise<void>;
49
+ addTrackToConnection: () => void;
50
+ private updateEncodings;
51
+ removeFromConnection: () => void;
52
+ replaceTrack: (newTrack: MediaStreamTrack | null, newTrackMetadata: TrackMetadata | undefined, webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>) => Promise<void>;
53
+ setTrackBandwidth: (bandwidth: BandwidthLimit) => Promise<void>;
54
+ setEncodingBandwidth(rid: Encoding, bandwidth: BandwidthLimit): Promise<void>;
55
+ updateTrackMetadata: (metadata: unknown) => void;
56
+ enableTrackEncoding: (encoding: Encoding) => Promise<void>;
57
+ getDisabledEncodings: () => Encoding[];
58
+ setMLineId: (mLineId: MLineId) => void;
59
+ private isNotSimulcastTrack;
60
+ getTrackBitrates: () => Bitrates;
61
+ createTrackVariantBitratesEvent: () => import("../mediaEvent").MediaEvent;
62
+ }