@zero-server/sdk 0.9.5 → 0.9.7
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/README.md +54 -64
- package/index.js +116 -4
- package/lib/app.js +22 -22
- package/lib/auth/authorize.js +11 -11
- package/lib/auth/enrollment.js +5 -5
- package/lib/auth/jwt.js +9 -9
- package/lib/auth/oauth.js +1 -1
- package/lib/auth/session.js +5 -5
- package/lib/auth/trustedDevice.js +2 -2
- package/lib/auth/twoFactor.js +11 -11
- package/lib/auth/webauthn.js +6 -6
- package/lib/body/json.js +1 -1
- package/lib/body/raw.js +1 -1
- package/lib/body/rawBuffer.js +1 -1
- package/lib/body/text.js +1 -1
- package/lib/body/urlencoded.js +3 -3
- package/lib/cli.js +19 -4
- package/lib/cluster.js +3 -3
- package/lib/debug.js +10 -10
- package/lib/env/index.js +11 -11
- package/lib/errors.js +131 -16
- package/lib/fetch/index.js +1 -1
- package/lib/grpc/call.js +14 -14
- package/lib/grpc/client.js +4 -4
- package/lib/grpc/codec.js +7 -7
- package/lib/grpc/credentials.js +2 -2
- package/lib/grpc/frame.js +2 -2
- package/lib/grpc/health.js +3 -3
- package/lib/grpc/index.js +3 -3
- package/lib/grpc/metadata.js +3 -3
- package/lib/grpc/proto.js +5 -5
- package/lib/grpc/reflection.js +2 -2
- package/lib/grpc/server.js +3 -3
- package/lib/grpc/status.js +2 -2
- package/lib/grpc/watch.js +1 -1
- package/lib/http/request.js +13 -13
- package/lib/http/response.js +2 -2
- package/lib/lifecycle.js +5 -5
- package/lib/middleware/compress.js +4 -4
- package/lib/observe/health.js +1 -1
- package/lib/observe/index.js +1 -1
- package/lib/observe/logger.js +3 -3
- package/lib/observe/metrics.js +4 -4
- package/lib/observe/tracing.js +4 -4
- package/lib/orm/adapters/json.js +1 -1
- package/lib/orm/adapters/memory.js +2 -2
- package/lib/orm/adapters/mongo.js +2 -2
- package/lib/orm/adapters/mysql.js +2 -2
- package/lib/orm/adapters/postgres.js +2 -2
- package/lib/orm/adapters/sqlite.js +3 -3
- package/lib/orm/audit.js +1 -1
- package/lib/orm/index.js +7 -7
- package/lib/orm/migrate.js +1 -1
- package/lib/orm/model.js +15 -15
- package/lib/orm/procedures.js +1 -1
- package/lib/orm/profiler.js +1 -1
- package/lib/orm/query.js +9 -9
- package/lib/orm/schema.js +1 -1
- package/lib/orm/seed/data/person.js +1 -1
- package/lib/orm/seed/fake.js +10 -10
- package/lib/orm/seed/index.js +4 -4
- package/lib/orm/seed/rng.js +1 -1
- package/lib/orm/snapshot.js +2 -2
- package/lib/orm/tenancy.js +6 -6
- package/lib/orm/views.js +1 -1
- package/lib/router/index.js +9 -9
- package/lib/webrtc/bot.js +361 -0
- package/lib/webrtc/cli.js +182 -0
- package/lib/webrtc/cluster.js +350 -0
- package/lib/webrtc/e2ee.js +282 -0
- package/lib/webrtc/ice.js +370 -0
- package/lib/webrtc/index.js +132 -0
- package/lib/webrtc/joinToken.js +116 -0
- package/lib/webrtc/observe.js +229 -0
- package/lib/webrtc/peer.js +116 -0
- package/lib/webrtc/room.js +171 -0
- package/lib/webrtc/sdp.js +508 -0
- package/lib/webrtc/sfu/index.js +201 -0
- package/lib/webrtc/sfu/livekit.js +301 -0
- package/lib/webrtc/sfu/mediasoup.js +317 -0
- package/lib/webrtc/sfu/memory.js +204 -0
- package/lib/webrtc/signaling.js +546 -0
- package/lib/webrtc/stun.js +492 -0
- package/lib/webrtc/turn/codec.js +370 -0
- package/lib/webrtc/turn/credentials.js +141 -0
- package/lib/webrtc/turn/server.js +633 -0
- package/package.json +2 -2
- package/types/body.d.ts +1 -1
- package/types/cli.d.ts +1 -1
- package/types/index.d.ts +16 -4
- package/types/middleware.d.ts +1 -1
- package/types/orm.d.ts +3 -3
- package/types/request.d.ts +3 -3
- package/types/webrtc.d.ts +501 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module webrtc/sfu
|
|
3
|
+
* @description SFU adapter base interface and discovery loader.
|
|
4
|
+
*
|
|
5
|
+
* `SfuAdapter` defines the contract every backend (memory / mediasoup /
|
|
6
|
+
* livekit / custom) must implement. `loadSfuAdapter()` resolves either
|
|
7
|
+
* a pre-constructed instance, a known name ('memory', 'mediasoup',
|
|
8
|
+
* 'livekit'), or a duck-typed object into a concrete adapter, throwing
|
|
9
|
+
* `WEBRTC_SFU_NOT_INSTALLED` when a native peerDep is missing.
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const { WebRTCError } = require('../../errors');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Base class every SFU adapter inherits from. Subclasses MUST override
|
|
17
|
+
* every async method; the default implementations throw
|
|
18
|
+
* `WEBRTC_SFU_NOT_IMPLEMENTED` so partial adapters fail loudly.
|
|
19
|
+
*
|
|
20
|
+
* The interface is intentionally tiny so a backend can be written in a
|
|
21
|
+
* single file:
|
|
22
|
+
*
|
|
23
|
+
* class MyAdapter extends SfuAdapter {
|
|
24
|
+
* async createRouter(opts) { ... }
|
|
25
|
+
* async createTransport(router, peer) { ... }
|
|
26
|
+
* ...
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
class SfuAdapter
|
|
30
|
+
{
|
|
31
|
+
constructor()
|
|
32
|
+
{
|
|
33
|
+
this._handlers = new Set();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Override to create a routing context for a single room. */
|
|
37
|
+
async createRouter(_opts)
|
|
38
|
+
{
|
|
39
|
+
throw new WebRTCError('SfuAdapter.createRouter() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Override to allocate a WebRTC transport for a peer in a router. */
|
|
43
|
+
async createTransport(_router, _peer)
|
|
44
|
+
{
|
|
45
|
+
throw new WebRTCError('SfuAdapter.createTransport() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Override to bind a producer ('audio' | 'video') to a transport. */
|
|
49
|
+
async produce(_transport, _kind, _rtpParams)
|
|
50
|
+
{
|
|
51
|
+
throw new WebRTCError('SfuAdapter.produce() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Override to bind a consumer of `producerId` to a transport. */
|
|
55
|
+
async consume(_transport, _producerId, _rtpCaps)
|
|
56
|
+
{
|
|
57
|
+
throw new WebRTCError('SfuAdapter.consume() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Override to pause a producer (mute upstream forwarding). */
|
|
61
|
+
async pauseProducer(_producerId)
|
|
62
|
+
{
|
|
63
|
+
throw new WebRTCError('SfuAdapter.pauseProducer() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Override to resume a previously paused producer. */
|
|
67
|
+
async resumeProducer(_producerId)
|
|
68
|
+
{
|
|
69
|
+
throw new WebRTCError('SfuAdapter.resumeProducer() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Override to close a router and cascade-close its transports. */
|
|
73
|
+
async closeRouter(_routerId)
|
|
74
|
+
{
|
|
75
|
+
throw new WebRTCError('SfuAdapter.closeRouter() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Override to return adapter stats; `scope` may be a routerId/transportId. */
|
|
79
|
+
async stats(_scope)
|
|
80
|
+
{
|
|
81
|
+
throw new WebRTCError('SfuAdapter.stats() not implemented', { code: 'WEBRTC_SFU_NOT_IMPLEMENTED' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Register a handler invoked as `(event, payload)` for adapter-level
|
|
86
|
+
* events ('producer-new', 'producer-pause', 'consumer-new',
|
|
87
|
+
* 'transport-close', 'router-close', etc.).
|
|
88
|
+
*
|
|
89
|
+
* Returns an unsubscribe function.
|
|
90
|
+
*/
|
|
91
|
+
onEvent(handler)
|
|
92
|
+
{
|
|
93
|
+
if (typeof handler !== 'function')
|
|
94
|
+
{
|
|
95
|
+
throw new WebRTCError('onEvent() handler must be a function', { code: 'WEBRTC_SFU_INVALID_HANDLER' });
|
|
96
|
+
}
|
|
97
|
+
this._handlers.add(handler);
|
|
98
|
+
return () => this._handlers.delete(handler);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Emit `event` with `payload` to every registered handler. */
|
|
102
|
+
_emit(event, payload)
|
|
103
|
+
{
|
|
104
|
+
for (const fn of this._handlers)
|
|
105
|
+
{
|
|
106
|
+
try { fn(event, payload); }
|
|
107
|
+
catch (_) { /* swallow handler errors so adapters keep running */ }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Lazy-load and instantiate an SFU adapter.
|
|
114
|
+
*
|
|
115
|
+
* @param {object|string} spec - one of:
|
|
116
|
+
* - an object exposing the SfuAdapter contract (returned as-is),
|
|
117
|
+
* - 'memory' | 'mediasoup' | 'livekit' | adapter package id.
|
|
118
|
+
* @param {object} [opts] - constructor options forwarded to the adapter.
|
|
119
|
+
* @returns {SfuAdapter}
|
|
120
|
+
*/
|
|
121
|
+
function loadSfuAdapter(spec, opts)
|
|
122
|
+
{
|
|
123
|
+
if (spec && typeof spec === 'object' && typeof spec.createRouter === 'function')
|
|
124
|
+
{
|
|
125
|
+
return spec;
|
|
126
|
+
}
|
|
127
|
+
if (typeof spec !== 'string' || spec.length === 0)
|
|
128
|
+
{
|
|
129
|
+
throw new WebRTCError(
|
|
130
|
+
'loadSfuAdapter() requires an adapter instance or a name (memory|mediasoup|livekit|<package>)',
|
|
131
|
+
{ code: 'WEBRTC_SFU_INVALID_SPEC' },
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (spec === 'memory')
|
|
136
|
+
{
|
|
137
|
+
const { MemorySfuAdapter } = require('./memory');
|
|
138
|
+
return new MemorySfuAdapter(opts);
|
|
139
|
+
}
|
|
140
|
+
if (spec === 'mediasoup')
|
|
141
|
+
{
|
|
142
|
+
const Ctor = _tryRequireAdapter('./mediasoup', 'mediasoup');
|
|
143
|
+
return new Ctor(opts);
|
|
144
|
+
}
|
|
145
|
+
if (spec === 'livekit')
|
|
146
|
+
{
|
|
147
|
+
const Ctor = _tryRequireAdapter('./livekit', 'livekit-server-sdk');
|
|
148
|
+
return new Ctor(opts);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// External adapter package - must export `default` or a class.
|
|
152
|
+
let mod;
|
|
153
|
+
try { mod = require(spec); }
|
|
154
|
+
catch (err)
|
|
155
|
+
{
|
|
156
|
+
throw new WebRTCError(
|
|
157
|
+
`SFU adapter package '${spec}' is not installed: ${err.message}`,
|
|
158
|
+
{ code: 'WEBRTC_SFU_NOT_INSTALLED', cause: err },
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const Ctor = mod && (mod.default || mod);
|
|
162
|
+
if (typeof Ctor !== 'function')
|
|
163
|
+
{
|
|
164
|
+
throw new WebRTCError(
|
|
165
|
+
`SFU adapter package '${spec}' does not export a class or default constructor`,
|
|
166
|
+
{ code: 'WEBRTC_SFU_INVALID_PACKAGE' },
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return new Ctor(opts);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @private
|
|
174
|
+
* Try to load a built-in adapter module; surface a clean install message
|
|
175
|
+
* when the wrapped peerDependency is missing.
|
|
176
|
+
*/
|
|
177
|
+
function _tryRequireAdapter(localPath, peerPkg)
|
|
178
|
+
{
|
|
179
|
+
let mod;
|
|
180
|
+
try { mod = require(localPath); }
|
|
181
|
+
catch (err)
|
|
182
|
+
{
|
|
183
|
+
throw new WebRTCError(
|
|
184
|
+
`SFU adapter '${peerPkg}' requires the '${peerPkg}' peerDependency: npm install ${peerPkg}`,
|
|
185
|
+
{ code: 'WEBRTC_SFU_NOT_INSTALLED', cause: err },
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
// The wrapper itself tries `require(peerPkg)`; rethrow with the install
|
|
189
|
+
// hint if construction fails for that reason.
|
|
190
|
+
const Ctor = mod && (mod.default || Object.values(mod).find((v) => typeof v === 'function'));
|
|
191
|
+
if (typeof Ctor !== 'function')
|
|
192
|
+
{
|
|
193
|
+
throw new WebRTCError(
|
|
194
|
+
`SFU adapter module '${localPath}' did not export a constructor`,
|
|
195
|
+
{ code: 'WEBRTC_SFU_INVALID_ADAPTER' },
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return Ctor;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { SfuAdapter, loadSfuAdapter };
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module webrtc/sfu/livekit
|
|
3
|
+
* @description LiveKit-backed SFU adapter (peerDependency on `livekit-server-sdk`).
|
|
4
|
+
*
|
|
5
|
+
* LiveKit's media plane is controlled remotely: rooms live on the
|
|
6
|
+
* LiveKit server, participants connect directly with a signed JWT, and
|
|
7
|
+
* the server SDK exposes a control-plane REST API. This adapter maps
|
|
8
|
+
* the {@link SfuAdapter} contract onto that model:
|
|
9
|
+
*
|
|
10
|
+
* - createRouter(opts) -> RoomServiceClient.createRoom(...)
|
|
11
|
+
* - createTransport(router,peer) -> mints an AccessToken (the "transport"
|
|
12
|
+
* handle is the URL + JWT the peer
|
|
13
|
+
* uses to connect to LiveKit directly)
|
|
14
|
+
* - produce / consume -> local bookkeeping; LiveKit handles
|
|
15
|
+
* the actual media plane client-side
|
|
16
|
+
* - pauseProducer / resume -> RoomServiceClient.mutePublishedTrack()
|
|
17
|
+
* when the producer was registered
|
|
18
|
+
* with a `{room, identity, trackSid}`
|
|
19
|
+
* hint; otherwise emits the event
|
|
20
|
+
* without touching the server
|
|
21
|
+
* - closeRouter(routerId) -> RoomServiceClient.deleteRoom(...)
|
|
22
|
+
* - stats() -> RoomServiceClient.listRooms() /
|
|
23
|
+
* listParticipants(...) plus local
|
|
24
|
+
* counters
|
|
25
|
+
*
|
|
26
|
+
* `livekit-server-sdk` is loaded lazily. Tests inject a stub via
|
|
27
|
+
* `opts.livekit`; in production the constructor `require`s the package
|
|
28
|
+
* and throws `WEBRTC_SFU_NOT_INSTALLED` if it is missing.
|
|
29
|
+
*/
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const { SfuAdapter } = require('./index');
|
|
33
|
+
const { WebRTCError } = require('../../errors');
|
|
34
|
+
|
|
35
|
+
const DEFAULT_TOKEN_TTL = '1h';
|
|
36
|
+
|
|
37
|
+
class LiveKitSfuAdapter extends SfuAdapter
|
|
38
|
+
{
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} opts
|
|
41
|
+
* @param {string} opts.url LiveKit server URL (wss://...).
|
|
42
|
+
* @param {string} opts.apiKey LiveKit API key.
|
|
43
|
+
* @param {string} opts.apiSecret LiveKit API secret.
|
|
44
|
+
* @param {object} [opts.livekit] Injected `livekit-server-sdk` module (testing).
|
|
45
|
+
* @param {object} [opts.client] Pre-built `RoomServiceClient` (testing).
|
|
46
|
+
* @param {object} [opts.defaultRoomOpts] Forwarded to `createRoom()` when fields are missing.
|
|
47
|
+
* @param {object} [opts.defaultGrants] Default `{canPublish, canSubscribe, ...}` for minted tokens.
|
|
48
|
+
* @param {string} [opts.tokenTtl='1h'] AccessToken TTL.
|
|
49
|
+
*/
|
|
50
|
+
constructor(opts)
|
|
51
|
+
{
|
|
52
|
+
super();
|
|
53
|
+
const o = opts || {};
|
|
54
|
+
if (!o.url || !o.apiKey || !o.apiSecret)
|
|
55
|
+
{
|
|
56
|
+
throw new WebRTCError(
|
|
57
|
+
'LiveKitSfuAdapter requires { url, apiKey, apiSecret }',
|
|
58
|
+
{ code: 'WEBRTC_SFU_INVALID_CONFIG' },
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
this._livekit = o.livekit || _tryRequireLivekit();
|
|
62
|
+
this._url = o.url;
|
|
63
|
+
this._apiKey = o.apiKey;
|
|
64
|
+
this._apiSecret = o.apiSecret;
|
|
65
|
+
this._defaultRoomOpts = o.defaultRoomOpts || {};
|
|
66
|
+
this._defaultGrants = o.defaultGrants || { canPublish: true, canSubscribe: true };
|
|
67
|
+
this._tokenTtl = o.tokenTtl || DEFAULT_TOKEN_TTL;
|
|
68
|
+
|
|
69
|
+
this._client = o.client || new this._livekit.RoomServiceClient(this._url, this._apiKey, this._apiSecret);
|
|
70
|
+
|
|
71
|
+
this._rooms = new Map(); // routerId -> { name, opts }
|
|
72
|
+
this._transports = new Map(); // transportId -> { identity, room, token }
|
|
73
|
+
this._producers = new Map(); // producerId -> { kind, transportId, room, identity, trackSid? }
|
|
74
|
+
this._consumers = new Map(); // consumerId -> { producerId, transportId }
|
|
75
|
+
|
|
76
|
+
this._idSeq = 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_nextId(prefix)
|
|
80
|
+
{
|
|
81
|
+
this._idSeq += 1;
|
|
82
|
+
return `${prefix}-${this._idSeq}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a LiveKit room. `opts.name` overrides the auto-generated name.
|
|
87
|
+
* Returns a router handle whose `id` is the room name.
|
|
88
|
+
*/
|
|
89
|
+
async createRouter(opts)
|
|
90
|
+
{
|
|
91
|
+
const o = { ...this._defaultRoomOpts, ...(opts || {}) };
|
|
92
|
+
const name = o.name || this._nextId('room');
|
|
93
|
+
const room = await this._client.createRoom({ ...o, name });
|
|
94
|
+
const id = room && room.name ? room.name : name;
|
|
95
|
+
this._rooms.set(id, { name: id, opts: o, native: room });
|
|
96
|
+
this._emit('router-new', { routerId: id });
|
|
97
|
+
return { id, routerId: id, name: id, _native: room };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Mint an AccessToken for `peer` to join the LiveKit room. Returns
|
|
102
|
+
* a transport handle containing the JWT and URL the peer hands to
|
|
103
|
+
* the LiveKit client SDK.
|
|
104
|
+
*/
|
|
105
|
+
async createTransport(router, peer)
|
|
106
|
+
{
|
|
107
|
+
const routerId = router && router.id;
|
|
108
|
+
const room = routerId && this._rooms.get(routerId);
|
|
109
|
+
if (!room)
|
|
110
|
+
{
|
|
111
|
+
throw new WebRTCError('createTransport: unknown router', { code: 'WEBRTC_SFU_NO_ROUTER' });
|
|
112
|
+
}
|
|
113
|
+
const identity = (peer && peer.id) || this._nextId('peer');
|
|
114
|
+
const at = new this._livekit.AccessToken(this._apiKey, this._apiSecret, {
|
|
115
|
+
identity,
|
|
116
|
+
ttl: this._tokenTtl,
|
|
117
|
+
name: (peer && peer.name) || identity,
|
|
118
|
+
});
|
|
119
|
+
at.addGrant({ roomJoin: true, room: routerId, ...this._defaultGrants });
|
|
120
|
+
const token = await at.toJwt();
|
|
121
|
+
const id = this._nextId('transport');
|
|
122
|
+
const handle = {
|
|
123
|
+
id,
|
|
124
|
+
transportId: id,
|
|
125
|
+
routerId,
|
|
126
|
+
peer: peer || null,
|
|
127
|
+
identity,
|
|
128
|
+
url: this._url,
|
|
129
|
+
token,
|
|
130
|
+
};
|
|
131
|
+
this._transports.set(id, handle);
|
|
132
|
+
this._emit('transport-new', { transportId: id, routerId, peerId: identity });
|
|
133
|
+
return handle;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async produce(transport, kind, rtpParameters)
|
|
137
|
+
{
|
|
138
|
+
if (kind !== 'audio' && kind !== 'video')
|
|
139
|
+
{
|
|
140
|
+
throw new WebRTCError('produce: kind must be "audio" or "video"', { code: 'WEBRTC_SFU_INVALID_KIND' });
|
|
141
|
+
}
|
|
142
|
+
const t = transport && this._transports.get(transport.id);
|
|
143
|
+
if (!t)
|
|
144
|
+
{
|
|
145
|
+
throw new WebRTCError('produce: unknown transport', { code: 'WEBRTC_SFU_NO_TRANSPORT' });
|
|
146
|
+
}
|
|
147
|
+
const id = this._nextId('producer');
|
|
148
|
+
const trackSid = (rtpParameters && rtpParameters.trackSid) || null;
|
|
149
|
+
const p = {
|
|
150
|
+
id, producerId: id, transportId: t.id, kind,
|
|
151
|
+
room: t.routerId, identity: t.identity, trackSid,
|
|
152
|
+
rtpParameters: rtpParameters || {}, paused: false,
|
|
153
|
+
};
|
|
154
|
+
this._producers.set(id, p);
|
|
155
|
+
this._emit('producer-new', { producerId: id, transportId: t.id, kind });
|
|
156
|
+
return p;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async consume(transport, producerId, rtpCapabilities)
|
|
160
|
+
{
|
|
161
|
+
const t = transport && this._transports.get(transport.id);
|
|
162
|
+
if (!t)
|
|
163
|
+
{
|
|
164
|
+
throw new WebRTCError('consume: unknown transport', { code: 'WEBRTC_SFU_NO_TRANSPORT' });
|
|
165
|
+
}
|
|
166
|
+
const prod = this._producers.get(producerId);
|
|
167
|
+
if (!prod)
|
|
168
|
+
{
|
|
169
|
+
throw new WebRTCError('consume: unknown producer', { code: 'WEBRTC_SFU_NO_PRODUCER' });
|
|
170
|
+
}
|
|
171
|
+
const id = this._nextId('consumer');
|
|
172
|
+
const c = {
|
|
173
|
+
id, consumerId: id, transportId: t.id, producerId,
|
|
174
|
+
kind: prod.kind, rtpParameters: prod.rtpParameters,
|
|
175
|
+
rtpCapabilities: rtpCapabilities || {},
|
|
176
|
+
};
|
|
177
|
+
this._consumers.set(id, c);
|
|
178
|
+
this._emit('consumer-new', { consumerId: id, transportId: t.id, producerId });
|
|
179
|
+
return c;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async pauseProducer(producerId)
|
|
183
|
+
{
|
|
184
|
+
const p = this._producers.get(producerId);
|
|
185
|
+
if (!p)
|
|
186
|
+
{
|
|
187
|
+
throw new WebRTCError('pauseProducer: unknown producer', { code: 'WEBRTC_SFU_NO_PRODUCER' });
|
|
188
|
+
}
|
|
189
|
+
if (p.trackSid && typeof this._client.mutePublishedTrack === 'function')
|
|
190
|
+
{
|
|
191
|
+
await this._client.mutePublishedTrack(p.room, p.identity, p.trackSid, true);
|
|
192
|
+
}
|
|
193
|
+
p.paused = true;
|
|
194
|
+
this._emit('producer-pause', { producerId });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async resumeProducer(producerId)
|
|
198
|
+
{
|
|
199
|
+
const p = this._producers.get(producerId);
|
|
200
|
+
if (!p)
|
|
201
|
+
{
|
|
202
|
+
throw new WebRTCError('resumeProducer: unknown producer', { code: 'WEBRTC_SFU_NO_PRODUCER' });
|
|
203
|
+
}
|
|
204
|
+
if (p.trackSid && typeof this._client.mutePublishedTrack === 'function')
|
|
205
|
+
{
|
|
206
|
+
await this._client.mutePublishedTrack(p.room, p.identity, p.trackSid, false);
|
|
207
|
+
}
|
|
208
|
+
p.paused = false;
|
|
209
|
+
this._emit('producer-resume', { producerId });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async closeRouter(routerId)
|
|
213
|
+
{
|
|
214
|
+
const room = this._rooms.get(routerId);
|
|
215
|
+
if (!room) return;
|
|
216
|
+
try { await this._client.deleteRoom(routerId); }
|
|
217
|
+
catch (err)
|
|
218
|
+
{
|
|
219
|
+
this._emit('router-close-error', { routerId, error: err && err.message });
|
|
220
|
+
}
|
|
221
|
+
// Drop every producer / consumer / transport that belonged to this room.
|
|
222
|
+
for (const [pid, p] of this._producers)
|
|
223
|
+
{
|
|
224
|
+
if (p.room === routerId)
|
|
225
|
+
{
|
|
226
|
+
this._producers.delete(pid);
|
|
227
|
+
this._emit('producer-close', { producerId: pid, reason: 'router-close' });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const [cid, c] of this._consumers)
|
|
231
|
+
{
|
|
232
|
+
const t = this._transports.get(c.transportId);
|
|
233
|
+
if (t && t.routerId === routerId)
|
|
234
|
+
{
|
|
235
|
+
this._consumers.delete(cid);
|
|
236
|
+
this._emit('consumer-close', { consumerId: cid, reason: 'router-close' });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const [tid, t] of this._transports)
|
|
240
|
+
{
|
|
241
|
+
if (t.routerId === routerId)
|
|
242
|
+
{
|
|
243
|
+
this._transports.delete(tid);
|
|
244
|
+
this._emit('transport-close', { transportId: tid, reason: 'router-close' });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this._rooms.delete(routerId);
|
|
248
|
+
this._emit('router-close', { routerId });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async stats(scope)
|
|
252
|
+
{
|
|
253
|
+
if (scope && this._rooms.has(scope))
|
|
254
|
+
{
|
|
255
|
+
let participants = null;
|
|
256
|
+
if (typeof this._client.listParticipants === 'function')
|
|
257
|
+
{
|
|
258
|
+
try { participants = await this._client.listParticipants(scope); }
|
|
259
|
+
catch (_) { participants = null; }
|
|
260
|
+
}
|
|
261
|
+
return { kind: 'router', routerId: scope, participants };
|
|
262
|
+
}
|
|
263
|
+
if (scope && this._transports.has(scope))
|
|
264
|
+
{
|
|
265
|
+
const t = this._transports.get(scope);
|
|
266
|
+
return { kind: 'transport', transportId: scope, routerId: t.routerId, identity: t.identity };
|
|
267
|
+
}
|
|
268
|
+
let rooms = null;
|
|
269
|
+
if (typeof this._client.listRooms === 'function')
|
|
270
|
+
{
|
|
271
|
+
try { rooms = await this._client.listRooms(); }
|
|
272
|
+
catch (_) { rooms = null; }
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
kind: 'global',
|
|
276
|
+
routers: this._rooms.size,
|
|
277
|
+
transports: this._transports.size,
|
|
278
|
+
producers: this._producers.size,
|
|
279
|
+
consumers: this._consumers.size,
|
|
280
|
+
rooms,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @private
|
|
287
|
+
* Try to `require('livekit-server-sdk')`; throw a clean install hint when missing.
|
|
288
|
+
*/
|
|
289
|
+
function _tryRequireLivekit()
|
|
290
|
+
{
|
|
291
|
+
try { return require('livekit-server-sdk'); }
|
|
292
|
+
catch (err)
|
|
293
|
+
{
|
|
294
|
+
throw new WebRTCError(
|
|
295
|
+
"SFU adapter 'livekit' requires the 'livekit-server-sdk' peerDependency: npm install livekit-server-sdk",
|
|
296
|
+
{ code: 'WEBRTC_SFU_NOT_INSTALLED', cause: err },
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = { LiveKitSfuAdapter };
|