eyevinn-channel-engine 4.3.2 → 4.3.3-rc.1
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/Dockerfile +3 -2
- package/Dockerfile.nginxlb +2 -0
- package/dist/engine/redis_state_store.d.ts +3 -0
- package/dist/engine/redis_state_store.js +42 -6
- package/dist/examples/default.js +24 -25
- package/dist/package.json +2 -1
- package/dist/server-demux.d.ts +1 -0
- package/dist/server-demux.js +107 -0
- package/dist/server-drm.d.ts +1 -0
- package/dist/server-drm.js +114 -0
- package/dist/server-livemix.d.ts +1 -0
- package/dist/server-livemix.js +196 -0
- package/dist/server-multicodec.d.ts +1 -0
- package/dist/server-multicodec.js +96 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +101 -0
- package/docker-compose-lb.yml +14 -0
- package/engine/redis_state_store.js +42 -6
- package/examples/default.ts +40 -35
- package/nginx-proxy-lb.conf +14 -0
- package/package.json +2 -1
- package/validation_data.json +2139 -0
package/Dockerfile
CHANGED
|
@@ -4,6 +4,9 @@ declare class RedisStateStore {
|
|
|
4
4
|
keyPrefix: any;
|
|
5
5
|
volatileKeyTTL: any;
|
|
6
6
|
client: any;
|
|
7
|
+
pipelines: {};
|
|
8
|
+
pipeline(id: any): any;
|
|
9
|
+
commit(id: any): Promise<any>;
|
|
7
10
|
initAsync(id: any, initData: any): Promise<{}>;
|
|
8
11
|
resetAsync(id: any, initData: any): Promise<void>;
|
|
9
12
|
resetAllAsync(): Promise<void>;
|
|
@@ -34,7 +34,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
34
34
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
-
var
|
|
37
|
+
var Redis = require("ioredis");
|
|
38
38
|
var debug = require("debug")("redis-state-store");
|
|
39
39
|
var DEFAULT_VOLATILE_KEY_TTL = 5; // Timeout so it should not expire within one normal increment iteration (in seconds)
|
|
40
40
|
var RedisStateStore = /** @class */ (function () {
|
|
@@ -50,8 +50,36 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
50
50
|
debug("Overriding default, volatileKeyTTL=".concat(opts.volatileKeyTTL, "s"));
|
|
51
51
|
this.volatileKeyTTL = opts.volatileKeyTTL;
|
|
52
52
|
}
|
|
53
|
-
this.client =
|
|
53
|
+
this.client = new Redis(opts.redisUrl);
|
|
54
|
+
this.pipelines = {};
|
|
54
55
|
}
|
|
56
|
+
RedisStateStore.prototype.pipeline = function (id) {
|
|
57
|
+
var pipelineId = id || '_';
|
|
58
|
+
if (!this.pipelines[pipelineId]) {
|
|
59
|
+
this.pipelines[pipelineId] = this.client.pipeline();
|
|
60
|
+
}
|
|
61
|
+
debug("REDIS[".concat(pipelineId, "]:pipeline length=").concat(this.pipelines[pipelineId].length));
|
|
62
|
+
return this.pipelines[pipelineId];
|
|
63
|
+
};
|
|
64
|
+
RedisStateStore.prototype.commit = function (id) {
|
|
65
|
+
var _this = this;
|
|
66
|
+
return new Promise(function (resolve, reject) {
|
|
67
|
+
var pipelineId = id || '_';
|
|
68
|
+
if (_this.pipelines[pipelineId]) {
|
|
69
|
+
var pipeline = _this.pipelines[pipelineId];
|
|
70
|
+
debug("REDIS[".concat(pipelineId, "]:pipeline length=").concat(_this.pipelines[pipelineId].length, " before commit"));
|
|
71
|
+
pipeline.exec(function (err, results) {
|
|
72
|
+
_this.pipelines[pipelineId] = _this.client.pipeline();
|
|
73
|
+
if (!err) {
|
|
74
|
+
resolve();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
reject(err);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
};
|
|
55
83
|
RedisStateStore.prototype.initAsync = function (id, initData) {
|
|
56
84
|
return __awaiter(this, void 0, void 0, function () {
|
|
57
85
|
var isInitiated, data, _i, _a, key, _b, _c, _d, _e, key, _f, _g;
|
|
@@ -154,7 +182,8 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
154
182
|
startMs = Date.now();
|
|
155
183
|
storeKey = "" + this.keyPrefix + id + key;
|
|
156
184
|
getAsync = new Promise(function (resolve, reject) {
|
|
157
|
-
_this.
|
|
185
|
+
var pipeline = _this.pipeline(id);
|
|
186
|
+
pipeline.get(storeKey, function (err, reply) {
|
|
158
187
|
var ioTimeMs = Date.now() - startMs;
|
|
159
188
|
debug("REDIS get ".concat(storeKey, ":").concat(reply ? reply.length + " chars" : "null", " (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? 'REDISSLOW!' : ''));
|
|
160
189
|
if (!err) {
|
|
@@ -164,6 +193,7 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
164
193
|
reject(err);
|
|
165
194
|
}
|
|
166
195
|
});
|
|
196
|
+
_this.commit(id);
|
|
167
197
|
});
|
|
168
198
|
return [4 /*yield*/, getAsync];
|
|
169
199
|
case 1:
|
|
@@ -183,7 +213,8 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
183
213
|
startMs = Date.now();
|
|
184
214
|
storeKey = "" + this.keyPrefix + id + key;
|
|
185
215
|
setAsync = new Promise(function (resolve, reject) {
|
|
186
|
-
|
|
216
|
+
var pipeline = _this.pipeline(id);
|
|
217
|
+
pipeline.set(storeKey, JSON.stringify(value), function (err, res) {
|
|
187
218
|
var ioTimeMs = Date.now() - startMs;
|
|
188
219
|
debug("REDIS set ".concat(storeKey, ": ").concat(res, " (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? "REDISSLOW!" : ""));
|
|
189
220
|
if (!err) {
|
|
@@ -193,6 +224,7 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
193
224
|
reject(err);
|
|
194
225
|
}
|
|
195
226
|
});
|
|
227
|
+
_this.commit(id);
|
|
196
228
|
});
|
|
197
229
|
return [4 /*yield*/, setAsync];
|
|
198
230
|
case 1: return [2 /*return*/, _a.sent()];
|
|
@@ -211,7 +243,8 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
211
243
|
data = _a.sent();
|
|
212
244
|
storeKey = "" + this.keyPrefix + id + key;
|
|
213
245
|
expireAsync = new Promise(function (resolve, reject) {
|
|
214
|
-
|
|
246
|
+
var pipeline = _this.pipeline(id);
|
|
247
|
+
pipeline.expire(storeKey, _this.volatileKeyTTL, function (err, res) {
|
|
215
248
|
if (!err) {
|
|
216
249
|
debug("REDIS expire ".concat(storeKey, " ").concat(_this.volatileKeyTTL, "s: ").concat(res === 1 ? "OK" : "KEY DOES NOT EXIST"));
|
|
217
250
|
resolve();
|
|
@@ -220,6 +253,7 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
220
253
|
reject(err);
|
|
221
254
|
}
|
|
222
255
|
});
|
|
256
|
+
_this.commit(id);
|
|
223
257
|
});
|
|
224
258
|
return [4 /*yield*/, expireAsync];
|
|
225
259
|
case 2:
|
|
@@ -239,7 +273,8 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
239
273
|
startMs = Date.now();
|
|
240
274
|
storeKey = "" + this.keyPrefix + id + key;
|
|
241
275
|
delAsync = new Promise(function (resolve, reject) {
|
|
242
|
-
_this.
|
|
276
|
+
var pipeline = _this.pipeline(id);
|
|
277
|
+
pipeline.del(storeKey, function (err, res) {
|
|
243
278
|
var ioTimeMs = Date.now() - startMs;
|
|
244
279
|
debug("REDIS remove ".concat(storeKey, ": (").concat(ioTimeMs, "ms) ").concat(ioTimeMs > 1000 ? "REDISSLOW!" : ""));
|
|
245
280
|
if (!err) {
|
|
@@ -249,6 +284,7 @@ var RedisStateStore = /** @class */ (function () {
|
|
|
249
284
|
reject(err);
|
|
250
285
|
}
|
|
251
286
|
});
|
|
287
|
+
_this.commit(id);
|
|
252
288
|
});
|
|
253
289
|
return [4 /*yield*/, delAsync];
|
|
254
290
|
case 1:
|
package/dist/examples/default.js
CHANGED
|
@@ -4,27 +4,35 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
var index_1 = require("../index");
|
|
7
|
-
var
|
|
8
|
-
|
|
7
|
+
var TEST_CHANNELS_COUNT = 1;
|
|
8
|
+
if (process.env.TEST_CHANNELS) {
|
|
9
|
+
TEST_CHANNELS_COUNT = parseInt(process.env.TEST_CHANNELS, 10);
|
|
10
|
+
}
|
|
9
11
|
var RefAssetManager = /** @class */ (function () {
|
|
10
12
|
function RefAssetManager(opts) {
|
|
11
|
-
this.assets = {
|
|
12
|
-
|
|
13
|
+
this.assets = {};
|
|
14
|
+
this.pos = {};
|
|
15
|
+
for (var i = 0; i < TEST_CHANNELS_COUNT; i++) {
|
|
16
|
+
var channelId = "".concat(i + 1);
|
|
17
|
+
this.assets[channelId] = [
|
|
13
18
|
{
|
|
14
19
|
id: 1,
|
|
20
|
+
title: "Streaming Tech TV+",
|
|
21
|
+
uri: "https://lab.cdn.eyevinn.technology/stswetvplus-promo-2023-5GBm231Mkz.mov/manifest.m3u8",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 2,
|
|
15
25
|
title: "Tears of Steel",
|
|
16
26
|
uri: "https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8",
|
|
17
27
|
},
|
|
18
28
|
{
|
|
19
|
-
id:
|
|
29
|
+
id: 3,
|
|
20
30
|
title: "VINN",
|
|
21
31
|
uri: "https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8",
|
|
22
32
|
},
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
1: 1,
|
|
27
|
-
};
|
|
33
|
+
];
|
|
34
|
+
this.pos[channelId] = 0;
|
|
35
|
+
}
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
30
38
|
*
|
|
@@ -44,22 +52,10 @@ var RefAssetManager = /** @class */ (function () {
|
|
|
44
52
|
if (_this.pos[channelId] > _this.assets[channelId].length - 1) {
|
|
45
53
|
_this.pos[channelId] = 0;
|
|
46
54
|
}
|
|
47
|
-
var payload = {
|
|
48
|
-
uri: vod.uri,
|
|
49
|
-
breaks: [
|
|
50
|
-
{
|
|
51
|
-
pos: 100,
|
|
52
|
-
duration: 15 * 1000,
|
|
53
|
-
url: "https://maitv-vod.lab.eyevinn.technology/ads/6cd7d768_e214_4ebc_9f14_7ed89710115e_mp4/master.m3u8",
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
};
|
|
57
|
-
var buff = Buffer.from(JSON.stringify(payload));
|
|
58
|
-
var encodedPayload = buff.toString("base64");
|
|
59
55
|
var vodResponse = {
|
|
60
56
|
id: vod.id,
|
|
61
57
|
title: vod.title,
|
|
62
|
-
uri:
|
|
58
|
+
uri: vod.uri,
|
|
63
59
|
};
|
|
64
60
|
resolve(vodResponse);
|
|
65
61
|
}
|
|
@@ -72,10 +68,13 @@ var RefAssetManager = /** @class */ (function () {
|
|
|
72
68
|
}());
|
|
73
69
|
var RefChannelManager = /** @class */ (function () {
|
|
74
70
|
function RefChannelManager() {
|
|
71
|
+
this.channels = [];
|
|
72
|
+
for (var i = 0; i < TEST_CHANNELS_COUNT; i++) {
|
|
73
|
+
this.channels.push({ id: "".concat(i + 1), profile: this._getProfile() });
|
|
74
|
+
}
|
|
75
75
|
}
|
|
76
76
|
RefChannelManager.prototype.getChannels = function () {
|
|
77
|
-
|
|
78
|
-
return [{ id: "1", profile: this._getProfile() }];
|
|
77
|
+
return this.channels;
|
|
79
78
|
};
|
|
80
79
|
RefChannelManager.prototype._getProfile = function () {
|
|
81
80
|
return [
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyevinn-channel-engine",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.3-rc.1",
|
|
4
4
|
"description": "OTT TV Channel Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@eyevinn/m3u8": "^0.5.3",
|
|
35
35
|
"abort-controller": "^3.0.0",
|
|
36
36
|
"debug": "^3.2.7",
|
|
37
|
+
"ioredis": "^5.3.2",
|
|
37
38
|
"memcache-client": "^0.10.1",
|
|
38
39
|
"nock": "^13.1.1",
|
|
39
40
|
"node-fetch": "^2.6.1",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
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);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
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);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
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);
|