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.
@@ -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
 
@@ -158,16 +159,16 @@ class SessionLive {
158
159
  this.waitForPlayhead = false;
159
160
 
160
161
  // Let the playhead move at an interval set according to live segment duration
161
- let liveSegmentDurationMs = DEFAULT_PLAYHEAD_INTERVAL_MS;
162
- let liveBws = Object.keys(this.liveSegQueue);
163
- if (liveBws.length !== 0 && this.liveSegQueue[liveBws[0]].length !== 0 && this.liveSegQueue[liveBws[0]][0].duration) {
164
- liveSegmentDurationMs = this.liveSegQueue[liveBws[0]][0].duration * 1000;
165
- }
162
+ const liveSegmentDurationMs = this._getAnyFirstSegmentDurationMs() || DEFAULT_PLAYHEAD_INTERVAL_MS;
166
163
 
167
164
  // Set the timer
168
165
  let timerValueMs = 0;
169
166
  if (this.timerCompensation) {
170
- if (liveSegmentDurationMs - (tsIncrementEnd - tsIncrementBegin) > 0) {
167
+ const isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
168
+ const incrementDuration = tsIncrementEnd - tsIncrementBegin;
169
+ if (incrementDuration >= liveSegmentDurationMs * 0.5 && isLeader) {
170
+ timerValueMs = liveSegmentDurationMs;
171
+ } else {
171
172
  timerValueMs = liveSegmentDurationMs - (tsIncrementEnd - tsIncrementBegin);
172
173
  }
173
174
  } else {
@@ -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})`);
@@ -540,12 +541,14 @@ class SessionLive {
540
541
  if (!leadersMediaSeqRaw < this.lastRequestedMediaSeqRaw && this.blockGenerateManifest) {
541
542
  this.blockGenerateManifest = false;
542
543
  }
543
- let attempts = 5;
544
544
 
545
+ let attempts = 5;
545
546
  // CHECK AGAIN CASE 1: Store Empty
546
547
  while (!leadersMediaSeqRaw && attempts > 0) {
547
- debug(`[${this.sessionId}]: FOLLOWER: Leader has not put anything in store... Will check again in 2000ms (Tries left=[${attempts}])`);
548
- await timer(2000);
548
+ const segDur = this._getAnyFirstSegmentDurationMs() || DEFAULT_PLAYHEAD_INTERVAL_MS;
549
+ const waitTimeMs = parseInt(segDur / 3, 10);
550
+ debug(`[${this.sessionId}]: FOLLOWER: Leader has not put anything in store... Will check again in ${waitTimeMs}ms (Tries left=[${attempts}])`);
551
+ await timer(waitTimeMs);
549
552
  this.timerCompensation = false;
550
553
  leadersMediaSeqRaw = await this.sessionLiveState.get("lastRequestedMediaSeqRaw");
551
554
  attempts--;
@@ -565,8 +568,10 @@ class SessionLive {
565
568
  attempts = 5;
566
569
  // CHECK AGAIN CASE 2: Store Old
567
570
  while (leadersMediaSeqRaw <= this.lastRequestedMediaSeqRaw && attempts > 0) {
568
- debug(`[${this.sessionId}]: FOLLOWER: Cannot find anything NEW in store... Will check again in 2000ms (Tries left=[${attempts}])`);
569
- await timer(2000);
571
+ const segDur = this._getAnyFirstSegmentDurationMs() || DEFAULT_PLAYHEAD_INTERVAL_MS;
572
+ const waitTimeMs = parseInt(segDur / 3, 10);
573
+ debug(`[${this.sessionId}]: FOLLOWER: Cannot find anything NEW in store... Will check again in ${waitTimeMs}ms (Tries left=[${attempts}])`);
574
+ await timer(waitTimeMs);
570
575
  this.timerCompensation = false;
571
576
  leadersMediaSeqRaw = await this.sessionLiveState.get("lastRequestedMediaSeqRaw");
572
577
  attempts--;
@@ -631,7 +636,9 @@ class SessionLive {
631
636
 
632
637
  // Handle if any promise got rejected
633
638
  if (manifestList.some((result) => result.status === "rejected")) {
634
- debug(`[${this.sessionId}]: ALERT! Promises I: Failed, Rejection Found! Trying again...`);
639
+ FETCH_ATTEMPTS--;
640
+ debug(`[${this.sessionId}]: ALERT! Promises I: Failed, Rejection Found! Trying again in 1000ms...`);
641
+ await timer(1000)
635
642
  continue;
636
643
  }
637
644
 
@@ -671,28 +678,40 @@ class SessionLive {
671
678
  let leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
672
679
  let tries = 20;
673
680
 
674
- while ((!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
681
+ while (!isLeader && (!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
675
682
  debug(`[${this.sessionId}]: NEW FOLLOWER: Waiting for LEADER to add 'firstCounts' in store! Will look again after 1000ms (tries left=${tries})`);
676
683
  await timer(1000);
677
684
  leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
678
685
  tries--;
686
+ // Might take over as Leader if Leader is not setting data due to being down.
687
+ isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
688
+ if (isLeader) {
689
+ debug(`[${this.sessionId}][${this.instanceId}]: I'm the new leader, and now I am going to add 'firstCounts' in store`);
690
+ }
679
691
  }
