eyevinn-channel-engine 4.3.3-rc.4 → 4.3.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.
- package/README.md +3 -2
- package/dist/engine/redis_state_store.js +19 -2
- package/dist/package.json +1 -1
- package/docs/demosite.png +0 -0
- package/engine/redis_state_store.js +24 -0
- package/ha-dev/README.md +25 -0
- package/package.json +1 -1
- package/dist/server-demux.d.ts +0 -1
- package/dist/server-demux.js +0 -107
- package/dist/server-drm.d.ts +0 -1
- package/dist/server-drm.js +0 -114
- package/dist/server-livemix.d.ts +0 -1
- package/dist/server-livemix.js +0 -196
- package/dist/server-multicodec.d.ts +0 -1
- package/dist/server-multicodec.js +0 -96
- package/dist/server.d.ts +0 -1
- package/dist/server.js +0 -101
- package/validation_data.json +0 -2139
- /package/{Dockerfile.nginxlb → ha-dev/Dockerfile.nginxlb} +0 -0
- /package/{docker-compose-lb.yml → ha-dev/docker-compose-lb.yml} +0 -0
- /package/{nginx-proxy-lb.conf → ha-dev/nginx-proxy-lb.conf} +0 -0
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ A live demo and example is available here: [https://vod2live.eyevinn.technology]
|
|
|
35
35
|
- Mix VOD2Live channel with a "real" live HLS stream
|
|
36
36
|
- Develop adapters to plugin with custom scheduling endpoints
|
|
37
37
|
- High Availability with Redis Cache as shared state store
|
|
38
|
+
- Support for WebVTT subtitles
|
|
38
39
|
- Support for DRM encrypted HLS VODs (in beta)
|
|
39
40
|
- And much more!
|
|
40
41
|
|
|
@@ -52,8 +53,8 @@ Supported Node.js Versions
|
|
|
52
53
|
|
|
53
54
|
| HLS Format | Muxed | Demuxed | Mix w. Live | Subtitles | DRM |
|
|
54
55
|
| ---------- | ----- | ------- | ----------- | --------- | --- |
|
|
55
|
-
| HLS + TS | Yes | Yes | Yes* |
|
|
56
|
-
| HLS + CMAF | No | Yes | TBD |
|
|
56
|
+
| HLS + TS | Yes | Yes | Yes* | Yes | No |
|
|
57
|
+
| HLS + CMAF | No | Yes | TBD | Yes | TBD |
|
|
57
58
|
|
|
58
59
|
*\* not supported with demuxed sources*
|
|
59
60
|
|
|
@@ -36,7 +36,13 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
36
36
|
};
|
|
37
37
|
var Redis = require("ioredis");
|
|
38
38
|
var debug = require("debug")("redis-state-store");
|
|
39
|
+
var cloudWatchLog = require("./util.js").cloudWatchLog;
|
|
39
40
|
var DEFAULT_VOLATILE_KEY_TTL = 5; // Timeout so it should not expire within one normal increment iteration (in seconds)
|
|
41
|
+
function isTrue(s) {
|
|
42
|
+
var regex = /^\s*(true|1)\s*$/i;
|
|
43
|
+
return regex.test(s);
|
|
44
|
+
}
|
|
45
|
+
var REDIS_VERBOSE_LOG = process.env.REDIS_VERBOSE_LOG ? isTrue(process.env.REDIS_VERBOSE_LOG) : false;
|
|
40
46
|
var RedisStateStore = /** @class */ (function () {
|
|
41
47
|
function RedisStateStore(keyPrefix, opts) {
|
|
42
48
|
this.keyPrefix = keyPrefix;
|
|
@@ -135,12 +141,13 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
135
141
|
};
|
|
136
142
|
RedisStateStore.prototype.getValues = function (id, keys) {
|
|
137
143
|
return __awaiter(this, void 0, void 0, function () {
|
|
138
|
-
var pipeline, data, _loop_1, this_1, _i, keys_1, key;
|
|
144
|
+
var pipeline, data, startMs, _loop_1, this_1, _i, keys_1, key, ops, ioTimeMs;
|
|
139
145
|
return __generator(this, function (_a) {
|
|
140
146
|
switch (_a.label) {
|
|
141
147
|
case 0:
|
|
142
148
|
pipeline = this.client.pipeline();
|
|
143
149
|
data = {};
|
|
150
|
+
startMs = Date.now();
|
|
144
151
|
_loop_1 = function (key) {
|
|
145
152
|
var storeKey = "" + this_1.keyPrefix + id + key;
|
|
146
153
|
pipeline.get(storeKey, function (err, reply) {
|
|
@@ -160,9 +167,12 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
160
167
|
key = keys_1[_i];
|
|
161
168
|
_loop_1(key);
|
|
162
169
|
}
|
|
170
|
+
ops = pipeline.length;
|
|
163
171
|
return [4 /*yield*/, pipeline.exec()];
|
|
164
172
|
case 1:
|
|
165
173
|
_a.sent();
|
|
174
|
+
ioTimeMs = Date.now() - startMs;
|
|
175
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis', { event: 'getValues', operations: ops, ioTimeMs: ioTimeMs });
|
|
166
176
|
return [2 /*return*/, data];
|
|
167
177
|
}
|
|
168
178
|
});
|
|
@@ -189,6 +199,7 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
189
199
|
catch (err) {
|
|
190
200
|
console.error("REDIS get: Failed to parse ".concat(storeKey, " data: '").concat(reply, "'"));
|
|
191
201
|
}
|
|
202
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis', { event: 'get', operations: 1, ioTimeMs: ioTimeMs });
|
|
192
203
|
resolve(data_1);
|
|
193
204
|
}
|
|
194
205
|
else {
|
|
@@ -206,11 +217,12 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
206
217
|
};
|
|
207
218
|
RedisStateStore.prototype.setValues = function (id, data) {
|
|
208
219
|
return __awaiter(this, void 0, void 0, function () {
|
|
209
|
-
var returnData, pipeline, _loop_2, this_2, _i, _a, key;
|
|
220
|
+
var returnData, startMs, pipeline, _loop_2, this_2, _i, _a, key, ops, ioTimeMs;
|
|
210
221
|
return __generator(this, function (_b) {
|
|
211
222
|
switch (_b.label) {
|
|
212
223
|
case 0:
|
|
213
224
|
returnData = {};
|
|
225
|
+
startMs = Date.now();
|
|
214
226
|
pipeline = this.client.pipeline();
|
|
215
227
|
_loop_2 = function (key) {
|
|
216
228
|
var storeKey = "" + this_2.keyPrefix + id + key;
|
|
@@ -227,9 +239,12 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
227
239
|
key = _a[_i];
|
|
228
240
|
_loop_2(key);
|
|
229
241
|
}
|
|
242
|
+
ops = pipeline.length;
|
|
230
243
|
return [4 /*yield*/, pipeline.exec()];
|
|
231
244
|
case 1:
|
|
232
245
|
_b.sent();
|
|
246
|
+
ioTimeMs = Date.now() - startMs;
|
|
247
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis', { event: 'setValues', operations: ops, ioTimeMs: ioTimeMs });
|
|
233
248
|
return [2 /*return*/, returnData];
|
|
234
249
|
}
|
|
235
250
|
});
|
|
@@ -249,6 +264,7 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
249
264
|
var ioTimeMs = Date.now() - startMs;
|
|
250
265
|
debug("REDIS set ".concat(storeKey, ": ").concat(res, " (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? "REDISSLOW!" : ""));
|
|
251
266
|
if (!err) {
|
|
267
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis', { event: 'set', operations: 1, ioTimeMs: ioTimeMs });
|
|
252
268
|
resolve(value);
|
|
253
269
|
}
|
|
254
270
|
else {
|
|
@@ -305,6 +321,7 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
305
321
|
var ioTimeMs = Date.now() - startMs;
|
|
306
322
|
debug("REDIS remove ".concat(storeKey, ": (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? "REDISSLOW!" : ""));
|
|
307
323
|
if (!err) {
|
|
324
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis', { event: 'remove', operations: 1, ioTimeMs: ioTimeMs });
|
|
308
325
|
resolve();
|
|
309
326
|
}
|
|
310
327
|
else {
|
package/dist/package.json
CHANGED
package/docs/demosite.png
CHANGED
|
Binary file
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
const Redis = require("ioredis");
|
|
2
2
|
const debug = require("debug")("redis-state-store");
|
|
3
|
+
const { cloudWatchLog } = require("./util.js");
|
|
3
4
|
|
|
4
5
|
const DEFAULT_VOLATILE_KEY_TTL = 5; // Timeout so it should not expire within one normal increment iteration (in seconds)
|
|
5
6
|
|
|
7
|
+
function isTrue(s) {
|
|
8
|
+
const regex = /^\s*(true|1)\s*$/i;
|
|
9
|
+
return regex.test(s);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const REDIS_VERBOSE_LOG = process.env.REDIS_VERBOSE_LOG ? isTrue(process.env.REDIS_VERBOSE_LOG) : false;
|
|
13
|
+
|
|
6
14
|
class RedisStateStore {
|
|
7
15
|
constructor(keyPrefix, opts) {
|
|
8
16
|
this.keyPrefix = keyPrefix;
|
|
@@ -59,6 +67,7 @@ class RedisStateStore {
|
|
|
59
67
|
async getValues(id, keys) {
|
|
60
68
|
const pipeline = this.client.pipeline();
|
|
61
69
|
let data = {};
|
|
70
|
+
const startMs = Date.now();
|
|
62
71
|
for (const key of keys) {
|
|
63
72
|
const storeKey = "" + this.keyPrefix + id + key;
|
|
64
73
|
pipeline.get(storeKey, (err, reply) => {
|
|
@@ -72,7 +81,11 @@ class RedisStateStore {
|
|
|
72
81
|
}
|
|
73
82
|
});
|
|
74
83
|
}
|
|
84
|
+
const ops = pipeline.length;
|
|
75
85
|
await pipeline.exec();
|
|
86
|
+
const ioTimeMs = Date.now() - startMs;
|
|
87
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis',
|
|
88
|
+
{ event: 'getValues', operations: ops, ioTimeMs: ioTimeMs });
|
|
76
89
|
return data;
|
|
77
90
|
}
|
|
78
91
|
|
|
@@ -90,6 +103,8 @@ class RedisStateStore {
|
|
|
90
103
|
} catch (err) {
|
|
91
104
|
console.error(`REDIS get: Failed to parse ${storeKey} data: '${reply}'`);
|
|
92
105
|
}
|
|
106
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis',
|
|
107
|
+
{ event: 'get', operations: 1, ioTimeMs: ioTimeMs });
|
|
93
108
|
resolve(data);
|
|
94
109
|
} else {
|
|
95
110
|
reject(err);
|
|
@@ -102,6 +117,7 @@ class RedisStateStore {
|
|
|
102
117
|
|
|
103
118
|
async setValues(id, data) {
|
|
104
119
|
const returnData = {};
|
|
120
|
+
const startMs = Date.now();
|
|
105
121
|
const pipeline = this.client.pipeline();
|
|
106
122
|
for (const key of Object.keys(data)) {
|
|
107
123
|
const storeKey = "" + this.keyPrefix + id + key;
|
|
@@ -113,7 +129,11 @@ class RedisStateStore {
|
|
|
113
129
|
}
|
|
114
130
|
});
|
|
115
131
|
}
|
|
132
|
+
const ops = pipeline.length;
|
|
116
133
|
await pipeline.exec();
|
|
134
|
+
const ioTimeMs = Date.now() - startMs;
|
|
135
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis',
|
|
136
|
+
{ event: 'setValues', operations: ops, ioTimeMs: ioTimeMs });
|
|
117
137
|
return returnData;
|
|
118
138
|
}
|
|
119
139
|
|
|
@@ -125,6 +145,8 @@ class RedisStateStore {
|
|
|
125
145
|
const ioTimeMs = Date.now() - startMs;
|
|
126
146
|
debug(`REDIS set ${storeKey}: ${res} (${ioTimeMs}ms) ${ioTimeMs > 1000 ? "REDISSLOW!" : ""}`);
|
|
127
147
|
if (!err) {
|
|
148
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis',
|
|
149
|
+
{ event: 'set', operations: 1, ioTimeMs: ioTimeMs });
|
|
128
150
|
resolve(value);
|
|
129
151
|
} else {
|
|
130
152
|
reject(err);
|
|
@@ -159,6 +181,8 @@ class RedisStateStore {
|
|
|
159
181
|
const ioTimeMs = Date.now() - startMs;
|
|
160
182
|
debug(`REDIS remove ${storeKey}: (${ioTimeMs}ms) ${ioTimeMs > 1000 ? "REDISSLOW!" : ""}`);
|
|
161
183
|
if (!err) {
|
|
184
|
+
cloudWatchLog(!REDIS_VERBOSE_LOG, 'redis',
|
|
185
|
+
{ event: 'remove', operations: 1, ioTimeMs: ioTimeMs });
|
|
162
186
|
resolve();
|
|
163
187
|
} else {
|
|
164
188
|
reject(err);
|
package/ha-dev/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Development env for HA
|
|
2
|
+
|
|
3
|
+
To develop and test Channel Engine in HA mode using Redis as shared state store.
|
|
4
|
+
|
|
5
|
+
Start an NGINX as Load Balancer and a Redis instance:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
cd ha-dev/
|
|
9
|
+
docker-compose -f docker-compose-lb.yml up --build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The Load Balancer is then reachable on `http://localhost:8000` and will proxy (round robin) to three
|
|
13
|
+
instances on port 8080, 8081 and 8082.
|
|
14
|
+
|
|
15
|
+
Start one engine instance with the following parameters (at least):
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
PORT=8080 REDIS_URL=redis://localhost:6379/ npm start
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
and a second engine instance:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
PORT=8081 REDIS_URL=redis://localhost:6379/ npm start
|
|
25
|
+
```
|
package/package.json
CHANGED
package/dist/server-demux.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/server-demux.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Reference implementation of Channel Engine library using demuxed VOD assets.
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
var index_1 = require("./index");
|
|
7
|
-
var RefAssetManager = /** @class */ (function () {
|
|
8
|
-
function RefAssetManager(opts) {
|
|
9
|
-
this.assets = {
|
|
10
|
-
1: [
|
|
11
|
-
{
|
|
12
|
-
id: 1,
|
|
13
|
-
title: "Elephants dream",
|
|
14
|
-
uri: "https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8",
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
id: 2,
|
|
18
|
-
title: "Test HLS Bird noises (1m10s)",
|
|
19
|
-
uri: "https://mtoczko.github.io/hls-test-streams/test-audio-pdt/playlist.m3u8",
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
};
|
|
23
|
-
this.pos = {
|
|
24
|
-
1: 0,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
* @param {Object} vodRequest
|
|
30
|
-
* {
|
|
31
|
-
* sessionId,
|
|
32
|
-
* category,
|
|
33
|
-
* playlistId
|
|
34
|
-
* }
|
|
35
|
-
*/
|
|
36
|
-
RefAssetManager.prototype.getNextVod = function (vodRequest) {
|
|
37
|
-
var _this = this;
|
|
38
|
-
return new Promise(function (resolve, reject) {
|
|
39
|
-
var channelId = vodRequest.playlistId;
|
|
40
|
-
if (_this.assets[channelId]) {
|
|
41
|
-
var vod = _this.assets[channelId][_this.pos[channelId]++];
|
|
42
|
-
if (_this.pos[channelId] > _this.assets[channelId].length - 1) {
|
|
43
|
-
_this.pos[channelId] = 0;
|
|
44
|
-
}
|
|
45
|
-
resolve(vod);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
reject("Invalid channelId provided");
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
return RefAssetManager;
|
|
53
|
-
}());
|
|
54
|
-
var RefChannelManager = /** @class */ (function () {
|
|
55
|
-
function RefChannelManager() {
|
|
56
|
-
}
|
|
57
|
-
RefChannelManager.prototype.getChannels = function () {
|
|
58
|
-
return [{ id: "1", profile: this._getProfile(), audioTracks: this._getAudioTracks(), subtitleTracks: this._getSubtitleTracks() }];
|
|
59
|
-
};
|
|
60
|
-
RefChannelManager.prototype._getProfile = function () {
|
|
61
|
-
return [
|
|
62
|
-
{
|
|
63
|
-
bw: 7934000,
|
|
64
|
-
codecs: "avc1.4d001f,mp4a.40.2",
|
|
65
|
-
resolution: [2880, 1440],
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
bw: 7514000,
|
|
69
|
-
codecs: "avc1.4d001f,mp4a.40.2",
|
|
70
|
-
resolution: [1920, 1080],
|
|
71
|
-
},
|
|
72
|
-
{ bw: 7134000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [1280, 720] },
|
|
73
|
-
{ bw: 6134000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [1024, 458] },
|
|
74
|
-
{ bw: 2323000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [640, 286] },
|
|
75
|
-
{ bw: 495894, codecs: "avc1.4d001f,mp4a.40.2", resolution: [480, 214] },
|
|
76
|
-
];
|
|
77
|
-
};
|
|
78
|
-
RefChannelManager.prototype._getAudioTracks = function () {
|
|
79
|
-
return [
|
|
80
|
-
{ language: "en", name: "English", default: true },
|
|
81
|
-
{ language: "es", name: "Spanish", default: false },
|
|
82
|
-
];
|
|
83
|
-
};
|
|
84
|
-
RefChannelManager.prototype._getSubtitleTracks = function () {
|
|
85
|
-
return [
|
|
86
|
-
{ language: "zh", name: "chinese", default: true },
|
|
87
|
-
{ language: "fr", name: "french", default: false }
|
|
88
|
-
];
|
|
89
|
-
};
|
|
90
|
-
return RefChannelManager;
|
|
91
|
-
}());
|
|
92
|
-
var refAssetManager = new RefAssetManager();
|
|
93
|
-
var refChannelManager = new RefChannelManager();
|
|
94
|
-
var engineOptions = {
|
|
95
|
-
heartbeat: "/",
|
|
96
|
-
averageSegmentDuration: 2000,
|
|
97
|
-
channelManager: refChannelManager,
|
|
98
|
-
defaultSlateUri: "https://mtoczko.github.io/hls-test-streams/test-audio-pdt/playlist.m3u8",
|
|
99
|
-
slateRepetitions: 10,
|
|
100
|
-
redisUrl: process.env.REDIS_URL,
|
|
101
|
-
useDemuxedAudio: true,
|
|
102
|
-
alwaysNewSegments: false,
|
|
103
|
-
useVTTSubtitles: true
|
|
104
|
-
};
|
|
105
|
-
var engine = new index_1.ChannelEngine(refAssetManager, engineOptions);
|
|
106
|
-
engine.start();
|
|
107
|
-
engine.listen(process.env.PORT || 8000);
|
package/dist/server-drm.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/server-drm.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Reference implementation of Channel Engine library using DRM (HLS+Widevine) VOD assets.
|
|
4
|
-
*
|
|
5
|
-
* Playback: https://shaka-player-demo.appspot.com/demo/#audiolang=sv-SE;textlang=sv-SE;uilang=sv-SE;asset=http://localhost:8000/channels/1/master.m3u8;license=https://cwip-shaka-proxy.appspot.com/no_auth;panel=CUSTOM%20CONTENT;build=uncompiled
|
|
6
|
-
*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
var index_1 = require("./index");
|
|
9
|
-
var RefAssetManager = /** @class */ (function () {
|
|
10
|
-
function RefAssetManager(opts) {
|
|
11
|
-
this.assets = {
|
|
12
|
-
1: [
|
|
13
|
-
{
|
|
14
|
-
id: 1,
|
|
15
|
-
title: "VINN DRM",
|
|
16
|
-
uri: "https://testcontent.eyevinn.technology/vinn/multidrm/index.m3u8"
|
|
17
|
-
// License server urL: https://widevine-dash.ezdrm.com/proxy?pX=1D331C
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
id: 2,
|
|
21
|
-
title: "VINN No DRM",
|
|
22
|
-
uri: "https://testcontent.eyevinn.technology/vinn/cmaf/index.m3u8"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: 3,
|
|
26
|
-
title: "CE Promo DRM",
|
|
27
|
-
uri: "https://testcontent.eyevinn.technology/drm/CE-promo/index.m3u8"
|
|
28
|
-
// License server urL: https://widevine-dash.ezdrm.com/proxy?pX=1D331C
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: 4,
|
|
32
|
-
title: "Eyevinn Reel DRM",
|
|
33
|
-
uri: "https://testcontent.eyevinn.technology/drm/Eyevinn-Reel/index.m3u8"
|
|
34
|
-
// License server urL: https://widevine-dash.ezdrm.com/proxy?pX=1D331C
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
};
|
|
38
|
-
this.pos = {
|
|
39
|
-
1: 0,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
44
|
-
* @param {Object} vodRequest
|
|
45
|
-
* {
|
|
46
|
-
* sessionId,
|
|
47
|
-
* category,
|
|
48
|
-
* playlistId
|
|
49
|
-
* }
|
|
50
|
-
*/
|
|
51
|
-
RefAssetManager.prototype.getNextVod = function (vodRequest) {
|
|
52
|
-
var _this = this;
|
|
53
|
-
return new Promise(function (resolve, reject) {
|
|
54
|
-
var channelId = vodRequest.playlistId;
|
|
55
|
-
if (_this.assets[channelId]) {
|
|
56
|
-
var vod = _this.assets[channelId][_this.pos[channelId]++];
|
|
57
|
-
if (_this.pos[channelId] > _this.assets[channelId].length - 1) {
|
|
58
|
-
_this.pos[channelId] = 0;
|
|
59
|
-
}
|
|
60
|
-
resolve(vod);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
reject("Invalid channelId provided");
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
return RefAssetManager;
|
|
68
|
-
}());
|
|
69
|
-
var RefChannelManager = /** @class */ (function () {
|
|
70
|
-
function RefChannelManager() {
|
|
71
|
-
}
|
|
72
|
-
RefChannelManager.prototype.getChannels = function () {
|
|
73
|
-
return [{ id: "1", profile: this._getProfile(), audioTracks: this._getAudioTracks() }];
|
|
74
|
-
};
|
|
75
|
-
RefChannelManager.prototype._getProfile = function () {
|
|
76
|
-
return [
|
|
77
|
-
{
|
|
78
|
-
bw: 7934000,
|
|
79
|
-
codecs: "avc1.4d001f,mp4a.40.2",
|
|
80
|
-
resolution: [2880, 1440],
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
bw: 7514000,
|
|
84
|
-
codecs: "avc1.4d001f,mp4a.40.2",
|
|
85
|
-
resolution: [1920, 1080],
|
|
86
|
-
},
|
|
87
|
-
{ bw: 7134000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [1280, 720] },
|
|
88
|
-
{ bw: 6134000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [1024, 458] },
|
|
89
|
-
{ bw: 2323000, codecs: "avc1.4d001f,mp4a.40.2", resolution: [640, 286] },
|
|
90
|
-
{ bw: 495894, codecs: "avc1.4d001f,mp4a.40.2", resolution: [480, 214] },
|
|
91
|
-
];
|
|
92
|
-
};
|
|
93
|
-
RefChannelManager.prototype._getAudioTracks = function () {
|
|
94
|
-
return [
|
|
95
|
-
{ language: "en", name: "English" },
|
|
96
|
-
];
|
|
97
|
-
};
|
|
98
|
-
return RefChannelManager;
|
|
99
|
-
}());
|
|
100
|
-
var refAssetManager = new RefAssetManager();
|
|
101
|
-
var refChannelManager = new RefChannelManager();
|
|
102
|
-
var engineOptions = {
|
|
103
|
-
heartbeat: "/",
|
|
104
|
-
averageSegmentDuration: 4000,
|
|
105
|
-
channelManager: refChannelManager,
|
|
106
|
-
defaultSlateUri: "https://maitv-vod.lab.eyevinn.technology/slate-consuo.mp4/master.m3u8",
|
|
107
|
-
slateRepetitions: 10,
|
|
108
|
-
redisUrl: process.env.REDIS_URL,
|
|
109
|
-
useDemuxedAudio: true,
|
|
110
|
-
alwaysNewSegments: true,
|
|
111
|
-
};
|
|
112
|
-
var engine = new index_1.ChannelEngine(refAssetManager, engineOptions);
|
|
113
|
-
engine.start();
|
|
114
|
-
engine.listen(process.env.PORT || 8000);
|
package/dist/server-livemix.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/server-livemix.js
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Reference implementation of Channel Engine library
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
var index_1 = require("./index");
|
|
7
|
-
var uuidv4 = require('uuid').v4;
|
|
8
|
-
var STITCH_ENDPOINT = process.env.STITCH_ENDPOINT || "http://lambda.eyevinn.technology/stitch/master.m3u8";
|
|
9
|
-
var RefAssetManager = /** @class */ (function () {
|
|
10
|
-
function RefAssetManager(opts) {
|
|
11
|
-
if (process.env.TEST_CHANNELS) {
|
|
12
|
-
this.assets = {};
|
|
13
|
-
this.pos = {};
|
|
14
|
-
var testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
|
|
15
|
-
for (var i = 0; i < testChannelsCount; i++) {
|
|
16
|
-
var channelId = "".concat(i + 1);
|
|
17
|
-
this.assets[channelId] = [
|
|
18
|
-
{ id: 1, title: "Tears of Steel", uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8" },
|
|
19
|
-
{ id: 2, title: "Unhinged Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/UNHINGED_Trailer_2020.mp4/master.m3u8" },
|
|
20
|
-
{ id: 3, title: "Morbius Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/MORBIUS_Trailer_2020.mp4/master.m3u8" },
|
|
21
|
-
{ id: 4, title: "TV Plus Joachim", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-joachim.mov/master.m3u8" },
|
|
22
|
-
{ id: 5, title: "The Outpost Trailer", uri: "https://maitv-vod.lab.eyevinn.technology/THE_OUTPOST_Trailer_2020.mp4/master.m3u8" },
|
|
23
|
-
{ id: 6, title: "TV Plus Megha", uri: "https://maitv-vod.lab.eyevinn.technology/tvplus-ad-megha.mov/master.m3u8" },
|
|
24
|
-
];
|
|
25
|
-
this.pos[channelId] = 2;
|
|
26
|
-
}
|
|
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
|
-
/* @param {Object} vodRequest
|
|
45
|
-
* {
|
|
46
|
-
* sessionId,
|
|
47
|
-
* category,
|
|
48
|
-
* playlistId
|
|
49
|
-
* }
|
|
50
|
-
*/
|
|
51
|
-
RefAssetManager.prototype.getNextVod = function (vodRequest) {
|
|
52
|
-
var _this = this;
|
|
53
|
-
return new Promise(function (resolve, reject) {
|
|
54
|
-
var channelId = vodRequest.playlistId;
|
|
55
|
-
if (_this.assets[channelId]) {
|
|
56
|
-
var vod = _this.assets[channelId][_this.pos[channelId]++];
|
|
57
|
-
if (_this.pos[channelId] > _this.assets[channelId].length - 1) {
|
|
58
|
-
_this.pos[channelId] = 0;
|
|
59
|
-
}
|
|
60
|
-
var 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
|
-
var buff = Buffer.from(JSON.stringify(payload));
|
|
71
|
-
var encodedPayload = buff.toString("base64");
|
|
72
|
-
var vodResponse = {
|
|
73
|
-
id: vod.id,
|
|
74
|
-
title: vod.title,
|
|
75
|
-
uri: STITCH_ENDPOINT + "?payload=" + encodedPayload
|
|
76
|
-
};
|
|
77
|
-
resolve(vodResponse);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
reject("Invalid channelId provided");
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
};
|
|
84
|
-
RefAssetManager.prototype.handleError = function (err, vodResponse) {
|
|
85
|
-
console.error(err.message);
|
|
86
|
-
};
|
|
87
|
-
return RefAssetManager;
|
|
88
|
-
}());
|
|
89
|
-
var RefChannelManager = /** @class */ (function () {
|
|
90
|
-
function RefChannelManager(opts) {
|
|
91
|
-
this.channels = [];
|
|
92
|
-
if (process.env.TEST_CHANNELS) {
|
|
93
|
-
var testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
|
|
94
|
-
for (var i = 0; i < testChannelsCount; i++) {
|
|
95
|
-
this.channels.push({ id: "".concat(i + 1), profile: this._getProfile() });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
this.channels = [{ id: "1", profile: this._getProfile() }];
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
RefChannelManager.prototype.getChannels = function () {
|
|
103
|
-
return this.channels;
|
|
104
|
-
};
|
|
105
|
-
RefChannelManager.prototype._getProfile = function () {
|
|
106
|
-
return [
|
|
107
|
-
{ bw: 8242000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [1024, 458] },
|
|
108
|
-
{ bw: 1274000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [640, 286] },
|
|
109
|
-
{ bw: 742000, codecs: 'avc1.4d001f,mp4a.40.2', resolution: [480, 214] },
|
|
110
|
-
];
|
|
111
|
-
};
|
|
112
|
-
return RefChannelManager;
|
|
113
|
-
}());
|
|
114
|
-
var StreamType = Object.freeze({
|
|
115
|
-
LIVE: 1,
|
|
116
|
-
VOD: 2,
|
|
117
|
-
});
|
|
118
|
-
var StreamSwitchManager = /** @class */ (function () {
|
|
119
|
-
function StreamSwitchManager() {
|
|
120
|
-
this.schedule = {};
|
|
121
|
-
if (process.env.TEST_CHANNELS) {
|
|
122
|
-
var testChannelsCount = parseInt(process.env.TEST_CHANNELS, 10);
|
|
123
|
-
for (var i = 0; i < testChannelsCount; i++) {
|
|
124
|
-
var channelId = "".concat(i + 1);
|
|
125
|
-
this.schedule[channelId] = [];
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
this.schedule = {
|
|
130
|
-
'1': []
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
StreamSwitchManager.prototype.generateID = function () {
|
|
135
|
-
return uuidv4();
|
|
136
|
-
};
|
|
137
|
-
StreamSwitchManager.prototype.getPrerollUri = function (channelId) {
|
|
138
|
-
var defaultPrerollSlateUri = "https://maitv-vod.lab.eyevinn.technology/slate-consuo.mp4/master.m3u8";
|
|
139
|
-
return new Promise(function (resolve, reject) { resolve(defaultPrerollSlateUri); });
|
|
140
|
-
};
|
|
141
|
-
StreamSwitchManager.prototype.getSchedule = function (channelId) {
|
|
142
|
-
var _this = this;
|
|
143
|
-
return new Promise(function (resolve, reject) {
|
|
144
|
-
if (_this.schedule[channelId]) {
|
|
145
|
-
var tsNow_1 = Date.now();
|
|
146
|
-
var streamDuration = 60 * 1000;
|
|
147
|
-
var startOffset = tsNow_1 + streamDuration;
|
|
148
|
-
var endTime = startOffset + streamDuration;
|
|
149
|
-
// Break in with live and scheduled VOD content after 60 seconds of VOD2Live the first time Channel Engine starts
|
|
150
|
-
// Required: "assetId", "start_time", "end_time", "uri", "duration"
|
|
151
|
-
// "duration" is only required for StreamType.VOD
|
|
152
|
-
_this.schedule[channelId] = _this.schedule[channelId].filter(function (obj) { return obj.end_time >= tsNow_1; });
|
|
153
|
-
if (_this.schedule[channelId].length === 0) {
|
|
154
|
-
_this.schedule[channelId].push({
|
|
155
|
-
eventId: _this.generateID(),
|
|
156
|
-
assetId: _this.generateID(),
|
|
157
|
-
title: "Live stream test",
|
|
158
|
-
type: StreamType.LIVE,
|
|
159
|
-
start_time: startOffset,
|
|
160
|
-
end_time: endTime,
|
|
161
|
-
uri: "https://d2fz24s2fts31b.cloudfront.net/out/v1/6484d7c664924b77893f9b4f63080e5d/manifest.m3u8",
|
|
162
|
-
}, {
|
|
163
|
-
eventId: _this.generateID(),
|
|
164
|
-
assetId: _this.generateID(),
|
|
165
|
-
title: "Scheduled VOD test",
|
|
166
|
-
type: StreamType.VOD,
|
|
167
|
-
start_time: (endTime + 100 * 1000),
|
|
168
|
-
end_time: (endTime + 100 * 1000) + streamDuration,
|
|
169
|
-
uri: "https://maitv-vod.lab.eyevinn.technology/COME_TO_DADDY_Trailer_2020.mp4/master.m3u8",
|
|
170
|
-
duration: streamDuration,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
resolve(_this.schedule[channelId]);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
reject("Invalid channelId provided");
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
};
|
|
180
|
-
return StreamSwitchManager;
|
|
181
|
-
}());
|
|
182
|
-
var refAssetManager = new RefAssetManager();
|
|
183
|
-
var refChannelManager = new RefChannelManager();
|
|
184
|
-
var refStreamSwitchManager = new StreamSwitchManager();
|
|
185
|
-
var engineOptions = {
|
|
186
|
-
heartbeat: "/",
|
|
187
|
-
averageSegmentDuration: 2000,
|
|
188
|
-
channelManager: refChannelManager,
|
|
189
|
-
streamSwitchManager: refStreamSwitchManager,
|
|
190
|
-
defaultSlateUri: "https://maitv-vod.lab.eyevinn.technology/slate-consuo.mp4/master.m3u8",
|
|
191
|
-
slateRepetitions: 10,
|
|
192
|
-
redisUrl: process.env.REDIS_URL,
|
|
193
|
-
};
|
|
194
|
-
var engine = new index_1.ChannelEngine(refAssetManager, engineOptions);
|
|
195
|
-
engine.start();
|
|
196
|
-
engine.listen(process.env.PORT || 8000);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|