eyevinn-channel-engine 3.0.6 → 3.1.1
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 +209 -141
- 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,6 +15,7 @@ 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;
|
|
20
21
|
const PlayheadState = Object.freeze({
|
|
@@ -30,7 +31,9 @@ class SessionLive {
|
|
|
30
31
|
this.sessionLiveStateStore = sessionLiveStore.sessionLiveStateStore;
|
|
31
32
|
this.instanceId = sessionLiveStore.instanceId;
|
|
32
33
|
this.mediaSeqCount = 0;
|
|
34
|
+
this.prevMediaSeqCount = 0;
|
|
33
35
|
this.discSeqCount = 0;
|
|
36
|
+
this.prevDiscSeqCount = 0;
|
|
34
37
|
this.targetDuration = 0;
|
|
35
38
|
this.masterManifestUri = null;
|
|
36
39
|
this.vodSegments = {};
|
|
@@ -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;
|
|
@@ -303,7 +307,7 @@ class SessionLive {
|
|
|
303
307
|
const leadersMediaSeqCount = liveCounts.mediaSeqCount;
|
|
304
308
|
const leadersDiscSeqCount = liveCounts.discSeqCount;
|
|
305
309
|
if (leadersMediaSeqCount !== null) {
|
|
306
|
-
this.mediaSeqCount = leadersMediaSeqCount
|
|
310
|
+
this.mediaSeqCount = leadersMediaSeqCount;
|
|
307
311
|
debug(`[${this.sessionId}]: Setting mediaSeqCount to: [${this.mediaSeqCount}]`);
|
|
308
312
|
}
|
|
309
313
|
if (leadersDiscSeqCount !== null) {
|
|
@@ -342,7 +346,7 @@ class SessionLive {
|
|
|
342
346
|
let bw = Object.keys(this.mediaManifestURIs)[i];
|
|
343
347
|
|
|
344
348
|
const liveTargetBandwidth = this._findNearestBw(bw, Object.keys(this.mediaManifestURIs));
|
|
345
|
-
const vodTargetBandwidth = this.
|
|
349
|
+
const vodTargetBandwidth = this._getNearestBandwidth(bw, Object.keys(this.vodSegments));
|
|
346
350
|
|
|
347
351
|
// Remove segments and disc-tag if they are on top
|
|
348
352
|
if (this.vodSegments[vodTargetBandwidth].length > 0 && this.vodSegments[vodTargetBandwidth][0].discontinuity) {
|
|
@@ -481,68 +485,28 @@ class SessionLive {
|
|
|
481
485
|
// FOLLOWER only function
|
|
482
486
|
_updateLiveSegQueue() {
|
|
483
487
|
if (Object.keys(this.liveSegsForFollowers).length === 0) {
|
|
484
|
-
debug(`[${this.sessionId}]: Error No Segments found at all.`);
|
|
488
|
+
debug(`[${this.sessionId}]: FOLLOWER: Error No Segments found at all.`);
|
|
485
489
|
}
|
|
486
|
-
const vodBws = Object.keys(this.vodSegments);
|
|
487
490
|
const liveBws = Object.keys(this.liveSegsForFollowers);
|
|
488
491
|
const size = this.liveSegsForFollowers[liveBws[0]].length;
|
|
489
492
|
|
|
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
|
|
493
|
+
// Push the New Live Segments to All Variants
|
|
514
494
|
for (let segIdx = 0; segIdx < size; segIdx++) {
|
|
515
|
-
let
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (!this.liveSegQueue[
|
|
519
|
-
this.liveSegQueue[
|
|
520
|
-
}
|
|
521
|
-
this.liveSegQueue[bw].push(liveSegFromLeader);
|
|
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
|
-
}
|
|
495
|
+
for (let i = 0; i < liveBws.length; i++) {
|
|
496
|
+
const liveBw = liveBws[i];
|
|
497
|
+
const liveSegFromLeader = this.liveSegsForFollowers[liveBw][segIdx];
|
|
498
|
+
if (!this.liveSegQueue[liveBw]) {
|
|
499
|
+
this.liveSegQueue[liveBw] = [];
|
|
535
500
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (incrementDiscSeqCount) {
|
|
539
|
-
this.discSeqCount++;
|
|
501
|
+
this.liveSegQueue[liveBw].push(liveSegFromLeader);
|
|
502
|
+
debug(`[${this.sessionId}]: FOLLOWER: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue' (${liveBw})`);
|
|
540
503
|
}
|
|
541
504
|
}
|
|
542
|
-
|
|
543
|
-
this.
|
|
544
|
-
|
|
545
|
-
|
|
505
|
+
// Remove older segments and update counts
|
|
506
|
+
const newTotalDuration = this._incrementAndShift("FOLLOWER");
|
|
507
|
+
if (newTotalDuration) {
|
|
508
|
+
debug(`[${this.sessionId}]: FOLLOWER: New Adjusted Playlist Duration=${newTotalDuration}s`);
|
|
509
|
+
}
|
|
546
510
|
}
|
|
547
511
|
|
|
548
512
|
/**
|
|
@@ -681,7 +645,9 @@ class SessionLive {
|
|
|
681
645
|
if (Object.keys(this.liveSegQueue).length > 0) {
|
|
682
646
|
const firstBw = Object.keys(this.liveSegQueue)[0];
|
|
683
647
|
const lastIdx = this.liveSegQueue[firstBw].length - 1;
|
|
684
|
-
|
|
648
|
+
if (this.liveSegQueue[firstBw][lastIdx].duration) {
|
|
649
|
+
retryDelayMs = this.liveSegQueue[firstBw][lastIdx].duration * 1000 * 0.25;
|
|
650
|
+
}
|
|
685
651
|
}
|
|
686
652
|
// Wait a little before trying again
|
|
687
653
|
debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after ${retryDelayMs}ms`);
|
|
@@ -718,8 +684,9 @@ class SessionLive {
|
|
|
718
684
|
|
|
719
685
|
// Respawners never do this, only starter followers.
|
|
720
686
|
// Edge Case: FOLLOWER transitioned from session with different segments from LEADER
|
|
721
|
-
|
|
722
|
-
|
|
687
|
+
const leaderStartedOnCount = leadersFirstSeqCounts.mediaSeqCount;
|
|
688
|
+
if (leaderStartedOnCount !== this.mediaSeqCount) {
|
|
689
|
+
this.mediaSeqCount = leaderStartedOnCount;
|
|
723
690
|
const transitSegs = await this.sessionLiveState.get("transitSegs");
|
|
724
691
|
if (!this._isEmpty(transitSegs)) {
|
|
725
692
|
this.vodSegments = transitSegs;
|
|
@@ -735,7 +702,7 @@ class SessionLive {
|
|
|
735
702
|
}
|
|
736
703
|
|
|
737
704
|
// Prepare to load segments...
|
|
738
|
-
debug(`[${this.instanceId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
|
|
705
|
+
debug(`[${this.instanceId}][${this.sessionId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
|
|
739
706
|
if (allMediaSeqCounts[0] === leadersFirstSeqCounts.liveSourceMseqCount) {
|
|
740
707
|
this.pushAmount = 1; // Follower from start
|
|
741
708
|
} else {
|
|
@@ -805,34 +772,35 @@ class SessionLive {
|
|
|
805
772
|
let pushPromises = [];
|
|
806
773
|
for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
|
|
807
774
|
let bw = Object.keys(this.mediaManifestURIs)[i];
|
|
775
|
+
// will add new segments to live seg queue
|
|
808
776
|
pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw));
|
|
809
777
|
debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
|
|
810
778
|
}
|
|
811
779
|
// Segment Pushing
|
|
812
780
|
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];
|
|
781
|
+
await allSettled(pushPromises);
|
|
782
|
+
|
|
783
|
+
// UPDATE COUNTS, & Shift Segments in vodSegments and liveSegQueue if needed.
|
|
784
|
+
const leaderORFollower = isLeader ? "LEADER" : "NEW FOLLOWER";
|
|
785
|
+
const newTotalDuration = this._incrementAndShift(leaderORFollower);
|
|
786
|
+
if (newTotalDuration) {
|
|
787
|
+
debug(`[${this.sessionId}]: New Adjusted Playlist Duration=${newTotalDuration}s`);
|
|
823
788
|
}
|
|
824
|
-
// UPDATE COUNTS, & Shift Segments in vodSegments
|
|
825
|
-
await this._incrementAndShift();
|
|
826
789
|
}
|
|
827
790
|
|
|
828
791
|
// -----------------------------------------------------
|
|
829
792
|
// Leader writes to store so that Followers can read.
|
|
830
793
|
// -----------------------------------------------------
|
|
831
794
|
if (isLeader) {
|
|
832
|
-
debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
|
|
833
795
|
if (this.allowedToSet) {
|
|
834
|
-
|
|
835
|
-
|
|
796
|
+
const liveBws = Object.keys(this.liveSegsForFollowers);
|
|
797
|
+
const segListSize = this.liveSegsForFollowers[liveBws[0]].length;
|
|
798
|
+
// Do not replace old data with empty data
|
|
799
|
+
if (segListSize > 0) {
|
|
800
|
+
debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
|
|
801
|
+
await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
|
|
802
|
+
await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
|
|
803
|
+
}
|
|
836
804
|
}
|
|
837
805
|
|
|
838
806
|
// [LASTLY]: LEADER does this for respawned-FOLLOWERS' sake.
|
|
@@ -841,8 +809,8 @@ class SessionLive {
|
|
|
841
809
|
await timer(1000); // maybe remove
|
|
842
810
|
const firstCounts = {
|
|
843
811
|
liveSourceMseqCount: this.lastRequestedMediaSeqRaw,
|
|
844
|
-
mediaSeqCount: this.
|
|
845
|
-
discSeqCount: this.
|
|
812
|
+
mediaSeqCount: this.prevMediaSeqCount,
|
|
813
|
+
discSeqCount: this.prevDiscSeqCount,
|
|
846
814
|
};
|
|
847
815
|
debug(`[${this.sessionId}]: LEADER: I am adding 'firstCounts'=${JSON.stringify(firstCounts)} to Store for future followers`);
|
|
848
816
|
await this.sessionLiveState.set("firstCounts", firstCounts);
|
|
@@ -858,39 +826,148 @@ class SessionLive {
|
|
|
858
826
|
return;
|
|
859
827
|
}
|
|
860
828
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
829
|
+
_shiftSegments(opt) {
|
|
830
|
+
let _totalDur = 0;
|
|
831
|
+
let _segments = {};
|
|
832
|
+
let _name = "";
|
|
833
|
+
let _removedSegments = 0;
|
|
834
|
+
let _removedDiscontinuities = 0;
|
|
835
|
+
|
|
836
|
+
if (opt && opt.totalDur) {
|
|
837
|
+
_totalDur = opt.totalDur;
|
|
838
|
+
}
|
|
839
|
+
if (opt && opt.segments) {
|
|
840
|
+
_segments = JSON.parse(JSON.stringify(opt.segments)); // clone it
|
|
841
|
+
}
|
|
842
|
+
if (opt && opt.name) {
|
|
843
|
+
_name = opt.name || "NONE";
|
|
844
|
+
}
|
|
845
|
+
if (opt && opt.removedSegments) {
|
|
846
|
+
_removedSegments = opt.removedSegments;
|
|
847
|
+
}
|
|
848
|
+
if (opt && opt.removedDiscontinuities) {
|
|
849
|
+
_removedDiscontinuities = opt.removedDiscontinuities;
|
|
850
|
+
}
|
|
851
|
+
const bws = Object.keys(_segments);
|
|
852
|
+
|
|
853
|
+
/* When Total Duration is past the Limit, start Shifting V2L|LIVE segments if found */
|
|
854
|
+
while (_totalDur > TARGET_PLAYLIST_DURATION_SEC) {
|
|
855
|
+
// Skip loop if there are no more segments to remove...
|
|
856
|
+
if (_segments[bws[0]].length === 0) {
|
|
857
|
+
return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
|
|
858
|
+
}
|
|
859
|
+
debug(`[${this.sessionId}]: ${_name}: (${_totalDur})s/(${TARGET_PLAYLIST_DURATION_SEC})s - Playlist Duration is Over the Target. Shift needed!`);
|
|
860
|
+
let timeToRemove = 0;
|
|
864
861
|
let incrementDiscSeqCount = false;
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
862
|
+
|
|
863
|
+
// Shift Segments for each variant...
|
|
864
|
+
for (let i = 0; i < bws.length; i++) {
|
|
865
|
+
let seg = _segments[bws[i]].shift();
|
|
866
|
+
if (i === 0) {
|
|
867
|
+
debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
|
|
868
|
+
}
|
|
869
|
+
if (seg && seg.discontinuity) {
|
|
870
|
+
incrementDiscSeqCount = true;
|
|
871
|
+
if (_segments[bws[i]].length > 0) {
|
|
872
|
+
seg = _segments[bws[i]].shift();
|
|
873
|
+
if (i === 0) {
|
|
874
|
+
debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
|
|
875
|
+
}
|
|
872
876
|
}
|
|
873
877
|
}
|
|
878
|
+
if (seg && seg.duration) {
|
|
879
|
+
timeToRemove = seg.duration;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (timeToRemove) {
|
|
883
|
+
_totalDur -= timeToRemove;
|
|
884
|
+
// Increment number of removed segments...
|
|
885
|
+
_removedSegments++;
|
|
874
886
|
}
|
|
875
887
|
if (incrementDiscSeqCount) {
|
|
876
|
-
|
|
888
|
+
// Update Session Live Discontinuity Sequence Count
|
|
889
|
+
_removedDiscontinuities++;
|
|
877
890
|
}
|
|
878
|
-
this.mediaSeqCount++;
|
|
879
891
|
}
|
|
892
|
+
return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
|
|
893
|
+
}
|
|
880
894
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
895
|
+
/**
|
|
896
|
+
* Shifts V2L or LIVE items if total segment duration (V2L+LIVE) are over the target duration.
|
|
897
|
+
* It will also update and increment SessionLive's MediaSeqCount and DiscSeqCount based
|
|
898
|
+
* on what was shifted.
|
|
899
|
+
* @param {string} instanceName Name of instance "LEADER" | "FOLLOWER"
|
|
900
|
+
* @returns {number} The new total duration in seconds
|
|
901
|
+
*/
|
|
902
|
+
_incrementAndShift(instanceName) {
|
|
903
|
+
if (!instanceName) {
|
|
904
|
+
instanceName = "UNKNOWN";
|
|
905
|
+
}
|
|
906
|
+
const vodBws = Object.keys(this.vodSegments);
|
|
907
|
+
const liveBws = Object.keys(this.liveSegQueue);
|
|
908
|
+
let vodTotalDur = 0;
|
|
909
|
+
let liveTotalDur = 0;
|
|
910
|
+
let totalDur = 0;
|
|
911
|
+
let removedSegments = 0;
|
|
912
|
+
let removedDiscontinuities = 0;
|
|
913
|
+
|
|
914
|
+
// Calculate Playlist Total Duration
|
|
915
|
+
this.vodSegments[vodBws[0]].forEach((seg) => {
|
|
916
|
+
if (seg.duration) {
|
|
917
|
+
vodTotalDur += seg.duration;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
this.liveSegQueue[liveBws[0]].forEach((seg) => {
|
|
921
|
+
if (seg.duration) {
|
|
922
|
+
liveTotalDur += seg.duration;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
totalDur = vodTotalDur + liveTotalDur;
|
|
926
|
+
debug(`[${this.sessionId}]: ${instanceName}: L2L dur->: ${liveTotalDur}s | V2L dur->: ${vodTotalDur}s | Total dur->: ${totalDur}s`);
|
|
927
|
+
|
|
928
|
+
/** --- SHIFT then INCREMENT --- **/
|
|
929
|
+
|
|
930
|
+
// Shift V2L Segments
|
|
931
|
+
const outputV2L = this._shiftSegments({
|
|
932
|
+
name: instanceName,
|
|
933
|
+
totalDur: totalDur,
|
|
934
|
+
segments: this.vodSegments,
|
|
935
|
+
removedSegments: removedSegments,
|
|
936
|
+
removedDiscontinuities: removedDiscontinuities,
|
|
937
|
+
});
|
|
938
|
+
// Update V2L Segments
|
|
939
|
+
this.vodSegments = outputV2L.shiftedSegments;
|
|
940
|
+
// Update values
|
|
941
|
+
totalDur = outputV2L.totalDuration;
|
|
942
|
+
removedSegments = outputV2L.removedSegments;
|
|
943
|
+
removedDiscontinuities = outputV2L.removedDiscontinuities;
|
|
944
|
+
// Shift LIVE Segments
|
|
945
|
+
const outputLIVE = this._shiftSegments({
|
|
946
|
+
name: instanceName,
|
|
947
|
+
totalDur: totalDur,
|
|
948
|
+
segments: this.liveSegQueue,
|
|
949
|
+
removedSegments: removedSegments,
|
|
950
|
+
removedDiscontinuities: removedDiscontinuities,
|
|
951
|
+
});
|
|
952
|
+
// Update LIVE Segments
|
|
953
|
+
this.liveSegQueue = outputLIVE.shiftedSegments;
|
|
954
|
+
// Update values
|
|
955
|
+
totalDur = outputLIVE.totalDuration;
|
|
956
|
+
removedSegments = outputLIVE.removedSegments;
|
|
957
|
+
removedDiscontinuities = outputLIVE.removedDiscontinuities;
|
|
958
|
+
|
|
959
|
+
// Update Session Live Discontinuity Sequence Count...
|
|
960
|
+
this.prevDiscSeqCount = this.discSeqCount;
|
|
961
|
+
this.discSeqCount += removedDiscontinuities;
|
|
962
|
+
// Update Session Live Media Sequence Count...
|
|
963
|
+
this.prevMediaSeqCount = this.mediaSeqCount;
|
|
964
|
+
this.mediaSeqCount += removedSegments;
|
|
965
|
+
if (this.discSeqCount !== this.prevDiscSeqCount) {
|
|
966
|
+
debug(`[${this.sessionId}]: ${instanceName}: Incrementing Dseq Count from {${this.prevDiscSeqCount}} -> {${this.discSeqCount}}`);
|
|
967
|
+
}
|
|
968
|
+
debug(`[${this.sessionId}]: ${instanceName}: Incrementing Mseq Count from [${this.prevMediaSeqCount}] -> [${this.mediaSeqCount}]`);
|
|
969
|
+
debug(`[${this.sessionId}]: ${instanceName}: Finished updating all Counts and Segment Queues!`);
|
|
970
|
+
return totalDur;
|
|
894
971
|
}
|
|
895
972
|
|
|
896
973
|
async _loadMediaManifest(bw) {
|
|
@@ -957,28 +1034,17 @@ class SessionLive {
|
|
|
957
1034
|
//debug(`[${this.sessionId}]: Current RAW Mseq: [${m3u.get("mediaSequence")}]`);
|
|
958
1035
|
//debug(`[${this.sessionId}]: Previous RAW Mseq: [${this.lastRequestedMediaSeqRaw}]`);
|
|
959
1036
|
|
|
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
1037
|
if (this.pushAmount >= 0) {
|
|
967
|
-
this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
|
|
968
|
-
}
|
|
1038
|
+
this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
|
|
1039
|
+
}
|
|
969
1040
|
this.targetDuration = m3u.get("targetDuration");
|
|
970
|
-
let amountRemovedDiscSeqs = 0;
|
|
971
1041
|
let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
|
|
972
1042
|
startIdx = startIdx < 0 ? 0 : startIdx;
|
|
973
1043
|
if (mediaManifestUri) {
|
|
974
1044
|
// push segments
|
|
975
|
-
|
|
1045
|
+
this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
|
|
976
1046
|
}
|
|
977
|
-
|
|
978
|
-
removedDiscSeqs: amountRemovedDiscSeqs,
|
|
979
|
-
bw: liveTargetBandwidth,
|
|
980
|
-
};
|
|
981
|
-
resolve(resolveObj);
|
|
1047
|
+
resolve();
|
|
982
1048
|
} catch (exc) {
|
|
983
1049
|
console.error("ERROR: " + exc);
|
|
984
1050
|
reject(exc);
|
|
@@ -986,8 +1052,15 @@ class SessionLive {
|
|
|
986
1052
|
});
|
|
987
1053
|
}
|
|
988
1054
|
|
|
1055
|
+
/**
|
|
1056
|
+
* Collects 'new' PlaylistItems and converts them into custom SegmentItems,
|
|
1057
|
+
* then Pushes them to the LiveSegQueue for all variants.
|
|
1058
|
+
* @param {number} startIdx
|
|
1059
|
+
* @param {m3u8.Item.PlaylistItem} playlistItems
|
|
1060
|
+
* @param {string} baseUrl
|
|
1061
|
+
* @param {string} liveTargetBandwidth
|
|
1062
|
+
*/
|
|
989
1063
|
_addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
|
|
990
|
-
let amountRemovedDiscSeqs = 0;
|
|
991
1064
|
for (let i = startIdx; i < playlistItems.length; i++) {
|
|
992
1065
|
debug(`[${this.sessionId}]: Adding Live Segment(s) to Queue (for bw=${liveTargetBandwidth})`);
|
|
993
1066
|
let seg = {};
|
|
@@ -1056,30 +1129,11 @@ class SessionLive {
|
|
|
1056
1129
|
if (daterangeData) {
|
|
1057
1130
|
seg["daterange"] = daterangeData;
|
|
1058
1131
|
}
|
|
1059
|
-
|
|
1132
|
+
// Push new Live Segments!
|
|
1060
1133
|
this.liveSegQueue[liveTargetBandwidth].push(seg);
|
|
1061
|
-
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
|
-
}
|
|
1134
|
+
this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
|
|
1080
1135
|
}
|
|
1081
1136
|
}
|
|
1082
|
-
return amountRemovedDiscSeqs;
|
|
1083
1137
|
}
|
|
1084
1138
|
|
|
1085
1139
|
/*
|
|
@@ -1096,7 +1150,7 @@ class SessionLive {
|
|
|
1096
1150
|
throw new Error("No bandwidth provided");
|
|
1097
1151
|
}
|
|
1098
1152
|
const liveTargetBandwidth = this._findNearestBw(bw, Object.keys(this.mediaManifestURIs));
|
|
1099
|
-
const vodTargetBandwidth = this.
|
|
1153
|
+
const vodTargetBandwidth = this._getNearestBandwidth(bw, Object.keys(this.vodSegments));
|
|
1100
1154
|
debug(`[${this.sessionId}]: Client requesting manifest for bw=(${bw}). Nearest LiveBw=(${liveTargetBandwidth})`);
|
|
1101
1155
|
|
|
1102
1156
|
if (this.blockGenerateManifest) {
|
|
@@ -1219,6 +1273,20 @@ class SessionLive {
|
|
|
1219
1273
|
});
|
|
1220
1274
|
}
|
|
1221
1275
|
|
|
1276
|
+
_getNearestBandwidth(bandwidthToMatch, array) {
|
|
1277
|
+
const sortedBandwidths = array.sort((a, b) => a - b);
|
|
1278
|
+
const exactMatch = sortedBandwidths.find((a) => a == bandwidthToMatch);
|
|
1279
|
+
if (exactMatch) {
|
|
1280
|
+
return exactMatch;
|
|
1281
|
+
}
|
|
1282
|
+
for (let i = 0; i < sortedBandwidths.length; i++) {
|
|
1283
|
+
if (Number(bandwidthToMatch) <= Number(sortedBandwidths[i])) {
|
|
1284
|
+
return sortedBandwidths[i];
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return sortedBandwidths[sortedBandwidths.length - 1];
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1222
1290
|
_getFirstBwWithSegmentsInList(allSegments) {
|
|
1223
1291
|
const bandwidths = Object.keys(allSegments);
|
|
1224
1292
|
for (let i = 0; i < bandwidths.length; i++) {
|
|
@@ -1253,7 +1321,7 @@ class SessionLive {
|
|
|
1253
1321
|
const toKeep = new Set();
|
|
1254
1322
|
let newItem = {};
|
|
1255
1323
|
profiles.forEach((profile) => {
|
|
1256
|
-
let bwToKeep = this.
|
|
1324
|
+
let bwToKeep = this._getNearestBandwidth(profile.bw, Object.keys(this.mediaManifestURIs));
|
|
1257
1325
|
toKeep.add(bwToKeep);
|
|
1258
1326
|
});
|
|
1259
1327
|
toKeep.forEach((bw) => {
|
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
|
};
|