eyevinn-channel-engine 3.1.7 → 3.1.8

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/engine/session.js CHANGED
@@ -43,9 +43,20 @@ class Session {
43
43
  this.playheadDiffThreshold = DEFAULT_PLAYHEAD_DIFF_THRESHOLD;
44
44
  this.maxTickInterval = DEFAULT_MAX_TICK_INTERVAL;
45
45
  this.diffCompensation = null;
46
+ this.prevVodMediaSeq = {
47
+ video: null,
48
+ audio: null
49
+ }
46
50
  this.waitingForNextVod = false;
47
51
  this.leaderIsSettingNextVod = false;
48
- this.isSwitchingBackFromLive = false;
52
+ this.isSwitchingBackToV2L = false;
53
+ this.switchDataForSession = {
54
+ mediaSeq: null,
55
+ discSeq: null,
56
+ mediaSeqOffset: null,
57
+ transitionSegments: null,
58
+ reloadBehind: null,
59
+ }
49
60
 
50
61
  if (config) {
51
62
  if (config.sessionId) {
@@ -139,7 +150,14 @@ class Session {
139
150
  state = await this._playheadState.getState();
140
151
 
141
152
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
142
- if (isLeader && [SessionState.VOD_NEXT_INIT, SessionState.VOD_NEXT_INITIATING].indexOf(sessionState.state) !== -1) {
153
+ if (isLeader &&
154
+ [
155
+ SessionState.VOD_NEXT_INIT,
156
+ SessionState.VOD_NEXT_INITIATING,
157
+ SessionState.VOD_RELOAD_INIT,
158
+ SessionState.VOD_RELOAD_INITIATING
159
+ ].indexOf(sessionState.state) !== -1) {
160
+
143
161
  const firstDuration = await this._getFirstDuration(manifest);
144
162
  const tickInterval = firstDuration < 2 ? 2 : firstDuration;
145
163
  debug(`[${this._sessionId}]: I am the leader and updated tick interval to ${tickInterval} sec`);
@@ -152,13 +170,13 @@ class Session {
152
170
  } else {
153
171
  const reqTickInterval = playheadState.tickInterval;
154
172
  const timeSpentInIncrement = (tsIncrementEnd - tsIncrementBegin) / 1000;
155
- let tickInterval = (reqTickInterval) - timeSpentInIncrement;
173
+ let tickInterval = reqTickInterval - timeSpentInIncrement;
156
174
  const delta = await this._getCurrentDeltaTime();
157
175
  if (delta != 0) {
158
176
  debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval`);
159
177
  tickInterval += delta;
160
178
  }
161
- const position = await this._getCurrentPlayheadPosition() * 1000;
179
+ const position = (await this._getCurrentPlayheadPosition()) * 1000;
162
180
  const timePosition = Date.now() - playheadState.playheadRef;
163
181
  const diff = position - timePosition;
164
182
  debug(`[${this._sessionId}]: ${timePosition}:${position}:${diff > 0 ? '+' : ''}${diff}ms`);
@@ -214,7 +232,7 @@ class Session {
214
232
  if (this.disabledPlayhead) {
215
233
  return {
216
234
  sessionId: this._sessionId,
217
- }
235
+ };
218
236
  } else {
219
237
  const playheadState = await this._playheadState.getValues(["tickMs", "mediaSeq", "vodMediaSeqVideo"]);
220
238
  const state = await this._playheadState.getState();
@@ -243,11 +261,16 @@ class Session {
243
261
  await this._playheadStateStore.reset(this._sessionId);
244
262
  }
245
263
 
264
+ async getSessionState() {
265
+ const state = await this._sessionState.get("state");
266
+ return state;
267
+ }
268
+
246
269
  async getTruncatedVodSegments(vodUri, duration) {
247
270
  try {
248
271
  const hlsVod = await this._truncateSlate(null, duration, vodUri);
249
272
  let vodSegments = hlsVod.getMediaSegments();
250
- Object.keys(vodSegments).forEach(bw => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
273
+ Object.keys(vodSegments).forEach((bw) => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
251
274
  return vodSegments;
252
275
  } catch (exc) {
253
276
  debug(`[${this._sessionId}]: Failed to generate truncated VOD!`);
@@ -257,20 +280,25 @@ class Session {
257
280
 
258
281
  async setCurrentMediaSequenceSegments(segments, mSeqOffset, reloadBehind) {
259
282
  if (!this._sessionState) {
260
- throw new Error('Session not ready');
283
+ throw new Error("Session not ready");
261
284
  }
285
+ this.isSwitchingBackToV2L = true;
262
286
 
263
- this.isSwitchingBackFromLive = true;
287
+ this.switchDataForSession.reloadBehind = reloadBehind;
288
+ this.switchDataForSession.transitionSegments = segments;
289
+ this.switchDataForSession.mediaSeqOffset = mSeqOffset;
264
290
 
265
291
  let isLeader = await this._sessionStateStore.isLeader(this._instanceId);
266
292
  if (!isLeader) {
267
293
  debug(`[${this._sessionId}]: FOLLOWER: Invalidate cache to ensure having the correct VOD!`);
268
294
  await this._sessionState.clearCurrentVodCache();
295
+
269
296
  let vodReloaded = await this._sessionState.get("vodReloaded");
270
297
  let attempts = 9;
271
298
  while (!isLeader && !vodReloaded && attempts > 0) {
272
299
  debug(`[${this._sessionId}]: FOLLOWER: I arrived before LEADER. Waiting (1000ms) for LEADER to reload currentVod in store! (tries left=${attempts})`);
273
300
  await timer(1000);
301
+ await this._sessionStateStore.clearLeaderCache();
274
302
  isLeader = await this._sessionStateStore.isLeader(this._instanceId);
275
303
  vodReloaded = await this._sessionState.get("vodReloaded");
276
304
  attempts--;
@@ -282,43 +310,26 @@ class Session {
282
310
  if (!isLeader || vodReloaded) {
283
311
  debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod. Clearing the cache now`);
284
312
  await this._sessionState.clearCurrentVodCache();
285
- this.isSwitchingBackFromLive = false;
286
313
  return;
287
314
  }
288
- }
315
+ debug(`[${this._sessionId}]: NEW LEADER: Setting state=VOD_RELOAD_INIT`);
316
+ this.isSwitchingBackToV2L = true;
317
+ await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT)
289
318
 
290
- if (isLeader) {
291
- let state = await this._sessionState.get("state");
292
- let ATTEMPTS = 5;
293
- // Do Not want to reload when session is loading next Vod
294
- while (state !== SessionState.VOD_PLAYING && ATTEMPTS > 0) {
295
- debug(`[${this._sessionId}]: LEADER: Wait for session to initialize next VOD. (tries left=${ATTEMPTS})`);
319
+ } else {
320
+ let vodReloaded = await this._sessionState.get("vodReloaded");
321
+ let attempts = 9;
322
+ while (!vodReloaded && attempts > 0) {
323
+ debug(`[${this._sessionId}]: LEADER: Waiting (1000ms) to buy some time reloading vod and adding it to store! (tries left=${attempts})`);
296
324
  await timer(1000);
297
- state = await this._sessionState.get("state");
298
- ATTEMPTS--;
299
- }
300
- if (ATTEMPTS === 0) {
301
- throw new Error("Failed to reload currentVod! Need a new LEADER!");
325
+ vodReloaded = await this._sessionState.get("vodReloaded");
326
+ attempts--;
302
327
  }
303
-
304
- debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
305
- const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
306
- let currentVod = await this._sessionState.getCurrentVod();
307
- let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
308
- if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
309
- currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
328
+ if (attempts === 0) {
329
+ debug(`[${this._sessionId}]: LEADER: WARNING! Vod was never Reloaded!`);
330
+ return;
310
331
  }
311
- // TODO: Support reloading with audioSegments as well
312
- await currentVod.reload(currentMseq, segments, null, reloadBehind);
313
- await this._sessionState.setCurrentVod(currentVod, { ttl: (currentVod.getDuration() * 1000) });
314
- await this._sessionState.set("vodReloaded", 1);
315
- await this._sessionState.set("vodMediaSeqVideo", 0);
316
- await this._sessionState.set("vodMediaSeqAudio", 0);
317
- await this._playheadState.set("vodMediaSeqVideo", 0);
318
- await this._playheadState.set("vodMediaSeqAudio", 0);
319
- await this._playheadState.set("playheadRef", Date.now());
320
332
  }
321
- this.isSwitchingBackFromLive = false;
322
333
  }
323
334
 
324
335
  async getCurrentMediaSequenceSegments() {
@@ -329,6 +340,18 @@ class Session {
329
340
  if (isLeader) {
330
341
  await this._sessionState.set("vodReloaded", 0);
331
342
  }
343
+
344
+ // Only read data from store if state is VOD_PLAYING
345
+ let state = await this.getSessionState();
346
+ let tries = 12;
347
+ while (state !== SessionState.VOD_PLAYING && tries > 0) {
348
+ const waitTimeMs = 500;
349
+ debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
350
+ await timer(waitTimeMs);
351
+ tries--;
352
+ state = await this.getSessionState();
353
+ }
354
+
332
355
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
333
356
  if (playheadState.vodMediaSeqVideo === 0) {
334
357
  if (!isLeader) {
@@ -340,7 +363,7 @@ class Session {
340
363
  if (currentVod) {
341
364
  try {
342
365
  const mediaSegments = currentVod.getLiveMediaSequenceSegments(playheadState.vodMediaSeqVideo);
343
- debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}`);
366
+ debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}_${currentVod.getLiveMediaSequencesCount()}`);
344
367
  return mediaSegments;
345
368
  } catch (err) {
346
369
  logerror(this._sessionId, err);
@@ -354,25 +377,31 @@ class Session {
354
377
 
355
378
  async setCurrentMediaAndDiscSequenceCount(_mediaSeq, _discSeq) {
356
379
  if (!this._sessionState) {
357
- throw new Error('Session not ready');
380
+ throw new Error("Session not ready");
358
381
  }
359
-
360
- this.isSwitchingBackFromLive = true;
361
382
 
362
- const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
363
- if (isLeader) {
364
- await this._sessionState.set("mediaSeq", _mediaSeq);
365
- await this._playheadState.set("mediaSeq", _mediaSeq);
366
- await this._sessionState.set("discSeq", _discSeq);
367
- debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${_mediaSeq}]:[${_discSeq}]`);
368
- }
369
- this.isSwitchingBackFromLive = false;
383
+ this.isSwitchingBackToV2L = true;
384
+
385
+ this.switchDataForSession.mediaSeq = _mediaSeq;
386
+ this.switchDataForSession.discSeq = _discSeq;
370
387
  }
371
388
 
372
389
  async getCurrentMediaAndDiscSequenceCount() {
373
390
  if (!this._sessionState) {
374
391
  throw new Error('Session not ready');
375
392
  }
393
+
394
+ // Only read data from store if state is VOD_PLAYING
395
+ let state = await this.getSessionState();
396
+ let tries = 12;
397
+ while (state !== SessionState.VOD_PLAYING && tries > 0) {
398
+ const waitTimeMs = 500;
399
+ debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
400
+ await timer(waitTimeMs);
401
+ tries--;
402
+ state = await this.getSessionState();
403
+ }
404
+
376
405
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
377
406
  const discSeqOffset = await this._sessionState.get("discSeq");
378
407
  if (playheadState.vodMediaSeqVideo === 0) {
@@ -407,8 +436,8 @@ class Session {
407
436
  }
408
437
  // Be sure that the leader is not in the middle of setting new vod data in store.
409
438
  // Followers will never run this part...
410
- let tries = 4;
411
- while(tries > 0 && this.leaderIsSettingNextVod) {
439
+ let tries = 12;
440
+ while (tries > 0 && this.leaderIsSettingNextVod) {
412
441
  debug(`[${this._sessionId}]: Leader is setting the next vod. Waiting 500ms_${tries}`);
413
442
  await timer(500);
414
443
  tries--;
@@ -417,17 +446,23 @@ class Session {
417
446
  const sessionState = await this._sessionState.getValues(["vodMediaSeqVideo", "discSeq"]);
418
447
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
419
448
 
449
+ if (this.prevVodMediaSeq.video !== null) {
450
+ this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
451
+ }
452
+
420
453
  if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
421
454
  debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
422
455
  const m3u8 = await this._playheadState.getLastM3u8();
423
456
  if (m3u8) {
457
+ this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
424
458
  return m3u8;
425
459
  } else {
426
460
  debug(`[${this._sessionId}]: We don't have any previously generated m3u8`);
427
461
  }
428
462
  }
429
463
 
430
- if (playheadState.vodMediaSeqVideo < 2) {
464
+ if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
465
+ debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
431
466
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
432
467
  if (!isLeader) {
433
468
  debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
@@ -441,6 +476,7 @@ class Session {
441
476
  debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
442
477
  const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
443
478
  debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
479
+ this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
444
480
  return m3u8;
445
481
  } catch (err) {
446
482
  logerror(this._sessionId, err);
@@ -482,13 +518,6 @@ class Session {
482
518
  async incrementAsync() {
483
519
  await this._tickAsync();
484
520
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
485
- // Avoid reading old values if new ones are on their way...
486
- let tries = 10;
487
- while (tries > 0 && this.isSwitchingBackFromLive) {
488
- debug(`[${this._sessionId}]: Leader is currently setting new data in store. Waiting 500ms_${tries}`);
489
- await timer(500);
490
- tries--;
491
- }
492
521
 
493
522
  let sessionState = await this._sessionState.getValues(
494
523
  ["state", "mediaSeq", "discSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
@@ -502,15 +531,17 @@ class Session {
502
531
  sessionState.discSeq === null) {
503
532
  debug(`[${this._sessionId}]: Session is not ready yet`);
504
533
  debug(sessionState);
534
+ await this._sessionState.clearCurrentVodCache();
505
535
  if (isLeader) {
506
536
  debug(`[${this._sessionId}]: I am the leader, trying to initiate the session`);
507
537
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_INIT);
508
538
  }
509
539
  return null;
510
540
  }
511
- if (sessionState.state === SessionState.VOD_NEXT_INITIATING) {
541
+ if (sessionState.state === SessionState.VOD_NEXT_INITIATING || sessionState.state === SessionState.VOD_RELOAD_INITIATING) {
512
542
  if (isLeader) {
513
- debug(`[${this._sessionId}]: I am the leader and have just initiated next VOD, let's move to VOD_PLAYING`);
543
+ const leaderAction = sessionState.state === SessionState.VOD_NEXT_INITIATING ? "initiated" : "reloaded";
544
+ debug(`[${this._sessionId}]: I am the leader and have just ${leaderAction} next VOD, let's move to VOD_PLAYING`);
514
545
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
515
546
  } else {
516
547
  debug(`[${this._sessionId}]: Return the last generated m3u8 to give the leader some time`);
@@ -532,6 +563,11 @@ class Session {
532
563
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INIT);
533
564
  }
534
565
 
566
+ if (this.isSwitchingBackToV2L) {
567
+ sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT);
568
+ this.isSwitchingBackToV2L = false;
569
+ }
570
+
535
571
  playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
536
572
  playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
537
573
  playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
@@ -809,6 +845,10 @@ class Session {
809
845
  case SessionState.VOD_INIT:
810
846
  case SessionState.VOD_INIT_BY_ID:
811
847
  try {
848
+ // Needed if store was reset
849
+ await this._sessionStateStore.clearLeaderCache();
850
+ isLeader = await this._sessionStateStore.isLeader(this._instanceId);
851
+
812
852
  let nextVodPromise;
813
853
  if (sessionState.state === SessionState.VOD_INIT) {
814
854
  debug(`[${this._sessionId}]: state=VOD_INIT`);
@@ -898,6 +938,17 @@ class Session {
898
938
  currentVod = await this._sessionState.getCurrentVod();
899
939
  this.waitingForNextVod = false;
900
940
  }
941
+ } else {
942
+ // Handle edge case where store has been reset, but leader has not cleared cache.
943
+ if (this.prevVodMediaSeq.video !== null) {
944
+ this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
945
+ }
946
+ if (this.prevVodMediaSeq.video < sessionState.vodMediaSeqVideo) {
947
+ debug(`[${this._sessionId}]: current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
948
+ await this._sessionState.clearCurrentVodCache();
949
+ currentVod = await this._sessionState.getCurrentVod();
950
+ this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
951
+ }
901
952
  }
902
953
  debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
903
954
  return;
@@ -1014,6 +1065,77 @@ class Session {
1014
1065
  }
1015
1066
  }
1016
1067
  break;
1068
+ case SessionState.VOD_RELOAD_INIT:
1069
+ try {
1070
+ debug(`[${this._sessionId}]: state=VOD_RELOAD_INIT`);
1071
+ if (isLeader) {
1072
+
1073
+ const startTS = Date.now();
1074
+
1075
+ // 1) To tell Follower that, Leader is working on it!
1076
+ sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
1077
+
1078
+ // 2) Set new 'offset' sequences, to carry on the continuity from session-live
1079
+ const mSeq = this.switchDataForSession.mediaSeq;
1080
+ const dSeq = this.switchDataForSession.discSeq;
1081
+ const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
1082
+ const reloadBehind = this.switchDataForSession.reloadBehind;
1083
+ const segments = this.switchDataForSession.transitionSegments;
1084
+
1085
+ if ([mSeq,dSeq,mSeqOffset,reloadBehind,segments].includes(null)) {
1086
+ debug(`[${this._sessionId}]: LEADER: Cannot Reload VOD, missing switch-back data`);
1087
+ return;
1088
+ }
1089
+
1090
+ await this._sessionState.set("mediaSeq", mSeq);
1091
+ await this._playheadState.set("mediaSeq", mSeq);
1092
+ await this._sessionState.set("discSeq", dSeq);
1093
+ debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
1094
+
1095
+ // 3) Set new media segments/currentVod, to carry on the continuity from session-live
1096
+ debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
1097
+ const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
1098
+ let currentVod = await this._sessionState.getCurrentVod();
1099
+ let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
1100
+ if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
1101
+ currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
1102
+ }
1103
+
1104
+ // TODO: Support reloading with audioSegments as well
1105
+
1106
+ await currentVod.reload(currentMseq, segments, null, reloadBehind);
1107
+ await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1108
+ await this._sessionState.set("vodReloaded", 1);
1109
+ await this._sessionState.set("vodMediaSeqVideo", 0);
1110
+ await this._sessionState.set("vodMediaSeqAudio", 0);
1111
+ await this._playheadState.set("vodMediaSeqVideo", 0);
1112
+ await this._playheadState.set("vodMediaSeqAudio", 0);
1113
+ await this._playheadState.set("playheadRef", Date.now());
1114
+ debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
1115
+ // 4) emit cloudwatch event object
1116
+ cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "switchback", channel: this._sessionId, reqTimeMs: Date.now() - startTS });
1117
+
1118
+ return;
1119
+ } else {
1120
+ debug(`[${this._sessionId}]: not a leader so will go directly to state VOD_RELOAD_INITIATING`);
1121
+ sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
1122
+ }
1123
+ } catch (err) {
1124
+ debug("Failed to init reload vod");
1125
+ throw err;
1126
+ }
1127
+ break;
1128
+ case SessionState.VOD_RELOAD_INITIATING:
1129
+ debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
1130
+ if (!isLeader) {
1131
+ debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be reloaded`);
1132
+ if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
1133
+ debug(`[${this._sessionId}]: First mediasequence in VOD and I am not the leader so invalidate current VOD cache and fetch the new one from the leader`);
1134
+ await this._sessionState.clearCurrentVodCache();
1135
+ }
1136
+ this.waitingForNextVod = true;
1137
+ }
1138
+ return;
1017
1139
  default:
1018
1140
  throw new Error("Invalid state: " + sessionState.state);
1019
1141
  }
@@ -48,6 +48,7 @@ class SessionLive {
48
48
  this.firstTime = true;
49
49
  this.allowedToSet = false;
50
50
  this.pushAmount = 0;
51
+ this.restAmount = 0;
51
52
  this.waitForPlayhead = true;
52
53
  this.blockGenerateManifest = false;
53
54
 
@@ -502,7 +503,7 @@ class SessionLive {
502
503
  // Do not push duplicates
503
504
  const liveSegURIs = this.liveSegQueue[liveBw].filter(seg => seg.uri).map(seg => seg.uri);
504
505
  if (liveSegFromLeader.uri && liveSegURIs.includes(liveSegFromLeader.uri)) {
505
- debug(`[${this.sessionId}]: FOLLOWER: Found duplicate live segment. Skip push!`);
506
+ debug(`[${this.sessionId}]: FOLLOWER: Found duplicate live segment. Skip push! (${liveBw})`);
506
507
  } else {
507
508
  this.liveSegQueue[liveBw].push(liveSegFromLeader);
508
509
  debug(`[${this.sessionId}]: FOLLOWER: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue' (${liveBw})`);
@@ -671,28 +672,40 @@ class SessionLive {
671
672
  let leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
672
673
  let tries = 20;
673
674
 
674
- while ((!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
675
+ while (!isLeader && (!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
675
676
  debug(`[${this.sessionId}]: NEW FOLLOWER: Waiting for LEADER to add 'firstCounts' in store! Will look again after 1000ms (tries left=${tries})`);
676
677
  await timer(1000);
677
678
  leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
678
679
  tries--;
680
+ // Might take over as Leader if Leader is not setting data due to being down.
681
+ isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
682
+ if (isLeader) {
683
+ debug(`[${this.sessionId}][${this.instanceId}]: I'm the new leader, and now I am going to add 'firstCounts' in store`);
684
+ }
679
685
  }
680
686
 
681
687
  if (tries === 0) {
682
688
  isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
683
689
  if (isLeader) {
684
- debug(`[${this.instanceId}]: I'm the new leader, and now I am going to add 'firstCounts' in store`);
690
+ debug(`[${this.sessionId}][${this.instanceId}]: I'm the new leader, and now I am going to add 'firstCounts' in store`);
685
691
  break;
686
692
  } else {
687
- debug(`[${this.instanceId}]: The leader is still alive`);
693
+ debug(`[${this.sessionId}][${this.instanceId}]: The leader is still alive`);
688
694
  leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
689
695
  if (!leadersFirstSeqCounts.liveSourceMseqCount) {
690
- debug(`[${this.instanceId}]: Could not find 'firstCounts' in store. Abort Executing Promises II & Returning to Playhead.`);
696
+ debug(`[${this.sessionId}][${this.instanceId}]: Could not find 'firstCounts' in store. Abort Executing Promises II & Returning to Playhead.`);
691
697
  return;
692
698
  }
693
699
  }
694
700
  }
695
701
 
702
+ if (isLeader) {
703
+ debug(`[${this.sessionId}]: NEW LEADER: Original Leader went missing, I am retrying live source fetch...`);
704
+ await this.sessionLiveState.set("transitSegs", this.vodSegments);
705
+ debug(`[${this.sessionId}]: NEW LEADER: I am adding 'transitSegs' to Store for future followers`);
706
+ continue;
707
+ }
708
+
696
709
  // Respawners never do this, only starter followers.
697
710
  // Edge Case: FOLLOWER transitioned from session with different segments from LEADER
698
711
  if (leadersFirstSeqCounts.discSeqCount !== this.discSeqCount) {
@@ -713,6 +726,10 @@ class SessionLive {
713
726
  if (currentMseqRaw === leadersFirstSeqCounts.liveSourceMseqCount) {
714
727
  this.pushAmount = 1; // Follower from start
715
728
  } else {
729
+
730
+ // TODO: To support and account for past discontinuity tags in the Live Source stream,
731
+ // we will need to get the real 'current' discontinuity-sequence count from Leader somehow.
732
+
716
733
  // RESPAWNED NODES
717
734
  this.pushAmount = currentMseqRaw - leadersFirstSeqCounts.liveSourceMseqCount + 1;
718
735
 
@@ -772,7 +789,7 @@ class SessionLive {
772
789
  for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
773
790
  let bw = Object.keys(this.mediaManifestURIs)[i];
774
791
  // will add new segments to live seg queue
775
- pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw));
792
+ pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw, isLeader));
776
793
  debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
777
794
  }
778
795
  // Segment Pushing
@@ -961,6 +978,12 @@ class SessionLive {
961
978
  // Update Session Live Media Sequence Count...
962
979
  this.prevMediaSeqCount = this.mediaSeqCount;
963
980
  this.mediaSeqCount += removedSegments;
981
+ if (this.restAmount) {
982
+ this.mediaSeqCount += this.restAmount;
983
+ debug(`[${this.sessionId}]: ${instanceName}: Added restAmount=[${this.restAmount}] to 'mediaSeqCount'`);
984
+ this.restAmount = 0;
985
+ }
986
+
964
987
  if (this.discSeqCount !== this.prevDiscSeqCount) {
965
988
  debug(`[${this.sessionId}]: ${instanceName}: Incrementing Dseq Count from {${this.prevDiscSeqCount}} -> {${this.discSeqCount}}`);
966
989
  }
@@ -1015,7 +1038,7 @@ class SessionLive {
1015
1038
  });
1016
1039
  }
1017
1040
 
1018
- _parseMediaManifest(m3u, mediaManifestUri, liveTargetBandwidth) {
1041
+ _parseMediaManifest(m3u, mediaManifestUri, liveTargetBandwidth, isLeader) {
1019
1042
  return new Promise(async (resolve, reject) => {
1020
1043
  try {
1021
1044
  if (!this.liveSegQueue[liveTargetBandwidth]) {
@@ -1038,10 +1061,13 @@ class SessionLive {
1038
1061
  }
1039
1062
  this.targetDuration = m3u.get("targetDuration");
1040
1063
  let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
1041
- startIdx = startIdx < 0 ? 0 : startIdx;
1064
+ if (startIdx < 0) {
1065
+ this.restAmount = startIdx * -1;
1066
+ startIdx = 0;
1067
+ }
1042
1068
  if (mediaManifestUri) {
1043
1069
  // push segments
1044
- this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
1070
+ this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth, isLeader);
1045
1071
  }
1046
1072
  resolve();
1047
1073
  } catch (exc) {
@@ -1059,7 +1085,9 @@ class SessionLive {
1059
1085
  * @param {string} baseUrl
1060
1086
  * @param {string} liveTargetBandwidth
1061
1087
  */
1062
- _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
1088
+ _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth, isLeader) {
1089
+ const leaderOrFollower = isLeader ? "LEADER": "NEW FOLLOWER";
1090
+
1063
1091
  for (let i = startIdx; i < playlistItems.length; i++) {
1064
1092
  let seg = {};
1065
1093
  let playlistItem = playlistItems[i];
@@ -1127,10 +1155,15 @@ class SessionLive {
1127
1155
  if (daterangeData) {
1128
1156
  seg["daterange"] = daterangeData;
1129
1157
  }
1130
- // Push new Live Segments!
1131
- debug(`[${this.sessionId}]: Adding Live Segment to Queue (for bw=${liveTargetBandwidth})\n${seg.uri ? seg.uri : ''}`);
1132
- this.liveSegQueue[liveTargetBandwidth].push(seg);
1133
- this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
1158
+ // Push new Live Segments! But do not push duplicates
1159
+ const liveSegURIs = this.liveSegQueue[liveTargetBandwidth].filter(seg => seg.uri).map(seg => seg.uri);
1160
+ if (seg.uri && liveSegURIs.includes(seg.uri)) {
1161
+ debug(`[${this.sessionId}]: ${leaderOrFollower}: Found duplicate live segment. Skip push! (${liveTargetBandwidth})`);
1162
+ } else {
1163
+ this.liveSegQueue[liveTargetBandwidth].push(seg);
1164
+ this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
1165
+ debug(`[${this.sessionId}]: ${leaderOrFollower}: Pushed segment (${seg.uri ? seg.uri : "Disc-tag"}) to 'liveSegQueue' (${liveTargetBandwidth})`);
1166
+ }
1134
1167
  }
1135
1168
  }
1136
1169
  }
@@ -7,6 +7,8 @@ const SessionState = Object.freeze({
7
7
  VOD_PLAYING: 2,
8
8
  VOD_NEXT_INIT: 3,
9
9
  VOD_NEXT_INITIATING: 4,
10
+ VOD_RELOAD_INIT: 5,
11
+ VOD_RELOAD_INITIATING: 6
10
12
  });
11
13
 
12
14
  const CURRENTVOD_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
@@ -157,6 +159,11 @@ class SessionStateStore extends SharedStateStore {
157
159
  await this.setVolatile("", instanceId, t);
158
160
  }
159
161
 
162
+ async clearLeaderCache() {
163
+ debug(`[${this.instanceId}]: clearing 'leader' cache`);
164
+ this.cache.leader.value = null;
165
+ }
166
+
160
167
  async isLeader(instanceId) {
161
168
  if (!instanceId) {
162
169
  throw new Error("Cannot determine leader without instance id");
@@ -164,7 +171,7 @@ class SessionStateStore extends SharedStateStore {
164
171
  let leader;
165
172
  if (this.cache.leader.value && Date.now() < this.cache.leader.ts + this.cacheTTL) {
166
173
  leader = this.cache.leader.value;
167
- debug(`[${instanceId}]: reading 'leader' from cache: I am ${leader === instanceId ? "" : "NOT"} the leader!`);
174
+ debug(`[${instanceId}]: reading 'leader' from cache: I am${leader === instanceId ? "" : " NOT"} the leader!`);
168
175
  return leader === instanceId;
169
176
  }
170
177
  leader = await this.get("", "leader");
@@ -189,7 +196,7 @@ class SessionStateStore extends SharedStateStore {
189
196
  }
190
197
  }
191
198
  }
192
- debug(`[${instanceId}]: I am ${leader === instanceId ? "" : "NOT"} the leader!`);
199
+ debug(`[${instanceId}]: I am${leader === instanceId ? "" : " NOT"} the leader!`);
193
200
  this.cache.leader.ts = Date.now();
194
201
  this.cache.leader.value = leader;
195
202
  return leader === instanceId;
@@ -2,6 +2,7 @@ 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");
5
+ const { SessionState } = require('./session_state');
5
6
 
6
7
  const SwitcherState = Object.freeze({
7
8
  V2L_TO_LIVE: 1,
@@ -88,8 +89,8 @@ class StreamSwitcher {
88
89
  return null;
89
90
  }
90
91
  // Handle Complete Storage Reset
91
- let sessionState = await session._sessionState.get("state");
92
- if (sessionState === 1 || !sessionState) {
92
+ let sessionState = await session.getSessionState();
93
+ if (sessionState === SessionState.VOD_INIT || !sessionState) {
93
94
  this.working = true;
94
95
  sessionLive.waitForPlayhead = false;
95
96
  sessionLive.allowedToSet = false; // only have effect on leader
@@ -341,14 +342,14 @@ class StreamSwitcher {
341
342
  return false;
342
343
  }
343
344
 
344
- await session.setCurrentMediaSequenceSegments(
345
- liveSegments.currMseqSegs,
346
- liveSegments.segCount
347
- );
348
345
  await session.setCurrentMediaAndDiscSequenceCount(
349
346
  liveCounts.mediaSeq,
350
347
  liveCounts.discSeq
351
348
  );
349
+ await session.setCurrentMediaSequenceSegments(
350
+ liveSegments.currMseqSegs,
351
+ liveSegments.segCount
352
+ );
352
353
 
353
354
  this.working = false;
354
355
  this.streamTypeLive = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.1.7",
3
+ "version": "3.1.8",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {