@norskvideo/norsk-sdk 0.0.322
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/dist/norsk-sdk.d.ts +2412 -0
- package/lib/src/media_nodes/common.d.ts +96 -0
- package/lib/src/media_nodes/common.js +434 -0
- package/lib/src/media_nodes/input.d.ts +423 -0
- package/lib/src/media_nodes/input.js +1080 -0
- package/lib/src/media_nodes/output.d.ts +362 -0
- package/lib/src/media_nodes/output.js +819 -0
- package/lib/src/media_nodes/processor.d.ts +515 -0
- package/lib/src/media_nodes/processor.js +1516 -0
- package/lib/src/media_nodes/types.d.ts +539 -0
- package/lib/src/media_nodes/types.js +1147 -0
- package/lib/src/sdk.d.ts +170 -0
- package/lib/src/sdk.js +515 -0
- package/lib/src/shared/utils.d.ts +2 -0
- package/lib/src/shared/utils.js +136 -0
- package/lib/src/system.d.ts +21 -0
- package/lib/src/system.js +53 -0
- package/lib/src/tsdoc-metadata.json +11 -0
- package/lib/src/types.d.ts +48 -0
- package/lib/src/types.js +359 -0
- package/package.json +41 -0
- package/src/sdk.ts +726 -0
- package/src/system.ts +43 -0
- package/src/types.ts +557 -0
- package/tsconfig.json +22 -0
package/src/sdk.ts
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
import * as grpc from "@grpc/grpc-js";
|
|
2
|
+
import { MediaClient } from "@norskvideo/norsk-api/lib/media_grpc_pb";
|
|
3
|
+
import {
|
|
4
|
+
Version,
|
|
5
|
+
CurrentLoad,
|
|
6
|
+
Log_Level,
|
|
7
|
+
} from "@norskvideo/norsk-api/lib/shared/common_pb";
|
|
8
|
+
import {
|
|
9
|
+
Wave,
|
|
10
|
+
NorskStatusEvent,
|
|
11
|
+
SineWave,
|
|
12
|
+
} from "@norskvideo/norsk-api/lib/media_pb";
|
|
13
|
+
import { Empty } from "@bufbuild/protobuf";
|
|
14
|
+
import { debuglog, norskHost, norskPort } from "./shared/utils";
|
|
15
|
+
import { MediaNodeState, PinToKey } from "./media_nodes/common";
|
|
16
|
+
import {
|
|
17
|
+
AudioSignalGeneratorNode,
|
|
18
|
+
AudioSignalGeneratorSettings,
|
|
19
|
+
BrowserInputNode,
|
|
20
|
+
BrowserInputSettings,
|
|
21
|
+
DeckLinkInputNode,
|
|
22
|
+
DeckLinkInputSettings,
|
|
23
|
+
ImageFileInputNode,
|
|
24
|
+
ImageFileInputSettings,
|
|
25
|
+
LocalFileInputSettings,
|
|
26
|
+
M3u8InputNode,
|
|
27
|
+
M3u8MediaInputSettings,
|
|
28
|
+
Mp4FileInputNode,
|
|
29
|
+
Mp4FileInputSettings,
|
|
30
|
+
NorskInput,
|
|
31
|
+
RtmpServerInputNode,
|
|
32
|
+
RtmpServerInputSettings,
|
|
33
|
+
RtpInputNode,
|
|
34
|
+
RtpInputSettings,
|
|
35
|
+
SrtInputNode,
|
|
36
|
+
SrtInputSettings,
|
|
37
|
+
TsFileInputNode,
|
|
38
|
+
UdpTsInputNode,
|
|
39
|
+
UdpTsInputSettings,
|
|
40
|
+
WebVttFileInputNode,
|
|
41
|
+
WhipInputNode,
|
|
42
|
+
WhipInputSettings,
|
|
43
|
+
} from "./media_nodes/input";
|
|
44
|
+
import {
|
|
45
|
+
Gain,
|
|
46
|
+
NorskProcessor,
|
|
47
|
+
LocalWebRTCNode,
|
|
48
|
+
LocalWebRTCSettings,
|
|
49
|
+
NorskDuplex,
|
|
50
|
+
} from "./media_nodes/processor";
|
|
51
|
+
import {
|
|
52
|
+
HlsAudioOutputNode,
|
|
53
|
+
HlsAudioOutputSettings,
|
|
54
|
+
HlsMasterOutputNode,
|
|
55
|
+
HlsMasterOutputSettings,
|
|
56
|
+
HlsMasterPushOutputNode,
|
|
57
|
+
HlsMasterPushOutputSettings,
|
|
58
|
+
HlsTsAudioOutputNode,
|
|
59
|
+
HlsTsAudioOutputSettings,
|
|
60
|
+
HlsTsAudioPushOutputNode,
|
|
61
|
+
HlsTsAudioPushOutputSettings,
|
|
62
|
+
HlsTsCombinedPushOutputNode,
|
|
63
|
+
HlsTsCombinedPushOutputSettings,
|
|
64
|
+
HlsTsVideoOutputNode,
|
|
65
|
+
HlsTsVideoOutputSettings,
|
|
66
|
+
HlsTsVideoPushOutputNode,
|
|
67
|
+
HlsTsVideoPushOutputSettings,
|
|
68
|
+
HlsVideoOutputNode,
|
|
69
|
+
HlsVideoOutputSettings,
|
|
70
|
+
HlsWebVttOutputNode,
|
|
71
|
+
HlsWebVttOutputSettings,
|
|
72
|
+
HlsWebVttPushOutputNode,
|
|
73
|
+
HlsWebVttPushOutputSettings,
|
|
74
|
+
Mp4FileOutputNode,
|
|
75
|
+
Mp4FileOutputSettings,
|
|
76
|
+
NorskOutput,
|
|
77
|
+
RtmpOutputNode,
|
|
78
|
+
RtmpOutputSettings,
|
|
79
|
+
SrtOutputNode,
|
|
80
|
+
SrtOutputSettings,
|
|
81
|
+
TsFileOutputNode,
|
|
82
|
+
TsFileOutputSettings,
|
|
83
|
+
TsUdpOutputNode,
|
|
84
|
+
TsUdpOutputSettings,
|
|
85
|
+
WebRTCWhipOutputNode,
|
|
86
|
+
WebRTCWhipOutputSettings,
|
|
87
|
+
} from "./media_nodes/output";
|
|
88
|
+
import { StreamKey, StreamMetadata } from "./media_nodes/types";
|
|
89
|
+
import { hardwareInfo, NorskSystem } from "./system";
|
|
90
|
+
|
|
91
|
+
export * from "./types";
|
|
92
|
+
export * from "./system";
|
|
93
|
+
export * from "./media_nodes/types";
|
|
94
|
+
export * from "./media_nodes/input";
|
|
95
|
+
export * from "./media_nodes/output";
|
|
96
|
+
export * from "./media_nodes/processor";
|
|
97
|
+
export * from "./media_nodes/common";
|
|
98
|
+
export { AudioCodec } from "@norskvideo/norsk-api/lib/media_pb";
|
|
99
|
+
export * from "./system";
|
|
100
|
+
export { Version } from "@norskvideo/norsk-api/lib/shared/common_pb";
|
|
101
|
+
|
|
102
|
+
/** @public */
|
|
103
|
+
export type Log = {
|
|
104
|
+
level:
|
|
105
|
+
| "emergency"
|
|
106
|
+
| "alert"
|
|
107
|
+
| "critical"
|
|
108
|
+
| "error"
|
|
109
|
+
| "warning"
|
|
110
|
+
| "notice"
|
|
111
|
+
| "info"
|
|
112
|
+
| "debug";
|
|
113
|
+
timestamp: Date;
|
|
114
|
+
message: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @public
|
|
119
|
+
* Top level Norsk configuration
|
|
120
|
+
*/
|
|
121
|
+
export interface NorskSettings {
|
|
122
|
+
/**
|
|
123
|
+
* Callback URL to listen on for gRPC session with Norsk Media
|
|
124
|
+
* Defaults to $NORSK_HOST:$NORSK_PORT if the environment variables are set
|
|
125
|
+
* where NORSK_HOST defaults to "127.0.0.1" and NORSK_PORT to "6790"
|
|
126
|
+
* (so "127.0.0.1:6790" if neither variable is set)
|
|
127
|
+
*/
|
|
128
|
+
url?: string;
|
|
129
|
+
onAttemptingToConnect?: () => void;
|
|
130
|
+
onConnecting?: () => void;
|
|
131
|
+
onReady?: () => void;
|
|
132
|
+
onFailedToConnect?: () => void;
|
|
133
|
+
/** Code to execute if the Norsk node is shutdown - by default if logs and exits the client application */
|
|
134
|
+
onShutdown?: () => void;
|
|
135
|
+
onCurrentLoad?: (load: CurrentLoad) => void;
|
|
136
|
+
onHello?: (version: Version) => void;
|
|
137
|
+
onLogEvent?: (log: Log) => void;
|
|
138
|
+
/**
|
|
139
|
+
* Manually handle license events, such as missing/invalid licenses and
|
|
140
|
+
* sandbox timeout. (Logs messages to console by default.)
|
|
141
|
+
*/
|
|
142
|
+
onLicenseEvent?: (message: string) => void;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @public
|
|
147
|
+
* The entrypoint for all Norsk Media applications
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const norsk = new Norsk();
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export class Norsk {
|
|
155
|
+
/** @internal */
|
|
156
|
+
client: MediaClient;
|
|
157
|
+
/** @internal */
|
|
158
|
+
nodes: MediaNodeState[];
|
|
159
|
+
/** @internal */
|
|
160
|
+
settings: NorskSettings;
|
|
161
|
+
/** @internal */
|
|
162
|
+
connectivityState: number;
|
|
163
|
+
/** @internal */
|
|
164
|
+
statusStream?: grpc.ClientReadableStream<NorskStatusEvent>;
|
|
165
|
+
/** @internal */
|
|
166
|
+
publicWebPort?: number;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Implements the {@link NorskInput} interface
|
|
170
|
+
*/
|
|
171
|
+
public input: NorskInput;
|
|
172
|
+
/**
|
|
173
|
+
* Implements the {@link NorskOutput} interface
|
|
174
|
+
*/
|
|
175
|
+
public output: NorskOutput;
|
|
176
|
+
/**
|
|
177
|
+
* Implements the {@link NorskDuplex} interface
|
|
178
|
+
*/
|
|
179
|
+
public duplex: NorskDuplex;
|
|
180
|
+
/**
|
|
181
|
+
* Implements the {@link NorskProcessor} interface
|
|
182
|
+
*/
|
|
183
|
+
public processor: NorskProcessor;
|
|
184
|
+
/**
|
|
185
|
+
* Implements the {@link NorskSystem} interface
|
|
186
|
+
*/
|
|
187
|
+
public system: NorskSystem;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Norsk Runtime version informatio
|
|
191
|
+
*/
|
|
192
|
+
public version: Version;
|
|
193
|
+
|
|
194
|
+
/* @internal */
|
|
195
|
+
public resolveVersion: () => void;
|
|
196
|
+
|
|
197
|
+
/* @internal */
|
|
198
|
+
public initialised: Promise<void>;
|
|
199
|
+
|
|
200
|
+
/** @internal */
|
|
201
|
+
async registerNode<N extends MediaNodeState>(node: N): Promise<N> {
|
|
202
|
+
this.nodes.push(node);
|
|
203
|
+
return node;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public close() {
|
|
207
|
+
this.processor.close();
|
|
208
|
+
for (var n of this.nodes) {
|
|
209
|
+
n.close();
|
|
210
|
+
}
|
|
211
|
+
this.statusStream?.cancel();
|
|
212
|
+
try {
|
|
213
|
+
this.client.close();
|
|
214
|
+
} catch { }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @internal */
|
|
218
|
+
handleStatusEvent(data: NorskStatusEvent) {
|
|
219
|
+
const messageCase = data.message.case;
|
|
220
|
+
switch (messageCase) {
|
|
221
|
+
case undefined:
|
|
222
|
+
break;
|
|
223
|
+
case "hello": {
|
|
224
|
+
debuglog(
|
|
225
|
+
"Norsk status channel connected: %s",
|
|
226
|
+
data.message.value.version
|
|
227
|
+
);
|
|
228
|
+
this.publicWebPort = data.message.value.publicWebPort;
|
|
229
|
+
if (data.message.value.version === undefined) {
|
|
230
|
+
throw new Error("Norsk version is undefined");
|
|
231
|
+
} else {
|
|
232
|
+
this.version = data.message.value.version;
|
|
233
|
+
}
|
|
234
|
+
this.settings.onHello &&
|
|
235
|
+
data.message.value.version &&
|
|
236
|
+
this.settings.onHello(data.message.value.version);
|
|
237
|
+
this.resolveVersion();
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
case "currentLoad": {
|
|
241
|
+
this.settings.onCurrentLoad &&
|
|
242
|
+
this.settings.onCurrentLoad(data.message.value);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case "licenseEvent": {
|
|
246
|
+
if (this.settings.onLicenseEvent) {
|
|
247
|
+
this.settings.onLicenseEvent(data.message.value);
|
|
248
|
+
} else {
|
|
249
|
+
console.log(data.message.value);
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "logEvent": {
|
|
254
|
+
var level:
|
|
255
|
+
| "emergency"
|
|
256
|
+
| "alert"
|
|
257
|
+
| "critical"
|
|
258
|
+
| "error"
|
|
259
|
+
| "warning"
|
|
260
|
+
| "notice"
|
|
261
|
+
| "info"
|
|
262
|
+
| "debug";
|
|
263
|
+
|
|
264
|
+
switch (data.message.value.level) {
|
|
265
|
+
case Log_Level.EMERGENCY:
|
|
266
|
+
level = "emergency";
|
|
267
|
+
break;
|
|
268
|
+
case Log_Level.ALERT:
|
|
269
|
+
level = "alert";
|
|
270
|
+
break;
|
|
271
|
+
case Log_Level.CRITICAL:
|
|
272
|
+
level = "critical";
|
|
273
|
+
break;
|
|
274
|
+
case Log_Level.ERROR:
|
|
275
|
+
level = "error";
|
|
276
|
+
break;
|
|
277
|
+
case Log_Level.WARNING:
|
|
278
|
+
level = "warning";
|
|
279
|
+
break;
|
|
280
|
+
case Log_Level.NOTICE:
|
|
281
|
+
level = "notice";
|
|
282
|
+
break;
|
|
283
|
+
case Log_Level.INFO:
|
|
284
|
+
level = "info";
|
|
285
|
+
break;
|
|
286
|
+
case Log_Level.DEBUG:
|
|
287
|
+
level = "debug";
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
var log: Log = {
|
|
291
|
+
level: level,
|
|
292
|
+
message: data.message.value.msg,
|
|
293
|
+
timestamp: new Date(Number(data.message.value.timestamp) / 1000),
|
|
294
|
+
};
|
|
295
|
+
if (this.settings.onLogEvent) {
|
|
296
|
+
this.settings.onLogEvent(log);
|
|
297
|
+
} else {
|
|
298
|
+
debuglog("Norsk log event: %o", log);
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
default:
|
|
303
|
+
const exhaustiveCheck: never = messageCase;
|
|
304
|
+
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** @internal */
|
|
309
|
+
connectivityStateWatcher() {
|
|
310
|
+
var channel = this.client.getChannel();
|
|
311
|
+
var connectivityState = channel.getConnectivityState(true);
|
|
312
|
+
|
|
313
|
+
switch (connectivityState) {
|
|
314
|
+
case 0: {
|
|
315
|
+
if (this.connectivityState == 1 || this.connectivityState == 2) {
|
|
316
|
+
this.settings.onShutdown && this.settings.onShutdown();
|
|
317
|
+
}
|
|
318
|
+
// Idle
|
|
319
|
+
this.settings.onAttemptingToConnect &&
|
|
320
|
+
this.settings.onAttemptingToConnect();
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case 1: {
|
|
324
|
+
// Connecting
|
|
325
|
+
this.settings.onConnecting && this.settings.onConnecting();
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case 2: {
|
|
329
|
+
// Ready
|
|
330
|
+
this.settings.onReady && this.settings.onReady();
|
|
331
|
+
this.statusStream = this.client.createStatusChannel(new Empty());
|
|
332
|
+
this.statusStream.on("data", this.handleStatusEvent.bind(this));
|
|
333
|
+
this.statusStream.on("error", () => {
|
|
334
|
+
return;
|
|
335
|
+
});
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case 3: {
|
|
339
|
+
// Transient failure
|
|
340
|
+
this.settings.onFailedToConnect && this.settings.onFailedToConnect();
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case 4: {
|
|
344
|
+
// Shutdown
|
|
345
|
+
this.settings.onShutdown && this.settings.onShutdown();
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
debuglog("Channel connectivity state change: %d", connectivityState);
|
|
350
|
+
|
|
351
|
+
this.connectivityState = connectivityState;
|
|
352
|
+
channel.watchConnectivityState(connectivityState, Infinity, () => {
|
|
353
|
+
this.connectivityStateWatcher();
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** @public */
|
|
358
|
+
public static async connect(
|
|
359
|
+
settings?: NorskSettings
|
|
360
|
+
) {
|
|
361
|
+
settings = settings ?? {};
|
|
362
|
+
if (!settings.onShutdown) {
|
|
363
|
+
settings.onShutdown = () => {
|
|
364
|
+
console.log("Norsk has shutdown");
|
|
365
|
+
process.exit(1)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
let norsk = new Norsk(settings);
|
|
369
|
+
await norsk.initialised;
|
|
370
|
+
return norsk;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** @internal */
|
|
374
|
+
constructor(norskSettings: NorskSettings) {
|
|
375
|
+
this.connectivityState = 0;
|
|
376
|
+
this.client = new MediaClient(
|
|
377
|
+
norskSettings.url ? norskSettings.url : norskHost() + ":" + norskPort(),
|
|
378
|
+
grpc.credentials.createInsecure()
|
|
379
|
+
);
|
|
380
|
+
this.settings = norskSettings;
|
|
381
|
+
this.connectivityStateWatcher();
|
|
382
|
+
this.initialised = new Promise((resolve, reject) => {
|
|
383
|
+
this.resolveVersion = resolve;
|
|
384
|
+
});
|
|
385
|
+
this.nodes = [];
|
|
386
|
+
this.input = {
|
|
387
|
+
rtmpServer: async (settings: RtmpServerInputSettings) =>
|
|
388
|
+
RtmpServerInputNode.create(settings, this.client),
|
|
389
|
+
localTsFile: async (settings: LocalFileInputSettings) =>
|
|
390
|
+
TsFileInputNode.create(settings, this.client),
|
|
391
|
+
srt: async (settings: SrtInputSettings) =>
|
|
392
|
+
SrtInputNode.create(settings, this.client),
|
|
393
|
+
whip: async (settings: WhipInputSettings) =>
|
|
394
|
+
WhipInputNode.create(settings, this.client),
|
|
395
|
+
m3u8Media: async (settings: M3u8MediaInputSettings) =>
|
|
396
|
+
M3u8InputNode.create(settings, this.client),
|
|
397
|
+
udpTs: async (settings: UdpTsInputSettings) =>
|
|
398
|
+
UdpTsInputNode.create(settings, this.client),
|
|
399
|
+
localWebVttFile: async (settings: LocalFileInputSettings) =>
|
|
400
|
+
WebVttFileInputNode.create(settings, this.client),
|
|
401
|
+
imageFile: async (settings: ImageFileInputSettings) =>
|
|
402
|
+
ImageFileInputNode.create(settings, this.client),
|
|
403
|
+
localMp4File: async (settings: Mp4FileInputSettings) =>
|
|
404
|
+
Mp4FileInputNode.create(settings, this.client),
|
|
405
|
+
rtp: async (settings: RtpInputSettings) =>
|
|
406
|
+
RtpInputNode.create(settings, this.client),
|
|
407
|
+
audioSignal: async (settings: AudioSignalGeneratorSettings) =>
|
|
408
|
+
AudioSignalGeneratorNode.create(settings, this.client),
|
|
409
|
+
browser: async (settings: BrowserInputSettings) =>
|
|
410
|
+
BrowserInputNode.create(settings, this.client),
|
|
411
|
+
deckLink: async (settings: DeckLinkInputSettings) =>
|
|
412
|
+
DeckLinkInputNode.create(settings, this.client),
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
this.output = {
|
|
416
|
+
hlsVideo: async (settings: HlsVideoOutputSettings) =>
|
|
417
|
+
HlsVideoOutputNode.create(settings, this.client),
|
|
418
|
+
hlsAudio: async (
|
|
419
|
+
settings: HlsAudioOutputSettings
|
|
420
|
+
): Promise<HlsAudioOutputNode> =>
|
|
421
|
+
HlsAudioOutputNode.create(settings, this.client),
|
|
422
|
+
hlsWebVtt: async (settings: HlsWebVttOutputSettings) =>
|
|
423
|
+
HlsWebVttOutputNode.create(settings, this.client),
|
|
424
|
+
hlsWebVttPush: async (settings: HlsWebVttPushOutputSettings) =>
|
|
425
|
+
HlsWebVttPushOutputNode.create(settings, this.client),
|
|
426
|
+
hlsTsVideo: async (settings: HlsTsVideoOutputSettings) =>
|
|
427
|
+
HlsTsVideoOutputNode.create(settings, this.client),
|
|
428
|
+
tsUdp: async (settings: TsUdpOutputSettings) =>
|
|
429
|
+
TsUdpOutputNode.create(settings, this.client),
|
|
430
|
+
srt: async (settings: SrtOutputSettings) =>
|
|
431
|
+
SrtOutputNode.create(settings, this.client),
|
|
432
|
+
hlsTsAudio: async (settings: HlsTsAudioOutputSettings) =>
|
|
433
|
+
HlsTsAudioOutputNode.create(settings, this.client),
|
|
434
|
+
hlsTsVideoPush: async (settings: HlsTsVideoPushOutputSettings) =>
|
|
435
|
+
HlsTsVideoPushOutputNode.create(settings, this.client),
|
|
436
|
+
hlsTsAudioPush: async (settings: HlsTsAudioPushOutputSettings) =>
|
|
437
|
+
HlsTsAudioPushOutputNode.create(settings, this.client),
|
|
438
|
+
hlsTsCombinedPush: async (settings: HlsTsCombinedPushOutputSettings) =>
|
|
439
|
+
HlsTsCombinedPushOutputNode.create(settings, this.client),
|
|
440
|
+
hlsMaster: async (settings: HlsMasterOutputSettings) =>
|
|
441
|
+
HlsMasterOutputNode.create(
|
|
442
|
+
settings,
|
|
443
|
+
this.client
|
|
444
|
+
),
|
|
445
|
+
hlsMasterPush: async (settings: HlsMasterPushOutputSettings) =>
|
|
446
|
+
HlsMasterPushOutputNode.create(settings, this.client),
|
|
447
|
+
webRTCWhip: async (settings: WebRTCWhipOutputSettings) =>
|
|
448
|
+
WebRTCWhipOutputNode.create(settings, this.client),
|
|
449
|
+
|
|
450
|
+
rtmp: async (settings: RtmpOutputSettings) =>
|
|
451
|
+
RtmpOutputNode.create(settings, this.client),
|
|
452
|
+
localTsFile: async (settings: TsFileOutputSettings) =>
|
|
453
|
+
TsFileOutputNode.create(settings, this.client),
|
|
454
|
+
localMp4File: async (settings: Mp4FileOutputSettings) =>
|
|
455
|
+
Mp4FileOutputNode.create(settings, this.client),
|
|
456
|
+
};
|
|
457
|
+
this.processor = new NorskProcessor(this.client);
|
|
458
|
+
this.system = {
|
|
459
|
+
hardwareInfo: async () => hardwareInfo(this.client),
|
|
460
|
+
};
|
|
461
|
+
this.duplex = {
|
|
462
|
+
localWebRTC: async (settings: LocalWebRTCSettings) =>
|
|
463
|
+
LocalWebRTCNode.create(
|
|
464
|
+
settings,
|
|
465
|
+
this.client
|
|
466
|
+
),
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Rob's notes: Help, I need somebody to do this properly without 'any' and 'ignore'
|
|
470
|
+
for (var i in this.output) {
|
|
471
|
+
let k = i as keyof NorskOutput;
|
|
472
|
+
let original = this.output[k];
|
|
473
|
+
let fn = async (s: any) => {
|
|
474
|
+
let n = await original(s);
|
|
475
|
+
return this.registerNode(n);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
//@ts-ignore
|
|
479
|
+
this.output[k] = fn;
|
|
480
|
+
}
|
|
481
|
+
for (var i in this.input) {
|
|
482
|
+
let k = i as keyof NorskInput;
|
|
483
|
+
let original = this.input[k];
|
|
484
|
+
let fn = async (s: any) => {
|
|
485
|
+
let n = await original(s);
|
|
486
|
+
return this.registerNode(n);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
//@ts-ignore
|
|
490
|
+
this.input[k] = fn;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* @public
|
|
497
|
+
* Filters a context to only the audio streams within it
|
|
498
|
+
* @param streams - The media context from which to return the streams
|
|
499
|
+
* @returns The audio streams in the media context
|
|
500
|
+
*/
|
|
501
|
+
export function audioStreams(
|
|
502
|
+
streams: readonly StreamMetadata[]
|
|
503
|
+
): StreamMetadata[] {
|
|
504
|
+
return streams.filter((stream) => stream.message.case === "audio");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @public
|
|
509
|
+
* Filters a context to only the video streams within it
|
|
510
|
+
* @param streams - The media context from which to return the streams
|
|
511
|
+
* @returns The video streams in the media context
|
|
512
|
+
*/
|
|
513
|
+
export function videoStreams(
|
|
514
|
+
streams: readonly StreamMetadata[]
|
|
515
|
+
): StreamMetadata[] {
|
|
516
|
+
return streams.filter((stream) => stream.message.case === "video");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* @public
|
|
521
|
+
* Filters a context to only the subtitle streams within it
|
|
522
|
+
* @param streams - The media context from which to return the streams
|
|
523
|
+
* @returns The subtitle streams in the media context
|
|
524
|
+
*/
|
|
525
|
+
export function subtitleStreams(
|
|
526
|
+
streams: readonly StreamMetadata[]
|
|
527
|
+
): StreamMetadata[] {
|
|
528
|
+
return streams.filter((stream) => stream.message.case === "subtitle");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* @public
|
|
533
|
+
* Returns the stream keys for audio streams in a media context
|
|
534
|
+
* @param streams - The media context from which to return the stream keys
|
|
535
|
+
* @returns The audio stream keys in the media context
|
|
536
|
+
*/
|
|
537
|
+
export function audioStreamKeys(
|
|
538
|
+
streams: readonly StreamMetadata[]
|
|
539
|
+
): StreamKey[] {
|
|
540
|
+
return removeUndefined(
|
|
541
|
+
audioStreams(streams).map((stream) => stream.streamKey)
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* @public
|
|
547
|
+
* Returns the stream keys for video streams in a media context
|
|
548
|
+
* @param streams - The media context from which to return the stream keys
|
|
549
|
+
* @returns The video stream keys in the media context
|
|
550
|
+
*/
|
|
551
|
+
export function videoStreamKeys(
|
|
552
|
+
streams: readonly StreamMetadata[]
|
|
553
|
+
): StreamKey[] {
|
|
554
|
+
return removeUndefined(
|
|
555
|
+
videoStreams(streams).map((stream) => stream.streamKey)
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* @public
|
|
561
|
+
* Returns the stream keys for subtitle streams in a media context
|
|
562
|
+
* @param streams - The media context from which to return the stream keys
|
|
563
|
+
* @returns The subtitle stream keys in the media context
|
|
564
|
+
*/
|
|
565
|
+
export function subtitleStreamKeys(
|
|
566
|
+
streams: readonly StreamMetadata[]
|
|
567
|
+
): StreamKey[] {
|
|
568
|
+
return removeUndefined(
|
|
569
|
+
subtitleStreams(streams).map((stream) => stream.streamKey)
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function removeUndefined<T>(values: readonly (T | undefined)[]): T[] {
|
|
574
|
+
// This isn't really typechecked as you would like :(
|
|
575
|
+
return values.filter((v): v is T => v !== undefined);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** @public */
|
|
579
|
+
export function newSilentMatrix(rows: number, cols: number): Gain[][] {
|
|
580
|
+
return new Array(rows).fill(0).map(() => new Array(cols).fill(null));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** @public */
|
|
584
|
+
export function mkSine(freq: number) {
|
|
585
|
+
return new Wave({ message: { case: "sine", value: new SineWave({ freq }) } });
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* @public
|
|
590
|
+
* Select all the audio and video streams from the input
|
|
591
|
+
* @param streams - The streams from the inbound Context
|
|
592
|
+
* @returns Array of selected StreamKeys
|
|
593
|
+
*/
|
|
594
|
+
export function selectAV(streams: readonly StreamMetadata[]): StreamKey[] {
|
|
595
|
+
const audio = audioStreamKeys(streams);
|
|
596
|
+
const video = videoStreamKeys(streams);
|
|
597
|
+
return audio.concat(video);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** @public */
|
|
601
|
+
export function selectSubtitles(
|
|
602
|
+
streams: readonly StreamMetadata[]
|
|
603
|
+
): StreamKey[] {
|
|
604
|
+
return subtitleStreamKeys(streams);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** @public */
|
|
608
|
+
export function selectAudio(streams: readonly StreamMetadata[]): StreamKey[] {
|
|
609
|
+
return audioStreamKeys(streams);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** @public */
|
|
613
|
+
export function selectVideo(streams: readonly StreamMetadata[]): StreamKey[] {
|
|
614
|
+
return videoStreamKeys(streams);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** @public */
|
|
618
|
+
export function selectAllVideos(
|
|
619
|
+
count: number
|
|
620
|
+
): (streams: readonly StreamMetadata[]) => StreamKey[] {
|
|
621
|
+
return (streams: readonly StreamMetadata[]) => {
|
|
622
|
+
const video = videoStreamKeys(streams);
|
|
623
|
+
if (video.length == count) {
|
|
624
|
+
return video;
|
|
625
|
+
}
|
|
626
|
+
return [];
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** @public */
|
|
631
|
+
export function selectAllAudios(
|
|
632
|
+
count: number
|
|
633
|
+
): (streams: readonly StreamMetadata[]) => StreamKey[] {
|
|
634
|
+
return (streams: readonly StreamMetadata[]) => {
|
|
635
|
+
const audio = audioStreamKeys(streams);
|
|
636
|
+
if (audio.length == count) {
|
|
637
|
+
return audio;
|
|
638
|
+
}
|
|
639
|
+
return [];
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* @public
|
|
645
|
+
* Generate encryption parameters from from an encryption KeyID and Key,
|
|
646
|
+
* in the form KEYID:KEY, both 16byte hexadecimal
|
|
647
|
+
*/
|
|
648
|
+
export function mkEncryption(
|
|
649
|
+
encryption: string | undefined
|
|
650
|
+
): { encryptionKey: string; encryptionKeyId: string } | undefined {
|
|
651
|
+
let encryption_params = encryption
|
|
652
|
+
? encryption.split(":").map((s) => s.trim())
|
|
653
|
+
: undefined;
|
|
654
|
+
if (encryption_params && encryption_params.length !== 2) {
|
|
655
|
+
console.log(
|
|
656
|
+
"Warning: bad encryption format, must have two fields (hexadecimal key id and hexadecimal key)"
|
|
657
|
+
);
|
|
658
|
+
encryption_params = undefined;
|
|
659
|
+
}
|
|
660
|
+
return encryption_params
|
|
661
|
+
? {
|
|
662
|
+
encryptionKeyId: encryption_params[0],
|
|
663
|
+
encryptionKey: encryption_params[1],
|
|
664
|
+
}
|
|
665
|
+
: undefined;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** @public */
|
|
669
|
+
export const videoToPin = <Pins extends string>(pin: Pins) => {
|
|
670
|
+
return (streams: StreamMetadata[]): PinToKey<Pins> => {
|
|
671
|
+
const video = videoStreamKeys(streams);
|
|
672
|
+
if (video.length == 1) {
|
|
673
|
+
// I want to do this, but it loses the types
|
|
674
|
+
// return { [pin]: video };
|
|
675
|
+
let o: PinToKey<Pins> = {};
|
|
676
|
+
o[pin] = video;
|
|
677
|
+
return o;
|
|
678
|
+
}
|
|
679
|
+
return undefined;
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
/** @public */
|
|
684
|
+
export const audioToPin = <Pins extends string>(pin: Pins) => {
|
|
685
|
+
return (streams: StreamMetadata[]): PinToKey<Pins> => {
|
|
686
|
+
const audio = audioStreamKeys(streams);
|
|
687
|
+
if (audio.length >= 1) {
|
|
688
|
+
// I want to do this, but it loses the types
|
|
689
|
+
// return { [pin]: video };
|
|
690
|
+
let o: PinToKey<Pins> = {};
|
|
691
|
+
o[pin] = audio;
|
|
692
|
+
return o;
|
|
693
|
+
}
|
|
694
|
+
return undefined;
|
|
695
|
+
};
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
/** @public */
|
|
699
|
+
export const avToPin = <Pins extends string>(pin: Pins) => {
|
|
700
|
+
return (streams: StreamMetadata[]): PinToKey<Pins> => {
|
|
701
|
+
const audio = audioStreamKeys(streams);
|
|
702
|
+
const video = videoStreamKeys(streams);
|
|
703
|
+
const keys = audio.concat(video);
|
|
704
|
+
if (keys.length > 1) {
|
|
705
|
+
// I want to do this, but it loses the types
|
|
706
|
+
// return { [pin]: video };
|
|
707
|
+
let o: PinToKey<Pins> = {};
|
|
708
|
+
o[pin] = keys;
|
|
709
|
+
return o;
|
|
710
|
+
}
|
|
711
|
+
return undefined;
|
|
712
|
+
};
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
/** @public */
|
|
716
|
+
export const subtitlesToPin = <Pins extends string>(pin: Pins) => {
|
|
717
|
+
return (streams: StreamMetadata[]): PinToKey<Pins> => {
|
|
718
|
+
const subs = subtitleStreamKeys(streams);
|
|
719
|
+
if (subs.length == 1) {
|
|
720
|
+
let o: PinToKey<Pins> = {};
|
|
721
|
+
o[pin] = subs;
|
|
722
|
+
return o;
|
|
723
|
+
}
|
|
724
|
+
return undefined;
|
|
725
|
+
};
|
|
726
|
+
};
|