eyevinn-channel-engine 3.1.9 → 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
@@ -57,6 +57,7 @@ class Session {
57
57
  transitionSegments: null,
58
58
  reloadBehind: null,
59
59
  }
60
+ this.alreadyClearedVodCache = null;
60
61
 
61
62
  if (config) {
62
63
  if (config.sessionId) {
@@ -353,12 +354,9 @@ class Session {
353
354
  }
354
355
 
355
356
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
356
- if (playheadState.vodMediaSeqVideo === 0) {
357
- if (!isLeader) {
358
- debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
359
- await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
360
- }
361
- }
357
+
358
+ // NOTE: Assume that VOD cache was already cleared in 'getCurrentMediaAndDiscSequenceCount()'
359
+ // and that we now have access to the correct vod cache
362
360
  const currentVod = await this._sessionState.getCurrentVod();
363
361
  if (currentVod) {
364
362
  try {
@@ -404,10 +402,11 @@ class Session {
404
402
 
405
403
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
406
404
  const discSeqOffset = await this._sessionState.get("discSeq");
405
+ // Clear Vod Cache here when Switching to Live just to be safe...
407
406
  if (playheadState.vodMediaSeqVideo === 0) {
408
407
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
409
408
  if (!isLeader) {
410
- 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.`);
411
410
  await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
412
411
  }
413
412
  }
@@ -461,13 +460,18 @@ class Session {
461
460
  }
462
461
  }
463
462
 
463
+ // Force reading up from store, but only once if the condition is right
464
464
  if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
465
465
  debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
466
466
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
467
- if (!isLeader) {
467
+ if (!isLeader && !this.alreadyClearedVodCache) {
468
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.`);
469
- await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
469
+ await this._sessionState.clearCurrentVodCache();
470
+ this.alreadyClearedVodCache = true;
470
471
  }
472
+ } else {
473
+ // Reset the value
474
+ this.alreadyClearedVodCache = false;
471
475
  }
472
476
 
473
477
  const currentVod = await this._sessionState.getCurrentVod();
@@ -575,9 +579,17 @@ class Session {
575
579
  let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
576
580
  await this._playheadState.setLastM3u8(m3u8);
577
581
 
578
- if (!isLeader && playheadState.vodMediaSeqVideo < 2) {
579
- debug(`[${this._sessionId}]: Not a leader and have just set playheadState vodMediaSeqVideo to 0|1. Invalidate cache to ensure having the correct VOD.`);
580
- 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;
581
593
  }
582
594
 
583
595
  return m3u8;
@@ -834,13 +846,6 @@ class Session {
834
846
  sessionState.state = SessionState.VOD_INIT;
835
847
  }
836
848
 
837
- if (!isLeader && this.waitingForNextVod) {
838
- // By now Leader should have added the next Vod in store
839
- debug(`[${this._sessionId}]: Not leader! New VOD loaded during last tick. Invalidate current VOD cache`);
840
- await this._sessionState.clearCurrentVodCache();
841
- this.waitingForNextVod = false;
842
- }
843
-
844
849
  switch (sessionState.state) {
845
850
  case SessionState.VOD_INIT:
846
851
  case SessionState.VOD_INIT_BY_ID:
@@ -931,19 +936,13 @@ class Session {
931
936
  }
932
937
  }
933
938
  case SessionState.VOD_PLAYING:
934
- if (!isLeader) {
935
- if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
936
- debug(`[${this._sessionId}]: Not leader! Invalidate current VOD cache and fetch the new one from the leader`);
937
- await this._sessionState.clearCurrentVodCache();
938
- currentVod = await this._sessionState.getCurrentVod();
939
- this.waitingForNextVod = false;
940
- }
941
- } else {
939
+ if (isLeader) {
942
940
  // Handle edge case where store has been reset, but leader has not cleared cache.
943
941
  if (this.prevVodMediaSeq.video === null) {
944
942
  this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
945
943
  }
946
- if (this.prevVodMediaSeq.video < sessionState.vodMediaSeqVideo) {
944
+ // Clear Cache if prev count is HIGHER than current...
945
+ if (sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video ) {
947
946
  debug(`[${this._sessionId}]: current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
948
947
  await this._sessionState.clearCurrentVodCache();
949
948
  currentVod = await this._sessionState.getCurrentVod();
@@ -956,11 +955,6 @@ class Session {
956
955
  debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
957
956
  if (!isLeader) {
958
957
  debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be initiated`);
959
- if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
960
- 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`);
961
- await this._sessionState.clearCurrentVodCache();
962
- }
963
- this.waitingForNextVod = true;
964
958
  }
965
959
  return;
966
960
  case SessionState.VOD_NEXT_INIT:
@@ -636,7 +636,9 @@ class SessionLive {
636
636
 
637
637
  // Handle if any promise got rejected
638
638
  if (manifestList.some((result) => result.status === "rejected")) {
639
- 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)
640
642
  continue;
641
643
  }
642
644
 
package/engine/util.js CHANGED
@@ -122,7 +122,33 @@ const logerror = (sessionId, err) => {
122
122
  console.error(err);
123
123
  };
124
124
 
125
- const timer = ms => new Promise(res => setTimeout(res, ms));
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
+ }
126
152
 
127
153
  module.exports = {
128
154
  filterQueryParser,
@@ -131,5 +157,6 @@ module.exports = {
131
157
  m3u8Header,
132
158
  toHHMMSS,
133
159
  logerror,
134
- timer
160
+ timer,
161
+ WaitTimeGenerator,
135
162
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.1.9",
3
+ "version": "3.2.0",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {