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.
@@ -2,7 +2,7 @@ name: Publish
2
2
 
3
3
  on:
4
4
  release:
5
- types: [published]
5
+ types: [created]
6
6
 
7
7
  jobs:
8
8
  build:
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
- if (playheadState.vodMediaSeqVideo === 0) {
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 {
@@ -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 - 1;
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._findNearestBw(bw, Object.keys(this.vodSegments));
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
- let newSegCount = 0;
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 incrementDiscSeqCount = false;
516
- liveBws.forEach((bw) => {
517
- let liveSegFromLeader = this.liveSegsForFollowers[bw][segIdx];
518
- if (!this.liveSegQueue[bw]) {
519
- this.liveSegQueue[bw] = [];
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[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
- }
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
- let prevVal = this.mediaSeqCount;
543
- this.mediaSeqCount += newSegCount;
544
- debug(`[${this.sessionId}]: Incrementing Mseq Count from [${prevVal}] -> [${this.mediaSeqCount}]`);
545
- debug(`[${this.sessionId}]: Finished updating all Follower's Counts and Segment Queues!`);
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
- retryDelayMs = this.liveSegQueue[firstBw][lastIdx].duration * 1000 * 0.25;
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
- if (leadersFirstSeqCounts.mediaSeqCount - 1 !== this.mediaSeqCount) {
722
- this.mediaSeqCount = leadersFirstSeqCounts.mediaSeqCount - 1;
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
- let results = await allSettled(pushPromises); //await Promise.all(pushPromises);
814
- const removedDiscSeqList = results.map((item) => {
815
- if (item.status === "rejected") {
816
- return -1;
817
- }
818
- return item.value.removedDiscSeqs;
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
- await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
835
- await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
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.mediaSeqCount,
845
- discSeqCount: this.discSeqCount,
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
- async _incrementAndShift() {
862
- const vodBandwidths = Object.keys(this.vodSegments);
863
- for (let j = 0; j < this.pushAmount; j++) {
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
- // Shift the top vod segment
866
- for (let i = 0; i < vodBandwidths.length; i++) {
867
- let seg = this.vodSegments[vodBandwidths[i]].shift();
868
- if (seg) {
869
- if (seg.discontinuity) {
870
- this.vodSegments[vodBandwidths[i]].shift();
871
- incrementDiscSeqCount = true;
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
- this.discSeqCount++;
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
- // TODO: LASTLY: SHRINK IF NEEDED. (should only happen once if ever.)
882
- // let extraSegments = (this.liveSegQueue[liveTargetBandwidth].length + this.vodSegments[vodBandwidths[0]].length) - this.targetNumSeg;
883
- // if(extraSegments > 0) {
884
- // // POP top vod seg, 'extraSegments' times for all variants
885
- // for (let k = 0; k < extraSegments; k++) {
886
- // // Shift the top vod segment
887
- // for (let i = 0; i < vodBandwidths.length; i++) {
888
- // this.vodSegments[vodBandwidths[i]].shift();
889
- // }
890
- // this.mediaSeqCount++;
891
- // }
892
- // debug(`[${this.sessionId}]: Success! We shrunk manifest segment amount by ${extraSegments}_units`);
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
- amountRemovedDiscSeqs = this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
1020
+ this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
976
1021
  }
977
- let resolveObj = {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.0.7",
3
+ "version": "3.1.2",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -202,36 +202,42 @@ segment_0_8.ts
202
202
 
203
203
  const mockLiveSegments = {
204
204
  180000: [
205
- { duration: 7, uri: "http://mock.mock.com/180000/seg09.ts" },
206
- { duration: 7, uri: "http://mock.mock.com/180000/seg10.ts" },
207
- { duration: 7, uri: "http://mock.mock.com/180000/seg11.ts" },
208
- { duration: 7, uri: "http://mock.mock.com/180000/seg12.ts" },
209
- { duration: 7, uri: "http://mock.mock.com/180000/seg13.ts" },
210
- { duration: 7, uri: "http://mock.mock.com/180000/seg14.ts" },
211
- { duration: 7, uri: "http://mock.mock.com/180000/seg15.ts" },
212
- { duration: 7, uri: "http://mock.mock.com/180000/seg16.ts" },
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: 7, uri: "http://mock.mock.com/180000/seg09.ts" },
217
- { duration: 7, uri: "http://mock.mock.com/180000/seg10.ts" },
218
- { duration: 7, uri: "http://mock.mock.com/180000/seg11.ts" },
219
- { duration: 7, uri: "http://mock.mock.com/180000/seg12.ts" },
220
- { duration: 7, uri: "http://mock.mock.com/180000/seg13.ts" },
221
- { duration: 7, uri: "http://mock.mock.com/180000/seg14.ts" },
222
- { duration: 7, uri: "http://mock.mock.com/180000/seg15.ts" },
223
- { duration: 7, uri: "http://mock.mock.com/180000/seg16.ts" },
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: 7, uri: "http://mock.mock.com/180000/seg09.ts" },
228
- { duration: 7, uri: "http://mock.mock.com/180000/seg10.ts" },
229
- { duration: 7, uri: "http://mock.mock.com/180000/seg11.ts" },
230
- { duration: 7, uri: "http://mock.mock.com/180000/seg12.ts" },
231
- { duration: 7, uri: "http://mock.mock.com/180000/seg13.ts" },
232
- { duration: 7, uri: "http://mock.mock.com/180000/seg14.ts" },
233
- { duration: 7, uri: "http://mock.mock.com/180000/seg15.ts" },
234
- { duration: 7, uri: "http://mock.mock.com/180000/seg16.ts" },
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
  };