eyevinn-channel-engine 3.0.0 → 3.0.4

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.
@@ -9,7 +9,7 @@ jobs:
9
9
 
10
10
  strategy:
11
11
  matrix:
12
- node-version: [10.x, 12.x, 14.x]
12
+ node-version: [12.x, 14.x]
13
13
 
14
14
  steps:
15
15
  - uses: actions/checkout@v1
package/README.md CHANGED
@@ -18,12 +18,18 @@ To run the basic reference implementation included in this repository you run:
18
18
  $ npm start
19
19
  ```
20
20
 
21
- And to run the basic reference implementation for using demuxed VODs you run:
21
+ To run the basic reference implementation for using demuxed VODs you run:
22
22
 
23
23
  ```
24
24
  $ npm run start-demux
25
25
  ```
26
26
 
27
+ And to run the basic reference implementation for using live-mixing you run:
28
+
29
+ ```
30
+ $ npm run start-livemix
31
+ ```
32
+
27
33
 
28
34
  Then point your HLS video player to `http://localhost:8000/channels/1/master.m3u8` to start playing the linear live stream.
29
35
 
@@ -122,8 +128,6 @@ engine.start();
122
128
  engine.listen(process.env.port || 8000);
123
129
  ```
124
130
 
125
- Note that this feature is currently in beta which means that it is close to production-ready but has not been run in production yet. We appreciate all efforts to try this out and provide feedback.
126
-
127
131
 
128
132
  ## Live Mixing (BETA)
129
133
 
@@ -137,8 +141,9 @@ class MyStreamSwitchManager {
137
141
  getSchedule(channelId) -> [ { eventId, assetId, title, type, start_time, end_time, uri, duration } ]
138
142
  }
139
143
  ```
144
+ When using **Live Mixing** in **High-Availablilty** mode, it is important to know that the class's `getSchedule(channelId)` function will be called by each channel-engine instance. Meaning that it is crucial that the implementation of this function will return the same response for each and every channel-engine instance, as they are expecting to be recieving identical lists in order to be synced up for live mixing.
140
145
 
141
- The class's `getSchedule(channelId)` function should return a list of event objects in the following format:
146
+ The class's `getSchedule(channelId)` function should return a promise containing the list of events as an object in the following format below:
142
147
 
143
148
  ```
144
149
  {
@@ -204,6 +209,7 @@ Below are examples of a Live stream event and a VOD event respectively:
204
209
  (*Important Note: When it comes to VOD events. Desired `duration` has priority over `end_time`. Meaning that you will only resume VOD2Live content after the asset in the VOD event has finished. This may change in the future.*)
205
210
 
206
211
 
212
+
207
213
  Then create an instance of the class and reference it as the `streamSwitchManager` in your engineOptions, just like you'd do with the channel manager.
208
214
 
209
215
  ```
@@ -244,7 +250,7 @@ If the connection to the Live stream ends or if the current time has passed the
244
250
 
245
251
  Note that this feature is also currently in beta which means that it is close to production-ready but has not been run in production yet. We appreciate all efforts to try this out and provide feedback.
246
252
 
247
- ## Demo
253
+ ## Support
248
254
 
249
255
  If you want help to get started to build a service of your own based on this library you can hire an [Eyevinn Video-Dev Team](https://video-dev.team) to help you out.
250
256
 
package/engine/server.js CHANGED
@@ -167,12 +167,18 @@ class ChannelEngine {
167
167
 
168
168
  const StreamSwitchLoop = async (timeIntervalMs) => {
169
169
  while(true) {
170
- const ts_1 = Date.now();
171
- await this.updateStreamSwitchAsync()
172
- const ts_2 = Date.now();
173
- let interval = (timeIntervalMs - (ts_2 - ts_1)) < 0 ? 50 : (timeIntervalMs - (ts_2 - ts_1));
174
- await timer(interval)
175
- debug(`SteamSwitchLoop waited for all channels. Next tick in: ${interval}ms`)
170
+ try {
171
+ const ts_1 = Date.now();
172
+ await this.updateStreamSwitchAsync()
173
+ const ts_2 = Date.now();
174
+ let interval = (timeIntervalMs - (ts_2 - ts_1)) < 0 ? 50 : (timeIntervalMs - (ts_2 - ts_1));
175
+ await timer(interval)
176
+ debug(`StreamSwitchLoop waited for all channels. Next tick in: ${interval}ms`)
177
+ } catch (err) {
178
+ console.error(err)
179
+ debug(`StreamSwitchLoop iteration failed. Trying Again in 1000ms!`);
180
+ await timer(1000);
181
+ }
176
182
  }
177
183
  }
178
184
  StreamSwitchLoop(this.streamSwitchTimeIntervalMs);
@@ -188,6 +194,7 @@ class ChannelEngine {
188
194
  let status = null;
189
195
  try {
190
196
  status = await switcher.streamSwitcher(sessions[channel], sessionsLive[channel]);
197
+ debug(`[${channel}]: streamSwitcher returned switchstatus=${status}`);
191
198
  if (status === undefined) {
192
199
  debug(`[WARNING]: switcherStatus->${status}. Setting value to previous status->${prevStatus}`);
193
200
  status = prevStatus;
@@ -200,7 +207,13 @@ class ChannelEngine {
200
207
  debug(`Tried to switch stream on a non-existing channel=[${channel}]. Switching Ignored!)`);
201
208
  }
202
209
  }
203
- await Promise.all(channels.map(channel => getSwitchStatusAndPerformSwitch(channel)));
210
+ try {
211
+ await Promise.all(channels.map(channel => getSwitchStatusAndPerformSwitch(channel)));
212
+ } catch (err) {
213
+ debug('Problem occured when updating streamSwitchers');
214
+ throw new Error (err);
215
+ }
216
+
204
217
  }
205
218
 
206
219
  async updateChannelsAsync(channelMgr, options) {
@@ -521,7 +534,7 @@ class ChannelEngine {
521
534
  await timer(500);
522
535
  }
523
536
  let body = null;
524
- debug(`switcherStatus[req.params[1]]=[${switcherStatus[req.params[1]]}]`);
537
+ debug(`switcherStatus[${req.params[1]}]=[${switcherStatus[req.params[1]]}]`);
525
538
  if (switcherStatus[req.params[1]]) {
526
539
  debug(`[${req.params[1]}]: Responding with Live-stream manifest`);
527
540
  body = await sessionLive.getCurrentMediaManifestAsync(req.params[0]);
@@ -663,8 +676,9 @@ class ChannelEngine {
663
676
  let sessionResets = [];
664
677
  for (const sessionId of Object.keys(sessions)) {
665
678
  const session = sessions[sessionId];
666
- if (session) {
667
- await session.resetAsync();
679
+ const sessionLive = sessionsLive[sessionId];
680
+ if (session && sessionLive) {
681
+ await session.resetAsync();
668
682
  sessionResets.push(sessionId);
669
683
  } else {
670
684
  const err = new errs.NotFoundError('Invalid session');
package/engine/session.js CHANGED
@@ -43,6 +43,7 @@ class Session {
43
43
  this.playheadDiffThreshold = DEFAULT_PLAYHEAD_DIFF_THRESHOLD;
44
44
  this.maxTickInterval = DEFAULT_MAX_TICK_INTERVAL;
45
45
  this.diffCompensation = null;
46
+ this.waitingForNextVod = false;
46
47
 
47
48
  if (config) {
48
49
  if (config.sessionId) {
@@ -244,7 +245,7 @@ class Session {
244
245
  try {
245
246
  const hlsVod = await this._truncateSlate(null, duration, vodUri);
246
247
  let vodSegments = hlsVod.getMediaSegments();
247
- Object.keys(vodSegments).forEach(bw => vodSegments[bw].unshift({ discontinuity: true }));
248
+ Object.keys(vodSegments).forEach(bw => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
248
249
  return vodSegments;
249
250
  } catch (exc) {
250
251
  debug(`[${this._sessionId}]: Failed to generate truncated VOD!`);
@@ -362,6 +363,7 @@ class Session {
362
363
  throw new Error('Session not ready');
363
364
  }
364
365
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
366
+ const discSeqOffset = await this._sessionState.get("discSeq");
365
367
  if (playheadState.vodMediaSeqVideo === 0) {
366
368
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
367
369
  if (!isLeader) {
@@ -372,7 +374,7 @@ class Session {
372
374
  const currentVod = await this._sessionState.getCurrentVod();
373
375
  if (currentVod) {
374
376
  try {
375
- const discSeqCount = currentVod.getLastUsedDiscSeq();
377
+ const discSeqCount = discSeqOffset + currentVod.discontinuities[playheadState.vodMediaSeqVideo];
376
378
  debug(`[${this._sessionId}]: MediaSeq: (${(playheadState.mediaSeq + playheadState.vodMediaSeqVideo)}) and DiscSeq: (${discSeqCount}) requested `);
377
379
  return {
378
380
  'mediaSeq': (playheadState.mediaSeq + playheadState.vodMediaSeqVideo),
@@ -756,6 +758,13 @@ class Session {
756
758
  sessionState.state = SessionState.VOD_INIT;
757
759
  }
758
760
 
761
+ if (!isLeader && this.waitingForNextVod) {
762
+ // By now Leader should have added the next Vod in store
763
+ debug(`[${this._sessionId}]: Not leader! New VOD loaded during last tick. Invalidate current VOD cache`);
764
+ await this._sessionState.clearCurrentVodCache();
765
+ this.waitingForNextVod = false;
766
+ }
767
+
759
768
  switch (sessionState.state) {
760
769
  case SessionState.VOD_INIT:
761
770
  case SessionState.VOD_INIT_BY_ID:
@@ -842,10 +851,13 @@ class Session {
842
851
  }
843
852
  }
844
853
  case SessionState.VOD_PLAYING:
845
- if (!isLeader && sessionState.vodMediaSeqVideo === 0) {
846
- debug(`[${this._sessionId}]: First mediasequence in VOD and I am not the leader so invalidate current VOD cache and fetch the new one from the leader`);
847
- await this._sessionState.clearCurrentVodCache();
848
- currentVod = await this._sessionState.getCurrentVod();
854
+ if (!isLeader) {
855
+ if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
856
+ debug(`[${this._sessionId}]: Not leader! Invalidate current VOD cache and fetch the new one from the leader`);
857
+ await this._sessionState.clearCurrentVodCache();
858
+ currentVod = await this._sessionState.getCurrentVod();
859
+ this.waitingForNextVod = false;
860
+ }
849
861
  }
850
862
  debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
851
863
  return;
@@ -853,10 +865,11 @@ class Session {
853
865
  debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
854
866
  if (!isLeader) {
855
867
  debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be initiated`);
856
- if (sessionState.vodMediaSeqVideo === 0) {
868
+ if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
857
869
  debug(`[${this._sessionId}]: First mediasequence in VOD and I am not the leader so invalidate current VOD cache and fetch the new one from the leader`);
858
870
  await this._sessionState.clearCurrentVodCache();
859
871
  }
872
+ this.waitingForNextVod = true;
860
873
  }
861
874
  return;
862
875
  case SessionState.VOD_NEXT_INIT:
@@ -938,6 +951,7 @@ class Session {
938
951
  return;
939
952
  } else {
940
953
  debug(`[${this._sessionId}]: not a leader so will go directly to state VOD_NEXT_INITIATING`);
954
+ this.waitingForNextVod = true;
941
955
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
942
956
  sessionState.currentVod = await this._sessionState.getCurrentVod();
943
957
  }
@@ -1,4 +1,4 @@
1
- const debug = require("debug")("engine-sessionLive");
1
+ const debug = require("debug")("engine-session-live");
2
2
  const allSettled = require("promise.allsettled");
3
3
  const crypto = require("crypto");
4
4
  const m3u8 = require("@eyevinn/m3u8");
@@ -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;
@@ -254,7 +254,9 @@ class SessionLive {
254
254
  this.vodSegments[bw].push(segments[bw][segIdx]);
255
255
  }
256
256
  if (!segments[bw][segments[bw].length - 1].discontinuity) {
257
- this.vodSegments[bw].push({ discontinuity: true });
257
+ this.vodSegments[bw].push({ discontinuity: true, cue: { in: true } });
258
+ } else {
259
+ segments[bw][segments[bw].length - 1]["cue"] = { in: true };
258
260
  }
259
261
  }
260
262
 
@@ -287,6 +289,13 @@ class SessionLive {
287
289
  // Don't use what Session gave you. Use the Leaders number if it's available
288
290
  const isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
289
291
  let liveCounts = await this.sessionLiveState.get("firstCounts");
292
+ if (liveCounts === null) {
293
+ liveCounts = {
294
+ liveSourceMseqCount: null,
295
+ mediaSeqCount: null,
296
+ discSeqCount: null,
297
+ };
298
+ }
290
299
  if (isLeader) {
291
300
  liveCounts.discSeqCount = this.discSeqCount;
292
301
  await this.sessionLiveState.set("firstCounts", liveCounts);
@@ -345,7 +354,7 @@ class SessionLive {
345
354
  currentMediaSequenceSegments[liveTargetBandwidth] = [];
346
355
  // In case we switch back before we've depleted all transitional segments
347
356
  currentMediaSequenceSegments[liveTargetBandwidth] = this.vodSegments[vodTargetBandwidth].concat(this.liveSegQueue[liveTargetBandwidth]);
348
- currentMediaSequenceSegments[liveTargetBandwidth].push({ discontinuity: true });
357
+ currentMediaSequenceSegments[liveTargetBandwidth].push({ discontinuity: true, cue: { in: true } });
349
358
  debug(`[${this.sessionId}]: Getting current media segments for bw=${bw}`);
350
359
  }
351
360
 
@@ -477,33 +486,31 @@ class SessionLive {
477
486
  const vodBws = Object.keys(this.vodSegments);
478
487
  const liveBws = Object.keys(this.liveSegsForFollowers);
479
488
  const size = this.liveSegsForFollowers[liveBws[0]].length;
480
- debug(`[${this.sessionId}]: [size=${size}]->this.liveSegsForFollowers=${Object.keys(this.liveSegsForFollowers)} `);
489
+
490
+ let newSegCount = 0;
491
+ this.liveSegsForFollowers[liveBws[0]].map((seg) => {if (seg.uri) { newSegCount++ }});
492
+
493
+ debug(`[${this.sessionId}]: FOLLOWER: liveSegsForFollowers Bws=${Object.keys(this.liveSegsForFollowers)}_newSegCount=${newSegCount} `);
481
494
  // Remove transitional segs & add live source segs collected from store
482
- for (let k = 0; k < size; k++) {
495
+ for (let k = 0; k < newSegCount; k++) {
483
496
  let incrementDiscSeqCount = false;
484
497
  // Shift the top vod segment on all variants
485
498
  for (let i = 0; i < vodBws.length; i++) {
486
499
  let seg = this.vodSegments[vodBws[i]].shift();
487
- if (seg && seg.discontinuity || seg && seg.cue) {
488
- if (seg.discontinuity) {
489
- incrementDiscSeqCount = true;
490
- }
491
- seg = this.vodSegments[vodBws[i]].shift();
492
- }
493
- if (seg && seg.discontinuity || seg && seg.cue) {
494
- if (seg.discontinuity) {
495
- incrementDiscSeqCount = true;
496
- }
500
+ if (seg && seg.discontinuity) {
501
+ incrementDiscSeqCount = true;
497
502
  this.vodSegments[vodBws[i]].shift();
498
503
  }
499
504
  }
500
505
  if (incrementDiscSeqCount) {
501
- this.mediaSeqCount++;
506
+ this.discSeqCount++;
502
507
  }
503
- // Push to bottom, new live source segment on all variants
504
- for (let i = 0; i < liveBws.length; i++) {
505
- const bw = liveBws[i];
506
- const liveSegFromLeader = this.liveSegsForFollowers[bw][k];
508
+ }
509
+ // Push to bottom, new live source segment on all variants
510
+ for (let segIdx = 0; segIdx < size; segIdx++) {
511
+ let incrementDiscSeqCount = false;
512
+ liveBws.forEach((bw)=>{
513
+ let liveSegFromLeader = this.liveSegsForFollowers[bw][segIdx];
507
514
  if (!this.liveSegQueue[bw]) {
508
515
  this.liveSegQueue[bw] = [];
509
516
  }
@@ -512,27 +519,21 @@ class SessionLive {
512
519
  let segCount = 0;
513
520
  this.liveSegQueue[bw].map((seg) => {if (seg.uri) { segCount++ }});
514
521
  if (segCount > this.targetNumSeg) {
515
- seg = this.liveSegQueue[bw].shift();
516
- if (seg && seg.discontinuity || seg && seg.cue) {
517
- if (seg.discontinuity) {
518
- incrementDiscSeqCount = true;
519
- }
522
+ let seg = this.liveSegQueue[bw].shift();
523
+ if (seg && seg.discontinuity) {
524
+ incrementDiscSeqCount = true;
520
525
  seg = this.liveSegQueue[bw].shift();
521
526
  }
522
- if (seg && seg.discontinuity || seg && seg.cue) {
523
- if (seg.discontinuity) {
524
- incrementDiscSeqCount = true;
525
- }
526
- this.liveSegQueue[bw].shift();
527
- }
528
527
  }
529
- debug(`[${this.sessionId}]: Pushed a segment to 'liveSegQueue'`);
530
- }
528
+ debug(`[${this.sessionId}]: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue'`);
529
+ });
531
530
  if (incrementDiscSeqCount) {
532
531
  this.discSeqCount++;
533
532
  }
534
- this.mediaSeqCount++;
535
533
  }
534
+ let prevVal = this.mediaSeqCount;
535
+ this.mediaSeqCount += newSegCount;
536
+ debug(`[${this.sessionId}]: Incrementing Mseq Count from [${prevVal}] -> [${this.mediaSeqCount}]`);
536
537
  debug(`[${this.sessionId}]: Finished updating all Follower's Counts and Segment Queues!`);
537
538
  }
538
539
 
@@ -792,13 +793,22 @@ class SessionLive {
792
793
  let pushPromises = [];
793
794
  for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
794
795
  let bw = Object.keys(this.mediaManifestURIs)[i];
795
- pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, bw, this.mediaManifestURIs[bw], bw));
796
- //debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
796
+ pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw));
797
+ debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
797
798
  }
798
-
799
799
  // Segment Pushing
800
800
  debug(`[${this.sessionId}]: Executing Promises II: Segment Pushing`);
801
- await Promise.all(pushPromises);
801
+ let results = await allSettled(pushPromises);//await Promise.all(pushPromises);
802
+ const removedDiscSeqList = results.map((item) => {
803
+ if (item.status === "rejected") {
804
+ return -1;
805
+ }
806
+ return item.value.removedDiscSeqs;
807
+ });
808
+ if (removedDiscSeqList.every((val, i, arr) => val === arr[0])) {
809
+ // if all variants removed equal amounts of disc-tags
810
+ this.discSeqCount += removedDiscSeqList[0];
811
+ }
802
812
  // UPDATE COUNTS, & Shift Segments in vodSegments
803
813
  await this._incrementAndShift();
804
814
  }
@@ -814,7 +824,7 @@ class SessionLive {
814
824
  }
815
825
 
816
826
  // [LASTLY]: LEADER does this for respawned-FOLLOWERS' sake.
817
- if (this.firstTime) {
827
+ if (this.firstTime && this.allowedToSet) {
818
828
  // Buy some time for followers (NOT Respawned) to fetch their own L.S m3u8.
819
829
  await timer(1000); // maybe remove
820
830
  const firstCounts = {
@@ -843,17 +853,11 @@ class SessionLive {
843
853
  // Shift the top vod segment
844
854
  for (let i = 0; i < vodBandwidths.length; i++) {
845
855
  let seg = this.vodSegments[vodBandwidths[i]].shift();
846
- if (seg && seg.discontinuity || seg && seg.cue) {
847
- if (seg.discontinuity) {
848
- incrementDiscSeqCount = true;
849
- }
850
- seg = this.vodSegments[vodBandwidths[i]].shift();
851
- }
852
- if (seg && seg.discontinuity || seg && seg.cue) {
856
+ if (seg) {
853
857
  if (seg.discontinuity) {
858
+ this.vodSegments[vodBandwidths[i]].shift();
854
859
  incrementDiscSeqCount = true;
855
860
  }
856
- this.vodSegments[vodBandwidths[i]].shift();
857
861
  }
858
862
  }
859
863
  if (incrementDiscSeqCount) {
@@ -905,8 +909,12 @@ class SessionLive {
905
909
  return new Promise((resolve, reject) => {
906
910
  parser.on("m3u", (m3u) => {
907
911
  try {
908
- let manifestObj = this._miniparse(m3u, bw, mediaManifestUri, liveTargetBandwidth);
909
- resolve(manifestObj);
912
+ const resolveObj = {
913
+ M3U: m3u,
914
+ mediaSeq: m3u.get("mediaSequence"),
915
+ };
916
+ this.liveSourceM3Us[liveTargetBandwidth] = resolveObj;
917
+ resolve(resolveObj);
910
918
  } catch (exc) {
911
919
  debug(`[${this.sessionId}]: Error when parsing latest manifest`);
912
920
  reject(exc);
@@ -919,33 +927,7 @@ class SessionLive {
919
927
  });
920
928
  }
921
929
 
922
- _miniparse(m3u, bw, mediaManifestUri, liveTargetBandwidth) {
923
- if (m3u === null) {
924
- throw new Error("No m3u object provided");
925
- }
926
- return new Promise(async (resolve, reject) => {
927
- try {
928
- const resolveObj = {
929
- M3U: m3u,
930
- mediaSeq: m3u.get("mediaSequence"),
931
- };
932
-
933
- this.liveSourceM3Us[liveTargetBandwidth] = resolveObj;
934
-
935
- resolve(resolveObj);
936
- } catch (exc) {
937
- console.error("ERROR: " + exc);
938
- reject({
939
- message: exc,
940
- bandwidth: liveTargetBandwidth,
941
- m3u8: null,
942
- mediaSeq: -1,
943
- });
944
- }
945
- });
946
- }
947
-
948
- _parseMediaManifest(m3u, bw, mediaManifestUri, liveTargetBandwidth) {
930
+ _parseMediaManifest(m3u, mediaManifestUri, liveTargetBandwidth) {
949
931
  return new Promise(async (resolve, reject) => {
950
932
  try {
951
933
  if (!this.liveSegQueue[liveTargetBandwidth]) {
@@ -954,7 +936,6 @@ class SessionLive {
954
936
  if (!this.liveSegsForFollowers[liveTargetBandwidth]) {
955
937
  this.liveSegsForFollowers[liveTargetBandwidth] = [];
956
938
  }
957
-
958
939
  let baseUrl = "";
959
940
  const m = mediaManifestUri.match(/^(.*)\/.*?$/);
960
941
  if (m) {
@@ -972,20 +953,18 @@ class SessionLive {
972
953
 
973
954
  this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
974
955
  this.targetDuration = m3u.get("targetDuration");
975
-
976
- // Switch out relative URIs if they are used, with absolute URLs
956
+ let amountRemovedDiscSeqs = 0;
957
+ let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
958
+ startIdx = startIdx < 0 ? 0 : startIdx;
977
959
  if (mediaManifestUri) {
978
- // CREATE NEW MANIFEST
979
- let startIdx;
980
- startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
981
- if (startIdx < 0) {
982
- startIdx = 0;
983
- }
984
960
  // push segments
985
- this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
961
+ amountRemovedDiscSeqs = this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
986
962
  }
987
-
988
- resolve(this.lastRequestedMediaSeqRaw);
963
+ let resolveObj = {
964
+ removedDiscSeqs: amountRemovedDiscSeqs,
965
+ bw: liveTargetBandwidth
966
+ }
967
+ resolve(resolveObj);
989
968
  } catch (exc) {
990
969
  console.error("ERROR: " + exc);
991
970
  reject(exc);
@@ -994,53 +973,61 @@ class SessionLive {
994
973
  }
995
974
 
996
975
  _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
997
- let incrementDiscSeqCount = false;
976
+ let amountRemovedDiscSeqs = 0;
998
977
  for (let i = startIdx; i < playlistItems.length; i++) {
999
978
  debug(`[${this.sessionId}]: Adding Live Segment(s) to Queue (for bw=${liveTargetBandwidth})`);
1000
979
  let seg = {};
1001
980
  let playlistItem = playlistItems[i];
1002
981
  let segmentUri;
982
+ let cueData = null;
983
+ let daterangeData = null;
1003
984
  let attributes = playlistItem["attributes"].attributes;
1004
-
1005
985
  if (playlistItem.properties.discontinuity) {
1006
986
  this.liveSegQueue[liveTargetBandwidth].push({ discontinuity: true });
1007
987
  this.liveSegsForFollowers[liveTargetBandwidth].push({ discontinuity: true });
1008
988
  }
1009
989
  if ("cuein" in attributes) {
1010
- this.liveSegQueue[liveTargetBandwidth].push({ cue: { in: true } });
1011
- this.liveSegsForFollowers[liveTargetBandwidth].push({ cue: { in: true } });
990
+ if (!cueData) {
991
+ cueData = {};
992
+ }
993
+ cueData["in"] = true;
1012
994
  }
1013
995
  if ("cueout" in attributes) {
1014
- this.liveSegQueue[liveTargetBandwidth].push({ cue: { out: true, duration: attributes["cueout"] }, });
1015
- this.liveSegsForFollowers[liveTargetBandwidth].push({ cue: { out: true, duration: attributes["cueout"] } });
1016
- }
996
+ if (!cueData) {
997
+ cueData = {};
998
+ }
999
+ cueData["out"] = true;
1000
+ cueData["duration"] = attributes["cueout"];
1001
+ }
1017
1002
  if ("cuecont" in attributes) {
1018
- this.liveSegQueue[liveTargetBandwidth].push({ cue: { cont: true } });
1019
- this.liveSegsForFollowers[liveTargetBandwidth].push({ cue: { cont: true } });
1003
+ if (!cueData) {
1004
+ cueData = {};
1005
+ }
1006
+ cueData["cont"] = true;
1020
1007
  }
1021
1008
  if ("scteData" in attributes) {
1022
- this.liveSegQueue[liveTargetBandwidth].push({ cue: { scteData: attributes["scteData"] } });
1023
- this.liveSegsForFollowers[liveTargetBandwidth].push({ cue: { scteData: attributes["scteData"] } });
1009
+ if (!cueData) {
1010
+ cueData = {};
1011
+ }
1012
+ cueData["scteData"] = attributes["scteData"];
1024
1013
  }
1025
1014
  if ("assetData" in attributes) {
1026
- this.liveSegQueue[liveTargetBandwidth].push({ cue: { assetData: attributes["assetData"] } });
1027
- this.liveSegsForFollowers[liveTargetBandwidth].push({ cue: { assetData: attributes["assetData"] } });
1015
+ if (!cueData) {
1016
+ cueData = {};
1017
+ }
1018
+ cueData["assetData"] = attributes["assetData"];
1028
1019
  }
1029
1020
  if ("daterange" in attributes) {
1030
- this.liveSegQueue[liveTargetBandwidth].push({
1031
- daterange: {
1032
- id: attributes["daterange"]["ID"],
1033
- "start-date": attributes["daterange"]["START-DATE"],
1034
- "planned-duration": parseFloat(attributes["daterange"]["PLANNED-DURATION"]),
1035
- }
1036
- });
1037
- this.liveSegsForFollowers[liveTargetBandwidth].push({
1038
- daterange: {
1039
- id: attributes["daterange"]["ID"],
1040
- "start-date": attributes["daterange"]["START-DATE"],
1041
- "planned-duration": parseFloat(attributes["daterange"]["PLANNED-DURATION"]),
1042
- }
1043
- });
1021
+ if(!daterangeData) {
1022
+ daterangeData = {}
1023
+ }
1024
+ daterangeData = {
1025
+ id: attributes["daterange"]["ID"],
1026
+ "start-date": attributes["daterange"]["START-DATE"],
1027
+ "planned-duration": parseFloat(
1028
+ attributes["daterange"]["PLANNED-DURATION"]
1029
+ ),
1030
+ };
1044
1031
  }
1045
1032
  if (playlistItem.properties.uri) {
1046
1033
  if (playlistItem.properties.uri.match("^http")) {
@@ -1050,6 +1037,10 @@ class SessionLive {
1050
1037
  }
1051
1038
  seg["duration"] = playlistItem.properties.duration;
1052
1039
  seg["uri"] = segmentUri;
1040
+ seg["cue"] = cueData;
1041
+ if (daterangeData) {
1042
+ seg["daterange"] = daterangeData;
1043
+ }
1053
1044
 
1054
1045
  this.liveSegQueue[liveTargetBandwidth].push(seg);
1055
1046
  this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
@@ -1057,25 +1048,19 @@ class SessionLive {
1057
1048
  this.liveSegQueue[liveTargetBandwidth].map((seg) => {if (seg.uri) { segCount++ }});
1058
1049
  debug(`[${this.sessionId}]: size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
1059
1050
  if (segCount > this.targetNumSeg) {
1051
+ debug(`[${this.sessionId}]: liveTargetBandwidth=${liveTargetBandwidth}_seg=${JSON.stringify(seg,null,2)}`);
1060
1052
  seg = this.liveSegQueue[liveTargetBandwidth].shift();
1061
- if (seg && seg.discontinuity || seg && seg.cue) {
1053
+ if (seg) {
1062
1054
  if (seg.discontinuity) {
1063
- incrementDiscSeqCount = true;
1055
+ this.liveSegQueue[liveTargetBandwidth].shift();
1056
+ amountRemovedDiscSeqs++;
1064
1057
  }
1065
- seg = this.liveSegQueue[liveTargetBandwidth].shift();
1066
- }
1067
- if (seg && seg.discontinuity || seg && seg.cue) {
1068
- if (seg.discontinuity) {
1069
- incrementDiscSeqCount = true;
1070
- }
1071
- this.liveSegQueue[liveTargetBandwidth].shift();
1072
1058
  }
1059
+ debug(`[${this.sessionId}]: After Shifting -> size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
1073
1060
  }
1074
1061
  }
1075
1062
  }
1076
- if (incrementDiscSeqCount) {
1077
- this.discSeqCount++;
1078
- }
1063
+ return amountRemovedDiscSeqs;
1079
1064
  }
1080
1065
 
1081
1066
  /*
@@ -1168,9 +1153,6 @@ class SessionLive {
1168
1153
  m3u8 += "#EXT-X-DISCONTINUITY\n";
1169
1154
  }
1170
1155
  if (seg.cue) {
1171
- if (seg.cue.in){
1172
- m3u8 += "#EXT-X-CUE-IN" + "\n";
1173
- }
1174
1156
  if(seg.cue.out) {
1175
1157
  if (seg.cue.scteData) {
1176
1158
  m3u8 += "#EXT-OATCLS-SCTE35:" + seg.cue.scteData + "\n";
@@ -1196,6 +1178,10 @@ class SessionLive {
1196
1178
  }
1197
1179
  m3u8 += "#EXT-X-DATERANGE:" + dateRangeAttributes + "\n";
1198
1180
  }
1181
+ // Mimick logic used in hls-vodtolive
1182
+ if (seg.cue && seg.cue.in){
1183
+ m3u8 += "#EXT-X-CUE-IN" + "\n";
1184
+ }
1199
1185
  if (seg.uri) {
1200
1186
  m3u8 += "#EXTINF:" + seg.duration.toFixed(3) + ",\n";
1201
1187
  m3u8 += seg.uri + "\n";
@@ -1,5 +1,5 @@
1
1
  const SharedStateStore = require('./shared_state_store.js');
2
- const debug = require("debug")("sessionLive-state-store");
2
+ const debug = require("debug")("session-live-state-store");
3
3
 
4
4
  class SharedSessionLiveState {
5
5
  constructor(store, sessionId, instanceId, opts) {