eyevinn-channel-engine 3.0.7 → 3.1.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.
- package/.github/workflows/publish.yml +1 -1
- package/engine/session.js +24 -9
- package/engine/session_live.js +198 -169
- package/package.json +1 -1
- package/spec/engine/playhead_live_spec.js +30 -24
package/engine/session.js
CHANGED
|
@@ -276,7 +276,8 @@ class Session {
|
|
|
276
276
|
debug(`[${this._sessionId}]: FOLLOWER: WARNING! Attempts=0 - Risk of using wrong currentVod`);
|
|
277
277
|
}
|
|
278
278
|
if (!isLeader || vodReloaded) {
|
|
279
|
-
debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod`);
|
|
279
|
+
debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod. Clearing the cache now`);
|
|
280
|
+
await this._sessionState.clearCurrentVodCache();
|
|
280
281
|
return;
|
|
281
282
|
}
|
|
282
283
|
}
|
|
@@ -394,9 +395,21 @@ class Session {
|
|
|
394
395
|
if (!this._sessionState) {
|
|
395
396
|
throw new Error('Session not ready');
|
|
396
397
|
}
|
|
397
|
-
const sessionState = await this._sessionState.getValues(["discSeq"]);
|
|
398
|
+
const sessionState = await this._sessionState.getValues(["mediaSeq", "discSeq", "vodMediaSeqVideo"]);
|
|
398
399
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
399
|
-
|
|
400
|
+
|
|
401
|
+
/*
|
|
402
|
+
Since sessionState is always newer. If it is a smaller value, then this signals that
|
|
403
|
+
a new vod has been loaded but playheadState has yet to get the new mseq value in 'incrementAsync()'.
|
|
404
|
+
Without this, we risk returning an m3u8 with an old mseq on a new vod to the client.
|
|
405
|
+
*/
|
|
406
|
+
if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
|
|
407
|
+
playheadState.vodMediaSeqVideo = sessionState.vodMediaSeqVideo;
|
|
408
|
+
playheadState.mediaSeq = sessionState.mediaSeq;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (playheadState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
412
|
+
this.waitingForNextVod = false;
|
|
400
413
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
401
414
|
if (!isLeader) {
|
|
402
415
|
debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
@@ -406,6 +419,7 @@ class Session {
|
|
|
406
419
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
407
420
|
if (currentVod) {
|
|
408
421
|
try {
|
|
422
|
+
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
409
423
|
const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
410
424
|
debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
|
|
411
425
|
return m3u8;
|
|
@@ -726,10 +740,11 @@ class Session {
|
|
|
726
740
|
length = currentVod.getLiveMediaSequencesCount();
|
|
727
741
|
lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
728
742
|
}
|
|
743
|
+
await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
744
|
+
await this._sessionState.setCurrentVod(slateVod);
|
|
745
|
+
|
|
729
746
|
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
730
747
|
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
731
|
-
await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
732
|
-
await this._sessionState.setCurrentVod(slateVod);
|
|
733
748
|
await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
|
|
734
749
|
await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
735
750
|
await this._sessionState.set("slateCount", sessionState.slateCount + 1);
|
|
@@ -813,10 +828,6 @@ class Session {
|
|
|
813
828
|
debug(`[${this._sessionId}]: ${currentVod.getDeltaTimes()}`);
|
|
814
829
|
debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
|
|
815
830
|
//debug(newVod);
|
|
816
|
-
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
|
|
817
|
-
sessionState.discSeq = await this._sessionState.set("discSeq", 0);
|
|
818
|
-
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
819
|
-
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
820
831
|
await this._playheadState.set("playheadRef", Date.now());
|
|
821
832
|
this.produceEvent({
|
|
822
833
|
type: 'NOW_PLAYING',
|
|
@@ -827,6 +838,10 @@ class Session {
|
|
|
827
838
|
});
|
|
828
839
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
|
|
829
840
|
sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
841
|
+
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
|
|
842
|
+
sessionState.discSeq = await this._sessionState.set("discSeq", 0);
|
|
843
|
+
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
844
|
+
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
830
845
|
await this._sessionState.remove("nextVod");
|
|
831
846
|
return;
|
|
832
847
|
} else {
|
package/engine/session_live.js
CHANGED
|
@@ -15,8 +15,10 @@ const daterangeAttribute = (key, attr) => {
|
|
|
15
15
|
return key.toUpperCase() + "=" + `"${attr}"`;
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
+
const TARGET_PLAYLIST_DURATION_SEC = 60;
|
|
18
19
|
const RESET_DELAY = 5000;
|
|
19
20
|
const FAIL_TIMEOUT = 4000;
|
|
21
|
+
const DEFAULT_PLAYHEAD_INTERVAL_MS = 6 * 1000;
|
|
20
22
|
const PlayheadState = Object.freeze({
|
|
21
23
|
RUNNING: 1,
|
|
22
24
|
STOPPED: 2,
|
|
@@ -30,14 +32,15 @@ class SessionLive {
|
|
|
30
32
|
this.sessionLiveStateStore = sessionLiveStore.sessionLiveStateStore;
|
|
31
33
|
this.instanceId = sessionLiveStore.instanceId;
|
|
32
34
|
this.mediaSeqCount = 0;
|
|
35
|
+
this.prevMediaSeqCount = 0;
|
|
33
36
|
this.discSeqCount = 0;
|
|
37
|
+
this.prevDiscSeqCount = 0;
|
|
34
38
|
this.targetDuration = 0;
|
|
35
39
|
this.masterManifestUri = null;
|
|
36
40
|
this.vodSegments = {};
|
|
37
41
|
this.mediaManifestURIs = {};
|
|
38
42
|
this.liveSegQueue = {};
|
|
39
43
|
this.lastRequestedMediaSeqRaw = null;
|
|
40
|
-
this.targetNumSeg = 0;
|
|
41
44
|
this.liveSourceM3Us = {};
|
|
42
45
|
this.playheadState = PlayheadState.IDLE;
|
|
43
46
|
this.liveSegsForFollowers = {};
|
|
@@ -106,6 +109,7 @@ class SessionLive {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
this.mediaSeqCount = 0;
|
|
112
|
+
this.prevMediaSeqCount = 0;
|
|
109
113
|
this.discSeqCount = 0;
|
|
110
114
|
this.targetDuration = 0;
|
|
111
115
|
this.masterManifestUri = null;
|
|
@@ -113,7 +117,6 @@ class SessionLive {
|
|
|
113
117
|
this.mediaManifestURIs = {};
|
|
114
118
|
this.liveSegQueue = {};
|
|
115
119
|
this.lastRequestedMediaSeqRaw = null;
|
|
116
|
-
this.targetNumSeg = 0;
|
|
117
120
|
this.liveSourceM3Us = {};
|
|
118
121
|
this.liveSegsForFollowers = {};
|
|
119
122
|
this.timerCompensation = null;
|
|
@@ -145,13 +148,6 @@ class SessionLive {
|
|
|
145
148
|
return;
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
// Let the playhead move at an interval set according to live segment duration
|
|
149
|
-
let liveSegmentDurationMs = 6000;
|
|
150
|
-
let liveBws = Object.keys(this.liveSegQueue);
|
|
151
|
-
if (liveBws.length !== 0 && this.liveSegQueue[liveBws[0]].length !== 0 && this.liveSegQueue[liveBws[0]][0].duration) {
|
|
152
|
-
liveSegmentDurationMs = this.liveSegQueue[liveBws[0]][0].duration * 1000;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
151
|
// Fetch Live-Source Segments, and get ready for on-the-fly manifest generation
|
|
156
152
|
// And also compensate for processing time
|
|
157
153
|
|
|
@@ -161,6 +157,13 @@ class SessionLive {
|
|
|
161
157
|
const tsIncrementEnd = Date.now();
|
|
162
158
|
this.waitForPlayhead = false;
|
|
163
159
|
|
|
160
|
+
// Let the playhead move at an interval set according to live segment duration
|
|
161
|
+
let liveSegmentDurationMs = DEFAULT_PLAYHEAD_INTERVAL_MS;
|
|
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
|
+
}
|
|
166
|
+
|
|
164
167
|
// Set the timer
|
|
165
168
|
let timerValueMs = 0;
|
|
166
169
|
if (this.timerCompensation) {
|
|
@@ -238,7 +241,6 @@ class SessionLive {
|
|
|
238
241
|
}
|
|
239
242
|
// Make it possible to add & share new segments
|
|
240
243
|
this.allowedToSet = true;
|
|
241
|
-
let segCount = 0;
|
|
242
244
|
const allBws = Object.keys(segments);
|
|
243
245
|
for (let i = 0; i < allBws.length; i++) {
|
|
244
246
|
const bw = allBws[i];
|
|
@@ -260,12 +262,6 @@ class SessionLive {
|
|
|
260
262
|
}
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
for (let segIdx = 0; segIdx < segments[allBws[0]].length; segIdx++) {
|
|
264
|
-
if (segments[allBws[0]][segIdx].uri) {
|
|
265
|
-
segCount++;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
this.targetNumSeg = segCount;
|
|
269
265
|
debug(`[${this.sessionId}]: Setting CurrentMediaSequenceSegments. First seg is: [${this.vodSegments[allBws[0]][0].uri}]`);
|
|
270
266
|
|
|
271
267
|
const isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
|
|
@@ -303,7 +299,7 @@ class SessionLive {
|
|
|
303
299
|
const leadersMediaSeqCount = liveCounts.mediaSeqCount;
|
|
304
300
|
const leadersDiscSeqCount = liveCounts.discSeqCount;
|
|
305
301
|
if (leadersMediaSeqCount !== null) {
|
|
306
|
-
this.mediaSeqCount = leadersMediaSeqCount
|
|
302
|
+
this.mediaSeqCount = leadersMediaSeqCount;
|
|
307
303
|
debug(`[${this.sessionId}]: Setting mediaSeqCount to: [${this.mediaSeqCount}]`);
|
|
308
304
|
}
|
|
309
305
|
if (leadersDiscSeqCount !== null) {
|
|
@@ -342,7 +338,7 @@ class SessionLive {
|
|
|
342
338
|
let bw = Object.keys(this.mediaManifestURIs)[i];
|
|
343
339
|
|
|
344
340
|
const liveTargetBandwidth = this._findNearestBw(bw, Object.keys(this.mediaManifestURIs));
|
|
345
|
-
const vodTargetBandwidth = this.
|
|
341
|
+
const vodTargetBandwidth = this._getNearestBandwidth(bw, Object.keys(this.vodSegments));
|
|
346
342
|
|
|
347
343
|
// Remove segments and disc-tag if they are on top
|
|
348
344
|
if (this.vodSegments[vodTargetBandwidth].length > 0 && this.vodSegments[vodTargetBandwidth][0].discontinuity) {
|
|
@@ -481,68 +477,28 @@ class SessionLive {
|
|
|
481
477
|
// FOLLOWER only function
|
|
482
478
|
_updateLiveSegQueue() {
|
|
483
479
|
if (Object.keys(this.liveSegsForFollowers).length === 0) {
|
|
484
|
-
debug(`[${this.sessionId}]: Error No Segments found at all.`);
|
|
480
|
+
debug(`[${this.sessionId}]: FOLLOWER: Error No Segments found at all.`);
|
|
485
481
|
}
|
|
486
|
-
const vodBws = Object.keys(this.vodSegments);
|
|
487
482
|
const liveBws = Object.keys(this.liveSegsForFollowers);
|
|
488
483
|
const size = this.liveSegsForFollowers[liveBws[0]].length;
|
|
489
484
|
|
|
490
|
-
|
|
491
|
-
this.liveSegsForFollowers[liveBws[0]].map((seg) => {
|
|
492
|
-
if (seg.uri) {
|
|
493
|
-
newSegCount++;
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
debug(`[${this.sessionId}]: FOLLOWER: liveSegsForFollowers Bws=${Object.keys(this.liveSegsForFollowers)}_newSegCount=${newSegCount} `);
|
|
498
|
-
// Remove transitional segs & add live source segs collected from store
|
|
499
|
-
for (let k = 0; k < newSegCount; k++) {
|
|
500
|
-
let incrementDiscSeqCount = false;
|
|
501
|
-
// Shift the top vod segment on all variants
|
|
502
|
-
for (let i = 0; i < vodBws.length; i++) {
|
|
503
|
-
let seg = this.vodSegments[vodBws[i]].shift();
|
|
504
|
-
if (seg && seg.discontinuity) {
|
|
505
|
-
incrementDiscSeqCount = true;
|
|
506
|
-
this.vodSegments[vodBws[i]].shift();
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
if (incrementDiscSeqCount) {
|
|
510
|
-
this.discSeqCount++;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
// Push to bottom, new live source segment on all variants
|
|
485
|
+
// Push the New Live Segments to All Variants
|
|
514
486
|
for (let segIdx = 0; segIdx < size; segIdx++) {
|
|
515
|
-
let
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (!this.liveSegQueue[
|
|
519
|
-
this.liveSegQueue[
|
|
487
|
+
for (let i = 0; i < liveBws.length; i++) {
|
|
488
|
+
const liveBw = liveBws[i];
|
|
489
|
+
const liveSegFromLeader = this.liveSegsForFollowers[liveBw][segIdx];
|
|
490
|
+
if (!this.liveSegQueue[liveBw]) {
|
|
491
|
+
this.liveSegQueue[liveBw] = [];
|
|
520
492
|
}
|
|
521
|
-
this.liveSegQueue[
|
|
522
|
-
|
|
523
|
-
let segCount = 0;
|
|
524
|
-
this.liveSegQueue[bw].map((seg) => {
|
|
525
|
-
if (seg.uri) {
|
|
526
|
-
segCount++;
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
if (segCount > this.targetNumSeg) {
|
|
530
|
-
let seg = this.liveSegQueue[bw].shift();
|
|
531
|
-
if (seg && seg.discontinuity) {
|
|
532
|
-
incrementDiscSeqCount = true;
|
|
533
|
-
seg = this.liveSegQueue[bw].shift();
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
debug(`[${this.sessionId}]: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue'`);
|
|
537
|
-
});
|
|
538
|
-
if (incrementDiscSeqCount) {
|
|
539
|
-
this.discSeqCount++;
|
|
493
|
+
this.liveSegQueue[liveBw].push(liveSegFromLeader);
|
|
494
|
+
debug(`[${this.sessionId}]: FOLLOWER: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue' (${liveBw})`);
|
|
540
495
|
}
|
|
541
496
|
}
|
|
542
|
-
|
|
543
|
-
this.
|
|
544
|
-
|
|
545
|
-
|
|
497
|
+
// Remove older segments and update counts
|
|
498
|
+
const newTotalDuration = this._incrementAndShift("FOLLOWER");
|
|
499
|
+
if (newTotalDuration) {
|
|
500
|
+
debug(`[${this.sessionId}]: FOLLOWER: New Adjusted Playlist Duration=${newTotalDuration}s`);
|
|
501
|
+
}
|
|
546
502
|
}
|
|
547
503
|
|
|
548
504
|
/**
|
|
@@ -681,12 +637,13 @@ class SessionLive {
|
|
|
681
637
|
if (Object.keys(this.liveSegQueue).length > 0) {
|
|
682
638
|
const firstBw = Object.keys(this.liveSegQueue)[0];
|
|
683
639
|
const lastIdx = this.liveSegQueue[firstBw].length - 1;
|
|
684
|
-
|
|
640
|
+
if (this.liveSegQueue[firstBw][lastIdx].duration) {
|
|
641
|
+
retryDelayMs = this.liveSegQueue[firstBw][lastIdx].duration * 1000 * 0.25;
|
|
642
|
+
}
|
|
685
643
|
}
|
|
686
644
|
// Wait a little before trying again
|
|
687
645
|
debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after ${retryDelayMs}ms`);
|
|
688
646
|
await timer(retryDelayMs);
|
|
689
|
-
this.timerCompensation = false;
|
|
690
647
|
continue;
|
|
691
648
|
}
|
|
692
649
|
|
|
@@ -718,24 +675,17 @@ class SessionLive {
|
|
|
718
675
|
|
|
719
676
|
// Respawners never do this, only starter followers.
|
|
720
677
|
// Edge Case: FOLLOWER transitioned from session with different segments from LEADER
|
|
721
|
-
|
|
722
|
-
|
|
678
|
+
const leaderStartedOnCount = leadersFirstSeqCounts.mediaSeqCount;
|
|
679
|
+
if (leaderStartedOnCount !== this.mediaSeqCount) {
|
|
680
|
+
this.mediaSeqCount = leaderStartedOnCount;
|
|
723
681
|
const transitSegs = await this.sessionLiveState.get("transitSegs");
|
|
724
682
|
if (!this._isEmpty(transitSegs)) {
|
|
725
683
|
this.vodSegments = transitSegs;
|
|
726
|
-
let discTagCount = 0;
|
|
727
|
-
const allBws = Object.keys(this.vodSegments);
|
|
728
|
-
for (let segIdx = 0; segIdx < this.vodSegments[allBws[0]].length; segIdx++) {
|
|
729
|
-
if (this.vodSegments[allBws[0]][segIdx].discontinuity) {
|
|
730
|
-
discTagCount++;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
this.targetNumSeg = this.vodSegments[allBws[0]].length - (discTagCount - 1);
|
|
734
684
|
}
|
|
735
685
|
}
|
|
736
686
|
|
|
737
687
|
// Prepare to load segments...
|
|
738
|
-
debug(`[${this.instanceId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
|
|
688
|
+
debug(`[${this.instanceId}][${this.sessionId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
|
|
739
689
|
if (allMediaSeqCounts[0] === leadersFirstSeqCounts.liveSourceMseqCount) {
|
|
740
690
|
this.pushAmount = 1; // Follower from start
|
|
741
691
|
} else {
|
|
@@ -746,14 +696,6 @@ class SessionLive {
|
|
|
746
696
|
//debug(`[${this.sessionId}]: NEW FOLLOWER: I tried to get 'transitSegs'. This is what I found ${JSON.stringify(transitSegs)}`);
|
|
747
697
|
if (!this._isEmpty(transitSegs)) {
|
|
748
698
|
this.vodSegments = transitSegs;
|
|
749
|
-
let discTagCount = 0;
|
|
750
|
-
const allBws = Object.keys(this.vodSegments);
|
|
751
|
-
for (let segIdx = 0; segIdx < this.vodSegments[allBws[0]].length; segIdx++) {
|
|
752
|
-
if (this.vodSegments[allBws[0]][segIdx].discontinuity) {
|
|
753
|
-
discTagCount++;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
this.targetNumSeg = this.vodSegments[allBws[0]].length - (discTagCount - 1);
|
|
757
699
|
}
|
|
758
700
|
}
|
|
759
701
|
debug(`[${this.sessionId}]: ...pushAmount=${this.pushAmount}`);
|
|
@@ -805,34 +747,35 @@ class SessionLive {
|
|
|
805
747
|
let pushPromises = [];
|
|
806
748
|
for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
|
|
807
749
|
let bw = Object.keys(this.mediaManifestURIs)[i];
|
|
750
|
+
// will add new segments to live seg queue
|
|
808
751
|
pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw));
|
|
809
752
|
debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
|
|
810
753
|
}
|
|
811
754
|
// Segment Pushing
|
|
812
755
|
debug(`[${this.sessionId}]: Executing Promises II: Segment Pushing`);
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (removedDiscSeqList.every((val, i, arr) => val === arr[0])) {
|
|
821
|
-
// if all variants removed equal amounts of disc-tags
|
|
822
|
-
this.discSeqCount += removedDiscSeqList[0];
|
|
756
|
+
await allSettled(pushPromises);
|
|
757
|
+
|
|
758
|
+
// UPDATE COUNTS, & Shift Segments in vodSegments and liveSegQueue if needed.
|
|
759
|
+
const leaderORFollower = isLeader ? "LEADER" : "NEW FOLLOWER";
|
|
760
|
+
const newTotalDuration = this._incrementAndShift(leaderORFollower);
|
|
761
|
+
if (newTotalDuration) {
|
|
762
|
+
debug(`[${this.sessionId}]: New Adjusted Playlist Duration=${newTotalDuration}s`);
|
|
823
763
|
}
|
|
824
|
-
// UPDATE COUNTS, & Shift Segments in vodSegments
|
|
825
|
-
await this._incrementAndShift();
|
|
826
764
|
}
|
|
827
765
|
|
|
828
766
|
// -----------------------------------------------------
|
|
829
767
|
// Leader writes to store so that Followers can read.
|
|
830
768
|
// -----------------------------------------------------
|
|
831
769
|
if (isLeader) {
|
|
832
|
-
debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
|
|
833
770
|
if (this.allowedToSet) {
|
|
834
|
-
|
|
835
|
-
|
|
771
|
+
const liveBws = Object.keys(this.liveSegsForFollowers);
|
|
772
|
+
const segListSize = this.liveSegsForFollowers[liveBws[0]].length;
|
|
773
|
+
// Do not replace old data with empty data
|
|
774
|
+
if (segListSize > 0) {
|
|
775
|
+
debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
|
|
776
|
+
await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
|
|
777
|
+
await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
|
|
778
|
+
}
|
|
836
779
|
}
|
|
837
780
|
|
|
838
781
|
// [LASTLY]: LEADER does this for respawned-FOLLOWERS' sake.
|
|
@@ -841,8 +784,8 @@ class SessionLive {
|
|
|
841
784
|
await timer(1000); // maybe remove
|
|
842
785
|
const firstCounts = {
|
|
843
786
|
liveSourceMseqCount: this.lastRequestedMediaSeqRaw,
|
|
844
|
-
mediaSeqCount: this.
|
|
845
|
-
discSeqCount: this.
|
|
787
|
+
mediaSeqCount: this.prevMediaSeqCount,
|
|
788
|
+
discSeqCount: this.prevDiscSeqCount,
|
|
846
789
|
};
|
|
847
790
|
debug(`[${this.sessionId}]: LEADER: I am adding 'firstCounts'=${JSON.stringify(firstCounts)} to Store for future followers`);
|
|
848
791
|
await this.sessionLiveState.set("firstCounts", firstCounts);
|
|
@@ -858,39 +801,148 @@ class SessionLive {
|
|
|
858
801
|
return;
|
|
859
802
|
}
|
|
860
803
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
804
|
+
_shiftSegments(opt) {
|
|
805
|
+
let _totalDur = 0;
|
|
806
|
+
let _segments = {};
|
|
807
|
+
let _name = "";
|
|
808
|
+
let _removedSegments = 0;
|
|
809
|
+
let _removedDiscontinuities = 0;
|
|
810
|
+
|
|
811
|
+
if (opt && opt.totalDur) {
|
|
812
|
+
_totalDur = opt.totalDur;
|
|
813
|
+
}
|
|
814
|
+
if (opt && opt.segments) {
|
|
815
|
+
_segments = JSON.parse(JSON.stringify(opt.segments)); // clone it
|
|
816
|
+
}
|
|
817
|
+
if (opt && opt.name) {
|
|
818
|
+
_name = opt.name || "NONE";
|
|
819
|
+
}
|
|
820
|
+
if (opt && opt.removedSegments) {
|
|
821
|
+
_removedSegments = opt.removedSegments;
|
|
822
|
+
}
|
|
823
|
+
if (opt && opt.removedDiscontinuities) {
|
|
824
|
+
_removedDiscontinuities = opt.removedDiscontinuities;
|
|
825
|
+
}
|
|
826
|
+
const bws = Object.keys(_segments);
|
|
827
|
+
|
|
828
|
+
/* When Total Duration is past the Limit, start Shifting V2L|LIVE segments if found */
|
|
829
|
+
while (_totalDur > TARGET_PLAYLIST_DURATION_SEC) {
|
|
830
|
+
// Skip loop if there are no more segments to remove...
|
|
831
|
+
if (_segments[bws[0]].length === 0) {
|
|
832
|
+
return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
|
|
833
|
+
}
|
|
834
|
+
debug(`[${this.sessionId}]: ${_name}: (${_totalDur})s/(${TARGET_PLAYLIST_DURATION_SEC})s - Playlist Duration is Over the Target. Shift needed!`);
|
|
835
|
+
let timeToRemove = 0;
|
|
864
836
|
let incrementDiscSeqCount = false;
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
837
|
+
|
|
838
|
+
// Shift Segments for each variant...
|
|
839
|
+
for (let i = 0; i < bws.length; i++) {
|
|
840
|
+
let seg = _segments[bws[i]].shift();
|
|
841
|
+
if (i === 0) {
|
|
842
|
+
debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
|
|
843
|
+
}
|
|
844
|
+
if (seg && seg.discontinuity) {
|
|
845
|
+
incrementDiscSeqCount = true;
|
|
846
|
+
if (_segments[bws[i]].length > 0) {
|
|
847
|
+
seg = _segments[bws[i]].shift();
|
|
848
|
+
if (i === 0) {
|
|
849
|
+
debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
|
|
850
|
+
}
|
|
872
851
|
}
|
|
873
852
|
}
|
|
853
|
+
if (seg && seg.duration) {
|
|
854
|
+
timeToRemove = seg.duration;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (timeToRemove) {
|
|
858
|
+
_totalDur -= timeToRemove;
|
|
859
|
+
// Increment number of removed segments...
|
|
860
|
+
_removedSegments++;
|
|
874
861
|
}
|
|
875
862
|
if (incrementDiscSeqCount) {
|
|
876
|
-
|
|
863
|
+
// Update Session Live Discontinuity Sequence Count
|
|
864
|
+
_removedDiscontinuities++;
|
|
877
865
|
}
|
|
878
|
-
this.mediaSeqCount++;
|
|
879
866
|
}
|
|
867
|
+
return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
|
|
868
|
+
}
|
|
880
869
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
870
|
+
/**
|
|
871
|
+
* Shifts V2L or LIVE items if total segment duration (V2L+LIVE) are over the target duration.
|
|
872
|
+
* It will also update and increment SessionLive's MediaSeqCount and DiscSeqCount based
|
|
873
|
+
* on what was shifted.
|
|
874
|
+
* @param {string} instanceName Name of instance "LEADER" | "FOLLOWER"
|
|
875
|
+
* @returns {number} The new total duration in seconds
|
|
876
|
+
*/
|
|
877
|
+
_incrementAndShift(instanceName) {
|
|
878
|
+
if (!instanceName) {
|
|
879
|
+
instanceName = "UNKNOWN";
|
|
880
|
+
}
|
|
881
|
+
const vodBws = Object.keys(this.vodSegments);
|
|
882
|
+
const liveBws = Object.keys(this.liveSegQueue);
|
|
883
|
+
let vodTotalDur = 0;
|
|
884
|
+
let liveTotalDur = 0;
|
|
885
|
+
let totalDur = 0;
|
|
886
|
+
let removedSegments = 0;
|
|
887
|
+
let removedDiscontinuities = 0;
|
|
888
|
+
|
|
889
|
+
// Calculate Playlist Total Duration
|
|
890
|
+
this.vodSegments[vodBws[0]].forEach((seg) => {
|
|
891
|
+
if (seg.duration) {
|
|
892
|
+
vodTotalDur += seg.duration;
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
this.liveSegQueue[liveBws[0]].forEach((seg) => {
|
|
896
|
+
if (seg.duration) {
|
|
897
|
+
liveTotalDur += seg.duration;
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
totalDur = vodTotalDur + liveTotalDur;
|
|
901
|
+
debug(`[${this.sessionId}]: ${instanceName}: L2L dur->: ${liveTotalDur}s | V2L dur->: ${vodTotalDur}s | Total dur->: ${totalDur}s`);
|
|
902
|
+
|
|
903
|
+
/** --- SHIFT then INCREMENT --- **/
|
|
904
|
+
|
|
905
|
+
// Shift V2L Segments
|
|
906
|
+
const outputV2L = this._shiftSegments({
|
|
907
|
+
name: instanceName,
|
|
908
|
+
totalDur: totalDur,
|
|
909
|
+
segments: this.vodSegments,
|
|
910
|
+
removedSegments: removedSegments,
|
|
911
|
+
removedDiscontinuities: removedDiscontinuities,
|
|
912
|
+
});
|
|
913
|
+
// Update V2L Segments
|
|
914
|
+
this.vodSegments = outputV2L.shiftedSegments;
|
|
915
|
+
// Update values
|
|
916
|
+
totalDur = outputV2L.totalDuration;
|
|
917
|
+
removedSegments = outputV2L.removedSegments;
|
|
918
|
+
removedDiscontinuities = outputV2L.removedDiscontinuities;
|
|
919
|
+
// Shift LIVE Segments
|
|
920
|
+
const outputLIVE = this._shiftSegments({
|
|
921
|
+
name: instanceName,
|
|
922
|
+
totalDur: totalDur,
|
|
923
|
+
segments: this.liveSegQueue,
|
|
924
|
+
removedSegments: removedSegments,
|
|
925
|
+
removedDiscontinuities: removedDiscontinuities,
|
|
926
|
+
});
|
|
927
|
+
// Update LIVE Segments
|
|
928
|
+
this.liveSegQueue = outputLIVE.shiftedSegments;
|
|
929
|
+
// Update values
|
|
930
|
+
totalDur = outputLIVE.totalDuration;
|
|
931
|
+
removedSegments = outputLIVE.removedSegments;
|
|
932
|
+
removedDiscontinuities = outputLIVE.removedDiscontinuities;
|
|
933
|
+
|
|
934
|
+
// Update Session Live Discontinuity Sequence Count...
|
|
935
|
+
this.prevDiscSeqCount = this.discSeqCount;
|
|
936
|
+
this.discSeqCount += removedDiscontinuities;
|
|
937
|
+
// Update Session Live Media Sequence Count...
|
|
938
|
+
this.prevMediaSeqCount = this.mediaSeqCount;
|
|
939
|
+
this.mediaSeqCount += removedSegments;
|
|
940
|
+
if (this.discSeqCount !== this.prevDiscSeqCount) {
|
|
941
|
+
debug(`[${this.sessionId}]: ${instanceName}: Incrementing Dseq Count from {${this.prevDiscSeqCount}} -> {${this.discSeqCount}}`);
|
|
942
|
+
}
|
|
943
|
+
debug(`[${this.sessionId}]: ${instanceName}: Incrementing Mseq Count from [${this.prevMediaSeqCount}] -> [${this.mediaSeqCount}]`);
|
|
944
|
+
debug(`[${this.sessionId}]: ${instanceName}: Finished updating all Counts and Segment Queues!`);
|
|
945
|
+
return totalDur;
|
|
894
946
|
}
|
|
895
947
|
|
|
896
948
|
async _loadMediaManifest(bw) {
|
|
@@ -957,28 +1009,17 @@ class SessionLive {
|
|
|
957
1009
|
//debug(`[${this.sessionId}]: Current RAW Mseq: [${m3u.get("mediaSequence")}]`);
|
|
958
1010
|
//debug(`[${this.sessionId}]: Previous RAW Mseq: [${this.lastRequestedMediaSeqRaw}]`);
|
|
959
1011
|
|
|
960
|
-
// TODO: UPDATE target for number of segments in a manifest, if appropriate.
|
|
961
|
-
// if (m3u.items.PlaylistItem.length < this.targetNumSeg) {
|
|
962
|
-
// debug(`[${this.sessionId}]: WE PLAN TO LOWER SEGMENT AMOUNT IN MANIFEST FROM (${this.targetNumSeg}) TO (${m3u.items.PlaylistItem.length})`);
|
|
963
|
-
// this.targetNumSeg = m3u.items.PlaylistItem.length;
|
|
964
|
-
// }
|
|
965
|
-
|
|
966
1012
|
if (this.pushAmount >= 0) {
|
|
967
1013
|
this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
|
|
968
1014
|
}
|
|
969
1015
|
this.targetDuration = m3u.get("targetDuration");
|
|
970
|
-
let amountRemovedDiscSeqs = 0;
|
|
971
1016
|
let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
|
|
972
1017
|
startIdx = startIdx < 0 ? 0 : startIdx;
|
|
973
1018
|
if (mediaManifestUri) {
|
|
974
1019
|
// push segments
|
|
975
|
-
|
|
1020
|
+
this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
|
|
976
1021
|
}
|
|
977
|
-
|
|
978
|
-
removedDiscSeqs: amountRemovedDiscSeqs,
|
|
979
|
-
bw: liveTargetBandwidth,
|
|
980
|
-
};
|
|
981
|
-
resolve(resolveObj);
|
|
1022
|
+
resolve();
|
|
982
1023
|
} catch (exc) {
|
|
983
1024
|
console.error("ERROR: " + exc);
|
|
984
1025
|
reject(exc);
|
|
@@ -986,8 +1027,15 @@ class SessionLive {
|
|
|
986
1027
|
});
|
|
987
1028
|
}
|
|
988
1029
|
|
|
1030
|
+
/**
|
|
1031
|
+
* Collects 'new' PlaylistItems and converts them into custom SegmentItems,
|
|
1032
|
+
* then Pushes them to the LiveSegQueue for all variants.
|
|
1033
|
+
* @param {number} startIdx
|
|
1034
|
+
* @param {m3u8.Item.PlaylistItem} playlistItems
|
|
1035
|
+
* @param {string} baseUrl
|
|
1036
|
+
* @param {string} liveTargetBandwidth
|
|
1037
|
+
*/
|
|
989
1038
|
_addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
|
|
990
|
-
let amountRemovedDiscSeqs = 0;
|
|
991
1039
|
for (let i = startIdx; i < playlistItems.length; i++) {
|
|
992
1040
|
debug(`[${this.sessionId}]: Adding Live Segment(s) to Queue (for bw=${liveTargetBandwidth})`);
|
|
993
1041
|
let seg = {};
|
|
@@ -1056,30 +1104,11 @@ class SessionLive {
|
|
|
1056
1104
|
if (daterangeData) {
|
|
1057
1105
|
seg["daterange"] = daterangeData;
|
|
1058
1106
|
}
|
|
1059
|
-
|
|
1107
|
+
// Push new Live Segments!
|
|
1060
1108
|
this.liveSegQueue[liveTargetBandwidth].push(seg);
|
|
1061
1109
|
this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
|
|
1062
|
-
let segCount = 0;
|
|
1063
|
-
this.liveSegQueue[liveTargetBandwidth].map((seg) => {
|
|
1064
|
-
if (seg.uri) {
|
|
1065
|
-
segCount++;
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
|
-
debug(`[${this.sessionId}]: size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
|
|
1069
|
-
if (segCount > this.targetNumSeg) {
|
|
1070
|
-
debug(`[${this.sessionId}]: liveTargetBandwidth=${liveTargetBandwidth}_seg=${JSON.stringify(seg, null, 2)}`);
|
|
1071
|
-
seg = this.liveSegQueue[liveTargetBandwidth].shift();
|
|
1072
|
-
if (seg) {
|
|
1073
|
-
if (seg.discontinuity) {
|
|
1074
|
-
this.liveSegQueue[liveTargetBandwidth].shift();
|
|
1075
|
-
amountRemovedDiscSeqs++;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
debug(`[${this.sessionId}]: After Shifting -> size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
|
|
1079
|
-
}
|
|
1080
1110
|
}
|
|
1081
1111
|
}
|
|
1082
|
-
return amountRemovedDiscSeqs;
|
|
1083
1112
|
}
|
|
1084
1113
|
|
|
1085
1114
|
/*
|
package/package.json
CHANGED
|
@@ -202,36 +202,42 @@ segment_0_8.ts
|
|
|
202
202
|
|
|
203
203
|
const mockLiveSegments = {
|
|
204
204
|
180000: [
|
|
205
|
-
{ duration:
|
|
206
|
-
{ duration:
|
|
207
|
-
{ duration:
|
|
208
|
-
{ duration:
|
|
209
|
-
{ duration:
|
|
210
|
-
{ duration:
|
|
211
|
-
{ duration:
|
|
212
|
-
{ duration:
|
|
205
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg09.ts" },
|
|
206
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg10.ts" },
|
|
207
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg11.ts" },
|
|
208
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg12.ts" },
|
|
209
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg13.ts" },
|
|
210
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg14.ts" },
|
|
211
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg15.ts" },
|
|
212
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg16.ts" },
|
|
213
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg17.ts" },
|
|
214
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg18.ts" },
|
|
213
215
|
{ discontinuity: true },
|
|
214
216
|
],
|
|
215
217
|
1258000: [
|
|
216
|
-
{ duration:
|
|
217
|
-
{ duration:
|
|
218
|
-
{ duration:
|
|
219
|
-
{ duration:
|
|
220
|
-
{ duration:
|
|
221
|
-
{ duration:
|
|
222
|
-
{ duration:
|
|
223
|
-
{ duration:
|
|
218
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg09.ts" },
|
|
219
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg10.ts" },
|
|
220
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg11.ts" },
|
|
221
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg12.ts" },
|
|
222
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg13.ts" },
|
|
223
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg14.ts" },
|
|
224
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg15.ts" },
|
|
225
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg16.ts" },
|
|
226
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg17.ts" },
|
|
227
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg18.ts" },
|
|
224
228
|
{ discontinuity: true },
|
|
225
229
|
],
|
|
226
230
|
2488000: [
|
|
227
|
-
{ duration:
|
|
228
|
-
{ duration:
|
|
229
|
-
{ duration:
|
|
230
|
-
{ duration:
|
|
231
|
-
{ duration:
|
|
232
|
-
{ duration:
|
|
233
|
-
{ duration:
|
|
234
|
-
{ duration:
|
|
231
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg09.ts" },
|
|
232
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg10.ts" },
|
|
233
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg11.ts" },
|
|
234
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg12.ts" },
|
|
235
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg13.ts" },
|
|
236
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg14.ts" },
|
|
237
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg15.ts" },
|
|
238
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg16.ts" },
|
|
239
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg17.ts" },
|
|
240
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg18.ts" },
|
|
235
241
|
{ discontinuity: true },
|
|
236
242
|
],
|
|
237
243
|
};
|