@norskvideo/norsk-studio-built-ins 1.27.0-2025-05-27-5d82451c → 1.27.0-2025-05-28-6662b418

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.
@@ -28,6 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  const norsk_sdk_1 = require("@norskvideo/norsk-sdk");
30
30
  const __1 = require("../");
31
+ const path_1 = __importDefault(require("path"));
31
32
  const builder_1 = require("@norskvideo/norsk-studio/lib/test/_util/builder");
32
33
  const document = __importStar(require("@norskvideo/norsk-studio/lib/runtime/document"));
33
34
  const yaml_1 = __importDefault(require("yaml"));
@@ -48,345 +49,15 @@ async function defaultRuntime() {
48
49
  await (0, __1.registerAll)(runtime);
49
50
  return runtime;
50
51
  }
51
- describe("Auto CMAF Output", () => {
52
- async function sharedSetup(norsk, sources, mode, cfg) {
53
- const runtime = await defaultRuntime();
54
- const yaml = new builder_1.YamlBuilder()
55
- .addNode(new builder_1.YamlNodeBuilder("cmaf", (mode == 'cmaf' ? (0, info_1.default)(client_types_1.RegistrationConsts) : (0, info_2.default)(client_types_1.RegistrationConsts)), {
56
- name: 'default',
57
- sessionId: false,
58
- segments: {
59
- retentionPeriod: 60,
60
- targetPartDuration: 0.5,
61
- targetSegmentDuration: 2,
62
- },
63
- destinations: [],
64
- multiplePrograms: true,
65
- initialState: 'enabled',
66
- ...cfg
67
- }).reify())
68
- .reify();
69
- const compiled = document.load(__filename, runtime, yaml_1.default.stringify(yaml));
70
- const result = await (0, execution_1.default)(norsk, compiled);
71
- const cmaf = result.components["cmaf"];
72
- const sub = new execution_1.ComponentSubscriptions(norsk, cmaf);
73
- function doSubscribe() {
74
- sub.setSources(sources.map((s) => {
75
- return new execution_1.StudioNodeSubscriptionSource(s, (0, sources_1.testSourceDescription)(), {
76
- type: 'take-first-stream', filter: [{ media: "video" }, { media: "audio" }]
77
- });
78
- }));
79
- }
80
- sources.forEach((s) => {
81
- s.relatedMediaNodes.onChange(() => {
82
- doSubscribe();
83
- });
84
- });
85
- doSubscribe();
86
- function setSources(newSources) {
87
- sources = newSources;
88
- doSubscribe();
89
- }
90
- return { doSubscribe, setSources, ...result };
91
- }
92
- async function awaitCompleteManifest(url, expectedStreams) {
93
- return new Promise((resolve) => {
94
- const checkManifest = async () => {
95
- try {
96
- const request = await (0, node_fetch_1.default)(url);
97
- if (request.ok) {
98
- const hls = await request.text();
99
- const parsed = HLS.parse(hls);
100
- if (parsed.isMasterPlaylist) {
101
- const master = parsed;
102
- const current = master.variants.reduce((acc, v) => {
103
- acc.add(v.uri);
104
- v.audio.forEach((a) => { if (a.uri)
105
- acc.add(a.uri); });
106
- return acc;
107
- }, new Set());
108
- if (current.size === expectedStreams) {
109
- clearInterval(intervalId);
110
- resolve(master);
111
- }
112
- }
113
- }
114
- }
115
- catch (error) {
116
- console.error("Error checking manifest:", error);
117
- }
118
- };
119
- const intervalId = setInterval(() => {
120
- void checkManifest();
121
- }, 10.0);
122
- });
123
- }
124
- function describeTest(title, cb) {
125
- describe(`${title} - CMAF`, cb('cmaf'));
126
- describe(`${title} - TS`, cb('ts'));
127
- }
128
- describeTest("A single video and audio stream", (mode) => () => {
129
- let norsk = undefined;
130
- let result = undefined;
131
- afterEach(async () => {
132
- await norsk?.close();
133
- });
134
- beforeEach(async () => {
135
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
136
- const source = await (0, sources_1.videoAndAudio)(norsk, 'source');
137
- result = await sharedSetup(norsk, [source], mode);
138
- });
139
- it("Spins up a multi-variant playlist with the one stream", async () => {
140
- const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
141
- (0, chai_1.expect)(mv).exist;
142
- if (mv?.url) {
143
- const finalManifest = await awaitCompleteManifest(mv.url, 2);
144
- (0, chai_1.expect)(finalManifest.variants).length(1);
145
- (0, chai_1.expect)(finalManifest.variants[0]?.audio).length(1);
146
- }
147
- });
148
- });
149
- describeTest("Starting up disabled", (mode) => () => {
150
- let norsk = undefined;
151
- let result = undefined;
152
- afterEach(async () => {
153
- await norsk?.close();
154
- });
155
- beforeEach(async () => {
156
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
157
- const source = await (0, sources_1.videoAndAudio)(norsk, 'source');
158
- result = await sharedSetup(norsk, [source], mode, { initialState: 'disabled' });
159
- });
160
- it("Doesn't spin anything up", async () => {
161
- const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
162
- const disabledResponse = await (0, node_fetch_1.default)(mv.url);
163
- (0, chai_1.expect)(disabledResponse.status).to.not.equal(200);
164
- });
165
- it("Has the disabled state", async () => {
166
- const cmaf = result.components["cmaf"];
167
- (0, chai_1.expect)(cmaf.runtime.updates.latest().enabled).to.be.false;
168
- });
169
- });
170
- describeTest("Two videos and one audio stream", (mode) => () => {
171
- let norsk = undefined;
172
- let result = undefined;
173
- afterEach(async () => {
174
- await norsk?.close();
175
- });
176
- beforeEach(async () => {
177
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
178
- const video1 = await (0, sources_1.video)(norsk, 'source1', { renditionName: "high", sourceName: "source" });
179
- const video2 = await (0, sources_1.video)(norsk, 'source2', { renditionName: "low", sourceName: "source" });
180
- const audio1 = await (0, sources_1.audio)(norsk, 'source3', { renditionName: "default", sourceName: "source" });
181
- result = await sharedSetup(norsk, [video1, video2, audio1], mode);
182
- });
183
- it("Spins up a multi-variant playlist with all the streams", async () => {
184
- const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
185
- (0, chai_1.expect)(mv).exist;
186
- if (mv?.url) {
187
- const finalManifest = await awaitCompleteManifest(mv.url, 3);
188
- (0, chai_1.expect)(finalManifest.variants).length(2);
189
- (0, chai_1.expect)(finalManifest.variants[0]?.audio).length(1);
190
- }
191
- });
192
- });
193
- describeTest("Two sources", (mode) => () => {
194
- let norsk = undefined;
195
- let result = undefined;
196
- afterEach(async () => {
197
- await norsk?.close();
198
- });
199
- beforeEach(async () => {
200
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
201
- const video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
202
- const video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "high", sourceName: "source2" });
203
- const audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
204
- const audio2 = await (0, sources_1.audio)(norsk, 'source2-audio', { renditionName: "default", sourceName: "source2" });
205
- result = await sharedSetup(norsk, [video1, video2, audio1, audio2], mode);
206
- });
207
- it("Spins up a multi-variant playlist with all the streams", async () => {
208
- await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 3);
209
- const firstOne = result.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
210
- const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
211
- const source2 = result?.registeredOutputs.find((s) => s.url?.endsWith("source2-1.m3u8"));
212
- (0, chai_1.expect)(firstOne?.url).not.empty;
213
- (0, chai_1.expect)(source1?.url).not.empty;
214
- (0, chai_1.expect)(source2?.url).not.empty;
215
- const defaultManifest = await awaitCompleteManifest(firstOne.url, 2);
216
- const sourceOneManifest = await awaitCompleteManifest(source1.url, 2);
217
- const sourceTwoManifest = await awaitCompleteManifest(source2.url, 2);
218
- (0, chai_1.expect)(defaultManifest.variants).length(1);
219
- (0, chai_1.expect)(defaultManifest.variants[0]?.audio).length(1);
220
- (0, chai_1.expect)(sourceOneManifest.variants).length(1);
221
- (0, chai_1.expect)(sourceOneManifest.variants[0]?.audio).length(1);
222
- (0, chai_1.expect)(sourceTwoManifest.variants).length(1);
223
- (0, chai_1.expect)(sourceTwoManifest.variants[0]?.audio).length(1);
224
- });
225
- });
226
- describeTest("Stream within a source goes away", (mode) => () => {
227
- let norsk = undefined;
228
- let result = undefined;
229
- let video1 = undefined;
230
- let video2 = undefined;
231
- let video3 = undefined;
232
- let audio1 = undefined;
233
- let audio2 = undefined;
234
- afterEach(async () => {
235
- await norsk?.close();
236
- });
237
- beforeEach(async () => {
238
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
239
- video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
240
- video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "high", sourceName: "source2" });
241
- video3 = await (0, sources_1.video)(norsk, 'source1-video-low', { renditionName: "low", sourceName: "source1" });
242
- audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
243
- audio2 = await (0, sources_1.audio)(norsk, 'source2-audio', { renditionName: "default", sourceName: "source2" });
244
- result = await sharedSetup(norsk, [video1, video2, audio1, audio2, video3], mode);
245
- });
246
- it("Removes the media stream from the multivariant", async () => {
247
- await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 3);
248
- const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
249
- const source2 = result?.registeredOutputs.find((s) => s.url?.endsWith("source2-1.m3u8"));
250
- await awaitCompleteManifest(source1.url, 3);
251
- await awaitCompleteManifest(source2.url, 2);
252
- await video3.close();
253
- const sourceOneManifest = await awaitCompleteManifest(source1.url, 2);
254
- const sourceTwoManifest = await awaitCompleteManifest(source2.url, 2);
255
- (0, chai_1.expect)(sourceOneManifest.variants).length(1);
256
- (0, chai_1.expect)(sourceOneManifest.variants[0]?.audio).length(1);
257
- (0, chai_1.expect)(sourceTwoManifest.variants).length(1);
258
- (0, chai_1.expect)(sourceTwoManifest.variants[0]?.audio).length(1);
259
- });
260
- });
261
- function streamGoesAwayAndComesBack(wait, mode) {
262
- let norsk = undefined;
263
- let result = undefined;
264
- let video1 = undefined;
265
- let video2 = undefined;
266
- let audio1 = undefined;
267
- after(async () => {
268
- await norsk?.close();
269
- });
270
- before(async () => {
271
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
272
- video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
273
- video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
274
- audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
275
- result = await sharedSetup(norsk, [video1, video2, audio1], mode);
276
- });
277
- it("Removes the stream from the multi-variant then re-adds it", async () => {
278
- await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 2);
279
- const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
280
- await awaitCompleteManifest(source1.url, 3);
281
- await video2.close();
282
- if (wait)
283
- await awaitCompleteManifest(source1.url, 2);
284
- video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
285
- result.setSources([video1, video2, audio1]);
286
- const sourceOneManifest = await awaitCompleteManifest(source1.url, 3);
287
- (0, chai_1.expect)(sourceOneManifest.variants).length(2);
288
- (0, chai_1.expect)(sourceOneManifest.variants[0]?.audio).length(1);
289
- });
290
- }
291
- describeTest("Stream within a source goes away and comes back, wait", (mode) => () => {
292
- streamGoesAwayAndComesBack(true, mode);
293
- });
294
- describeTest("Stream within a source goes away and comes back, no wait", (mode) => () => {
295
- streamGoesAwayAndComesBack(true, mode);
296
- });
297
- describeTest("Whole source goes away", (mode) => () => {
298
- let norsk = undefined;
299
- let result = undefined;
300
- let video1 = undefined;
301
- let video2 = undefined;
302
- let audio1 = undefined;
303
- after(async () => {
304
- await norsk?.close();
305
- });
306
- before(async () => {
307
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
308
- video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
309
- video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
310
- audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
311
- result = await sharedSetup(norsk, [video1, video2, audio1], mode);
312
- });
313
- it("Removes the multivariant", async () => {
314
- await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 2);
315
- const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
316
- await awaitCompleteManifest(source1.url, 3);
317
- await audio1.close();
318
- await video2.close();
319
- await video1.close();
320
- async function playlistExists() {
321
- const response = await (0, node_fetch_1.default)(source1.url);
322
- return response.status == 200;
323
- }
324
- await (0, sinks_1.waitForAssert)(async () => !(await playlistExists()), async () => !(await playlistExists()));
325
- });
326
- });
327
- function wholeSourceGoesAwayComesBack(wait, mode) {
328
- let norsk = undefined;
329
- let result = undefined;
330
- let video1 = undefined;
331
- let video2 = undefined;
332
- let audio1 = undefined;
333
- after(async () => {
334
- await norsk?.close();
335
- });
336
- before(async () => {
337
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
338
- video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
339
- video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
340
- audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
341
- result = await sharedSetup(norsk, [video1, video2, audio1], mode);
342
- });
343
- it("Re-creates the multivariant", async () => {
344
- await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 2);
345
- const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
346
- await awaitCompleteManifest(source1.url, 3);
347
- await audio1.close();
348
- await video2.close();
349
- await video1.close();
350
- async function playlistExists() {
351
- const response = await (0, node_fetch_1.default)(source1.url);
352
- return response.status == 200;
353
- }
354
- if (wait)
355
- await (0, util_1.waitForCondition)(async () => !(await playlistExists()));
356
- video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
357
- video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
358
- audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
359
- result.setSources([video1, video2, audio1]);
360
- const finalManifest = await awaitCompleteManifest(source1.url, 3);
361
- (0, chai_1.expect)(finalManifest.variants).length(2);
362
- (0, chai_1.expect)(finalManifest.variants[0]?.audio).length(1);
363
- });
364
- }
365
- describeTest("Whole source goes away and comes back again, wait", (mode) => () => {
366
- wholeSourceGoesAwayComesBack(true, mode);
367
- });
368
- describeTest("Whole source goes away and comes back again, no wait", (mode) => () => {
369
- wholeSourceGoesAwayComesBack(false, mode);
370
- });
371
- describeTest("Auto CMAF state management", (mode) => () => {
372
- let norsk = undefined;
373
- let result = undefined;
374
- let app;
375
- let server;
376
- let port;
377
- let source;
378
- beforeEach(async () => {
379
- norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
380
- app = (0, express_1.default)();
381
- app.use(express_1.default.json());
382
- server = app.listen(0);
383
- port = server.address().port;
384
- source = await (0, sources_1.videoAndAudio)(norsk, 'source');
52
+ function cmafTest(sessionId) {
53
+ let cmaf = undefined;
54
+ describe(`Auto CMAF Output, sessionId: ${sessionId}`, () => {
55
+ async function sharedSetup(norsk, sources, mode, cfg) {
385
56
  const runtime = await defaultRuntime();
386
57
  const yaml = new builder_1.YamlBuilder()
387
58
  .addNode(new builder_1.YamlNodeBuilder("cmaf", (mode == 'cmaf' ? (0, info_1.default)(client_types_1.RegistrationConsts) : (0, info_2.default)(client_types_1.RegistrationConsts)), {
388
59
  name: 'default',
389
- sessionId: false,
60
+ sessionId,
390
61
  segments: {
391
62
  retentionPeriod: 60,
392
63
  targetPartDuration: 0.5,
@@ -394,119 +65,503 @@ describe("Auto CMAF Output", () => {
394
65
  },
395
66
  destinations: [],
396
67
  multiplePrograms: true,
397
- initialState: 'enabled'
68
+ initialState: 'enabled',
69
+ ...cfg
398
70
  }).reify())
399
71
  .reify();
400
72
  const compiled = document.load(__filename, runtime, yaml_1.default.stringify(yaml));
401
- result = await (0, execution_1.default)(norsk, compiled, app);
402
- const cmaf = result.components["cmaf"];
73
+ const result = await (0, execution_1.default)(norsk, compiled);
74
+ cmaf = result.components["cmaf"];
403
75
  const sub = new execution_1.ComponentSubscriptions(norsk, cmaf);
404
- sub.setSources([new execution_1.StudioNodeSubscriptionSource(source, (0, sources_1.testSourceDescription)(), { type: 'take-first-stream', filter: [{ media: "video" }, { media: "audio" }] })]);
405
- });
406
- afterEach(async () => {
407
- try {
408
- if (server) {
409
- await new Promise((resolve) => {
410
- server?.close(() => resolve());
76
+ function doSubscribe() {
77
+ sub.setSources(sources.map((s) => {
78
+ return new execution_1.StudioNodeSubscriptionSource(s, (0, sources_1.testSourceDescription)(), {
79
+ type: 'take-first-stream', filter: [{ media: "video" }, { media: "audio" }]
411
80
  });
412
- }
413
- if (source) {
414
- await source.close().catch(() => { });
415
- }
416
- if (norsk) {
417
- await norsk.close().catch(() => { });
418
- }
419
- await new Promise(f => setTimeout(f, 500));
81
+ }));
420
82
  }
421
- catch (error) {
422
- console.error('Error in afterEach cleanup:', error);
83
+ sources.forEach((s) => {
84
+ s.relatedMediaNodes.onChange(() => {
85
+ doSubscribe();
86
+ });
87
+ });
88
+ doSubscribe();
89
+ function setSources(newSources) {
90
+ sources = newSources;
91
+ doSubscribe();
423
92
  }
93
+ return { doSubscribe, setSources, ...result };
94
+ }
95
+ async function awaitCompleteManifest(url, expectedStreams) {
96
+ return new Promise((resolve) => {
97
+ const checkManifest = async () => {
98
+ try {
99
+ const request = await (0, node_fetch_1.default)(url);
100
+ if (request.ok) {
101
+ const hls = await request.text();
102
+ const parsed = HLS.parse(hls);
103
+ if (parsed.isMasterPlaylist) {
104
+ const master = parsed;
105
+ const current = master.variants.reduce((acc, v) => {
106
+ acc.add(v.uri);
107
+ v.audio.forEach((a) => { if (a.uri)
108
+ acc.add(a.uri); });
109
+ return acc;
110
+ }, new Set());
111
+ if (current.size === expectedStreams) {
112
+ clearInterval(intervalId);
113
+ resolve(master);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ catch (error) {
119
+ console.error("Error checking manifest:", error);
120
+ }
121
+ };
122
+ const intervalId = setInterval(() => {
123
+ void checkManifest();
124
+ }, 10.0);
125
+ });
126
+ }
127
+ function describeTest(title, cb) {
128
+ describe(`${title} - CMAF`, cb('cmaf'));
129
+ describe(`${title} - TS`, cb('ts'));
130
+ }
131
+ describeTest("A single video and audio stream", (mode) => () => {
132
+ let norsk = undefined;
133
+ let result = undefined;
134
+ afterEach(async () => {
135
+ await norsk?.close();
136
+ });
137
+ beforeEach(async () => {
138
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
139
+ const source = await (0, sources_1.videoAndAudio)(norsk, 'source');
140
+ result = await sharedSetup(norsk, [source], mode);
141
+ });
142
+ it("Spins up a multi-variant playlist with the one stream", async () => {
143
+ const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
144
+ (0, chai_1.expect)(mv).exist;
145
+ if (mv?.url) {
146
+ const finalManifest = await awaitCompleteManifest(mv.url, 2);
147
+ (0, chai_1.expect)(finalManifest.variants).length(1);
148
+ (0, chai_1.expect)(finalManifest.variants[0]?.audio).length(1);
149
+ if (sessionId) {
150
+ (0, chai_1.expect)(cmaf.sessionId).is.not.undefined;
151
+ if (cmaf.sessionId) {
152
+ await checkAllVariantsHaveSessionId(cmaf.sessionId, mv.url, finalManifest);
153
+ }
154
+ }
155
+ }
156
+ });
424
157
  });
425
- it("should handle initial state correctly", async () => {
426
- const cmaf = result.components["cmaf"];
427
- const initialState = cmaf.runtime.updates.latest();
428
- (0, chai_1.expect)(initialState.enabled).to.be.true;
429
- const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
430
- (0, chai_1.expect)(mv).to.exist;
431
- (0, chai_1.expect)(mv?.url).to.exist;
432
- const initialManifest = await awaitCompleteManifest(mv.url, 2);
433
- (0, chai_1.expect)(initialManifest.variants).length(1);
434
- (0, chai_1.expect)(initialManifest.variants[0]?.audio).length(1);
158
+ describeTest("Starting up disabled", (mode) => () => {
159
+ let norsk = undefined;
160
+ let result = undefined;
161
+ afterEach(async () => {
162
+ await norsk?.close();
163
+ });
164
+ beforeEach(async () => {
165
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
166
+ const source = await (0, sources_1.videoAndAudio)(norsk, 'source');
167
+ result = await sharedSetup(norsk, [source], mode, { initialState: 'disabled' });
168
+ });
169
+ it("Doesn't spin anything up", async () => {
170
+ const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
171
+ const disabledResponse = await (0, node_fetch_1.default)(mv.url);
172
+ (0, chai_1.expect)(disabledResponse.status).to.not.equal(200);
173
+ });
174
+ it("Has the disabled state", async () => {
175
+ (0, chai_1.expect)(cmaf.runtime.updates.latest().enabled).to.be.false;
176
+ });
435
177
  });
436
- it("should handle disable and enable cycle", async () => {
437
- const cmaf = result.components["cmaf"];
438
- const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
439
- (0, chai_1.expect)(mv).to.exist;
440
- (0, chai_1.expect)(cmaf.runtime.updates.latest().enabled).to.be.true;
441
- const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
442
- method: 'POST'
443
- });
444
- (0, chai_1.expect)(disableResponse.status).to.equal(204);
445
- const enableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/enable`, {
446
- method: 'POST'
447
- });
448
- (0, chai_1.expect)(enableResponse.status).to.equal(204);
178
+ describeTest("Two videos and one audio stream", (mode) => () => {
179
+ let norsk = undefined;
180
+ let result = undefined;
181
+ afterEach(async () => {
182
+ await norsk?.close();
183
+ });
184
+ beforeEach(async () => {
185
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
186
+ const video1 = await (0, sources_1.video)(norsk, 'source1', { renditionName: "high", sourceName: "source" });
187
+ const video2 = await (0, sources_1.video)(norsk, 'source2', { renditionName: "low", sourceName: "source" });
188
+ const audio1 = await (0, sources_1.audio)(norsk, 'source3', { renditionName: "default", sourceName: "source" });
189
+ result = await sharedSetup(norsk, [video1, video2, audio1], mode);
190
+ });
191
+ it("Spins up a multi-variant playlist with all the streams", async () => {
192
+ const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
193
+ (0, chai_1.expect)(mv).exist;
194
+ if (mv?.url) {
195
+ const finalManifest = await awaitCompleteManifest(mv.url, 3);
196
+ (0, chai_1.expect)(finalManifest.variants).length(2);
197
+ (0, chai_1.expect)(finalManifest.variants[0]?.audio).length(1);
198
+ if (sessionId) {
199
+ (0, chai_1.expect)(cmaf.sessionId).is.not.undefined;
200
+ if (cmaf.sessionId) {
201
+ await checkAllVariantsHaveSessionId(cmaf.sessionId, mv.url, finalManifest);
202
+ }
203
+ }
204
+ }
205
+ });
449
206
  });
450
- it("should handle invalid API requests appropriately", async () => {
451
- const cmaf = result.components["cmaf"];
452
- const alreadyEnabledResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/enable`, {
453
- method: 'POST'
207
+ describeTest("Two sources", (mode) => () => {
208
+ let norsk = undefined;
209
+ let result = undefined;
210
+ afterEach(async () => {
211
+ await norsk?.close();
454
212
  });
455
- (0, chai_1.expect)(alreadyEnabledResponse.status).to.equal(400);
456
- const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
457
- method: 'POST'
213
+ beforeEach(async () => {
214
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
215
+ const video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
216
+ const video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "high", sourceName: "source2" });
217
+ const audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
218
+ const audio2 = await (0, sources_1.audio)(norsk, 'source2-audio', { renditionName: "default", sourceName: "source2" });
219
+ result = await sharedSetup(norsk, [video1, video2, audio1, audio2], mode);
458
220
  });
459
- (0, chai_1.expect)(disableResponse.status).to.equal(204);
460
- const alreadyDisabledResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
461
- method: 'POST'
221
+ it("Spins up a multi-variant playlist with all the streams", async () => {
222
+ await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 3);
223
+ const firstOne = result.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
224
+ const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
225
+ const source2 = result?.registeredOutputs.find((s) => s.url?.endsWith("source2-1.m3u8"));
226
+ (0, chai_1.expect)(firstOne?.url).not.empty;
227
+ (0, chai_1.expect)(source1?.url).not.empty;
228
+ (0, chai_1.expect)(source2?.url).not.empty;
229
+ const defaultManifest = await awaitCompleteManifest(firstOne.url, 2);
230
+ const sourceOneManifest = await awaitCompleteManifest(source1.url, 2);
231
+ const sourceTwoManifest = await awaitCompleteManifest(source2.url, 2);
232
+ (0, chai_1.expect)(defaultManifest.variants).length(1);
233
+ (0, chai_1.expect)(defaultManifest.variants[0]?.audio).length(1);
234
+ (0, chai_1.expect)(sourceOneManifest.variants).length(1);
235
+ (0, chai_1.expect)(sourceOneManifest.variants[0]?.audio).length(1);
236
+ (0, chai_1.expect)(sourceTwoManifest.variants).length(1);
237
+ (0, chai_1.expect)(sourceTwoManifest.variants[0]?.audio).length(1);
238
+ if (sessionId) {
239
+ (0, chai_1.expect)(cmaf.sessionId).is.not.undefined;
240
+ if (cmaf.sessionId) {
241
+ await checkAllVariantsHaveSessionId(cmaf.sessionId, firstOne.url, sourceOneManifest);
242
+ await checkAllVariantsHaveSessionId(cmaf.sessionId, source1.url, sourceTwoManifest);
243
+ await checkAllVariantsHaveSessionId(cmaf.sessionId, source2.url, defaultManifest);
244
+ }
245
+ }
462
246
  });
463
- (0, chai_1.expect)(alreadyDisabledResponse.status).to.equal(400);
464
247
  });
465
- it("should maintain disabled state across source changes", async () => {
466
- const cmaf = result.components["cmaf"];
467
- const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
468
- (0, chai_1.expect)(mv).to.exist;
469
- const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
470
- method: 'POST'
471
- });
472
- (0, chai_1.expect)(disableResponse.status).to.equal(204);
473
- const newSource = await (0, sources_1.videoAndAudio)(norsk, 'new-source');
474
- const sub = new execution_1.ComponentSubscriptions(norsk, cmaf);
475
- sub.setSources([new execution_1.StudioNodeSubscriptionSource(newSource, (0, sources_1.testSourceDescription)(), { type: 'take-first-stream', filter: [{ media: "video" }, { media: "audio" }] })]);
476
- const disabledResponse = await (0, node_fetch_1.default)(mv.url);
477
- (0, chai_1.expect)(disabledResponse.status).to.not.equal(200);
248
+ describeTest("Stream within a source goes away", (mode) => () => {
249
+ let norsk = undefined;
250
+ let result = undefined;
251
+ let video1 = undefined;
252
+ let video2 = undefined;
253
+ let video3 = undefined;
254
+ let audio1 = undefined;
255
+ let audio2 = undefined;
256
+ afterEach(async () => {
257
+ await norsk?.close();
258
+ });
259
+ beforeEach(async () => {
260
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
261
+ video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
262
+ video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "high", sourceName: "source2" });
263
+ video3 = await (0, sources_1.video)(norsk, 'source1-video-low', { renditionName: "low", sourceName: "source1" });
264
+ audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
265
+ audio2 = await (0, sources_1.audio)(norsk, 'source2-audio', { renditionName: "default", sourceName: "source2" });
266
+ result = await sharedSetup(norsk, [video1, video2, audio1, audio2, video3], mode);
267
+ });
268
+ it("Removes the media stream from the multivariant", async () => {
269
+ await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 3);
270
+ const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
271
+ const source2 = result?.registeredOutputs.find((s) => s.url?.endsWith("source2-1.m3u8"));
272
+ await awaitCompleteManifest(source1.url, 3);
273
+ await awaitCompleteManifest(source2.url, 2);
274
+ await video3.close();
275
+ const sourceOneManifest = await awaitCompleteManifest(source1.url, 2);
276
+ const sourceTwoManifest = await awaitCompleteManifest(source2.url, 2);
277
+ (0, chai_1.expect)(sourceOneManifest.variants).length(1);
278
+ (0, chai_1.expect)(sourceOneManifest.variants[0]?.audio).length(1);
279
+ (0, chai_1.expect)(sourceTwoManifest.variants).length(1);
280
+ (0, chai_1.expect)(sourceTwoManifest.variants[0]?.audio).length(1);
281
+ });
478
282
  });
479
- it("should emit appropriate events on state changes", async () => {
480
- const cmaf = result.components["cmaf"];
481
- const disabledEventPromise = new Promise((resolve) => {
482
- const originalRaiseEvent = cmaf.runtime.updates.raiseEvent;
483
- cmaf.runtime.updates.raiseEvent = (event) => {
484
- if (event.type === 'output-disabled') {
485
- resolve();
283
+ function streamGoesAwayAndComesBack(wait, mode) {
284
+ let norsk = undefined;
285
+ let result = undefined;
286
+ let video1 = undefined;
287
+ let video2 = undefined;
288
+ let audio1 = undefined;
289
+ after(async () => {
290
+ await norsk?.close();
291
+ });
292
+ before(async () => {
293
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
294
+ video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
295
+ video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
296
+ audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
297
+ result = await sharedSetup(norsk, [video1, video2, audio1], mode);
298
+ });
299
+ it("Removes the stream from the multi-variant then re-adds it", async () => {
300
+ await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 2);
301
+ const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
302
+ await awaitCompleteManifest(source1.url, 3);
303
+ await video2.close();
304
+ if (wait)
305
+ await awaitCompleteManifest(source1.url, 2);
306
+ video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
307
+ result.setSources([video1, video2, audio1]);
308
+ const sourceOneManifest = await awaitCompleteManifest(source1.url, 3);
309
+ (0, chai_1.expect)(sourceOneManifest.variants).length(2);
310
+ (0, chai_1.expect)(sourceOneManifest.variants[0]?.audio).length(1);
311
+ if (sessionId) {
312
+ (0, chai_1.expect)(cmaf.sessionId).is.not.undefined;
313
+ if (cmaf.sessionId) {
314
+ await checkAllVariantsHaveSessionId(cmaf.sessionId, source1.url, sourceOneManifest);
486
315
  }
487
- return originalRaiseEvent.call(cmaf.runtime.updates, event);
488
- };
316
+ }
489
317
  });
490
- const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
491
- method: 'POST'
318
+ }
319
+ describeTest("Stream within a source goes away and comes back, wait", (mode) => () => {
320
+ streamGoesAwayAndComesBack(true, mode);
321
+ });
322
+ describeTest("Stream within a source goes away and comes back, no wait", (mode) => () => {
323
+ streamGoesAwayAndComesBack(true, mode);
324
+ });
325
+ describeTest("Whole source goes away", (mode) => () => {
326
+ let norsk = undefined;
327
+ let result = undefined;
328
+ let video1 = undefined;
329
+ let video2 = undefined;
330
+ let audio1 = undefined;
331
+ after(async () => {
332
+ await norsk?.close();
492
333
  });
493
- (0, chai_1.expect)(disableResponse.status).to.equal(204);
494
- await disabledEventPromise;
495
- const enabledEventPromise = new Promise((resolve) => {
496
- const originalRaiseEvent = cmaf.runtime.updates.raiseEvent;
497
- cmaf.runtime.updates.raiseEvent = (event) => {
498
- if (event.type === 'output-enabled') {
499
- resolve();
334
+ before(async () => {
335
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
336
+ video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
337
+ video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
338
+ audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
339
+ result = await sharedSetup(norsk, [video1, video2, audio1], mode);
340
+ });
341
+ it("Removes the multivariant", async () => {
342
+ await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 2);
343
+ const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
344
+ await awaitCompleteManifest(source1.url, 3);
345
+ await audio1.close();
346
+ await video2.close();
347
+ await video1.close();
348
+ async function playlistExists() {
349
+ const response = await (0, node_fetch_1.default)(source1.url);
350
+ return response.status == 200;
351
+ }
352
+ await (0, sinks_1.waitForAssert)(async () => !(await playlistExists()), async () => !(await playlistExists()));
353
+ });
354
+ });
355
+ function wholeSourceGoesAwayComesBack(wait, mode) {
356
+ let norsk = undefined;
357
+ let result = undefined;
358
+ let video1 = undefined;
359
+ let video2 = undefined;
360
+ let audio1 = undefined;
361
+ let firstSessionId = '';
362
+ after(async () => {
363
+ await norsk?.close();
364
+ });
365
+ before(async () => {
366
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
367
+ video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
368
+ video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
369
+ audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
370
+ result = await sharedSetup(norsk, [video1, video2, audio1], mode);
371
+ cmaf = result.components['cmaf'];
372
+ });
373
+ it("Re-creates the multivariant", async () => {
374
+ await (0, util_1.waitForCondition)(() => result?.registeredOutputs.length === 2);
375
+ const source1 = result?.registeredOutputs.find((s) => s.url?.endsWith("source1-1.m3u8"));
376
+ await awaitCompleteManifest(source1.url, 3);
377
+ firstSessionId = cmaf.sessionId;
378
+ await audio1.close();
379
+ await video2.close();
380
+ await video1.close();
381
+ async function playlistExists() {
382
+ const response = await (0, node_fetch_1.default)(source1.url);
383
+ return response.status == 200;
384
+ }
385
+ if (wait)
386
+ await (0, util_1.waitForCondition)(async () => !(await playlistExists()));
387
+ video1 = await (0, sources_1.video)(norsk, 'source1-video', { renditionName: "high", sourceName: "source1" });
388
+ video2 = await (0, sources_1.video)(norsk, 'source2-video', { renditionName: "medium", sourceName: "source1" });
389
+ audio1 = await (0, sources_1.audio)(norsk, 'source1-audio', { renditionName: "default", sourceName: "source1" });
390
+ result.setSources([video1, video2, audio1]);
391
+ const finalManifest = await awaitCompleteManifest(source1.url, 3);
392
+ (0, chai_1.expect)(finalManifest.variants).length(2);
393
+ (0, chai_1.expect)(finalManifest.variants[0]?.audio).length(1);
394
+ if (wait && sessionId) {
395
+ (0, chai_1.expect)(cmaf.sessionId).not.equal(firstSessionId);
396
+ (0, chai_1.expect)(cmaf.sessionId).not.undefined;
397
+ await checkAllVariantsHaveSessionId(cmaf.sessionId, source1.url, finalManifest);
398
+ }
399
+ });
400
+ }
401
+ describeTest("Whole source goes away and comes back again, wait", (mode) => () => {
402
+ wholeSourceGoesAwayComesBack(true, mode);
403
+ });
404
+ describeTest("Whole source goes away and comes back again, no wait", (mode) => () => {
405
+ wholeSourceGoesAwayComesBack(false, mode);
406
+ });
407
+ describeTest("Auto CMAF state management", (mode) => () => {
408
+ let norsk = undefined;
409
+ let result = undefined;
410
+ let app;
411
+ let server;
412
+ let port;
413
+ let source;
414
+ beforeEach(async () => {
415
+ norsk = await norsk_sdk_1.Norsk.connect({ onShutdown: () => { } });
416
+ app = (0, express_1.default)();
417
+ app.use(express_1.default.json());
418
+ server = app.listen(0);
419
+ port = server.address().port;
420
+ source = await (0, sources_1.videoAndAudio)(norsk, 'source');
421
+ const runtime = await defaultRuntime();
422
+ const yaml = new builder_1.YamlBuilder()
423
+ .addNode(new builder_1.YamlNodeBuilder("cmaf", (mode == 'cmaf' ? (0, info_1.default)(client_types_1.RegistrationConsts) : (0, info_2.default)(client_types_1.RegistrationConsts)), {
424
+ name: 'default',
425
+ sessionId,
426
+ segments: {
427
+ retentionPeriod: 60,
428
+ targetPartDuration: 0.5,
429
+ targetSegmentDuration: 2,
430
+ },
431
+ destinations: [],
432
+ multiplePrograms: true,
433
+ initialState: 'enabled'
434
+ }).reify())
435
+ .reify();
436
+ const compiled = document.load(__filename, runtime, yaml_1.default.stringify(yaml));
437
+ result = await (0, execution_1.default)(norsk, compiled, app);
438
+ cmaf = result.components["cmaf"];
439
+ const sub = new execution_1.ComponentSubscriptions(norsk, cmaf);
440
+ sub.setSources([new execution_1.StudioNodeSubscriptionSource(source, (0, sources_1.testSourceDescription)(), { type: 'take-first-stream', filter: [{ media: "video" }, { media: "audio" }] })]);
441
+ });
442
+ afterEach(async () => {
443
+ try {
444
+ if (server) {
445
+ await new Promise((resolve) => {
446
+ server?.close(() => resolve());
447
+ });
500
448
  }
501
- return originalRaiseEvent.call(cmaf.runtime.updates, event);
502
- };
449
+ if (source) {
450
+ await source.close().catch(() => { });
451
+ }
452
+ if (norsk) {
453
+ await norsk.close().catch(() => { });
454
+ }
455
+ await new Promise(f => setTimeout(f, 500));
456
+ }
457
+ catch (error) {
458
+ console.error('Error in afterEach cleanup:', error);
459
+ }
460
+ });
461
+ it("should handle initial state correctly", async () => {
462
+ const initialState = cmaf.runtime.updates.latest();
463
+ (0, chai_1.expect)(initialState.enabled).to.be.true;
464
+ const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
465
+ (0, chai_1.expect)(mv).to.exist;
466
+ (0, chai_1.expect)(mv?.url).to.exist;
467
+ const initialManifest = await awaitCompleteManifest(mv.url, 2);
468
+ (0, chai_1.expect)(initialManifest.variants).length(1);
469
+ (0, chai_1.expect)(initialManifest.variants[0]?.audio).length(1);
470
+ });
471
+ it("should handle disable and enable cycle", async () => {
472
+ const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
473
+ (0, chai_1.expect)(mv).to.exist;
474
+ (0, chai_1.expect)(cmaf.runtime.updates.latest().enabled).to.be.true;
475
+ const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
476
+ method: 'POST'
477
+ });
478
+ (0, chai_1.expect)(disableResponse.status).to.equal(204);
479
+ const enableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/enable`, {
480
+ method: 'POST'
481
+ });
482
+ (0, chai_1.expect)(enableResponse.status).to.equal(204);
503
483
  });
504
- const enableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/enable`, {
505
- method: 'POST'
484
+ it("should handle invalid API requests appropriately", async () => {
485
+ const alreadyEnabledResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/enable`, {
486
+ method: 'POST'
487
+ });
488
+ (0, chai_1.expect)(alreadyEnabledResponse.status).to.equal(400);
489
+ const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
490
+ method: 'POST'
491
+ });
492
+ (0, chai_1.expect)(disableResponse.status).to.equal(204);
493
+ const alreadyDisabledResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
494
+ method: 'POST'
495
+ });
496
+ (0, chai_1.expect)(alreadyDisabledResponse.status).to.equal(400);
497
+ });
498
+ it("should maintain disabled state across source changes", async () => {
499
+ const mv = result?.registeredOutputs.find((s) => s.url?.endsWith("default.m3u8"));
500
+ (0, chai_1.expect)(mv).to.exist;
501
+ const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
502
+ method: 'POST'
503
+ });
504
+ (0, chai_1.expect)(disableResponse.status).to.equal(204);
505
+ const newSource = await (0, sources_1.videoAndAudio)(norsk, 'new-source');
506
+ const sub = new execution_1.ComponentSubscriptions(norsk, cmaf);
507
+ sub.setSources([new execution_1.StudioNodeSubscriptionSource(newSource, (0, sources_1.testSourceDescription)(), { type: 'take-first-stream', filter: [{ media: "video" }, { media: "audio" }] })]);
508
+ const disabledResponse = await (0, node_fetch_1.default)(mv.url);
509
+ (0, chai_1.expect)(disabledResponse.status).to.not.equal(200);
510
+ });
511
+ it("should emit appropriate events on state changes", async () => {
512
+ const disabledEventPromise = new Promise((resolve) => {
513
+ const originalRaiseEvent = cmaf.runtime.updates.raiseEvent;
514
+ cmaf.runtime.updates.raiseEvent = (event) => {
515
+ if (event.type === 'output-disabled') {
516
+ resolve();
517
+ }
518
+ return originalRaiseEvent.call(cmaf.runtime.updates, event);
519
+ };
520
+ });
521
+ const disableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/disable`, {
522
+ method: 'POST'
523
+ });
524
+ (0, chai_1.expect)(disableResponse.status).to.equal(204);
525
+ await disabledEventPromise;
526
+ const enabledEventPromise = new Promise((resolve) => {
527
+ const originalRaiseEvent = cmaf.runtime.updates.raiseEvent;
528
+ cmaf.runtime.updates.raiseEvent = (event) => {
529
+ if (event.type === 'output-enabled') {
530
+ resolve();
531
+ }
532
+ return originalRaiseEvent.call(cmaf.runtime.updates, event);
533
+ };
534
+ });
535
+ const enableResponse = await (0, node_fetch_1.default)(`http://localhost:${port}/${cmaf.id}/enable`, {
536
+ method: 'POST'
537
+ });
538
+ (0, chai_1.expect)(enableResponse.status).to.equal(204);
539
+ await enabledEventPromise;
506
540
  });
507
- (0, chai_1.expect)(enableResponse.status).to.equal(204);
508
- await enabledEventPromise;
509
541
  });
510
542
  });
511
- });
543
+ }
544
+ cmafTest(true);
545
+ cmafTest(false);
546
+ async function playlistHasSessionId(uri, sessionId) {
547
+ const content = await (0, node_fetch_1.default)(path_1.default.join(uri)).then(async (p) => p.text());
548
+ return content.indexOf(sessionId) >= 0;
549
+ }
550
+ async function checkAllVariantsHaveSessionId(sessionId, mvUrl, mv) {
551
+ const baseUrl = path_1.default.dirname(mvUrl);
552
+ await (0, util_1.waitForCondition)(async () => {
553
+ const vHas = await Promise.all(mv.variants.map(async (v) => playlistHasSessionId(path_1.default.join(baseUrl, v.uri), sessionId)));
554
+ const audioUri = mv.variants[0].audio[0].uri;
555
+ const aHas = audioUri ? await playlistHasSessionId(path_1.default.join(baseUrl, audioUri), sessionId) : true;
556
+ return vHas.every((e) => e) && aHas;
557
+ }, 5000, 1000);
558
+ await Promise.all(mv.variants.map(async (v) => {
559
+ const content = await (0, node_fetch_1.default)(path_1.default.join(baseUrl, v.uri ?? '')).then(async (p) => p.text());
560
+ (0, chai_1.expect)(content).contains(sessionId);
561
+ await Promise.all(v.audio.map(async (a) => {
562
+ const content = await (0, node_fetch_1.default)(path_1.default.join(baseUrl, a.uri ?? '')).then(async (p) => p.text());
563
+ (0, chai_1.expect)(content).contains(sessionId);
564
+ }));
565
+ }));
566
+ }
512
567
  //# sourceMappingURL=auto-cmaf.js.map