eyevinn-channel-engine 3.1.7 → 3.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/engine/server.js CHANGED
@@ -12,7 +12,7 @@ const { SessionStateStore } = require('./session_state.js');
12
12
  const { SessionLiveStateStore } = require('./session_live_state.js');
13
13
  const { PlayheadStateStore } = require('./playhead_state.js');
14
14
 
15
- const { filterQueryParser, toHHMMSS } = require('./util.js');
15
+ const { filterQueryParser, toHHMMSS, WaitTimeGenerator } = require('./util.js');
16
16
  const { version } = require('../package.json');
17
17
 
18
18
  const timer = ms => new Promise(res => setTimeout(res, ms));
@@ -166,14 +166,17 @@ class ChannelEngine {
166
166
  }, 3000);
167
167
 
168
168
  const StreamSwitchLoop = async (timeIntervalMs) => {
169
+ const minIntervalMs = 50;
170
+ const WTG = new WaitTimeGenerator(timeIntervalMs, minIntervalMs);
169
171
  while(true) {
170
172
  try {
171
173
  const ts_1 = Date.now();
172
174
  await this.updateStreamSwitchAsync()
173
175
  const ts_2 = Date.now();
174
- let interval = (timeIntervalMs - (ts_2 - ts_1)) < 0 ? 50 : (timeIntervalMs - (ts_2 - ts_1));
175
- await timer(interval)
176
- debug(`StreamSwitchLoop waited for all channels. Next tick in: ${interval}ms`)
176
+ const interval = (timeIntervalMs - (ts_2 - ts_1)) < 0 ? minIntervalMs : (timeIntervalMs - (ts_2 - ts_1));
177
+ const tickInterval = await WTG.getWaitTime(interval);
178
+ await timer(tickInterval)
179
+ debug(`StreamSwitchLoop waited for all channels. Next tick in: ${tickInterval}ms`)
177
180
  } catch (err) {
178
181
  console.error(err)
179
182
  debug(`StreamSwitchLoop iteration failed. Trying Again in 1000ms!`);
package/engine/session.js CHANGED
@@ -43,9 +43,21 @@ 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
+ }
60
+ this.alreadyClearedVodCache = null;
49
61
 
50
62
  if (config) {
51
63
  if (config.sessionId) {
@@ -139,7 +151,14 @@ class Session {
139
151
  state = await this._playheadState.getState();
140
152
 
141
153
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
142
- if (isLeader && [SessionState.VOD_NEXT_INIT, SessionState.VOD_NEXT_INITIATING].indexOf(sessionState.state) !== -1) {
154
+ if (isLeader &&
155
+ [
156
+ SessionState.VOD_NEXT_INIT,
157
+ SessionState.VOD_NEXT_INITIATING,
158
+ SessionState.VOD_RELOAD_INIT,
159
+ SessionState.VOD_RELOAD_INITIATING
160
+ ].indexOf(sessionState.state) !== -1) {
161
+
143
162
  const firstDuration = await this._getFirstDuration(manifest);
144
163
  const tickInterval = firstDuration < 2 ? 2 : firstDuration;
145
164
  debug(`[${this._sessionId}]: I am the leader and updated tick interval to ${tickInterval} sec`);
@@ -152,13 +171,13 @@ class Session {
152
171
  } else {
153
172
  const reqTickInterval = playheadState.tickInterval;
154
173
  const timeSpentInIncrement = (tsIncrementEnd - tsIncrementBegin) / 1000;
155
- let tickInterval = (reqTickInterval) - timeSpentInIncrement;
174
+ let tickInterval = reqTickInterval - timeSpentInIncrement;
156
175
  const delta = await this._getCurrentDeltaTime();
157
176
  if (delta != 0) {
158
177
  debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval`);
159
178
  tickInterval += delta;
160
179
  }
161
- const position = await this._getCurrentPlayheadPosition() * 1000;
180
+ const position = (await this._getCurrentPlayheadPosition()) * 1000;
162
181
  const timePosition = Date.now() - playheadState.playheadRef;
163
182
  const diff = position - timePosition;
164
183
  debug(`[${this._sessionId}]: ${timePosition}:${position}:${diff > 0 ? '+' : ''}${diff}ms`);
@@ -214,7 +233,7 @@ class Session {
214
233
  if (this.disabledPlayhead) {
215
234
  return {
216
235
  sessionId: this._sessionId,
217
- }
236
+ };
218
237
  } else {
219
238
  const playheadState = await this._playheadState.getValues(["tickMs", "mediaSeq", "vodMediaSeqVideo"]);
220
239
  const state = await this._playheadState.getState();
@@ -243,11 +262,16 @@ class Session {
243
262
  await this._playheadStateStore.reset(this._sessionId);
244
263
  }
245
264
 
265
+ async getSessionState() {
266
+ const state = await this._sessionState.get("state");
267
+ return state;
268
+ }
269
+
246
270
  async getTruncatedVodSegments(vodUri, duration) {
247
271
  try {
248
272
  const hlsVod = await this._truncateSlate(null, duration, vodUri);
249
273
  let vodSegments = hlsVod.getMediaSegments();
250
- Object.keys(vodSegments).forEach(bw => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
274
+ Object.keys(vodSegments).forEach((bw) => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
251
275
  return vodSegments;
252
276
  } catch (exc) {
253
277
  debug(`[${this._sessionId}]: Failed to generate truncated VOD!`);
@@ -257,20 +281,25 @@ class Session {
257
281
 
258
282
  async setCurrentMediaSequenceSegments(segments, mSeqOffset, reloadBehind) {
259
283
  if (!this._sessionState) {
260
- throw new Error('Session not ready');
284
+ throw new Error("Session not ready");
261
285
  }
286
+ this.isSwitchingBackToV2L = true;
262
287
 
263
- this.isSwitchingBackFromLive = true;
288
+ this.switchDataForSession.reloadBehind = reloadBehind;
289
+ this.switchDataForSession.transitionSegments = segments;
290
+ this.switchDataForSession.mediaSeqOffset = mSeqOffset;
264
291
 
265
292
  let isLeader = await this._sessionStateStore.isLeader(this._instanceId);
266
293
  if (!isLeader) {
267
294
  debug(`[${this._sessionId}]: FOLLOWER: Invalidate cache to ensure having the correct VOD!`);
268
295
  await this._sessionState.clearCurrentVodCache();
296
+
269
297
  let vodReloaded = await this._sessionState.get("vodReloaded");
270
298
  let attempts = 9;
271
299
  while (!isLeader && !vodReloaded && attempts > 0) {
272
300
  debug(`[${this._sessionId}]: FOLLOWER: I arrived before LEADER. Waiting (1000ms) for LEADER to reload currentVod in store! (tries left=${attempts})`);
273
301
  await timer(1000);
302
+ await this._sessionStateStore.clearLeaderCache();
274
303
  isLeader = await this._sessionStateStore.isLeader(this._instanceId);
275
304
  vodReloaded = await this._sessionState.get("vodReloaded");
276
305
  attempts--;
@@ -282,43 +311,26 @@ class Session {
282
311
  if (!isLeader || vodReloaded) {
283
312
  debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod. Clearing the cache now`);
284
313
  await this._sessionState.clearCurrentVodCache();
285
- this.isSwitchingBackFromLive = false;
286
314
  return;
287
315
  }
288
- }
316
+ debug(`[${this._sessionId}]: NEW LEADER: Setting state=VOD_RELOAD_INIT`);
317
+ this.isSwitchingBackToV2L = true;
318
+ await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT)
289
319
 
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})`);
320
+ } else {
321
+ let vodReloaded = await this._sessionState.get("vodReloaded");
322
+ let attempts = 9;
323
+ while (!vodReloaded && attempts > 0) {
324
+ debug(`[${this._sessionId}]: LEADER: Waiting (1000ms) to buy some time reloading vod and adding it to store! (tries left=${attempts})`);
296
325
  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!");
326
+ vodReloaded = await this._sessionState.get("vodReloaded");
327
+ attempts--;
302
328
  }
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;
329
+ if (attempts === 0) {
330
+ debug(`[${this._sessionId}]: LEADER: WARNING! Vod was never Reloaded!`);
331
+ return;
310
332
  }
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
333
  }
321
- this.isSwitchingBackFromLive = false;
322
334
  }
323
335
 
324
336
  async getCurrentMediaSequenceSegments() {
@@ -329,18 +341,27 @@ class Session {
329
341
  if (isLeader) {
330
342
  await this._sessionState.set("vodReloaded", 0);
331
343
  }
332
- const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
333
- if (playheadState.vodMediaSeqVideo === 0) {
334
- if (!isLeader) {
335
- debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
336
- await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
337
- }
344
+
345
+ // Only read data from store if state is VOD_PLAYING
346
+ let state = await this.getSessionState();
347
+ let tries = 12;
348
+ while (state !== SessionState.VOD_PLAYING && tries > 0) {
349
+ const waitTimeMs = 500;
350
+ debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
351
+ await timer(waitTimeMs);
352
+ tries--;
353
+ state = await this.getSessionState();
338
354
  }
355
+
356
+ const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
357
+
358
+ // NOTE: Assume that VOD cache was already cleared in 'getCurrentMediaAndDiscSequenceCount()'
359
+ // and that we now have access to the correct vod cache
339
360
  const currentVod = await this._sessionState.getCurrentVod();
340
361
  if (currentVod) {
341
362
  try {
342
363
  const mediaSegments = currentVod.getLiveMediaSequenceSegments(playheadState.vodMediaSeqVideo);
343
- debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}`);
364
+ debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}_${currentVod.getLiveMediaSequencesCount()}`);
344
365
  return mediaSegments;
345
366
  } catch (err) {
346
367
  logerror(this._sessionId, err);
@@ -354,31 +375,38 @@ class Session {
354
375
 
355
376
  async setCurrentMediaAndDiscSequenceCount(_mediaSeq, _discSeq) {
356
377
  if (!this._sessionState) {
357
- throw new Error('Session not ready');
378
+ throw new Error("Session not ready");
358
379
  }
359
-
360
- this.isSwitchingBackFromLive = true;
361
380
 
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;
381
+ this.isSwitchingBackToV2L = true;
382
+
383
+ this.switchDataForSession.mediaSeq = _mediaSeq;
384
+ this.switchDataForSession.discSeq = _discSeq;
370
385
  }
371
386
 
372
387
  async getCurrentMediaAndDiscSequenceCount() {
373
388
  if (!this._sessionState) {
374
389
  throw new Error('Session not ready');
375
390
  }
391
+
392
+ // Only read data from store if state is VOD_PLAYING
393
+ let state = await this.getSessionState();
394
+ let tries = 12;
395
+ while (state !== SessionState.VOD_PLAYING && tries > 0) {
396
+ const waitTimeMs = 500;
397
+ debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
398
+ await timer(waitTimeMs);
399
+ tries--;
400
+ state = await this.getSessionState();
401
+ }
402
+
376
403
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
377
404
  const discSeqOffset = await this._sessionState.get("discSeq");
405
+ // Clear Vod Cache here when Switching to Live just to be safe...
378
406
  if (playheadState.vodMediaSeqVideo === 0) {
379
407
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
380
408
  if (!isLeader) {
381
- debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
409
+ debug(`[${this._sessionId}]: Not a leader, about to switch, and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
382
410
  await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
383
411
  }
