@norskvideo/norsk-studio-built-ins 1.18.0 → 1.26.0-2025-02-19-33cde15d

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 (247) hide show
  1. package/client/info.js +5979 -695
  2. package/client/output.preview/styles.css +69 -69
  3. package/client/output.whep/styles.css +9 -0
  4. package/client/style.css +283 -856
  5. package/lib/info.js +14 -14
  6. package/lib/info.js.map +1 -1
  7. package/lib/input.rtmp/info.js +19 -5
  8. package/lib/input.rtmp/info.js.map +1 -1
  9. package/lib/input.rtmp/inline-view.js +3 -3
  10. package/lib/input.rtmp/runtime.d.ts +46 -7
  11. package/lib/input.rtmp/runtime.js +245 -59
  12. package/lib/input.rtmp/runtime.js.map +1 -1
  13. package/lib/input.rtmp/summary-view.d.ts +3 -5
  14. package/lib/input.rtmp/summary-view.js +51 -6
  15. package/lib/input.rtmp/summary-view.js.map +1 -1
  16. package/lib/input.rtmp/types.d.ts +193 -0
  17. package/lib/{processor.browserOverlay/openApi.js → input.rtmp/types.js} +1 -1
  18. package/lib/input.rtmp/types.js.map +1 -0
  19. package/lib/input.rtmp/types.yaml +147 -0
  20. package/lib/input.silence/info.js +9 -2
  21. package/lib/input.silence/info.js.map +1 -1
  22. package/lib/input.silence/runtime.d.ts +1 -0
  23. package/lib/input.silence/runtime.js.map +1 -1
  24. package/lib/input.srt-caller/info.js +8 -1
  25. package/lib/input.srt-caller/info.js.map +1 -1
  26. package/lib/input.srt-caller/runtime.d.ts +1 -0
  27. package/lib/input.srt-caller/runtime.js.map +1 -1
  28. package/lib/input.srt-listener/info.js +15 -2
  29. package/lib/input.srt-listener/info.js.map +1 -1
  30. package/lib/input.srt-listener/runtime.d.ts +41 -5
  31. package/lib/input.srt-listener/runtime.js +236 -57
  32. package/lib/input.srt-listener/runtime.js.map +1 -1
  33. package/lib/input.srt-listener/summary-view.d.ts +3 -5
  34. package/lib/input.srt-listener/summary-view.js +45 -6
  35. package/lib/input.srt-listener/summary-view.js.map +1 -1
  36. package/lib/input.srt-listener/types.d.ts +193 -0
  37. package/lib/input.srt-listener/types.js +3 -0
  38. package/lib/input.srt-listener/types.js.map +1 -0
  39. package/lib/input.srt-listener/types.yaml +145 -0
  40. package/lib/input.udp-ts/info.js +2 -1
  41. package/lib/input.udp-ts/info.js.map +1 -1
  42. package/lib/input.udp-ts/runtime.d.ts +1 -0
  43. package/lib/input.udp-ts/runtime.js.map +1 -1
  44. package/lib/input.videoTestCard/info.d.ts +1 -1
  45. package/lib/input.videoTestCard/info.js +17 -4
  46. package/lib/input.videoTestCard/info.js.map +1 -1
  47. package/lib/input.videoTestCard/runtime.d.ts +1 -0
  48. package/lib/output.autoCmaf/form-views.d.ts +2 -2
  49. package/lib/output.autoCmaf/form-views.js +13 -3
  50. package/lib/output.autoCmaf/form-views.js.map +1 -1
  51. package/lib/output.autoCmaf/info.d.ts +21 -2
  52. package/lib/output.autoCmaf/info.js +106 -32
  53. package/lib/output.autoCmaf/info.js.map +1 -1
  54. package/lib/output.autoCmaf/inline-view.d.ts +6 -0
  55. package/lib/output.autoCmaf/inline-view.js +8 -0
  56. package/lib/output.autoCmaf/inline-view.js.map +1 -0
  57. package/lib/output.autoCmaf/runtime.d.ts +31 -46
  58. package/lib/output.autoCmaf/runtime.js +334 -108
  59. package/lib/output.autoCmaf/runtime.js.map +1 -1
  60. package/lib/output.autoCmaf/summary-view.d.ts +4 -0
  61. package/lib/output.autoCmaf/summary-view.js +24 -0
  62. package/lib/output.autoCmaf/summary-view.js.map +1 -0
  63. package/lib/output.autoCmaf/summary.js +3 -0
  64. package/lib/output.autoCmaf/summary.js.map +1 -1
  65. package/lib/output.autoCmaf/types.d.ts +158 -0
  66. package/lib/output.autoCmaf/types.js +3 -0
  67. package/lib/output.autoCmaf/types.js.map +1 -0
  68. package/lib/output.autoCmaf/types.yaml +182 -0
  69. package/lib/output.autoHls/info.d.ts +3 -0
  70. package/lib/output.autoHls/info.js +22 -0
  71. package/lib/output.autoHls/info.js.map +1 -0
  72. package/lib/output.autoHls/runtime.d.ts +6 -0
  73. package/lib/output.autoHls/runtime.js +13 -0
  74. package/lib/output.autoHls/runtime.js.map +1 -0
  75. package/lib/output.preview/info.d.ts +5 -2
  76. package/lib/output.preview/info.js +23 -3
  77. package/lib/output.preview/info.js.map +1 -1
  78. package/lib/output.preview/inline-view.d.ts +2 -4
  79. package/lib/output.preview/inline-view.js +65 -7
  80. package/lib/output.preview/inline-view.js.map +1 -1
  81. package/lib/output.preview/runtime.d.ts +18 -12
  82. package/lib/output.preview/runtime.js +122 -33
  83. package/lib/output.preview/runtime.js.map +1 -1
  84. package/lib/output.preview/types.d.ts +56 -0
  85. package/lib/output.preview/types.js +3 -0
  86. package/lib/output.preview/types.js.map +1 -0
  87. package/lib/output.preview/types.yaml +37 -0
  88. package/lib/output.rtmp/info.js +14 -2
  89. package/lib/output.rtmp/info.js.map +1 -1
  90. package/lib/output.rtmp/inline-view.js +5 -2
  91. package/lib/output.rtmp/inline-view.js.map +1 -1
  92. package/lib/output.rtmp/runtime.d.ts +31 -5
  93. package/lib/output.rtmp/runtime.js +169 -34
  94. package/lib/output.rtmp/runtime.js.map +1 -1
  95. package/lib/output.rtmp/summary-view.d.ts +4 -0
  96. package/lib/output.rtmp/summary-view.js +24 -0
  97. package/lib/output.rtmp/summary-view.js.map +1 -0
  98. package/lib/output.rtmp/types.d.ts +101 -0
  99. package/lib/output.rtmp/types.js +3 -0
  100. package/lib/output.rtmp/types.js.map +1 -0
  101. package/lib/output.rtmp/types.yaml +30 -0
  102. package/lib/output.srt/info.d.ts +2 -2
  103. package/lib/output.srt/info.js +29 -1
  104. package/lib/output.srt/info.js.map +1 -1
  105. package/lib/output.srt/inline-view.d.ts +6 -0
  106. package/lib/output.srt/inline-view.js +8 -0
  107. package/lib/output.srt/inline-view.js.map +1 -0
  108. package/lib/output.srt/runtime.d.ts +35 -5
  109. package/lib/output.srt/runtime.js +174 -6
  110. package/lib/output.srt/runtime.js.map +1 -1
  111. package/lib/output.srt/summary-view.d.ts +4 -0
  112. package/lib/output.srt/summary-view.js +24 -0
  113. package/lib/output.srt/summary-view.js.map +1 -0
  114. package/lib/output.srt/types.d.ts +101 -0
  115. package/lib/output.srt/types.js +3 -0
  116. package/lib/output.srt/types.js.map +1 -0
  117. package/lib/output.srt/types.yaml +31 -0
  118. package/lib/output.statistics/info.js +3 -1
  119. package/lib/output.statistics/info.js.map +1 -1
  120. package/lib/output.statistics/inline-view.js +3 -2
  121. package/lib/output.statistics/inline-view.js.map +1 -1
  122. package/lib/output.statistics/runtime.d.ts +1 -0
  123. package/lib/output.statistics/runtime.js.map +1 -1
  124. package/lib/output.udpTs/info.js +4 -0
  125. package/lib/output.udpTs/info.js.map +1 -1
  126. package/lib/output.udpTs/runtime.d.ts +1 -0
  127. package/lib/output.udpTs/runtime.js.map +1 -1
  128. package/lib/output.whep/info.d.ts +2 -2
  129. package/lib/output.whep/info.js +61 -1
  130. package/lib/output.whep/info.js.map +1 -1
  131. package/lib/output.whep/inline-view.d.ts +4 -0
  132. package/lib/output.whep/inline-view.js +57 -0
  133. package/lib/output.whep/inline-view.js.map +1 -0
  134. package/lib/output.whep/runtime.d.ts +39 -5
  135. package/lib/output.whep/runtime.js +175 -13
  136. package/lib/output.whep/runtime.js.map +1 -1
  137. package/lib/output.whep/summary-view.d.ts +4 -0
  138. package/lib/output.whep/summary-view.js +24 -0
  139. package/lib/output.whep/summary-view.js.map +1 -0
  140. package/lib/output.whep/types.d.ts +101 -0
  141. package/lib/output.whep/types.js +3 -0
  142. package/lib/output.whep/types.js.map +1 -0
  143. package/lib/output.whep/types.yaml +30 -0
  144. package/lib/processor.audioEncoder/form-views.d.ts +8 -0
  145. package/lib/processor.audioEncoder/form-views.js +8 -0
  146. package/lib/processor.audioEncoder/form-views.js.map +1 -0
  147. package/lib/processor.audioEncoder/info.d.ts +3 -0
  148. package/lib/processor.audioEncoder/info.js +160 -0
  149. package/lib/processor.audioEncoder/info.js.map +1 -0
  150. package/lib/processor.audioEncoder/runtime.d.ts +21 -0
  151. package/lib/processor.audioEncoder/runtime.js +20 -0
  152. package/lib/processor.audioEncoder/runtime.js.map +1 -0
  153. package/lib/processor.browserOverlay/info.js +1 -0
  154. package/lib/processor.browserOverlay/info.js.map +1 -1
  155. package/lib/processor.browserOverlay/runtime.d.ts +1 -0
  156. package/lib/processor.browserOverlay/runtime.js +6 -26
  157. package/lib/processor.browserOverlay/runtime.js.map +1 -1
  158. package/lib/processor.browserOverlay/types.js +3 -0
  159. package/lib/processor.browserOverlay/types.js.map +1 -0
  160. package/lib/processor.cascadingSwitch/info.js +3 -2
  161. package/lib/processor.cascadingSwitch/info.js.map +1 -1
  162. package/lib/processor.cascadingSwitch/inline-view.js +3 -3
  163. package/lib/processor.cascadingSwitch/runtime.d.ts +1 -0
  164. package/lib/processor.cascadingSwitch/runtime.js +2 -2
  165. package/lib/processor.cascadingSwitch/runtime.js.map +1 -1
  166. package/lib/processor.fixedLadder/codec-editor.js +1 -1
  167. package/lib/processor.fixedLadder/codec-editor.js.map +1 -1
  168. package/lib/processor.fixedLadder/codec-view.d.ts +1 -1
  169. package/lib/processor.fixedLadder/codec-view.js +1 -1
  170. package/lib/processor.fixedLadder/codec-view.js.map +1 -1
  171. package/lib/processor.fixedLadder/info.d.ts +33 -7
  172. package/lib/processor.fixedLadder/info.js +19 -6
  173. package/lib/processor.fixedLadder/info.js.map +1 -1
  174. package/lib/processor.fixedLadder/runtime.d.ts +16 -29
  175. package/lib/processor.fixedLadder/runtime.js +16 -1
  176. package/lib/processor.fixedLadder/runtime.js.map +1 -1
  177. package/lib/processor.fixedLadder/types.d.ts +240 -0
  178. package/lib/processor.fixedLadder/types.js +3 -0
  179. package/lib/processor.fixedLadder/types.js.map +1 -0
  180. package/lib/processor.fixedLadder/types.yaml +541 -0
  181. package/lib/processor.onscreenGraphic/info.js +11 -5
  182. package/lib/processor.onscreenGraphic/info.js.map +1 -1
  183. package/lib/processor.onscreenGraphic/runtime.d.ts +27 -2
  184. package/lib/processor.onscreenGraphic/runtime.js +116 -52
  185. package/lib/processor.onscreenGraphic/runtime.js.map +1 -1
  186. package/lib/processor.onscreenGraphic/summary-view.d.ts +1 -1
  187. package/lib/processor.onscreenGraphic/summary-view.js +235 -11
  188. package/lib/processor.onscreenGraphic/summary-view.js.map +1 -1
  189. package/lib/processor.onscreenGraphic/types.d.ts +15 -1
  190. package/lib/processor.onscreenGraphic/types.yaml +63 -14
  191. package/lib/processor.streamKeyOverride/info.d.ts +3 -0
  192. package/lib/processor.streamKeyOverride/info.js +151 -0
  193. package/lib/processor.streamKeyOverride/info.js.map +1 -0
  194. package/lib/processor.streamKeyOverride/runtime.d.ts +55 -0
  195. package/lib/processor.streamKeyOverride/runtime.js +226 -0
  196. package/lib/processor.streamKeyOverride/runtime.js.map +1 -0
  197. package/lib/shared/api.d.ts +35 -0
  198. package/lib/shared/api.js +34 -0
  199. package/lib/shared/api.js.map +1 -0
  200. package/lib/test/auto-cmaf.js +219 -43
  201. package/lib/test/auto-cmaf.js.map +1 -1
  202. package/lib/test/onscreen-graphic.js +79 -42
  203. package/lib/test/onscreen-graphic.js.map +1 -1
  204. package/lib/test/preview.js +69 -64
  205. package/lib/test/preview.js.map +1 -1
  206. package/lib/test/rtmp-input.js +6 -3
  207. package/lib/test/rtmp-input.js.map +1 -1
  208. package/lib/test/rtmp-output.js +105 -1
  209. package/lib/test/rtmp-output.js.map +1 -1
  210. package/lib/test/srt-input-listener.js +331 -0
  211. package/lib/test/srt-input-listener.js.map +1 -1
  212. package/lib/test/srt-output.js +93 -4
  213. package/lib/test/srt-output.js.map +1 -1
  214. package/lib/test/whep-output.js +192 -5
  215. package/lib/test/whep-output.js.map +1 -1
  216. package/lib/util.stats.latency/info.js +1 -0
  217. package/lib/util.stats.latency/info.js.map +1 -1
  218. package/lib/util.stats.latency/runtime.d.ts +1 -0
  219. package/lib/util.stats.latency/runtime.js +2 -2
  220. package/lib/util.stats.latency/runtime.js.map +1 -1
  221. package/package.json +4 -4
  222. package/lib/processor.browserOverlay/openApi.js.map +0 -1
  223. package/lib/processor.whisper-transcribe/info.d.ts +0 -3
  224. package/lib/processor.whisper-transcribe/info.js +0 -44
  225. package/lib/processor.whisper-transcribe/info.js.map +0 -1
  226. package/lib/processor.whisper-transcribe/runtime.d.ts +0 -29
  227. package/lib/processor.whisper-transcribe/runtime.js +0 -83
  228. package/lib/processor.whisper-transcribe/runtime.js.map +0 -1
  229. package/lib/util.stats.ma35d/info.d.ts +0 -3
  230. package/lib/util.stats.ma35d/info.js +0 -79
  231. package/lib/util.stats.ma35d/info.js.map +0 -1
  232. package/lib/util.stats.ma35d/inline-view.d.ts +0 -6
  233. package/lib/util.stats.ma35d/inline-view.js +0 -76
  234. package/lib/util.stats.ma35d/inline-view.js.map +0 -1
  235. package/lib/util.stats.ma35d/runtime.d.ts +0 -21
  236. package/lib/util.stats.ma35d/runtime.js +0 -49
  237. package/lib/util.stats.ma35d/runtime.js.map +0 -1
  238. package/lib/util.timestamps/info.d.ts +0 -5
  239. package/lib/util.timestamps/info.js +0 -87
  240. package/lib/util.timestamps/info.js.map +0 -1
  241. package/lib/util.timestamps/inline-view.d.ts +0 -6
  242. package/lib/util.timestamps/inline-view.js +0 -94
  243. package/lib/util.timestamps/inline-view.js.map +0 -1
  244. package/lib/util.timestamps/runtime.d.ts +0 -29
  245. package/lib/util.timestamps/runtime.js +0 -40
  246. package/lib/util.timestamps/runtime.js.map +0 -1
  247. /package/lib/processor.browserOverlay/{openApi.d.ts → types.d.ts} +0 -0
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.AutoCmaf = void 0;
4
7
  const norsk_sdk_1 = require("@norskvideo/norsk-sdk");
