eyevinn-channel-engine 3.0.1 → 3.0.5

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
@@ -141,8 +141,9 @@ class MyStreamSwitchManager {
141
141
  getSchedule(channelId) -> [ { eventId, assetId, title, type, start_time, end_time, uri, duration } ]
142
142
  }
143
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.
144
145
 
145
- 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:
146
147
 
147
148
  ```
148
149
  {
@@ -208,6 +209,7 @@ Below are examples of a Live stream event and a VOD event respectively:
208
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.*)
209
210
 
210
211
 
212
+
211
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.
212
214
 
213
215
  ```
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) {
@@ -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
  }
@@ -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;
@@ -289,6 +289,13 @@ class SessionLive {
289
289
  // Don't use what Session gave you. Use the Leaders number if it's available
290
290
  const isLeader = await this.sessionLiveStateStore.isLeader(this.instanceId);
291
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
+ }
292
299
  if (isLeader) {
293
300
  liveCounts.discSeqCount = this.discSeqCount;
294
301
  await this.sessionLiveState.set("firstCounts", liveCounts);
@@ -479,9 +486,13 @@ class SessionLive {
479
486
  const vodBws = Object.keys(this.vodSegments);
480
487
  const liveBws = Object.keys(this.liveSegsForFollowers);
481
488
  const size = this.liveSegsForFollowers[liveBws[0]].length;
482
- 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} `);
483
494
  // Remove transitional segs & add live source segs collected from store
484
- for (let k = 0; k < size; k++) {
495
+ for (let k = 0; k < newSegCount; k++) {
485
496
  let incrementDiscSeqCount = false;
486
497
  // Shift the top vod segment on all variants
487
498
  for (let i = 0; i < vodBws.length; i++) {
@@ -493,12 +504,13 @@ class SessionLive {
493
504
  }
494
505
  if (incrementDiscSeqCount) {
495
506
  this.discSeqCount++;
496
- incrementDiscSeqCount = false;
497
507
  }
498
- // Push to bottom, new live source segment on all variants
499
- for (let i = 0; i < liveBws.length; i++) {
500
- const bw = liveBws[i];
501
- 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];
502
514
  if (!this.liveSegQueue[bw]) {
503
515
  this.liveSegQueue[bw] = [];
504
516
  }
@@ -507,19 +519,21 @@ class SessionLive {
507
519
  let segCount = 0;
508
520
  this.liveSegQueue[bw].map((seg) => {if (seg.uri) { segCount++ }});
509
521
  if (segCount > this.targetNumSeg) {
510
- seg = this.liveSegQueue[bw].shift();
522
+ let seg = this.liveSegQueue[bw].shift();
511
523
  if (seg && seg.discontinuity) {
512
524
  incrementDiscSeqCount = true;
513
525
  seg = this.liveSegQueue[bw].shift();
514
526
  }
515
527
  }
516
- debug(`[${this.sessionId}]: Pushed a segment to 'liveSegQueue'`);
517
- }
528
+ debug(`[${this.sessionId}]: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue'`);
529
+ });
518
530
  if (incrementDiscSeqCount) {
519
531
  this.discSeqCount++;
520
532
  }
521
- this.mediaSeqCount++;
522
533
  }
534
+ let prevVal = this.mediaSeqCount;
535
+ this.mediaSeqCount += newSegCount;
536
+ debug(`[${this.sessionId}]: Incrementing Mseq Count from [${prevVal}] -> [${this.mediaSeqCount}]`);
523
537
  debug(`[${this.sessionId}]: Finished updating all Follower's Counts and Segment Queues!`);
524
538
  }
525
539
 
@@ -810,7 +824,7 @@ class SessionLive {
810
824
  }
811
825
 
812
826
  // [LASTLY]: LEADER does this for respawned-FOLLOWERS' sake.
