eyevinn-channel-engine 4.0.9 → 4.0.11

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.
@@ -19,7 +19,7 @@ declare class SharedPlayheadState {
19
19
  getState(): Promise<4>;
20
20
  getLastM3u8(): Promise<any>;
21
21
  getValues(keys: any): Promise<any>;
22
- set(key: any, value: any): Promise<any>;
22
+ set(key: any, value: any, isLeader: any): Promise<any>;
23
23
  setState(newState: any): Promise<4>;
24
24
  setLastM3u8(m3u8: any): Promise<any>;
25
25
  }
@@ -97,12 +97,16 @@ var SharedPlayheadState = /** @class */ (function () {
97
97
  });
98
98
  });
99
99
  };
100
- SharedPlayheadState.prototype.set = function (key, value) {
100
+ SharedPlayheadState.prototype.set = function (key, value, isLeader) {
101
101
  return __awaiter(this, void 0, void 0, function () {
102
102
  return __generator(this, function (_a) {
103
103
  switch (_a.label) {
104
- case 0: return [4 /*yield*/, this.store.set(this.sessionId, key, value)];
104
+ case 0:
105
+ if (!isLeader) return [3 /*break*/, 2];
106
+ return [4 /*yield*/, this.store.set(this.sessionId, key, value)];
105
107
  case 1: return [2 /*return*/, _a.sent()];
108
+ case 2: return [4 /*yield*/, this.store.get(this.sessionId, key)];
109
+ case 3: return [2 /*return*/, _a.sent()];
106
110
  }
107
111
  });
108
112
  });
@@ -34,7 +34,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
34
34
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
35
  }
36
36
  };
37
- var redis = require("redis");
37
+ var createClient = require("redis").createClient;
38
38
  var debug = require("debug")("redis-state-store");
39
39
  var DEFAULT_VOLATILE_KEY_TTL = 5; // Timeout so it should not expire within one normal increment iteration (in seconds)
