eyevinn-channel-engine 3.4.4 → 4.0.0-rc.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.
@@ -10,13 +10,13 @@ var RefAssetManager = /** @class */ (function () {
10
10
  1: [
11
11
  {
12
12
  id: 1,
13
- title: "Elephants Dream",
14
- uri: "https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8",
13
+ title: "Nymo bloopers 2022",
14
+ uri: "https://vod.streaming.a2d.tv/3f389c48-03e3-48a2-8e98-a02c55185a68/4c792a30-89ad-11ed-95d9-1b374c4e2f9f_20411056.ism/.m3u8",
15
15
  },
16
16
  {
17
17
  id: 2,
18
- title: "Test HLS Bird noises (1m10s)",
19
- uri: "https://mtoczko.github.io/hls-test-streams/test-audio-pdt/playlist.m3u8",
18
+ title: "Skrattfest",
19
+ uri: "https://vod.streaming.a2d.tv/09399ba0-70de-4ce7-9d84-bfcd6d9f8d43/fba308d0-39f5-11ed-b93e-a747b4a166ca_20062029.ism/.m3u8",
20
20
  },
21
21
  ],
22
22
  };
@@ -81,9 +81,7 @@ var RefChannelManager = /** @class */ (function () {
81
81
  };
82
82
  RefChannelManager.prototype._getAudioTracks = function () {
83
83
  return [
84
- { language: "sp", name: "Spanish" },
85
- { language: "ru", name: "Russian" },
86
- { language: "en", name: "English", default: true },
84
+ { language: "sv", name: "Swedish", default: true },
87
85
  ];
88
86
  };
89
87
  return RefChannelManager;
package/engine/server.ts CHANGED
@@ -78,6 +78,7 @@ export interface VodTimedMetadata {
78
78
  }
79
79
 
80
80
  export interface VodResponse {
81
+ title: any;
81
82
  id: string;
82
83
  uri: string;
83
84
  offset?: number;
package/engine/session.js CHANGED
@@ -153,10 +153,13 @@ class Session {
153
153
  let playheadState = await this._playheadState.getValues(["state"]);
154
154
  let state = await this._playheadState.setState(PlayheadState.RUNNING);
155
155
  let numberOfLargeTicks = 0;
156
+ let audioIncrement = 1;
156
157
  while (state !== PlayheadState.CRASHED) {
157
158
  try {
158
159
  const tsIncrementBegin = Date.now();
159
- const manifest = await this.incrementAsync();
160
+ const manifest = await this.incrementAsync({
161
+ audio: audioIncrement
162
+ });
160
163
  if (!manifest) {
161
164
  debug(`[${this._sessionId}]: No manifest available yet, will try again after 1000ms`);
162
165
  await timer(1000);
@@ -196,6 +199,38 @@ class Session {
196
199
  debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval. tick=${tickInterval}`);
197
200
  }
198
201
  const position = (await this._getCurrentPlayheadPosition()) * 1000;
202
+
203
+ if (this.use_demuxed_audio) {
204
+ let currentVod = await this._sessionState.getCurrentVod();
205
+ const sessionState = await this._sessionState.getValues(["vodMediaSeqAudio"]);
206
+ let posDiff;
207
+ let incrementValue = 1;
208
+ let index = 0;
209
+ const audioSeqLastIdx = currentVod.getLiveMediaSequencesCount("audio") - 1;
210
+ const thresh = 0.5
211
+ let direction = 0;
212
+ do {
213
+ const audioPosition = (await this._getAudioPlayheadPosition(sessionState.vodMediaSeqAudio + index)) * 1000; // PUT REAL WHEN DONE
214
+ posDiff = position - audioPosition;
215
+ if (posDiff > thresh && direction >= 0) {
216
+ index++;
217
+ incrementValue++;
218
+ direction = 1;
219
+ } else if (posDiff < -thresh && direction <= 0) {
220
+ incrementValue = 0;
221
+ index--;
222
+ direction = -1;
223
+ break;
224
+ } else if (direction > 0 && posDiff < 0) {
225
+ break;
226
+ }
227
+ if (sessionState.vodMediaSeqAudio + index > audioSeqLastIdx || sessionState.vodMediaSeqAudio + index < 0) {
228
+ break;
229
+ }
230
+ } while (!(-thresh < posDiff && posDiff < thresh));
231
+ audioIncrement = incrementValue;
232
+ }
233
+
199
234
  let timePosition = Date.now() - playheadState.playheadRef;
200
235
  // Apply time position offset if set, only after external diff compensation has concluded.
201
236
  if (this.timePositionOffset && this.diffCompensation <= 0 && this.alwaysNewSegments) {
@@ -477,6 +512,8 @@ class Session {
477
512
 
478
513
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
479
514
  const discSeqOffset = await this._sessionState.get("discSeq");
515
+ // TODO: support Audio too ^
516
+
480
517
  // Clear Vod Cache here when Switching to Live just to be safe...
481
518
  if (playheadState.vodMediaSeqVideo === 0) {
482
519
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
@@ -618,17 +655,17 @@ class Session {
618
655
  throw new Error('Session not ready');
619
656
  }
620
657
 
621
- const sessionState = await this._sessionState.getValues(["discSeq"]);
622
- const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqAudio"]);
658
+ const sessionState = await this._sessionState.getValues(["discSeqAudio"]);
659
+ const playheadState = await this._playheadState.getValues(["mediaSeqAudio", "vodMediaSeqAudio"]);
623
660
  const currentVod = await this._sessionState.getCurrentVod();
624
661
  if (currentVod) {
625
662
  try {
626
- const m3u8 = currentVod.getLiveMediaAudioSequences(playheadState.mediaSeq, audioGroupId, audioLanguage, playheadState.vodMediaSeqAudio, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
663
+ const m3u8 = currentVod.getLiveMediaAudioSequences(playheadState.mediaSeqAudio, audioGroupId, audioLanguage, playheadState.vodMediaSeqAudio, sessionState.discSeqAudio, this.targetDurationPadding, this.forceTargetDuration);
627
664
  // # Case: current VOD does not have the selected track.
628
665
  if (!m3u8) {
629
- debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqAudio}] Request Failed for current audio manifest for ${audioGroupId}-${audioLanguage}`);
666
+ debug(`[${this._sessionId}]: [${playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio}] Request Failed for current audio manifest for ${audioGroupId}-${audioLanguage}`);
630
667
  }
631
- debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqAudio}] Current audio manifest for ${audioGroupId}-${audioLanguage} requested`);
668
+ debug(`[${this._sessionId}]: [${playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio}] Current audio manifest for ${audioGroupId}-${audioLanguage} requested`);
632
669
  return m3u8;
633
670
  } catch (err) {
634
671
  logerror(this._sessionId, err);
@@ -640,13 +677,13 @@ class Session {
640
677
  }
641
678
  }
642
679
 
643
- async incrementAsync() {
680
+ async incrementAsync(incOpts) {
644
681
  await this._tickAsync();
645
682
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
646
683
 
647
684
  let sessionState = await this._sessionState.getValues(
648
- ["state", "mediaSeq", "discSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
649
- let playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
685
+ ["state", "mediaSeq", "mediaSeqAudio", "discSeq", "discSeqAudio", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
686
+ let playheadState = await this._playheadState.getValues(["mediaSeq", "mediaSeqAudio", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
650
687
  let currentVod = await this._sessionState.getCurrentVod();
651
688
  if (!currentVod ||
652
689
  sessionState.vodMediaSeqVideo === null ||
@@ -678,14 +715,16 @@ class Session {
678
715
  }
679
716
  this.isAllowedToClearVodCache = true;
680
717
  }
681
- } else {
718
+ } else { // TODO ADD BETTER INCREMENT
682
719
  sessionState.vodMediaSeqVideo = await this._sessionState.increment("vodMediaSeqVideo");
683
- sessionState.vodMediaSeqAudio = await this._sessionState.increment("vodMediaSeqAudio");
720
+ if (incOpts) {
721
+ sessionState.vodMediaSeqAudio = await this._sessionState.increment("vodMediaSeqAudio", incOpts.audio);
722
+ }
684
723
  }
685
724
 
686
725
  if (sessionState.vodMediaSeqVideo >= currentVod.getLiveMediaSequencesCount() - 1) {
687
726
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", currentVod.getLiveMediaSequencesCount() - 1);
688
- sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount() - 1);
727
+ sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount("audio") - 1);
689
728
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INIT);
690
729
  }
691
730
 
@@ -695,6 +734,7 @@ class Session {
695
734
  }
696
735
 
697
736
  playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
737
+ playheadState.mediaSeqAudio = await this._playheadState.set("mediaSeqAudio", sessionState.mediaSeqAudio);
698
738
  playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
699
739
  playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
700
740
 
@@ -703,7 +743,7 @@ class Session {
703
743
  debug(`[${this._sessionId}]: Session can now serve mseq={${playheadState.mediaSeq + mediaSequenceValue}}`);
704
744
  }
705
745
 
706
- debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio} of ${currentVod.getLiveMediaSequencesCount()})`);
746
+ debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio} of ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
707
747
 
708
748
  // As a FOLLOWER, we might need to read up from shared store...
709
749
  if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
@@ -819,15 +859,15 @@ class Session {
819
859
  let timeSinceLastRequest = (tsLastRequestAudio === null) ? 0 : Date.now() - tsLastRequestAudio;
820
860
 
821
861
  let sessionState = await this._sessionState.getValues(
822
- ["state", "vodMediaSeqVideo", "vodMediaSeqAudio", "mediaSeq", "discSeq", "lastM3u8"]);
862
+ ["state", "vodMediaSeqVideo", "vodMediaSeqAudio", "mediaSeqAudio", "discSeqAudio", "lastM3u8"]);
823
863
  const currentVod = await this._sessionState.getCurrentVod();
824
864
  if (sessionState.state !== SessionState.VOD_NEXT_INITIATING) {
825
865
  let sequencesToIncrement = Math.ceil(timeSinceLastRequest / this.averageSegmentDuration);
826
866
 
827
867
  if (sessionState.vodMediaSeqAudio < sessionState.vodMediaSeqVideo) {
828
868
  sessionState.vodMediaSeqAudio = await this._sessionState.increment("vodMediaSeqAudio", sequencesToIncrement);
829
- if (sessionState.vodMediaSeqAudio >= currentVod.getLiveMediaSequencesCount() - 1) {
830
- sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount() - 1);
869
+ if (sessionState.vodMediaSeqAudio >= currentVod.getLiveMediaSequencesCount("audio") - 1) {
870
+ sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount("audio") - 1);
831
871
  }
832
872
  }
833
873
  }
@@ -835,7 +875,7 @@ class Session {
835
875
  debug(`[${this._sessionId}]: AUDIO ${timeSinceLastRequest} (${this.averageSegmentDuration}) audioGroupId=${audioGroupId} audioLanguage=${audioLanguage} vodMediaSeq=(${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio})`);
836
876
  let m3u8;
837
877
  try {
838
- m3u8 = currentVod.getLiveMediaAudioSequences(sessionState.mediaSeq, audioGroupId, audioLanguage, sessionState.vodMediaSeqAudio, sessionState.discSeq);
878
+ m3u8 = currentVod.getLiveMediaAudioSequences(sessionState.mediaSeqAudio, audioGroupId, audioLanguage, sessionState.vodMediaSeqAudio, sessionState.discSeqAudio);
839
879
  } catch (exc) {
840
880
  if (sessionState.lastM3u8[audioGroupId][audioLanguage]) {
841
881
  m3u8 = sessionState.lastM3u8[audioGroupId][audioLanguage];
@@ -847,7 +887,7 @@ class Session {
847
887
  let lastM3u8 = sessionState.lastM3u8;
848
888
  lastM3u8[audioGroupId] = {};
849
889
  lastM3u8[audioGroupId][audioLanguage] = m3u8;
850
- sessionState.lastM3u8 = await this._sessionState.set("lastM3u8", lastM3u8);
890
+ sessionState.lastM3u8 = await this._sessionState.set("lastM3u8", lastM3u8); // for audio?
851
891
  sessionState.tsLastRequestAudio = await this._sessionState.set("tsLastRequestAudio", Date.now());
852
892
  return m3u8;
853
893
  }
@@ -954,23 +994,30 @@ class Session {
954
994
  console.error(`[${this._sessionId}]: Will insert slate`);
955
995
  const slateVod = await this._loadSlate(currentVod);
956
996
  debug(`[${this._sessionId}]: slate loaded`);
957
- const sessionState = await this._sessionState.getValues(["slateCount", "mediaSeq", "discSeq"]);
997
+ const sessionState = await this._sessionState.getValues(["slateCount", "mediaSeq", "discSeq", "mediaSeqAudio", "discSeqAudio"]);
958
998
  let endValue = 0;
999
+ let endValueAudio = 0;
959
1000
  let lastDiscontinuity = 0;
1001
+ let lastDiscontinuityAudio = 0;
960
1002
  if (currentVod) {
961
1003
  if (currentVod.sequenceAlwaysContainNewSegments) {
962
1004
  endValue = currentVod.getLastSequenceMediaSequenceValue();
1005
+ endValueAudio = currentVod.getLastSequenceMediaSequenceValueAudio();
963
1006
  } else {
964
1007
  endValue = currentVod.getLiveMediaSequencesCount();
1008
+ endValueAudio = currentVod.getLiveMediaSequencesCount("audio");
965
1009
  }
966
1010
  lastDiscontinuity = currentVod.getLastDiscontinuity();
1011
+ lastDiscontinuityAudio = currentVod.getLastDiscontinuityAudio();
967
1012
  }
968
1013
  await this._sessionState.set("vodMediaSeqVideo", 0);
969
1014
  await this._sessionState.set("vodMediaSeqAudio", 0);
970
1015
  await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
971
1016
  await this._sessionState.setCurrentVod(slateVod);
972
1017
  await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endValue);
1018
+ await this._sessionState.set("mediaSeqAudio", sessionState.mediaSeqAudio + endValueAudio);
973
1019
  await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
1020
+ await this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio);
974
1021
  await this._sessionState.set("slateCount", sessionState.slateCount + 1);
975
1022
  await this._playheadState.set("playheadRef", Date.now());
976
1023
 
@@ -986,7 +1033,7 @@ class Session {
986
1033
  let newVod;
987
1034
 
988
1035
  let sessionState = await this._sessionState.getValues(
989
- ["state", "assetId", "vodMediaSeqVideo", "vodMediaSeqAudio", "mediaSeq", "discSeq", "nextVod"]);
1036
+ ["state", "assetId", "vodMediaSeqVideo", "vodMediaSeqAudio", "mediaSeq", "mediaSeqAudio", "discSeq", "discSeqAudio", "nextVod"]);
990
1037
 
991
1038
  let isLeader = await this._sessionStateStore.isLeader(this._instanceId);
992
1039
 
@@ -1022,7 +1069,7 @@ class Session {
1022
1069
  let loadPromise;
1023
1070
  if (!vodResponse.type) {
1024
1071
  debug(`[${this._sessionId}]: got first VOD uri=${vodResponse.uri}:${vodResponse.offset || 0}`);
1025
- const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
1072
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1026
1073
  newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1027
1074
  if (vodResponse.timedMetadata) {
1028
1075
  Object.keys(vodResponse.timedMetadata).map(k => {
@@ -1051,7 +1098,9 @@ class Session {
1051
1098
  debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
1052
1099
  //debug(newVod);
1053
1100
  sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
1101
+ sessionState.mediaSeqAudio = await this._sessionState.set("mediaSeqAudio", 0);
1054
1102
  sessionState.discSeq = await this._sessionState.set("discSeq", 0);
1103
+ sessionState.discSeqAudio = await this._sessionState.set("discSeqAudio", 0);
1055
1104
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
1056
1105
  sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
1057
1106
  await this._playheadState.set("playheadRef", Date.now());
@@ -1112,10 +1161,10 @@ class Session {
1112
1161
  this.isAllowedToClearVodCache = true;
1113
1162
  }
1114
1163
  }
1115
- debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
1164
+ debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
1116
1165
  return;
1117
1166
  case SessionState.VOD_NEXT_INITIATING:
1118
- debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
1167
+ debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
1119
1168
  if (!isLeader) {
1120
1169
  debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be initiated`);
1121
1170
  }
@@ -1133,12 +1182,16 @@ class Session {
1133
1182
  }
1134
1183
  const length = currentVod.getLiveMediaSequencesCount();
1135
1184
  let endMseqValue;
1185
+ let endMseqValueAudio;
1136
1186
  if (currentVod.sequenceAlwaysContainNewSegments) {
1137
1187
  endMseqValue = currentVod.getLastSequenceMediaSequenceValue();
1188
+ endMseqValueAudio = currentVod.getLastSequenceMediaSequenceValueAudio();
1138
1189
  } else {
1139
1190
  endMseqValue = currentVod.getLiveMediaSequencesCount();
1191
+ endMseqValueAudio = currentVod.getLiveMediaSequencesCount("audio");
1140
1192
  }
1141
1193
  const lastDiscontinuity = currentVod.getLastDiscontinuity();
1194
+ const lastDiscontinuityAudio = currentVod.getLastDiscontinuityAudio();
1142
1195
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
1143
1196
  let vodPromise = this._getNextVod();
1144
1197
  if (length === 1) {
@@ -1155,7 +1208,7 @@ class Session {
1155
1208
  let loadPromise;
1156
1209
  if (!vodResponse.type) {
1157
1210
  debug(`[${this._sessionId}]: got next VOD uri=${vodResponse.uri}:${vodResponse.offset}`);
1158
- const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
1211
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1159
1212
  newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1160
1213
  if (vodResponse.timedMetadata) {
1161
1214
  Object.keys(vodResponse.timedMetadata).map(k => {
@@ -1198,12 +1251,14 @@ class Session {
1198
1251
  debug(`[${this._sessionId}]: next VOD loaded (${newVod.getDeltaTimes()})`);
1199
1252
  debug(`[${this._sessionId}]: ${newVod.getPlayheadPositions()}`);
1200
1253
  currentVod = newVod;
1201
- debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
1254
+ debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}; audio msequences=${currentVod.getLiveMediaSequencesCount("audio")}`);
1202
1255
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
1203
1256
  sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
1204
1257
  sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endMseqValue);
1258
+ sessionState.mediaSeqAudio = await this._sessionState.set("mediaSeqAudio", sessionState.mediaSeqAudio + endMseqValueAudio);
1205
1259
  sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
1206
- debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]`);
1260
+ sessionState.discSeqAudio = await this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio);
1261
+ debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]_[${sessionState.mediaSeqAudio}][${sessionState.discSeqAudio}]`);
1207
1262
  await this._sessionState.remove("nextVod");
1208
1263
  sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
1209
1264
  this.leaderIsSettingNextVod = false;
@@ -1265,6 +1320,7 @@ class Session {
1265
1320
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
1266
1321
  // 2) Set new 'offset' sequences, to carry on the continuity from session-live
1267
1322
  let mSeq = this.switchDataForSession.mediaSeq;
1323
+ // TODO: support demux^
1268
1324
  let currentVod = await this._sessionState.getCurrentVod();
1269
1325
  if (currentVod.sequenceAlwaysContainNewSegments) {
1270
1326
  // (!) will need to compensate if using this setting on HLSVod Object.
@@ -1286,6 +1342,7 @@ class Session {
1286
1342
  await this._sessionState.set("mediaSeq", mSeq);
1287
1343
  await this._playheadState.set("mediaSeq", mSeq);
1288
1344
  await this._sessionState.set("discSeq", dSeq);
1345
+ // TODO: support demux^
1289
1346
  debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
1290
1347
  // 3) Set new media segments/currentVod, to carry on the continuity from session-live
1291
1348
  debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
@@ -1324,7 +1381,7 @@ class Session {
1324
1381
  }
1325
1382
  break;
1326
1383
  case SessionState.VOD_RELOAD_INITIATING:
1327
- debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
1384
+ debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
1328
1385
  if (!isLeader) {
1329
1386
  debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be reloaded`);
1330
1387
  if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
@@ -1381,7 +1438,7 @@ class Session {
1381
1438
 
1382
1439
  slateVod.load()
1383
1440
  .then(() => {
1384
- const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
1441
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1385
1442
  hlsVod = new HLSVod(this.slateUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
1386
1443
  const timestamp = Date.now();
1387
1444
  hlsVod.addMetadata('id', `slate-${timestamp}`);
@@ -1393,11 +1450,26 @@ class Session {
1393
1450
  mediaManifestStream.push(null);
1394
1451
  return mediaManifestStream;
1395
1452
  };
1396
- if (afterVod) {
1397
- return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader);
1453
+ if (this.use_demuxed_audio) {
1454
+ const slateAudioManifestLoader = (audioGroupId, audioLanguage) => {
1455
+ let mediaManifestStream = new Readable();
1456
+ mediaManifestStream.push(slateVod.getAudioManifest(audioGroupId, audioLanguage));
1457
+ mediaManifestStream.push(null);
1458
+ return mediaManifestStream;
1459
+ };
1460
+ if (afterVod) {
1461
+ return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader, slateAudioManifestLoader);
1462
+ } else {
1463
+ return hlsVod.load(null, slateMediaManifestLoader, slateAudioManifestLoader);
1464
+ }
1398
1465
  } else {
1399
- return hlsVod.load(null, slateMediaManifestLoader);
1466
+ if (afterVod) {
1467
+ return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader);
1468
+ } else {
1469
+ return hlsVod.load(null, slateMediaManifestLoader);
1470
+ }
1400
1471
  }
1472
+
1401
1473
  })
1402
1474
  .then(() => {
1403
1475
  resolve(hlsVod);
@@ -1426,7 +1498,7 @@ class Session {
1426
1498
 
1427
1499
  slateVod.load()
1428
1500
  .then(() => {
1429
- const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
1501
+ const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1430
1502
  hlsVod = new HLSVod(nexVodUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
1431
1503
  const timestamp = Date.now();
1432
1504
  hlsVod.addMetadata('id', `slate-${timestamp}`);
@@ -1438,10 +1510,24 @@ class Session {
1438
1510
  mediaManifestStream.push(null);
1439
1511
  return mediaManifestStream;
1440
1512
  };
1441
- if (afterVod) {
1442
- return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader);
1513
+ if (this.use_demuxed_audio) {
1514
+ const slateAudioManifestLoader = (audioGroupId, audioLanguage) => {
1515
+ let mediaManifestStream = new Readable();
1516
+ mediaManifestStream.push(slateVod.getAudioManifest(audioGroupId, audioLanguage));
1517
+ mediaManifestStream.push(null);
1518
+ return mediaManifestStream;
1519
+ };
1520
+ if (afterVod) {
1521
+ return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader, slateAudioManifestLoader);
1522
+ } else {
1523
+ return hlsVod.load(null, slateMediaManifestLoader, slateAudioManifestLoader);
1524
+ }
1443
1525
  } else {
1444
- return hlsVod.load(null, slateMediaManifestLoader);
1526
+ if (afterVod) {
1527
+ return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader);
1528
+ } else {
1529
+ return hlsVod.load(null, slateMediaManifestLoader);
1530
+ }
1445
1531
  }
1446
1532
  })
1447
1533
  .then(() => {
@@ -1538,6 +1624,13 @@ class Session {
1538
1624
  return playheadPositions[sessionState.vodMediaSeqVideo];
1539
1625
  }
1540
1626
 
1627
+ async _getAudioPlayheadPosition(seqIdx) {
1628
+ const currentVod = await this._sessionState.getCurrentVod();
1629
+ const playheadPositions = currentVod.getPlayheadPositions("audio");
1630
+ debug(`[${this._sessionId}]: Current audio playhead position (${seqIdx}): ${playheadPositions[seqIdx]}`);
1631
+ return playheadPositions[seqIdx];
1632
+ }
1633
+
1541
1634
  _getLastDuration(manifest) {
1542
1635
  return new Promise((resolve, reject) => {
1543
1636
  try {
@@ -131,6 +131,8 @@ class SessionStateStore extends SharedStateStore {
131
131
  super("session", opts, {
132
132
  mediaSeq: 0,
133
133
  discSeq: 0,
134
+ mediaSeqAudio: 0,
135
+ discSeqAudio: 0,
134
136
  vodMediaSeqVideo: 0,
135
137
  vodMediaSeqAudio: 0, // assume only one audio group now
136
138
  state: SessionState.VOD_INIT,
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.4.4",
3
+ "version": "4.0.0-rc.0",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc --project ./ && cp ./package.json dist/",
9
9
  "debug-server": "DEBUG=engine-*,vod-to-live node dist/server.js",
10
- "test": "$(npm bin)/jasmine",
10
+ "test": "jasmine",
11
11
  "postversion": "git push && git push --tags",
12
12
  "start": "node dist/server.js",
13
13
  "start-demux": "node dist/server-demux.js",
@@ -23,10 +23,10 @@
23
23
  "repository": "https://github.com/Eyevinn/channel-engine",
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
- "@eyevinn/hls-repeat": "^0.1.1",
27
- "@eyevinn/hls-truncate": "^0.1.0",
28
- "@eyevinn/hls-vodtolive": "^2.0.2",
29
- "@eyevinn/m3u8": "^0.4.1",
26
+ "@eyevinn/hls-repeat": "^0.2.0",
27
+ "@eyevinn/hls-truncate": "^0.2.0",
28
+ "@eyevinn/hls-vodtolive": "^2.1.1",
29
+ "@eyevinn/m3u8": "^0.5.3",
30
30
  "abort-controller": "^3.0.0",
31
31
  "debug": "^3.2.7",
32
32
  "memcache-client": "^0.10.1",
package/server-demux.ts CHANGED
@@ -2,26 +2,53 @@
2
2
  * Reference implementation of Channel Engine library using demuxed VOD assets.
3
3
  */
4
4
 
5
- import { ChannelEngine, ChannelEngineOpts,
6
- IAssetManager, IChannelManager,
7
- VodRequest, VodResponse, Channel, ChannelProfile,
8
- AudioTracks
5
+ import {
6
+ ChannelEngine,
7
+ ChannelEngineOpts,
8
+ IAssetManager,
9
+ IChannelManager,
10
+ VodRequest,
11
+ VodResponse,
12
+ Channel,
13
+ ChannelProfile,
14
+ AudioTracks,
9
15
  } from "./index";
10
16
 
17
+ const STITCH_ENDPOINT = "http://localhost:8000/stitch/master.m3u8";
18
+
11
19
  class RefAssetManager implements IAssetManager {
12
20
  private assets;
13
21
  private pos;
14
22
  constructor(opts?) {
15
23
  this.assets = {
16
24
  1: [
25
+ /* #TS+DEMUX HLS VODS */
26
+ // {
27
+ // id: 1,
28
+ // title: "Elephants Dream",
29
+ // uri: "https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8",
30
+ // },
31
+ // {
32
+ // id: 2,
33
+ // title: "Test HLS Bird noises (1m10s)",
34
+ // uri: "https://mtoczko.github.io/hls-test-streams/test-audio-pdt/playlist.m3u8",
35
+ // },
36
+ /* #CMAF+DEMUX HLS VODS */
17
37
  {
18
38
  id: 1,
19
- title: "Elephants Dream",
20
- uri: "https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8", },
39
+ title: "Nymo Bloopers",
40
+ uri: "https://vod.streaming.a2d.tv/a07ff4eb-6770-4805-a0ad-a4d1b127880d/4fef8b00-6d0b-11ed-89b6-2b1a288899a0_20356478.ism/.m3u8"
41
+ },
21
42
  {
22
43
  id: 2,
23
- title: "Test HLS Bird noises (1m10s)",
24
- uri: "https://mtoczko.github.io/hls-test-streams/test-audio-pdt/playlist.m3u8",},
44
+ title: "Benjamin Sjunger",
45
+ uri: "https://vod.streaming.a2d.tv/3f389c48-03e3-48a2-8e98-a02c55185a68/4c792a30-89ad-11ed-95d9-1b374c4e2f9f_20411056.ism/.m3u8"
46
+ },
47
+ {
48
+ id: 3,
49
+ title: "Idol Vinnarlåten",
50
+ uri: "https://vod.streaming.a2d.tv/369f8dd6-bc62-40a3-b734-4bc1cb50c00b/e9ef93d0-6d10-11ed-83d0-17c0db863fe0_20356484.ism/.m3u8"
51
+ },
25
52
  ],
26
53
  };
27
54
  this.pos = {
@@ -47,8 +74,32 @@ class RefAssetManager implements IAssetManager {
47
74
  this.pos[channelId] = 0;
48
75
  }
49
76
  vod.timedMetadata = {
50
- 'start-date': new Date().toISOString(),
51
- 'class': 'se.eyevinn.demo'
77
+ "start-date": new Date().toISOString(),
78
+ class: "se.eyevinn.demo",
79
+ };
80
+ const payload = {
81
+ uri: vod.uri,
82
+ breaks: [
83
+ /* #TS+DEMUX HLS BREAK VOD */
84
+ // {
85
+ // pos: 0,
86
+ // duration: 60 * 1000,
87
+ // url: "https://lab-live.cdn.eyevinn.technology/DEMUX_002/master_demux_aac-en-fr.m3u8",
88
+ // },
89
+ /* #CMAF+DEMUX HLS BREAK VOD */
90
+ {
91
+ pos: 0,
92
+ duration: 20 * 1000,
93
+ url: "https://ovpuspvod.a2d-stage.tv/trailers/63ef9c36e3ffa90028603374/output.ism/.m3u8",
94
+ },
95
+ ],
96
+ };
97
+ const buff = Buffer.from(JSON.stringify(payload));
98
+ const encodedPayload = buff.toString("base64");
99
+ vod = {
100
+ id: vod.id,
101
+ title: vod.title,
102
+ uri: STITCH_ENDPOINT + "?payload=" + encodedPayload,
52
103
  };
53
104
  resolve(vod);
54
105
  } else {
@@ -60,7 +111,7 @@ class RefAssetManager implements IAssetManager {
60
111
 
61
112
  class RefChannelManager implements IChannelManager {
62
113
  getChannels(): Channel[] {
63
- return [ { id: "1", profile: this._getProfile(), audioTracks: this._getAudioTracks(), }, ];
114
+ return [{ id: "1", profile: this._getProfile(), audioTracks: this._getAudioTracks() }];
64
115
  }
65
116
 
66
117
  _getProfile(): ChannelProfile[] {
@@ -82,11 +133,7 @@ class RefChannelManager implements IChannelManager {
82
133
  ];
83
134
  }
84
135
  _getAudioTracks(): AudioTracks[] {
85
- return [
86
- { language: "sp", name: "Spanish" },
87
- { language: "ru", name: "Russian" },
88
- { language: "en", name: "English", default: true },
89
- ];
136
+ return [{ language: "Swedish", name: "Swedish" }];
90
137
  }
91
138
  }
92
139
 
@@ -97,13 +144,16 @@ const engineOptions: ChannelEngineOpts = {
97
144
  heartbeat: "/",
98
145
  averageSegmentDuration: 2000,
99
146
  channelManager: refChannelManager,
100
- defaultSlateUri:
101
- "https://maitv-vod.lab.eyevinn.technology/slate-consuo.mp4/master.m3u8",
147
+ /* #TS+DEMUX SLATE VOD */
148
+ defaultSlateUri: "https://ovpuspvod.a2d-stage.tv/trailers/bumpers/tv4_spring/output.ism/.m3u8",
149
+ /* #CMAF+DEMUX SLATE VOD */
150
+ //defaultSlateUri: "https://ovpuspvod.a2d-stage.tv/trailers/bumpers/tv4_spring/output.ism/.m3u8",
102
151
  slateRepetitions: 10,
103
152
  redisUrl: process.env.REDIS_URL,
104
153
  useDemuxedAudio: true,
154
+ alwaysNewSegments: true,
105
155
  };
106
156
 
107
157
  const engine = new ChannelEngine(refAssetManager, engineOptions);
108
158
  engine.start();
109
- engine.listen(process.env.port || 8000);
159
+ engine.listen(process.env.PORT || 8000);