eyevinn-channel-engine 4.3.0 → 4.3.2

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.
@@ -5,6 +5,7 @@ declare class MemcachedStateStore {
5
5
  client: any;
6
6
  initAsync(id: any, initData: any): Promise<{}>;
7
7
  resetAsync(id: any, initData: any): Promise<void>;
8
+ resetAllAsync(): Promise<void>;
8
9
  getAsync(id: any, key: any): Promise<any>;
9
10
  setAsync(id: any, key: any, value: any): Promise<any>;
10
11
  setVolatileAsync(id: any, key: any, value: any): Promise<void>;
@@ -112,6 +112,14 @@ var MemcachedStateStore = /** @class */ (function () {
112
112
  });
113
113
  });
114
114
  };
115
+ MemcachedStateStore.prototype.resetAllAsync = function () {
116
+ return __awaiter(this, void 0, void 0, function () {
117
+ return __generator(this, function (_a) {
118
+ console.error("Shared Storage Reset Failed.\nMemcache-client: Flush All Command Not Implemented Yet");
119
+ return [2 /*return*/];
120
+ });
121
+ });
122
+ };
115
123
  MemcachedStateStore.prototype.getAsync = function (id, key) {
116
124
  return __awaiter(this, void 0, void 0, function () {
117
125
  var storeKey, data;
@@ -4,6 +4,8 @@ declare class MemoryStateStore {
4
4
  sharedStates: {};
5
5
  globalSharedStates: {};
6
6
  initAsync(id: any, initData: any): Promise<any>;
7
+ resetAsync(id: any, initData: any): Promise<void>;
8
+ resetAllAsync(): Promise<void>;
7
9
  getAsync(id: any, key: any): Promise<any>;
8
10
  setAsync(id: any, key: any, value: any): Promise<any>;
9
11
  setVolatileAsync(id: any, key: any, value: any): Promise<any>;
@@ -54,6 +54,29 @@ var MemoryStateStore = /** @class */ (function () {
54
54
  });
55
55
  });
56
56
  };
57
+ MemoryStateStore.prototype.resetAsync = function (id, initData) {
58
+ return __awaiter(this, void 0, void 0, function () {
59
+ return __generator(this, function (_a) {
60
+ switch (_a.label) {
61
+ case 0:
62
+ this.sharedStates[id] = null;
63
+ return [4 /*yield*/, this.initAsync(id, initData)];
64
+ case 1:
65
+ _a.sent();
66
+ return [2 /*return*/];
67
+ }
68
+ });
69
+ });
70
+ };
71
+ MemoryStateStore.prototype.resetAllAsync = function () {
72
+ return __awaiter(this, void 0, void 0, function () {
73
+ return __generator(this, function (_a) {
74
+ this.sharedStates = {};
75
+ this.globalSharedStates = {};
76
+ return [2 /*return*/];
77
+ });
78
+ });
79
+ };
57
80
  MemoryStateStore.prototype.getAsync = function (id, key) {
58
81
  return __awaiter(this, void 0, void 0, function () {
59
82
  var value;
@@ -62,6 +85,9 @@ var MemoryStateStore = /** @class */ (function () {
62
85
  value = this.globalSharedStates[key];
63
86
  }
64
87
  else {
88
+ if (!this.sharedStates[id]) {
89
+ return [2 /*return*/, null];
90
+ }
65
91
  value = this.sharedStates[id][key];
66
92
  }
67
93
  return [2 /*return*/, value];
@@ -75,6 +101,9 @@ var MemoryStateStore = /** @class */ (function () {
75
101
  this.globalSharedStates[key] = value;
76
102
  }
77
103
  else {
104
+ if (!this.sharedStates[id]) {
105
+ this.sharedStates[id] = {};
106
+ }
78
107
  this.sharedStates[id][key] = value;
79
108
  return [2 /*return*/, this.sharedStates[id][key]];
80
109
  }
@@ -6,6 +6,7 @@ declare class RedisStateStore {
6
6
  client: any;
7
7
  initAsync(id: any, initData: any): Promise<{}>;
8
8
  resetAsync(id: any, initData: any): Promise<void>;
9
+ resetAllAsync(): Promise<void>;
9
10
  getAsync(id: any, key: any): Promise<any>;
10
11
  setAsync(id: any, key: any, value: any): Promise<any>;
11
12
  setVolatileAsync(id: any, key: any, value: any): Promise<any>;
@@ -104,6 +104,21 @@ var RedisStateStore = /** @class */ (function () {
104
104
  });
105
105
  };
106
106
  RedisStateStore.prototype.resetAsync = function (id, initData) {
107
+ return __awaiter(this, void 0, void 0, function () {
108
+ return __generator(this, function (_a) {
109
+ switch (_a.label) {
110
+ case 0: return [4 /*yield*/, this.setAsync(id, "_initiated", false)];
111
+ case 1:
112
+ _a.sent();
113
+ return [4 /*yield*/, this.initAsync(id, initData)];
114
+ case 2:
115
+ _a.sent();
116
+ return [2 /*return*/];
117
+ }
118
+ });
119
+ });
120
+ };
121
+ RedisStateStore.prototype.resetAllAsync = function () {
107
122
  return __awaiter(this, void 0, void 0, function () {
108
123
  var resetAsync;
109
124
  var _this = this;
@@ -170,7 +185,7 @@ var RedisStateStore = /** @class */ (function () {
170
185
  setAsync = new Promise(function (resolve, reject) {
171
186
  _this.client.set(storeKey, JSON.stringify(value), function (err, res) {
172
187
  var ioTimeMs = Date.now() - startMs;
173
- debug("REDIS set ".concat(storeKey, ": ").concat(res, " (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? 'REDISSLOW!' : ''));
188
+ debug("REDIS set ".concat(storeKey, ": ").concat(res, " (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? "REDISSLOW!" : ""));
174
189
  if (!err) {
175
190
  resolve(value);
176
191
  }
@@ -226,7 +241,7 @@ var RedisStateStore = /** @class */ (function () {
226
241
  delAsync = new Promise(function (resolve, reject) {
227
242
  _this.client.del(storeKey, function (err, res) {
228
243
  var ioTimeMs = Date.now() - startMs;
229
- debug("REDIS remove ".concat(storeKey, ": (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? 'REDISSLOW!' : ''));
244
+ debug("REDIS remove ".concat(storeKey, ": (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? "REDISSLOW!" : ""));
230
245
  if (!err) {
231
246
  resolve();
232
247
  }
@@ -29,6 +29,7 @@ export interface ChannelEngineOpts {
29
29
  noSessionDataTags?: boolean;
30
30
  volatileKeyTTL?: number;
31
31
  autoCreateSession?: boolean;
32
+ sessionResetKey?: string;
32
33
  }
33
34
  export interface VodRequest {
34
35
  sessionId: string;
@@ -147,6 +148,7 @@ export declare class ChannelEngine {
147
148
  private adCopyMgrUri?;
148
149
  private adXchangeUri?;
149
150
  private autoCreateSession;
151
+ private sessionResetKey;
150
152
  constructor(assetMgr: IAssetManager, options?: ChannelEngineOpts);
151
153
  updateStreamSwitchAsync(): Promise<void>;
152
154
  updateChannelsAsync(channelMgr: any, options: any): Promise<void>;
@@ -172,6 +174,7 @@ export declare class ChannelEngine {
172
174
  _handleStatus(req: any, res: any, next: any): Promise<void>;
173
175
  _handleAggregatedSessionHealth(req: any, res: any, next: any): Promise<void>;
174
176
  _handleSessionHealth(req: any, res: any, next: any): Promise<void>;
177
+ _handleSessionsReset(req: any, res: any, next: any): Promise<void>;
175
178
  _handleSessionReset(req: any, res: any, next: any): Promise<void>;
176
179
  _gracefulErrorHandler(errMsg: any): any;
177
180
  _errorHandler(errMsg: any): any;
@@ -71,6 +71,7 @@ var ChannelEngine = /** @class */ (function () {
71
71
  function ChannelEngine(assetMgr, options) {
72
72
  var _this = this;
73
73
  this.autoCreateSession = false;
74
+ this.sessionResetKey = "";
74
75
  this.options = options;
75
76
  if (options && options.adCopyMgrUri) {
76
77
  this.adCopyMgrUri = options.adCopyMgrUri;
@@ -85,6 +86,10 @@ var ChannelEngine = /** @class */ (function () {
85
86
  this.useVTTSubtitles = (options && options.useVTTSubtitles) ? options.useVTTSubtitles : false;
86
87
  this.dummySubtitleEndpoint = (options && options.dummySubtitleEndpoint) ? options.dummySubtitleEndpoint : DefaultDummySubtitleEndpointPath;
87
88
  this.subtitleSliceEndpoint = (options && options.subtitleSliceEndpoint) ? options.subtitleSliceEndpoint : DefaultSubtitleSpliceEndpointPath;
89
+ this.sessionResetKey = "";
90
+ if (options && options.sessionResetKey) {
91
+ this.sessionResetKey = options.sessionResetKey;
92
+ }
88
93
  this.alwaysNewSegments = false;
89
94
  if (options && options.alwaysNewSegments) {
90
95
  this.alwaysNewSegments = true;
@@ -262,7 +267,8 @@ var ChannelEngine = /** @class */ (function () {
262
267
  this.server.get('/status/:sessionId', this._handleStatus.bind(this));
263
268
  this.server.get('/health', this._handleAggregatedSessionHealth.bind(this));
264
269
  this.server.get('/health/:sessionId', this._handleSessionHealth.bind(this));
265
- this.server.get('/reset', this._handleSessionReset.bind(this));
270
+ this.server.get('/reset', this._handleSessionsReset.bind(this));
271
+ this.server.get('/reset/:sessionId', this._handleSessionReset.bind(this));
266
272
  this.server.get(DefaultDummySubtitleEndpointPath, this._handleDummySubtitleEndpoint.bind(this));
267
273
  this.server.get(DefaultSubtitleSpliceEndpointPath, this._handleSubtitleSliceEndpoint.bind(this));
268
274
  this.server.on('NotFound', function (req, res, err, next) {
@@ -445,6 +451,7 @@ var ChannelEngine = /** @class */ (function () {
445
451
  subtitleSliceEndpoint: this.subtitleSliceEndpoint,
446
452
  useVTTSubtitles: this.useVTTSubtitles,
447
453
  alwaysNewSegments: options.alwaysNewSegments,
454
+ sessionResetKey: options.sessionResetKey,
448
455
  partialStoreHLSVod: options.partialStoreHLSVod,
449
456
  alwaysMapBandwidthByNearest: options.alwaysMapBandwidthByNearest,
450
457
  noSessionDataTags: options.noSessionDataTags,
@@ -1013,7 +1020,7 @@ var ChannelEngine = /** @class */ (function () {
1013
1020
  return __generator(this, function (_a) {
1014
1021
  debug("req.url=".concat(req.url));
1015
1022
  try {
1016
- body = "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:0,LOCAL:00:00:00.000";
1023
+ body = "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:0,LOCAL:00:00:00.000\n\n";
1017
1024
  res.sendRaw(200, Buffer.from(body, 'utf8'), {
1018
1025
  "Content-Type": "text/vtt",
1019
1026
  "Access-Control-Allow-Origin": "*",
@@ -1275,13 +1282,22 @@ var ChannelEngine = /** @class */ (function () {
1275
1282
  });
1276
1283
  });
1277
1284
  };
1278
- ChannelEngine.prototype._handleSessionReset = function (req, res, next) {
1285
+ ChannelEngine.prototype._handleSessionsReset = function (req, res, next) {
1279
1286
  return __awaiter(this, void 0, void 0, function () {
1280
1287
  var sessionResets, _i, _a, sessionId, session, sessionLive, err;
1281
1288
  return __generator(this, function (_b) {
1282
1289
  switch (_b.label) {
1283
1290
  case 0:
1284
1291
  debug("req.url=".concat(req.url));
1292
+ if (this.sessionResetKey && req.query.key !== this.sessionResetKey) {
1293
+ res.sendRaw(403, JSON.stringify({ "message": "Invalid Session-Reset-Key" }), {
1294
+ "Content-Type": "application/json",
1295
+ "Access-Control-Allow-Origin": "*",
1296
+ "Cache-Control": "no-cache",
1297
+ });
1298
+ next();
1299
+ return [2 /*return*/];
1300
+ }
1285
1301
  sessionResets = [];
1286
1302
  _i = 0, _a = Object.keys(sessions);
1287
1303
  _b.label = 1;
@@ -1314,6 +1330,69 @@ var ChannelEngine = /** @class */ (function () {
1314
1330
  });
1315
1331
  });
1316
1332
  };
1333
+ ChannelEngine.prototype._handleSessionReset = function (req, res, next) {
1334
+ return __awaiter(this, void 0, void 0, function () {
1335
+ var sessionId, sessionResets, session, sessionLive, e_1, err;
1336
+ return __generator(this, function (_a) {
1337
+ switch (_a.label) {
1338
+ case 0:
1339
+ debug("req.url=".concat(req.url));
1340
+ if (this.sessionResetKey && req.query.key !== this.sessionResetKey) {
1341
+ res.sendRaw(403, JSON.stringify({ "message": "Invalid Session-Reset-Key" }), {
1342
+ "Content-Type": "application/json",
1343
+ "Access-Control-Allow-Origin": "*",
1344
+ "Cache-Control": "no-cache",
1345
+ });
1346
+ next();
1347
+ return [2 /*return*/];
1348
+ }
1349
+ _a.label = 1;
1350
+ case 1:
1351
+ _a.trys.push([1, 5, , 6]);
1352
+ sessionId = void 0;
1353
+ if (req.params && req.params.sessionId) {
1354
+ sessionId = req.params.sessionId;
1355
+ }
1356
+ sessionResets = [];
1357
+ session = sessions[sessionId];
1358
+ sessionLive = sessionsLive[sessionId];
1359
+ if (!(session && sessionLive)) return [3 /*break*/, 3];
1360
+ return [4 /*yield*/, session.resetAsync(sessionId)];
1361
+ case 2:
1362
+ _a.sent();
1363
+ sessionResets.push(sessionId);
1364
+ return [3 /*break*/, 4];
1365
+ case 3:
1366
+ res.sendRaw(400, JSON.stringify({ "message": "Invalid Session ID" }), {
1367
+ "Content-Type": "application/json",
1368
+ "Access-Control-Allow-Origin": "*",
1369
+ "Cache-Control": "no-cache",
1370
+ });
1371
+ next();
1372
+ return [2 /*return*/];
1373
+ case 4:
1374
+ res.sendRaw(200, JSON.stringify({ "status": "ok", "instanceId": this.instanceId, "resets": sessionResets }), {
1375
+ "Content-Type": "application/json",
1376
+ "Access-Control-Allow-Origin": "*",
1377
+ "Cache-Control": "no-cache",
1378
+ });
1379
+ next();
1380
+ return [3 /*break*/, 6];
1381
+ case 5:
1382
+ e_1 = _a.sent();
1383
+ res.sendRaw(500, JSON.stringify({ "error": e_1 }), {
1384
+ "Content-Type": "application/json",
1385
+ "Access-Control-Allow-Origin": "*",
1386
+ "Cache-Control": "no-cache",
1387
+ });
1388
+ err = new errs.NotFoundError('Session Reset Failed');
1389
+ next(err);
1390
+ return [3 /*break*/, 6];
1391
+ case 6: return [2 /*return*/];
1392
+ }
1393
+ });
1394
+ });
1395
+ };
1317
1396
  ChannelEngine.prototype._gracefulErrorHandler = function (errMsg) {
1318
1397
  console.error(errMsg);
1319
1398
  var err = new errs.NotFoundError(errMsg);
@@ -83,7 +83,7 @@ declare class Session {
83
83
  } | {
84
84
  sessionId: any;
85
85
  }>;
86
- resetAsync(): Promise<void>;
86
+ resetAsync(id: any): Promise<void>;
87
87
  getSessionState(): Promise<any>;
88
88
  getTruncatedVodSegments(vodUri: any, duration: any): Promise<any>;
89
89
  setCurrentMediaSequenceSegments(segments: any, mSeqOffset: any, reloadBehind: any): Promise<void>;
@@ -454,15 +454,24 @@ var Session = /** @class */ (function () {
454
454
  });
455
455
  });
456
456
  };
457
- Session.prototype.resetAsync = function () {
457
+ Session.prototype.resetAsync = function (id) {
458
458
  return __awaiter(this, void 0, void 0, function () {
459
459
  return __generator(this, function (_a) {
460
460
  switch (_a.label) {
461
- case 0: return [4 /*yield*/, this._sessionStateStore.reset(this._sessionId)];
461
+ case 0:
462
+ if (!id) return [3 /*break*/, 3];
463
+ return [4 /*yield*/, this._sessionStateStore.reset(id)];
462
464
  case 1:
463
465
  _a.sent();
464
- return [4 /*yield*/, this._playheadStateStore.reset(this._sessionId)];
466
+ return [4 /*yield*/, this._playheadStateStore.reset(id)];
465
467
  case 2:
468
+ _a.sent();
469
+ _a.label = 3;
470
+ case 3: return [4 /*yield*/, this._sessionStateStore.resetAll()];
471
+ case 4:
472
+ _a.sent();
473
+ return [4 /*yield*/, this._playheadStateStore.resetAll()];
474
+ case 5:
466
475
  _a.sent();
467
476
  return [2 /*return*/];
468
477
  }
@@ -10,6 +10,7 @@ declare class SharedStateStore {
10
10
  isShared(): boolean;
11
11
  init(id: any): Promise<void>;
12
12
  reset(id: any): Promise<void>;
13
+ resetAll(): Promise<void>;
13
14
  get(id: any, key: any): Promise<any>;
14
15
  set(id: any, key: any, value: any): Promise<any>;
15
16
  setVolatile(id: any, key: any, value: any): Promise<any>;
@@ -87,6 +87,18 @@ var SharedStateStore = /** @class */ (function () {
87
87
  });
88
88
  });
89
89
  };
90
+ SharedStateStore.prototype.resetAll = function () {
91
+ return __awaiter(this, void 0, void 0, function () {
92
+ return __generator(this, function (_a) {
93
+ switch (_a.label) {
94
+ case 0: return [4 /*yield*/, this.store.resetAllAsync()];
95
+ case 1:
96
+ _a.sent();
97
+ return [2 /*return*/];
98
+ }
99
+ });
100
+ });
101
+ };
90
102
  SharedStateStore.prototype.get = function (id, key) {
91
103
  return __awaiter(this, void 0, void 0, function () {
92
104
  var data;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "@eyevinn/hls-repeat": "^0.2.0",
32
32
  "@eyevinn/hls-truncate": "^0.3.0",
33
- "@eyevinn/hls-vodtolive": "^4.0.0",
33
+ "@eyevinn/hls-vodtolive": "^4.0.1",
34
34
  "@eyevinn/m3u8": "^0.5.3",
35
35
  "abort-controller": "^3.0.0",
36
36
  "debug": "^3.2.7",
@@ -36,6 +36,10 @@ class MemcachedStateStore {
36
36
  await this.initAsync(id, initData);
37
37
  }
38
38
 
39
+ async resetAllAsync() {
40
+ console.error("Shared Storage Reset Failed.\nMemcache-client: Flush All Command Not Implemented Yet");
41
+ }
42
+
39
43
  async getAsync(id, key) {
40
44
  const storeKey = "" + this.keyPrefix + id + key;
41
45
  const data = await this.client.get(storeKey);
@@ -9,18 +9,31 @@ class MemoryStateStore {
9
9
  async initAsync(id, initData) {
10
10
  if (!this.sharedStates[id]) {
11
11
  this.sharedStates[id] = {};
12
- Object.keys(initData).forEach(key => {
12
+ Object.keys(initData).forEach((key) => {
13
13
  this.sharedStates[id][key] = initData[key];
14
14
  });
15
15
  }
16
16
  return this.sharedStates[id];
17
17
  }
18
18
 
19
+ async resetAsync(id, initData) {
20
+ this.sharedStates[id] = null;
21
+ await this.initAsync(id, initData);
22
+ }
23
+
24
+ async resetAllAsync() {
25
+ this.sharedStates = {};
26
+ this.globalSharedStates = {};
27
+ }
28
+
19
29
  async getAsync(id, key) {
20
30
  let value;
21
31
  if (id === "" || id === null) {
22
32
  value = this.globalSharedStates[key];
23
33
  } else {
34
+ if (!this.sharedStates[id]) {
35
+ return null;
36
+ }
24
37
  value = this.sharedStates[id][key];
25
38
  }
26
39
  return value;
@@ -30,6 +43,9 @@ class MemoryStateStore {
30
43
  if (id === "" || id === null) {
31
44
  this.globalSharedStates[key] = value;
32
45
  } else {
46
+ if (!this.sharedStates[id]) {
47
+ this.sharedStates[id] = {};
48
+ }
33
49
  this.sharedStates[id][key] = value;
34
50
  return this.sharedStates[id][key];
35
51
  }
@@ -41,7 +57,7 @@ class MemoryStateStore {
41
57
 
42
58
  async removeAsync(id, key) {
43
59
  delete this.sharedStates[id][key];
44
- }
60
+ }
45
61
  }
46
62
 
47
63
  module.exports = MemoryStateStore;
@@ -14,7 +14,7 @@ class RedisStateStore {
14
14
  this.volatileKeyTTL = DEFAULT_VOLATILE_KEY_TTL;
15
15
  if (opts.volatileKeyTTL) {
16
16
  debug(`Overriding default, volatileKeyTTL=${opts.volatileKeyTTL}s`);
17
- this.volatileKeyTTL = opts.volatileKeyTTL;
17
+ this.volatileKeyTTL = opts.volatileKeyTTL;
18
18
  }
19
19
  this.client = createClient(opts.redisUrl);
20
20
  }
@@ -23,14 +23,14 @@ class RedisStateStore {
23
23
  const isInitiated = await this.getAsync(id, "_initiated");
24
24
  let data = {};
25
25
  if (!isInitiated) {
26
- for(const key of Object.keys(initData)) {
26
+ for (const key of Object.keys(initData)) {
27
27
  debug(`${this.keyPrefix}:${id}: Initiating key ${key} with init data`);
28
28
  data[key] = await this.setAsync(id, key, initData[key]);
29
29
  }
30
30
  await this.setAsync(id, "_initiated", true);
31
31
  } else {
32
32
  debug(`${this.keyPrefix}:${id}: Already initiated, not initiating with init data`);
33
- for(const key of Object.keys(initData)) {
33
+ for (const key of Object.keys(initData)) {
34
34
  debug(`${this.keyPrefix}:${id}: Initiating key ${key} with data from store`);
35
35
  data[key] = await this.getAsync(id, key);
36
36
  }
@@ -39,8 +39,13 @@ class RedisStateStore {
39
39
  }
40
40
 
41
41
  async resetAsync(id, initData) {
42
+ await this.setAsync(id, "_initiated", false);
43
+ await this.initAsync(id, initData);
44
+ }
45
+
46
+ async resetAllAsync() {
42
47
  const resetAsync = new Promise((resolve, reject) => {
43
- this.client.flushall((err, reply) => {
48
+ this.client.flushall((err, reply) => {
44
49
  if (!err) {
45
50
  console.log("Flushed Redis db: ", reply);
46
51
  resolve();
@@ -64,7 +69,7 @@ class RedisStateStore {
64
69
  } else {
65
70
  reject(err);
66
71
  }
67
- });
72
+ });
68
73
  });
69
74
  const data = await getAsync;
70
75
  return data;
@@ -76,7 +81,7 @@ class RedisStateStore {
76
81
  const setAsync = new Promise((resolve, reject) => {
77
82
  this.client.set(storeKey, JSON.stringify(value), (err, res) => {
78
83
  const ioTimeMs = Date.now() - startMs;
79
- debug(`REDIS set ${storeKey}: ${res} (${ioTimeMs}ms) ${ioTimeMs > 1000 ? 'REDISSLOW!' : ''}`);
84
+ debug(`REDIS set ${storeKey}: ${res} (${ioTimeMs}ms) ${ioTimeMs > 1000 ? "REDISSLOW!" : ""}`);
80
85
  if (!err) {
81
86
  resolve(value);
82
87
  } else {
@@ -110,7 +115,7 @@ class RedisStateStore {
110
115
  const delAsync = new Promise((resolve, reject) => {
111
116
  this.client.del(storeKey, (err, res) => {
112
117
  const ioTimeMs = Date.now() - startMs;
113
- debug(`REDIS remove ${storeKey}: (${ioTimeMs}ms) ${ioTimeMs > 1000 ? 'REDISSLOW!' : ''}`);
118
+ debug(`REDIS remove ${storeKey}: (${ioTimeMs}ms) ${ioTimeMs > 1000 ? "REDISSLOW!" : ""}`);
114
119
  if (!err) {
115
120
  resolve();
116
121
  } else {
package/engine/server.ts CHANGED
@@ -58,6 +58,7 @@ export interface ChannelEngineOpts {
58
58
  noSessionDataTags?: boolean;
59
59
  volatileKeyTTL?: number;
60
60
  autoCreateSession?: boolean;
61
+ sessionResetKey?:string;
61
62
  }
62
63
 
63
64
  interface StreamerOpts {
@@ -203,6 +204,7 @@ export class ChannelEngine {
203
204
  private adCopyMgrUri?: string;
204
205
  private adXchangeUri?: string;
205
206
  private autoCreateSession: boolean = false;
207
+ private sessionResetKey: string = "";
206
208
 
207
209
  constructor(assetMgr: IAssetManager, options?: ChannelEngineOpts) {
208
210
  this.options = options;
@@ -221,6 +223,10 @@ export class ChannelEngine {
221
223
  this.dummySubtitleEndpoint = (options && options.dummySubtitleEndpoint) ? options.dummySubtitleEndpoint : DefaultDummySubtitleEndpointPath;
222
224
  this.subtitleSliceEndpoint = (options && options.subtitleSliceEndpoint) ? options.subtitleSliceEndpoint : DefaultSubtitleSpliceEndpointPath;
223
225
 
226
+ this.sessionResetKey = "";
227
+ if (options && options.sessionResetKey) {
228
+ this.sessionResetKey = options.sessionResetKey;
229
+ }
224
230
  this.alwaysNewSegments = false;
225
231
  if (options && options.alwaysNewSegments) {
226
232
  this.alwaysNewSegments = true;
@@ -361,7 +367,8 @@ export class ChannelEngine {
361
367
  this.server.get('/status/:sessionId', this._handleStatus.bind(this));
362
368
  this.server.get('/health', this._handleAggregatedSessionHealth.bind(this));
363
369
  this.server.get('/health/:sessionId', this._handleSessionHealth.bind(this));
364
- this.server.get('/reset', this._handleSessionReset.bind(this));
370
+ this.server.get('/reset', this._handleSessionsReset.bind(this));
371
+ this.server.get('/reset/:sessionId', this._handleSessionReset.bind(this));
365
372
  this.server.get(DefaultDummySubtitleEndpointPath, this._handleDummySubtitleEndpoint.bind(this));
366
373
  this.server.get(DefaultSubtitleSpliceEndpointPath, this._handleSubtitleSliceEndpoint.bind(this));
367
374
 
@@ -466,6 +473,7 @@ export class ChannelEngine {
466
473
  subtitleSliceEndpoint: this.subtitleSliceEndpoint,
467
474
  useVTTSubtitles: this.useVTTSubtitles,
468
475
  alwaysNewSegments: options.alwaysNewSegments,
476
+ sessionResetKey: options.sessionResetKey,
469
477
  partialStoreHLSVod: options.partialStoreHLSVod,
470
478
  alwaysMapBandwidthByNearest: options.alwaysMapBandwidthByNearest,
471
479
  noSessionDataTags: options.noSessionDataTags,
@@ -820,7 +828,7 @@ export class ChannelEngine {
820
828
  async _handleDummySubtitleEndpoint(req,res,next) {
821
829
  debug(`req.url=${req.url}`);
822
830
  try {
823
- const body = `WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:0,LOCAL:00:00:00.000`;
831
+ const body = `WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:0,LOCAL:00:00:00.000\n\n`;
824
832
  res.sendRaw(200, Buffer.from(body, 'utf8'), {
825
833
  "Content-Type": "text/vtt",
826
834
  "Access-Control-Allow-Origin": "*",
@@ -1005,8 +1013,18 @@ export class ChannelEngine {
1005
1013
  }
1006
1014
  }
1007
1015
 
1008
- async _handleSessionReset(req, res, next) {
1016
+ async _handleSessionsReset(req, res, next) {
1009
1017
  debug(`req.url=${req.url}`);
1018
+ if (this.sessionResetKey && req.query.key !== this.sessionResetKey) {
1019
+ res.sendRaw(403, JSON.stringify({ "message": "Invalid Session-Reset-Key" }),
1020
+ {
1021
+ "Content-Type": "application/json",
1022
+ "Access-Control-Allow-Origin": "*",
1023
+ "Cache-Control": "no-cache",
1024
+ });
1025
+ next();
1026
+ return;
1027
+ }
1010
1028
  let sessionResets = [];
1011
1029
  for (const sessionId of Object.keys(sessions)) {
1012
1030
  const session = sessions[sessionId];
@@ -1027,6 +1045,60 @@ export class ChannelEngine {
1027
1045
  });
1028
1046
  }
1029
1047
 
1048
+ async _handleSessionReset(req, res, next) {
1049
+ debug(`req.url=${req.url}`);
1050
+ if (this.sessionResetKey && req.query.key !== this.sessionResetKey) {
1051
+ res.sendRaw(403, JSON.stringify({ "message": "Invalid Session-Reset-Key" }),
1052
+ {
1053
+ "Content-Type": "application/json",
1054
+ "Access-Control-Allow-Origin": "*",
1055
+ "Cache-Control": "no-cache",
1056
+ });
1057
+ next();
1058
+ return;
1059
+ }
1060
+ try {
1061
+ let sessionId;
1062
+ if (req.params && req.params.sessionId) {
1063
+ sessionId = req.params.sessionId;
1064
+ }
1065
+ let sessionResets = [];
1066
+ const session = sessions[sessionId];
1067
+ const sessionLive = sessionsLive[sessionId];
1068
+ if (session && sessionLive) {
1069
+ await session.resetAsync(sessionId);
1070
+ sessionResets.push(sessionId);
1071
+ } else {
1072
+ res.sendRaw(400, JSON.stringify({ "message": "Invalid Session ID" }),
1073
+ {
1074
+ "Content-Type": "application/json",
1075
+ "Access-Control-Allow-Origin": "*",
1076
+ "Cache-Control": "no-cache",
1077
+ });
1078
+ next();
1079
+ return;
1080
+ }
1081
+
1082
+ res.sendRaw(200, JSON.stringify({ "status": "ok", "instanceId": this.instanceId, "resets": sessionResets }),
1083
+ {
1084
+ "Content-Type": "application/json",
1085
+ "Access-Control-Allow-Origin": "*",
1086
+ "Cache-Control": "no-cache",
1087
+ });
1088
+ next();
1089
+ } catch (e) {
1090
+ res.sendRaw(500, JSON.stringify({ "error": e}),
1091
+ {
1092
+ "Content-Type": "application/json",
1093
+ "Access-Control-Allow-Origin": "*",
1094
+ "Cache-Control": "no-cache",
1095
+ });
1096
+ const err = new errs.NotFoundError('Session Reset Failed');
1097
+ next(err);
1098
+ }
1099
+ }
1100
+
1101
+
1030
1102
  _gracefulErrorHandler(errMsg) {
1031
1103
  console.error(errMsg);
1032
1104
  const err = new errs.NotFoundError(errMsg);
package/engine/session.js CHANGED
@@ -341,9 +341,13 @@ class Session {
341
341
  }
342
342
  }
343
343
 
344
- async resetAsync() {
345
- await this._sessionStateStore.reset(this._sessionId);
346
- await this._playheadStateStore.reset(this._sessionId);
344
+ async resetAsync(id) {
345
+ if (id) {
346
+ await this._sessionStateStore.reset(id);
347
+ await this._playheadStateStore.reset(id);
348
+ }
349
+ await this._sessionStateStore.resetAll();
350
+ await this._playheadStateStore.resetAll();
347
351
  }
348
352
 
349
353
  async getSessionState() {
@@ -38,6 +38,10 @@ class SharedStateStore {
38
38
  await this.store.resetAsync(id, this.initData);
39
39
  }
40
40
 
41
+ async resetAll() {
42
+ await this.store.resetAllAsync();
43
+ }
44
+
41
45
  async get(id, key) {
42
46
  //debug(`${this.type}:${id}:${key} Reading from shared store`);
43
47
  let data = await this.store.getAsync(id, key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "@eyevinn/hls-repeat": "^0.2.0",
32
32
  "@eyevinn/hls-truncate": "^0.3.0",
33
- "@eyevinn/hls-vodtolive": "^4.0.0",
33
+ "@eyevinn/hls-vodtolive": "^4.0.1",
34
34
  "@eyevinn/m3u8": "^0.5.3",
35
35
  "abort-controller": "^3.0.0",
36
36
  "debug": "^3.2.7",