40
40
  var RedisStateStore = /** @class */ (function () {
@@ -50,7 +50,7 @@ var RedisStateStore = /** @class */ (function () {
50
50
  debug("Overriding default, volatileKeyTTL=".concat(opts.volatileKeyTTL, "s"));
51
51
  this.volatileKeyTTL = opts.volatileKeyTTL;
52
52
  }
53
- this.client = redis.createClient(opts.redisUrl);
53
+ this.client = createClient(opts.redisUrl);
54
54
  }
55
55
  RedisStateStore.prototype.initAsync = function (id, initData) {
56
56
  return __awaiter(this, void 0, void 0, function () {
@@ -131,15 +131,17 @@ var RedisStateStore = /** @class */ (function () {
131
131
  };
132
132
  RedisStateStore.prototype.getAsync = function (id, key) {
133
133
  return __awaiter(this, void 0, void 0, function () {
134
- var storeKey, getAsync, data;
134
+ var startMs, storeKey, getAsync, data;
135
135
  var _this = this;
136
136
  return __generator(this, function (_a) {
137
137
  switch (_a.label) {
138
138
  case 0:
139
+ startMs = Date.now();
139
140
  storeKey = "" + this.keyPrefix + id + key;
140
141
  getAsync = new Promise(function (resolve, reject) {
141
142
  _this.client.get(storeKey, function (err, reply) {
142
- debug("REDIS get ".concat(storeKey, ":").concat(reply ? reply.length + " chars" : "null"));
143
+ var ioTimeMs = Date.now() - startMs;
144
+ debug("REDIS get ".concat(storeKey, ":").concat(reply ? reply.length + " chars" : "null", " (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? 'REDISSLOW!' : ''));
143
145
  if (!err) {
144
146
  resolve(JSON.parse(reply));
145
147
  }
@@ -158,15 +160,17 @@ var RedisStateStore = /** @class */ (function () {
158
160
  };
159
161
  RedisStateStore.prototype.setAsync = function (id, key, value) {
160
162
  return __awaiter(this, void 0, void 0, function () {
161
- var storeKey, setAsync;
163
+ var startMs, storeKey, setAsync;
162
164
  var _this = this;
163
165
  return __generator(this, function (_a) {
164
166
  switch (_a.label) {
165
167
  case 0:
168
+ startMs = Date.now();
166
169
  storeKey = "" + this.keyPrefix + id + key;
167
170
  setAsync = new Promise(function (resolve, reject) {
168
171
  _this.client.set(storeKey, JSON.stringify(value), function (err, res) {
169
- debug("REDIS set ".concat(storeKey, ": ").concat(res));
172
+ var ioTimeMs = Date.now() - startMs;
173
+ debug("REDIS set ".concat(storeKey, ": ").concat(res, " (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? 'REDISSLOW!' : ''));
170
174
  if (!err) {
171
175
  resolve(value);
172
176
  }
@@ -212,14 +216,17 @@ var RedisStateStore = /** @class */ (function () {
212
216
  };
213
217
  RedisStateStore.prototype.removeAsync = function (id, key) {
214
218
  return __awaiter(this, void 0, void 0, function () {
215
- var storeKey, delAsync;
219
+ var startMs, storeKey, delAsync;
216
220
  var _this = this;
217
221
  return __generator(this, function (_a) {
218
222
  switch (_a.label) {
219
223
  case 0:
224
+ startMs = Date.now();
220
225
  storeKey = "" + this.keyPrefix + id + key;
221
226
  delAsync = new Promise(function (resolve, reject) {
222
227
  _this.client.del(storeKey, function (err, res) {
228
+ var ioTimeMs = Date.now() - startMs;
229
+ debug("REDIS remove ".concat(storeKey, ": (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? 'REDISSLOW!' : ''));
223
230
  if (!err) {
224
231
  resolve();
225
232
  }
@@ -329,7 +329,9 @@ var ChannelEngine = /** @class */ (function () {
329
329
  }
330
330
  });
331
331
  }); };
332
- StreamSwitchLoop(this.streamSwitchTimeIntervalMs);
332
+ if (this.streamSwitchManager) {
333
+ StreamSwitchLoop(this.streamSwitchTimeIntervalMs);
334
+ }
333
335
  }
334
336
  ChannelEngine.prototype.updateStreamSwitchAsync = function () {
335
337
  return __awaiter(this, void 0, void 0, function () {
@@ -887,34 +889,39 @@ var ChannelEngine = /** @class */ (function () {
887
889
  debug(req.params);
888
890
  session = sessions[req.params[1]];
889
891
  sessionLive = sessionsLive[req.params[1]];
890
- if (!(session && sessionLive)) return [3 /*break*/, 11];
892
+ if (!(session && sessionLive)) return [3 /*break*/, 12];
891
893
  _a.label = 1;
892
894
  case 1:
893
- _a.trys.push([1, 9, , 10]);
894
- _a.label = 2;
895
+ _a.trys.push([1, 10, , 11]);
896
+ body = null;
897
+ if (!!this.streamSwitchManager) return [3 /*break*/, 3];
898
+ debug("[".concat(req.params[1], "]: Responding with VOD2Live manifest"));
899
+ return [4 /*yield*/, session.getCurrentMediaManifestAsync(req.params[0], req.headers["x-playback-session-id"])];
895
900
  case 2:
896
- if (!(switcherStatus[req.params[1]] === null || switcherStatus[req.params[1]] === undefined)) return [3 /*break*/, 4];
901
+ body = _a.sent();
902
+ return [3 /*break*/, 9];
903
+ case 3:
904
+ if (!(switcherStatus[req.params[1]] === null || switcherStatus[req.params[1]] === undefined)) return [3 /*break*/, 5];
897
905
  debug("[".concat(req.params[1], "]: (").concat(switcherStatus[req.params[1]], ") Waiting for streamSwitcher to respond"));
898
906
  return [4 /*yield*/, timer(500)];
899
- case 3:
900
- _a.sent();
901
- return [3 /*break*/, 2];
902
907
  case 4:
903
- body = null;
908
+ _a.sent();
909
+ return [3 /*break*/, 3];
910
+ case 5:
904
911
  debug("switcherStatus[".concat(req.params[1], "]=[").concat(switcherStatus[req.params[1]], "]"));
905
- if (!switcherStatus[req.params[1]]) return [3 /*break*/, 6];
912
+ if (!switcherStatus[req.params[1]]) return [3 /*break*/, 7];
906
913
  debug("[".concat(req.params[1], "]: Responding with Live-stream manifest"));
907
914
  return [4 /*yield*/, sessionLive.getCurrentMediaManifestAsync(req.params[0])];
908
- case 5:
909
- body = _a.sent();
910
- return [3 /*break*/, 8];
911
915
  case 6:
916
+ body = _a.sent();
917
+ return [3 /*break*/, 9];
918
+ case 7:
912
919
  debug("[".concat(req.params[1], "]: Responding with VOD2Live manifest"));
913
920
  return [4 /*yield*/, session.getCurrentMediaManifestAsync(req.params[0], req.headers["x-playback-session-id"])];
914
- case 7:
915
- body = _a.sent();
916
- _a.label = 8;
917
921
  case 8:
922
+ body = _a.sent();
923
+ _a.label = 9;
924
+ case 9:
918
925
  //verbose(`[${session.sessionId}] body=`);
919
926
  //verbose(body);
920
927
  res.sendRaw(200, Buffer.from(body, 'utf8'), {
@@ -924,17 +931,17 @@ var ChannelEngine = /** @class */ (function () {
924
931
  "X-Instance-Id": this.instanceId + "<".concat(version, ">"),
925
932
  });
926
933
  next();
927
- return [3 /*break*/, 10];
928
- case 9:
934
+ return [3 /*break*/, 11];
935
+ case 10:
929
936
  err_6 = _a.sent();
930
937
  next(this._gracefulErrorHandler(err_6));
931
- return [3 /*break*/, 10];
932
- case 10: return [3 /*break*/, 12];
933
- case 11:
938
+ return [3 /*break*/, 11];
939
+ case 11: return [3 /*break*/, 13];
940
+ case 12:
934
941
  err = new errs.NotFoundError('Invalid session(s)');
935
942
  next(err);
936
- _a.label = 12;
937
- case 12: return [2 /*return*/];
943
+ _a.label = 13;
944
+ case 13: return [2 /*return*/];
938
945
  }
939
946
  });
940
947
  });
@@ -253,7 +253,7 @@ var Session = /** @class */ (function () {
253
253
  tickInterval = firstDuration < 2 ? 2 : firstDuration;
254
254
  debug("[".concat(this._sessionId, "]: I am the leader and updated tick interval to ").concat(tickInterval, " sec"));
255
255
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'tickIntervalUpdated', channel: this._sessionId, tickIntervalSec: tickInterval });
256
- this._playheadState.set("tickInterval", tickInterval);
256
+ this._playheadState.set("tickInterval", tickInterval, isLeader);
257
257
  return [3 /*break*/, 21];
258
258
  case 13:
259
259
  if (!(state == PlayheadState.STOPPED)) return [3 /*break*/, 14];
@@ -326,7 +326,7 @@ var Session = /** @class */ (function () {
326
326
  case 17:
327
327
  _a.sent();
328
328
  tsTickEnd = Date.now();
329
- return [4 /*yield*/, this._playheadState.set("tickMs", (tsTickEnd - tsIncrementBegin))];
329
+ return [4 /*yield*/, this._playheadState.set("tickMs", (tsTickEnd - tsIncrementBegin), isLeader)];
330
330
  case 18:
331
331
  _a.sent();
332
332
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'tickInterval', channel: this._sessionId, tickTimeMs: (tsTickEnd - tsIncrementBegin) });
@@ -335,7 +335,7 @@ var Session = /** @class */ (function () {
335
335
  case 19:
336
336
  lastDuration = _a.sent();
337
337
  nextTickInterval = lastDuration < 2 ? 2 : lastDuration;
338
- return [4 /*yield*/, this._playheadState.set("tickInterval", nextTickInterval)];
338
+ return [4 /*yield*/, this._playheadState.set("tickInterval", nextTickInterval, isLeader)];
339
339
  case 20:
340
340
  _a.sent();
341
341
  cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "tickIntervalUpdated", channel: this._sessionId, tickIntervalSec: nextTickInterval });
@@ -375,12 +375,15 @@ var Session = /** @class */ (function () {
375
375
  };
376
376
  Session.prototype.stopPlayheadAsync = function () {
377
377
  return __awaiter(this, void 0, void 0, function () {
378
+ var isLeader;
378
379
  return __generator(this, function (_a) {
379
380
  switch (_a.label) {
380
- case 0:
381
- debug("[".concat(this._sessionId, "]: Stopping playhead consumer"));
382
- return [4 /*yield*/, this._playheadState.set("state", PlayheadState.STOPPED)];
381
+ case 0: return [4 /*yield*/, this._sessionStateStore.isLeader(this._instanceId)];
383
382
  case 1:
383
+ isLeader = _a.sent();
384
+ debug("[".concat(this._sessionId, "]: Stopping playhead consumer"));
385
+ return [4 /*yield*/, this._playheadState.set("state", PlayheadState.STOPPED, isLeader)];
386
+ case 2:
384
387
  _a.sent();
385
388
  return [2 /*return*/];
386
389
  }
@@ -1092,20 +1095,23 @@ var Session = /** @class */ (function () {
1092
1095
  this.isSwitchingBackToV2L = false;
1093
1096
  _o.label = 32;
1094
1097
  case 32:
1098
+ if (isLeader) {
1099
+ debug("[".concat(this._sessionId, "]: I am the leader, updating PlayheadState values"));
1100
+ }
1095
1101
  _j = playheadState;
1096
- return [4 /*yield*/, this._playheadState.set("mediaSeq", sessionState.mediaSeq)];
1102
+ return [4 /*yield*/, this._playheadState.set("mediaSeq", sessionState.mediaSeq, isLeader)];
1097
1103
  case 33:
1098
1104
  _j.mediaSeq = _o.sent();
1099
1105
  _k = playheadState;
1100
- return [4 /*yield*/, this._playheadState.set("mediaSeqAudio", sessionState.mediaSeqAudio)];
1106
+ return [4 /*yield*/, this._playheadState.set("mediaSeqAudio", sessionState.mediaSeqAudio, isLeader)];
1101
1107
  case 34:
1102
1108
  _k.mediaSeqAudio = _o.sent();
1103
1109
  _l = playheadState;
1104
- return [4 /*yield*/, this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo)];
1110
+ return [4 /*yield*/, this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo, isLeader)];
1105
1111
  case 35:
1106
1112
  _l.vodMediaSeqVideo = _o.sent();
1107
1113
  _m = playheadState;
1108
- return [4 /*yield*/, this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio)];
1114
+ return [4 /*yield*/, this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio, isLeader)];
1109
1115
  case 36:
1110
1116
  _m.vodMediaSeqAudio = _o.sent();
1111
1117
  if (currentVod.sequenceAlwaysContainNewSegments) {
@@ -1483,11 +1489,11 @@ var Session = /** @class */ (function () {
1483
1489
  };
1484
1490
  Session.prototype._insertSlate = function (currentVod) {
1485
1491
  return __awaiter(this, void 0, void 0, function () {
1486
- var slateVod, sessionState, endValue, endValueAudio, lastDiscontinuity, lastDiscontinuityAudio;
1492
+ var slateVod, sessionState, endValue, endValueAudio, lastDiscontinuity, lastDiscontinuityAudio, isLeader;
1487
1493
  return __generator(this, function (_a) {
1488
1494
  switch (_a.label) {
1489
1495
  case 0:
1490
- if (!this.slateUri) return [3 /*break*/, 13];
1496
+ if (!this.slateUri) return [3 /*break*/, 14];
1491
1497
  console.error("[".concat(this._sessionId, "]: Will insert slate"));
1492
1498
  return [4 /*yield*/, this._loadSlate(currentVod)];
1493
1499
  case 1:
@@ -1512,39 +1518,42 @@ var Session = /** @class */ (function () {
1512
1518
  lastDiscontinuity = currentVod.getLastDiscontinuity();
1513
1519
  lastDiscontinuityAudio = currentVod.getLastDiscontinuityAudio();
1514
1520
  }
1515
- return [4 /*yield*/, this._sessionState.set("vodMediaSeqVideo", 0)];
1521
+ return [4 /*yield*/, this._sessionStateStore.isLeader(this._instanceId)];
1516
1522
  case 3:
1517
- _a.sent();
1518
- return [4 /*yield*/, this._sessionState.set("vodMediaSeqAudio", 0)];
1523
+ isLeader = _a.sent();
1524
+ return [4 /*yield*/, this._sessionState.set("vodMediaSeqVideo", 0)];
1519
1525
  case 4:
1520
1526
  _a.sent();
1521
- return [4 /*yield*/, this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING)];
1527
+ return [4 /*yield*/, this._sessionState.set("vodMediaSeqAudio", 0)];
1522
1528
  case 5:
1523
1529
  _a.sent();
1524
- return [4 /*yield*/, this._sessionState.setCurrentVod(slateVod)];
1530
+ return [4 /*yield*/, this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING)];
1525
1531
  case 6:
1526
1532
  _a.sent();
1527
- return [4 /*yield*/, this._sessionState.set("mediaSeq", sessionState.mediaSeq + endValue)];
1533
+ return [4 /*yield*/, this._sessionState.setCurrentVod(slateVod)];
1528
1534
  case 7:
1529
1535
  _a.sent();
1530
- return [4 /*yield*/, this._sessionState.set("mediaSeqAudio", sessionState.mediaSeqAudio + endValueAudio)];
1536
+ return [4 /*yield*/, this._sessionState.set("mediaSeq", sessionState.mediaSeq + endValue)];
1531
1537
  case 8:
1532
1538
  _a.sent();
1533
- return [4 /*yield*/, this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity)];
1539
+ return [4 /*yield*/, this._sessionState.set("mediaSeqAudio", sessionState.mediaSeqAudio + endValueAudio)];
1534
1540
  case 9:
1535
1541
  _a.sent();
1536
- return [4 /*yield*/, this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio)];
1542
+ return [4 /*yield*/, this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity)];
1537
1543
  case 10:
1538
1544
  _a.sent();
1539
- return [4 /*yield*/, this._sessionState.set("slateCount", sessionState.slateCount + 1)];
1545
+ return [4 /*yield*/, this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio)];
1540
1546
  case 11:
1541
1547
  _a.sent();
1542
- return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now())];
1548
+ return [4 /*yield*/, this._sessionState.set("slateCount", sessionState.slateCount + 1)];
1543
1549
  case 12:
1550
+ _a.sent();
1551
+ return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now(), isLeader)];
1552
+ case 13:
1544
1553
  _a.sent();
1545
1554
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'slateInserted', channel: this._sessionId });
1546
1555
  return [2 /*return*/, slateVod];
1547
- case 13: return [2 /*return*/, null];
1556
+ case 14: return [2 /*return*/, null];
1548
1557
  }
1549
1558
  });
1550
1559
  });
@@ -1666,7 +1675,7 @@ var Session = /** @class */ (function () {
1666
1675
  return [4 /*yield*/, this._sessionState.set("vodMediaSeqAudio", 0)];
1667
1676
  case 15:
1668
1677
  _h.vodMediaSeqAudio = _2.sent();
1669
- return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now())];
1678
+ return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now(), isLeader)];
1670
1679
  case 16:
1671
1680
  _2.sent();
1672
1681
  this.produceEvent({
@@ -1898,10 +1907,10 @@ var Session = /** @class */ (function () {
1898
1907
  case 50:
1899
1908
  _x.currentVod = _2.sent();
1900
1909
  this.leaderIsSettingNextVod = false;
1901
- return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now())];
1910
+ return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now(), isLeader)];
1902
1911
  case 51:
1903
1912
  _2.sent();
1904
- return [4 /*yield*/, this._playheadState.set("diffCompensation", this.diffCompensation)];
1913
+ return [4 /*yield*/, this._playheadState.set("diffCompensation", this.diffCompensation, isLeader)];
1905
1914
  case 52:
1906
1915
  _2.sent();
1907
1916
  debug("[".concat(this._sessionId, "]: sharing durrent vods diffCompensation=").concat(this.diffCompensation));
@@ -1999,7 +2008,7 @@ var Session = /** @class */ (function () {
1999
2008
  return [4 /*yield*/, this._sessionState.set("mediaSeq", mSeq)];
2000
2009
  case 65:
2001
2010
  _2.sent();
2002
- return [4 /*yield*/, this._playheadState.set("mediaSeq", mSeq)];
2011
+ return [4 /*yield*/, this._playheadState.set("mediaSeq", mSeq, isLeader)];
2003
2012
  case 66:
2004
2013
  _2.sent();
2005
2014
  return [4 /*yield*/, this._sessionState.set("discSeq", dSeq)];
@@ -2037,13 +2046,13 @@ var Session = /** @class */ (function () {
2037
2046
  return [4 /*yield*/, this._sessionState.set("vodMediaSeqAudio", 0)];
2038
2047
  case 73:
2039
2048
  _2.sent();
2040
- return [4 /*yield*/, this._playheadState.set("vodMediaSeqVideo", 0)];
2049
+ return [4 /*yield*/, this._playheadState.set("vodMediaSeqVideo", 0, isLeader)];
2041
2050
  case 74:
2042
2051
  _2.sent();
2043
- return [4 /*yield*/, this._playheadState.set("vodMediaSeqAudio", 0)];
2052
+ return [4 /*yield*/, this._playheadState.set("vodMediaSeqAudio", 0, isLeader)];
2044
2053
  case 75:
2045
2054
  _2.sent();
2046
- return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now())];
2055
+ return [4 /*yield*/, this._playheadState.set("playheadRef", Date.now(), isLeader)];
2047
2056
  case 76:
2048
2057
  _2.sent();
2049
2058
  // 4) Log to debug and cloudwatch
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "4.0.9",
3
+ "version": "4.0.11",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "nock": "^13.1.1",
35
35
  "node-fetch": "^2.6.1",
36
36
  "promise.allsettled": "^1.0.4",
37
- "redis": "^3.0.2",
37
+ "redis": "3.1.0",
38
38
  "request": ">=2.88.0",
39
39
  "restify": "9.0.0",
40
40
  "restify-errors": "^8.0.2",
@@ -31,8 +31,12 @@ class SharedPlayheadState {
31
31
  return await this.store.getValues(this.sessionId, keys);
32
32
  }
33
33
 
34
- async set(key, value) {
35
- return await this.store.set(this.sessionId, key, value);
34
+ async set(key, value, isLeader) {
35
+ if (isLeader) {
36
+ return await this.store.set(this.sessionId, key, value);
37
+ } else {
38
+ return await this.store.get(this.sessionId, key);
39
+ }
36
40
  }
37
41
 
38
42
  async setState(newState) {
@@ -1,4 +1,4 @@
1
- const redis = require("redis");
1
+ const { createClient } = require("redis");
2
2
  const debug = require("debug")("redis-state-store");
3
3
 
4
4
  const DEFAULT_VOLATILE_KEY_TTL = 5; // Timeout so it should not expire within one normal increment iteration (in seconds)
@@ -16,7 +16,7 @@ class RedisStateStore {
16
16
  debug(`Overriding default, volatileKeyTTL=${opts.volatileKeyTTL}s`);
17
17
  this.volatileKeyTTL = opts.volatileKeyTTL;
18
18
  }
19
- this.client = redis.createClient(opts.redisUrl);
19
+ this.client = createClient(opts.redisUrl);
20
20
  }
21
21
 
22
22
  async initAsync(id, initData) {
@@ -53,10 +53,12 @@ class RedisStateStore {
53
53
  }
54
54
 
55
55
  async getAsync(id, key) {
56
+ const startMs = Date.now();
56
57
  const storeKey = "" + this.keyPrefix + id + key;
57
58
  const getAsync = new Promise((resolve, reject) => {
58
59
  this.client.get(storeKey, (err, reply) => {
59
- debug(`REDIS get ${storeKey}:${reply ? reply.length + " chars" : "null"}`);
60
+ const ioTimeMs = Date.now() - startMs;
61
+ debug(`REDIS get ${storeKey}:${reply ? reply.length + " chars" : "null"} (${ioTimeMs}ms) ${ioTimeMs > 1000 ? 'REDISSLOW!' : ''}`);
60
62
  if (!err) {
61
63
  resolve(JSON.parse(reply));
62
64
  } else {
@@ -69,10 +71,12 @@ class RedisStateStore {
69
71
  }
70
72
 
71
73
  async setAsync(id, key, value) {
74
+ const startMs = Date.now();
72
75
  const storeKey = "" + this.keyPrefix + id + key;
73
76
  const setAsync = new Promise((resolve, reject) => {
74
77
  this.client.set(storeKey, JSON.stringify(value), (err, res) => {
75
- debug(`REDIS set ${storeKey}: ${res}`);
78
+ const ioTimeMs = Date.now() - startMs;
79
+ debug(`REDIS set ${storeKey}: ${res} (${ioTimeMs}ms) ${ioTimeMs > 1000 ? 'REDISSLOW!' : ''}`);
76
80
  if (!err) {
77
81
  resolve(value);
78
82
  } else {
@@ -101,9 +105,12 @@ class RedisStateStore {
101
105
  }
102
106
 
103
107
  async removeAsync(id, key) {
108
+ const startMs = Date.now();
104
109
  const storeKey = "" + this.keyPrefix + id + key;
105
110
  const delAsync = new Promise((resolve, reject) => {
106
111
  this.client.del(storeKey, (err, res) => {
112
+ const ioTimeMs = Date.now() - startMs;
113
+ debug(`REDIS remove ${storeKey}: (${ioTimeMs}ms) ${ioTimeMs > 1000 ? 'REDISSLOW!' : ''}`);
107
114
  if (!err) {
108
115
  resolve();
109
116
  } else {
package/engine/server.ts CHANGED
@@ -357,7 +357,9 @@ export class ChannelEngine {
357
357
  }
358
358
  }
359
359
  }
360
- StreamSwitchLoop(this.streamSwitchTimeIntervalMs);
360
+ if (this.streamSwitchManager) {
361
+ StreamSwitchLoop(this.streamSwitchTimeIntervalMs);
362
+ }
361
363
  }
362
364
 
363
365
  async updateStreamSwitchAsync() {
@@ -710,19 +712,25 @@ export class ChannelEngine {
710
712
  const sessionLive = sessionsLive[req.params[1]];
711
713
  if (session && sessionLive) {
712
714
  try {
713
- while (switcherStatus[req.params[1]] === null || switcherStatus[req.params[1]] === undefined) {
714
- debug(`[${req.params[1]}]: (${switcherStatus[req.params[1]]}) Waiting for streamSwitcher to respond`);
715
- await timer(500);
716
- }
717
715
  let body = null;
718
- debug(`switcherStatus[${req.params[1]}]=[${switcherStatus[req.params[1]]}]`);
719
- if (switcherStatus[req.params[1]]) {
720
- debug(`[${req.params[1]}]: Responding with Live-stream manifest`);
721
- body = await sessionLive.getCurrentMediaManifestAsync(req.params[0]);
722
- } else {
716
+ if (!this.streamSwitchManager) {
723
717
  debug(`[${req.params[1]}]: Responding with VOD2Live manifest`);
724
718
  body = await session.getCurrentMediaManifestAsync(req.params[0], req.headers["x-playback-session-id"]);
719
+ } else {
720
+ while (switcherStatus[req.params[1]] === null || switcherStatus[req.params[1]] === undefined) {
721
+ debug(`[${req.params[1]}]: (${switcherStatus[req.params[1]]}) Waiting for streamSwitcher to respond`);
722
+ await timer(500);
723
+ }
724
+ debug(`switcherStatus[${req.params[1]}]=[${switcherStatus[req.params[1]]}]`);
725
+ if (switcherStatus[req.params[1]]) {
726
+ debug(`[${req.params[1]}]: Responding with Live-stream manifest`);
727
+ body = await sessionLive.getCurrentMediaManifestAsync(req.params[0]);
728
+ } else {
729
+ debug(`[${req.params[1]}]: Responding with VOD2Live manifest`);
730
+ body = await session.getCurrentMediaManifestAsync(req.params[0], req.headers["x-playback-session-id"]);
731
+ }
725
732
  }
733
+
726
734
  //verbose(`[${session.sessionId}] body=`);
727
735
  //verbose(body);
728
736
  res.sendRaw(200, Buffer.from(body, 'utf8'), {
package/engine/session.js CHANGED
@@ -185,7 +185,7 @@ class Session {
185
185
  debug(`[${this._sessionId}]: I am the leader and updated tick interval to ${tickInterval} sec`);
186
186
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
187
187
  { event: 'tickIntervalUpdated', channel: this._sessionId, tickIntervalSec: tickInterval });
188
- this._playheadState.set("tickInterval", tickInterval);
188
+ this._playheadState.set("tickInterval", tickInterval, isLeader);
189
189
  } else if (state == PlayheadState.STOPPED) {
190
190
  debug(`[${this._sessionId}]: Stopping playhead`);
191
191
  return;
@@ -251,14 +251,14 @@ class Session {
251
251
  debug(`[${this._sessionId}]: (${(new Date()).toISOString()}) ${timeSpentInIncrement}sec in increment. Next tick in ${tickInterval} seconds`)
252
252
  await timer((tickInterval * 1000) - 50);
253
253
  const tsTickEnd = Date.now();
254
- await this._playheadState.set("tickMs", (tsTickEnd - tsIncrementBegin));
254
+ await this._playheadState.set("tickMs", (tsTickEnd - tsIncrementBegin), isLeader);
255
255
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
256
256
  { event: 'tickInterval', channel: this._sessionId, tickTimeMs: (tsTickEnd - tsIncrementBegin) });
257
257
  if (this.alwaysNewSegments) {
258
258
  // Use dynamic base-tickInterval. Set according to duration of latest segment.
259
259
  const lastDuration = await this._getLastDuration(manifest);
260
260
  const nextTickInterval = lastDuration < 2 ? 2 : lastDuration;
261
- await this._playheadState.set("tickInterval", nextTickInterval);
261
+ await this._playheadState.set("tickInterval", nextTickInterval, isLeader);
262
262
  cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "tickIntervalUpdated", channel: this._sessionId, tickIntervalSec: nextTickInterval });
263
263
  }
264
264
  }
@@ -280,8 +280,9 @@ class Session {
280
280
  }
281
281
 
282
282
  async stopPlayheadAsync() {
283
+ const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
283
284
  debug(`[${this._sessionId}]: Stopping playhead consumer`);
284
- await this._playheadState.set("state", PlayheadState.STOPPED);
285
+ await this._playheadState.set("state", PlayheadState.STOPPED, isLeader);
285
286
  }
286
287
 
287
288
  async getStatusAsync() {
@@ -771,10 +772,13 @@ class Session {
771
772
  this.isSwitchingBackToV2L = false;
772
773
  }
773
774
 
774
- playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
775
- playheadState.mediaSeqAudio = await this._playheadState.set("mediaSeqAudio", sessionState.mediaSeqAudio);
776
- playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
777
- playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
775
+ if (isLeader) {
776
+ debug(`[${this._sessionId}]: I am the leader, updating PlayheadState values`);
777
+ }
778
+ playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq, isLeader);
779
+ playheadState.mediaSeqAudio = await this._playheadState.set("mediaSeqAudio", sessionState.mediaSeqAudio, isLeader);
780
+ playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo, isLeader);
781
+ playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio, isLeader);
778
782
 
779
783
  if (currentVod.sequenceAlwaysContainNewSegments) {
780
784
  const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
@@ -1077,6 +1081,7 @@ class Session {
1077
1081
  lastDiscontinuity = currentVod.getLastDiscontinuity();
1078
1082
  lastDiscontinuityAudio = currentVod.getLastDiscontinuityAudio();
1079
1083
  }
1084
+ const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
1080
1085
  await this._sessionState.set("vodMediaSeqVideo", 0);
1081
1086
  await this._sessionState.set("vodMediaSeqAudio", 0);
1082
1087
  await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
@@ -1086,7 +1091,7 @@ class Session {
1086
1091
  await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
1087
1092
  await this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio);
1088
1093
  await this._sessionState.set("slateCount", sessionState.slateCount + 1);
1089
- await this._playheadState.set("playheadRef", Date.now());
1094
+ await this._playheadState.set("playheadRef", Date.now(), isLeader);
1090
1095
 
1091
1096
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'slateInserted', channel: this._sessionId });
1092
1097
 
@@ -1171,7 +1176,7 @@ class Session {
1171
1176
  sessionState.discSeqAudio = await this._sessionState.set("discSeqAudio", 0);
1172
1177
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
1173
1178
  sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
1174
- await this._playheadState.set("playheadRef", Date.now());
1179
+ await this._playheadState.set("playheadRef", Date.now(), isLeader);
1175
1180
  this.produceEvent({
1176
1181
  type: 'NOW_PLAYING',
1177
1182
  data: {
@@ -1340,8 +1345,8 @@ class Session {
1340
1345
  await this._sessionState.remove("nextVod");
1341
1346
  sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1342
1347
  this.leaderIsSettingNextVod = false;
1343
- await this._playheadState.set("playheadRef", Date.now());
1344
- await this._playheadState.set("diffCompensation", this.diffCompensation);
1348
+ await this._playheadState.set("playheadRef", Date.now(), isLeader);
1349
+ await this._playheadState.set("diffCompensation", this.diffCompensation, isLeader);
1345
1350
  debug(`[${this._sessionId}]: sharing durrent vods diffCompensation=${this.diffCompensation}`);
1346
1351
  this.produceEvent({
1347
1352
  type: 'NOW_PLAYING',
@@ -1418,7 +1423,7 @@ class Session {
1418
1423
  return;
1419
1424
  }
1420
1425
  await this._sessionState.set("mediaSeq", mSeq);
1421
- await this._playheadState.set("mediaSeq", mSeq);
1426
+ await this._playheadState.set("mediaSeq", mSeq, isLeader);
1422
1427
  await this._sessionState.set("discSeq", dSeq);
1423
1428
  // TODO: support demux^
1424
1429
  debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
@@ -1439,9 +1444,9 @@ class Session {
1439
1444
  await this._sessionState.set("vodReloaded", 1);
1440
1445
  await this._sessionState.set("vodMediaSeqVideo", 0);
1441
1446
  await this._sessionState.set("vodMediaSeqAudio", 0);
1442
- await this._playheadState.set("vodMediaSeqVideo", 0);
1443
- await this._playheadState.set("vodMediaSeqAudio", 0);
1444
- await this._playheadState.set("playheadRef", Date.now());
1447
+ await this._playheadState.set("vodMediaSeqVideo", 0, isLeader);
1448
+ await this._playheadState.set("vodMediaSeqAudio", 0, isLeader);
1449
+ await this._playheadState.set("playheadRef", Date.now(), isLeader);
1445
1450
  // 4) Log to debug and cloudwatch
1446
1451
  debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
1447
1452
  debug(`[${this._sessionId}]: next VOD Reloaded (${currentVod.getDeltaTimes()})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "4.0.9",
3
+ "version": "4.0.11",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "nock": "^13.1.1",
35
35
  "node-fetch": "^2.6.1",
36
36
  "promise.allsettled": "^1.0.4",
37
- "redis": "^3.0.2",
37
+ "redis": "3.1.0",
38
38
  "request": ">=2.88.0",
39
39
  "restify": "9.0.0",
40
40
  "restify-errors": "^8.0.2",