813
- if (this.firstTime) {
827
+ if (this.firstTime && this.allowedToSet) {
814
828
  // Buy some time for followers (NOT Respawned) to fetch their own L.S m3u8.
815
829
  await timer(1000); // maybe remove
816
830
  const firstCounts = {
@@ -1004,16 +1018,17 @@ class SessionLive {
1004
1018
  cueData["assetData"] = attributes["assetData"];
1005
1019
  }
1006
1020
  if ("daterange" in attributes) {
1007
- if(!daterangeData) {
1008
- daterangeData = {}
1021
+ if (!daterangeData) {
1022
+ daterangeData = {};
1009
1023
  }
1010
- daterangeData = {
1011
- id: attributes["daterange"]["ID"],
1012
- "start-date": attributes["daterange"]["START-DATE"],
1013
- "planned-duration": parseFloat(
1014
- attributes["daterange"]["PLANNED-DURATION"]
1015
- ),
1016
- };
1024
+ let allDaterangeAttributes = Object.keys(attributes["daterange"]);
1025
+ allDaterangeAttributes.forEach((attr) => {
1026
+ if (attr.match(/DURATION$/)) {
1027
+ daterangeData[attr.toLowerCase()] = parseFloat(attributes["daterange"][attr]);
1028
+ } else {
1029
+ daterangeData[attr.toLowerCase()] = attributes["daterange"][attr];
1030
+ }
1031
+ });
1017
1032
  }
1018
1033
  if (playlistItem.properties.uri) {
1019
1034
  if (playlistItem.properties.uri.match("^http")) {
@@ -1034,6 +1049,7 @@ class SessionLive {
1034
1049
  this.liveSegQueue[liveTargetBandwidth].map((seg) => {if (seg.uri) { segCount++ }});
1035
1050
  debug(`[${this.sessionId}]: size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
1036
1051
  if (segCount > this.targetNumSeg) {
1052
+ debug(`[${this.sessionId}]: liveTargetBandwidth=${liveTargetBandwidth}_seg=${JSON.stringify(seg,null,2)}`);
1037
1053
  seg = this.liveSegQueue[liveTargetBandwidth].shift();
1038
1054
  if (seg) {
1039
1055
  if (seg.discontinuity) {
@@ -1041,6 +1057,7 @@ class SessionLive {
1041
1057
  amountRemovedDiscSeqs++;
1042
1058
  }
1043
1059
  }
1060
+ debug(`[${this.sessionId}]: After Shifting -> size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
1044
1061
  }
1045
1062
  }
1046
1063
  }
@@ -1155,9 +1172,12 @@ class SessionLive {
1155
1172
  }
1156
1173
  }
1157
1174
  }
1175
+ if (seg.datetime) {
1176
+ m3u8 += `#EXT-X-PROGRAM-DATE-TIME:${seg.datetime}\n`;
1177
+ }
1158
1178
  if (seg.daterange) {
1159
1179
  const dateRangeAttributes = Object.keys(seg.daterange).map(key => daterangeAttribute(key, seg.daterange[key])).join(',');
1160
- if (seg.daterange['start-date']) {
1180
+ if (!seg.datetime && seg.daterange['start-date']) {
1161
1181
  m3u8 += "#EXT-X-PROGRAM-DATE-TIME:" + seg.daterange['start-date'] + "\n";
1162
1182
  }
1163
1183
  m3u8 += "#EXT-X-DATERANGE:" + dateRangeAttributes + "\n";
@@ -30,6 +30,7 @@ class StreamSwitcher {
30
30
  this.timeDiff = null;
31
31
  this.abortTimeStamp = null;
32
32
 
33
+
33
34
  if (config) {
34
35
  if (config.sessionId) {
35
36
  this.sessionId = config.sessionId;
@@ -50,6 +51,26 @@ class StreamSwitcher {
50
51
  return this.eventId;
51
52
  }
52
53
 
54
+ async abortLiveFeed(session, sessionLive, message) {
55
+ if (this.streamTypeLive) {
56
+ let status = null;
57
+ debug(`[${this.sessionId}]: Abort Live Stream! Reason: ${message}`);
58
+ try {
59
+ this.abortTimeStamp = Date.now();
60
+ status = await this._initSwitching(
61
+ SwitcherState.LIVE_TO_V2L,
62
+ session,
63
+ sessionLive,
64
+ null
65
+ );
66
+ return status;
67
+ } catch (err) {
68
+ debug(`Failed to force a switch off live feed: ${err}`);
69
+ throw new Error(err);
70
+ }
71
+ }
72
+ }
73
+
53
74
  /**
54
75
  *
55
76
  * @param {Session} session The VOD2Live Session object.
@@ -57,6 +78,7 @@ class StreamSwitcher {
57
78
  * @returns A bool, true if streamSwitchManager contains current Live event to be played else false.
58
79
  */
59
80
  async streamSwitcher(session, sessionLive) {
81
+ let status = null;
60
82
  if (!this.streamSwitchManager) {
61
83
  debug(`[${this.sessionId}]: No streamSwitchManager available`);
62
84
  return false;
@@ -65,11 +87,28 @@ class StreamSwitcher {
65
87
  debug(`[${this.sessionId}]: streamSwitcher is currently busy`);
66
88
  return null;
67
89
  }
68
- let status = null;
90
+ // Handle Complete Storage Reset
91
+ let sessionState = await session._sessionState.get("state");
92
+ if (sessionState === 1 || !sessionState) {
93
+ this.working = true;
94
+ sessionLive.waitForPlayhead = false;
95
+ sessionLive.allowedToSet = false; // only have effect on leader
96
+ await sessionLive.resetSession();
97
+ await sessionLive.resetLiveStoreAsync(0);
98
+ this.working = false;
99
+ this.abortTimeStamp = Date.now() + 20 * 1000; // 30 second V2L->LIVE timeout
100
+ if (this.streamTypeLive) {
101
+ debug(`[${this.sessionId}]: [ Ending LIVE Abruptly, Going to -> V2L ]`);
102
+ this.streamTypeLive = false;
103
+ }
104
+ debug(`[${this.sessionId}]: StreamSwitcher reacting to Full Store Reset`);
105
+ return false; // Go to V2L feed
106
+ }
107
+
69
108
  // Filter out schedule objects from the past
70
- const tsNow = Date.now();
109
+ const tsNow = Date.now();
71
110
  const strmSchedule = await this.streamSwitchManager.getSchedule(this.sessionId);
72
- const schedule = strmSchedule.filter((obj) => obj.end_time > tsNow);
111
+ const schedule = strmSchedule.filter((obj) => obj.end_time > tsNow);
73
112
  if (schedule.length === 0 && this.streamTypeLive) {
74
113
  status = await this._initSwitching(
75
114
  SwitcherState.LIVE_TO_V2L,
@@ -102,47 +141,54 @@ class StreamSwitcher {
102
141
  let tries = 0;
103
142
  let validURI = false;
104
143
  while (!validURI && tries < MAX_FAILS) {
105
- debug(`[${this.sessionId}]: Switcher is validating Master URI... (tries left=${MAX_FAILS - tries})`);
144
+ debug(
145
+ `[${
146
+ this.sessionId
147
+ }]: Switcher is validating Master URI... (tries left=${
148
+ MAX_FAILS - tries
149
+ })`
150
+ );
106
151
  validURI = await this._validURI(scheduleObj.uri);
107
152
  tries++;
108
153
  }
109
154
  if (!validURI) {
110
155
  debug(`[${this.sessionId}]: Unreachable URI: [${scheduleObj.uri}]`);
111
156
  if (this.streamTypeLive) {
112
- debug(`[${this.sessionId}]: Abort Live Stream! Switching back to VOD2Live due to unreachable URI`);
113
- this.abortTimeStamp = Date.now();
114
- status = await this._initSwitching(
115
- SwitcherState.LIVE_TO_V2L,
157
+ await this.abortLiveFeed(
116
158
  session,
117
159
  sessionLive,
118
- null
160
+ "Switching back to VOD2Live due to unreachable URI"
119
161
  );
120
- return status;
121
162
  }
122
163
  return false;
123
164
  }
124
165
  debug(`[${this.sessionId}]: ....Master URI -> VALID`);
125
166
 
126
-
127
- if (this.abortTimeStamp && (tsNow - this.abortTimeStamp) <= 10000) {
167
+ if (this.abortTimeStamp && tsNow - this.abortTimeStamp <= 10000) {
128
168
  // If we have a valid URI and no more than 10 seconds have passed since switching from Live->V2L.
129
169
  // Stay on V2L to give live sessionLive some time to prepare before switching back to live.
130
- debug(`[${this.sessionId}]: Waiting [${10000 - (tsNow - this.abortTimeStamp)}ms] before switching back to Live due to unreachable URI`);
170
+ debug(
171
+ `[${this.sessionId}]: Waiting [${
172
+ 10000 - (tsNow - this.abortTimeStamp)
173
+ }ms] before switching back to Live due to unreachable URI`
174
+ );
131
175
  return false;
132
- }
176
+ }
133
177
  this.abortTimeStamp = null;
134
178
 
135
179
  if (this.streamTypeLive) {
136
- if (tsNow >= scheduleObj.start_time && this.eventId !== scheduleObj.eventId) {
180
+ if (
181
+ tsNow >= scheduleObj.start_time &&
182
+ this.eventId !== scheduleObj.eventId
183
+ ) {
137
184
  if (scheduleObj.type === StreamType.LIVE) {
138
- status = await this._initSwitching(
185
+ status = await this._initSwitching(
139
186
  SwitcherState.LIVE_TO_LIVE,
140
187
  session,
141
188
  sessionLive,
142
189
  scheduleObj
143
190
  );
144
191
  return status;
145
-
146
192
  }
147
193
  status = await this._initSwitching(
148
194
  SwitcherState.LIVE_TO_VOD,
@@ -153,7 +199,11 @@ class StreamSwitcher {
153
199
  return status;
154
200
  }
155
201
  }
156
- if (tsNow >= scheduleObj.start_time && tsNow < scheduleObj.end_time && (scheduleObj.end_time - tsNow) > 10000) {
202
+ if (
203
+ tsNow >= scheduleObj.start_time &&
204
+ tsNow < scheduleObj.end_time &&
205
+ scheduleObj.end_time - tsNow > 10000
206
+ ) {
157
207
  if (scheduleObj.type === StreamType.LIVE) {
158
208
  if (!this.streamTypeLive) {
159
209
  status = await this._initSwitching(
@@ -168,7 +218,9 @@ class StreamSwitcher {
168
218
  }
169
219
  if (!this.streamTypeLive) {
170
220
  if (!scheduleObj.duration) {
171
- debug(`[${this.sessionId}]: Cannot switch VOD no duration specified for schedule item: [${scheduleObj.assetId}]`);
221
+ debug(
222
+ `[${this.sessionId}]: Cannot switch VOD no duration specified for schedule item: [${scheduleObj.assetId}]`
223
+ );
172
224
  return false;
173
225
  }
174
226
  if (this.eventId !== scheduleObj.eventId) {
@@ -198,133 +250,205 @@ class StreamSwitcher {
198
250
 
199
251
  switch (state) {
200
252
  case SwitcherState.V2L_TO_LIVE:
201
- debug(`[${this.sessionId}]: [ INIT Switching from V2L->LIVE ]`);
202
- this.eventId = scheduleObj.eventId;
203
- currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
204
- currVodSegments = await session.getCurrentMediaSequenceSegments();
205
-
206
- // In risk that the SL-playhead might have updated some data after
207
- // we reset last time... we should Reset SessionLive before sending new data.
208
- await sessionLive.resetLiveStoreAsync(0);
209
- await sessionLive.setCurrentMediaAndDiscSequenceCount(currVodCounts.mediaSeq, currVodCounts.discSeq);
210
- await sessionLive.setCurrentMediaSequenceSegments(currVodSegments);
211
- liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
212
-
213
- if (!liveUri) {
214
- debug(`[${this.sessionId}]: [ ERROR Switching from V2L->LIVE ]`);
215
- this.working = false;
216
- this.eventId = null;
217
- return false;
218
- }
253
+ try {
254
+ debug(`[${this.sessionId}]: [ INIT Switching from V2L->LIVE ]`);
255
+ this.eventId = scheduleObj.eventId;
256
+ currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
257
+ currVodSegments = await session.getCurrentMediaSequenceSegments();
258
+
259
+ // In risk that the SL-playhead might have updated some data after
260
+ // we reset last time... we should Reset SessionLive before sending new data.
261
+ await sessionLive.resetLiveStoreAsync(0);
262
+ await sessionLive.setCurrentMediaAndDiscSequenceCount(
263
+ currVodCounts.mediaSeq,
264
+ currVodCounts.discSeq
265
+ );
266
+ await sessionLive.setCurrentMediaSequenceSegments(currVodSegments);
267
+ liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
219
268
 
220
- this.working = false;
221
- this.streamTypeLive = true;
222
- debug(`[${this.sessionId}]: [ Switched from V2L->LIVE ]`);
223
- return true;
224
- case SwitcherState.V2L_TO_VOD:
225
- debug(`[${this.sessionId}]: [ INIT Switching from V2L->VOD ]`);
226
- this.eventId = scheduleObj.eventId;
227
- currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
228
- eventSegments = await session.getTruncatedVodSegments(scheduleObj.uri, scheduleObj.duration / 1000);
269
+ if (!liveUri) {
270
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->LIVE ]`);
271
+ this.working = false;
272
+ this.eventId = null;
273
+ return false;
274
+ }
229
275
 
230
- if (!eventSegments) {
231
- debug(`[${this.sessionId}]: [ ERROR Switching from V2L->VOD ]`);
276
+ this.working = false;
277
+ this.streamTypeLive = true;
278
+ debug(`[${this.sessionId}]: [ Switched from V2L->LIVE ]`);
279
+ return true;
280
+ } catch (err) {
281
+ this.streamTypeLive = false;
232
282
  this.working = false;
233
283
  this.eventId = null;
234
- return false;
284
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->LIVE ]`);
285
+ throw new Error(err);
235
286
  }
236
287
 
237
- await session.setCurrentMediaAndDiscSequenceCount(currVodCounts.mediaSeq, currVodCounts.discSeq);
238
- await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
288
+ case SwitcherState.V2L_TO_VOD:
289
+ try {
290
+ debug(`[${this.sessionId}]: [ INIT Switching from V2L->VOD ]`);
291
+ this.eventId = scheduleObj.eventId;
292
+ currVodCounts = await session.getCurrentMediaAndDiscSequenceCount();
293
+ eventSegments = await session.getTruncatedVodSegments(
294
+ scheduleObj.uri,
295
+ scheduleObj.duration / 1000
296
+ );
239
297
 
240
- this.working = false;
241
- debug(`[${this.sessionId}]: [ Switched from V2L->VOD ]`);
242
- return false;
243
- case SwitcherState.LIVE_TO_V2L:
244
- debug(`[${this.sessionId}]: [ INIT Switching from LIVE->V2L ]`);
245
- this.eventId = null;
246
- liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
247
- liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
298
+ if (!eventSegments) {
299
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->VOD ]`);
300
+ this.working = false;
301
+ this.eventId = null;
302
+ return false;
303
+ }
248
304
 
249
- await sessionLive.resetSession();
250
- sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
305
+ await session.setCurrentMediaAndDiscSequenceCount(
306
+ currVodCounts.mediaSeq,
307
+ currVodCounts.discSeq
308
+ );
309
+ await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
251
310
 
252
- if (scheduleObj && !scheduleObj.duration) {
253
- debug(`[${this.sessionId}]: Cannot switch VOD. No duration specified for schedule item: [${scheduleObj.assetId}]`);
311
+ this.working = false;
312
+ debug(`[${this.sessionId}]: [ Switched from V2L->VOD ]`);
313
+ return false;
314
+ } catch (err) {
315
+ this.streamTypeLive = false;
316
+ this.working = false;
317
+ this.eventId = null;
318
+ debug(`[${this.sessionId}]: [ ERROR Switching from V2L->VOD ]`);
319
+ throw new Error(err);
254
320
  }
321
+ case SwitcherState.LIVE_TO_V2L:
322
+ try {
323
+ debug(`[${this.sessionId}]: [ INIT Switching from LIVE->V2L ]`);
324
+ this.eventId = null;
325
+ liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
326
+ liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
327
+
328
+ await sessionLive.resetSession();
329
+ sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
330
+
331
+ if (scheduleObj && !scheduleObj.duration) {
332
+ debug(
333
+ `[${this.sessionId}]: Cannot switch VOD. No duration specified for schedule item: [${scheduleObj.assetId}]`
334
+ );
335
+ }
336
+
337
+ if (this._isEmpty(liveSegments.currMseqSegs)) {
338
+ this.working = false;
339
+ this.streamTypeLive = false;
340
+ debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
341
+ return false;
342
+ }
343
+
344
+ await session.setCurrentMediaSequenceSegments(
345
+ liveSegments.currMseqSegs,
346
+ liveSegments.segCount
347
+ );
348
+ await session.setCurrentMediaAndDiscSequenceCount(
349
+ liveCounts.mediaSeq,
350
+ liveCounts.discSeq
351
+ );
255
352
 
256
- if (this._isEmpty(liveSegments.currMseqSegs)) {
257
353
  this.working = false;
258
354
  this.streamTypeLive = false;
259
355
  debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
260
- return false;;
356
+ return false;
357
+ } catch (err) {
358
+ this.streamTypeLive = false;
359
+ this.working = false;
360
+ this.eventId = null;
361
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->V2L ]`);
362
+ throw new Error(err);
261
363
  }
262
-
263
- await session.setCurrentMediaSequenceSegments(liveSegments.currMseqSegs, liveSegments.segCount);
264
- await session.setCurrentMediaAndDiscSequenceCount(liveCounts.mediaSeq, liveCounts.discSeq);
265
-
266
-
267
- this.working = false;
268
- this.streamTypeLive = false;
269
- debug(`[${this.sessionId}]: [ Switched from LIVE->V2L ]`);
270
- return false;
271
364
  case SwitcherState.LIVE_TO_VOD:
272
- debug(`[${this.sessionId}]: INIT Switching from LIVE->VOD`);
273
- // TODO: Not yet fully tested/supported
274
- this.eventId = scheduleObj.eventId;
275
- liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
276
- liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
277
- await sessionLive.resetSession();
278
- sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
279
-
280
- eventSegments = await session.getTruncatedVodSegments(scheduleObj.uri, scheduleObj.duration / 1000);
281
- if (!eventSegments) {
282
- debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->VOD ]`);
365
+ try {
366
+ debug(`[${this.sessionId}]: INIT Switching from LIVE->VOD`);
367
+ // TODO: Not yet fully tested/supported
368
+ this.eventId = scheduleObj.eventId;
369
+ liveSegments = await sessionLive.getCurrentMediaSequenceSegments();
370
+ liveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
371
+ await sessionLive.resetSession();
372
+ sessionLive.resetLiveStoreAsync(RESET_DELAY); // In parallel
373
+
374
+ eventSegments = await session.getTruncatedVodSegments(
375
+ scheduleObj.uri,
376
+ scheduleObj.duration / 1000
377
+ );
378
+ if (!eventSegments) {
379
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->VOD ]`);
380
+ this.streamTypeLive = false;
381
+ this.working = false;
382
+ this.eventId = null;
383
+ return false;
384
+ }
385
+
386
+ await session.setCurrentMediaAndDiscSequenceCount(
387
+ liveCounts.mediaSeq - 1,
388
+ liveCounts.discSeq - 1
389
+ );
390
+ await session.setCurrentMediaSequenceSegments(
391
+ liveSegments.currMseqSegs,
392
+ liveSegments.segCount
393
+ );
394
+ await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
395
+
396
+ this.working = false;
397
+ this.streamTypeLive = false;
398
+ debug(`[${this.sessionId}]: Switched from LIVE->VOD`);
399
+ return false;
400
+ } catch (err) {
283
401
  this.streamTypeLive = false;
284
402
  this.working = false;
285
403
  this.eventId = null;
286
- return false;
404
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->VOD ]`);
405
+ throw new Error(err);
287
406
  }
288
-
289
- await session.setCurrentMediaAndDiscSequenceCount(liveCounts.mediaSeq - 1, liveCounts.discSeq - 1);
290
- await session.setCurrentMediaSequenceSegments(liveSegments.currMseqSegs, liveSegments.segCount);
291
- await session.setCurrentMediaSequenceSegments(eventSegments, 0, true);
292
-
293
- this.working = false;
294
- this.streamTypeLive = false;
295
- debug(`[${this.sessionId}]: Switched from LIVE->VOD`);
296
- return false;
297
407
  case SwitcherState.LIVE_TO_LIVE:
298
- debug(`[${this.sessionId}]: INIT Switching from LIVE->LIVE`);
299
- // TODO: Not yet fully tested/supported
300
- this.eventId = scheduleObj.eventId;
301
- eventSegments = await sessionLive.getCurrentMediaSequenceSegments();
302
- currLiveCounts = await sessionLive.getCurrentMediaAndDiscSequenceCount();
303
-
304
- await sessionLive.resetSession();
305
- await sessionLive.resetLiveStoreAsync(0);
408
+ try {
409
+ debug(`[${this.sessionId}]: INIT Switching from LIVE->LIVE`);
410
+ // TODO: Not yet fully tested/supported
411
+ this.eventId = scheduleObj.eventId;
412
+ eventSegments = await sessionLive.getCurrentMediaSequenceSegments();
413
+ currLiveCounts =
414
+ await sessionLive.getCurrentMediaAndDiscSequenceCount();
415
+
416
+ await sessionLive.resetSession();
417
+ await sessionLive.resetLiveStoreAsync(0);
418
+
419
+ await sessionLive.setCurrentMediaAndDiscSequenceCount(
420
+ currLiveCounts.mediaSeq,
421
+ currLiveCounts.discSeq
422
+ );
423
+ await sessionLive.setCurrentMediaSequenceSegments(
424
+ eventSegments.currMseqSegs
425
+ );
426
+ liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
306
427
 
307
- await sessionLive.setCurrentMediaAndDiscSequenceCount(currLiveCounts.mediaSeq, currLiveCounts.discSeq);
308
- await sessionLive.setCurrentMediaSequenceSegments(eventSegments.currMseqSegs);
309
- liveUri = await sessionLive.setLiveUri(scheduleObj.uri);
428
+ if (!liveUri) {
429
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->LIVE ]`);
430
+ this.streamTypeLive = false;
431
+ this.working = false;
432
+ this.eventId = null;
433
+ return false;
434
+ }
310
435
 
311
- if (!liveUri) {
312
- debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->LIVE ]`);
436
+ this.working = false;
437
+ debug(`[${this.sessionId}]: Switched from LIVE->LIVE`);
438
+ return true;
439
+ } catch (err) {
313
440
  this.streamTypeLive = false;
314
441
  this.working = false;
315
442
  this.eventId = null;
316
- return false;
443
+ debug(`[${this.sessionId}]: [ ERROR Switching from LIVE->LIVE ]`);
444
+ throw new Error(err);
317
445
  }
318
-
319
- this.working = false;
320
- debug(`[${this.sessionId}]: Switched from LIVE->LIVE`);
321
- return true;
322
446
  default:
323
447
  debug(`[${this.sessionId}]: SwitcherState [${state}] not implemented`);
324
448
  this.streamTypeLive = false;
325
449
  this.working = false;
326
450
  this.eventId = null;
327
- return false;;
451
+ return false;
328
452
  }
329
453
  }
330
454
 
@@ -352,10 +476,14 @@ class StreamSwitcher {
352
476
  if (online.status >= 200 && online.status < 300) {
353
477
  return true;
354
478
  }
355
- debug(`[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! Returned Status Code: ${online.status}`);
479
+ debug(
480
+ `[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! Returned Status Code: ${online.status}`
481
+ );
356
482
  return false;
357
483
  } catch (err) {
358
- debug(`[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! ${err}`);
484
+ debug(
485
+ `[${this.sessionId}]: Failed to validate URI: ${uri}\nERROR! ${err}`
486
+ );
359
487
  return false;
360
488
  } finally {
361
489
  clearTimeout(timeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.0.1",
3
+ "version": "3.0.5",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -100,4 +100,4 @@ const engineOptions = {
100
100
 
101
101
  const engine = new ChannelEngine(refAssetManager, engineOptions);
102
102
  engine.start();
103
- engine.listen(process.env.PORT || 8000);
103
+ engine.listen(process.env.port || 8000);