eyevinn-channel-engine 3.2.7 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/engine/server.js +7 -1
- package/engine/session.js +98 -27
- package/engine/session_live.js +2 -2
- package/package.json +2 -2
- package/server.js +1 -1
- package/spec/engine/init_switching_spec.js +21 -19
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
|
|
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
|
@@ -47,6 +47,10 @@ class Session {
|
|
|
47
47
|
video: null,
|
|
48
48
|
audio: null
|
|
49
49
|
}
|
|
50
|
+
this.prevMediaSeqOffset = {
|
|
51
|
+
video: null,
|
|
52
|
+
audio: null
|
|
53
|
+
}
|
|
50
54
|
this.waitingForNextVod = false;
|
|
51
55
|
this.leaderIsSettingNextVod = false;
|
|
52
56
|
this.isSwitchingBackToV2L = false;
|
|
@@ -58,8 +62,12 @@ class Session {
|
|
|
58
62
|
reloadBehind: null,
|
|
59
63
|
}
|
|
60
64
|
this.isAllowedToClearVodCache = null;
|
|
61
|
-
|
|
65
|
+
this.alwaysNewSegments = null;
|
|
62
66
|
if (config) {
|
|
67
|
+
if (config.alwaysNewSegments) {
|
|
68
|
+
this.alwaysNewSegments = config.alwaysNewSegments;
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
if (config.sessionId) {
|
|
64
72
|
this._sessionId = config.sessionId;
|
|
65
73
|
}
|
|
@@ -195,7 +203,7 @@ class Session {
|
|
|
195
203
|
this.diffCompensation -= DIFF_COMPENSATION;
|
|
196
204
|
}
|
|
197
205
|
debug(`[${this._sessionId}]: Requested tickInterval=${tickInterval}s (max=${this.maxTickInterval / 1000}s, diffThreshold=${this.playheadDiffThreshold}msec)`);
|
|
198
|
-
if (tickInterval <= 0) {
|
|
206
|
+
if (tickInterval <= 0.5) {
|
|
199
207
|
tickInterval = 0.5;
|
|
200
208
|
} else if (tickInterval > (this.maxTickInterval / 1000)) {
|
|
201
209
|
tickInterval = this.maxTickInterval / 1000;
|
|
@@ -376,7 +384,16 @@ class Session {
|
|
|
376
384
|
if (currentVod) {
|
|
377
385
|
try {
|
|
378
386
|
const mediaSegments = currentVod.getLiveMediaSequenceSegments(playheadState.vodMediaSeqVideo);
|
|
379
|
-
|
|
387
|
+
|
|
388
|
+
let mediaSequenceValue = 0;
|
|
389
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
390
|
+
mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
|
|
391
|
+
debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}`);
|
|
392
|
+
} else {
|
|
393
|
+
mediaSequenceValue = playheadState.vodMediaSeqVideo;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}(${mediaSequenceValue})_${currentVod.getLiveMediaSequencesCount()}`);
|
|
380
397
|
return mediaSegments;
|
|
381
398
|
} catch (err) {
|
|
382
399
|
logerror(this._sessionId, err);
|
|
@@ -428,10 +445,18 @@ class Session {
|
|
|
428
445
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
429
446
|
if (currentVod) {
|
|
430
447
|
try {
|
|
448
|
+
let mediaSequenceValue = 0;
|
|
449
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
450
|
+
mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
|
|
451
|
+
debug(`[${this._sessionId}]: seqIndex=${playheadState.vodMediaSeqVideo}_seqValue=${mediaSequenceValue}`)
|
|
452
|
+
} else {
|
|
453
|
+
mediaSequenceValue = playheadState.vodMediaSeqVideo;
|
|
454
|
+
}
|
|
431
455
|
const discSeqCount = discSeqOffset + currentVod.discontinuities[playheadState.vodMediaSeqVideo];
|
|
432
|
-
|
|
456
|
+
|
|
457
|
+
debug(`[${this._sessionId}]: MediaSeq: (${playheadState.mediaSeq}+${mediaSequenceValue}=${(playheadState.mediaSeq + mediaSequenceValue)}) and DiscSeq: (${discSeqCount}) requested `);
|
|
433
458
|
return {
|
|
434
|
-
'mediaSeq': (playheadState.mediaSeq +
|
|
459
|
+
'mediaSeq': (playheadState.mediaSeq + mediaSequenceValue),
|
|
435
460
|
'discSeq': discSeqCount,
|
|
436
461
|
'vodMediaSeqVideo': playheadState.vodMediaSeqVideo,
|
|
437
462
|
};
|
|
@@ -464,6 +489,9 @@ class Session {
|
|
|
464
489
|
if (this.prevVodMediaSeq.video === null) {
|
|
465
490
|
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
466
491
|
}
|
|
492
|
+
if (this.prevMediaSeqOffset.video === null) {
|
|
493
|
+
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
494
|
+
}
|
|
467
495
|
|
|
468
496
|
if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
|
|
469
497
|
debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
|
|
@@ -477,8 +505,13 @@ class Session {
|
|
|
477
505
|
}
|
|
478
506
|
|
|
479
507
|
// Force reading up from store, but only once if the condition is right
|
|
480
|
-
if (playheadState.vodMediaSeqVideo < 2 || playheadState.
|
|
508
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
481
509
|
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
510
|
+
debug(`[${this._sessionId}]: current-offset[${playheadState.mediaSeq}]_prev-offset[${this.prevMediaSeqOffset.video}]`);
|
|
511
|
+
// If true, then we have not updated the prev-values and not cleared the cache yet.
|
|
512
|
+
if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
513
|
+
this.isAllowedToClearVodCache = true;
|
|
514
|
+
}
|
|
482
515
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
483
516
|
if (!isLeader && this.isAllowedToClearVodCache) {
|
|
484
517
|
debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
@@ -492,10 +525,17 @@ class Session {
|
|
|
492
525
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
493
526
|
if (currentVod) {
|
|
494
527
|
try {
|
|
528
|
+
|
|
529
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
530
|
+
const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
|
|
531
|
+
debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}}`);
|
|
532
|
+
}
|
|
533
|
+
|
|
495
534
|
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
496
535
|
const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
497
536
|
debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
|
|
498
537
|
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
538
|
+
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
499
539
|
return m3u8;
|
|
500
540
|
} catch (err) {
|
|
501
541
|
logerror(this._sessionId, err);
|
|
@@ -591,19 +631,25 @@ class Session {
|
|
|
591
631
|
playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
|
|
592
632
|
playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
|
|
593
633
|
playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
|
|
634
|
+
|
|
635
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
636
|
+
const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
|
|
637
|
+
debug(`[${this._sessionId}]: Session can now serve mseq={${playheadState.mediaSeq + mediaSequenceValue}}`);
|
|
638
|
+
}
|
|
639
|
+
|
|
594
640
|
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
641
|
|
|
598
642
|
// As a FOLLOWER, we might need to read up from shared store...
|
|
599
|
-
if (playheadState.vodMediaSeqVideo < 2 || playheadState.
|
|
643
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
600
644
|
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
601
|
-
|
|
602
|
-
|
|
645
|
+
debug(`[${this._sessionId}]: current-offset[${playheadState.mediaSeq}]_prev-offset[${this.prevMediaSeqOffset.video}]`);
|
|
646
|
+
if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
647
|
+
this.isAllowedToClearVodCache = true;
|
|
603
648
|
}
|
|
604
649
|
if (!isLeader && this.isAllowedToClearVodCache) {
|
|
605
650
|
debug(`[${this._sessionId}]: Not a leader and have just set 'playheadState.vodMediaSeqVideo' to 0|1. Invalidate cache to ensure having the correct VOD.`);
|
|
606
651
|
await this._sessionState.clearCurrentVodCache();
|
|
652
|
+
currentVod = await this._sessionState.getCurrentVod();
|
|
607
653
|
this.isAllowedToClearVodCache = false;
|
|
608
654
|
}
|
|
609
655
|
} else {
|
|
@@ -611,7 +657,10 @@ class Session {
|
|
|
611
657
|
}
|
|
612
658
|
// Update Value for Previous Sequence
|
|
613
659
|
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
660
|
+
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
614
661
|
|
|
662
|
+
let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
|
|
663
|
+
await this._playheadState.setLastM3u8(m3u8);
|
|
615
664
|
return m3u8;
|
|
616
665
|
}
|
|
617
666
|
|
|
@@ -828,17 +877,21 @@ class Session {
|
|
|
828
877
|
const slateVod = await this._loadSlate(currentVod);
|
|
829
878
|
debug(`[${this._sessionId}]: slate loaded`);
|
|
830
879
|
const sessionState = await this._sessionState.getValues(["slateCount", "mediaSeq", "discSeq"]);
|
|
831
|
-
let
|
|
880
|
+
let endValue = 0;
|
|
832
881
|
let lastDiscontinuity = 0;
|
|
833
882
|
if (currentVod) {
|
|
834
|
-
|
|
883
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
884
|
+
endValue = currentVod.getLastSequenceMediaSequenceValue();
|
|
885
|
+
} else {
|
|
886
|
+
endValue = currentVod.getLiveMediaSequencesCount();
|
|
887
|
+
}
|
|
835
888
|
lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
836
889
|
}
|
|
837
890
|
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
838
891
|
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
839
892
|
await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
840
893
|
await this._sessionState.setCurrentVod(slateVod);
|
|
841
|
-
await this._sessionState.set("mediaSeq", sessionState.mediaSeq +
|
|
894
|
+
await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endValue);
|
|
842
895
|
await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
843
896
|
await this._sessionState.set("slateCount", sessionState.slateCount + 1);
|
|
844
897
|
await this._playheadState.set("playheadRef", Date.now());
|
|
@@ -891,7 +944,8 @@ class Session {
|
|
|
891
944
|
let loadPromise;
|
|
892
945
|
if (!vodResponse.type) {
|
|
893
946
|
debug(`[${this._sessionId}]: got first VOD uri=${vodResponse.uri}:${vodResponse.offset || 0}`);
|
|
894
|
-
|
|
947
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
948
|
+
newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
|
|
895
949
|
if (vodResponse.timedMetadata) {
|
|
896
950
|
Object.keys(vodResponse.timedMetadata).map(k => {
|
|
897
951
|
newVod.addMetadata(k, vodResponse.timedMetadata[k]);
|
|
@@ -961,6 +1015,9 @@ class Session {
|
|
|
961
1015
|
if (this.prevVodMediaSeq.video === null) {
|
|
962
1016
|
this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
|
|
963
1017
|
}
|
|
1018
|
+
if (this.prevMediaSeqOffset.video === null) {
|
|
1019
|
+
this.prevMediaSeqOffset.video = sessionState.mediaSeq;
|
|
1020
|
+
}
|
|
964
1021
|
// Clear Cache if prev count is HIGHER than current...
|
|
965
1022
|
if (sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
|
|
966
1023
|
debug(`[${this._sessionId}]: state=VOD_PLAYING, current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
@@ -970,8 +1027,9 @@ class Session {
|
|
|
970
1027
|
}
|
|
971
1028
|
} else {
|
|
972
1029
|
// Handle edge case where Leader loaded next vod but Follower remained in state=VOD_PLAYING
|
|
973
|
-
if (this.
|
|
1030
|
+
if (this.prevMediaSeqOffset.video !== null & sessionState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
974
1031
|
debug(`[${this._sessionId}]: state=VOD_PLAYING, current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
1032
|
+
debug(`[${this._sessionId}]: mediaSeq offsets -> current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}]`);
|
|
975
1033
|
// Allow Follower to clear VodCache...
|
|
976
1034
|
this.isAllowedToClearVodCache = true;
|
|
977
1035
|
}
|
|
@@ -996,6 +1054,12 @@ class Session {
|
|
|
996
1054
|
return;
|
|
997
1055
|
}
|
|
998
1056
|
const length = currentVod.getLiveMediaSequencesCount();
|
|
1057
|
+
let endMseqValue;
|
|
1058
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
1059
|
+
endMseqValue = currentVod.getLastSequenceMediaSequenceValue();
|
|
1060
|
+
} else {
|
|
1061
|
+
endMseqValue = currentVod.getLiveMediaSequencesCount();
|
|
1062
|
+
}
|
|
999
1063
|
const lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
1000
1064
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
1001
1065
|
let vodPromise = this._getNextVod();
|
|
@@ -1013,7 +1077,8 @@ class Session {
|
|
|
1013
1077
|
let loadPromise;
|
|
1014
1078
|
if (!vodResponse.type) {
|
|
1015
1079
|
debug(`[${this._sessionId}]: got next VOD uri=${vodResponse.uri}:${vodResponse.offset}`);
|
|
1016
|
-
|
|
1080
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1081
|
+
newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
|
|
1017
1082
|
if (vodResponse.timedMetadata) {
|
|
1018
1083
|
Object.keys(vodResponse.timedMetadata).map(k => {
|
|
1019
1084
|
newVod.addMetadata(k, vodResponse.timedMetadata[k]);
|
|
@@ -1049,10 +1114,10 @@ class Session {
|
|
|
1049
1114
|
debug(`[${this._sessionId}]: ${newVod.getPlayheadPositions()}`);
|
|
1050
1115
|
currentVod = newVod;
|
|
1051
1116
|
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);
|
|
1052
1119
|
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
1053
1120
|
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
1054
|
-
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
|
|
1055
|
-
sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
1056
1121
|
debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]`);
|
|
1057
1122
|
await this._sessionState.remove("nextVod");
|
|
1058
1123
|
sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
@@ -1103,7 +1168,12 @@ class Session {
|
|
|
1103
1168
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
|
|
1104
1169
|
|
|
1105
1170
|
// 2) Set new 'offset' sequences, to carry on the continuity from session-live
|
|
1106
|
-
|
|
1171
|
+
let mSeq = this.switchDataForSession.mediaSeq;
|
|
1172
|
+
let currentVod = await this._sessionState.getCurrentVod();
|
|
1173
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
1174
|
+
// (!) will need to compensate if using this setting on HLSVod Object.
|
|
1175
|
+
mSeq -= 1;
|
|
1176
|
+
}
|
|
1107
1177
|
const dSeq = this.switchDataForSession.discSeq;
|
|
1108
1178
|
const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
|
|
1109
1179
|
const reloadBehind = this.switchDataForSession.reloadBehind;
|
|
@@ -1122,15 +1192,14 @@ class Session {
|
|
|
1122
1192
|
// 3) Set new media segments/currentVod, to carry on the continuity from session-live
|
|
1123
1193
|
debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
|
|
1124
1194
|
const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
|
|
1125
|
-
let
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
1195
|
+
let nextMseq = playheadState.vodMediaSeqVideo + 1;
|
|
1196
|
+
if (nextMseq > currentVod.getLiveMediaSequencesCount() - 1) {
|
|
1197
|
+
nextMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
1129
1198
|
}
|
|
1130
1199
|
|
|
1131
1200
|
// TODO: Support reloading with audioSegments as well
|
|
1132
1201
|
|
|
1133
|
-
await currentVod.reload(
|
|
1202
|
+
await currentVod.reload(nextMseq, segments, null, reloadBehind);
|
|
1134
1203
|
await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
1135
1204
|
await this._sessionState.set("vodReloaded", 1);
|
|
1136
1205
|
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
@@ -1210,7 +1279,8 @@ class Session {
|
|
|
1210
1279
|
|
|
1211
1280
|
slateVod.load()
|
|
1212
1281
|
.then(() => {
|
|
1213
|
-
|
|
1282
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1283
|
+
hlsVod = new HLSVod(this.slateUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
|
|
1214
1284
|
const timestamp = Date.now();
|
|
1215
1285
|
hlsVod.addMetadata('id', `slate-${timestamp}`);
|
|
1216
1286
|
hlsVod.addMetadata('start-date', new Date(timestamp).toISOString());
|
|
@@ -1254,7 +1324,8 @@ class Session {
|
|
|
1254
1324
|
|
|
1255
1325
|
slateVod.load()
|
|
1256
1326
|
.then(() => {
|
|
1257
|
-
|
|
1327
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1328
|
+
hlsVod = new HLSVod(nexVodUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
|
|
1258
1329
|
const timestamp = Date.now();
|
|
1259
1330
|
hlsVod.addMetadata('id', `slate-${timestamp}`);
|
|
1260
1331
|
hlsVod.addMetadata('start-date', new Date(timestamp).toISOString());
|
package/engine/session_live.js
CHANGED
|
@@ -542,7 +542,7 @@ class SessionLive {
|
|
|
542
542
|
this.blockGenerateManifest = false;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
let attempts =
|
|
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 =
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyevinn-channel-engine",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
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": "^
|
|
25
|
+
"@eyevinn/hls-vodtolive": "^2.0.0",
|
|
26
26
|
"@eyevinn/m3u8": "^0.4.1",
|
|
27
27
|
"abort-controller": "^3.0.0",
|
|
28
28
|
"debug": "^3.2.7",
|
package/server.js
CHANGED
|
@@ -413,12 +413,14 @@ 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
|
-
|
|
417
|
-
|
|
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
|
|
418
420
|
});
|
|
419
421
|
expect(sessionCurrentSegs["1313000"][size - 1]).toEqual({
|
|
420
422
|
duration: 7.5,
|
|
421
|
-
uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-
|
|
423
|
+
uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00010.ts",
|
|
422
424
|
timelinePosition: null,
|
|
423
425
|
cue: null,
|
|
424
426
|
});
|
|
@@ -469,7 +471,7 @@ describe("The initialize switching", () => {
|
|
|
469
471
|
const size = sessionCurrentSegs["1313000"].length;
|
|
470
472
|
expect(sessionCurrentSegs["1313000"][0]).toEqual({
|
|
471
473
|
duration: 7.5,
|
|
472
|
-
uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-
|
|
474
|
+
uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/600/600-00012.ts",
|
|
473
475
|
timelinePosition: null,
|
|
474
476
|
cue: null,
|
|
475
477
|
});
|
|
@@ -745,41 +747,41 @@ describe("The initialize switching", () => {
|
|
|
745
747
|
const borderSegment = newVodSegments["1313000"][6];
|
|
746
748
|
|
|
747
749
|
const expectedLastVODSegItem = {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
timelinePosition: null,
|
|
751
|
-
cue: null,
|
|
750
|
+
discontinuity: true,
|
|
751
|
+
daterange: null
|
|
752
752
|
}
|
|
753
753
|
const expectedFirstV2LSegItem = {
|
|
754
|
-
duration:
|
|
755
|
-
uri: "https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00000.ts",
|
|
754
|
+
duration: 7.2,
|
|
756
755
|
timelinePosition: null,
|
|
757
756
|
cue: null,
|
|
757
|
+
uri: 'https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00001.ts'
|
|
758
758
|
}
|
|
759
759
|
const expectedSecondVODSegItem = {
|
|
760
760
|
duration: 7.5075,
|
|
761
|
-
uri: "https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/
|
|
761
|
+
uri: "https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/1000/1000-00001.ts",
|
|
762
762
|
timelinePosition: null,
|
|
763
763
|
cue: null,
|
|
764
764
|
}
|
|
765
765
|
|
|
766
766
|
expect(newVodSegments["1313000"][0]).toEqual(expectedLastVODSegItem);
|
|
767
767
|
expect(newVodSegments["1313000"][1]).toEqual({
|
|
768
|
-
|
|
769
|
-
|
|
768
|
+
duration: 10.846444,
|
|
769
|
+
timelinePosition: null,
|
|
770
|
+
cue: null,
|
|
771
|
+
uri: 'https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00000.ts'
|
|
770
772
|
});
|
|
771
773
|
expect(newVodSegments["1313000"][2]).toEqual(expectedFirstV2LSegItem);
|
|
772
|
-
expect(newVodSegments["1313000"][5]).toEqual({
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
});
|
|
774
|
+
// expect(newVodSegments["1313000"][5]).toEqual({
|
|
775
|
+
// discontinuity: true,
|
|
776
|
+
// cue: { in: true },
|
|
777
|
+
// });
|
|
776
778
|
expect(borderSegment.daterange["planned-duration"]).toEqual(vodEventDurationMs2 / 1000);
|
|
777
779
|
expect("start-date" in borderSegment.daterange).toBe(true);
|
|
778
780
|
expect("id" in borderSegment.daterange).toBe(true);
|
|
779
|
-
expect(borderSegment.duration).toEqual(11.
|
|
781
|
+
expect(borderSegment.duration).toEqual(11.3447);
|
|
780
782
|
expect(borderSegment.timelinePosition).toEqual(null);
|
|
781
783
|
expect(borderSegment.cue).toEqual(null);
|
|
782
|
-
expect(borderSegment.uri).toEqual("https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/
|
|
784
|
+
expect(borderSegment.uri).toEqual("https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/1000/1000-00000.ts");
|
|
783
785
|
expect(newVodSegments["1313000"][7]).toEqual(expectedSecondVODSegItem);
|
|
784
786
|
});
|
|
785
787
|
|