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.
- package/dist/engine/playhead_state.d.ts +1 -1
- package/dist/engine/playhead_state.js +6 -2
- package/dist/engine/redis_state_store.js +14 -7
- package/dist/engine/server.js +30 -23
- package/dist/engine/session.js +40 -31
- package/dist/package.json +2 -2
- package/engine/playhead_state.js +6 -2
- package/engine/redis_state_store.js +11 -4
- package/engine/server.ts +18 -10
- package/engine/session.js +21 -16
- package/package.json +2 -2
|
@@ -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:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/engine/server.js
CHANGED
|
@@ -329,7 +329,9 @@ var ChannelEngine = /** @class */ (function () {
|
|
|
329
329
|
}
|
|
330
330
|
});
|
|
331
331
|
}); };
|
|
332
|
-
|
|
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*/,
|
|
892
|
+
if (!(session && sessionLive)) return [3 /*break*/, 12];
|
|
891
893
|
_a.label = 1;
|
|
892
894
|
case 1:
|
|
893
|
-
_a.trys.push([1,
|
|
894
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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*/,
|
|
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*/,
|
|
928
|
-
case
|
|
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*/,
|
|
932
|
-
case
|
|
933
|
-
case
|
|
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 =
|
|
937
|
-
case
|
|
943
|
+
_a.label = 13;
|
|
944
|
+
case 13: return [2 /*return*/];
|
|
938
945
|
}
|
|
939
946
|
});
|
|
940
947
|
});
|
package/dist/engine/session.js
CHANGED
|
@@ -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*/,
|
|
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.
|
|
1521
|
+
return [4 /*yield*/, this._sessionStateStore.isLeader(this._instanceId)];
|
|
1516
1522
|
case 3:
|
|
1517
|
-
_a.sent();
|
|
1518
|
-
return [4 /*yield*/, this._sessionState.set("
|
|
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("
|
|
1527
|
+
return [4 /*yield*/, this._sessionState.set("vodMediaSeqAudio", 0)];
|
|
1522
1528
|
case 5:
|
|
1523
1529
|
_a.sent();
|
|
1524
|
-
return [4 /*yield*/, this._sessionState.
|
|
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.
|
|
1533
|
+
return [4 /*yield*/, this._sessionState.setCurrentVod(slateVod)];
|
|
1528
1534
|
case 7:
|
|
1529
1535
|
_a.sent();
|
|
1530
|
-
return [4 /*yield*/, this._sessionState.set("
|
|
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("
|
|
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("
|
|
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("
|
|
1545
|
+
return [4 /*yield*/, this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio)];
|
|
1540
1546
|
case 11:
|
|
1541
1547
|
_a.sent();
|
|
1542
|
-
return [4 /*yield*/, this.
|
|
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
|
|
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.
|
|
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": "
|
|
37
|
+
"redis": "3.1.0",
|
|
38
38
|
"request": ">=2.88.0",
|
|
39
39
|
"restify": "9.0.0",
|
|
40
40
|
"restify-errors": "^8.0.2",
|
package/engine/playhead_state.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
playheadState.
|
|
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.
|
|
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": "
|
|
37
|
+
"redis": "3.1.0",
|
|
38
38
|
"request": ">=2.88.0",
|
|
39
39
|
"restify": "9.0.0",
|
|
40
40
|
"restify-errors": "^8.0.2",
|