eyevinn-channel-engine 3.0.5 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,20 @@ 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(["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
+ }
409
+
410
+ if (playheadState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
411
+ this.waitingForNextVod = false;
400
412
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
401
413
  if (!isLeader) {
402
414
  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 +418,7 @@ class Session {
406
418
  const currentVod = await this._sessionState.getCurrentVod();
407
419
  if (currentVod) {
408
420
  try {
421
+ debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
409
422
  const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
410
423
  debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
411
424
  return m3u8;
@@ -726,10 +739,11 @@ class Session {
726
739
  length = currentVod.getLiveMediaSequencesCount();
727
740
  lastDiscontinuity = currentVod.getLastDiscontinuity();
728
741
  }
742
+ await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
743
+ await this._sessionState.setCurrentVod(slateVod);
744
+
729
745
  await this._sessionState.set("vodMediaSeqVideo", 0);
730
746
  await this._sessionState.set("vodMediaSeqAudio", 0);
731
- await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
732
- await this._sessionState.setCurrentVod(slateVod);
733
747
  await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
734
748
  await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
735
749
  await this._sessionState.set("slateCount", sessionState.slateCount + 1);
@@ -813,10 +827,6 @@ class Session {
813
827
  debug(`[${this._sessionId}]: ${currentVod.getDeltaTimes()}`);
814
828
  debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
815
829
  //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
830
  await this._playheadState.set("playheadRef", Date.now());
821
831
  this.produceEvent({
822
832
  type: 'NOW_PLAYING',
@@ -827,6 +837,10 @@ class Session {
827
837
  });
828
838
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
829
839
  sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
840
+ sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
841
+ sessionState.discSeq = await this._sessionState.set("discSeq", 0);
842
+ sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
843
+ sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
830
844
  await this._sessionState.remove("nextVod");
831
845
  return;
832
846
  } else {
@@ -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;
@@ -136,7 +140,7 @@ class SessionLive {
136
140
  if (!this.masterManifestUri) {
137
141
  await timer(3000);
138
142
  continue;
139
- }
143
+ }
140
144
  if (this.playheadState === PlayheadState.STOPPED) {
141
145
  debug(`[${this.sessionId}]: Playhead has Stopped, clearing local session and store.`);
142
146
  this.waitForPlayhead = false;
@@ -208,7 +212,7 @@ class SessionLive {
208
212
  while (!this.masterManifestUri && attempts > 0) {
209
213
  attempts--;
210
214
  try {
211
- debug(`[${this.instanceId}][${this.sessionId}]: Going to fetch Live Master Manifest!`)
215
+ debug(`[${this.instanceId}][${this.sessionId}]: Going to fetch Live Master Manifest!`);
212
216
  // Load & Parse all Media Manifest URIs from Master
213
217
  await this._loadMasterManifest(masterManifestUri);
214
218
  this.masterManifestUri = masterManifestUri;
@@ -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 - 1;
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._findNearestBw(bw, Object.keys(this.vodSegments));
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) {
@@ -390,7 +394,7 @@ class SessionLive {
390
394
  // Generate manifest to give to client
391
395
  async getCurrentMediaManifestAsync(bw) {
392
396
  if (!this.sessionLiveState) {
393
- throw new Error('SessionLive not ready');
397
+ throw new Error("SessionLive not ready");
394
398
  }
395
399
  if (bw === null) {
396
400
  debug(`[${this.sessionId}]: No bandwidth provided`);
@@ -481,60 +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
- let newSegCount = 0;
491
- this.liveSegsForFollowers[liveBws[0]].map((seg) => {if (seg.uri) { newSegCount++ }});
492
-
493
- debug(`[${this.sessionId}]: FOLLOWER: liveSegsForFollowers Bws=${Object.keys(this.liveSegsForFollowers)}_newSegCount=${newSegCount} `);
494
- // Remove transitional segs & add live source segs collected from store
495
- for (let k = 0; k < newSegCount; k++) {
496
- let incrementDiscSeqCount = false;
497
- // Shift the top vod segment on all variants
498
- for (let i = 0; i < vodBws.length; i++) {
499
- let seg = this.vodSegments[vodBws[i]].shift();
500
- if (seg && seg.discontinuity) {
501
- incrementDiscSeqCount = true;
502
- this.vodSegments[vodBws[i]].shift();
503
- }
504
- }
505
- if (incrementDiscSeqCount) {
506
- this.discSeqCount++;
507
- }
508
- }
509
- // Push to bottom, new live source segment on all variants
493
+ // Push the New Live Segments to All Variants
510
494
  for (let segIdx = 0; segIdx < size; segIdx++) {
511
- let incrementDiscSeqCount = false;
512
- liveBws.forEach((bw)=>{
513
- let liveSegFromLeader = this.liveSegsForFollowers[bw][segIdx];
514
- if (!this.liveSegQueue[bw]) {
515
- this.liveSegQueue[bw] = [];
516
- }
517
- this.liveSegQueue[bw].push(liveSegFromLeader);
518
-
519
- let segCount = 0;
520
- this.liveSegQueue[bw].map((seg) => {if (seg.uri) { segCount++ }});
521
- if (segCount > this.targetNumSeg) {
522
- let seg = this.liveSegQueue[bw].shift();
523
- if (seg && seg.discontinuity) {
524
- incrementDiscSeqCount = true;
525
- seg = this.liveSegQueue[bw].shift();
526
- }
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] = [];
527
500
  }
528
- debug(`[${this.sessionId}]: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue'`);
529
- });
530
- if (incrementDiscSeqCount) {
531
- this.discSeqCount++;
501
+ this.liveSegQueue[liveBw].push(liveSegFromLeader);
502
+ debug(`[${this.sessionId}]: FOLLOWER: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue' (${liveBw})`);
532
503
  }
533
504
  }
534
- let prevVal = this.mediaSeqCount;
535
- this.mediaSeqCount += newSegCount;
536
- debug(`[${this.sessionId}]: Incrementing Mseq Count from [${prevVal}] -> [${this.mediaSeqCount}]`);
537
- debug(`[${this.sessionId}]: Finished updating all Follower's Counts and Segment Queues!`);
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
+ }
538
510
  }
539
511
 
540
512
  /**
@@ -651,7 +623,7 @@ class SessionLive {
651
623
  }
652
624
 
653
625
  // Handle if any promise got rejected
654
- if (manifestList.some(result => result.status === "rejected")) {
626
+ if (manifestList.some((result) => result.status === "rejected")) {
655
627
  debug(`[${this.sessionId}]: ALERT! Promises I: Failed, Rejection Found! Trying again...`);
656
628
  continue;
657
629
  }
@@ -663,16 +635,21 @@ class SessionLive {
663
635
  return item.value.mediaSeq;
664
636
  });
665
637
 
666
-
667
638
  // Handle if mediaSeqCounts are NOT synced up!
668
639
  if (!allMediaSeqCounts.every((val, i, arr) => val === arr[0])) {
669
-
670
640
  debug(`[${this.sessionId}]: Live Mseq counts=[${allMediaSeqCounts}]`);
671
641
  // Decrement fetch counter
672
642
  FETCH_ATTEMPTS--;
643
+ // Calculate retry delay time. Default=1500
644
+ let retryDelayMs = 1500;
645
+ if (Object.keys(this.liveSegQueue).length > 0) {
646
+ const firstBw = Object.keys(this.liveSegQueue)[0];
647
+ const lastIdx = this.liveSegQueue[firstBw].length - 1;
648
+ retryDelayMs = this.liveSegQueue[firstBw][lastIdx].duration * 1000 * 0.25;
649
+ }
673
650
  // Wait a little before trying again
674
- debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after 1500ms`);
675
- await timer(1500);
651
+ debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after ${retryDelayMs}ms`);
652
+ await timer(retryDelayMs);
676
653
  this.timerCompensation = false;
677
654
  continue;
678
655
  }
@@ -681,7 +658,7 @@ class SessionLive {
681
658
  let leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
682
659
  let tries = 20;
683
660
 
684
- while (!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0 || (leadersFirstSeqCounts.liveSourceMseqCount === 0)) {
661
+ while ((!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
685
662
  debug(`[${this.sessionId}]: NEW FOLLOWER: Waiting for LEADER to add 'firstCounts' in store! Will look again after 1000ms (tries left=${tries})`);
686
663
  await timer(1000);
687
664
  leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
@@ -705,8 +682,9 @@ class SessionLive {
705
682
 
706
683
  // Respawners never do this, only starter followers.
707
684
  // Edge Case: FOLLOWER transitioned from session with different segments from LEADER
708
- if (leadersFirstSeqCounts.mediaSeqCount - 1 !== this.mediaSeqCount) {
709
- this.mediaSeqCount = leadersFirstSeqCounts.mediaSeqCount - 1;
685
+ const leaderStartedOnCount = leadersFirstSeqCounts.mediaSeqCount;
686
+ if (leaderStartedOnCount !== this.mediaSeqCount) {
687
+ this.mediaSeqCount = leaderStartedOnCount;
710
688
  const transitSegs = await this.sessionLiveState.get("transitSegs");
711
689
  if (!this._isEmpty(transitSegs)) {
712
690
  this.vodSegments = transitSegs;
@@ -722,12 +700,12 @@ class SessionLive {
722
700
  }
723
701
 
724
702
  // Prepare to load segments...
725
- debug(`[${this.instanceId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
703
+ debug(`[${this.instanceId}][${this.sessionId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
726
704
  if (allMediaSeqCounts[0] === leadersFirstSeqCounts.liveSourceMseqCount) {
727
705
  this.pushAmount = 1; // Follower from start
728
706
  } else {
729
707
  // RESPAWNED NODES
730
- this.pushAmount = (allMediaSeqCounts[0] - leadersFirstSeqCounts.liveSourceMseqCount) + 1;
708
+ this.pushAmount = allMediaSeqCounts[0] - leadersFirstSeqCounts.liveSourceMseqCount + 1;
731
709
 
732
710
  const transitSegs = await this.sessionLiveState.get("transitSegs");
733
711
  //debug(`[${this.sessionId}]: NEW FOLLOWER: I tried to get 'transitSegs'. This is what I found ${JSON.stringify(transitSegs)}`);
@@ -760,7 +738,7 @@ class SessionLive {
760
738
  }
761
739
 
762
740
  if (FETCH_ATTEMPTS === 0) {
763
- debug(`[${this.sessionId}]: Fetching from Live-Source did not work! Returning to Playhead Loop...`)
741
+ debug(`[${this.sessionId}]: Fetching from Live-Source did not work! Returning to Playhead Loop...`);
764
742
  return;
765
743
  }
766
744
 
@@ -781,8 +759,7 @@ class SessionLive {
781
759
  this.firstTime = false;
782
760
  debug(`[${this.sessionId}]: Got all needed segments from live-source (read from store).\nWe are now able to build Live Manifest: [${this.mediaSeqCount}]`);
783
761
  return;
784
- }
785
- else if (leadersCurrentMseqRaw < this.lastRequestedMediaSeqRaw) {
762
+ } else if (leadersCurrentMseqRaw < this.lastRequestedMediaSeqRaw) {
786
763
  // WE ARE A RESPAWN-NODE, and we are ahead of leader.
787
764
  this.blockGenerateManifest = true;
788
765
  }
@@ -793,34 +770,34 @@ class SessionLive {
793
770
  let pushPromises = [];
794
771
  for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
795
772
  let bw = Object.keys(this.mediaManifestURIs)[i];
773
+ // will add new segments to live seg queue
796
774
  pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw));
797
775
  debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
798
776
  }
799
777
  // Segment Pushing
800
778
  debug(`[${this.sessionId}]: Executing Promises II: Segment Pushing`);
801
- let results = await allSettled(pushPromises);//await Promise.all(pushPromises);
802
- const removedDiscSeqList = results.map((item) => {
803
- if (item.status === "rejected") {
804
- return -1;
805
- }
806
- return item.value.removedDiscSeqs;
807
- });
808
- if (removedDiscSeqList.every((val, i, arr) => val === arr[0])) {
809
- // if all variants removed equal amounts of disc-tags
810
- this.discSeqCount += removedDiscSeqList[0];
779
+ await allSettled(pushPromises);
780
+
781
+ // UPDATE COUNTS, & Shift Segments in vodSegments and liveSegQueue if needed.
782
+ const newTotalDuration = this._incrementAndShift("LEADER");
783
+ if (newTotalDuration) {
784
+ debug(`[${this.sessionId}]: New Adjusted Playlist Duration=${newTotalDuration}s`);
811
785
  }
812
- // UPDATE COUNTS, & Shift Segments in vodSegments
813
- await this._incrementAndShift();
814
786
  }
815
787
 
816
788
  // -----------------------------------------------------
817
789
  // Leader writes to store so that Followers can read.
818
790
  // -----------------------------------------------------
819
791
  if (isLeader) {
820
- debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
821
792
  if (this.allowedToSet) {
822
- await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
823
- await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
793
+ const liveBws = Object.keys(this.liveSegsForFollowers);
794
+ const segListSize = this.liveSegsForFollowers[liveBws[0]].length;
795
+ // Do not replace old data with empty data
796
+ if (segListSize > 0) {
797
+ debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
798
+ await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
799
+ await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
800
+ }
824
801
  }
825
802
 
826
803
  // [LASTLY]: LEADER does this for respawned-FOLLOWERS' sake.
@@ -829,8 +806,8 @@ class SessionLive {
829
806
  await timer(1000); // maybe remove
830
807
  const firstCounts = {
831
808
  liveSourceMseqCount: this.lastRequestedMediaSeqRaw,
832
- mediaSeqCount: this.mediaSeqCount,
833
- discSeqCount: this.discSeqCount,
809
+ mediaSeqCount: this.prevMediaSeqCount,
810
+ discSeqCount: this.prevDiscSeqCount,
834
811
  };
835
812
  debug(`[${this.sessionId}]: LEADER: I am adding 'firstCounts'=${JSON.stringify(firstCounts)} to Store for future followers`);
836
813
  await this.sessionLiveState.set("firstCounts", firstCounts);
@@ -846,39 +823,148 @@ class SessionLive {
846
823
  return;
847
824
  }
848
825
 
849
- async _incrementAndShift() {
850
- const vodBandwidths = Object.keys(this.vodSegments);
851
- for (let j = 0; j < this.pushAmount; j++) {
826
+ _shiftSegments(opt) {
827
+ let _totalDur = 0;
828
+ let _segments = {};
829
+ let _name = "";
830
+ let _removedSegments = 0;
831
+ let _removedDiscontinuities = 0;
832
+
833
+ if (opt && opt.totalDur) {
834
+ _totalDur = opt.totalDur;
835
+ }
836
+ if (opt && opt.segments) {
837
+ _segments = JSON.parse(JSON.stringify(opt.segments)); // clone it
838
+ }
839
+ if (opt && opt.name) {
840
+ _name = opt.name || "NONE";
841
+ }
842
+ if (opt && opt.removedSegments) {
843
+ _removedSegments = opt.removedSegments;
844
+ }
845
+ if (opt && opt.removedDiscontinuities) {
846
+ _removedDiscontinuities = opt.removedDiscontinuities;
847
+ }
848
+ const bws = Object.keys(_segments);
849
+
850
+ /* When Total Duration is past the Limit, start Shifting V2L|LIVE segments if found */
851
+ while (_totalDur > TARGET_PLAYLIST_DURATION_SEC) {
852
+ // Skip loop if there are no more segments to remove...
853
+ if (_segments[bws[0]].length === 0) {
854
+ return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
855
+ }
856
+ debug(`[${this.sessionId}]: ${_name}: (${_totalDur})s/(${TARGET_PLAYLIST_DURATION_SEC})s - Playlist Duration is Over the Target. Shift needed!`);
857
+ let timeToRemove = 0;
852
858
  let incrementDiscSeqCount = false;
853
- // Shift the top vod segment
854
- for (let i = 0; i < vodBandwidths.length; i++) {
855
- let seg = this.vodSegments[vodBandwidths[i]].shift();
856
- if (seg) {
857
- if (seg.discontinuity) {
858
- this.vodSegments[vodBandwidths[i]].shift();
859
- incrementDiscSeqCount = true;
859
+
860
+ // Shift Segments for each variant...
861
+ for (let i = 0; i < bws.length; i++) {
862
+ let seg = _segments[bws[i]].shift();
863
+ if (i === 0) {
864
+ debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
865
+ }
866
+ if (seg && seg.discontinuity) {
867
+ incrementDiscSeqCount = true;
868
+ if (_segments[bws[i]].length > 0) {
869
+ seg = _segments[bws[i]].shift();
870
+ if (i === 0) {
871
+ debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
872
+ }
860
873
  }
861
874
  }
875
+ if (seg && seg.duration) {
876
+ timeToRemove = seg.duration;
877
+ }
878
+ }
879
+ if (timeToRemove) {
880
+ _totalDur -= timeToRemove;
881
+ // Increment number of removed segments...
882
+ _removedSegments++;
862
883
  }
863
884
  if (incrementDiscSeqCount) {
864
- this.discSeqCount++;
885
+ // Update Session Live Discontinuity Sequence Count
886
+ _removedDiscontinuities++;
865
887
  }
866
- this.mediaSeqCount++;
867
888
  }
889
+ return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
890
+ }
868
891
 
869
- // TODO: LASTLY: SHRINK IF NEEDED. (should only happen once if ever.)
870
- // let extraSegments = (this.liveSegQueue[liveTargetBandwidth].length + this.vodSegments[vodBandwidths[0]].length) - this.targetNumSeg;
871
- // if(extraSegments > 0) {
872
- // // POP top vod seg, 'extraSegments' times for all variants
873
- // for (let k = 0; k < extraSegments; k++) {
874
- // // Shift the top vod segment
875
- // for (let i = 0; i < vodBandwidths.length; i++) {
876
- // this.vodSegments[vodBandwidths[i]].shift();
877
- // }
878
- // this.mediaSeqCount++;
879
- // }
880
- // debug(`[${this.sessionId}]: Success! We shrunk manifest segment amount by ${extraSegments}_units`);
881
- // }
892
+ /**
893
+ * Shifts V2L or LIVE items if total segment duration (V2L+LIVE) are over the target duration.
894
+ * It will also update and increment SessionLive's MediaSeqCount and DiscSeqCount based
895
+ * on what was shifted.
896
+ * @param {string} instanceName Name of instance "LEADER" | "FOLLOWER"
897
+ * @returns {number} The new total duration in seconds
898
+ */
899
+ _incrementAndShift(instanceName) {
900
+ if (!instanceName) {
901
+ instanceName = "UNKNOWN";
902
+ }
903
+ const vodBws = Object.keys(this.vodSegments);
904
+ const liveBws = Object.keys(this.liveSegQueue);
905
+ let vodTotalDur = 0;
906
+ let liveTotalDur = 0;
907
+ let totalDur = 0;
908
+ let removedSegments = 0;
909
+ let removedDiscontinuities = 0;
910
+
911
+ // Calculate Playlist Total Duration
912
+ this.vodSegments[vodBws[0]].forEach((seg) => {
913
+ if (seg.duration) {
914
+ vodTotalDur += seg.duration;
915
+ }
916
+ });
917
+ this.liveSegQueue[liveBws[0]].forEach((seg) => {
918
+ if (seg.duration) {
919
+ liveTotalDur += seg.duration;
920
+ }
921
+ });
922
+ totalDur = vodTotalDur + liveTotalDur;
923
+ debug(`[${this.sessionId}]: ${instanceName}: L2L dur->: ${liveTotalDur}s | V2L dur->: ${vodTotalDur}s | Total dur->: ${totalDur}s`);
924
+
925
+ /** --- SHIFT then INCREMENT --- **/
926
+
927
+ // Shift V2L Segments
928
+ const outputV2L = this._shiftSegments({
929
+ name: instanceName,
930
+ totalDur: totalDur,
931
+ segments: this.vodSegments,
932
+ removedSegments: removedSegments,
933
+ removedDiscontinuities: removedDiscontinuities,
934
+ });
935
+ // Update V2L Segments
936
+ this.vodSegments = outputV2L.shiftedSegments;
937
+ // Update values
938
+ totalDur = outputV2L.totalDuration;
939
+ removedSegments = outputV2L.removedSegments;
940
+ removedDiscontinuities = outputV2L.removedDiscontinuities;
941
+ // Shift LIVE Segments
942
+ const outputLIVE = this._shiftSegments({
943
+ name: instanceName,
944
+ totalDur: totalDur,
945
+ segments: this.liveSegQueue,
946
+ removedSegments: removedSegments,
947
+ removedDiscontinuities: removedDiscontinuities,
948
+ });
949
+ // Update LIVE Segments
950
+ this.liveSegQueue = outputLIVE.shiftedSegments;
951
+ // Update values
952
+ totalDur = outputLIVE.totalDuration;
953
+ removedSegments = outputLIVE.removedSegments;
954
+ removedDiscontinuities = outputLIVE.removedDiscontinuities;
955
+
956
+ // Update Session Live Discontinuity Sequence Count...
957
+ this.prevDiscSeqCount = this.discSeqCount;
958
+ this.discSeqCount += removedDiscontinuities;
959
+ // Update Session Live Media Sequence Count...
960
+ this.prevMediaSeqCount = this.mediaSeqCount;
961
+ this.mediaSeqCount += removedSegments;
962
+ if (this.discSeqCount !== this.prevDiscSeqCount) {
963
+ debug(`[${this.sessionId}]: ${instanceName}: Incrementing Dseq Count from {${this.prevDiscSeqCount}} -> {${this.discSeqCount}}`);
964
+ }
965
+ debug(`[${this.sessionId}]: ${instanceName}: Incrementing Mseq Count from [${this.prevMediaSeqCount}] -> [${this.mediaSeqCount}]`);
966
+ debug(`[${this.sessionId}]: ${instanceName}: Finished updating all Counts and Segment Queues!`);
967
+ return totalDur;
882
968
  }
883
969
 
884
970
  async _loadMediaManifest(bw) {
@@ -945,26 +1031,17 @@ class SessionLive {
945
1031
  //debug(`[${this.sessionId}]: Current RAW Mseq: [${m3u.get("mediaSequence")}]`);
946
1032
  //debug(`[${this.sessionId}]: Previous RAW Mseq: [${this.lastRequestedMediaSeqRaw}]`);
947
1033
 
948
- // TODO: UPDATE target for number of segments in a manifest, if appropriate.
949
- // if (m3u.items.PlaylistItem.length < this.targetNumSeg) {
950
- // debug(`[${this.sessionId}]: WE PLAN TO LOWER SEGMENT AMOUNT IN MANIFEST FROM (${this.targetNumSeg}) TO (${m3u.items.PlaylistItem.length})`);
951
- // this.targetNumSeg = m3u.items.PlaylistItem.length;
952
- // }
953
-
954
- this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
1034
+ if (this.pushAmount >= 0) {
1035
+ this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
1036
+ }
955
1037
  this.targetDuration = m3u.get("targetDuration");
956
- let amountRemovedDiscSeqs = 0;
957
1038
  let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
958
1039
  startIdx = startIdx < 0 ? 0 : startIdx;
959
1040
  if (mediaManifestUri) {
960
1041
  // push segments
961
- amountRemovedDiscSeqs = this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
1042
+ this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
962
1043
  }
963
- let resolveObj = {
964
- removedDiscSeqs: amountRemovedDiscSeqs,
965
- bw: liveTargetBandwidth
966
- }
967
- resolve(resolveObj);
1044
+ resolve();
968
1045
  } catch (exc) {
969
1046
  console.error("ERROR: " + exc);
970
1047
  reject(exc);
@@ -972,8 +1049,15 @@ class SessionLive {
972
1049
  });
973
1050
  }
974
1051
 
1052
+ /**
1053
+ * Collects 'new' PlaylistItems and converts them into custom SegmentItems,
1054
+ * then Pushes them to the LiveSegQueue for all variants.
1055
+ * @param {number} startIdx
1056
+ * @param {m3u8.Item.PlaylistItem} playlistItems
1057
+ * @param {string} baseUrl
1058
+ * @param {string} liveTargetBandwidth
1059
+ */
975
1060
  _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
976
- let amountRemovedDiscSeqs = 0;
977
1061
  for (let i = startIdx; i < playlistItems.length; i++) {
978
1062
  debug(`[${this.sessionId}]: Adding Live Segment(s) to Queue (for bw=${liveTargetBandwidth})`);
979
1063
  let seg = {};
@@ -998,7 +1082,7 @@ class SessionLive {
998
1082
  }
999
1083
  cueData["out"] = true;
1000
1084
  cueData["duration"] = attributes["cueout"];
1001
- }
1085
+ }
1002
1086
  if ("cuecont" in attributes) {
1003
1087
  if (!cueData) {
1004
1088
  cueData = {};
@@ -1042,26 +1126,11 @@ class SessionLive {
1042
1126
  if (daterangeData) {
1043
1127
  seg["daterange"] = daterangeData;
1044
1128
  }
1045
-
1129
+ // Push new Live Segments!
1046
1130
  this.liveSegQueue[liveTargetBandwidth].push(seg);
1047
- this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
1048
- let segCount = 0;
1049
- this.liveSegQueue[liveTargetBandwidth].map((seg) => {if (seg.uri) { segCount++ }});
1050
- debug(`[${this.sessionId}]: size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
1051
- if (segCount > this.targetNumSeg) {
1052
- debug(`[${this.sessionId}]: liveTargetBandwidth=${liveTargetBandwidth}_seg=${JSON.stringify(seg,null,2)}`);
1053
- seg = this.liveSegQueue[liveTargetBandwidth].shift();
1054
- if (seg) {
1055
- if (seg.discontinuity) {
1056
- this.liveSegQueue[liveTargetBandwidth].shift();
1057
- amountRemovedDiscSeqs++;
1058
- }
1059
- }
1060
- debug(`[${this.sessionId}]: After Shifting -> size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
1061
- }
1131
+ this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
1062
1132
  }
1063
1133
  }
1064
- return amountRemovedDiscSeqs;
1065
1134
  }
1066
1135
 
1067
1136
  /*
@@ -1078,9 +1147,8 @@ class SessionLive {
1078
1147
  throw new Error("No bandwidth provided");
1079
1148
  }
1080
1149
  const liveTargetBandwidth = this._findNearestBw(bw, Object.keys(this.mediaManifestURIs));
1081
- const vodTargetBandwidth = this._findNearestBw(bw, Object.keys(this.vodSegments));
1082
- debug(`[${this.sessionId}]: Client requesting manifest for bw=(${bw}). Nearest LiveBw=(${liveTargetBandwidth})`)
1083
-
1150
+ const vodTargetBandwidth = this._getNearestBandwidth(bw, Object.keys(this.vodSegments));
1151
+ debug(`[${this.sessionId}]: Client requesting manifest for bw=(${bw}). Nearest LiveBw=(${liveTargetBandwidth})`);
1084
1152
 
1085
1153
  if (this.blockGenerateManifest) {
1086
1154
  debug(`[${this.sessionId}]: FOLLOWER: Cannot Generate Manifest! Waiting to sync-up with Leader...`);
@@ -1154,7 +1222,7 @@ class SessionLive {
1154
1222
  m3u8 += "#EXT-X-DISCONTINUITY\n";
1155
1223
  }
1156
1224
  if (seg.cue) {
1157
- if(seg.cue.out) {
1225
+ if (seg.cue.out) {
1158
1226
  if (seg.cue.scteData) {
1159
1227
  m3u8 += "#EXT-OATCLS-SCTE35:" + seg.cue.scteData + "\n";
1160
1228
  }
@@ -1166,8 +1234,7 @@ class SessionLive {
1166
1234
  if (seg.cue.cont) {
1167
1235
  if (seg.cue.scteData) {
1168
1236
  m3u8 += "#EXT-X-CUE-OUT-CONT:ElapsedTime=" + seg.cue.cont + ",Duration=" + seg.cue.duration + ",SCTE35=" + seg.cue.scteData + "\n";
1169
- }
1170
- else {
1237
+ } else {
1171
1238
  m3u8 += "#EXT-X-CUE-OUT-CONT:" + seg.cue.cont + "/" + seg.cue.duration + "\n";
1172
1239
  }
1173
1240
  }
@@ -1176,14 +1243,16 @@ class SessionLive {
1176
1243
  m3u8 += `#EXT-X-PROGRAM-DATE-TIME:${seg.datetime}\n`;
1177
1244
  }
1178
1245
  if (seg.daterange) {
1179
- const dateRangeAttributes = Object.keys(seg.daterange).map(key => daterangeAttribute(key, seg.daterange[key])).join(',');
1180
- if (!seg.datetime && seg.daterange['start-date']) {
1181
- m3u8 += "#EXT-X-PROGRAM-DATE-TIME:" + seg.daterange['start-date'] + "\n";
1246
+ const dateRangeAttributes = Object.keys(seg.daterange)
1247
+ .map((key) => daterangeAttribute(key, seg.daterange[key]))
1248
+ .join(",");
1249
+ if (!seg.datetime && seg.daterange["start-date"]) {
1250
+ m3u8 += "#EXT-X-PROGRAM-DATE-TIME:" + seg.daterange["start-date"] + "\n";
1182
1251
  }
1183
1252
  m3u8 += "#EXT-X-DATERANGE:" + dateRangeAttributes + "\n";
1184
1253
  }
1185
1254
  // Mimick logic used in hls-vodtolive
1186
- if (seg.cue && seg.cue.in){
1255
+ if (seg.cue && seg.cue.in) {
1187
1256
  m3u8 += "#EXT-X-CUE-IN" + "\n";
1188
1257
  }
1189
1258
  if (seg.uri) {
@@ -1201,6 +1270,20 @@ class SessionLive {
1201
1270
  });
1202
1271
  }
1203
1272
 
1273
+ _getNearestBandwidth(bandwidthToMatch, array) {
1274
+ const sortedBandwidths = array.sort((a, b) => a - b);
1275
+ const exactMatch = sortedBandwidths.find((a) => a == bandwidthToMatch);
1276
+ if (exactMatch) {
1277
+ return exactMatch;
1278
+ }
1279
+ for (let i = 0; i < sortedBandwidths.length; i++) {
1280
+ if (Number(bandwidthToMatch) <= Number(sortedBandwidths[i])) {
1281
+ return sortedBandwidths[i];
1282
+ }
1283
+ }
1284
+ return sortedBandwidths[sortedBandwidths.length - 1];
1285
+ }
1286
+
1204
1287
  _getFirstBwWithSegmentsInList(allSegments) {
1205
1288
  const bandwidths = Object.keys(allSegments);
1206
1289
  for (let i = 0; i < bandwidths.length; i++) {
@@ -1234,13 +1317,13 @@ class SessionLive {
1234
1317
  const profiles = this.sessionLiveProfile;
1235
1318
  const toKeep = new Set();
1236
1319
  let newItem = {};
1237
- profiles.forEach(profile => {
1238
- let bwToKeep = this._findNearestBw(profile.bw, Object.keys(this.mediaManifestURIs));
1320
+ profiles.forEach((profile) => {
1321
+ let bwToKeep = this._getNearestBandwidth(profile.bw, Object.keys(this.mediaManifestURIs));
1239
1322
  toKeep.add(bwToKeep);
1240
1323
  });
1241
1324
  toKeep.forEach((bw) => {
1242
1325
  newItem[bw] = this.mediaManifestURIs[bw];
1243
- })
1326
+ });
1244
1327
  this.mediaManifestURIs = newItem;
1245
1328
  }
1246
1329
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.0.5",
3
+ "version": "3.1.0",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server-livemix.js CHANGED
@@ -155,7 +155,7 @@ class StreamSwitchManager {
155
155
  type: StreamType.LIVE,
156
156
  start_time: startOffset,
157
157
  end_time: endTime,
158
- uri: "https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8",
158
+ uri: "https://d2fz24s2fts31b.cloudfront.net/out/v1/2e5b17b7dd0548c7b844eb1b311b6434/manifest.m3u8",
159
159
  },
160
160
  {
161
161
  eventId: this.generateID(),
@@ -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
  };