eyevinn-channel-engine 3.3.0 → 3.3.3

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
@@ -109,6 +109,7 @@ Available options when constructing the Channel Engine object are:
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
111
  - `alwaysNewSegments`: Force all new HLS media sequences to always contain at least 1 new segment. Default is false.
112
+ - `diffCompensationRate`: The rate for how much time is added on each sequence to slow down the playhead until it is back on schedule. Default is 0.5 (delay with half a segment duration).
112
113
 
113
114
  ## High Availability
114
115
 
package/engine/server.js CHANGED
@@ -109,6 +109,9 @@ class ChannelEngine {
109
109
  if (options && options.forceTargetDuration) {
110
110
  this.streamerOpts.forceTargetDuration = options.forceTargetDuration;
111
111
  }
112
+ if (options && options.diffCompensationRate) {
113
+ this.streamerOpts.diffCompensationRate = options.diffCompensationRate;
114
+ }
112
115
  const handleMasterRoute = async (req, res, next) => {
113
116
  debug(req.params);
114
117
  let m;
@@ -237,6 +240,7 @@ class ChannelEngine {
237
240
  maxTickInterval: channel.options && channel.options.maxTickInterval ? channel.options.maxTickInterval : this.streamerOpts.defaultMaxTickInterval,
238
241
  targetDurationPadding: channel.options && channel.options.targetDurationPadding ? channel.options.targetDurationPadding : this.streamerOpts.targetDurationPadding,
239
242
  forceTargetDuration: channel.options && channel.options.forceTargetDuration ? channel.options.forceTargetDuration : this.streamerOpts.forceTargetDuration,
243
+ diffCompensationRate: channel.options && channel.options.diffCompensationRate ? channel.options.diffCompensationRate : this.streamerOpts.diffCompensationRate,
240
244
  profile: channel.profile,
241
245
  audioTracks: channel.audioTracks,
242
246
  closedCaptions: channel.closedCaptions,
@@ -454,6 +458,7 @@ class ChannelEngine {
454
458
  options.maxTickInterval = this.streamerOpts.defaultMaxTickInterval;
455
459
  options.targetDurationPadding = this.streamerOpts.targetDurationPadding;
456
460
  options.forceTargetDuration = this.streamerOpts.forceTargetDuration;
461
+ options.diffCompensationRate = this.streamerOpts.diffCompensationRate;
457
462
  // if we are initiating a master manifest
458
463
  // outside of specific Channel context,
459
464
  // if slate options are set at the ChannelEngine level, then set these here
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 DEFAULT_DIFF_COMPENSATION_RATE = 0.5;
18
19
 
19
20
  const timer = ms => new Promise(res => setTimeout(res, ms));
20
21
 
@@ -42,7 +43,9 @@ class Session {
42
43
  this.cloudWatchLogging = false;
43
44
  this.playheadDiffThreshold = DEFAULT_PLAYHEAD_DIFF_THRESHOLD;
44
45
  this.maxTickInterval = DEFAULT_MAX_TICK_INTERVAL;
46
+ this.diffCompensationRate = DEFAULT_DIFF_COMPENSATION_RATE;
45
47
  this.diffCompensation = null;
48
+ this.timePositionOffset = 0;
46
49
  this.prevVodMediaSeq = {
47
50
  video: null,
48
51
  audio: null
@@ -117,6 +120,9 @@ class Session {
117
120
  if (config.forceTargetDuration) {
118
121
  this.forceTargetDuration = config.forceTargetDuration;
119
122
  }
123
+ if (config.diffCompensationRate) {
124
+ this.diffCompensationRate = config.diffCompensationRate;
125
+ }
120
126
  }
121
127
  }
122
128
 
@@ -180,28 +186,43 @@ class Session {
180
186
  const reqTickInterval = playheadState.tickInterval;
181
187
  const timeSpentInIncrement = (tsIncrementEnd - tsIncrementBegin) / 1000;
182
188
  let tickInterval = reqTickInterval - timeSpentInIncrement;
189
+ // Apply HLSVod delta time for current msequence.
183
190
  const delta = await this._getCurrentDeltaTime();
184
191
  if (delta != 0) {
185
- debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval`);
186
192
  tickInterval += delta;
193
+ debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval. tick=${tickInterval}`);
187
194
  }
188
195
  const position = (await this._getCurrentPlayheadPosition()) * 1000;
189
- const timePosition = Date.now() - playheadState.playheadRef;
196
+ let timePosition = Date.now() - playheadState.playheadRef;
197
+ // Apply time position offset if set, only after external diff compensation has concluded.
198
+ if (this.timePositionOffset && this.diffCompensation <= 0 && this.alwaysNewSegments) {
199
+ timePosition -= this.timePositionOffset;
200
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
201
+ { event: 'applyTimePositionOffset', channel: this._sessionId, offsetMs: this.timePositionOffset });
202
+ }
190
203
  const diff = position - timePosition;
191
204
  debug(`[${this._sessionId}]: ${timePosition}:${position}:${diff > 0 ? '+' : ''}${diff}ms`);
192
205
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
193
206
  { event: 'playheadDiff', channel: this._sessionId, diffMs: diff });
194
- if (diff > this.playheadDiffThreshold) {
195
- tickInterval += ((diff / 1000)) - (this.playheadDiffThreshold / 1000);
196
- } else if (diff < -this.playheadDiffThreshold) {
197
- tickInterval += ((diff / 1000)) + (this.playheadDiffThreshold / 1000);
198
- }
207
+ if (this.alwaysNewSegments) {
208
+ // Apply Playhead diff compensation, only after external diff compensation has concluded.
209
+ if (this.diffCompensation <= 0) {
210
+ const timeToAdd = this._getPlayheadDiffCompesationValue(diff, this.playheadDiffThreshold);
211
+ tickInterval += timeToAdd;
212
+ }
213
+ } else {
214
+ // Apply Playhead diff compensation, always.
215
+ const timeToAdd = this._getPlayheadDiffCompesationValue(diff, this.playheadDiffThreshold);
216
+ tickInterval += timeToAdd;
217
+ }
218
+ // Apply external diff compensation if available.
199
219
  if (this.diffCompensation && this.diffCompensation > 0) {
200
- const DIFF_COMPENSATION = 2000;
220
+ const DIFF_COMPENSATION = (reqTickInterval * this.diffCompensationRate).toFixed(2) * 1000;
201
221
  debug(`[${this._sessionId}]: Adding ${DIFF_COMPENSATION}msec to tickInterval to compensate for schedule diff (current=${this.diffCompensation}msec)`);
202
222
  tickInterval += (DIFF_COMPENSATION / 1000);
203
223
  this.diffCompensation -= DIFF_COMPENSATION;
204
224
  }
225
+ // Keep tickInterval within upper and lower limits.
205
226
  debug(`[${this._sessionId}]: Requested tickInterval=${tickInterval}s (max=${this.maxTickInterval / 1000}s, diffThreshold=${this.playheadDiffThreshold}msec)`);
206
227
  if (tickInterval <= 0.5) {
207
228
  tickInterval = 0.5;
@@ -214,6 +235,13 @@ class Session {
214
235
  await this._playheadState.set("tickMs", (tsTickEnd - tsIncrementBegin));
215
236
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
216
237
  { event: 'tickInterval', channel: this._sessionId, tickTimeMs: (tsTickEnd - tsIncrementBegin) });
238
+ if (this.alwaysNewSegments) {
239
+ // Use dynamic base-tickInterval. Set according to duration of latest segment.
240
+ const lastDuration = await this._getLastDuration(manifest);
241
+ const nextTickInterval = lastDuration < 2 ? 2 : lastDuration;
242
+ await this._playheadState.set("tickInterval", nextTickInterval);
243
+ cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "tickIntervalUpdated", channel: this._sessionId, tickIntervalSec: nextTickInterval });
244
+ }
217
245
  }
218
246
  } catch (err) {
219
247
  debug(`[${this._sessionId}]: Playhead consumer crashed (1)`);
@@ -388,7 +416,7 @@ class Session {
388
416
  let mediaSequenceValue = 0;
389
417
  if (currentVod.sequenceAlwaysContainNewSegments) {
390
418
  mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
391
- debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}`);
419
+ debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}}`);
392
420
  } else {
393
421
  mediaSequenceValue = playheadState.vodMediaSeqVideo;
394
422
  }
@@ -517,6 +545,18 @@ class Session {
517
545
  debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
518
546
  await this._sessionState.clearCurrentVodCache();
519
547
  this.isAllowedToClearVodCache = false;
548
+ const diffMs = await this._playheadState.get("diffCompensation");
549
+ if (diffMs) {
550
+ this.diffCompensation = diffMs;
551
+ debug(`[${this._sessionId}]: Setting diffCompensation->${this.diffCompensation}`);
552
+ if (this.diffCompensation) {
553
+ this.timePositionOffset = this.diffCompensation;
554
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
555
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
556
+ } else{
557
+ this.timePositionOffset = 0;
558
+ }
559
+ }
520
560
  }
521
561
  } else {
522
562
  this.isAllowedToClearVodCache = true;
@@ -650,6 +690,18 @@ class Session {
650
690
  debug(`[${this._sessionId}]: Not a leader and have just set 'playheadState.vodMediaSeqVideo' to 0|1. Invalidate cache to ensure having the correct VOD.`);
651
691
  await this._sessionState.clearCurrentVodCache();
652
692
  currentVod = await this._sessionState.getCurrentVod();
693
+ const diffMs = await this._playheadState.get("diffCompensation");
694
+ if (diffMs) {
695
+ this.diffCompensation = diffMs;
696
+ debug(`[${this._sessionId}]: Setting diffCompensation->${this.diffCompensation}`);
697
+ if (this.diffCompensation) {
698
+ this.timePositionOffset = this.diffCompensation;
699
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
700
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
701
+ } else{
702
+ this.timePositionOffset = 0;
703
+ }
704
+ }
653
705
  this.isAllowedToClearVodCache = false;
654
706
  }
655
707
  } else {
@@ -1095,6 +1147,13 @@ class Session {
1095
1147
  loadPromise = newVod.loadAfter(currentVod);
1096
1148
  if (vodResponse.diffMs) {
1097
1149
  this.diffCompensation = vodResponse.diffMs;
1150
+ if (this.diffCompensation) {
1151
+ this.timePositionOffset = this.diffCompensation;
1152
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
1153
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
1154
+ } else{
1155
+ this.timePositionOffset = 0;
1156
+ }
1098
1157
  }
1099
1158
  } else {
1100
1159
  loadPromise = new Promise((resolve, reject) => {
@@ -1114,15 +1173,17 @@ class Session {
1114
1173
  debug(`[${this._sessionId}]: ${newVod.getPlayheadPositions()}`);
1115
1174
  currentVod = newVod;
1116
1175
  debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
1117
- sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endMseqValue);
1118
- sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
1119
1176
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
1120
1177
  sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
1178
+ sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endMseqValue);
1179
+ sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
1121
1180
  debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]`);
1122
1181
  await this._sessionState.remove("nextVod");
1123
1182
  sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1124
1183
  this.leaderIsSettingNextVod = false;
1125
1184
  await this._playheadState.set("playheadRef", Date.now());
1185
+ await this._playheadState.set("diffCompensation", this.diffCompensation);
1186
+ debug(`[${this._sessionId}]: sharing durrent vods diffCompensation=${this.diffCompensation}`);
1126
1187
  this.produceEvent({
1127
1188
  type: 'NOW_PLAYING',
1128
1189
  data: {
@@ -1138,6 +1199,18 @@ class Session {
1138
1199
  // Allow Leader|Follower to clear vodCache...
1139
1200
  this.isAllowedToClearVodCache = true;
1140
1201
  sessionState.currentVod = await this._sessionState.getCurrentVod();
1202
+ const diffMs = await this._playheadState.get("diffCompensation");
1203
+ if (diffMs) {
1204
+ this.diffCompensation = diffMs;
1205
+ debug(`[${this._sessionId}]: Setting diffCompensation=${this.diffCompensation}`);
1206
+ if (this.diffCompensation) {
1207
+ this.timePositionOffset = this.diffCompensation;
1208
+ cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
1209
+ { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
1210
+ } else{
1211
+ this.timePositionOffset = 0;
1212
+ }
1213
+ }
1141
1214
  }
1142
1215
  } catch (err) {
1143
1216
  console.error(`[${this._sessionId}]: Failed to init next VOD`);
@@ -1209,6 +1282,9 @@ class Session {
1209
1282
  await this._playheadState.set("playheadRef", Date.now());
1210
1283
  debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
1211
1284
  // 4) emit cloudwatch event object
1285
+ debug(`[${this._sessionId}]: next VOD Reloaded (${currentVod.getDeltaTimes()})`);
1286
+ debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
1287
+ debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
1212
1288
  cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "switchback", channel: this._sessionId, reqTimeMs: Date.now() - startTS });
1213
1289
 
1214
1290
  return;
@@ -1435,6 +1511,47 @@ class Session {
1435
1511
  debug(`[${this._sessionId}]: Current playhead position (${sessionState.vodMediaSeqVideo}): ${playheadPositions[sessionState.vodMediaSeqVideo]}`);
1436
1512
  return playheadPositions[sessionState.vodMediaSeqVideo];
1437
1513
  }
1514
+
1515
+ _getLastDuration(manifest) {
1516
+ return new Promise((resolve, reject) => {
1517
+ try {
1518
+ const parser = m3u8.createStream();
1519
+ let manifestStream = new Readable();
1520
+ manifestStream.push(manifest);
1521
+ manifestStream.push(null);
1522
+
1523
+ manifestStream.pipe(parser);
1524
+ parser.on('m3u', m3u => {
1525
+ if (m3u.items.PlaylistItem.length > 0) {
1526
+ const endIdx = m3u.items.PlaylistItem.length - 1;
1527
+ const bottomDuration = m3u.items.PlaylistItem[endIdx].get("duration");
1528
+ resolve(bottomDuration);
1529
+ } else {
1530
+ console.error("Empty media playlist");
1531
+ console.error(manifest);
1532
+ reject('Empty media playlist!')
1533
+ }
1534
+ });
1535
+ } catch (err) {
1536
+ reject(err);
1537
+ }
1538
+ });
1539
+ }
1540
+
1541
+ _getPlayheadDiffCompesationValue(diffMs, thresholdMs) {
1542
+ let compensationSec = 0;
1543
+ if (diffMs > thresholdMs) {
1544
+ compensationSec = (diffMs / 1000) - (thresholdMs / 1000);
1545
+ debug(`[${this._sessionId}]: Playhead stepping msequences too early. Need to wait longer. adding ${compensationSec}s`);
1546
+ return compensationSec;
1547
+ } else if (diffMs < -thresholdMs) {
1548
+ compensationSec = (diffMs / 1000) + (thresholdMs / 1000);
1549
+ debug(`[${this._sessionId}]: Playhead stepping msequences too LATE. Need to fast-forward. adding ${compensationSec}s`);
1550
+ return compensationSec;
1551
+ } else {
1552
+ return compensationSec;
1553
+ }
1554
+ }
1438
1555
  }
1439
1556
 
1440
1557
  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 {
@@ -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.3.0",
3
+ "version": "3.3.3",
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": "^2.0.0",
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",
@@ -413,14 +413,12 @@ describe("The initialize switching", () => {
413
413
  uri: "http://mock.mock.com/180000/seg12.ts",
414
414
  });
415
415
  expect(sessionCurrentSegs["1313000"][size - 1 - 2]).toEqual({
416
- duration: 7.5,
417
- timelinePosition: null,
418
- uri: 'https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00008.ts',
419
- cue: null
416
+ discontinuity: true,
417
+ cue: { in: true }
420
418
  });
421
419
  expect(sessionCurrentSegs["1313000"][size - 1]).toEqual({
422
- duration: 7.5,
423
- uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00010.ts",
420
+ duration: 11.25,
421
+ uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00015.ts",
424
422
  timelinePosition: null,
425
423
  cue: null,
426
424
  });