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.
@@ -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,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 - 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) {
@@ -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
- 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
493
+ // Push the New Live Segments to All Variants
514
494
  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] = [];
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
- debug(`[${this.sessionId}]: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue'`);
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
- 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!`);
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
- retryDelayMs = this.liveSegQueue[firstBw][lastIdx].duration * 1000 * 0.25;
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
- if (leadersFirstSeqCounts.mediaSeqCount - 1 !== this.mediaSeqCount) {
722
- this.mediaSeqCount = leadersFirstSeqCounts.mediaSeqCount - 1;
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
- 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];
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
- await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
835
- await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
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.mediaSeqCount,
845
- discSeqCount: this.discSeqCount,
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
- async _incrementAndShift() {
862
- const vodBandwidths = Object.keys(this.vodSegments);
863
- for (let j = 0; j < this.pushAmount; j++) {
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
- // 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;
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
- this.discSeqCount++;
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
- // 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
- // }
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
- amountRemovedDiscSeqs = this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
1045
+ this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
976
1046
  }
977
- let resolveObj = {
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._findNearestBw(bw, Object.keys(this.vodSegments));
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._findNearestBw(profile.bw, Object.keys(this.mediaManifestURIs));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.0.6",
3
+ "version": "3.1.1",
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
  };