680
692
 
681
693
  if (tries === 0) {
682
694
  isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
683
695
  if (isLeader) {
684
- debug(`[${this.instanceId}]: I'm the new leader, and now I am going to add 'firstCounts' in store`);
696
+ debug(`[${this.sessionId}][${this.instanceId}]: I'm the new leader, and now I am going to add 'firstCounts' in store`);
685
697
  break;
686
698
  } else {
687
- debug(`[${this.instanceId}]: The leader is still alive`);
699
+ debug(`[${this.sessionId}][${this.instanceId}]: The leader is still alive`);
688
700
  leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
689
701
  if (!leadersFirstSeqCounts.liveSourceMseqCount) {
690
- debug(`[${this.instanceId}]: Could not find 'firstCounts' in store. Abort Executing Promises II & Returning to Playhead.`);
702
+ debug(`[${this.sessionId}][${this.instanceId}]: Could not find 'firstCounts' in store. Abort Executing Promises II & Returning to Playhead.`);
691
703
  return;
692
704
  }
693
705
  }
694
706
  }
695
707
 
708
+ if (isLeader) {
709
+ debug(`[${this.sessionId}]: NEW LEADER: Original Leader went missing, I am retrying live source fetch...`);
710
+ await this.sessionLiveState.set("transitSegs", this.vodSegments);
711
+ debug(`[${this.sessionId}]: NEW LEADER: I am adding 'transitSegs' to Store for future followers`);
712
+ continue;
713
+ }
714
+
696
715
  // Respawners never do this, only starter followers.
697
716
  // Edge Case: FOLLOWER transitioned from session with different segments from LEADER
698
717
  if (leadersFirstSeqCounts.discSeqCount !== this.discSeqCount) {
@@ -713,6 +732,10 @@ class SessionLive {
713
732
  if (currentMseqRaw === leadersFirstSeqCounts.liveSourceMseqCount) {
714
733
  this.pushAmount = 1; // Follower from start
715
734
  } else {
735
+
736
+ // TODO: To support and account for past discontinuity tags in the Live Source stream,
737
+ // we will need to get the real 'current' discontinuity-sequence count from Leader somehow.
738
+
716
739
  // RESPAWNED NODES
717
740
  this.pushAmount = currentMseqRaw - leadersFirstSeqCounts.liveSourceMseqCount + 1;
718
741
 
@@ -772,7 +795,7 @@ class SessionLive {
772
795
  for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
773
796
  let bw = Object.keys(this.mediaManifestURIs)[i];
774
797
  // will add new segments to live seg queue
775
- pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw));
798
+ pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw, isLeader));
776
799
  debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
777
800
  }
778
801
  // Segment Pushing
@@ -961,6 +984,12 @@ class SessionLive {
961
984
  // Update Session Live Media Sequence Count...
962
985
  this.prevMediaSeqCount = this.mediaSeqCount;
963
986
  this.mediaSeqCount += removedSegments;
987
+ if (this.restAmount) {
988
+ this.mediaSeqCount += this.restAmount;
989
+ debug(`[${this.sessionId}]: ${instanceName}: Added restAmount=[${this.restAmount}] to 'mediaSeqCount'`);
990
+ this.restAmount = 0;
991
+ }
992
+
964
993
  if (this.discSeqCount !== this.prevDiscSeqCount) {
965
994
  debug(`[${this.sessionId}]: ${instanceName}: Incrementing Dseq Count from {${this.prevDiscSeqCount}} -> {${this.discSeqCount}}`);
966
995
  }
@@ -1015,7 +1044,7 @@ class SessionLive {
1015
1044
  });
1016
1045
  }
1017
1046
 
1018
- _parseMediaManifest(m3u, mediaManifestUri, liveTargetBandwidth) {
1047
+ _parseMediaManifest(m3u, mediaManifestUri, liveTargetBandwidth, isLeader) {
1019
1048
  return new Promise(async (resolve, reject) => {
1020
1049
  try {
1021
1050
  if (!this.liveSegQueue[liveTargetBandwidth]) {
@@ -1038,10 +1067,13 @@ class SessionLive {
1038
1067
  }
1039
1068
  this.targetDuration = m3u.get("targetDuration");
1040
1069
  let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
1041
- startIdx = startIdx < 0 ? 0 : startIdx;
1070
+ if (startIdx < 0) {
1071
+ this.restAmount = startIdx * -1;
1072
+ startIdx = 0;
1073
+ }
1042
1074
  if (mediaManifestUri) {
1043
1075
  // push segments
1044
- this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
1076
+ this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth, isLeader);
1045
1077
  }
1046
1078
  resolve();
1047
1079
  } catch (exc) {
@@ -1059,7 +1091,9 @@ class SessionLive {
1059
1091
  * @param {string} baseUrl
1060
1092
  * @param {string} liveTargetBandwidth
1061
1093
  */
1062
- _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
1094
+ _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth, isLeader) {
1095
+ const leaderOrFollower = isLeader ? "LEADER": "NEW FOLLOWER";
1096
+
1063
1097
  for (let i = startIdx; i < playlistItems.length; i++) {
1064
1098
  let seg = {};
1065
1099
  let playlistItem = playlistItems[i];
@@ -1127,10 +1161,15 @@ class SessionLive {
1127
1161
  if (daterangeData) {
1128
1162
  seg["daterange"] = daterangeData;
1129
1163
  }
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);
1164
+ // Push new Live Segments! But do not push duplicates
1165
+ const liveSegURIs = this.liveSegQueue[liveTargetBandwidth].filter(seg => seg.uri).map(seg => seg.uri);
1166
+ if (seg.uri && liveSegURIs.includes(seg.uri)) {
1167
+ debug(`[${this.sessionId}]: ${leaderOrFollower}: Found duplicate live segment. Skip push! (${liveTargetBandwidth})`);
1168
+ } else {
1169
+ this.liveSegQueue[liveTargetBandwidth].push(seg);
1170
+ this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
1171
+ debug(`[${this.sessionId}]: ${leaderOrFollower}: Pushed segment (${seg.uri ? seg.uri : "Disc-tag"}) to 'liveSegQueue' (${liveTargetBandwidth})`);
1172
+ }
1134
1173
  }
1135
1174
  }
1136
1175
  }
@@ -1329,6 +1368,27 @@ class SessionLive {
1329
1368
  this.mediaManifestURIs = newItem;
1330
1369
  }
1331
1370
 
1371
+ _getAnyFirstSegmentDurationMs() {
1372
+ if (this._isEmpty(this.liveSegQueue)) {
1373
+ return null;
1374
+ }
1375
+
1376
+ const bw0 = Object.keys(this.liveSegQueue)[0];
1377
+ if (this.liveSegQueue[bw0].length === 0) {
1378
+ return null;
1379
+ }
1380
+
1381
+ for (let i = 0; i < this.liveSegQueue[bw0].length; i++) {
1382
+ const segment = this.liveSegQueue[bw0][i];
1383
+ if (!segment.duration) {
1384
+ continue;
1385
+ }
1386
+ return segment.duration * 1000;
1387
+ }
1388
+
1389
+ return null;
1390
+ }
1391
+
1332
1392
  _isEmpty(obj) {
1333
1393
  if (!obj) {
1334
1394
  return true;
@@ -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,8 @@ 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');
6
+ const { timer } = require("./util")
5
7
 
6
8
  const SwitcherState = Object.freeze({
7
9
  V2L_TO_LIVE: 1,
@@ -88,8 +90,8 @@ class StreamSwitcher {
88
90
  return null;
89
91
  }
90
92
  // Handle Complete Storage Reset
91
- let sessionState = await session._sessionState.get("state");
92
- if (sessionState === 1 || !sessionState) {
93
+ let sessionState = await session.getSessionState();
94
+ if (sessionState === SessionState.VOD_INIT || !sessionState) {
93
95
  this.working = true;
94
96
  sessionLive.waitForPlayhead = false;
95
97
  sessionLive.allowedToSet = false; // only have effect on leader
@@ -150,6 +152,11 @@ class StreamSwitcher {
150
152
  );
151
153
  validURI = await this._validURI(scheduleObj.uri);
152
154
  tries++;
155
+ if (!validURI) {
156
+ const delayMs = tries * 500;
157
+ debug(`[${this.sessionId}]: Going to try validating Master URI again in ${delayMs}ms`);
158
+ await timer(delayMs);
159
+ }
153
160
  }
154
161
  if (!validURI) {
155
162
  debug(`[${this.sessionId}]: Unreachable URI: [${scheduleObj.uri}]`);
@@ -341,14 +348,14 @@ class StreamSwitcher {
341
348
  return false;
342
349
  }
343
350
 
344
- await session.setCurrentMediaSequenceSegments(
345
- liveSegments.currMseqSegs,
346
- liveSegments.segCount
347
- );
348
351
  await session.setCurrentMediaAndDiscSequenceCount(
349
352
  liveCounts.mediaSeq,
350
353
  liveCounts.discSeq
351
354
  );
355
+ await session.setCurrentMediaSequenceSegments(
356
+ liveSegments.currMseqSegs,
357
+ liveSegments.segCount
358
+ );
352
359
 
353
360
  this.working = false;
354
361
  this.streamTypeLive = false;
package/engine/util.js CHANGED
@@ -122,6 +122,34 @@ const logerror = (sessionId, err) => {
122
122
  console.error(err);
123
123
  };
124
124
 
125
+ const timer = (ms) => new Promise((res) => setTimeout(res, ms));
126
+
127
+ class WaitTimeGenerator {
128
+ constructor(defaultIntervalMs, minValue) {
129
+ (this.timestamp = null), (this.prevWaitTime = null), (this.defaultIntervalMs = defaultIntervalMs || 3000), (this.minValue = minValue);
130
+ }
131
+ _getWaitTimeFromTimestamp() {
132
+ if (!this.timestamp) {
133
+ this.timestamp = new Date();
134
+ }
135
+ const sec = this.timestamp.getSeconds();
136
+ const defaultSec = this.defaultIntervalMs / 1000;
137
+ const d = parseInt(sec / defaultSec);
138
+ const waitSec = defaultSec * (d + 1) - sec;
139
+ return waitSec * 1000;
140
+ }
141
+ async getWaitTime(plannedTime) {
142
+ if (!this.timestamp || (this.prevWaitTime === this.minValue && plannedTime !== this.minValue)) {
143
+ this.timestamp = new Date();
144
+ const waitMs = this._getWaitTimeFromTimestamp();
145
+ this.prevWaitTime = waitMs;
146
+ return waitMs;
147
+ }
148
+ this.prevWaitTime = plannedTime;
149
+ return plannedTime;
150
+ }
151
+ }
152
+
125
153
  module.exports = {
126
154
  filterQueryParser,
127
155
  applyFilter,
@@ -129,4 +157,6 @@ module.exports = {
129
157
  m3u8Header,
130
158
  toHHMMSS,
131
159
  logerror,
160
+ timer,
161
+ WaitTimeGenerator,
132
162
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.1.7",
3
+ "version": "3.2.0",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {