eyevinn-channel-engine 3.2.7 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,6 +108,7 @@ Available options when constructing the Channel Engine object are:
108
108
  - `maxTickInterval`: The maximum interval for playhead tick interval. Default is 10000 ms.
109
109
  - `cloudWatchMetrics`: Output CloudWatch JSON metrics on console log. Default is false.
110
110
  - `useDemuxedAudio`: Enable playing VODs with multiple audio tracks. Default is false.
111
+ - `alwaysNewSegments`: Force all new HLS media sequences to always contain at least 1 new segment. Default is false.
111
112
 
112
113
  ## High Availability
113
114
 
package/engine/server.js CHANGED
@@ -33,9 +33,13 @@ class ChannelEngine {
33
33
  this.adXchangeUri = options.adXchangeUri;
34
34
  }
35
35
  this.useDemuxedAudio = false;
36
- if (options && options.useDemuxedAudio === true) {
36
+ if (options && options.useDemuxedAudio) {
37
37
  this.useDemuxedAudio = true;
38
38
  }
39
+ this.alwaysNewSegments = false;
40
+ if (options && options.alwaysNewSegments) {
41
+ this.alwaysNewSegments = true;
42
+ }
39
43
  if (options && options.defaultSlateUri) {
40
44
  this.defaultSlateUri = options.defaultSlateUri;
41
45
  this.slateRepetitions = options.slateRepetitions || 10;
@@ -228,6 +232,7 @@ class ChannelEngine {
228
232
  sessionId: channel.id,
229
233
  averageSegmentDuration: channel.options && channel.options.averageSegmentDuration ? channel.options.averageSegmentDuration : this.streamerOpts.defaultAverageSegmentDuration,
230
234
  useDemuxedAudio: options.useDemuxedAudio,
235
+ alwaysNewSegments: options.alwaysNewSegments,
231
236
  playheadDiffThreshold: channel.options && channel.options.playheadDiffThreshold ? channel.options.playheadDiffThreshold : this.streamerOpts.defaultPlayheadDiffThreshold,
232
237
  maxTickInterval: channel.options && channel.options.maxTickInterval ? channel.options.maxTickInterval : this.streamerOpts.defaultMaxTickInterval,
233
238
  targetDurationPadding: channel.options && channel.options.targetDurationPadding ? channel.options.targetDurationPadding : this.streamerOpts.targetDurationPadding,
@@ -444,6 +449,7 @@ class ChannelEngine {
444
449
  options.adXchangeUri = this.adXchangeUri;
445
450
  options.averageSegmentDuration = this.streamerOpts.defaultAverageSegmentDuration;
446
451
  options.useDemuxedAudio = this.useDemuxedAudio;
452
+ options.alwaysNewSegments = this.alwaysNewSegments;
447
453
  options.playheadDiffThreshold = this.streamerOpts.defaultPlayheadDiffThreshold;
448
454
  options.maxTickInterval = this.streamerOpts.defaultMaxTickInterval;
449
455
  options.targetDurationPadding = this.streamerOpts.targetDurationPadding;
package/engine/session.js CHANGED
@@ -15,6 +15,7 @@ const ChaosMonkey = require('./chaos_monkey.js');
15
15
  const AVERAGE_SEGMENT_DURATION = 3000;
16
16
  const DEFAULT_PLAYHEAD_DIFF_THRESHOLD = 1000;
17
17
  const DEFAULT_MAX_TICK_INTERVAL = 10000;
18
+ const DIFF_CORRECTION_RATE = 0.5;
18
19
 
19
20
  const timer = ms => new Promise(res => setTimeout(res, ms));
20
21
 
@@ -43,10 +44,15 @@ class Session {
43
44
  this.playheadDiffThreshold = DEFAULT_PLAYHEAD_DIFF_THRESHOLD;
44
45
  this.maxTickInterval = DEFAULT_MAX_TICK_INTERVAL;
45
46
  this.diffCompensation = null;
47
+ this.timePositionOffset = 0;
46
48
  this.prevVodMediaSeq = {
47
49
  video: null,
48
50
  audio: null
49
51
  }
52
+ this.prevMediaSeqOffset = {
53
+ video: null,
54
+ audio: null
55
+ }
50
56
  this.waitingForNextVod = false;
51
57
  this.leaderIsSettingNextVod = false;
52
58
  this.isSwitchingBackToV2L = false;
@@ -58,8 +64,12 @@ class Session {
58
64
  reloadBehind: null,
59
65
  }
60
66
  this.isAllowedToClearVodCache = null;
61
-
67
+ this.alwaysNewSegments = null;
62
68
  if (config) {
69
+ if (config.alwaysNewSegments) {
70
+ this.alwaysNewSegments = config.alwaysNewSegments;
71
+ }
72
+
63
73
  if (config.sessionId) {
64
74
  this._sessionId = config.sessionId;
65
75
  }
@@ -172,30 +182,45 @@ class Session {
172
182
  const reqTickInterval = playheadState.tickInterval;
173
183
  const timeSpentInIncrement = (tsIncrementEnd - tsIncrementBegin) / 1000;
174
184
  let tickInterval = reqTickInterval - timeSpentInIncrement;
185
+ // Apply HLSVod delta time for current msequence.
175
186
  const delta = await this._getCurrentDeltaTime();
176
187
  if (delta != 0) {
177
- debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval`);
178
188
  tickInterval += delta;
189
+ debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval. tick=${tickInterval}`);
179
190
  }
180
191
  const position = (await this._getCurrentPlayheadPosition()) * 1000;
181
- const timePosition = Date.now() - playheadState.playheadRef;
192
+ let timePosition = Date.now() - playheadState.playheadRef;
193
+ // Apply time position offset if set, only after external diff compensation has concluded.
194
+ if (this.timePositionOffset && this.diffCompensation <= 0 && this.alwaysNewSegments) {
195
+ timePosition -= this.timePositionOffset;
196
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
197
+ { event: 'applyTimePositionOffset', channel: this._sessionId, offsetMs: this.timePositionOffset });
198
+ }
182
199
  const diff = position - timePosition;
183
200
  debug(`[${this._sessionId}]: ${timePosition}:${position}:${diff > 0 ? '+' : ''}${diff}ms`);
184
201
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
185
202
  { event: 'playheadDiff', channel: this._sessionId, diffMs: diff });
186
- if (diff > this.playheadDiffThreshold) {
187
- tickInterval += ((diff / 1000)) - (this.playheadDiffThreshold / 1000);
188
- } else if (diff < -this.playheadDiffThreshold) {
189
- tickInterval += ((diff / 1000)) + (this.playheadDiffThreshold / 1000);
190
- }
203
+ if (this.alwaysNewSegments) {
204
+ // Apply Playhead diff compensation, only after external diff compensation has concluded.
205
+ if (this.diffCompensation <= 0) {
206
+ const timeToAdd = this._getPlayheadDiffCompesationValue(diff, this.playheadDiffThreshold);
207
+ tickInterval += timeToAdd;
208
+ }
209
+ } else {
210
+ // Apply Playhead diff compensation, always.
211
+ const timeToAdd = this._getPlayheadDiffCompesationValue(diff, this.playheadDiffThreshold);
212
+ tickInterval += timeToAdd;
213
+ }
214
+ // Apply external diff compensation if available.
191
215
  if (this.diffCompensation && this.diffCompensation > 0) {
192
- const DIFF_COMPENSATION = 2000;
216
+ const DIFF_COMPENSATION = (reqTickInterval * DIFF_CORRECTION_RATE).toFixed(2) * 1000;
193
217
  debug(`[${this._sessionId}]: Adding ${DIFF_COMPENSATION}msec to tickInterval to compensate for schedule diff (current=${this.diffCompensation}msec)`);
194
218
  tickInterval += (DIFF_COMPENSATION / 1000);
195
219
  this.diffCompensation -= DIFF_COMPENSATION;
196
220
  }
221
+ // Keep tickInterval within upper and lower limits.
197
222
  debug(`[${this._sessionId}]: Requested tickInterval=${tickInterval}s (max=${this.maxTickInterval / 1000}s, diffThreshold=${this.playheadDiffThreshold}msec)`);
198
- if (tickInterval <= 0) {
223
+ if (tickInterval <= 0.5) {
199
224
  tickInterval = 0.5;
200
225
  } else if (tickInterval > (this.maxTickInterval / 1000)) {
201
226
  tickInterval = this.maxTickInterval / 1000;
@@ -206,6 +231,13 @@ class Session {
206
231
  await this._playheadState.set("tickMs", (tsTickEnd - tsIncrementBegin));
207
232
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
208
233
  { event: 'tickInterval', channel: this._sessionId, tickTimeMs: (tsTickEnd - tsIncrementBegin) });
234
+ if (this.alwaysNewSegments) {
235
+ // Use dynamic base-tickInterval. Set according to duration of latest segment.
236
+ const lastDuration = await this._getLastDuration(manifest);
237
+ const nextTickInterval = lastDuration < 2 ? 2 : lastDuration;
238
+ await this._playheadState.set("tickInterval", nextTickInterval);
239
+ cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "tickIntervalUpdated", channel: this._sessionId, tickIntervalSec: nextTickInterval });
240
+ }
209
241
  }
210
242
  } catch (err) {
211
243
  debug(`[${this._sessionId}]: Playhead consumer crashed (1)`);
@@ -376,7 +408,16 @@ class Session {
376
408
  if (currentVod) {
377
409
  try {
378
410
  const mediaSegments = currentVod.getLiveMediaSequenceSegments(playheadState.vodMediaSeqVideo);
379
- debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}_${currentVod.getLiveMediaSequencesCount()}`);
411
+
412
+ let mediaSequenceValue = 0;
413
+ if (currentVod.sequenceAlwaysContainNewSegments) {
414
+ mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
415
+ debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}}`);
416
+ } else {
417
+ mediaSequenceValue = playheadState.vodMediaSeqVideo;
418
+ }
419
+
420
+ debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}(${mediaSequenceValue})_${currentVod.getLiveMediaSequencesCount()}`);
380
421
  return mediaSegments;
381
422
  } catch (err) {
382
423
  logerror(this._sessionId, err);
@@ -428,10 +469,18 @@ class Session {
428
469
  const currentVod = await this._sessionState.getCurrentVod();
429
470
  if (currentVod) {
430
471
  try {
472
+ let mediaSequenceValue = 0;
473
+ if (currentVod.sequenceAlwaysContainNewSegments) {
474
+ mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
475
+ debug(`[${this._sessionId}]: seqIndex=${playheadState.vodMediaSeqVideo}_seqValue=${mediaSequenceValue}`)
476
+ } else {
477
+ mediaSequenceValue = playheadState.vodMediaSeqVideo;
478
+ }
431
479
  const discSeqCount = discSeqOffset + currentVod.discontinuities[playheadState.vodMediaSeqVideo];
432
- debug(`[${this._sessionId}]: MediaSeq: (${(playheadState.mediaSeq + playheadState.vodMediaSeqVideo)}) and DiscSeq: (${discSeqCount}) requested `);
480
+
481
+ debug(`[${this._sessionId}]: MediaSeq: (${playheadState.mediaSeq}+${mediaSequenceValue}=${(playheadState.mediaSeq + mediaSequenceValue)}) and DiscSeq: (${discSeqCount}) requested `);
433
482
  return {
434
- 'mediaSeq': (playheadState.mediaSeq + playheadState.vodMediaSeqVideo),
483
+ 'mediaSeq': (playheadState.mediaSeq + mediaSequenceValue),
435
484
  'discSeq': discSeqCount,
436
485
  'vodMediaSeqVideo': playheadState.vodMediaSeqVideo,
437
486
  };
@@ -464,6 +513,9 @@ class Session {
464
513
  if (this.prevVodMediaSeq.video === null) {
465
514
  this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
466
515
  }
516
+ if (this.prevMediaSeqOffset.video === null) {
517
+ this.prevMediaSeqOffset.video = playheadState.mediaSeq;
518
+ }
467
519
 
468
520
  if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
469
521
  debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
@@ -477,13 +529,30 @@ class Session {
477
529
  }
478
530
 
479
531
  // Force reading up from store, but only once if the condition is right
480
- if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
532
+ if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
481
533
  debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
534
+ debug(`[${this._sessionId}]: current-offset[${playheadState.mediaSeq}]_prev-offset[${this.prevMediaSeqOffset.video}]`);
535
+ // If true, then we have not updated the prev-values and not cleared the cache yet.
536
+ if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
537
+ this.isAllowedToClearVodCache = true;
538
+ }
482
539
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
483
540
  if (!isLeader && this.isAllowedToClearVodCache) {
484
541
  debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
485
542
  await this._sessionState.clearCurrentVodCache();
486
543
  this.isAllowedToClearVodCache = false;
544
+ const diffMs = await this._playheadState.get("diffCompensation");
545
+ if (diffMs) {
546
+ this.diffCompensation = diffMs;
547
+ debug(`[${this._sessionId}]: Setting diffCompensation->${this.diffCompensation}`);
548
+ if (this.diffCompensation) {
549
+ this.timePositionOffset = this.diffCompensation;
550
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
551
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
552
+ } else{
553
+ this.timePositionOffset = 0;
554
+ }
555
+ }
487
556
  }
488
557
  } else {
489
558
  this.isAllowedToClearVodCache = true;
@@ -492,10 +561,17 @@ class Session {
492
561
  const currentVod = await this._sessionState.getCurrentVod();
493
562
  if (currentVod) {
494
563
  try {
564
+
565
+ if (currentVod.sequenceAlwaysContainNewSegments) {
566
+ const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
567
+ debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}}`);
568
+ }
569
+
495
570
  debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
