eyevinn-channel-engine 3.0.0 → 3.0.4

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.
@@ -1,4 +1,4 @@
1
- const debug = require("debug")("engine-streamSwitcher");
1
+ const debug = require("debug")("engine-stream-switcher");
2
2
  const crypto = require("crypto");
3
3
  const fetch = require("node-fetch");
4
4
  const { AbortController } = require("abort-controller");
@@ -30,6 +30,7 @@ class StreamSwitcher {
30
30
  this.timeDiff = null;
31
31
  this.abortTimeStamp = null;
32
32
 
33
+
33
34
  if (config) {
34
35
  if (config.sessionId) {
35
36
  this.sessionId = config.sessionId;
@@ -50,6 +51,26 @@ class StreamSwitcher {
50
51
  return this.eventId;
51
52
  }
52
53
 
54
+ async abortLiveFeed(session, sessionLive, message) {
55
+ if (this.streamTypeLive) {
56
+ let status = null;
57
+ debug(`[${this.sessionId}]: Abort Live Stream! Reason: ${message}`);
58
+ try {
59
+ this.abortTimeStamp = Date.now();
60
+ status = await this._initSwitching(
61
+ SwitcherState.LIVE_TO_V2L,
62
+ session,
63
+ sessionLive,
64
+ null
65
+ );
66
+ return status;
67
+ } catch (err) {
68
+ debug(`Failed to force a switch off live feed: ${err}`);
69
+ throw new Error(err);
70
+ }
71
+ }
72
+ }
73
+
53
74
  /**
54
75
  *
55
76
  * @param {Session} session The VOD2Live Session object.
@@ -57,6 +78,7 @@ class StreamSwitcher {
57
78
  * @returns A bool, true if streamSwitchManager contains current Live event to be played else false.
58
79
  */
59
80
  async streamSwitcher(session, sessionLive) {
81
+ let status = null;
60
82
  if (!this.streamSwitchManager) {
61
83
  debug(`[${this.sessionId}]: No streamSwitchManager available`);
62
84
  return false;
@@ -65,11 +87,28 @@ class StreamSwitcher {
65
87
  debug(`[${this.sessionId}]: streamSwitcher is currently busy`);
66
88
  return null;
67
89
  }
68
- let status = null;
90
+ // Handle Complete Storage Reset
91
+ let sessionState = await session._sessionState.get("state");
92
+ if (sessionState === 1 || !sessionState) {
93
+ this.working = true;
94
+ sessionLive.waitForPlayhead = false;
95
+ sessionLive.allowedToSet = false; // only have effect on leader
96
+ await sessionLive.resetSession();
97
+ await sessionLive.resetLiveStoreAsync(0);
98
+ this.working = false;
99
+ this.abortTimeStamp = Date.now() + 20 * 1000; // 30 second V2L->LIVE timeout
100
+ if (this.streamTypeLive) {
101
+ debug(`[${this.sessionId}]: [ Ending LIVE Abruptly, Going to -> V2L ]`);
102
+ this.streamTypeLive = false;
103
+ }
104
+ debug(`[${this.sessionId}]: StreamSwitcher reacting to Full Store Reset`);
105
+ return false; // Go to V2L feed
106
+ }
107
+
69
108
  // Filter out schedule objects from the past
70
- const tsNow = Date.now();
109
+ const tsNow = Date.now();
71
110
  const strmSchedule = await this.streamSwitchManager.getSchedule(this.sessionId);
72
- const schedule = strmSchedule.filter((obj) => obj.end_time > tsNow);
111
+ const schedule = strmSchedule.filter((obj) => obj.end_time > tsNow);
73
112
  if (schedule.length === 0 && this.streamTypeLive) {
74
113
  status = await this._initSwitching(
75
114
  SwitcherState.LIVE_TO_V2L,
@@ -102,47 +141,54 @@ class StreamSwitcher {
102
141
  let tries = 0;
103
142
  let validURI = false;
104
143
  while (!validURI && tries < MAX_FAILS) {
105
- debug(`[${this.sessionId}]: Switcher is validating Master URI... (tries left=${MAX_FAILS - tries})`);
144
+ debug(
145
+ `[${
146
+ this.sessionId
147
+ }]: Switcher is validating Master URI... (tries left=${
148
+ MAX_FAILS - tries
149
+ })`
150
+ );
106
151
  validURI = await this._validURI(scheduleObj.uri);
107
152
  tries++;
108
153
  }
109
154
  if (!validURI) {
110
155
  debug(`[${this.sessionId}]: Unreachable URI: [${scheduleObj.uri}]`);
111
156
  if (this.streamTypeLive) {
112
- debug(`[${this.sessionId}]: Abort Live Stream! Switching back to VOD2Live due to unreachable URI`);
113
- this.abortTimeStamp = Date.now();
114
- status = await this._initSwitching(
115
- SwitcherState.LIVE_TO_V2L,
157
+ await this.abortLiveFeed(
116
158
  session,
117
159
  sessionLive,
118
- null
160
+ "Switching back to VOD2Live due to unreachable URI"
119
161
  );
120
- return status;
121
162
  }
122
163
  return false;
123
164
  }
124
165
  debug(`[${this.sessionId}]: ....Master URI -> VALID`);
125
166
 
126
-
127
- if (this.abortTimeStamp && (tsNow - this.abortTimeStamp) <= 10000) {
167
+ if (this.abortTimeStamp && tsNow - this.abortTimeStamp <= 10000) {
128
168
  // If we have a valid URI and no more than 10 seconds have passed since switching from Live->V2L.
129
169
  // Stay on V2L to give live sessionLive some time to prepare before switching back to live.
130
- debug(`[${this.sessionId}]: Waiting [${10000 - (tsNow - this.abortTimeStamp)}ms] before switching back to Live due to unreachable URI`);
170
+ debug(
171
+ `[${this.sessionId}]: Waiting [${
172
+ 10000 - (tsNow - this.abortTimeStamp)
173
+ }ms] before switching back to Live due to unreachable URI`
174
+ );
131
175
  return false;
132
- }
176
+ }
133
177
  this.abortTimeStamp = null;
134
178
 
135
179
  if (this.streamTypeLive) {
136
- if (tsNow >= scheduleObj.start_time && this.eventId !== scheduleObj.eventId) {
180
+ if (
181
+ tsNow >= scheduleObj.start_time &&
182
+ this.eventId !== scheduleObj.eventId
183
+ ) {
137
184
  if (scheduleObj.type === StreamType.LIVE) {
138
- status = await this._initSwitching(
185
+ status = await this._initSwitching(
139
186
  SwitcherState.LIVE_TO_LIVE,
140
187
  session,
141
188
  sessionLive,
142
189
  scheduleObj
143
190
  );
144
191
  return status;
145
-
146
192
  }
147
193
  status = await this._initSwitching(
148
194
  SwitcherState.LIVE_TO_VOD,
@@ -153,7 +199,11 @@ class StreamSwitcher {
153
199
  return status;
154
200
  }
155
201
  }
156
- if (tsNow >= scheduleObj.start_time && tsNow < scheduleObj.end_time && (scheduleObj.end_time - tsNow) > 10000) {
202
+ if (
203
+ tsNow >= scheduleObj.start_time &&
204
+ tsNow < scheduleObj.end_time &&
205
+ scheduleObj.end_time - tsNow > 10000
206
+ ) {
157
207
  if (scheduleObj.type === StreamType.LIVE) {
158
208
  if (!this.streamTypeLive) {
159
209
  status = await this._initSwitching(
@@ -168,7 +218,9 @@ class StreamSwitcher {
168
218
  }
169
219
  if (!this.streamTypeLive) {
170
220
  if (!scheduleObj.duration) {
171
- debug(`[${this.sessionId}]: Cannot switch VOD no duration specified for schedule item: [${scheduleObj.assetId}]`);
221
+ debug(
222
+ `[${this.sessionId}]: Cannot switch VOD no duration specified for schedule item: [${scheduleObj.assetId}]`
223
+ );
172
224
  return false;
173
225
  }
174
226
  if (this.eventId !== scheduleObj.eventId) {
@@ -198,133 +250,190 @@ class StreamSwitcher {
198
250
 
199
251
  switch (state) {
200
252
  case SwitcherState.V2L_TO_LIVE:
201
- debug(`[${this.sessionId}]: [ INIT Switching from V2L->LIVE ]`);
202
- this.eventId = scheduleObj.eventId;
203
- currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
204
- currVodSegments = await session.getCurrentMediaSequenceSegments();
205
-
206
- // In risk that the SL-playhead might have updated some data after
207
- // we reset last time... we should Reset SessionLive before sending new data.
208
- await sessionLive.resetLiveStoreAsync(0);
209
- await sessionLive.setCurrentMediaAndDiscSequenceCount(currVodCounts.mediaSeq, currVodCounts.discSeq);
210
- await sessionLive.setCurrentMediaSequenceSegments(currVodSegments);
211
- liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
212
-
213
- if (!liveUri) {
214
- debug(`[${this.sessionId}]: [ ERROR Switching from V2L->LIVE ]`);
215
- this.working = false;
216
- this.eventId = null;
217
- return false;
218
- }
253
+ try {
254
+ debug(`[${this.sessionId}]: [ INIT Switching from V2L->LIVE ]`);
255
+ this.eventId = scheduleObj.eventId;
256
+ currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
257
+ currVodSegments = await session.getCurrentMediaSequenceSegments();
258
+
259
+ // In risk that the SL-playhead might have updated some data after
260
+ // we reset last time... we should Reset SessionLive before sending new data.
261
+ await sessionLive.resetLiveStoreAsync(0);
262
+ await sessionLive.setCurrentMediaAndDiscSequenceCount(
263
+ currVodCounts.mediaSeq,
264
+ currVodCounts.discSeq
265
+ );
266
+ await sessionLive.setCurrentMediaSequenceSegments(currVodSegments);
267
+ liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
219
268
 
220
- this.working = false;
221
- this.streamTypeLive = true;
222
- debug(`[${this.sessionId}]: [ Switched from V2L->LIVE ]`);
223
- return true;
224
- case SwitcherState.V2L_TO_VOD:
225
- debug(`[${this.sessionId}]: [ INIT Switching from V2L->VOD ]`);
226
- this.eventId = scheduleObj.eventId;
227
- currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
228
- eventSegments = await session.getTruncatedVodSegments(scheduleObj.uri, scheduleObj.duration / 1000);
269
+ if (!liveUri) {
270
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->LIVE ]`);
271
+ this.working = false;
272
+ this.eventId = null;
273
+ return false;
274
+ }
229
275
 
230
- if (!eventSegments) {
231
- debug(`[${this.sessionId}]: [ ERROR Switching from V2L->VOD ]`);
232
276
  this.working = false;
233
- this.eventId = null;
234
- return false;
277
+ this.streamTypeLive = true;
278
+ debug(`[${this.sessionId}]: [ Switched from V2L->LIVE ]`);
279
+ return true;
280
+ } catch (err) {
281
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->LIVE ]`);
282
+ throw new Error(err);
235
283
  }
236
284
 
237
- await session.setCurrentMediaAndDiscSequenceCount(currVodCounts.mediaSeq, currVodCounts.discSeq);
238
- await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
285
+ case SwitcherState.V2L_TO_VOD:
286
+ try {
287
+ debug(`[${this.sessionId}]: [ INIT Switching from V2L->VOD ]`);
288
+ this.eventId = scheduleObj.eventId;
289
+ currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
290
+ eventSegments = await session.getTruncatedVodSegments(
291
+ scheduleObj.uri,
292
+ scheduleObj.duration / 1000
293
+ );
239
294
 
240
- this.working = false;
241
- debug(`[${this.sessionId}]: [ Switched from V2L->VOD ]`);
242
- return false;
243
- case SwitcherState.LIVE_TO_V2L:
244
- debug(`[${this.sessionId}]: [ INIT Switching from LIVE->V2L ]`);
245
- this.eventId = null;
246
- liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
247
- liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
295
+ if (!eventSegments) {
296
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->VOD ]`);
297
+ this.working = false;
298
+ this.eventId = null;
299
+ return false;
300
+ }
248
301
 
249
- await sessionLive.resetSession();
250
- sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
302
+ await session.setCurrentMediaAndDiscSequenceCount(
303
+ currVodCounts.mediaSeq,
304
+ currVodCounts.discSeq
305
+ );
306
+ await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
251
307
 
252
- if (scheduleObj && !scheduleObj.duration) {
253
- debug(`[${this.sessionId}]: Cannot switch VOD. No duration specified for schedule item: [${scheduleObj.assetId}]`);
308
+ this.working = false;
309
+ debug(`[${this.sessionId}]: [ Switched from V2L->VOD ]`);
310
+ return false;
311
+ } catch (err) {
312
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->VOD ]`);
313
+ throw new Error(err);
254
314
  }
315
+ case SwitcherState.LIVE_TO_V2L:
316
+ try {
317
+ debug(`[${this.sessionId}]: [ INIT Switching from LIVE->V2L ]`);
318
+ this.eventId = null;
319
+ liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
320
+ liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
321
+
322
+ await sessionLive.resetSession();
323
+ sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
324
+
325
+ if (scheduleObj && !scheduleObj.duration) {
326
+ debug(
327
+ `[${this.sessionId}]: Cannot switch VOD. No duration specified for schedule item: [${scheduleObj.assetId}]`
328
+ );
329
+ }
330
+
331
+ if (this._isEmpty(liveSegments.currMseqSegs)) {
332
+ this.working = false;
333
+ this.streamTypeLive = false;
334
+ debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
335
+ return false;
336
+ }
337
+
338
+ await session.setCurrentMediaSequenceSegments(
339
+ liveSegments.currMseqSegs,
340
+ liveSegments.segCount
341
+ );
342
+ await session.setCurrentMediaAndDiscSequenceCount(
343
+ liveCounts.mediaSeq,
344
+ liveCounts.discSeq
345
+ );
255
346
 
256
- if (this._isEmpty(liveSegments.currMseqSegs)) {
257
347
  this.working = false;
258
348
  this.streamTypeLive = false;
259
349
  debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
260
- return false;;
350
+ return false;
351
+ } catch (err) {
352
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->V2L ]`);
353
+ throw new Error(err);
261
354
  }
262
-
263
- await session.setCurrentMediaSequenceSegments(liveSegments.currMseqSegs, liveSegments.segCount);
264
- await session.setCurrentMediaAndDiscSequenceCount(liveCounts.mediaSeq, liveCounts.discSeq);
265
-
266
-
267
- this.working = false;
268
- this.streamTypeLive = false;
269
- debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
270
- return false;
271
355
  case SwitcherState.LIVE_TO_VOD:
272
- debug(`[${this.sessionId}]: INIT Switching from LIVE->VOD`);
273
- // TODO: Not yet fully tested/supported
274
- this.eventId = scheduleObj.eventId;
275
- liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
276
- liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
277
- await sessionLive.resetSession();
278
- sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
279
-
280
- eventSegments = await session.getTruncatedVodSegments(scheduleObj.uri, scheduleObj.duration / 1000);
281
- if (!eventSegments) {
282
- debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->VOD ]`);
283
- this.streamTypeLive = false;
356
+ try {
357
+ debug(`[${this.sessionId}]: INIT Switching from LIVE->VOD`);
358
+ // TODO: Not yet fully tested/supported
359
+ this.eventId = scheduleObj.eventId;
360
+ liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
361
+ liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
362
+ await sessionLive.resetSession();
363
+ sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
364
+
365
+ eventSegments = await session.getTruncatedVodSegments(
366
+ scheduleObj.uri,
367
+ scheduleObj.duration / 1000
368
+ );
369
+ if (!eventSegments) {
370
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->VOD ]`);
371
+ this.streamTypeLive = false;
372
+ this.working = false;
373
+ this.eventId = null;
374
+ return false;
375
+ }
376
+
377
+ await session.setCurrentMediaAndDiscSequenceCount(
378
+ liveCounts.mediaSeq - 1,
379
+ liveCounts.discSeq - 1
380
+ );
381
+ await session.setCurrentMediaSequenceSegments(
382
+ liveSegments.currMseqSegs,
383
+ liveSegments.segCount
384
+ );
385
+ await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
386
+
284
387
  this.working = false;
285
- this.eventId = null;
388
+ this.streamTypeLive = false;
389
+ debug(`[${this.sessionId}]: Switched from LIVE->VOD`);
286
390
  return false;
391
+ } catch (err) {
392
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->VOD ]`);
393
+ throw new Error(err);
287
394
  }
288
-
289
- await session.setCurrentMediaAndDiscSequenceCount(liveCounts.mediaSeq - 1, liveCounts.discSeq - 1);
290
- await session.setCurrentMediaSequenceSegments(liveSegments.currMseqSegs, liveSegments.segCount);
291
- await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
292
-
293
- this.working = false;
294
- this.streamTypeLive = false;
295
- debug(`[${this.sessionId}]: Switched from LIVE->VOD`);
296
- return false;
297
395
  case SwitcherState.LIVE_TO_LIVE:
298
- debug(`[${this.sessionId}]: INIT Switching from LIVE->LIVE`);
299
- // TODO: Not yet fully tested/supported
300
- this.eventId = scheduleObj.eventId;
301
- eventSegments = await sessionLive.getCurrentMediaSequenceSegments();
302
- currLiveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
303
-
304
- await sessionLive.resetSession();
305
- await sessionLive.resetLiveStoreAsync(0);
396
+ try {
397
+ debug(`[${this.sessionId}]: INIT Switching from LIVE->LIVE`);
398
+ // TODO: Not yet fully tested/supported
399
+ this.eventId = scheduleObj.eventId;
400
+ eventSegments = await sessionLive.getCurrentMediaSequenceSegments();
401
+ currLiveCounts =
402
+ await sessionLive.getCurrentMediaAndDiscSequenceCount();
403
+
404
+ await sessionLive.resetSession();
405
+ await sessionLive.resetLiveStoreAsync(0);
406
+
407
+ await sessionLive.setCurrentMediaAndDiscSequenceCount(
408
+ currLiveCounts.mediaSeq,
409
+ currLiveCounts.discSeq
410
+ );
411
+ await sessionLive.setCurrentMediaSequenceSegments(
412
+ eventSegments.currMseqSegs
413
+ );
414
+ liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
306
415
 
307
- await sessionLive.setCurrentMediaAndDiscSequenceCount(currLiveCounts.mediaSeq, currLiveCounts.discSeq);
308
- await sessionLive.setCurrentMediaSequenceSegments(eventSegments.currMseqSegs);
309
- liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
416
+ if (!liveUri) {
417
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->LIVE ]`);
418
+ this.streamTypeLive = false;
419
+ this.working = false;
420
+ this.eventId = null;
421
+ return false;
422
+ }
310
423
 
311
- if (!liveUri) {
312
- debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->LIVE ]`);
313
- this.streamTypeLive = false;
314
424
  this.working = false;
315
- this.eventId = null;
316
- return false;
425
+ debug(`[${this.sessionId}]: Switched from LIVE->LIVE`);
426
+ return true;
427
+ } catch (err) {
428
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->LIVE ]`);
429
+ throw new Error(err);
317
430
  }
318
-
319
- this.working = false;
320
- debug(`[${this.sessionId}]: Switched from LIVE->LIVE`);
321
- return true;
322
431
  default:
323
432
  debug(`[${this.sessionId}]: SwitcherState [${state}] not implemented`);
324
433
  this.streamTypeLive = false;
325
434
  this.working = false;
326
435
  this.eventId = null;
327
- return false;;
436
+ return false;
328
437
  }
329
438
  }
330
439
 
@@ -352,10 +461,14 @@ class StreamSwitcher {
352
461
  if (online.status >= 200 && online.status < 300) {
353
462
  return true;
354
463
  }
355
- debug(`[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! Returned Status Code: ${online.status}`);
464
+ debug(
465
+ `[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! Returned Status Code: ${online.status}`
466
+ );
356
467
  return false;
357
468
  } catch (err) {
358
- debug(`[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! ${err}`);
469
+ debug(
470
+ `[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! ${err}`
471
+ );
359
472
  return false;
360
473
  } finally {
361
474
  clearTimeout(timeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.0.0",
3
+ "version": "3.0.4",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,7 +8,8 @@
8
8
  "test": "$(npm bin)/jasmine",
9
9
  "postversion": "git push && git push --tags",
10
10
  "start": "node server.js",
11
- "start-demux": "node server-demux.js"
11
+ "start-demux": "node server-demux.js",
12
+ "start-livemix": "node server-livemix.js"
12
13
  },
13
14
  "author": "Eyevinn Technology AB <work@eyevinn.se>",
14
15
  "contributors": [
@@ -21,8 +22,8 @@
21
22
  "dependencies": {
22
23
  "@eyevinn/hls-repeat": "^0.1.1",
23
24
  "@eyevinn/hls-truncate": "^0.1.0",
24
- "@eyevinn/hls-vodtolive": "^1.16.5",
25
- "@eyevinn/m3u8": "^0.4.0",
25
+ "@eyevinn/hls-vodtolive": "^1.17.1",
26
+ "@eyevinn/m3u8": "^0.4.1",
26
27
  "abort-controller": "^3.0.0",
27
28
  "debug": "^3.2.7",
28
29
  "memcache-client": "^0.10.1",