@@ -8,16 +11,112 @@ const logging_1 = require("@norskvideo/norsk-studio/lib/server/logging");
8
11
  const ezdrm_1 = require("../shared/drm/ezdrm");
9
12
  const axinom_1 = require("../shared/drm/axinom");
10
13
  const util_2 = require("@norskvideo/norsk-studio/lib/runtime/util");
14
+ const promises_1 = __importDefault(require("fs/promises"));
15
+ const json_refs_1 = require("json-refs");
16
+ const path_1 = __importDefault(require("path"));
17
+ const yaml_1 = __importDefault(require("yaml"));
18
+ function coreInfo(path, op) {
19
+ return {
20
+ url: path,
21
+ summary: op.summary,
22
+ description: op.description,
23
+ requestBody: op.requestBody,
24
+ responses: op.responses,
25
+ };
26
+ }
27
+ function post(path, paths) {
28
+ return {
29
+ ...coreInfo(path, paths[path]['post']),
30
+ method: 'POST',
31
+ };
32
+ }
11
33
  class AutoCmafDefinition {
12
- async create(norsk, cfg, cb, { updates, report }) {
13
- const node = await AutoCmaf.create(norsk, cfg, report);
34
+ async create(norsk, cfg, cb, runtime) {
35
+ return this.createImpl(norsk, { mode: 'cmaf', ...cfg }, cb, runtime);
36
+ }
37
+ async createImpl(norsk, cfg, cb, runtime) {
38
+ const node = await AutoCmaf.create(norsk, cfg, runtime);
14
39
  cb(node);
15
- const mv = node.mv;
16
- if (mv) {
17
- report.registerOutput(cfg.id, mv.url);
18
- updates.raiseEvent({ type: 'url-published', url: mv.url, drmToken: node.crypto?.token });
40
+ }
41
+ async handleCommand(node, command) {
42
+ switch (command.type) {
43
+ case 'enable-output':
44
+ await node.enableOutput();
45
+ break;
46
+ case 'disable-output':
47
+ await node.disableOutput();
48
+ break;
49
+ default:
50
+ (0, util_1.assertUnreachable)(command.type);
19
51
  }
20
52
  }
53
+ async instanceRoutes() {
54
+ const types = await promises_1.default.readFile(path_1.default.join(__dirname, 'types.yaml'));
55
+ const root = yaml_1.default.parse(types.toString());
56
+ const resolved = await (0, json_refs_1.resolveRefs)(root, {}).then((r) => r.resolved);
57
+ const paths = resolved.paths;
58
+ function waitFor(condition, finish) {
59
+ if (condition()) {
60
+ return finish();
61
+ }
62
+ setTimeout(() => {
63
+ waitFor(condition, finish);
64
+ }, 10);
65
+ }
66
+ return [
67
+ {
68
+ ...post('/enable', paths),
69
+ handler: ({ runtime }) => async (_req, res) => {
70
+ try {
71
+ const state = runtime.updates.latest();
72
+ if (state.enabled) {
73
+ return res.status(400).json({ error: 'Output is already enabled' });
74
+ }
75
+ runtime.updates.sendCommand({
76
+ type: 'enable-output'
77
+ });
78
+ waitFor(() => runtime.updates.latest().enabled, () => {
79
+ res.sendStatus(204);
80
+ });
81
+ }
82
+ catch (error) {
83
+ console.error('Error in enable handler:', error);
84
+ res.status(500).json({ error: 'Failed to enable output' });
85
+ }
86
+ }
87
+ },
88
+ {
89
+ ...post('/disable', paths),
90
+ handler: ({ runtime }) => async (_req, res) => {
91
+ try {
92
+ const state = runtime.updates.latest();
93
+ if (!state.enabled) {
94
+ return res.status(400).json({ error: 'Output is already disabled' });
95
+ }
96
+ runtime.updates.sendCommand({
97
+ type: 'disable-output'
98
+ });
99
+ waitFor(() => !runtime.updates.latest().enabled, () => {
100
+ res.sendStatus(204);
101
+ });
102
+ }
103
+ catch (error) {
104
+ console.error('Error in disable handler:', error);
105
+ res.status(500).json({ error: 'Failed to disable output' });
106
+ }
107
+ }
108
+ }
109
+ ];
110
+ }
111
+ async schemas() {
112
+ const types = await promises_1.default.readFile(path_1.default.join(__dirname, 'types.yaml'));
113
+ const root = yaml_1.default.parse(types.toString());
114
+ const resolved = await (0, json_refs_1.resolveRefs)(root, {}).then((r) => r.resolved);
115
+ return {
116
+ config: resolved.components.schemas['AutoCmafConfig'],
117
+ state: resolved.components.schemas['CmafOutputState']
118
+ };
119
+ }
21
120
  }
22
121
  exports.default = AutoCmafDefinition;
23
122
  class AutoCmaf extends base_nodes_1.CustomSinkNode {
@@ -26,27 +125,31 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
26
125
  currentSources = new Map();
27
126
  currentMedia = [];
28
127
  crypto;
128
+ control = new util_2.ContextPromiseControl(this.handleContext.bind(this));
29
129
  mv;
30
130
  defaultProgramNumber = 0;
31
131
  defaultSourceName = '';
32
- control = new util_2.ContextPromiseControl(this.handleContext.bind(this));
33
132
  currentMultiVariants = [];
34
- pendingResponses = [];
35
133
  initialised;
36
134
  sessionId;
37
135
  destinations;
38
136
  advertDestinations = [];
39
- report;
40
- static async create(norsk, cfg, report) {
41
- const node = new AutoCmaf(cfg, norsk, report);
137
+ runtime;
138
+ enabled = true;
139
+ static async create(norsk, cfg, runtime) {
140
+ const node = new AutoCmaf(cfg, norsk, runtime);
42
141
  await node.initialised;
43
142
  return node;
44
143
  }
45
- constructor(cfg, norsk, report) {
144
+ constructor(cfg, norsk, runtime) {
46
145
  super(cfg.id);
47
146
  this.cfg = cfg;
48
147
  this.norsk = norsk;
49
- this.report = report;
148
+ this.runtime = runtime;
149
+ this.enabled = cfg.initialState == 'enabled';
150
+ if (!this.enabled) {
151
+ runtime.updates.raiseEvent({ type: 'output-disabled' });
152
+ }
50
153
  if (this.cfg.sessionId) {
51
154
  this.sessionId = (Math.random() + 1).toString(36).substring(7);
52
155
  }
@@ -58,9 +161,9 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
58
161
  holdBackSeconds: (cfg.segments.holdBackSegments ?? 3) * cfg.segments.targetSegmentDuration,
59
162
  partHoldBackSeconds: (cfg.segments.holdBackParts ?? 3) * cfg.segments.targetPartDuration,
60
163
  sessionId: this.sessionId,
61
- id: 'local'
164
+ id: `local-${this.id}`
62
165
  });
63
- cfg.s3Destinations.forEach((d, i) => {
166
+ cfg.destinations.filter((d) => d.type == 's3').forEach((d, i) => {
64
167
  let sanitisedPrefix = d.prefix;
65
168
  if (sanitisedPrefix == "") {
66
169
  sanitisedPrefix = "/";
@@ -71,7 +174,7 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
71
174
  if (!sanitisedPrefix.endsWith("/"))
72
175
  sanitisedPrefix = sanitisedPrefix + "/";
73
176
  }
74
- const id = `s3-${i}`;
177
+ const id = `s3-${i}-${this.id}`;
75
178
  this.destinations.push({
76
179
  id,
77
180
  type: 's3',
@@ -88,6 +191,22 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
88
191
  this.advertDestinations.push(id);
89
192
  }
90
193
  });
194
+ cfg.destinations.filter((d) => d.type == 'akamai').forEach((d, i) => {
195
+ const id = `akamai-${i}-${this.id}`;
196
+ const url = new URL(d.ingest);
197
+ this.destinations.push({
198
+ id,
199
+ type: 'generic',
200
+ host: url.host,
201
+ port: url.port ? parseInt(url.port, 10) : 80,
202
+ pathPrefix: url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`,
203
+ retentionPeriodSeconds: cfg.segments.retentionPeriod,
204
+ });
205
+ if (d.includeAdInsertions) {
206
+ this.advertDestinations.push(id);
207
+ }
208
+ });
209
+ this.advertDestinations.push('local');
91
210
  this.initialised = this.initialise();
92
211
  }
93
212
  async initialise() {
@@ -106,16 +225,55 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
106
225
  ].join("\n"),
107
226
  mpdAdditions: "",
108
227
  } : {};
109
- const mv = await this.norsk.output.cmafMultiVariant({
110
- id: `${this.cfg.id}-multivariant`,
111
- playlistName: this.cfg.name,
112
- destinations: this.destinations,
113
- ...mvCryptoSettings,
114
- });
115
- this.setup({ sink: mv });
116
- this.mv = mv;
228
+ if (this.cfg.mode == 'cmaf') {
229
+ const cmaf = await this.norsk.output.cmafMultiVariant({
230
+ id: `${this.cfg.id}-multivariant-cmaf`,
231
+ playlistName: this.cfg.name,
232
+ destinations: this.destinations,
233
+ ...mvCryptoSettings,
234
+ });
235
+ this.setup({ sink: cmaf });
236
+ this.mv = cmaf;
237
+ }
238
+ else {
239
+ const ts = await this.norsk.output.hlsTsMultiVariant({
240
+ id: `${this.cfg.id}-multivariant-ts`,
241
+ playlistName: this.cfg.name,
242
+ destinations: this.destinations,
243
+ ...mvCryptoSettings,
244
+ });
245
+ this.setup({ sink: ts });
246
+ this.mv = ts;
247
+ }
248
+ this.runtime.report.registerOutput(this.cfg.id, this.mv.url);
249
+ this.runtime.updates.raiseEvent({ type: 'url-published', url: this.mv.url, drmToken: this.crypto?.token });
250
+ }
251
+ async enableOutput() {
252
+ if (!this.enabled) {
253
+ (0, logging_1.debuglog)("Enabling output", { id: this.id });
254
+ this.enabled = true;
255
+ await this.control.schedule();
256
+ this.runtime.updates.raiseEvent({ type: 'output-enabled' });
257
+ (0, logging_1.debuglog)("Output enabled", { id: this.id });
258
+ }
259
+ }
260
+ async disableOutput() {
261
+ if (this.enabled) {
262
+ (0, logging_1.debuglog)("Disabling output", { id: this.id });
263
+ this.enabled = false;
264
+ for (const media of this.currentMedia) {
265
+ await media.node?.close();
266
+ }
267
+ this.runtime.updates.raiseEvent({ type: 'output-disabled' });
268
+ this.currentMedia = [];
269
+ (0, logging_1.debuglog)("output disabled", { id: this.id });
270
+ }
117
271
  }
118
272
  async handleContext() {
273
+ if (!this.enabled) {
274
+ (0, logging_1.debuglog)("Skipping context handling - output disabled", { id: this.id });
275
+ return;
276
+ }
119
277
  const streams = [];
120
278
  this.currentSources.forEach((subscription) => {
121
279
  for (const stream of subscription.latestStreams()) {
@@ -131,32 +289,41 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
131
289
  const existing = this.currentMedia.find((e) => (0, norsk_sdk_1.streamKeysAreEqual)(e.key, stream.key));
132
290
  if (existing)
133
291
  return;
134
- const newMedia = {
135
- key: stream.key,
136
- node: undefined,
137
- scheduleAd: (_ad, _destinationId) => { }
138
- };
139
- this.currentMedia.push(newMedia);
292
+ (0, logging_1.debuglog)("Handling new stream in AutoCMAF", { id: this.id, streamKey: stream.key });
140
293
  if (this.currentMultiVariants.length == 0) {
294
+ (0, logging_1.debuglog)("Setting default multi-variant in AutoCMAF", { id: this.id, streamKey: stream.key });
141
295
  this.defaultProgramNumber = stream.key.programNumber;
142
296
  this.defaultSourceName = stream.key.sourceName;
143
297
  }
144
- if (!this.currentMultiVariants.find((v) => v.programNumber == stream.key.programNumber && v.sourceName == stream.key.sourceName)) {
145
- const newMv = {
146
- programNumber: stream.key.programNumber,
147
- sourceName: stream.key.sourceName,
148
- node: undefined
149
- };
150
- this.currentMultiVariants.push(newMv);
151
- const mv = await this.norsk.output.cmafMultiVariant({
152
- id: `${this.cfg.id}-multivariant-${stream.key.sourceName}-${stream.key.programNumber}`,
153
- playlistName: `${this.cfg.name}-${stream.key.sourceName}-${stream.key.programNumber}`,
154
- destinations: this.destinations
155
- });
156
- newMv.node = mv;
157
- this.report.registerOutput(this.cfg.id, mv.url);
298
+ if (this.cfg.multiplePrograms) {
299
+ if (!this.currentMultiVariants.find((v) => v.programNumber == stream.key.programNumber && v.sourceName == stream.key.sourceName)) {
300
+ const newMv = {
301
+ programNumber: stream.key.programNumber,
302
+ sourceName: stream.key.sourceName,
303
+ node: undefined,
304
+ };
305
+ this.currentMultiVariants.push(newMv);
306
+ (0, logging_1.debuglog)("Creating program-specific multi-variant in AutoPlaylist", { id: this.id, streamKey: stream.key });
307
+ if (this.cfg.mode == 'cmaf') {
308
+ newMv.node = await this.norsk.output.cmafMultiVariant({
309
+ id: `${this.cfg.id}-multivariant-${stream.key.sourceName}-${stream.key.programNumber}-cmaf`,
310
+ playlistName: `${this.cfg.name}-${stream.key.sourceName}-${stream.key.programNumber}`,
311
+ destinations: this.destinations
312
+ });
313
+ this.runtime.report.registerOutput(this.cfg.id, newMv.node.url);
314
+ }
315
+ else {
316
+ newMv.node = await this.norsk.output.cmafMultiVariant({
317
+ id: `${this.cfg.id}-multivariant-${stream.key.sourceName}-${stream.key.programNumber}-ts`,
318
+ playlistName: `${this.cfg.name}-${stream.key.sourceName}-${stream.key.programNumber}`,
319
+ destinations: this.destinations
320
+ });
321
+ this.runtime.report.registerOutput(this.cfg.id, newMv.node.url);
322
+ }
323
+ }
158
324
  }
159
325
  const streamKeyString = `${stream.key.sourceName}-${stream.key.programNumber}-${stream.key.streamId}-${stream.key.renditionName}`;
326
+ (0, logging_1.debuglog)("Setting up media nodes for key", { id: this.id, key: stream.key, type: stream.metadata.message.case });
160
327
  switch (stream.metadata.message.case) {
161
328
  case undefined:
162
329
  throw "Bad server message";
@@ -166,25 +333,48 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
166
333
  m3uAdditions: this.crypto.video.mediaSignaling,
167
334
  mpdAdditions: this.crypto.video.dashSignalling,
168
335
  } : {};
169
- const video = await this.norsk.output.cmafVideo({
170
- segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
171
- partDurationSeconds: this.cfg.segments.targetPartDuration,
172
- destinations: this.destinations,
173
- id: `${this.id}-${streamKeyString}-video`,
174
- ...videoCryptoSettings,
175
- });
176
- newMedia.node = video;
177
- newMedia.scheduleAd = (ad, destinationId) => {
178
- const now = new Date();
179
- video.scheduleTag(ad, now, destinationId);
336
+ let node = undefined;
337
+ if (this.cfg.mode == 'cmaf') {
338
+ const local = node = await this.norsk.output.cmafVideo({
339
+ segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
340
+ partDurationSeconds: this.cfg.segments.targetPartDuration,
341
+ destinations: this.destinations,
342
+ id: `${this.id}-${streamKeyString}-video-cmaf`,
343
+ ...videoCryptoSettings,
344
+ });
345
+ local.onPlaylistAddition = (_, p) => p;
346
+ local.onPlaylistAddition = undefined;
347
+ subscribes.push(new Promise((resolve, _reject) => {
348
+ local.subscribe([{
349
+ source: stream.source,
350
+ sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
351
+ }], (_) => true, (_) => resolve({}));
352
+ }));
353
+ }
354
+ else {
355
+ const local = node = await this.norsk.output.hlsTsVideo({
356
+ segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
357
+ destinations: this.destinations,
358
+ id: `${this.id}-${streamKeyString}-video-ts`,
359
+ ...videoCryptoSettings,
360
+ });
361
+ subscribes.push(new Promise((resolve, _reject) => {
362
+ local.subscribe([{
363
+ source: stream.source,
364
+ sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
365
+ }], (_) => true, (_) => resolve({}));
366
+ }));
367
+ }
368
+ const newMedia = {
369
+ key: stream.key,
370
+ node,
371
+ scheduleAd: (ad, destinationId) => {
372
+ const now = new Date();
373
+ node?.scheduleTag(ad, now, destinationId);
374
+ }
180
375
  };
181
- subscribes.push(new Promise((resolve, _reject) => {
182
- video.subscribe([{
183
- source: stream.source,
184
- sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
185
- }], (_) => true, (_) => resolve({}));
186
- }));
187
- this.registerInput(video);
376
+ this.currentMedia.push(newMedia);
377
+ this.registerInput(node);
188
378
  break;
189
379
  }
190
380
  case "audio": {
@@ -193,43 +383,74 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
193
383
  m3uAdditions: this.crypto.audio.mediaSignaling,
194
384
  mpdAdditions: this.crypto.audio.dashSignalling,
195
385
  } : {};
196
- const audio = await this.norsk.output.cmafAudio({
197
- segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
198
- partDurationSeconds: this.cfg.segments.targetPartDuration,
199
- destinations: this.destinations,
200
- id: `${this.id}-${streamKeyString}-audio`,
201
- ...audioCryptoSettings,
202
- });
203
- newMedia.node = audio;
204
- newMedia.scheduleAd = (ad, destinationId) => {
205
- const now = new Date();
206
- audio.scheduleTag(ad, now, destinationId);
386
+ let node = undefined;
387
+ if (this.cfg.mode == 'cmaf') {
388
+ const local = node = await this.norsk.output.cmafAudio({
389
+ segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
390
+ partDurationSeconds: this.cfg.segments.targetPartDuration,
391
+ destinations: this.destinations,
392
+ id: `${this.id}-${streamKeyString}-audio-cmaf`,
393
+ ...audioCryptoSettings,
394
+ });
395
+ local.onPlaylistAddition = (_, p) => p;
396
+ local.onPlaylistAddition = undefined;
397
+ subscribes.push(new Promise((resolve, _reject) => {
398
+ local.subscribe([{
399
+ source: stream.source,
400
+ sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
401
+ }], (_) => true, (_) => resolve({}));
402
+ }));
403
+ }
404
+ else {
405
+ const local = node = await this.norsk.output.hlsTsAudio({
406
+ segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
407
+ destinations: this.destinations,
408
+ id: `${this.id}-${streamKeyString}-audio-ts`,
409
+ ...audioCryptoSettings,
410
+ });
411
+ subscribes.push(new Promise((resolve, _reject) => {
412
+ local.subscribe([{
413
+ source: stream.source,
414
+ sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
415
+ }], (_) => true, (_) => resolve({}));
416
+ }));
417
+ }
418
+ const newMedia = {
419
+ key: stream.key,
420
+ node,
421
+ scheduleAd: (ad, destinationId) => {
422
+ const now = new Date();
423
+ node?.scheduleTag(ad, now, destinationId);
424
+ }
207
425
  };
208
- subscribes.push(new Promise((resolve, _reject) => {
209
- audio.subscribe([{
210
- source: stream.source,
211
- sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
212
- }], (_) => true, (_) => resolve({}));
213
- }));
214
- this.registerInput(audio);
426
+ this.currentMedia.push(newMedia);
427
+ this.registerInput(node);
215
428
  break;
216
429
  }
217
430
  case "subtitle": {
218
- const subtitle = await this.norsk.output.cmafWebVtt({
219
- segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
220
- destinations: this.destinations,
221
- id: `${this.id}-${streamKeyString}-webvtt`
222
- });
223
- newMedia.node = subtitle;
224
- newMedia.scheduleAd = (ad, destinationId) => {
225
- const now = new Date();
226
- subtitle.scheduleTag(ad, now, destinationId);
227
- };
228
- subtitle.subscribe([{
229
- source: stream.source,
230
- sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
231
- }]);
232
- this.registerInput(subtitle);
431
+ if (this.cfg.mode == 'cmaf') {
432
+ const subtitleCmaf = await this.norsk.output.cmafWebVtt({
433
+ segmentDurationSeconds: this.cfg.segments.targetSegmentDuration,
434
+ destinations: this.destinations,
435
+ id: `${this.id}-${streamKeyString}-webvtt`,
436
+ });
437
+ subscribes.push(new Promise((resolve, _reject) => {
438
+ subtitleCmaf.subscribe([{
439
+ source: stream.source,
440
+ sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
441
+ }], (_) => true, (_) => resolve({}));
442
+ }));
443
+ const newMedia = {
444
+ key: stream.key,
445
+ node: subtitleCmaf,
446
+ scheduleAd: (ad, destinationId) => {
447
+ const now = new Date();
448
+ subtitleCmaf.scheduleTag(ad, now, destinationId);
449
+ }
450
+ };
451
+ this.currentMedia.push(newMedia);
452
+ this.registerInput(subtitleCmaf);
453
+ }
233
454
  break;
234
455
  }
235
456
  case "ancillary": {
@@ -249,16 +470,18 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
249
470
  id: adId,
250
471
  scte35: message,
251
472
  startDate: schedule,
252
- durationSeconds: Number(command.breakDuration.duration / BigInt(90000.0))
473
+ durationSeconds: Number((command.breakDuration?.duration ?? 0n) / BigInt(90000.0))
253
474
  }, d);
254
475
  }
255
476
  }
256
477
  }
257
478
  });
258
- ancillary.subscribe([{
259
- source: stream.source,
260
- sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
261
- }]);
479
+ subscribes.push(new Promise((resolve, _reject) => {
480
+ ancillary.subscribe([{
481
+ source: stream.source,
482
+ sourceSelector: (0, norsk_sdk_1.selectExactKey)(stream.key)
483
+ }], (_) => true, (_) => resolve({}));
484
+ }));
262
485
  this.registerInput(ancillary);
263
486
  break;
264
487
  }
@@ -275,15 +498,15 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
275
498
  return true;
276
499
  });
277
500
  for (const deletion of deletions) {
278
- const node = deletion?.node;
279
- deletion.node = undefined;
280
- await node?.close();
501
+ const node = deletion.node;
281
502
  this.currentMedia = this.currentMedia.filter((x) => !(0, norsk_sdk_1.streamKeysAreEqual)(x.key, deletion.key));
503
+ (0, logging_1.debuglog)("Closing old media node for non-existant key", { id: this.id, key: deletion.key });
504
+ await node?.close();
282
505
  }
283
- const thisResponse = this.pendingResponses.shift();
506
+ (0, logging_1.debuglog)("Waiting for media playlists to be created", { id: this.id, count: creations.length });
284
507
  await Promise.all(creations);
285
508
  const defaultSources = this.currentMedia.flatMap((m) => {
286
- if (m.key.programNumber == this.defaultProgramNumber && m.key.sourceName == this.defaultSourceName && m.node)
509
+ if (m.key.programNumber == this.defaultProgramNumber && m.key.sourceName == this.defaultSourceName)
287
510
  return [{ source: m.node, sourceSelector: norsk_sdk_1.selectPlaylist }];
288
511
  return [];
289
512
  });
@@ -292,7 +515,8 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
292
515
  this.defaultProgramNumber = 0;
293
516
  this.defaultSourceName = '';
294
517
  }
295
- this.mv?.subscribe(defaultSources);
518
+ this.mv?.subscribe(defaultSources.
519
+ map((s) => ({ source: s.source, sourceSelector: s.sourceSelector })));
296
520
  for (const mv of this.currentMultiVariants) {
297
521
  const sources = this.currentMedia.flatMap((m) => {
298
522
  if (m.key.programNumber == mv.programNumber && m.key.sourceName == mv.sourceName && m.node)
@@ -307,10 +531,12 @@ class AutoCmaf extends base_nodes_1.CustomSinkNode {
307
531
  mv.node?.subscribe(sources);
308
532
  }
309
533
  await Promise.all(subscribes);
310
- if (thisResponse)
311
- thisResponse();
312
534
  }
313
535
  subscribe(subs) {
536
+ this.currentSources = new Map();
537
+ subs.forEach((s) => {
538
+ this.currentSources.set(s.source, s);
539
+ });
314
540
  this.control.setSources(subs);
315
541
  }
316
542
  }