eyevinn-channel-engine 3.0.4 → 3.0.7
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/engine/session_live.js +81 -45
- package/engine/stream_switcher.js +15 -0
- package/package.json +1 -1
- package/server-livemix.js +1 -1
package/engine/session_live.js
CHANGED
|
@@ -136,7 +136,7 @@ class SessionLive {
|
|
|
136
136
|
if (!this.masterManifestUri) {
|
|
137
137
|
await timer(3000);
|
|
138
138
|
continue;
|
|
139
|
-
}
|
|
139
|
+
}
|
|
140
140
|
if (this.playheadState === PlayheadState.STOPPED) {
|
|
141
141
|
debug(`[${this.sessionId}]: Playhead has Stopped, clearing local session and store.`);
|
|
142
142
|
this.waitForPlayhead = false;
|
|
@@ -208,7 +208,7 @@ class SessionLive {
|
|
|
208
208
|
while (!this.masterManifestUri && attempts > 0) {
|
|
209
209
|
attempts--;
|
|
210
210
|
try {
|
|
211
|
-
debug(`[${this.instanceId}][${this.sessionId}]: Going to fetch Live Master Manifest!`)
|
|
211
|
+
debug(`[${this.instanceId}][${this.sessionId}]: Going to fetch Live Master Manifest!`);
|
|
212
212
|
// Load & Parse all Media Manifest URIs from Master
|
|
213
213
|
await this._loadMasterManifest(masterManifestUri);
|
|
214
214
|
this.masterManifestUri = masterManifestUri;
|
|
@@ -390,7 +390,7 @@ class SessionLive {
|
|
|
390
390
|
// Generate manifest to give to client
|
|
391
391
|
async getCurrentMediaManifestAsync(bw) {
|
|
392
392
|
if (!this.sessionLiveState) {
|
|
393
|
-
throw new Error(
|
|
393
|
+
throw new Error("SessionLive not ready");
|
|
394
394
|
}
|
|
395
395
|
if (bw === null) {
|
|
396
396
|
debug(`[${this.sessionId}]: No bandwidth provided`);
|
|
@@ -488,7 +488,11 @@ class SessionLive {
|
|
|
488
488
|
const size = this.liveSegsForFollowers[liveBws[0]].length;
|
|
489
489
|
|
|
490
490
|
let newSegCount = 0;
|
|
491
|
-
this.liveSegsForFollowers[liveBws[0]].map((seg) => {
|
|
491
|
+
this.liveSegsForFollowers[liveBws[0]].map((seg) => {
|
|
492
|
+
if (seg.uri) {
|
|
493
|
+
newSegCount++;
|
|
494
|
+
}
|
|
495
|
+
});
|
|
492
496
|
|
|
493
497
|
debug(`[${this.sessionId}]: FOLLOWER: liveSegsForFollowers Bws=${Object.keys(this.liveSegsForFollowers)}_newSegCount=${newSegCount} `);
|
|
494
498
|
// Remove transitional segs & add live source segs collected from store
|
|
@@ -509,7 +513,7 @@ class SessionLive {
|
|
|
509
513
|
// Push to bottom, new live source segment on all variants
|
|
510
514
|
for (let segIdx = 0; segIdx < size; segIdx++) {
|
|
511
515
|
let incrementDiscSeqCount = false;
|
|
512
|
-
liveBws.forEach((bw)=>{
|
|
516
|
+
liveBws.forEach((bw) => {
|
|
513
517
|
let liveSegFromLeader = this.liveSegsForFollowers[bw][segIdx];
|
|
514
518
|
if (!this.liveSegQueue[bw]) {
|
|
515
519
|
this.liveSegQueue[bw] = [];
|
|
@@ -517,7 +521,11 @@ class SessionLive {
|
|
|
517
521
|
this.liveSegQueue[bw].push(liveSegFromLeader);
|
|
518
522
|
|
|
519
523
|
let segCount = 0;
|
|
520
|
-
this.liveSegQueue[bw].map((seg) => {
|
|
524
|
+
this.liveSegQueue[bw].map((seg) => {
|
|
525
|
+
if (seg.uri) {
|
|
526
|
+
segCount++;
|
|
527
|
+
}
|
|
528
|
+
});
|
|
521
529
|
if (segCount > this.targetNumSeg) {
|
|
522
530
|
let seg = this.liveSegQueue[bw].shift();
|
|
523
531
|
if (seg && seg.discontinuity) {
|
|
@@ -651,7 +659,7 @@ class SessionLive {
|
|
|
651
659
|
}
|
|
652
660
|
|
|
653
661
|
// Handle if any promise got rejected
|
|
654
|
-
if (manifestList.some(result => result.status === "rejected")) {
|
|
662
|
+
if (manifestList.some((result) => result.status === "rejected")) {
|
|
655
663
|
debug(`[${this.sessionId}]: ALERT! Promises I: Failed, Rejection Found! Trying again...`);
|
|
656
664
|
continue;
|
|
657
665
|
}
|
|
@@ -663,16 +671,21 @@ class SessionLive {
|
|
|
663
671
|
return item.value.mediaSeq;
|
|
664
672
|
});
|
|
665
673
|
|
|
666
|
-
|
|
667
674
|
// Handle if mediaSeqCounts are NOT synced up!
|
|
668
675
|
if (!allMediaSeqCounts.every((val, i, arr) => val === arr[0])) {
|
|
669
|
-
|
|
670
676
|
debug(`[${this.sessionId}]: Live Mseq counts=[${allMediaSeqCounts}]`);
|
|
671
677
|
// Decrement fetch counter
|
|
672
678
|
FETCH_ATTEMPTS--;
|
|
679
|
+
// Calculate retry delay time. Default=1500
|
|
680
|
+
let retryDelayMs = 1500;
|
|
681
|
+
if (Object.keys(this.liveSegQueue).length > 0) {
|
|
682
|
+
const firstBw = Object.keys(this.liveSegQueue)[0];
|
|
683
|
+
const lastIdx = this.liveSegQueue[firstBw].length - 1;
|
|
684
|
+
retryDelayMs = this.liveSegQueue[firstBw][lastIdx].duration * 1000 * 0.25;
|
|
685
|
+
}
|
|
673
686
|
// Wait a little before trying again
|
|
674
|
-
debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after
|
|
675
|
-
await timer(
|
|
687
|
+
debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after ${retryDelayMs}ms`);
|
|
688
|
+
await timer(retryDelayMs);
|
|
676
689
|
this.timerCompensation = false;
|
|
677
690
|
continue;
|
|
678
691
|
}
|
|
@@ -681,7 +694,7 @@ class SessionLive {
|
|
|
681
694
|
let leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
|
|
682
695
|
let tries = 20;
|
|
683
696
|
|
|
684
|
-
while (!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0 ||
|
|
697
|
+
while ((!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
|
|
685
698
|
debug(`[${this.sessionId}]: NEW FOLLOWER: Waiting for LEADER to add 'firstCounts' in store! Will look again after 1000ms (tries left=${tries})`);
|
|
686
699
|
await timer(1000);
|
|
687
700
|
leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
|
|
@@ -727,7 +740,7 @@ class SessionLive {
|
|
|
727
740
|
this.pushAmount = 1; // Follower from start
|
|
728
741
|
} else {
|
|
729
742
|
// RESPAWNED NODES
|
|
730
|
-
this.pushAmount =
|
|
743
|
+
this.pushAmount = allMediaSeqCounts[0] - leadersFirstSeqCounts.liveSourceMseqCount + 1;
|
|
731
744
|
|
|
732
745
|
const transitSegs = await this.sessionLiveState.get("transitSegs");
|
|
733
746
|
//debug(`[${this.sessionId}]: NEW FOLLOWER: I tried to get 'transitSegs'. This is what I found ${JSON.stringify(transitSegs)}`);
|
|
@@ -760,7 +773,7 @@ class SessionLive {
|
|
|
760
773
|
}
|
|
761
774
|
|
|
762
775
|
if (FETCH_ATTEMPTS === 0) {
|
|
763
|
-
debug(`[${this.sessionId}]: Fetching from Live-Source did not work! Returning to Playhead Loop...`)
|
|
776
|
+
debug(`[${this.sessionId}]: Fetching from Live-Source did not work! Returning to Playhead Loop...`);
|
|
764
777
|
return;
|
|
765
778
|
}
|
|
766
779
|
|
|
@@ -781,8 +794,7 @@ class SessionLive {
|
|
|
781
794
|
this.firstTime = false;
|
|
782
795
|
debug(`[${this.sessionId}]: Got all needed segments from live-source (read from store).\nWe are now able to build Live Manifest: [${this.mediaSeqCount}]`);
|
|
783
796
|
return;
|
|
784
|
-
}
|
|
785
|
-
else if (leadersCurrentMseqRaw < this.lastRequestedMediaSeqRaw) {
|
|
797
|
+
} else if (leadersCurrentMseqRaw < this.lastRequestedMediaSeqRaw) {
|
|
786
798
|
// WE ARE A RESPAWN-NODE, and we are ahead of leader.
|
|
787
799
|
this.blockGenerateManifest = true;
|
|
788
800
|
}
|
|
@@ -798,7 +810,7 @@ class SessionLive {
|
|
|
798
810
|
}
|
|
799
811
|
// Segment Pushing
|
|
800
812
|
debug(`[${this.sessionId}]: Executing Promises II: Segment Pushing`);
|
|
801
|
-
let results = await allSettled(pushPromises)
|
|
813
|
+
let results = await allSettled(pushPromises); //await Promise.all(pushPromises);
|
|
802
814
|
const removedDiscSeqList = results.map((item) => {
|
|
803
815
|
if (item.status === "rejected") {
|
|
804
816
|
return -1;
|
|
@@ -951,7 +963,9 @@ class SessionLive {
|
|
|
951
963
|
// this.targetNumSeg = m3u.items.PlaylistItem.length;
|
|
952
964
|
// }
|
|
953
965
|
|
|
954
|
-
this.
|
|
966
|
+
if (this.pushAmount >= 0) {
|
|
967
|
+
this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
|
|
968
|
+
}
|
|
955
969
|
this.targetDuration = m3u.get("targetDuration");
|
|
956
970
|
let amountRemovedDiscSeqs = 0;
|
|
957
971
|
let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
|
|
@@ -962,8 +976,8 @@ class SessionLive {
|
|
|
962
976
|
}
|
|
963
977
|
let resolveObj = {
|
|
964
978
|
removedDiscSeqs: amountRemovedDiscSeqs,
|
|
965
|
-
bw: liveTargetBandwidth
|
|
966
|
-
}
|
|
979
|
+
bw: liveTargetBandwidth,
|
|
980
|
+
};
|
|
967
981
|
resolve(resolveObj);
|
|
968
982
|
} catch (exc) {
|
|
969
983
|
console.error("ERROR: " + exc);
|
|
@@ -998,7 +1012,7 @@ class SessionLive {
|
|
|
998
1012
|
}
|
|
999
1013
|
cueData["out"] = true;
|
|
1000
1014
|
cueData["duration"] = attributes["cueout"];
|
|
1001
|
-
|
|
1015
|
+
}
|
|
1002
1016
|
if ("cuecont" in attributes) {
|
|
1003
1017
|
if (!cueData) {
|
|
1004
1018
|
cueData = {};
|
|
@@ -1018,16 +1032,17 @@ class SessionLive {
|
|
|
1018
1032
|
cueData["assetData"] = attributes["assetData"];
|
|
1019
1033
|
}
|
|
1020
1034
|
if ("daterange" in attributes) {
|
|
1021
|
-
if(!daterangeData) {
|
|
1022
|
-
daterangeData = {}
|
|
1035
|
+
if (!daterangeData) {
|
|
1036
|
+
daterangeData = {};
|
|
1023
1037
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1038
|
+
let allDaterangeAttributes = Object.keys(attributes["daterange"]);
|
|
1039
|
+
allDaterangeAttributes.forEach((attr) => {
|
|
1040
|
+
if (attr.match(/DURATION$/)) {
|
|
1041
|
+
daterangeData[attr.toLowerCase()] = parseFloat(attributes["daterange"][attr]);
|
|
1042
|
+
} else {
|
|
1043
|
+
daterangeData[attr.toLowerCase()] = attributes["daterange"][attr];
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1031
1046
|
}
|
|
1032
1047
|
if (playlistItem.properties.uri) {
|
|
1033
1048
|
if (playlistItem.properties.uri.match("^http")) {
|
|
@@ -1045,10 +1060,14 @@ class SessionLive {
|
|
|
1045
1060
|
this.liveSegQueue[liveTargetBandwidth].push(seg);
|
|
1046
1061
|
this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
|
|
1047
1062
|
let segCount = 0;
|
|
1048
|
-
this.liveSegQueue[liveTargetBandwidth].map((seg) => {
|
|
1063
|
+
this.liveSegQueue[liveTargetBandwidth].map((seg) => {
|
|
1064
|
+
if (seg.uri) {
|
|
1065
|
+
segCount++;
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1049
1068
|
debug(`[${this.sessionId}]: size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
|
|
1050
1069
|
if (segCount > this.targetNumSeg) {
|
|
1051
|
-
debug(`[${this.sessionId}]: liveTargetBandwidth=${liveTargetBandwidth}_seg=${JSON.stringify(seg,null,2)}`);
|
|
1070
|
+
debug(`[${this.sessionId}]: liveTargetBandwidth=${liveTargetBandwidth}_seg=${JSON.stringify(seg, null, 2)}`);
|
|
1052
1071
|
seg = this.liveSegQueue[liveTargetBandwidth].shift();
|
|
1053
1072
|
if (seg) {
|
|
1054
1073
|
if (seg.discontinuity) {
|
|
@@ -1077,9 +1096,8 @@ class SessionLive {
|
|
|
1077
1096
|
throw new Error("No bandwidth provided");
|
|
1078
1097
|
}
|
|
1079
1098
|
const liveTargetBandwidth = this._findNearestBw(bw, Object.keys(this.mediaManifestURIs));
|
|
1080
|
-
const vodTargetBandwidth = this.
|
|
1081
|
-
debug(`[${this.sessionId}]: Client requesting manifest for bw=(${bw}). Nearest LiveBw=(${liveTargetBandwidth})`)
|
|
1082
|
-
|
|
1099
|
+
const vodTargetBandwidth = this._getNearestBandwidth(bw, Object.keys(this.vodSegments));
|
|
1100
|
+
debug(`[${this.sessionId}]: Client requesting manifest for bw=(${bw}). Nearest LiveBw=(${liveTargetBandwidth})`);
|
|
1083
1101
|
|
|
1084
1102
|
if (this.blockGenerateManifest) {
|
|
1085
1103
|
debug(`[${this.sessionId}]: FOLLOWER: Cannot Generate Manifest! Waiting to sync-up with Leader...`);
|
|
@@ -1153,7 +1171,7 @@ class SessionLive {
|
|
|
1153
1171
|
m3u8 += "#EXT-X-DISCONTINUITY\n";
|
|
1154
1172
|
}
|
|
1155
1173
|
if (seg.cue) {
|
|
1156
|
-
if(seg.cue.out) {
|
|
1174
|
+
if (seg.cue.out) {
|
|
1157
1175
|
if (seg.cue.scteData) {
|
|
1158
1176
|
m3u8 += "#EXT-OATCLS-SCTE35:" + seg.cue.scteData + "\n";
|
|
1159
1177
|
}
|
|
@@ -1165,21 +1183,25 @@ class SessionLive {
|
|
|
1165
1183
|
if (seg.cue.cont) {
|
|
1166
1184
|
if (seg.cue.scteData) {
|
|
1167
1185
|
m3u8 += "#EXT-X-CUE-OUT-CONT:ElapsedTime=" + seg.cue.cont + ",Duration=" + seg.cue.duration + ",SCTE35=" + seg.cue.scteData + "\n";
|
|
1168
|
-
}
|
|
1169
|
-
else {
|
|
1186
|
+
} else {
|
|
1170
1187
|
m3u8 += "#EXT-X-CUE-OUT-CONT:" + seg.cue.cont + "/" + seg.cue.duration + "\n";
|
|
1171
1188
|
}
|
|
1172
1189
|
}
|
|
1173
1190
|
}
|
|
1191
|
+
if (seg.datetime) {
|
|
1192
|
+
m3u8 += `#EXT-X-PROGRAM-DATE-TIME:${seg.datetime}\n`;
|
|
1193
|
+
}
|
|
1174
1194
|
if (seg.daterange) {
|
|
1175
|
-
const dateRangeAttributes = Object.keys(seg.daterange)
|
|
1176
|
-
|
|
1177
|
-
|
|
1195
|
+
const dateRangeAttributes = Object.keys(seg.daterange)
|
|
1196
|
+
.map((key) => daterangeAttribute(key, seg.daterange[key]))
|
|
1197
|
+
.join(",");
|
|
1198
|
+
if (!seg.datetime && seg.daterange["start-date"]) {
|
|
1199
|
+
m3u8 += "#EXT-X-PROGRAM-DATE-TIME:" + seg.daterange["start-date"] + "\n";
|
|
1178
1200
|
}
|
|
1179
1201
|
m3u8 += "#EXT-X-DATERANGE:" + dateRangeAttributes + "\n";
|
|
1180
1202
|
}
|
|
1181
1203
|
// Mimick logic used in hls-vodtolive
|
|
1182
|
-
if (seg.cue && seg.cue.in){
|
|
1204
|
+
if (seg.cue && seg.cue.in) {
|
|
1183
1205
|
m3u8 += "#EXT-X-CUE-IN" + "\n";
|
|
1184
1206
|
}
|
|
1185
1207
|
if (seg.uri) {
|
|
@@ -1197,6 +1219,20 @@ class SessionLive {
|
|
|
1197
1219
|
});
|
|
1198
1220
|
}
|
|
1199
1221
|
|
|
1222
|
+
_getNearestBandwidth(bandwidthToMatch, array) {
|
|
1223
|
+
const sortedBandwidths = array.sort((a, b) => a - b);
|
|
1224
|
+
const exactMatch = sortedBandwidths.find((a) => a == bandwidthToMatch);
|
|
1225
|
+
if (exactMatch) {
|
|
1226
|
+
return exactMatch;
|
|
1227
|
+
}
|
|
1228
|
+
for (let i = 0; i < sortedBandwidths.length; i++) {
|
|
1229
|
+
if (Number(bandwidthToMatch) <= Number(sortedBandwidths[i])) {
|
|
1230
|
+
return sortedBandwidths[i];
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return sortedBandwidths[sortedBandwidths.length - 1];
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1200
1236
|
_getFirstBwWithSegmentsInList(allSegments) {
|
|
1201
1237
|
const bandwidths = Object.keys(allSegments);
|
|
1202
1238
|
for (let i = 0; i < bandwidths.length; i++) {
|
|
@@ -1230,13 +1266,13 @@ class SessionLive {
|
|
|
1230
1266
|
const profiles = this.sessionLiveProfile;
|
|
1231
1267
|
const toKeep = new Set();
|
|
1232
1268
|
let newItem = {};
|
|
1233
|
-
profiles.forEach(profile => {
|
|
1234
|
-
let bwToKeep = this.
|
|
1269
|
+
profiles.forEach((profile) => {
|
|
1270
|
+
let bwToKeep = this._getNearestBandwidth(profile.bw, Object.keys(this.mediaManifestURIs));
|
|
1235
1271
|
toKeep.add(bwToKeep);
|
|
1236
1272
|
});
|
|
1237
1273
|
toKeep.forEach((bw) => {
|
|
1238
1274
|
newItem[bw] = this.mediaManifestURIs[bw];
|
|
1239
|
-
})
|
|
1275
|
+
});
|
|
1240
1276
|
this.mediaManifestURIs = newItem;
|
|
1241
1277
|
}
|
|
1242
1278
|
|
|
@@ -278,6 +278,9 @@ class StreamSwitcher {
|
|
|
278
278
|
debug(`[${this.sessionId}]: [ Switched from V2L->LIVE ]`);
|
|
279
279
|
return true;
|
|
280
280
|
} catch (err) {
|
|
281
|
+
this.streamTypeLive = false;
|
|
282
|
+
this.working = false;
|
|
283
|
+
this.eventId = null;
|
|
281
284
|
debug(`[${this.sessionId}]: [ ERROR Switching from V2L->LIVE ]`);
|
|
282
285
|
throw new Error(err);
|
|
283
286
|
}
|
|
@@ -309,6 +312,9 @@ class StreamSwitcher {
|
|
|
309
312
|
debug(`[${this.sessionId}]: [ Switched from V2L->VOD ]`);
|
|
310
313
|
return false;
|
|
311
314
|
} catch (err) {
|
|
315
|
+
this.streamTypeLive = false;
|
|
316
|
+
this.working = false;
|
|
317
|
+
this.eventId = null;
|
|
312
318
|
debug(`[${this.sessionId}]: [ ERROR Switching from V2L->VOD ]`);
|
|
313
319
|
throw new Error(err);
|
|
314
320
|
}
|
|
@@ -349,6 +355,9 @@ class StreamSwitcher {
|
|
|
349
355
|
debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
|
|
350
356
|
return false;
|
|
351
357
|
} catch (err) {
|
|
358
|
+
this.streamTypeLive = false;
|
|
359
|
+
this.working = false;
|
|
360
|
+
this.eventId = null;
|
|
352
361
|
debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->V2L ]`);
|
|
353
362
|
throw new Error(err);
|
|
354
363
|
}
|
|
@@ -389,6 +398,9 @@ class StreamSwitcher {
|
|
|
389
398
|
debug(`[${this.sessionId}]: Switched from LIVE->VOD`);
|
|
390
399
|
return false;
|
|
391
400
|
} catch (err) {
|
|
401
|
+
this.streamTypeLive = false;
|
|
402
|
+
this.working = false;
|
|
403
|
+
this.eventId = null;
|
|
392
404
|
debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->VOD ]`);
|
|
393
405
|
throw new Error(err);
|
|
394
406
|
}
|
|
@@ -425,6 +437,9 @@ class StreamSwitcher {
|
|
|
425
437
|
debug(`[${this.sessionId}]: Switched from LIVE->LIVE`);
|
|
426
438
|
return true;
|
|
427
439
|
} catch (err) {
|
|
440
|
+
this.streamTypeLive = false;
|
|
441
|
+
this.working = false;
|
|
442
|
+
this.eventId = null;
|
|
428
443
|
debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->LIVE ]`);
|
|
429
444
|
throw new Error(err);
|
|
430
445
|
}
|
package/package.json
CHANGED
package/server-livemix.js
CHANGED
|
@@ -155,7 +155,7 @@ class StreamSwitchManager {
|
|
|
155
155
|
type: StreamType.LIVE,
|
|
156
156
|
start_time: startOffset,
|
|
157
157
|
end_time: endTime,
|
|
158
|
-
uri: "https://
|
|
158
|
+
uri: "https://d2fz24s2fts31b.cloudfront.net/out/v1/2e5b17b7dd0548c7b844eb1b311b6434/manifest.m3u8",
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
eventId: this.generateID(),
|