496
571
  const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
497
572
  debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
498
573
  this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
574
+ this.prevMediaSeqOffset.video = playheadState.mediaSeq;
499
575
  return m3u8;
500
576
  } catch (err) {
501
577
  logerror(this._sessionId, err);
@@ -591,19 +667,37 @@ class Session {
591
667
  playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
592
668
  playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
593
669
  playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
670
+
671
+ if (currentVod.sequenceAlwaysContainNewSegments) {
672
+ const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
673
+ debug(`[${this._sessionId}]: Session can now serve mseq={${playheadState.mediaSeq + mediaSequenceValue}}`);
674
+ }
675
+
594
676
  debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio} of ${currentVod.getLiveMediaSequencesCount()})`);
595
- let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
596
- await this._playheadState.setLastM3u8(m3u8);
597
677
 
598
678
  // As a FOLLOWER, we might need to read up from shared store...
599
- if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
679
+ if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
600
680
  debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
601
- if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
602
- this.alreadyCleredVodCache = false;
681
+ debug(`[${this._sessionId}]: current-offset[${playheadState.mediaSeq}]_prev-offset[${this.prevMediaSeqOffset.video}]`);
682
+ if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
683
+ this.isAllowedToClearVodCache = true;
603
684
  }
604
685
  if (!isLeader && this.isAllowedToClearVodCache) {
605
686
  debug(`[${this._sessionId}]: Not a leader and have just set 'playheadState.vodMediaSeqVideo' to 0|1. Invalidate cache to ensure having the correct VOD.`);
606
687
  await this._sessionState.clearCurrentVodCache();
688
+ currentVod = await this._sessionState.getCurrentVod();
689
+ const diffMs = await this._playheadState.get("diffCompensation");
690
+ if (diffMs) {
691
+ this.diffCompensation = diffMs;
692
+ debug(`[${this._sessionId}]: Setting diffCompensation->${this.diffCompensation}`);
693
+ if (this.diffCompensation) {
694
+ this.timePositionOffset = this.diffCompensation;
695
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
696
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
697
+ } else{
698
+ this.timePositionOffset = 0;
699
+ }
700
+ }
607
701
  this.isAllowedToClearVodCache = false;
608
702
  }
609
703
  } else {
@@ -611,7 +705,10 @@ class Session {
611
705
  }
612
706
  // Update Value for Previous Sequence
613
707
  this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
708
+ this.prevMediaSeqOffset.video = playheadState.mediaSeq;
614
709
 
710
+ let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
711
+ await this._playheadState.setLastM3u8(m3u8);
615
712
  return m3u8;
616
713
  }
617
714
 
@@ -828,17 +925,21 @@ class Session {
828
925
  const slateVod = await this._loadSlate(currentVod);
829
926
  debug(`[${this._sessionId}]: slate loaded`);
830
927
  const sessionState = await this._sessionState.getValues(["slateCount", "mediaSeq", "discSeq"]);
831
- let length = 0;
928
+ let endValue = 0;
832
929
  let lastDiscontinuity = 0;
833
930
  if (currentVod) {
834
- length = currentVod.getLiveMediaSequencesCount();
931
+ if (currentVod.sequenceAlwaysContainNewSegments) {
932
+ endValue = currentVod.getLastSequenceMediaSequenceValue();
933
+ } else {
934
+ endValue = currentVod.getLiveMediaSequencesCount();
935
+ }
835
936
  lastDiscontinuity = currentVod.getLastDiscontinuity();
836
937
  }
837
938
  await this._sessionState.set("vodMediaSeqVideo", 0);
838
939
  await this._sessionState.set("vodMediaSeqAudio", 0);
839
940
  await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
840
941
  await this._sessionState.setCurrentVod(slateVod);
841
- await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
942
+ await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endValue);
842
943
  await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
843
944
  await this._sessionState.set("slateCount", sessionState.slateCount + 1);
844
945
  await this._playheadState.set("playheadRef", Date.now());
@@ -891,7 +992,8 @@ class Session {
891
992
  let loadPromise;
892
993
  if (!vodResponse.type) {
893
994
  debug(`[${this._sessionId}]: got first VOD uri=${vodResponse.uri}:${vodResponse.offset || 0}`);
894
- newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId));
995
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
996
+ newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
895
997
  if (vodResponse.timedMetadata) {
896
998
  Object.keys(vodResponse.timedMetadata).map(k => {
897
999
  newVod.addMetadata(k, vodResponse.timedMetadata[k]);
@@ -961,6 +1063,9 @@ class Session {
961
1063
  if (this.prevVodMediaSeq.video === null) {
962
1064
  this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
963
1065
  }
1066
+ if (this.prevMediaSeqOffset.video === null) {
1067
+ this.prevMediaSeqOffset.video = sessionState.mediaSeq;
1068
+ }
964
1069
  // Clear Cache if prev count is HIGHER than current...
965
1070
  if (sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
966
1071
  debug(`[${this._sessionId}]: state=VOD_PLAYING, current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
@@ -970,8 +1075,9 @@ class Session {
970
1075
  }
971
1076
  } else {
972
1077
  // Handle edge case where Leader loaded next vod but Follower remained in state=VOD_PLAYING
973
- if (this.prevVodMediaSeq.video !== null & sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
1078
+ if (this.prevMediaSeqOffset.video !== null & sessionState.mediaSeq !== this.prevMediaSeqOffset.video) {
974
1079
  debug(`[${this._sessionId}]: state=VOD_PLAYING, current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
1080
+ debug(`[${this._sessionId}]: mediaSeq offsets -> current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}]`);
975
1081
  // Allow Follower to clear VodCache...
976
1082
  this.isAllowedToClearVodCache = true;
977
1083
  }
@@ -996,6 +1102,12 @@ class Session {
996
1102
  return;
997
1103
  }
998
1104
  const length = currentVod.getLiveMediaSequencesCount();
1105
+ let endMseqValue;
1106
+ if (currentVod.sequenceAlwaysContainNewSegments) {
1107
+ endMseqValue = currentVod.getLastSequenceMediaSequenceValue();
1108
+ } else {
1109
+ endMseqValue = currentVod.getLiveMediaSequencesCount();
1110
+ }
999
1111
  const lastDiscontinuity = currentVod.getLastDiscontinuity();
1000
1112
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
1001
1113
  let vodPromise = this._getNextVod();
@@ -1013,7 +1125,8 @@ class Session {
1013
1125
  let loadPromise;
1014
1126
  if (!vodResponse.type) {
1015
1127
  debug(`[${this._sessionId}]: got next VOD uri=${vodResponse.uri}:${vodResponse.offset}`);
1016
- newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId));
1128
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
1129
+ newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1017
1130
  if (vodResponse.timedMetadata) {
1018
1131
  Object.keys(vodResponse.timedMetadata).map(k => {
1019
1132
  newVod.addMetadata(k, vodResponse.timedMetadata[k]);
@@ -1030,6 +1143,13 @@ class Session {
1030
1143
  loadPromise = newVod.loadAfter(currentVod);
1031
1144
  if (vodResponse.diffMs) {
1032
1145
  this.diffCompensation = vodResponse.diffMs;
1146
+ if (this.diffCompensation) {
1147
+ this.timePositionOffset = this.diffCompensation;
1148
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
1149
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
1150
+ } else{
1151
+ this.timePositionOffset = 0;
1152
+ }
1033
1153
  }
1034
1154
  } else {
1035
1155
  loadPromise = new Promise((resolve, reject) => {
@@ -1051,13 +1171,15 @@ class Session {
1051
1171
  debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
1052
1172
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
1053
1173
  sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
1054
- sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
1174
+ sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endMseqValue);
1055
1175
  sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
1056
1176
  debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]`);
1057
1177
  await this._sessionState.remove("nextVod");
1058
1178
  sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1059
1179
  this.leaderIsSettingNextVod = false;
1060
1180
  await this._playheadState.set("playheadRef", Date.now());
1181
+ await this._playheadState.set("diffCompensation", this.diffCompensation);
1182
+ debug(`[${this._sessionId}]: sharing durrent vods diffCompensation=${this.diffCompensation}`);
1061
1183
  this.produceEvent({
1062
1184
  type: 'NOW_PLAYING',
1063
1185
  data: {
@@ -1073,6 +1195,18 @@ class Session {
1073
1195
  // Allow Leader|Follower to clear vodCache...
1074
1196
  this.isAllowedToClearVodCache = true;
1075
1197
  sessionState.currentVod = await this._sessionState.getCurrentVod();
1198
+ const diffMs = await this._playheadState.get("diffCompensation");
1199
+ if (diffMs) {
1200
+ this.diffCompensation = diffMs;
1201
+ debug(`[${this._sessionId}]: Setting diffCompensation=${this.diffCompensation}`);
1202
+ if (this.diffCompensation) {
1203
+ this.timePositionOffset = this.diffCompensation;
1204
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
1205
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
1206
+ } else{
1207
+ this.timePositionOffset = 0;
1208
+ }
1209
+ }
1076
1210
  }
1077
1211
  } catch (err) {
1078
1212
  console.error(`[${this._sessionId}]: Failed to init next VOD`);
@@ -1103,7 +1237,12 @@ class Session {
1103
1237
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
1104
1238
 
1105
1239
  // 2) Set new 'offset' sequences, to carry on the continuity from session-live
1106
- const mSeq = this.switchDataForSession.mediaSeq;
1240
+ let mSeq = this.switchDataForSession.mediaSeq;
1241
+ let currentVod = await this._sessionState.getCurrentVod();
1242
+ if (currentVod.sequenceAlwaysContainNewSegments) {
1243
+ // (!) will need to compensate if using this setting on HLSVod Object.
1244
+ mSeq -= 1;
1245
+ }
1107
1246
  const dSeq = this.switchDataForSession.discSeq;
1108
1247
  const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
1109
1248
  const reloadBehind = this.switchDataForSession.reloadBehind;
@@ -1122,15 +1261,14 @@ class Session {
1122
1261
  // 3) Set new media segments/currentVod, to carry on the continuity from session-live
1123
1262
  debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
1124
1263
  const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
1125
- let currentVod = await this._sessionState.getCurrentVod();
1126
- let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
1127
- if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
1128
- currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
1264
+ let nextMseq = playheadState.vodMediaSeqVideo + 1;
1265
+ if (nextMseq > currentVod.getLiveMediaSequencesCount() - 1) {
1266
+ nextMseq = currentVod.getLiveMediaSequencesCount() - 1;
1129
1267
  }
1130
1268
 
1131
1269
  // TODO: Support reloading with audioSegments as well
1132
1270
 
1133
- await currentVod.reload(currentMseq, segments, null, reloadBehind);
1271
+ await currentVod.reload(nextMseq, segments, null, reloadBehind);
1134
1272
  await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1135
1273
  await this._sessionState.set("vodReloaded", 1);
1136
1274
  await this._sessionState.set("vodMediaSeqVideo", 0);
@@ -1140,6 +1278,9 @@ class Session {
1140
1278
  await this._playheadState.set("playheadRef", Date.now());
1141
1279
  debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
1142
1280
  // 4) emit cloudwatch event object
1281
+ debug(`[${this._sessionId}]: next VOD Reloaded (${currentVod.getDeltaTimes()})`);
1282
+ debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
1283
+ debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
1143
1284
  cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "switchback", channel: this._sessionId, reqTimeMs: Date.now() - startTS });
1144
1285
 
1145
1286
  return;
@@ -1210,7 +1351,8 @@ class Session {
1210
1351
 
1211
1352
  slateVod.load()
1212
1353
  .then(() => {
1213
- hlsVod = new HLSVod(this.slateUri, null, null, null, m3u8Header(this._instanceId));
1354
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
1355
+ hlsVod = new HLSVod(this.slateUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
1214
1356
  const timestamp = Date.now();
1215
1357
  hlsVod.addMetadata('id', `slate-${timestamp}`);
1216
1358
  hlsVod.addMetadata('start-date', new Date(timestamp).toISOString());
@@ -1254,7 +1396,8 @@ class Session {
1254
1396
 
1255
1397
  slateVod.load()
1256
1398
  .then(() => {
1257
- hlsVod = new HLSVod(nexVodUri, null, null, null, m3u8Header(this._instanceId));
1399
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
1400
+ hlsVod = new HLSVod(nexVodUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
1258
1401
  const timestamp = Date.now();
1259
1402
  hlsVod.addMetadata('id', `slate-${timestamp}`);
1260
1403
  hlsVod.addMetadata('start-date', new Date(timestamp).toISOString());
@@ -1364,6 +1507,47 @@ class Session {
1364
1507
  debug(`[${this._sessionId}]: Current playhead position (${sessionState.vodMediaSeqVideo}): ${playheadPositions[sessionState.vodMediaSeqVideo]}`);
1365
1508
  return playheadPositions[sessionState.vodMediaSeqVideo];
1366
1509
  }
1510
+
1511
+ _getLastDuration(manifest) {
1512
+ return new Promise((resolve, reject) => {
1513
+ try {
1514
+ const parser = m3u8.createStream();
1515
+ let manifestStream = new Readable();
1516
+ manifestStream.push(manifest);
1517
+ manifestStream.push(null);
1518
+
1519
+ manifestStream.pipe(parser);
1520
+ parser.on('m3u', m3u => {
1521
+ if (m3u.items.PlaylistItem.length > 0) {
1522
+ const endIdx = m3u.items.PlaylistItem.length - 1;
1523
+ const bottomDuration = m3u.items.PlaylistItem[endIdx].get("duration");
1524
+ resolve(bottomDuration);
1525
+ } else {
1526
+ console.error("Empty media playlist");
1527
+ console.error(manifest);
1528
+ reject('Empty media playlist!')
1529
+ }
1530
+ });
1531
+ } catch (err) {
1532
+ reject(err);
1533
+ }
1534
+ });
1535
+ }
1536
+
1537
+ _getPlayheadDiffCompesationValue(diffMs, thresholdMs) {
1538
+ let compensationSec = 0;
1539
+ if (diffMs > thresholdMs) {
1540
+ compensationSec = (diffMs / 1000) - (thresholdMs / 1000);
1541
+ debug(`[${this._sessionId}]: Playhead stepping msequences too early. Need to wait longer. adding ${compensationSec}s`);
1542
+ return compensationSec;
1543
+ } else if (diffMs < -thresholdMs) {
1544
+ compensationSec = (diffMs / 1000) + (thresholdMs / 1000);
1545
+ debug(`[${this._sessionId}]: Playhead stepping msequences too LATE. Need to fast-forward. adding ${compensationSec}s`);
1546
+ return compensationSec;
1547
+ } else {
1548
+ return compensationSec;
1549
+ }
1550
+ }
1367
1551
  }
1368
1552
 
1369
1553
  module.exports = Session;
@@ -267,7 +267,7 @@ class SessionLive {
267
267
  debug(`[${this.sessionId}]: 'vodSegments' not empty = Using 'transitSegs'`);
268
268
  }
269
269
 
270
- debug(`[${this.sessionId}]: Setting CurrentMediaSequenceSegments. First seg is: [${this.vodSegments[allBws[0]][0].uri}]`);
270
+ debug(`[${this.sessionId}]: Setting CurrentMediaSequenceSegments. First seg is: [${this.vodSegments[Object.keys(this.vodSegments)[0]][0].uri}]`);
271
271
 
272
272
  const isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
273
273
  if (isLeader) {
@@ -501,7 +501,7 @@ class SessionLive {
501
501
  this.liveSegQueue[liveBw] = [];
502
502
  }
503
503
  // Do not push duplicates
504
- const liveSegURIs = this.liveSegQueue[liveBw].filter(seg => seg.uri).map(seg => seg.uri);
504
+ const liveSegURIs = this.liveSegQueue[liveBw].filter((seg) => seg.uri).map((seg) => seg.uri);
505
505
  if (liveSegFromLeader.uri && liveSegURIs.includes(liveSegFromLeader.uri)) {
506
506
  debug(`[${this.sessionId}]: FOLLOWER: Found duplicate live segment. Skip push! (${liveBw})`);
507
507
  } else {
@@ -542,7 +542,7 @@ class SessionLive {
542
542
  this.blockGenerateManifest = false;
543
543
  }
544
544
 
545
- let attempts = 5;
545
+ let attempts = 10;
546
546
  // CHECK AGAIN CASE 1: Store Empty
547
547
  while (!leadersMediaSeqRaw && attempts > 0) {
548
548
  const segDur = this._getAnyFirstSegmentDurationMs() || DEFAULT_PLAYHEAD_INTERVAL_MS;
@@ -565,7 +565,7 @@ class SessionLive {
565
565
  }
566
566
  }
567
567
 
568
- attempts = 5;
568
+ attempts = 10;
569
569
  // CHECK AGAIN CASE 2: Store Old
570
570
  while (leadersMediaSeqRaw <= this.lastRequestedMediaSeqRaw && attempts > 0) {
571
571
  const segDur = this._getAnyFirstSegmentDurationMs() || DEFAULT_PLAYHEAD_INTERVAL_MS;
@@ -638,7 +638,7 @@ class SessionLive {
638
638
  if (manifestList.some((result) => result.status === "rejected")) {
639
639
  FETCH_ATTEMPTS--;
640
640
  debug(`[${this.sessionId}]: ALERT! Promises I: Failed, Rejection Found! Trying again in 1000ms...`);
641
- await timer(1000)
641
+ await timer(1000);
642
642
  continue;
643
643
  }
644
644
 
@@ -654,8 +654,8 @@ class SessionLive {
654
654
  debug(`[${this.sessionId}]: Live Mseq counts=[${allMediaSeqCounts}]`);
655
655
  // Decrement fetch counter
656
656
  FETCH_ATTEMPTS--;
657
- // Calculate retry delay time. Default=1500
658
- let retryDelayMs = 1500;
657
+ // Calculate retry delay time. Default=1000
658
+ let retryDelayMs = 1000;
659
659
  if (Object.keys(this.liveSegQueue).length > 0) {
660
660
  const firstBw = Object.keys(this.liveSegQueue)[0];
661
661
  const lastIdx = this.liveSegQueue[firstBw].length - 1;
@@ -678,7 +678,7 @@ class SessionLive {
678
678
  let leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
679
679
  let tries = 20;
680
680
 
681
- while (!isLeader && (!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
681
+ while ((!isLeader && !leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
682
682
  debug(`[${this.sessionId}]: NEW FOLLOWER: Waiting for LEADER to add 'firstCounts' in store! Will look again after 1000ms (tries left=${tries})`);
683
683
  await timer(1000);
684
684
  leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
@@ -719,8 +719,9 @@ class SessionLive {
719
719
  }
720
720
  if (leadersFirstSeqCounts.mediaSeqCount !== this.mediaSeqCount) {
721
721
  this.mediaSeqCount = leadersFirstSeqCounts.mediaSeqCount;
722
- debug(`[${this.sessionId}]: FOLLOWER transistioned with wrong V2L segments, updating counts to [${
723
- this.mediaSeqCount}][${this.discSeqCount}], and reading 'transitSegs' from store`);
722
+ debug(
723
+ `[${this.sessionId}]: FOLLOWER transistioned with wrong V2L segments, updating counts to [${this.mediaSeqCount}][${this.discSeqCount}], and reading 'transitSegs' from store`
724
+ );
724
725
  const transitSegs = await this.sessionLiveState.get("transitSegs");
725
726
  if (!this._isEmpty(transitSegs)) {
726
727
  this.vodSegments = transitSegs;
@@ -1070,7 +1071,7 @@ class SessionLive {
1070
1071
  if (startIdx < 0) {
1071
1072
  this.restAmount = startIdx * -1;
1072
1073
  startIdx = 0;
1073
- }
1074
+ }
1074
1075
  if (mediaManifestUri) {
1075
1076
  // push segments
1076
1077
  this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth, isLeader);
@@ -1092,7 +1093,7 @@ class SessionLive {
1092
1093
  * @param {string} liveTargetBandwidth
1093
1094
  */
1094
1095
  _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth, isLeader) {
1095
- const leaderOrFollower = isLeader ? "LEADER": "NEW FOLLOWER";
1096
+ const leaderOrFollower = isLeader ? "LEADER" : "NEW FOLLOWER";
1096
1097
 
1097
1098
  for (let i = startIdx; i < playlistItems.length; i++) {
1098
1099
  let seg = {};
@@ -1162,7 +1163,7 @@ class SessionLive {
1162
1163
  seg["daterange"] = daterangeData;
1163
1164
  }
1164
1165
  // Push new Live Segments! But do not push duplicates
1165
- const liveSegURIs = this.liveSegQueue[liveTargetBandwidth].filter(seg => seg.uri).map(seg => seg.uri);
1166
+ const liveSegURIs = this.liveSegQueue[liveTargetBandwidth].filter((seg) => seg.uri).map((seg) => seg.uri);
1166
1167
  if (seg.uri && liveSegURIs.includes(seg.uri)) {
1167
1168
  debug(`[${this.sessionId}]: ${leaderOrFollower}: Found duplicate live segment. Skip push! (${liveTargetBandwidth})`);
1168
1169
  } else {
@@ -1372,12 +1373,12 @@ class SessionLive {
1372
1373
  if (this._isEmpty(this.liveSegQueue)) {
1373
1374
  return null;
1374
1375
  }
1375
-
1376
- const bw0 = Object.keys(this.liveSegQueue)[0];
1376
+
1377
+ const bw0 = Object.keys(this.liveSegQueue)[0];
1377
1378
  if (this.liveSegQueue[bw0].length === 0) {
1378
1379
  return null;
1379
1380
  }
1380
-
1381
+
1381
1382
  for (let i = 0; i < this.liveSegQueue[bw0].length; i++) {
1382
1383
  const segment = this.liveSegQueue[bw0][i];
1383
1384
  if (!segment.duration) {
@@ -482,7 +482,7 @@ class StreamSwitcher {
482
482
  if (mediaUri.match("^http")) {
483
483
  mediaURIs[bw] = mediaUri;
484
484
  } else {
485
- mediaURIs[bw] = new URL(mediaUri, uri);
485
+ mediaURIs[bw] = new URL(mediaUri, uri).href;
486
486
  }
487
487
  }
488
488
 
@@ -575,7 +575,7 @@ class StreamSwitcher {
575
575
  if (playlistItem.properties.uri.match("^http")) {
576
576
  segmentUri = playlistItem.properties.uri;
577
577
  } else {
578
- segmentUri = new URL(playlistItem.properties.uri, mediaURIs[bw]);
578
+ segmentUri = new URL(playlistItem.properties.uri, mediaURIs[bw]).href;
579
579
  }
580
580
  seg["duration"] = playlistItem.properties.duration;
581
581
  seg["uri"] = segmentUri;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.2.7",
3
+ "version": "3.3.2",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@eyevinn/hls-repeat": "^0.1.1",
24
24
  "@eyevinn/hls-truncate": "^0.1.0",
25
- "@eyevinn/hls-vodtolive": "^1.17.1",
25
+ "@eyevinn/hls-vodtolive": "^2.0.2",
26
26
  "@eyevinn/m3u8": "^0.4.1",
27
27
  "abort-controller": "^3.0.0",
28
28
  "debug": "^3.2.7",
package/server.js CHANGED
@@ -100,4 +100,4 @@ const engineOptions = {
100
100
 
101
101
  const engine = new ChannelEngine(refAssetManager, engineOptions);
102
102
  engine.start();
103
- engine.listen(process.env.port || 8000);
103
+ engine.listen(process.env.PORT || 8000);
@@ -414,11 +414,11 @@ describe("The initialize switching", () => {
414
414
  });
415
415
  expect(sessionCurrentSegs["1313000"][size - 1 - 2]).toEqual({
416
416
  discontinuity: true,
417
- cue: { in: true },
417
+ cue: { in: true }
418
418
  });
419
419
  expect(sessionCurrentSegs["1313000"][size - 1]).toEqual({
420
- duration: 7.5,
421
- uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00016.ts",
420
+ duration: 11.25,
421
+ uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00015.ts",
422
422
  timelinePosition: null,
423
423
  cue: null,
424
424
  });
@@ -469,7 +469,7 @@ describe("The initialize switching", () => {
469
469
  const size = sessionCurrentSegs["1313000"].length;
470
470
  expect(sessionCurrentSegs["1313000"][0]).toEqual({
471
471
  duration: 7.5,
472
- uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00011.ts",
472
+ uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00012.ts",
473
473
  timelinePosition: null,
474
474
  cue: null,
475
475
  });
@@ -745,41 +745,41 @@ describe("The initialize switching", () => {
745
745
  const borderSegment = newVodSegments["1313000"][6];
746
746
 
747
747
  const expectedLastVODSegItem = {
748
- duration: 7.2,
749
- uri: "https://maitv-vod.lab.eyevinn.technology/MORBIUS_Trailer_2020.mp4/1000/1000-00008.ts",
750
- timelinePosition: null,
751
- cue: null,
748
+ discontinuity: true,
749
+ daterange: null
752
750
  }
753
751
  const expectedFirstV2LSegItem = {
754
- duration: 10.846444,
755
- uri: "https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00000.ts",
752
+ duration: 7.2,
756
753
  timelinePosition: null,
757
754
  cue: null,
755
+ uri: 'https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00001.ts'
758
756
  }
759
757
  const expectedSecondVODSegItem = {
760
758
  duration: 7.5075,
761
- uri: "https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/600/600-00001.ts",
759
+ uri: "https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/1000/1000-00001.ts",
762
760
  timelinePosition: null,
763
761
  cue: null,
764
762
  }
765
763
 
766
764
  expect(newVodSegments["1313000"][0]).toEqual(expectedLastVODSegItem);
767
765
  expect(newVodSegments["1313000"][1]).toEqual({
768
- discontinuity: true,
769
- daterange: null,
766
+ duration: 10.846444,
767
+ timelinePosition: null,
768
+ cue: null,
769
+ uri: 'https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00000.ts'
770
770
  });
771
771
  expect(newVodSegments["1313000"][2]).toEqual(expectedFirstV2LSegItem);
772
- expect(newVodSegments["1313000"][5]).toEqual({
773
- discontinuity: true,
774
- cue: { in: true },
775
- });
772
+ // expect(newVodSegments["1313000"][5]).toEqual({
773
+ // discontinuity: true,
774
+ // cue: { in: true },
775
+ // });
776
776
  expect(borderSegment.daterange["planned-duration"]).toEqual(vodEventDurationMs2 / 1000);
777
777
  expect("start-date" in borderSegment.daterange).toBe(true);
778
778
  expect("id" in borderSegment.daterange).toBe(true);
779
- expect(borderSegment.duration).toEqual(11.3077);
779
+ expect(borderSegment.duration).toEqual(11.3447);
780
780
  expect(borderSegment.timelinePosition).toEqual(null);
781
781
  expect(borderSegment.cue).toEqual(null);
782
- expect(borderSegment.uri).toEqual("https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/600/600-00000.ts");
782
+ expect(borderSegment.uri).toEqual("https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/1000/1000-00000.ts");
783
783
  expect(newVodSegments["1313000"][7]).toEqual(expectedSecondVODSegItem);
784
784
  });
785
785