eyevinn-channel-engine 3.1.9 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/engine/server.js +7 -4
- package/engine/session.js +27 -33
- package/engine/session_live.js +3 -1
- package/engine/util.js +29 -2
- package/package.json +1 -1
package/engine/server.js
CHANGED
|
@@ -12,7 +12,7 @@ const { SessionStateStore } = require('./session_state.js');
|
|
|
12
12
|
const { SessionLiveStateStore } = require('./session_live_state.js');
|
|
13
13
|
const { PlayheadStateStore } = require('./playhead_state.js');
|
|
14
14
|
|
|
15
|
-
const { filterQueryParser, toHHMMSS } = require('./util.js');
|
|
15
|
+
const { filterQueryParser, toHHMMSS, WaitTimeGenerator } = require('./util.js');
|
|
16
16
|
const { version } = require('../package.json');
|
|
17
17
|
|
|
18
18
|
const timer = ms => new Promise(res => setTimeout(res, ms));
|
|
@@ -166,14 +166,17 @@ class ChannelEngine {
|
|
|
166
166
|
}, 3000);
|
|
167
167
|
|
|
168
168
|
const StreamSwitchLoop = async (timeIntervalMs) => {
|
|
169
|
+
const minIntervalMs = 50;
|
|
170
|
+
const WTG = new WaitTimeGenerator(timeIntervalMs, minIntervalMs);
|
|
169
171
|
while(true) {
|
|
170
172
|
try {
|
|
171
173
|
const ts_1 = Date.now();
|
|
172
174
|
await this.updateStreamSwitchAsync()
|
|
173
175
|
const ts_2 = Date.now();
|
|
174
|
-
|
|
175
|
-
await
|
|
176
|
-
|
|
176
|
+
const interval = (timeIntervalMs - (ts_2 - ts_1)) < 0 ? minIntervalMs : (timeIntervalMs - (ts_2 - ts_1));
|
|
177
|
+
const tickInterval = await WTG.getWaitTime(interval);
|
|
178
|
+
await timer(tickInterval)
|
|
179
|
+
debug(`StreamSwitchLoop waited for all channels. Next tick in: ${tickInterval}ms`)
|
|
177
180
|
} catch (err) {
|
|
178
181
|
console.error(err)
|
|
179
182
|
debug(`StreamSwitchLoop iteration failed. Trying Again in 1000ms!`);
|
package/engine/session.js
CHANGED
|
@@ -57,6 +57,7 @@ class Session {
|
|
|
57
57
|
transitionSegments: null,
|
|
58
58
|
reloadBehind: null,
|
|
59
59
|
}
|
|
60
|
+
this.alreadyClearedVodCache = null;
|
|
60
61
|
|
|
61
62
|
if (config) {
|
|
62
63
|
if (config.sessionId) {
|
|
@@ -353,12 +354,9 @@ class Session {
|
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
360
|
-
}
|
|
361
|
-
}
|
|
357
|
+
|
|
358
|
+
// NOTE: Assume that VOD cache was already cleared in 'getCurrentMediaAndDiscSequenceCount()'
|
|
359
|
+
// and that we now have access to the correct vod cache
|
|
362
360
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
363
361
|
if (currentVod) {
|
|
364
362
|
try {
|
|
@@ -404,10 +402,11 @@ class Session {
|
|
|
404
402
|
|
|
405
403
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
406
404
|
const discSeqOffset = await this._sessionState.get("discSeq");
|
|
405
|
+
// Clear Vod Cache here when Switching to Live just to be safe...
|
|
407
406
|
if (playheadState.vodMediaSeqVideo === 0) {
|
|
408
407
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
409
408
|
if (!isLeader) {
|
|
410
|
-
debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
409
|
+
debug(`[${this._sessionId}]: Not a leader, about to switch, and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
411
410
|
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
412
411
|
}
|
|
413
412
|
}
|
|
@@ -461,13 +460,18 @@ class Session {
|
|
|
461
460
|
}
|
|
462
461
|
}
|
|
463
462
|
|
|
463
|
+
// Force reading up from store, but only once if the condition is right
|
|
464
464
|
if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
|
|
465
465
|
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
466
466
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
467
|
-
if (!isLeader) {
|
|
467
|
+
if (!isLeader && !this.alreadyClearedVodCache) {
|
|
468
468
|
debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
469
|
-
await this._sessionState.clearCurrentVodCache();
|
|
469
|
+
await this._sessionState.clearCurrentVodCache();
|
|
470
|
+
this.alreadyClearedVodCache = true;
|
|
470
471
|
}
|
|
472
|
+
} else {
|
|
473
|
+
// Reset the value
|
|
474
|
+
this.alreadyClearedVodCache = false;
|
|
471
475
|
}
|
|
472
476
|
|
|
473
477
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
@@ -575,9 +579,17 @@ class Session {
|
|
|
575
579
|
let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
|
|
576
580
|
await this._playheadState.setLastM3u8(m3u8);
|
|
577
581
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
582
|
+
// As a FOLLOWER, we might need to read up from shared store...
|
|
583
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
|
|
584
|
+
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
585
|
+
if (!isLeader && !this.alreadyClearedVodCache) {
|
|
586
|
+
debug(`[${this._sessionId}]: Not a leader and have just set 'playheadState.vodMediaSeqVideo' to 0|1. Invalidate cache to ensure having the correct VOD.`);
|
|
587
|
+
await this._sessionState.clearCurrentVodCache();
|
|
588
|
+
this.alreadyClearedVodCache = true;
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
// Reset the value
|
|
592
|
+
this.alreadyClearedVodCache = false;
|
|
581
593
|
}
|
|
582
594
|
|
|
583
595
|
return m3u8;
|
|
@@ -834,13 +846,6 @@ class Session {
|
|
|
834
846
|
sessionState.state = SessionState.VOD_INIT;
|
|
835
847
|
}
|
|
836
848
|
|
|
837
|
-
if (!isLeader && this.waitingForNextVod) {
|
|
838
|
-
// By now Leader should have added the next Vod in store
|
|
839
|
-
debug(`[${this._sessionId}]: Not leader! New VOD loaded during last tick. Invalidate current VOD cache`);
|
|
840
|
-
await this._sessionState.clearCurrentVodCache();
|
|
841
|
-
this.waitingForNextVod = false;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
849
|
switch (sessionState.state) {
|
|
845
850
|
case SessionState.VOD_INIT:
|
|
846
851
|
case SessionState.VOD_INIT_BY_ID:
|
|
@@ -931,19 +936,13 @@ class Session {
|
|
|
931
936
|
}
|
|
932
937
|
}
|
|
933
938
|
case SessionState.VOD_PLAYING:
|
|
934
|
-
if (
|
|
935
|
-
if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
936
|
-
debug(`[${this._sessionId}]: Not leader! Invalidate current VOD cache and fetch the new one from the leader`);
|
|
937
|
-
await this._sessionState.clearCurrentVodCache();
|
|
938
|
-
currentVod = await this._sessionState.getCurrentVod();
|
|
939
|
-
this.waitingForNextVod = false;
|
|
940
|
-
}
|
|
941
|
-
} else {
|
|
939
|
+
if (isLeader) {
|
|
942
940
|
// Handle edge case where store has been reset, but leader has not cleared cache.
|
|
943
941
|
if (this.prevVodMediaSeq.video === null) {
|
|
944
942
|
this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
|
|
945
943
|
}
|
|
946
|
-
if
|
|
944
|
+
// Clear Cache if prev count is HIGHER than current...
|
|
945
|
+
if (sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video ) {
|
|
947
946
|
debug(`[${this._sessionId}]: current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
948
947
|
await this._sessionState.clearCurrentVodCache();
|
|
949
948
|
currentVod = await this._sessionState.getCurrentVod();
|
|
@@ -956,11 +955,6 @@ class Session {
|
|
|
956
955
|
debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
957
956
|
if (!isLeader) {
|
|
958
957
|
debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be initiated`);
|
|
959
|
-
if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
960
|
-
debug(`[${this._sessionId}]: First mediasequence in VOD and I am not the leader so invalidate current VOD cache and fetch the new one from the leader`);
|
|
961
|
-
await this._sessionState.clearCurrentVodCache();
|
|
962
|
-
}
|
|
963
|
-
this.waitingForNextVod = true;
|
|
964
958
|
}
|
|
965
959
|
return;
|
|
966
960
|
case SessionState.VOD_NEXT_INIT:
|
package/engine/session_live.js
CHANGED
|
@@ -636,7 +636,9 @@ class SessionLive {
|
|
|
636
636
|
|
|
637
637
|
// Handle if any promise got rejected
|
|
638
638
|
if (manifestList.some((result) => result.status === "rejected")) {
|
|
639
|
-
|
|
639
|
+
FETCH_ATTEMPTS--;
|
|
640
|
+
debug(`[${this.sessionId}]: ALERT! Promises I: Failed, Rejection Found! Trying again in 1000ms...`);
|
|
641
|
+
await timer(1000)
|
|
640
642
|
continue;
|
|
641
643
|
}
|
|
642
644
|
|
package/engine/util.js
CHANGED
|
@@ -122,7 +122,33 @@ const logerror = (sessionId, err) => {
|
|
|
122
122
|
console.error(err);
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
-
const timer = ms => new Promise(res => setTimeout(res, ms));
|
|
125
|
+
const timer = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
126
|
+
|
|
127
|
+
class WaitTimeGenerator {
|
|
128
|
+
constructor(defaultIntervalMs, minValue) {
|
|
129
|
+
(this.timestamp = null), (this.prevWaitTime = null), (this.defaultIntervalMs = defaultIntervalMs || 3000), (this.minValue = minValue);
|
|
130
|
+
}
|
|
131
|
+
_getWaitTimeFromTimestamp() {
|
|
132
|
+
if (!this.timestamp) {
|
|
133
|
+
this.timestamp = new Date();
|
|
134
|
+
}
|
|
135
|
+
const sec = this.timestamp.getSeconds();
|
|
136
|
+
const defaultSec = this.defaultIntervalMs / 1000;
|
|
137
|
+
const d = parseInt(sec / defaultSec);
|
|
138
|
+
const waitSec = defaultSec * (d + 1) - sec;
|
|
139
|
+
return waitSec * 1000;
|
|
140
|
+
}
|
|
141
|
+
async getWaitTime(plannedTime) {
|
|
142
|
+
if (!this.timestamp || (this.prevWaitTime === this.minValue && plannedTime !== this.minValue)) {
|
|
143
|
+
this.timestamp = new Date();
|
|
144
|
+
const waitMs = this._getWaitTimeFromTimestamp();
|
|
145
|
+
this.prevWaitTime = waitMs;
|
|
146
|
+
return waitMs;
|
|
147
|
+
}
|
|
148
|
+
this.prevWaitTime = plannedTime;
|
|
149
|
+
return plannedTime;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
126
152
|
|
|
127
153
|
module.exports = {
|
|
128
154
|
filterQueryParser,
|
|
@@ -131,5 +157,6 @@ module.exports = {
|
|
|
131
157
|
m3u8Header,
|
|
132
158
|
toHHMMSS,
|
|
133
159
|
logerror,
|
|
134
|
-
timer
|
|
160
|
+
timer,
|
|
161
|
+
WaitTimeGenerator,
|
|
135
162
|
};
|