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.
@@ -0,0 +1,195 @@
1
+ /*
2
+ * Reference implementation of Channel Engine library
3
+ */
4
+
5
+ const ChannelEngine = require("./index.js");
6
+ const { v4: uuidv4 } = require('uuid');
7
+
8
+ const STITCH_ENDPOINT = process.env.STITCH_ENDPOINT || "http://lambda.eyevinn.technology/stitch/master.m3u8";
9
+ class RefAssetManager {
10
+ constructor(opts) {
11
+ if (process.env.TEST_CHANNELS) {
12
+ this.assets = {};
13
+ this.pos = {};
14
+
15
+ const testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
16
+ for (let i = 0; i < testChannelsCount; i++) {
17
+ const channelId = `${i + 1}`;
18
+ this.assets[channelId] = [
19
+ { id: 1, title: "Tears of Steel", uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8" },
20
+ { id: 2, title: "Unhinged Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/UNHINGED_Trailer_2020.mp4/master.m3u8" },
21
+ { id: 3, title: "Morbius Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/MORBIUS_Trailer_2020.mp4/master.m3u8" },
22
+ { id: 4, title: "TV Plus Joachim", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-joachim.mov/master.m3u8" },
23
+ { id: 5, title: "The Outpost Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/THE_OUTPOST_Trailer_2020.mp4/master.m3u8" },
24
+ { id: 6, title: "TV Plus Megha", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-megha.mov/master.m3u8" },
25
+ ];
26
+ this.pos[channelId] = 2;
27
+ }
28
+ } else {
29
+ this.assets = {
30
+ '1': [
31
+ { id: 1, title: "Tears of Steel", uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8" },
32
+ { id: 2, title: "Morbius Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/MORBIUS_Trailer_2020.mp4/master.m3u8" },
33
+ { id: 3, title: "The Outpost Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/THE_OUTPOST_Trailer_2020.mp4/master.m3u8" },
34
+ { id: 4, title: "Unhinged Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/UNHINGED_Trailer_2020.mp4/master.m3u8" },
35
+ { id: 5, title: "TV Plus Megha", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-megha.mov/master.m3u8" },
36
+ { id: 6, title: "TV Plus Joachim", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-joachim.mov/master.m3u8" },
37
+ ]
38
+ };
39
+ this.pos = {
40
+ '1': 1
41
+ };
42
+ }
43
+ }
44
+
45
+ /* @param {Object} vodRequest
46
+ * {
47
+ * sessionId,
48
+ * category,
49
+ * playlistId
50
+ * }
51
+ */
52
+ getNextVod(vodRequest) {
53
+ return new Promise((resolve, reject) => {
54
+ const channelId = vodRequest.playlistId;
55
+ if (this.assets[channelId]) {
56
+ let vod = this.assets[channelId][this.pos[channelId]++];
57
+ if (this.pos[channelId] > this.assets[channelId].length - 1) {
58
+ this.pos[channelId] = 0;
59
+ }
60
+ const payload = {
61
+ uri: vod.uri,
62
+ breaks: [
63
+ {
64
+ pos: 0,
65
+ duration: 15 * 1000,
66
+ url: "https://maitv-vod.lab.eyevinn.technology/ads/apotea-15s.mp4/master.m3u8"
67
+ }
68
+ ]
69
+ };
70
+ const buff = Buffer.from(JSON.stringify(payload));
71
+ const encodedPayload = buff.toString("base64");
72
+ const vodResponse = {
73
+ id: vod.id,
74
+ title: vod.title,
75
+ uri: STITCH_ENDPOINT + "?payload=" + encodedPayload
76
+ };
77
+ resolve(vodResponse);
78
+ } else {
79
+ reject("Invalid channelId provided");
80
+ }
81
+ });
82
+ }
83
+
84
+ handleError(err, vodResponse) {
85
+ console.error(err.message);
86
+ }
87
+ }
88
+
89
+ class RefChannelManager {
90
+ constructor(opts) {
91
+ this.channels = [];
92
+ if (process.env.TEST_CHANNELS) {
93
+ const testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
94
+ for (let i = 0; i < testChannelsCount; i++) {
95
+ this.channels.push({ id: `${i + 1}`, profile: this._getProfile() });
96
+ }
97
+ } else {
98
+ this.channels = [{ id: "1", profile: this._getProfile() }];
99
+ }
100
+ }
101
+
102
+ getChannels() {
103
+ return this.channels;
104
+ }
105
+
106
+ _getProfile() {
107
+ return [
108
+ { bw: 8242000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [1024, 458] },
109
+ { bw: 1274000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [640, 286] },
110
+ { bw: 742000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [480, 214] },
111
+ ]
112
+ }
113
+ }
114
+ const StreamType = Object.freeze({
115
+ LIVE: 1,
116
+ VOD: 2,
117
+ });
118
+
119
+ class StreamSwitchManager {
120
+ constructor() {
121
+ this.schedule = {};
122
+ if (process.env.TEST_CHANNELS) {
123
+ const testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
124
+ for (let i = 0; i < testChannelsCount; i++) {
125
+ const channelId = `${i + 1}`;
126
+ this.schedule[channelId] = [];
127
+ }
128
+ } else {
129
+ this.schedule = {
130
+ '1': []
131
+ };
132
+ }
133
+ }
134
+
135
+ generateID() {
136
+ return uuidv4();
137
+ }
138
+
139
+ getSchedule(channelId) {
140
+ return new Promise((resolve, reject) => {
141
+ if (this.schedule[channelId]) {
142
+ const tsNow = Date.now();
143
+ const streamDuration = 60 * 1000;
144
+ const startOffset = tsNow + streamDuration;
145
+ const endTime = startOffset + streamDuration;
146
+ // Break in with live and scheduled VOD content after 60 seconds of VOD2Live the first time Channel Engine starts
147
+ // Required: "assetId", "start_time", "end_time", "uri", "duration"
148
+ // "duration" is only required for StreamType.VOD
149
+ this.schedule[channelId] = this.schedule[channelId].filter((obj) => obj.end_time >= tsNow);
150
+ if (this.schedule[channelId].length === 0) {
151
+ this.schedule[channelId].push({
152
+ eventId: this.generateID(),
153
+ assetId: this.generateID(),
154
+ title: "Live stream test",
155
+ type: StreamType.LIVE,
156
+ start_time: startOffset,
157
+ end_time: endTime,
158
+ uri: "https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8",
159
+ },
160
+ {
161
+ eventId: this.generateID(),
162
+ assetId: this.generateID(),
163
+ title: "Scheduled VOD test",
164
+ type: StreamType.VOD,
165
+ start_time: (endTime + 100*1000),
166
+ end_time: (endTime + 100*1000) + streamDuration,
167
+ uri: "https://maitv-vod.lab.eyevinn.technology/COME_TO_DADDY_Trailer_2020.mp4/master.m3u8",
168
+ duration: streamDuration,
169
+ });
170
+ }
171
+ resolve(this.schedule[channelId]);
172
+ } else {
173
+ reject("Invalid channelId provided");
174
+ }
175
+ });
176
+ }
177
+ }
178
+
179
+ const refAssetManager = new RefAssetManager();
180
+ const refChannelManager = new RefChannelManager();
181
+ const refStreamSwitchManager = new StreamSwitchManager();
182
+
183
+ const engineOptions = {
184
+ heartbeat: "/",
185
+ averageSegmentDuration: 2000,
186
+ channelManager: refChannelManager,
187
+ streamSwitchManager: refStreamSwitchManager,
188
+ defaultSlateUri: "https://maitv-vod.lab.eyevinn.technology/slate-consuo.mp4/master.m3u8",
189
+ slateRepetitions: 10,
190
+ redisUrl: process.env.REDIS_URL,
191
+ };
192
+
193
+ const engine = new ChannelEngine(refAssetManager, engineOptions);
194
+ engine.start();
195
+ engine.listen(process.env.PORT || 8000);
package/server.js CHANGED
@@ -3,46 +3,34 @@
3
3
  */
4
4
 
5
5
  const ChannelEngine = require("./index.js");
6
- const { v4: uuidv4 } = require('uuid');
7
6
 
8
- const STITCH_ENDPOINT = process.env.STITCH_ENDPOINT || "http://lambda.eyevinn.technology/stitch/master.m3u8";
7
+ const STITCH_ENDPOINT =
8
+ process.env.STITCH_ENDPOINT ||
9
+ "http://lambda.eyevinn.technology/stitch/master.m3u8";
9
10
  class RefAssetManager {
10
11
  constructor(opts) {
11
- if (process.env.TEST_CHANNELS) {
12
- this.assets = {};
13
- this.pos = {};
14
-
15
- const testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
16
- for (let i = 0; i < testChannelsCount; i++) {
17
- const channelId = `${i + 1}`;
18
- this.assets[channelId] = [
19
- { id: 1, title: "Tears of Steel", uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8" },
20
- { id: 2, title: "Unhinged Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/UNHINGED_Trailer_2020.mp4/master.m3u8" },
21
- { id: 3, title: "Morbius Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/MORBIUS_Trailer_2020.mp4/master.m3u8" },
22
- { id: 4, title: "TV Plus Joachim", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-joachim.mov/master.m3u8" },
23
- { id: 5, title: "The Outpost Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/THE_OUTPOST_Trailer_2020.mp4/master.m3u8" },
24
- { id: 6, title: "TV Plus Megha", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-megha.mov/master.m3u8" },
25
- ];
26
- this.pos[channelId] = 2;
27
- }
28
- } else {
29
- this.assets = {
30
- '1': [
31
- { id: 1, title: "Tears of Steel", uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8" },
32
- { id: 2, title: "Morbius Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/MORBIUS_Trailer_2020.mp4/master.m3u8" },
33
- { id: 3, title: "The Outpost Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/THE_OUTPOST_Trailer_2020.mp4/master.m3u8" },
34
- { id: 4, title: "Unhinged Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/UNHINGED_Trailer_2020.mp4/master.m3u8" },
35
- { id: 5, title: "TV Plus Megha", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-megha.mov/master.m3u8" },
36
- { id: 6, title: "TV Plus Joachim", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-joachim.mov/master.m3u8" },
37
- ]
38
- };
39
- this.pos = {
40
- '1': 1
41
- };
42
- }
12
+ this.assets = {
13
+ 1: [
14
+ {
15
+ id: 1,
16
+ title: "Tears of Steel",
17
+ uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8",
18
+ },
19
+ {
20
+ id: 2,
21
+ title: "VINN",
22
+ uri: "https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8",
23
+ },
24
+ ],
25
+ };
26
+ this.pos = {
27
+ 1: 1,
28
+ };
43
29
  }
44
30
 
45
- /* @param {Object} vodRequest
31
+ /**
32
+ *
33
+ * @param {Object} vodRequest
46
34
  * {
47
35
  * sessionId,
48
36
  * category,
@@ -63,16 +51,16 @@ class RefAssetManager {
63
51
  {
64
52
  pos: 100,
65
53
  duration: 15 * 1000,
66
- url: "https://maitv-vod.lab.eyevinn.technology/ads/6cd7d768_e214_4ebc_9f14_7ed89710115e_mp4/master.m3u8"
67
- }
68
- ]
54
+ url: "https://maitv-vod.lab.eyevinn.technology/ads/6cd7d768_e214_4ebc_9f14_7ed89710115e_mp4/master.m3u8",
55
+ },
56
+ ],
69
57
  };
70
58
  const buff = Buffer.from(JSON.stringify(payload));
71
59
  const encodedPayload = buff.toString("base64");
72
60
  const vodResponse = {
73
61
  id: vod.id,
74
62
  title: vod.title,
75
- uri: STITCH_ENDPOINT + "?payload=" + encodedPayload
63
+ uri: STITCH_ENDPOINT + "?payload=" + encodedPayload,
76
64
  };
77
65
  resolve(vodResponse);
78
66
  } else {
@@ -80,116 +68,36 @@ class RefAssetManager {
80
68
  }
81
69
  });
82
70
  }
83
-
84
- handleError(err, vodResponse) {
85
- console.error(err.message);
86
- }
87
71
  }
88
72
 
89
73
  class RefChannelManager {
90
- constructor(opts) {
91
- this.channels = [];
92
- if (process.env.TEST_CHANNELS) {
93
- const testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
94
- for (let i = 0; i < testChannelsCount; i++) {
95
- this.channels.push({ id: `${i + 1}`, profile: this._getProfile() });
96
- }
97
- } else {
98
- this.channels = [{ id: "1", profile: this._getProfile() }];
99
- }
100
- }
101
-
102
74
  getChannels() {
103
- return this.channels;
75
+ //return [ { id: '1', profile: this._getProfile() }, { id: 'faulty', profile: this._getProfile() } ];
76
+ return [{ id: "1", profile: this._getProfile() }];
104
77
  }
105
78
 
106
79
  _getProfile() {
107
80
  return [
108
- { bw: 8242000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [1024, 458] },
109
- { bw: 1274000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [640, 286] },
110
- { bw: 742000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [480, 214] },
111
- ]
112
- }
113
- }
114
- const StreamType = Object.freeze({
115
- LIVE: 1,
116
- VOD: 2,
117
- });
118
-
119
- class StreamSwitchManager {
120
- constructor() {
121
- this.schedule = {};
122
- if (process.env.TEST_CHANNELS) {
123
- const testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
124
- for (let i = 0; i < testChannelsCount; i++) {
125
- const channelId = `${i + 1}`;
126
- this.schedule[channelId] = [];
127
- }
128
- } else {
129
- this.schedule = {
130
- '1': []
131
- };
132
- }
133
- }
134
-
135
- generateID() {
136
- return uuidv4();
137
- }
138
-
139
- getSchedule(channelId) {
140
- return new Promise((resolve, reject) => {
141
- if (this.schedule[channelId]) {
142
- const tsNow = Date.now();
143
- const streamDuration = 60 * 1000;
144
- const startOffset = tsNow + streamDuration;
145
- const endTime = startOffset + streamDuration;
146
- // Break in with live and scheduled VOD content after 60 seconds of VOD2Live the first time Channel Engine starts
147
- // Required: "assetId", "start_time", "end_time", "uri", "duration"
148
- // "duration" is only required for StreamType.VOD
149
- this.schedule[channelId] = this.schedule[channelId].filter((obj) => obj.end_time >= tsNow);
150
- if (this.schedule[channelId].length === 0) {
151
- this.schedule[channelId].push({
152
- eventId: this.generateID(),
153
- assetId: this.generateID(),
154
- title: "Live stream test",
155
- type: StreamType.LIVE,
156
- start_time: startOffset,
157
- end_time: endTime,
158
- uri: "https://test2.frankspeech.com/live/Test2/playlist.m3u8",
159
- },
160
- {
161
- eventId: this.generateID(),
162
- assetId: this.generateID(),
163
- title: "Scheduled VOD test",
164
- type: StreamType.VOD,
165
- start_time: (endTime + 100*1000),
166
- end_time: (endTime + 100*1000) + streamDuration,
167
- uri: "https://maitv-vod.lab.eyevinn.technology/COME_TO_DADDY_Trailer_2020.mp4/master.m3u8",
168
- duration: streamDuration,
169
- });
170
- }
171
- resolve(this.schedule[channelId]);
172
- } else {
173
- reject("Invalid channelId provided");
174
- }
175
- });
81
+ { bw: 6134000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [1024, 458] },
82
+ { bw: 2323000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [640, 286] },
83
+ { bw: 1313000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [480, 214] },
84
+ ];
176
85
  }
177
86
  }
178
87
 
179
88
  const refAssetManager = new RefAssetManager();
180
89
  const refChannelManager = new RefChannelManager();
181
- const refStreamSwitchManager = new StreamSwitchManager();
182
90
 
183
91
  const engineOptions = {
184
92
  heartbeat: "/",
185
93
  averageSegmentDuration: 2000,
186
94
  channelManager: refChannelManager,
187
- streamSwitchManager: refStreamSwitchManager,
188
- defaultSlateUri: "https://maitv-vod.lab.eyevinn.technology/slate-consuo.mp4/master.m3u8",
95
+ defaultSlateUri:
96
+ "https://maitv-vod.lab.eyevinn.technology/slate-consuo.mp4/master.m3u8",
189
97
  slateRepetitions: 10,
190
98
  redisUrl: process.env.REDIS_URL,
191
99
  };
192
100
 
193
101
  const engine = new ChannelEngine(refAssetManager, engineOptions);
194
102
  engine.start();
195
- engine.listen(process.env.PORT || 8000);
103
+ engine.listen(process.env.port || 8000);
@@ -353,7 +353,7 @@ describe("The initialize switching", () => {
353
353
  discSeq: 0,
354
354
  });
355
355
  expect(Object.keys(tSegments)).toEqual(Object.keys(currVodSegments));
356
- currVodSegments[1313000].push({ discontinuity: true });
356
+ currVodSegments[1313000].push({ discontinuity: true, cue: { in: true } });
357
357
  expect(tSegments[1313000]).toEqual(currVodSegments[1313000]);
358
358
  nock.cleanAll();
359
359
  }, 10000);
@@ -407,7 +407,7 @@ describe("The initialize switching", () => {
407
407
  uri: "http://mock.mock.com/180000/seg10.ts",
408
408
  });
409
409
  expect(sessionCurrentSegs["1313000"][size - 1 - 1]).toEqual({
410
- discontinuity: true,
410
+ discontinuity: true, cue: { in: true }
411
411
  });
412
412
  expect(sessionCurrentSegs["1313000"][size - 1]).toEqual({
413
413
  duration: 7.5,
@@ -462,6 +462,7 @@ describe("The initialize switching", () => {
462
462
  });
463
463
  expect(sessionCurrentSegs["1313000"][size - 1 - 1]).toEqual({
464
464
  discontinuity: true,
465
+ cue: { in: true }
465
466
  });
466
467
  expect(
467
468
  sessionCurrentSegs["1313000"][size - 1].daterange["planned-duration"]
@@ -781,6 +782,7 @@ describe("The initialize switching", () => {
781
782
  });
782
783
  expect(newVodSegments["1313000"][size - 1 - 1 - 1]).toEqual({
783
784
  discontinuity: true,
785
+ cue: { in: true }
784
786
  });
785
787
  expect(
786
788
  newVodSegments["1313000"][size - 1 - 1].daterange["planned-duration"]