eyevinn-channel-engine 5.1.2 → 5.2.0
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/.github/workflows/nodejs.yml +1 -1
- package/.github/workflows/publish.yml +1 -1
- package/.github/workflows/rc.yml +1 -1
- package/CONTRIBUTING.md +40 -0
- package/Dockerfile +1 -1
- package/README.md +70 -8
- package/dist/engine/chaos_monkey.d.ts +1 -1
- package/dist/engine/chaos_monkey.js +1 -0
- package/dist/engine/event_stream.d.ts +1 -6
- package/dist/engine/event_stream.js +2 -0
- package/dist/engine/memcached_state_store.d.ts +1 -13
- package/dist/engine/memcached_state_store.js +4 -0
- package/dist/engine/memory_state_store.d.ts +1 -13
- package/dist/engine/memory_state_store.js +5 -0
- package/dist/engine/playhead_state.d.ts +0 -26
- package/dist/engine/playhead_state.js +9 -0
- package/dist/engine/preflight.d.ts +1 -1
- package/dist/engine/preflight.js +1 -0
- package/dist/engine/redis_state_store.d.ts +1 -19
- package/dist/engine/redis_state_store.js +5 -0
- package/dist/engine/server.d.ts +27 -0
- package/dist/engine/server.js +140 -6
- package/dist/engine/session.d.ts +1 -148
- package/dist/engine/session.js +398 -11
- package/dist/engine/session_live.d.ts +1 -181
- package/dist/engine/session_live.js +19 -4
- package/dist/engine/session_live_state.d.ts +0 -23
- package/dist/engine/session_live_state.js +5 -0
- package/dist/engine/session_state.d.ts +0 -45
- package/dist/engine/session_state.js +10 -0
- package/dist/engine/shared_state_store.d.ts +1 -25
- package/dist/engine/shared_state_store.js +10 -0
- package/dist/engine/state_store_types.d.ts +32 -0
- package/dist/engine/state_store_types.js +5 -0
- package/dist/engine/stream_switcher.d.ts +1 -40
- package/dist/engine/stream_switcher.js +108 -20
- package/dist/engine/subtitle_slicer.d.ts +1 -8
- package/dist/engine/subtitle_slicer.js +1 -0
- package/dist/engine/util.d.ts +1 -29
- package/dist/engine/util.js +2 -1
- package/dist/examples/adbreak.d.ts +1 -0
- package/dist/examples/adbreak.js +104 -0
- package/dist/index.d.ts +1 -1
- package/dist/package.json +20 -4
- package/docs/design/mixing-muxed-demuxed-audio.md +153 -0
- package/docs/design/subtitles-live-mix-demux.md +135 -0
- package/docs/reference.md +67 -0
- package/engine/chaos_monkey.ts +17 -0
- package/engine/{event_stream.js → event_stream.ts} +5 -3
- package/engine/{memcached_state_store.js → memcached_state_store.ts} +19 -12
- package/engine/memory_state_store.ts +71 -0
- package/engine/{playhead_state.js → playhead_state.ts} +92 -82
- package/engine/{preflight.js → preflight.ts} +1 -1
- package/engine/{redis_state_store.js → redis_state_store.ts} +216 -208
- package/engine/server.ts +210 -8
- package/engine/{session.js → session.ts} +421 -22
- package/engine/{session_live.js → session_live.ts} +54 -31
- package/engine/{session_live_state.js → session_live_state.ts} +129 -120
- package/engine/{session_state.js → session_state.ts} +269 -248
- package/engine/{shared_state_store.js → shared_state_store.ts} +31 -19
- package/engine/state_store_types.ts +35 -0
- package/engine/{stream_switcher.js → stream_switcher.ts} +133 -34
- package/engine/{subtitle_slicer.js → subtitle_slicer.ts} +10 -8
- package/engine/{util.js → util.ts} +28 -24
- package/examples/adbreak.ts +115 -0
- package/index.ts +2 -0
- package/package.json +20 -4
- package/spec/engine/custom_vod_request_params_spec.ts +99 -0
- package/spec/engine/drm_spec.ts +128 -0
- package/spec/engine/{engine_spec.js → engine_spec.ts} +44 -1
- package/spec/engine/{ha_spec.js → ha_spec.ts} +7 -4
- package/spec/engine/{init_switching_spec.js → init_switching_spec.ts} +10 -6
- package/spec/engine/{interface_spec.js → interface_spec.ts} +16 -4
- package/spec/engine/{playhead_live_spec.js → playhead_live_spec.ts} +2 -1
- package/spec/engine/{playhead_spec.js → playhead_spec.ts} +15 -8
- package/spec/engine/session_spec.ts +800 -0
- package/spec/engine/stream_switcher_audio_reconcile_spec.ts +158 -0
- package/spec/engine/{stream_switcher_spec.js → stream_switcher_spec.ts} +111 -13
- package/spec/engine/subtitle_missing_language_spec.ts +196 -0
- package/spec/engine/{subtitle_spec.js → subtitle_spec.ts} +28 -27
- package/spec/helpers/ts-resolve.js +65 -0
- package/spec/support/jasmine.json +1 -1
- package/spec/testvectors/hls_subs_no_language/config.subtitleTracks.json +12 -0
- package/spec/testvectors/hls_subs_no_language/evidence_dummy_segments_name_mismatch.m3u8 +24 -0
- package/spec/testvectors/hls_subs_no_language/evidence_real_cues_name_match.m3u8 +24 -0
- package/spec/testvectors/hls_subs_no_language/french.m3u8 +9 -0
- package/spec/testvectors/hls_subs_no_language/master.m3u8 +11 -0
- package/spec/testvectors/hls_subs_no_language/video.m3u8 +25 -0
- package/spec/testvectors/slate/master.m3u8 +3 -0
- package/spec/testvectors/slate/media.m3u8 +9 -0
- package/tsconfig.json +21 -4
- package/tsconfig.spec.json +16 -0
- package/engine/chaos_monkey.js +0 -17
- package/engine/memory_state_store.js +0 -63
- package/spec/engine/session_spec.js +0 -116
- /package/spec/engine/{session_live_spec.js → session_live_spec.ts} +0 -0
- /package/spec/engine/{util_spec.js → util_spec.ts} +0 -0
package/engine/server.ts
CHANGED
|
@@ -29,10 +29,39 @@ const eventStreams = {};
|
|
|
29
29
|
const DefaultDummySubtitleEndpointPath = "/dummyUrl"
|
|
30
30
|
const DefaultSubtitleSpliceEndpointPath = "/sliceUrl"
|
|
31
31
|
|
|
32
|
+
// Configuration for SGAI HLS-interstitial ad breaks. Config surface only for
|
|
33
|
+
// now (issue #367): this describes when/where a channel may open an ad break
|
|
34
|
+
// and which slate to fall back to. No interstitial tags are emitted from this
|
|
35
|
+
// config yet — that lands in a follow-up (#368). Defaults to disabled.
|
|
36
|
+
export interface AdBreakOpts {
|
|
37
|
+
// Master enable/disable flag for interstitial ad breaks on the channel.
|
|
38
|
+
// When false (the default) no ad-break plumbing is active.
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
// URL of the ad-serving endpoint queried to fill an interstitial break.
|
|
41
|
+
// Required and validated (must be an absolute http/https URL) when enabled.
|
|
42
|
+
adServerUri?: string;
|
|
43
|
+
// Optional slate reference shown while the break is being resolved. Reuses
|
|
44
|
+
// the existing slate shape (uri/repetitions/duration); when omitted the
|
|
45
|
+
// channel/engine slate configuration is used.
|
|
46
|
+
slate?: SlateOpts;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Shared slate reference shape, mirroring the loosely-typed `channel.slate`
|
|
50
|
+
// object already consumed by the session plumbing (slateUri/slateRepetitions/
|
|
51
|
+
// slateDuration).
|
|
52
|
+
export interface SlateOpts {
|
|
53
|
+
uri: string;
|
|
54
|
+
repetitions?: number;
|
|
55
|
+
duration?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
export interface ChannelEngineOpts {
|
|
33
59
|
defaultSlateUri?: string;
|
|
34
60
|
slateRepetitions?: number;
|
|
35
61
|
slateDuration?: number;
|
|
62
|
+
// Default ad-break configuration applied to channels that do not carry their
|
|
63
|
+
// own per-channel `adBreak`. Defaults to disabled when unset.
|
|
64
|
+
adBreak?: AdBreakOpts;
|
|
36
65
|
redisUrl?: string;
|
|
37
66
|
memcachedUrl?: string;
|
|
38
67
|
sharedStoreCacheTTL?: number;
|
|
@@ -66,6 +95,35 @@ export interface ChannelEngineOpts {
|
|
|
66
95
|
sessionEventStream?: boolean;
|
|
67
96
|
sessionHealthKey?: string;
|
|
68
97
|
rollingPDT?: boolean;
|
|
98
|
+
event?: boolean;
|
|
99
|
+
// Allowlist of query-parameter names permitted to be forwarded to the asset
|
|
100
|
+
// manager's getNextVod() request (issue #377). Only parameters whose name is
|
|
101
|
+
// present in this list may be passed through; all others are ignored. This
|
|
102
|
+
// issue only establishes the config surface — the actual pass-through is wired
|
|
103
|
+
// up in follow-ups (#378/#379/#380). Defaults to no allowlist (an empty list),
|
|
104
|
+
// i.e. no pass-through, when omitted.
|
|
105
|
+
customVodRequestParams?: string[];
|
|
106
|
+
// Base stream-switch polling interval in milliseconds (default 3000).
|
|
107
|
+
// This is the cadence at which the global StreamSwitchLoop calls
|
|
108
|
+
// updateStreamSwitchAsync() to evaluate live-schedule switches.
|
|
109
|
+
streamSwitchTimeIntervalMs?: number;
|
|
110
|
+
// Polling interval in milliseconds used while an event is ongoing (default 3000).
|
|
111
|
+
// "Ongoing event": the switcher is on a live stream (stream_switcher.js
|
|
112
|
+
// `this.streamTypeLive === true`) and the current scheduleObj is within its
|
|
113
|
+
// window, i.e. now >= scheduleObj.start_time && now < scheduleObj.end_time.
|
|
114
|
+
// A slower/less-frequent poll is acceptable in this steady state.
|
|
115
|
+
// NOTE: config surface only for now; the polling cadence still uses the base
|
|
116
|
+
// interval. Wire-up lands in the follow-up (#366).
|
|
117
|
+
streamSwitchOngoingEventIntervalMs?: number;
|
|
118
|
+
// Polling interval in milliseconds used when an event is near its end (default 1000).
|
|
119
|
+
// "Near end of event": an ongoing event whose remaining time is within the
|
|
120
|
+
// end-of-event guard window used by the switcher, i.e.
|
|
121
|
+
// scheduleObj.end_time - now <= 10000 (the 10000ms guard in
|
|
122
|
+
// stream_switcher.js ~L200). A faster poll here reduces switch latency at the
|
|
123
|
+
// boundary. Derived from that guard rather than a new unrelated constant.
|
|
124
|
+
// NOTE: config surface only for now; the polling cadence still uses the base
|
|
125
|
+
// interval. Wire-up lands in the follow-up (#366).
|
|
126
|
+
streamSwitchNearEndEventIntervalMs?: number;
|
|
69
127
|
}
|
|
70
128
|
|
|
71
129
|
interface StreamerOpts {
|
|
@@ -82,6 +140,12 @@ export interface VodRequest {
|
|
|
82
140
|
sessionId: string;
|
|
83
141
|
category?: string;
|
|
84
142
|
playlistId: string;
|
|
143
|
+
// Optional custom pass-through parameters forwarded to the asset manager's
|
|
144
|
+
// getNextVod() (issue #378). Sourced from per-session state populated by the
|
|
145
|
+
// master-manifest request handler (wired up in #379) and filtered against the
|
|
146
|
+
// customVodRequestParams allowlist (#377). Optional and defaults to absent so
|
|
147
|
+
// existing IAssetManager implementations are unaffected.
|
|
148
|
+
customParams?: { [key: string]: string };
|
|
85
149
|
}
|
|
86
150
|
|
|
87
151
|
export interface VodResponseMetadata {
|
|
@@ -137,6 +201,10 @@ export interface Channel {
|
|
|
137
201
|
audioTracks?: AudioTracks[];
|
|
138
202
|
subtitleTracks?: SubtitleTracks[];
|
|
139
203
|
closedCaptions?: ClosedCaptions[];
|
|
204
|
+
slate?: SlateOpts;
|
|
205
|
+
// Per-channel ad-break configuration. Overrides the engine-level `adBreak`
|
|
206
|
+
// default when present. Defaults to disabled when neither is set.
|
|
207
|
+
adBreak?: AdBreakOpts;
|
|
140
208
|
}
|
|
141
209
|
|
|
142
210
|
export interface ClosedCaptions {
|
|
@@ -196,6 +264,7 @@ export class ChannelEngine {
|
|
|
196
264
|
private alwaysMapBandwidthByNearest: boolean;
|
|
197
265
|
private defaultSlateUri?: string;
|
|
198
266
|
private slateDuration?: number;
|
|
267
|
+
private adBreak?: AdBreakOpts;
|
|
199
268
|
private assetMgr: IAssetManager;
|
|
200
269
|
private streamSwitchManager?: any;
|
|
201
270
|
private slateRepetitions?: number;
|
|
@@ -204,6 +273,8 @@ export class ChannelEngine {
|
|
|
204
273
|
private serverStartTime: number;
|
|
205
274
|
private instanceId: string;
|
|
206
275
|
private streamSwitchTimeIntervalMs: number;
|
|
276
|
+
private streamSwitchOngoingEventIntervalMs: number;
|
|
277
|
+
private streamSwitchNearEndEventIntervalMs: number;
|
|
207
278
|
private sessionStore: any;
|
|
208
279
|
private sessionLiveStore: any;
|
|
209
280
|
private streamerOpts: StreamerOpts;
|
|
@@ -214,7 +285,50 @@ export class ChannelEngine {
|
|
|
214
285
|
private sessionResetKey: string = "";
|
|
215
286
|
private sessionEventStream: boolean = false;
|
|
216
287
|
private sessionHealthKey: string = "";
|
|
217
|
-
|
|
288
|
+
// Allowlist of query-parameter names permitted to be forwarded to
|
|
289
|
+
// getNextVod() (issue #377). Config surface only — defaults to an empty list
|
|
290
|
+
// (no pass-through) when the option is omitted. Consumed in follow-ups
|
|
291
|
+
// (#378/#379/#380).
|
|
292
|
+
private customVodRequestParams: string[] = [];
|
|
293
|
+
|
|
294
|
+
// Validate and normalize an ad-break configuration (issue #367).
|
|
295
|
+
// Returns a config that is always defined and disabled-by-default:
|
|
296
|
+
// - undefined/omitted -> { enabled: false }
|
|
297
|
+
// - { enabled: false, ... } -> { enabled: false } (endpoint ignored)
|
|
298
|
+
// - { enabled: true, adServerUri } -> validated, with the slate reference
|
|
299
|
+
// (if any) carried through
|
|
300
|
+
// Throws when an enabled break is missing a syntactically valid absolute
|
|
301
|
+
// http(s) ad-serving endpoint URL. `scope` is only used for error context.
|
|
302
|
+
private static normalizeAdBreak(adBreak: AdBreakOpts | undefined, scope: string): AdBreakOpts {
|
|
303
|
+
if (!adBreak || !adBreak.enabled) {
|
|
304
|
+
return { enabled: false };
|
|
305
|
+
}
|
|
306
|
+
if (!adBreak.adServerUri) {
|
|
307
|
+
throw new Error(`Ad-break configuration (${scope}) is enabled but no adServerUri was provided`);
|
|
308
|
+
}
|
|
309
|
+
let parsed: URL;
|
|
310
|
+
try {
|
|
311
|
+
parsed = new URL(adBreak.adServerUri);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
throw new Error(`Ad-break configuration (${scope}) has an invalid adServerUri: ${adBreak.adServerUri}`);
|
|
314
|
+
}
|
|
315
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
316
|
+
throw new Error(`Ad-break configuration (${scope}) adServerUri must be an http(s) URL: ${adBreak.adServerUri}`);
|
|
317
|
+
}
|
|
318
|
+
const normalized: AdBreakOpts = {
|
|
319
|
+
enabled: true,
|
|
320
|
+
adServerUri: adBreak.adServerUri
|
|
321
|
+
};
|
|
322
|
+
if (adBreak.slate && adBreak.slate.uri) {
|
|
323
|
+
normalized.slate = {
|
|
324
|
+
uri: adBreak.slate.uri,
|
|
325
|
+
repetitions: adBreak.slate.repetitions || 10,
|
|
326
|
+
duration: adBreak.slate.duration || 4000
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return normalized;
|
|
330
|
+
}
|
|
331
|
+
|
|
218
332
|
constructor(assetMgr: IAssetManager, options?: ChannelEngineOpts) {
|
|
219
333
|
this.options = options;
|
|
220
334
|
if (options && options.adCopyMgrUri) {
|
|
@@ -257,12 +371,23 @@ export class ChannelEngine {
|
|
|
257
371
|
this.slateRepetitions = options.slateRepetitions || 10;
|
|
258
372
|
this.slateDuration = options.slateDuration || 4000;
|
|
259
373
|
}
|
|
374
|
+
// Ad-break config surface (issue #367). Validate and normalize the
|
|
375
|
+
// engine-level default; disabled when unset. Throws on an enabled break
|
|
376
|
+
// that lacks a valid ad-serving endpoint URL.
|
|
377
|
+
this.adBreak = ChannelEngine.normalizeAdBreak(options && options.adBreak, "engine");
|
|
260
378
|
if (options && options.streamSwitchManager) {
|
|
261
379
|
this.streamSwitchManager = options.streamSwitchManager;
|
|
262
380
|
}
|
|
263
381
|
if (options && options.autoCreateSession !== undefined) {
|
|
264
382
|
this.autoCreateSession = options.autoCreateSession;
|
|
265
383
|
}
|
|
384
|
+
// Custom VOD request-parameter allowlist (issue #377). Config surface only —
|
|
385
|
+
// stored on the instance so follow-ups (#378/#379/#380) can consume it.
|
|
386
|
+
// Defaults to an empty list (no pass-through) when omitted.
|
|
387
|
+
this.customVodRequestParams =
|
|
388
|
+
options && Array.isArray(options.customVodRequestParams)
|
|
389
|
+
? options.customVodRequestParams
|
|
390
|
+
: [];
|
|
266
391
|
this.assetMgr = assetMgr;
|
|
267
392
|
this.monitorTimer = {};
|
|
268
393
|
this.server = fastify;
|
|
@@ -276,7 +401,27 @@ export class ChannelEngine {
|
|
|
276
401
|
this.serverStartTime = Date.now();
|
|
277
402
|
this.instanceId = uuidv4();
|
|
278
403
|
|
|
279
|
-
|
|
404
|
+
// Base stream-switch polling interval (ms). Configurable via options,
|
|
405
|
+
// defaults to the previously-hardcoded 3000ms. See ChannelEngineOpts.
|
|
406
|
+
this.streamSwitchTimeIntervalMs =
|
|
407
|
+
options && options.streamSwitchTimeIntervalMs !== undefined
|
|
408
|
+
? options.streamSwitchTimeIntervalMs
|
|
409
|
+
: 3000;
|
|
410
|
+
// Polling interval (ms) while an event is ongoing. See "ongoing event"
|
|
411
|
+
// definition in ChannelEngineOpts. Defaults to the base interval (3000ms).
|
|
412
|
+
// Config surface only for now — not yet applied to the loop cadence (#366).
|
|
413
|
+
this.streamSwitchOngoingEventIntervalMs =
|
|
414
|
+
options && options.streamSwitchOngoingEventIntervalMs !== undefined
|
|
415
|
+
? options.streamSwitchOngoingEventIntervalMs
|
|
416
|
+
: 3000;
|
|
417
|
+
// Polling interval (ms) when an event is near its end. See "near end of
|
|
418
|
+
// event" definition in ChannelEngineOpts, derived from the 10000ms
|
|
419
|
+
// end-of-event guard in stream_switcher.js. Defaults to a faster 1000ms.
|
|
420
|
+
// Config surface only for now — not yet applied to the loop cadence (#366).
|
|
421
|
+
this.streamSwitchNearEndEventIntervalMs =
|
|
422
|
+
options && options.streamSwitchNearEndEventIntervalMs !== undefined
|
|
423
|
+
? options.streamSwitchNearEndEventIntervalMs
|
|
424
|
+
: 1000;
|
|
280
425
|
|
|
281
426
|
this.sessionStore = {
|
|
282
427
|
sessionStateStore: new SessionStateStore({
|
|
@@ -422,10 +567,15 @@ export class ChannelEngine {
|
|
|
422
567
|
const ts_1 = Date.now();
|
|
423
568
|
await this.updateStreamSwitchAsync()
|
|
424
569
|
const ts_2 = Date.now();
|
|
425
|
-
|
|
570
|
+
// Adaptive cadence: back off while all active channels are mid-event and
|
|
571
|
+
// speed up as any channel nears its end. The poll loop is global but event
|
|
572
|
+
// state is per-channel, so we take the MINIMUM interval required across all
|
|
573
|
+
// channels — never under-polling a channel that needs responsiveness.
|
|
574
|
+
const desiredIntervalMs = this.computeStreamSwitchIntervalMs(timeIntervalMs);
|
|
575
|
+
const interval = (desiredIntervalMs - (ts_2 - ts_1)) < 0 ? minIntervalMs : (desiredIntervalMs - (ts_2 - ts_1));
|
|
426
576
|
const tickInterval = await WTG.getWaitTime(interval);
|
|
427
577
|
await timer(tickInterval)
|
|
428
|
-
debug(`StreamSwitchLoop waited for all channels. Next tick in: ${tickInterval}ms`)
|
|
578
|
+
debug(`StreamSwitchLoop waited for all channels. Next tick in: ${tickInterval}ms (desired base=${desiredIntervalMs}ms)`)
|
|
429
579
|
} catch (err) {
|
|
430
580
|
console.error(err)
|
|
431
581
|
debug(`StreamSwitchLoop iteration failed. Trying Again in 1000ms!`);
|
|
@@ -438,6 +588,41 @@ export class ChannelEngine {
|
|
|
438
588
|
}
|
|
439
589
|
}
|
|
440
590
|
|
|
591
|
+
// Compute the desired next poll interval for the global StreamSwitchLoop from the
|
|
592
|
+
// aggregated per-channel event state. Because the loop is global but event state is
|
|
593
|
+
// per-channel, the result is the MINIMUM interval any channel requires so we never
|
|
594
|
+
// under-poll a channel that needs responsiveness:
|
|
595
|
+
// - a channel that is near-end contributes the fast near-end interval;
|
|
596
|
+
// - a channel that is mid-event but not near-end contributes the slower ongoing
|
|
597
|
+
// interval;
|
|
598
|
+
// - a channel with no ongoing event contributes the base interval.
|
|
599
|
+
// The loop uses the minimum across all channels. When no channel reports an ongoing
|
|
600
|
+
// event (no schedule / no ongoing event) this falls back to the base interval,
|
|
601
|
+
// keeping behavior identical to today.
|
|
602
|
+
computeStreamSwitchIntervalMs(baseIntervalMs) {
|
|
603
|
+
const channels = Object.keys(sessionSwitchers);
|
|
604
|
+
let desiredMs = baseIntervalMs;
|
|
605
|
+
for (const channel of channels) {
|
|
606
|
+
const switcher = sessionSwitchers[channel];
|
|
607
|
+
if (!switcher || typeof switcher.getEventPollingState !== "function") {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const state = switcher.getEventPollingState();
|
|
611
|
+
let channelMs;
|
|
612
|
+
if (state.nearEnd) {
|
|
613
|
+
channelMs = this.streamSwitchNearEndEventIntervalMs;
|
|
614
|
+
} else if (state.midEvent) {
|
|
615
|
+
channelMs = this.streamSwitchOngoingEventIntervalMs;
|
|
616
|
+
} else {
|
|
617
|
+
channelMs = baseIntervalMs;
|
|
618
|
+
}
|
|
619
|
+
if (channelMs < desiredMs) {
|
|
620
|
+
desiredMs = channelMs;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return desiredMs;
|
|
624
|
+
}
|
|
625
|
+
|
|
441
626
|
async updateStreamSwitchAsync() {
|
|
442
627
|
const channels = Object.keys(sessionSwitchers);
|
|
443
628
|
const getSwitchStatusAndPerformSwitch = async (channel) => {
|
|
@@ -455,7 +640,7 @@ export class ChannelEngine {
|
|
|
455
640
|
}
|
|
456
641
|
switcherStatus[channel] = status;
|
|
457
642
|
} catch (err) {
|
|
458
|
-
throw new Error
|
|
643
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
459
644
|
}
|
|
460
645
|
} else {
|
|
461
646
|
debug(`Tried to switch stream on a non-existing channel=[${channel}]. Switching Ignored!)`);
|
|
@@ -465,7 +650,7 @@ export class ChannelEngine {
|
|
|
465
650
|
await Promise.all(channels.map(channel => getSwitchStatusAndPerformSwitch(channel)));
|
|
466
651
|
} catch (err) {
|
|
467
652
|
debug('Problem occured when updating streamSwitchers');
|
|
468
|
-
throw new Error
|
|
653
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
469
654
|
}
|
|
470
655
|
|
|
471
656
|
}
|
|
@@ -499,9 +684,11 @@ export class ChannelEngine {
|
|
|
499
684
|
slateUri: channel.slate && channel.slate.uri ? channel.slate.uri : this.defaultSlateUri,
|
|
500
685
|
slateRepetitions: channel.slate && channel.slate.repetitions ? channel.slate.repetitions : this.slateRepetitions,
|
|
501
686
|
slateDuration: channel.slate && channel.slate.duration ? channel.slate.duration : this.slateDuration,
|
|
687
|
+
adBreak: channel.adBreak ? ChannelEngine.normalizeAdBreak(channel.adBreak, `channel ${channel.id}`) : this.adBreak,
|
|
502
688
|
cloudWatchMetrics: this.logCloudWatchMetrics,
|
|
503
689
|
sessionEventStream: options.sessionEventStream,
|
|
504
|
-
rollingPDT: options.rollingPDT
|
|
690
|
+
rollingPDT: options.rollingPDT,
|
|
691
|
+
event: options.event
|
|
505
692
|
}, this.sessionStore);
|
|
506
693
|
|
|
507
694
|
sessionsLive[channel.id] = new SessionLive({
|
|
@@ -762,7 +949,22 @@ export class ChannelEngine {
|
|
|
762
949
|
if (session) {
|
|
763
950
|
const eventStream = new EventStream(session);
|
|
764
951
|
eventStreams[session.sessionId] = eventStream;
|
|
765
|
-
|
|
952
|
+
|
|
953
|
+
// Plumb allowlisted master.m3u8 query params into the session so the next
|
|
954
|
+
// _getNextVod() call includes them (#379). Sessions are persistent (created
|
|
955
|
+
// once in updateChannelsAsync and reused), so scope custom params per request:
|
|
956
|
+
// set them on every master-manifest request — using an empty object when the
|
|
957
|
+
// allowlist is unset or no allowlisted params are present — so a later request
|
|
958
|
+
// without them doesn't inherit a previous request's values. Non-allowlisted
|
|
959
|
+
// params are ignored.
|
|
960
|
+
const customParams: { [key: string]: string } = {};
|
|
961
|
+
for (const key of this.customVodRequestParams) {
|
|
962
|
+
if (request.query[key] !== undefined) {
|
|
963
|
+
customParams[key] = request.query[key];
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
session.setCustomParams(customParams);
|
|
967
|
+
|
|
766
968
|
let filter;
|
|
767
969
|
if (request.query['filter']) {
|
|
768
970
|
debug(`Applying filter on master manifest ${request.query['filter']}`);
|