384
412
  }
@@ -407,8 +435,8 @@ class Session {
407
435
  }
408
436
  // Be sure that the leader is not in the middle of setting new vod data in store.
409
437
  // Followers will never run this part...
410
- let tries = 4;
411
- while(tries > 0 && this.leaderIsSettingNextVod) {
438
+ let tries = 12;
439
+ while (tries > 0 && this.leaderIsSettingNextVod) {
412
440
  debug(`[${this._sessionId}]: Leader is setting the next vod. Waiting 500ms_${tries}`);
413
441
  await timer(500);
414
442
  tries--;
@@ -417,22 +445,33 @@ class Session {
417
445
  const sessionState = await this._sessionState.getValues(["vodMediaSeqVideo", "discSeq"]);
418
446
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
419
447
 
448
+ if (this.prevVodMediaSeq.video === null) {
449
+ this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
450
+ }
451
+
420
452
  if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
421
453
  debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
422
454
  const m3u8 = await this._playheadState.getLastM3u8();
423
455
  if (m3u8) {
456
+ this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
424
457
  return m3u8;
425
458
  } else {
426
459
  debug(`[${this._sessionId}]: We don't have any previously generated m3u8`);
427
460
  }
428
461
  }
429
462
 
430
- if (playheadState.vodMediaSeqVideo < 2) {
463
+ // Force reading up from store, but only once if the condition is right
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
- if (!isLeader) {
467
+ if (!isLeader && !this.alreadyClearedVodCache) {
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.`);
434
- await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
469
+ await this._sessionState.clearCurrentVodCache();
470
+ this.alreadyClearedVodCache = true;
435
471
  }
472
+ } else {
473
+ // Reset the value
474
+ this.alreadyClearedVodCache = false;
436
475
  }
437
476
 
438
477
  const currentVod = await this._sessionState.getCurrentVod();
@@ -441,6 +480,7 @@ class Session {
441
480
  debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
442
481
  const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
443
482
  debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
483
+ this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
444
484
  return m3u8;
445
485
  } catch (err) {
446
486
  logerror(this._sessionId, err);
@@ -482,13 +522,6 @@ class Session {
482
522
  async incrementAsync() {
483
523
  await this._tickAsync();
484
524
  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
525
 
493
526
  let sessionState = await this._sessionState.getValues(
494
527
  ["state", "mediaSeq", "discSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
@@ -502,15 +535,17 @@ class Session {
502
535
  sessionState.discSeq === null) {
503
536
  debug(`[${this._sessionId}]: Session is not ready yet`);
504
537
  debug(sessionState);
538
+ await this._sessionState.clearCurrentVodCache();
505
539
  if (isLeader) {
506
540
  debug(`[${this._sessionId}]: I am the leader, trying to initiate the session`);
507
541
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_INIT);
508
542
  }
509
543
  return null;
510
544
  }
511
- if (sessionState.state === SessionState.VOD_NEXT_INITIATING) {
545
+ if (sessionState.state === SessionState.VOD_NEXT_INITIATING || sessionState.state === SessionState.VOD_RELOAD_INITIATING) {
512
546
  if (isLeader) {
513
- debug(`[${this._sessionId}]: I am the leader and have just initiated next VOD, let's move to VOD_PLAYING`);
547
+ const leaderAction = sessionState.state === SessionState.VOD_NEXT_INITIATING ? "initiated" : "reloaded";
548
+ debug(`[${this._sessionId}]: I am the leader and have just ${leaderAction} next VOD, let's move to VOD_PLAYING`);
514
549
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
515
550
  } else {
516
551
  debug(`[${this._sessionId}]: Return the last generated m3u8 to give the leader some time`);
@@ -532,6 +567,11 @@ class Session {
532
567
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INIT);
533
568
  }
534
569
 
570
+ if (this.isSwitchingBackToV2L) {
571
+ sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT);
572
+ this.isSwitchingBackToV2L = false;
573
+ }
574
+
535
575
  playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
536
576
  playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
537
577
  playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
@@ -539,9 +579,17 @@ class Session {
539
579
  let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
540
580
  await this._playheadState.setLastM3u8(m3u8);
541
581
 
542
- if (!isLeader && playheadState.vodMediaSeqVideo < 2) {
543
- debug(`[${this._sessionId}]: Not a leader and have just set playheadState vodMediaSeqVideo to 0|1. Invalidate cache to ensure having the correct VOD.`);
544
- await this._sessionState.clearCurrentVodCache();
582
+ // As a FOLLOWER, we might need to read up from shared store...
583
+ if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
584
+ debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
585
+ if (!isLeader && !this.alreadyClearedVodCache) {
586
+ debug(`[${this._sessionId}]: Not a leader and have just set 'playheadState.vodMediaSeqVideo' to 0|1. Invalidate cache to ensure having the correct VOD.`);
587
+ await this._sessionState.clearCurrentVodCache();
588
+ this.alreadyClearedVodCache = true;
589
+ }
590
+ } else {
591
+ // Reset the value
592
+ this.alreadyClearedVodCache = false;
545
593
  }
546
594
 
547
595
  return m3u8;
@@ -798,17 +846,14 @@ class Session {
798
846
  sessionState.state = SessionState.VOD_INIT;
799
847
  }
800
848
 
801
- if (!isLeader && this.waitingForNextVod) {
802
- // By now Leader should have added the next Vod in store
803
- debug(`[${this._sessionId}]: Not leader! New VOD loaded during last tick. Invalidate current VOD cache`);
804
- await this._sessionState.clearCurrentVodCache();
805
- this.waitingForNextVod = false;
806
- }
807
-
808
849
  switch (sessionState.state) {
809
850
  case SessionState.VOD_INIT:
810
851
  case SessionState.VOD_INIT_BY_ID:
811
852
  try {
853
+ // Needed if store was reset
854
+ await this._sessionStateStore.clearLeaderCache();
855
+ isLeader = await this._sessionStateStore.isLeader(this._instanceId);
856
+
812
857
  let nextVodPromise;
813
858
  if (sessionState.state === SessionState.VOD_INIT) {
814
859
  debug(`[${this._sessionId}]: state=VOD_INIT`);
@@ -891,12 +936,17 @@ class Session {
891
936
  }
892
937
  }
893
938
  case SessionState.VOD_PLAYING:
894
- if (!isLeader) {
895
- if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
896
- debug(`[${this._sessionId}]: Not leader! Invalidate current VOD cache and fetch the new one from the leader`);
939
+ if (isLeader) {
940
+ // Handle edge case where store has been reset, but leader has not cleared cache.
941
+ if (this.prevVodMediaSeq.video === null) {
942
+ this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
943
+ }
944
+ // Clear Cache if prev count is HIGHER than current...
945
+ if (sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video ) {
946
+ debug(`[${this._sessionId}]: current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
897
947
  await this._sessionState.clearCurrentVodCache();
898
948
  currentVod = await this._sessionState.getCurrentVod();
899
- this.waitingForNextVod = false;
949
+ this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
900
950
  }
901
951
  }
902
952
  debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
@@ -905,11 +955,6 @@ class Session {
905
955
  debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
906
956
  if (!isLeader) {
907
957
  debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be initiated`);
908
- if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
909
- 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`);
910
- await this._sessionState.clearCurrentVodCache();
911
- }
912
- this.waitingForNextVod = true;
913
958
  }
914
959
  return;
915
960
  case SessionState.VOD_NEXT_INIT:
@@ -1014,6 +1059,77 @@ class Session {
1014
1059
  }
1015
1060
  }
1016
1061
  break;
1062
+ case SessionState.VOD_RELOAD_INIT:
1063
+ try {
1064
+ debug(`[${this._sessionId}]: state=VOD_RELOAD_INIT`);
1065
+ if (isLeader) {
1066
+
1067
+ const startTS = Date.now();
1068
+
1069
+ // 1) To tell Follower that, Leader is working on it!
1070
+ sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
1071
+
1072
+ // 2) Set new 'offset' sequences, to carry on the continuity from session-live
1073
+ const mSeq = this.switchDataForSession.mediaSeq;
1074
+ const dSeq = this.switchDataForSession.discSeq;
1075
+ const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
1076
+ const reloadBehind = this.switchDataForSession.reloadBehind;
1077
+ const segments = this.switchDataForSession.transitionSegments;
1078
+
1079
+ if ([mSeq,dSeq,mSeqOffset,reloadBehind,segments].includes(null)) {
1080
+ debug(`[${this._sessionId}]: LEADER: Cannot Reload VOD, missing switch-back data`);
1081
+ return;
1082
+ }
1083
+
1084
+ await this._sessionState.set("mediaSeq", mSeq);
1085
+ await this._playheadState.set("mediaSeq", mSeq);
1086
+ await this._sessionState.set("discSeq", dSeq);
1087
+ debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
1088
+
1089
+ // 3) Set new media segments/currentVod, to carry on the continuity from session-live
1090
+ debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
1091
+ const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
1092
+ let currentVod = await this._sessionState.getCurrentVod();
1093
+ let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
1094
+ if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
1095
+ currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
1096
+ }
1097
+
1098
+ // TODO: Support reloading with audioSegments as well
1099
+
1100
+ await currentVod.reload(currentMseq, segments, null, reloadBehind);
1101
+ await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1102
+ await this._sessionState.set("vodReloaded", 1);
1103
+ await this._sessionState.set("vodMediaSeqVideo", 0);
1104
+ await this._sessionState.set("vodMediaSeqAudio", 0);
1105
+ await this._playheadState.set("vodMediaSeqVideo", 0);
1106
+ await this._playheadState.set("vodMediaSeqAudio", 0);
1107
+ await this._playheadState.set("playheadRef", Date.now());
1108
+ debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
1109
+ // 4) emit cloudwatch event object
1110
+ cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "switchback", channel: this._sessionId, reqTimeMs: Date.now() - startTS });
1111
+
1112
+ return;
1113
+ } else {
1114
+ debug(`[${this._sessionId}]: not a leader so will go directly to state VOD_RELOAD_INITIATING`);
1115
+ sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
1116
+ }
1117
+ } catch (err) {
1118
+ debug("Failed to init reload vod");
1119
+ throw err;
1120
+ }
1121
+ break;
1122
+ case SessionState.VOD_RELOAD_INITIATING:
1123
+ debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
1124
+ if (!isLeader) {
1125
+ debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be reloaded`);
1126
+ if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
1127
+ 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`);
1128
+ await this._sessionState.clearCurrentVodCache();
1129
+ }
1130
+ this.waitingForNextVod = true;
1131
+ }
1132
+ return;
1017
1133
  default:
1018
1134
  throw new Error("Invalid state: " + sessionState.state);
1019
1135
  }