eyevinn-channel-engine 3.3.3 → 3.3.6

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/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM node:slim
1
+ FROM node:12.16.1-alpine
2
2
 
3
3
  WORKDIR /app
4
4
 
package/engine/session.js CHANGED
@@ -566,14 +566,16 @@ class Session {
566
566
  if (currentVod) {
567
567
  try {
568
568
 
569
+ let manifestMseq = playheadState.mediaSeq + playheadState.vodMediaSeqVideo;
569
570
  if (currentVod.sequenceAlwaysContainNewSegments) {
570
571
  const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
571
572
  debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}}`);
573
+ manifestMseq = playheadState.mediaSeq + mediaSequenceValue;
572
574
  }
573
575
 
574
576
  debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
575
577
  const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
576
- debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
578
+ debug(`[${this._sessionId}]: [${manifestMseq}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
577
579
  this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
578
580
  this.prevMediaSeqOffset.video = playheadState.mediaSeq;
579
581
  return m3u8;
@@ -1234,34 +1236,33 @@ class Session {
1234
1236
  try {
1235
1237
  debug(`[${this._sessionId}]: state=VOD_RELOAD_INIT`);
1236
1238
  if (isLeader) {
1237
-
1238
1239
  const startTS = Date.now();
1239
-
1240
1240
  // 1) To tell Follower that, Leader is working on it!
1241
1241
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
1242
-
1243
1242
  // 2) Set new 'offset' sequences, to carry on the continuity from session-live
1244
1243
  let mSeq = this.switchDataForSession.mediaSeq;
1245
1244
  let currentVod = await this._sessionState.getCurrentVod();
1246
1245
  if (currentVod.sequenceAlwaysContainNewSegments) {
1247
1246
  // (!) will need to compensate if using this setting on HLSVod Object.
1248
- mSeq -= 1;
1249
- }
1247
+ Object.keys(this.switchDataForSession.transitionSegments).forEach(bw => {
1248
+ let shiftedSeg = this.switchDataForSession.transitionSegments[bw].shift();
1249
+ if (shiftedSeg && shiftedSeg.discontinuity) {
1250
+ shiftedSeg = this.switchDataForSession.transitionSegments[bw].shift();
1251
+ }
1252
+ });
1253
+ }
1250
1254
  const dSeq = this.switchDataForSession.discSeq;
1251
1255
  const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
1252
1256
  const reloadBehind = this.switchDataForSession.reloadBehind;
1253
1257
  const segments = this.switchDataForSession.transitionSegments;
1254
-
1255
1258
  if ([mSeq,dSeq,mSeqOffset,reloadBehind,segments].includes(null)) {
1256
1259
  debug(`[${this._sessionId}]: LEADER: Cannot Reload VOD, missing switch-back data`);
1257
1260
  return;
1258
1261
  }
1259
-
1260
1262
  await this._sessionState.set("mediaSeq", mSeq);
1261
1263
  await this._playheadState.set("mediaSeq", mSeq);
1262
1264
  await this._sessionState.set("discSeq", dSeq);
1263
1265
  debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
1264
-
1265
1266
  // 3) Set new media segments/currentVod, to carry on the continuity from session-live
1266
1267
  debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
1267
1268
  const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
@@ -1270,8 +1271,10 @@ class Session {
1270
1271
  nextMseq = currentVod.getLiveMediaSequencesCount() - 1;
1271
1272
  }
1272
1273
 
1273
- // TODO: Support reloading with audioSegments as well
1274
-
1274
+ // ---------------------------------------------------.
1275
+ // TODO: Support reloading with audioSegments as well |
1276
+ // ---------------------------------------------------'
1277
+
1275
1278
  await currentVod.reload(nextMseq, segments, null, reloadBehind);
1276
1279
  await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1277
1280
  await this._sessionState.set("vodReloaded", 1);
@@ -1280,13 +1283,12 @@ class Session {
1280
1283
  await this._playheadState.set("vodMediaSeqVideo", 0);
1281
1284
  await this._playheadState.set("vodMediaSeqAudio", 0);
1282
1285
  await this._playheadState.set("playheadRef", Date.now());
1286
+ // 4) Log to debug and cloudwatch
1283
1287
  debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
1284
- // 4) emit cloudwatch event object
1285
1288
  debug(`[${this._sessionId}]: next VOD Reloaded (${currentVod.getDeltaTimes()})`);
1286
1289
  debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
1287
1290
  debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
1288
1291
  cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "switchback", channel: this._sessionId, reqTimeMs: Date.now() - startTS });
1289
-
1290
1292
  return;
1291
1293
  } else {
1292
1294
  debug(`[${this._sessionId}]: not a leader so will go directly to state VOD_RELOAD_INITIATING`);
@@ -85,7 +85,7 @@ class SessionLive {
85
85
  if (resetDelay === null || resetDelay < 0) {
86
86
  resetDelay = RESET_DELAY;
87
87
  }
88
-
88
+ debug(`[${this.instanceId}][${this.sessionId}]: LEADER: Resetting SessionLive values in Store ${resetDelay === 0 ? "Immediately" : `after a delay=(${resetDelay}ms)`}`);
89
89
  await timer(resetDelay);
90
90
  await this.sessionLiveState.set("liveSegsForFollowers", null);
91
91
  await this.sessionLiveState.set("lastRequestedMediaSeqRaw", null);
@@ -95,7 +95,7 @@ class SessionLive {
95
95
  mediaSeqCount: null,
96
96
  discSeqCount: null,
97
97
  });
98
- debug(`[${this.instanceId}][${this.sessionId}]: LEADER: Resetting SessionLive values in Store ${resetDelay === 0 ? "Immediately" : `after a delay=(${resetDelay}ms)`}`);
98
+ debug(`[${this.instanceId}][${this.sessionId}]: LEADER: SessionLive values in Store have now been reset!`);
99
99
  }
100
100
 
101
101
  async resetSession() {
@@ -218,6 +218,7 @@ class SessionLive {
218
218
  this.masterManifestUri = masterManifestUri;
219
219
  if (this.sessionLiveProfile) {
220
220
  this._filterLiveProfiles();
221
+ debug(`[${this.sessionId}]: Filtered Live profiles! (${Object.keys(this.mediaManifestURIs).length}) profiles left!`);
221
222
  }
222
223
  } catch (err) {
223
224
  this.masterManifestUri = null;
@@ -253,14 +254,30 @@ class SessionLive {
253
254
  if (segments[bw][0].discontinuity) {
254
255
  segments[bw].shift();
255
256
  }
256
-
257
+ let cueInExists = null;
257
258
  for (let segIdx = 0; segIdx < segments[bw].length; segIdx++) {
258
- this.vodSegments[bw].push(segments[bw][segIdx]);
259
+ const v2lSegment = segments[bw][segIdx];
260
+ if (v2lSegment.cue) {
261
+ if (v2lSegment.cue["in"]) {
262
+ cueInExists = true;
263
+ } else {
264
+ cueInExists = false;
265
+ }
266
+ }
267
+ this.vodSegments[bw].push(v2lSegment);
259
268
  }
260
- if (!segments[bw][segments[bw].length - 1].discontinuity) {
261
- this.vodSegments[bw].push({ discontinuity: true, cue: { in: true } });
269
+
270
+ const endIdx = segments[bw].length - 1;
271
+ if (!segments[bw][endIdx].discontinuity) {
272
+ const finalSegItem = { discontinuity: true };
273
+ if (!cueInExists) {
274
+ finalSegItem["cue"] = { in: true };
275
+ }
276
+ this.vodSegments[bw].push(finalSegItem);
262
277
  } else {
263
- segments[bw][segments[bw].length - 1]["cue"] = { in: true };
278
+ if (!cueInExists) {
279
+ segments[bw][endIdx]["cue"] = { in: true };
280
+ }
264
281
  }
265
282
  }
266
283
  } else {
@@ -545,6 +562,18 @@ class SessionLive {
545
562
  let attempts = 10;
546
563
  // CHECK AGAIN CASE 1: Store Empty
547
564
  while (!leadersMediaSeqRaw && attempts > 0) {
565
+ if (!leadersMediaSeqRaw) {
566
+ isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
567
+ if (isLeader) {
568
+ debug(`[${this.instanceId}]: I'm the new leader`);
569
+ return;
570
+ }
571
+ }
572
+
573
+ if (!this.allowedToSet) {
574
+ debug(`[${this.sessionId}]: We are about to switch away from LIVE. Abort fetching from Store`);
575
+ break;
576
+ }
548
577
  const segDur = this._getAnyFirstSegmentDurationMs() || DEFAULT_PLAYHEAD_INTERVAL_MS;
549
578
  const waitTimeMs = parseInt(segDur / 3, 10);
550
579
  debug(`[${this.sessionId}]: FOLLOWER: Leader has not put anything in store... Will check again in ${waitTimeMs}ms (Tries left=[${attempts}])`);
@@ -555,19 +584,25 @@ class SessionLive {
555
584
  }
556
585
 
557
586
  if (!leadersMediaSeqRaw) {
558
- isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
559
- if (isLeader) {
560
- debug(`[${this.instanceId}]: I'm the new leader`);
561
- return;
562
- } else {
563
- debug(`[${this.instanceId}]: The leader is still alive`);
564
- return;
565
- }
587
+ debug(`[${this.instanceId}]: The leader is still alive`);
588
+ return;
566
589
  }
567
590
 
568
591
  attempts = 10;
569
592
  // CHECK AGAIN CASE 2: Store Old
570
593
  while (leadersMediaSeqRaw <= this.lastRequestedMediaSeqRaw && attempts > 0) {
594
+ if (!this.allowedToSet) {
595
+ debug(`[${this.sessionId}]: We are about to switch away from LIVE. Abort fetching from Store`);
596
+ break;
597
+ }
598
+ if (leadersMediaSeqRaw <= this.lastRequestedMediaSeqRaw) {
599
+ isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
600
+ if (isLeader) {
601
+ debug(`[${this.instanceId}][${this.sessionId}]: I'm the new leader`);
602
+ return;
603
+ }
604
+ }
605
+
571
606
  const segDur = this._getAnyFirstSegmentDurationMs() || DEFAULT_PLAYHEAD_INTERVAL_MS;
572
607
  const waitTimeMs = parseInt(segDur / 3, 10);
573
608
  debug(`[${this.sessionId}]: FOLLOWER: Cannot find anything NEW in store... Will check again in ${waitTimeMs}ms (Tries left=[${attempts}])`);
@@ -576,18 +611,10 @@ class SessionLive {
576
611
  leadersMediaSeqRaw = await this.sessionLiveState.get("lastRequestedMediaSeqRaw");
577
612
  attempts--;
578
613
  }
579
-
580
- if (leadersMediaSeqRaw <= this.lastRequestedMediaSeqRaw) {
581
- isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
582
- if (isLeader) {
583
- debug(`[${this.instanceId}][${this.sessionId}]: I'm the new leader`);
584
- return;
585
- } else {
586
- debug(`[${this.instanceId}][${this.sessionId}]: The leader is still alive`);
587
- return;
588
- }
614
+ if(leadersMediaSeqRaw <= this.lastRequestedMediaSeqRaw) {
615
+ debug(`[${this.instanceId}][${this.sessionId}]: The leader is still alive`);
616
+ return;
589
617
  }
590
-
591
618
  // Follower updates its manifest building blocks (segment holders & counts)
592
619
  this.lastRequestedMediaSeqRaw = leadersMediaSeqRaw;
593
620
  this.liveSegsForFollowers = await this.sessionLiveState.get("liveSegsForFollowers");
@@ -601,7 +628,7 @@ class SessionLive {
601
628
  // ---------------------------------
602
629
  let FETCH_ATTEMPTS = 10;
603
630
  this.liveSegsForFollowers = {};
604
-
631
+ let bandwidthsToSkipOnRetry = [];
605
632
  while (FETCH_ATTEMPTS > 0) {
606
633
  if (isLeader) {
607
634
  debug(`[${this.sessionId}]: LEADER: Trying to fetch manifests for all bandwidths\n Attempts left=[${FETCH_ATTEMPTS}]`);
@@ -619,9 +646,15 @@ class SessionLive {
619
646
  let manifestList = [];
620
647
  this.pushAmount = 0;
621
648
  try {
649
+ if (bandwidthsToSkipOnRetry.length > 0) {
650
+ debug(`[${this.sessionId}]: (X) Skipping loadMedia promises for bws ${JSON.stringify(bandwidthsToSkipOnRetry)}`);
651
+ }
622
652
  // Collect Live Source Requesting Promises
623
653
  for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
624
654
  let bw = Object.keys(this.mediaManifestURIs)[i];
655
+ if (bandwidthsToSkipOnRetry.includes(bw)) {
656
+ continue;
657
+ }
625
658
  livePromises.push(this._loadMediaManifest(bw));
626
659
  debug(`[${this.sessionId}]: Pushed loadMedia promise for bw=[${bw}]`);
627
660
  }
@@ -642,16 +675,28 @@ class SessionLive {
642
675
  continue;
643
676
  }
644
677
 
645
- const allMediaSeqCounts = manifestList.map((item) => {
646
- if (item.status === "rejected") {
647
- return item.reason.mediaSeq;
678
+ // Store the results locally
679
+ manifestList.forEach((variantItem) => {
680
+ const bw = variantItem.value.bandwidth;
681
+ if (!this.liveSourceM3Us[bw]) {
682
+ this.liveSourceM3Us[bw] = {};
648
683
  }
649
- return item.value.mediaSeq;
684
+ this.liveSourceM3Us[bw] = variantItem.value;
650
685
  });
651
686
 
687
+ const allStoredMediaSeqCounts = Object.keys(this.liveSourceM3Us).map((variant) => this.liveSourceM3Us[variant].mediaSeq);
688
+
652
689
  // Handle if mediaSeqCounts are NOT synced up!
653
- if (!allMediaSeqCounts.every((val, i, arr) => val === arr[0])) {
654
- debug(`[${this.sessionId}]: Live Mseq counts=[${allMediaSeqCounts}]`);
690
+ if (!allStoredMediaSeqCounts.every((val, i, arr) => val === arr[0])) {
691
+ debug(`[${this.sessionId}]: Live Mseq counts=[${allStoredMediaSeqCounts}]`);
692
+ // Figure out what bw's are behind.
693
+ const higestMediaSeqCount = Math.max(...allStoredMediaSeqCounts);
694
+ bandwidthsToSkipOnRetry = Object.keys(this.liveSourceM3Us).filter((bw) => {
695
+ if (this.liveSourceM3Us[bw].mediaSeq === higestMediaSeqCount) {
696
+ return true;
697
+ }
698
+ return false;
699
+ });
655
700
  // Decrement fetch counter
656
701
  FETCH_ATTEMPTS--;
657
702
  // Calculate retry delay time. Default=1000
@@ -672,7 +717,7 @@ class SessionLive {
672
717
  continue;
673
718
  }
674
719
 
675
- currentMseqRaw = allMediaSeqCounts[0];
720
+ currentMseqRaw = allStoredMediaSeqCounts[0];
676
721
 
677
722
  if (!isLeader) {
678
723
  let leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
@@ -733,7 +778,6 @@ class SessionLive {
733
778
  if (currentMseqRaw === leadersFirstSeqCounts.liveSourceMseqCount) {
734
779
  this.pushAmount = 1; // Follower from start
735
780
  } else {
736
-
737
781
  // TODO: To support and account for past discontinuity tags in the Live Source stream,
738
782
  // we will need to get the real 'current' discontinuity-sequence count from Leader somehow.
739
783
 
@@ -773,7 +817,7 @@ class SessionLive {
773
817
  const leadersCurrentMseqRaw = await this.sessionLiveState.get("lastRequestedMediaSeqRaw");
774
818
  const counts = await this.sessionLiveState.get("firstCounts");
775
819
  const leadersFirstMseqRaw = counts.liveSourceMseqCount;
776
- if (leadersCurrentMseqRaw !== null && leadersCurrentMseqRaw !== currentMseqRaw) {
820
+ if (leadersCurrentMseqRaw !== null && leadersCurrentMseqRaw > currentMseqRaw) {
777
821
  // if leader never had any segs from prev mseq
778
822
  if (leadersFirstMseqRaw !== null && leadersFirstMseqRaw === leadersCurrentMseqRaw) {
779
823
  // Follower updates it's manifest ingedients (segment holders & counts)
@@ -821,8 +865,8 @@ class SessionLive {
821
865
  // Do not replace old data with empty data
822
866
  if (segListSize > 0) {
823
867
  debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
824
- await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
825
868
  await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
869
+ await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
826
870
  }
827
871
  }
828
872
 
@@ -1030,8 +1074,8 @@ class SessionLive {
1030
1074
  const resolveObj = {
1031
1075
  M3U: m3u,
1032
1076
  mediaSeq: m3u.get("mediaSequence"),
1077
+ bandwidth: liveTargetBandwidth,
1033
1078
  };
1034
- this.liveSourceM3Us[liveTargetBandwidth] = resolveObj;
1035
1079
  resolve(resolveObj);
1036
1080
  } catch (exc) {
1037
1081
  debug(`[${this.sessionId}]: Error when parsing latest manifest`);
@@ -307,9 +307,6 @@ class StreamSwitcher {
307
307
  liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
308
308
  liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
309
309
 
310
- await sessionLive.resetSession();
311
- sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
312
-
313
310
  if (scheduleObj && !scheduleObj.duration) {
314
311
  debug(`[${this.sessionId}]: Cannot switch VOD. No duration specified for schedule item: [${scheduleObj.assetId}]`);
315
312
  }
@@ -331,6 +328,9 @@ class StreamSwitcher {
331
328
  await session.setCurrentMediaAndDiscSequenceCount(liveCounts.mediaSeq, liveCounts.discSeq);
332
329
  await session.setCurrentMediaSequenceSegments(liveSegments.currMseqSegs, liveSegments.segCount);
333
330
 
331
+ await sessionLive.resetSession();
332
+ sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
333
+
334
334
  this.working = false;
335
335
  this.streamTypeLive = false;
336
336
  debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
@@ -349,8 +349,6 @@ class StreamSwitcher {
349
349
  this.eventId = scheduleObj.eventId;
350
350
  liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
351
351
  liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
352
- await sessionLive.resetSession();
353
- sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
354
352
 
355
353
  eventSegments = await session.getTruncatedVodSegments(scheduleObj.uri, scheduleObj.duration / 1000);
356
354
  if (!eventSegments) {
@@ -369,9 +367,11 @@ class StreamSwitcher {
369
367
  const prerollSegments = this.prerollsCache[this.sessionId].segments;
370
368
  eventSegments = this._mergeSegments(prerollSegments, eventSegments, true);
371
369
  }
372
-
373
370
  await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
374
371
 
372
+ await sessionLive.resetSession();
373
+ sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
374
+
375
375
  this.working = false;
376
376
  this.streamTypeLive = false;
377
377
  debug(`[${this.sessionId}]: Switched from LIVE->VOD`);
@@ -626,7 +626,10 @@ class StreamSwitcher {
626
626
  } else {
627
627
  const lastSeg = toSegments[bw][toSegments[bw].length - 1];
628
628
  if (lastSeg.uri && !lastSeg.discontinuity) {
629
- toSegments[bw].push({ discontinuity: true });
629
+ toSegments[bw].push({ discontinuity: true, cue: { in: true } });
630
+ OUTPUT_SEGMENTS[bw] = toSegments[bw].concat(fromSegments[targetBw]);
631
+ } else if (lastSeg.discontinuity && !lastSeg.cue) {
632
+ toSegments[bw][toSegments[bw].length - 1].cue = { in: true }
630
633
  OUTPUT_SEGMENTS[bw] = toSegments[bw].concat(fromSegments[targetBw]);
631
634
  } else {
632
635
  OUTPUT_SEGMENTS[bw] = toSegments[bw].concat(fromSegments[targetBw]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.3.3",
3
+ "version": "3.3.6",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {