eyevinn-channel-engine 3.2.5 → 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 +111 -29
- 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;
|
|
@@ -361,9 +369,13 @@ class Session {
|
|
|
361
369
|
state = await this.getSessionState();
|
|
362
370
|
}
|
|
363
371
|
|
|
364
|
-
const playheadState =
|
|
365
|
-
|
|
372
|
+
const playheadState = {
|
|
373
|
+
vodMediaSeqVideo: null
|
|
374
|
+
}
|
|
375
|
+
if (opts && opts.targetMseq !== undefined) {
|
|
366
376
|
playheadState.vodMediaSeqVideo = opts.targetMseq;
|
|
377
|
+
} else {
|
|
378
|
+
playheadState.vodMediaSeqVideo = await this._playheadState.get("vodMediaSeqVideo");
|
|
367
379
|
}
|
|
368
380
|
|
|
369
381
|
// NOTE: Assume that VOD cache was already cleared in 'getCurrentMediaAndDiscSequenceCount()'
|
|
@@ -372,7 +384,16 @@ class Session {
|
|
|
372
384
|
if (currentVod) {
|
|
373
385
|
try {
|
|
374
386
|
const mediaSegments = currentVod.getLiveMediaSequenceSegments(playheadState.vodMediaSeqVideo);
|
|
375
|
-
|
|
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()}`);
|
|
376
397
|
return mediaSegments;
|
|
377
398
|
} catch (err) {
|
|
378
399
|
logerror(this._sessionId, err);
|
|
@@ -424,10 +445,18 @@ class Session {
|
|
|
424
445
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
425
446
|
if (currentVod) {
|
|
426
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
|
+
}
|
|
427
455
|
const discSeqCount = discSeqOffset + currentVod.discontinuities[playheadState.vodMediaSeqVideo];
|
|
428
|
-
|
|
456
|
+
|
|
457
|
+
debug(`[${this._sessionId}]: MediaSeq: (${playheadState.mediaSeq}+${mediaSequenceValue}=${(playheadState.mediaSeq + mediaSequenceValue)}) and DiscSeq: (${discSeqCount}) requested `);
|
|
429
458
|
return {
|
|
430
|
-
'mediaSeq': (playheadState.mediaSeq +
|
|
459
|
+
'mediaSeq': (playheadState.mediaSeq + mediaSequenceValue),
|
|
431
460
|
'discSeq': discSeqCount,
|
|
432
461
|
'vodMediaSeqVideo': playheadState.vodMediaSeqVideo,
|
|
433
462
|
};
|
|
@@ -460,6 +489,9 @@ class Session {
|
|
|
460
489
|
if (this.prevVodMediaSeq.video === null) {
|
|
461
490
|
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
462
491
|
}
|
|
492
|
+
if (this.prevMediaSeqOffset.video === null) {
|
|
493
|
+
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
494
|
+
}
|
|
463
495
|
|
|
464
496
|
if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
|
|
465
497
|
debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
|
|
@@ -473,8 +505,13 @@ class Session {
|
|
|
473
505
|
}
|
|
474
506
|
|
|
475
507
|
// Force reading up from store, but only once if the condition is right
|
|
476
|
-
if (playheadState.vodMediaSeqVideo < 2 || playheadState.
|
|
508
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
477
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
|
+
}
|
|
478
515
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
479
516
|
if (!isLeader && this.isAllowedToClearVodCache) {
|
|
480
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.`);
|
|
@@ -488,10 +525,17 @@ class Session {
|
|
|
488
525
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
489
526
|
if (currentVod) {
|
|
490
527
|
try {
|
|
528
|
+
|
|
529
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
530
|
+
const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
|
|
531
|
+
debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}}`);
|
|
532
|
+
}
|
|
533
|
+
|
|
491
534
|
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
492
535
|
const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
493
536
|
debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
|
|
494
537
|
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
538
|
+
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
495
539
|
return m3u8;
|
|
496
540
|
} catch (err) {
|
|
497
541
|
logerror(this._sessionId, err);
|
|
@@ -566,6 +610,7 @@ class Session {
|
|
|
566
610
|
} else {
|
|
567
611
|
debug(`[${this._sessionId}]: We don't have any previously generated m3u8`);
|
|
568
612
|
}
|
|
613
|
+
this.isAllowedToClearVodCache = true;
|
|
569
614
|
}
|
|
570
615
|
} else {
|
|
571
616
|
sessionState.vodMediaSeqVideo = await this._sessionState.increment("vodMediaSeqVideo");
|
|
@@ -586,19 +631,25 @@ class Session {
|
|
|
586
631
|
playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
|
|
587
632
|
playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
|
|
588
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
|
+
|
|
589
640
|
debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio} of ${currentVod.getLiveMediaSequencesCount()})`);
|
|
590
|
-
let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
|
|
591
|
-
await this._playheadState.setLastM3u8(m3u8);
|
|
592
641
|
|
|
593
642
|
// As a FOLLOWER, we might need to read up from shared store...
|
|
594
|
-
if (playheadState.vodMediaSeqVideo < 2 || playheadState.
|
|
643
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
595
644
|
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
596
|
-
|
|
597
|
-
|
|
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;
|
|
598
648
|
}
|
|
599
649
|
if (!isLeader && this.isAllowedToClearVodCache) {
|
|
600
650
|
debug(`[${this._sessionId}]: Not a leader and have just set 'playheadState.vodMediaSeqVideo' to 0|1. Invalidate cache to ensure having the correct VOD.`);
|
|
601
651
|
await this._sessionState.clearCurrentVodCache();
|
|
652
|
+
currentVod = await this._sessionState.getCurrentVod();
|
|
602
653
|
this.isAllowedToClearVodCache = false;
|
|
603
654
|
}
|
|
604
655
|
} else {
|
|
@@ -606,7 +657,10 @@ class Session {
|
|
|
606
657
|
}
|
|
607
658
|
// Update Value for Previous Sequence
|
|
608
659
|
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
660
|
+
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
609
661
|
|
|
662
|
+
let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
|
|
663
|
+
await this._playheadState.setLastM3u8(m3u8);
|
|
610
664
|
return m3u8;
|
|
611
665
|
}
|
|
612
666
|
|
|
@@ -823,17 +877,21 @@ class Session {
|
|
|
823
877
|
const slateVod = await this._loadSlate(currentVod);
|
|
824
878
|
debug(`[${this._sessionId}]: slate loaded`);
|
|
825
879
|
const sessionState = await this._sessionState.getValues(["slateCount", "mediaSeq", "discSeq"]);
|
|
826
|
-
let
|
|
880
|
+
let endValue = 0;
|
|
827
881
|
let lastDiscontinuity = 0;
|
|
828
882
|
if (currentVod) {
|
|
829
|
-
|
|
883
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
884
|
+
endValue = currentVod.getLastSequenceMediaSequenceValue();
|
|
885
|
+
} else {
|
|
886
|
+
endValue = currentVod.getLiveMediaSequencesCount();
|
|
887
|
+
}
|
|
830
888
|
lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
831
889
|
}
|
|
832
890
|
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
833
891
|
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
834
892
|
await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
835
893
|
await this._sessionState.setCurrentVod(slateVod);
|
|
836
|
-
await this._sessionState.set("mediaSeq", sessionState.mediaSeq +
|
|
894
|
+
await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endValue);
|
|
837
895
|
await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
838
896
|
await this._sessionState.set("slateCount", sessionState.slateCount + 1);
|
|
839
897
|
await this._playheadState.set("playheadRef", Date.now());
|
|
@@ -886,7 +944,8 @@ class Session {
|
|
|
886
944
|
let loadPromise;
|
|
887
945
|
if (!vodResponse.type) {
|
|
888
946
|
debug(`[${this._sessionId}]: got first VOD uri=${vodResponse.uri}:${vodResponse.offset || 0}`);
|
|
889
|
-
|
|
947
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
948
|
+
newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
|
|
890
949
|
if (vodResponse.timedMetadata) {
|
|
891
950
|
Object.keys(vodResponse.timedMetadata).map(k => {
|
|
892
951
|
newVod.addMetadata(k, vodResponse.timedMetadata[k]);
|
|
@@ -956,6 +1015,9 @@ class Session {
|
|
|
956
1015
|
if (this.prevVodMediaSeq.video === null) {
|
|
957
1016
|
this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
|
|
958
1017
|
}
|
|
1018
|
+
if (this.prevMediaSeqOffset.video === null) {
|
|
1019
|
+
this.prevMediaSeqOffset.video = sessionState.mediaSeq;
|
|
1020
|
+
}
|
|
959
1021
|
// Clear Cache if prev count is HIGHER than current...
|
|
960
1022
|
if (sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
|
|
961
1023
|
debug(`[${this._sessionId}]: state=VOD_PLAYING, current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
@@ -965,8 +1027,9 @@ class Session {
|
|
|
965
1027
|
}
|
|
966
1028
|
} else {
|
|
967
1029
|
// Handle edge case where Leader loaded next vod but Follower remained in state=VOD_PLAYING
|
|
968
|
-
if (this.
|
|
1030
|
+
if (this.prevMediaSeqOffset.video !== null & sessionState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
969
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}]`);
|
|
970
1033
|
// Allow Follower to clear VodCache...
|
|
971
1034
|
this.isAllowedToClearVodCache = true;
|
|
972
1035
|
}
|
|
@@ -991,6 +1054,12 @@ class Session {
|
|
|
991
1054
|
return;
|
|
992
1055
|
}
|
|
993
1056
|
const length = currentVod.getLiveMediaSequencesCount();
|
|
1057
|
+
let endMseqValue;
|
|
1058
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
1059
|
+
endMseqValue = currentVod.getLastSequenceMediaSequenceValue();
|
|
1060
|
+
} else {
|
|
1061
|
+
endMseqValue = currentVod.getLiveMediaSequencesCount();
|
|
1062
|
+
}
|
|
994
1063
|
const lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
995
1064
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
996
1065
|
let vodPromise = this._getNextVod();
|
|
@@ -1008,7 +1077,8 @@ class Session {
|
|
|
1008
1077
|
let loadPromise;
|
|
1009
1078
|
if (!vodResponse.type) {
|
|
1010
1079
|
debug(`[${this._sessionId}]: got next VOD uri=${vodResponse.uri}:${vodResponse.offset}`);
|
|
1011
|
-
|
|
1080
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1081
|
+
newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
|
|
1012
1082
|
if (vodResponse.timedMetadata) {
|
|
1013
1083
|
Object.keys(vodResponse.timedMetadata).map(k => {
|
|
1014
1084
|
newVod.addMetadata(k, vodResponse.timedMetadata[k]);
|
|
@@ -1044,10 +1114,10 @@ class Session {
|
|
|
1044
1114
|
debug(`[${this._sessionId}]: ${newVod.getPlayheadPositions()}`);
|
|
1045
1115
|
currentVod = newVod;
|
|
1046
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);
|
|
1047
1119
|
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
1048
1120
|
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
1049
|
-
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
|
|
1050
|
-
sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
1051
1121
|
debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]`);
|
|
1052
1122
|
await this._sessionState.remove("nextVod");
|
|
1053
1123
|
sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
@@ -1065,6 +1135,8 @@ class Session {
|
|
|
1065
1135
|
debug(`[${this._sessionId}]: not a leader so will go directly to state VOD_NEXT_INITIATING`);
|
|
1066
1136
|
this.waitingForNextVod = true;
|
|
1067
1137
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
1138
|
+
// Allow Leader|Follower to clear vodCache...
|
|
1139
|
+
this.isAllowedToClearVodCache = true;
|
|
1068
1140
|
sessionState.currentVod = await this._sessionState.getCurrentVod();
|
|
1069
1141
|
}
|
|
1070
1142
|
} catch (err) {
|
|
@@ -1081,6 +1153,8 @@ class Session {
|
|
|
1081
1153
|
debug("No slate to load");
|
|
1082
1154
|
throw err;
|
|
1083
1155
|
}
|
|
1156
|
+
// Allow Leader|Follower to clear vodCache...
|
|
1157
|
+
this.isAllowedToClearVodCache = true;
|
|
1084
1158
|
}
|
|
1085
1159
|
break;
|
|
1086
1160
|
case SessionState.VOD_RELOAD_INIT:
|
|
@@ -1094,7 +1168,12 @@ class Session {
|
|
|
1094
1168
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
|
|
1095
1169
|
|
|
1096
1170
|
// 2) Set new 'offset' sequences, to carry on the continuity from session-live
|
|
1097
|
-
|
|
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
|
+
}
|
|
1098
1177
|
const dSeq = this.switchDataForSession.discSeq;
|
|
1099
1178
|
const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
|
|
1100
1179
|
const reloadBehind = this.switchDataForSession.reloadBehind;
|
|
@@ -1113,15 +1192,14 @@ class Session {
|
|
|
1113
1192
|
// 3) Set new media segments/currentVod, to carry on the continuity from session-live
|
|
1114
1193
|
debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
|
|
1115
1194
|
const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
|
|
1116
|
-
let
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
1195
|
+
let nextMseq = playheadState.vodMediaSeqVideo + 1;
|
|
1196
|
+
if (nextMseq > currentVod.getLiveMediaSequencesCount() - 1) {
|
|
1197
|
+
nextMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
1120
1198
|
}
|
|
1121
1199
|
|
|
1122
1200
|
// TODO: Support reloading with audioSegments as well
|
|
1123
1201
|
|
|
1124
|
-
await currentVod.reload(
|
|
1202
|
+
await currentVod.reload(nextMseq, segments, null, reloadBehind);
|
|
1125
1203
|
await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
1126
1204
|
await this._sessionState.set("vodReloaded", 1);
|
|
1127
1205
|
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
@@ -1153,6 +1231,8 @@ class Session {
|
|
|
1153
1231
|
}
|
|
1154
1232
|
this.waitingForNextVod = true;
|
|
1155
1233
|
}
|
|
1234
|
+
// Allow Leader|Follower to clear vodCache...
|
|
1235
|
+
this.isAllowedToClearVodCache = true;
|
|
1156
1236
|
return;
|
|
1157
1237
|
default:
|
|
1158
1238
|
throw new Error("Invalid state: " + sessionState.state);
|
|
@@ -1199,7 +1279,8 @@ class Session {
|
|
|
1199
1279
|
|
|
1200
1280
|
slateVod.load()
|
|
1201
1281
|
.then(() => {
|
|
1202
|
-
|
|
1282
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1283
|
+
hlsVod = new HLSVod(this.slateUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
|
|
1203
1284
|
const timestamp = Date.now();
|
|
1204
1285
|
hlsVod.addMetadata('id', `slate-${timestamp}`);
|
|
1205
1286
|
hlsVod.addMetadata('start-date', new Date(timestamp).toISOString());
|
|
@@ -1243,7 +1324,8 @@ class Session {
|
|
|
1243
1324
|
|
|
1244
1325
|
slateVod.load()
|
|
1245
1326
|
.then(() => {
|
|
1246
|
-
|
|
1327
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1328
|
+
hlsVod = new HLSVod(nexVodUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
|
|
1247
1329
|
const timestamp = Date.now();
|
|
1248
1330
|
hlsVod.addMetadata('id', `slate-${timestamp}`);
|
|
1249
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
|
|