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