eyevinn-channel-engine 3.4.3 → 4.0.0-rc.0
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 +6 -0
- package/dist/engine/server.d.ts +8 -0
- package/dist/engine/server.js +24 -0
- package/dist/package.json +2 -2
- package/dist/server-demux.js +5 -7
- package/engine/server.ts +28 -0
- package/engine/session.js +128 -35
- package/engine/session_state.js +2 -0
- package/package.json +7 -7
- package/server-demux.ts +69 -19
package/README.md
CHANGED
|
@@ -33,6 +33,12 @@ Supported Node.js Versions
|
|
|
33
33
|
|
|
34
34
|
Follow [this tutorial](https://vod2live.docs.eyevinn.technology/getting-started.html) to get started building your own FAST channel engine.
|
|
35
35
|
|
|
36
|
+
## Migration
|
|
37
|
+
|
|
38
|
+
### Upgrading from 3.3.x to >= 3.4.x
|
|
39
|
+
|
|
40
|
+
A breaking change was introduced in v3.4.0 when Typescript types were introduced. The library no longer exports a default. This means that you need to change `const ChannelEngine = require('eyevinn-channel-engine')` to `const { ChannelEngine } = require('eyevinn-channel-engine')`;
|
|
41
|
+
|
|
36
42
|
## Support
|
|
37
43
|
|
|
38
44
|
Join our [community on Slack](http://slack.streamingtech.se) where you can post any questions regarding any of our open source projects. Eyevinn's consulting business can also offer you:
|
package/dist/engine/server.d.ts
CHANGED
|
@@ -61,6 +61,14 @@ export interface Channel {
|
|
|
61
61
|
id: string;
|
|
62
62
|
profile: ChannelProfile[];
|
|
63
63
|
audioTracks?: AudioTracks[];
|
|
64
|
+
closedCaptions?: ClosedCaptions[];
|
|
65
|
+
}
|
|
66
|
+
export interface ClosedCaptions {
|
|
67
|
+
id: string;
|
|
68
|
+
lang: string;
|
|
69
|
+
name: string;
|
|
70
|
+
default?: boolean;
|
|
71
|
+
auto?: boolean;
|
|
64
72
|
}
|
|
65
73
|
export interface AudioTracks {
|
|
66
74
|
language: string;
|
package/dist/engine/server.js
CHANGED
|
@@ -203,6 +203,30 @@ var ChannelEngine = /** @class */ (function () {
|
|
|
203
203
|
}
|
|
204
204
|
});
|
|
205
205
|
}); });
|
|
206
|
+
this.server.opts('/live/:file', function (req, res, next) { return __awaiter(_this, void 0, void 0, function () {
|
|
207
|
+
return __generator(this, function (_a) {
|
|
208
|
+
res.sendRaw(204, "", {
|
|
209
|
+
headers: {
|
|
210
|
+
"Access-Control-Allow-Origin": "*",
|
|
211
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
next();
|
|
215
|
+
return [2 /*return*/];
|
|
216
|
+
});
|
|
217
|
+
}); });
|
|
218
|
+
this.server.opts('/channels/:channelId/:file', function (req, res, next) { return __awaiter(_this, void 0, void 0, function () {
|
|
219
|
+
return __generator(this, function (_a) {
|
|
220
|
+
res.sendRaw(204, "", {
|
|
221
|
+
headers: {
|
|
222
|
+
"Access-Control-Allow-Origin": "*",
|
|
223
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
next();
|
|
227
|
+
return [2 /*return*/];
|
|
228
|
+
});
|
|
229
|
+
}); });
|
|
206
230
|
this.server.get('/eventstream/:sessionId', this._handleEventStream.bind(this));
|
|
207
231
|
this.server.get('/status/:sessionId', this._handleStatus.bind(this));
|
|
208
232
|
this.server.get('/health', this._handleAggregatedSessionHealth.bind(this));
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyevinn-channel-engine",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"description": "OTT TV Channel Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"promise.allsettled": "^1.0.4",
|
|
36
36
|
"redis": "^3.0.2",
|
|
37
37
|
"request": ">=2.88.0",
|
|
38
|
-
"restify": "
|
|
38
|
+
"restify": "9.0.0",
|
|
39
39
|
"restify-errors": "^8.0.2",
|
|
40
40
|
"uuid": "^8.3.2"
|
|
41
41
|
},
|
package/dist/server-demux.js
CHANGED
|
@@ -10,13 +10,13 @@ var RefAssetManager = /** @class */ (function () {
|
|
|
10
10
|
1: [
|
|
11
11
|
{
|
|
12
12
|
id: 1,
|
|
13
|
-
title: "
|
|
14
|
-
uri: "https://
|
|
13
|
+
title: "Nymo bloopers 2022",
|
|
14
|
+
uri: "https://vod.streaming.a2d.tv/3f389c48-03e3-48a2-8e98-a02c55185a68/4c792a30-89ad-11ed-95d9-1b374c4e2f9f_20411056.ism/.m3u8",
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
id: 2,
|
|
18
|
-
title: "
|
|
19
|
-
uri: "https://
|
|
18
|
+
title: "Skrattfest",
|
|
19
|
+
uri: "https://vod.streaming.a2d.tv/09399ba0-70de-4ce7-9d84-bfcd6d9f8d43/fba308d0-39f5-11ed-b93e-a747b4a166ca_20062029.ism/.m3u8",
|
|
20
20
|
},
|
|
21
21
|
],
|
|
22
22
|
};
|
|
@@ -81,9 +81,7 @@ var RefChannelManager = /** @class */ (function () {
|
|
|
81
81
|
};
|
|
82
82
|
RefChannelManager.prototype._getAudioTracks = function () {
|
|
83
83
|
return [
|
|
84
|
-
{ language: "
|
|
85
|
-
{ language: "ru", name: "Russian" },
|
|
86
|
-
{ language: "en", name: "English", default: true },
|
|
84
|
+
{ language: "sv", name: "Swedish", default: true },
|
|
87
85
|
];
|
|
88
86
|
};
|
|
89
87
|
return RefChannelManager;
|
package/engine/server.ts
CHANGED
|
@@ -78,6 +78,7 @@ export interface VodTimedMetadata {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export interface VodResponse {
|
|
81
|
+
title: any;
|
|
81
82
|
id: string;
|
|
82
83
|
uri: string;
|
|
83
84
|
offset?: number;
|
|
@@ -103,6 +104,15 @@ export interface Channel {
|
|
|
103
104
|
id: string;
|
|
104
105
|
profile: ChannelProfile[];
|
|
105
106
|
audioTracks?: AudioTracks[];
|
|
107
|
+
closedCaptions?: ClosedCaptions[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ClosedCaptions {
|
|
111
|
+
id: string;
|
|
112
|
+
lang: string;
|
|
113
|
+
name: string;
|
|
114
|
+
default?: boolean;
|
|
115
|
+
auto?: boolean;
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
export interface AudioTracks {
|
|
@@ -267,6 +277,24 @@ export class ChannelEngine {
|
|
|
267
277
|
req.query['channel'] = req.params.channelId;
|
|
268
278
|
await handleMasterRoute(req, res, next);
|
|
269
279
|
});
|
|
280
|
+
this.server.opts('/live/:file', async (req, res, next) => {
|
|
281
|
+
res.sendRaw(204, "", {
|
|
282
|
+
headers: {
|
|
283
|
+
"Access-Control-Allow-Origin": "*",
|
|
284
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
next();
|
|
288
|
+
});
|
|
289
|
+
this.server.opts('/channels/:channelId/:file', async (req, res, next) => {
|
|
290
|
+
res.sendRaw(204, "", {
|
|
291
|
+
headers: {
|
|
292
|
+
"Access-Control-Allow-Origin": "*",
|
|
293
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
next();
|
|
297
|
+
});
|
|
270
298
|
this.server.get('/eventstream/:sessionId', this._handleEventStream.bind(this));
|
|
271
299
|
this.server.get('/status/:sessionId', this._handleStatus.bind(this));
|
|
272
300
|
this.server.get('/health', this._handleAggregatedSessionHealth.bind(this));
|
package/engine/session.js
CHANGED
|
@@ -153,10 +153,13 @@ class Session {
|
|
|
153
153
|
let playheadState = await this._playheadState.getValues(["state"]);
|
|
154
154
|
let state = await this._playheadState.setState(PlayheadState.RUNNING);
|
|
155
155
|
let numberOfLargeTicks = 0;
|
|
156
|
+
let audioIncrement = 1;
|
|
156
157
|
while (state !== PlayheadState.CRASHED) {
|
|
157
158
|
try {
|
|
158
159
|
const tsIncrementBegin = Date.now();
|
|
159
|
-
const manifest = await this.incrementAsync(
|
|
160
|
+
const manifest = await this.incrementAsync({
|
|
161
|
+
audio: audioIncrement
|
|
162
|
+
});
|
|
160
163
|
if (!manifest) {
|
|
161
164
|
debug(`[${this._sessionId}]: No manifest available yet, will try again after 1000ms`);
|
|
162
165
|
await timer(1000);
|
|
@@ -196,6 +199,38 @@ class Session {
|
|
|
196
199
|
debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval. tick=${tickInterval}`);
|
|
197
200
|
}
|
|
198
201
|
const position = (await this._getCurrentPlayheadPosition()) * 1000;
|
|
202
|
+
|
|
203
|
+
if (this.use_demuxed_audio) {
|
|
204
|
+
let currentVod = await this._sessionState.getCurrentVod();
|
|
205
|
+
const sessionState = await this._sessionState.getValues(["vodMediaSeqAudio"]);
|
|
206
|
+
let posDiff;
|
|
207
|
+
let incrementValue = 1;
|
|
208
|
+
let index = 0;
|
|
209
|
+
const audioSeqLastIdx = currentVod.getLiveMediaSequencesCount("audio") - 1;
|
|
210
|
+
const thresh = 0.5
|
|
211
|
+
let direction = 0;
|
|
212
|
+
do {
|
|
213
|
+
const audioPosition = (await this._getAudioPlayheadPosition(sessionState.vodMediaSeqAudio + index)) * 1000; // PUT REAL WHEN DONE
|
|
214
|
+
posDiff = position - audioPosition;
|
|
215
|
+
if (posDiff > thresh && direction >= 0) {
|
|
216
|
+
index++;
|
|
217
|
+
incrementValue++;
|
|
218
|
+
direction = 1;
|
|
219
|
+
} else if (posDiff < -thresh && direction <= 0) {
|
|
220
|
+
incrementValue = 0;
|
|
221
|
+
index--;
|
|
222
|
+
direction = -1;
|
|
223
|
+
break;
|
|
224
|
+
} else if (direction > 0 && posDiff < 0) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
if (sessionState.vodMediaSeqAudio + index > audioSeqLastIdx || sessionState.vodMediaSeqAudio + index < 0) {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
} while (!(-thresh < posDiff && posDiff < thresh));
|
|
231
|
+
audioIncrement = incrementValue;
|
|
232
|
+
}
|
|
233
|
+
|
|
199
234
|
let timePosition = Date.now() - playheadState.playheadRef;
|
|
200
235
|
// Apply time position offset if set, only after external diff compensation has concluded.
|
|
201
236
|
if (this.timePositionOffset && this.diffCompensation <= 0 && this.alwaysNewSegments) {
|
|
@@ -477,6 +512,8 @@ class Session {
|
|
|
477
512
|
|
|
478
513
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
479
514
|
const discSeqOffset = await this._sessionState.get("discSeq");
|
|
515
|
+
// TODO: support Audio too ^
|
|
516
|
+
|
|
480
517
|
// Clear Vod Cache here when Switching to Live just to be safe...
|
|
481
518
|
if (playheadState.vodMediaSeqVideo === 0) {
|
|
482
519
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
@@ -618,17 +655,17 @@ class Session {
|
|
|
618
655
|
throw new Error('Session not ready');
|
|
619
656
|
}
|
|
620
657
|
|
|
621
|
-
const sessionState = await this._sessionState.getValues(["
|
|
622
|
-
const playheadState = await this._playheadState.getValues(["
|
|
658
|
+
const sessionState = await this._sessionState.getValues(["discSeqAudio"]);
|
|
659
|
+
const playheadState = await this._playheadState.getValues(["mediaSeqAudio", "vodMediaSeqAudio"]);
|
|
623
660
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
624
661
|
if (currentVod) {
|
|
625
662
|
try {
|
|
626
|
-
const m3u8 = currentVod.getLiveMediaAudioSequences(playheadState.
|
|
663
|
+
const m3u8 = currentVod.getLiveMediaAudioSequences(playheadState.mediaSeqAudio, audioGroupId, audioLanguage, playheadState.vodMediaSeqAudio, sessionState.discSeqAudio, this.targetDurationPadding, this.forceTargetDuration);
|
|
627
664
|
// # Case: current VOD does not have the selected track.
|
|
628
665
|
if (!m3u8) {
|
|
629
|
-
debug(`[${this._sessionId}]: [${playheadState.
|
|
666
|
+
debug(`[${this._sessionId}]: [${playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio}] Request Failed for current audio manifest for ${audioGroupId}-${audioLanguage}`);
|
|
630
667
|
}
|
|
631
|
-
debug(`[${this._sessionId}]: [${playheadState.
|
|
668
|
+
debug(`[${this._sessionId}]: [${playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio}] Current audio manifest for ${audioGroupId}-${audioLanguage} requested`);
|
|
632
669
|
return m3u8;
|
|
633
670
|
} catch (err) {
|
|
634
671
|
logerror(this._sessionId, err);
|
|
@@ -640,13 +677,13 @@ class Session {
|
|
|
640
677
|
}
|
|
641
678
|
}
|
|
642
679
|
|
|
643
|
-
async incrementAsync() {
|
|
680
|
+
async incrementAsync(incOpts) {
|
|
644
681
|
await this._tickAsync();
|
|
645
682
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
646
683
|
|
|
647
684
|
let sessionState = await this._sessionState.getValues(
|
|
648
|
-
["state", "mediaSeq", "discSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
|
|
649
|
-
let playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
|
|
685
|
+
["state", "mediaSeq", "mediaSeqAudio", "discSeq", "discSeqAudio", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
|
|
686
|
+
let playheadState = await this._playheadState.getValues(["mediaSeq", "mediaSeqAudio", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
|
|
650
687
|
let currentVod = await this._sessionState.getCurrentVod();
|
|
651
688
|
if (!currentVod ||
|
|
652
689
|
sessionState.vodMediaSeqVideo === null ||
|
|
@@ -678,14 +715,16 @@ class Session {
|
|
|
678
715
|
}
|
|
679
716
|
this.isAllowedToClearVodCache = true;
|
|
680
717
|
}
|
|
681
|
-
} else {
|
|
718
|
+
} else { // TODO ADD BETTER INCREMENT
|
|
682
719
|
sessionState.vodMediaSeqVideo = await this._sessionState.increment("vodMediaSeqVideo");
|
|
683
|
-
|
|
720
|
+
if (incOpts) {
|
|
721
|
+
sessionState.vodMediaSeqAudio = await this._sessionState.increment("vodMediaSeqAudio", incOpts.audio);
|
|
722
|
+
}
|
|
684
723
|
}
|
|
685
724
|
|
|
686
725
|
if (sessionState.vodMediaSeqVideo >= currentVod.getLiveMediaSequencesCount() - 1) {
|
|
687
726
|
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", currentVod.getLiveMediaSequencesCount() - 1);
|
|
688
|
-
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount() - 1);
|
|
727
|
+
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount("audio") - 1);
|
|
689
728
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INIT);
|
|
690
729
|
}
|
|
691
730
|
|
|
@@ -695,6 +734,7 @@ class Session {
|
|
|
695
734
|
}
|
|
696
735
|
|
|
697
736
|
playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
|
|
737
|
+
playheadState.mediaSeqAudio = await this._playheadState.set("mediaSeqAudio", sessionState.mediaSeqAudio);
|
|
698
738
|
playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
|
|
699
739
|
playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
|
|
700
740
|
|
|
@@ -703,7 +743,7 @@ class Session {
|
|
|
703
743
|
debug(`[${this._sessionId}]: Session can now serve mseq={${playheadState.mediaSeq + mediaSequenceValue}}`);
|
|
704
744
|
}
|
|
705
745
|
|
|
706
|
-
debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio} of ${currentVod.getLiveMediaSequencesCount()})`);
|
|
746
|
+
debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio} of ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
|
|
707
747
|
|
|
708
748
|
// As a FOLLOWER, we might need to read up from shared store...
|
|
709
749
|
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
@@ -819,15 +859,15 @@ class Session {
|
|
|
819
859
|
let timeSinceLastRequest = (tsLastRequestAudio === null) ? 0 : Date.now() - tsLastRequestAudio;
|
|
820
860
|
|
|
821
861
|
let sessionState = await this._sessionState.getValues(
|
|
822
|
-
["state", "vodMediaSeqVideo", "vodMediaSeqAudio", "
|
|
862
|
+
["state", "vodMediaSeqVideo", "vodMediaSeqAudio", "mediaSeqAudio", "discSeqAudio", "lastM3u8"]);
|
|
823
863
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
824
864
|
if (sessionState.state !== SessionState.VOD_NEXT_INITIATING) {
|
|
825
865
|
let sequencesToIncrement = Math.ceil(timeSinceLastRequest / this.averageSegmentDuration);
|
|
826
866
|
|
|
827
867
|
if (sessionState.vodMediaSeqAudio < sessionState.vodMediaSeqVideo) {
|
|
828
868
|
sessionState.vodMediaSeqAudio = await this._sessionState.increment("vodMediaSeqAudio", sequencesToIncrement);
|
|
829
|
-
if (sessionState.vodMediaSeqAudio >= currentVod.getLiveMediaSequencesCount() - 1) {
|
|
830
|
-
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount() - 1);
|
|
869
|
+
if (sessionState.vodMediaSeqAudio >= currentVod.getLiveMediaSequencesCount("audio") - 1) {
|
|
870
|
+
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount("audio") - 1);
|
|
831
871
|
}
|
|
832
872
|
}
|
|
833
873
|
}
|
|
@@ -835,7 +875,7 @@ class Session {
|
|
|
835
875
|
debug(`[${this._sessionId}]: AUDIO ${timeSinceLastRequest} (${this.averageSegmentDuration}) audioGroupId=${audioGroupId} audioLanguage=${audioLanguage} vodMediaSeq=(${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio})`);
|
|
836
876
|
let m3u8;
|
|
837
877
|
try {
|
|
838
|
-
m3u8 = currentVod.getLiveMediaAudioSequences(sessionState.
|
|
878
|
+
m3u8 = currentVod.getLiveMediaAudioSequences(sessionState.mediaSeqAudio, audioGroupId, audioLanguage, sessionState.vodMediaSeqAudio, sessionState.discSeqAudio);
|
|
839
879
|
} catch (exc) {
|
|
840
880
|
if (sessionState.lastM3u8[audioGroupId][audioLanguage]) {
|
|
841
881
|
m3u8 = sessionState.lastM3u8[audioGroupId][audioLanguage];
|
|
@@ -847,7 +887,7 @@ class Session {
|
|
|
847
887
|
let lastM3u8 = sessionState.lastM3u8;
|
|
848
888
|
lastM3u8[audioGroupId] = {};
|
|
849
889
|
lastM3u8[audioGroupId][audioLanguage] = m3u8;
|
|
850
|
-
sessionState.lastM3u8 = await this._sessionState.set("lastM3u8", lastM3u8);
|
|
890
|
+
sessionState.lastM3u8 = await this._sessionState.set("lastM3u8", lastM3u8); // for audio?
|
|
851
891
|
sessionState.tsLastRequestAudio = await this._sessionState.set("tsLastRequestAudio", Date.now());
|
|
852
892
|
return m3u8;
|
|
853
893
|
}
|
|
@@ -954,23 +994,30 @@ class Session {
|
|
|
954
994
|
console.error(`[${this._sessionId}]: Will insert slate`);
|
|
955
995
|
const slateVod = await this._loadSlate(currentVod);
|
|
956
996
|
debug(`[${this._sessionId}]: slate loaded`);
|
|
957
|
-
const sessionState = await this._sessionState.getValues(["slateCount", "mediaSeq", "discSeq"]);
|
|
997
|
+
const sessionState = await this._sessionState.getValues(["slateCount", "mediaSeq", "discSeq", "mediaSeqAudio", "discSeqAudio"]);
|
|
958
998
|
let endValue = 0;
|
|
999
|
+
let endValueAudio = 0;
|
|
959
1000
|
let lastDiscontinuity = 0;
|
|
1001
|
+
let lastDiscontinuityAudio = 0;
|
|
960
1002
|
if (currentVod) {
|
|
961
1003
|
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
962
1004
|
endValue = currentVod.getLastSequenceMediaSequenceValue();
|
|
1005
|
+
endValueAudio = currentVod.getLastSequenceMediaSequenceValueAudio();
|
|
963
1006
|
} else {
|
|
964
1007
|
endValue = currentVod.getLiveMediaSequencesCount();
|
|
1008
|
+
endValueAudio = currentVod.getLiveMediaSequencesCount("audio");
|
|
965
1009
|
}
|
|
966
1010
|
lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
1011
|
+
lastDiscontinuityAudio = currentVod.getLastDiscontinuityAudio();
|
|
967
1012
|
}
|
|
968
1013
|
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
969
1014
|
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
970
1015
|
await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
971
1016
|
await this._sessionState.setCurrentVod(slateVod);
|
|
972
1017
|
await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endValue);
|
|
1018
|
+
await this._sessionState.set("mediaSeqAudio", sessionState.mediaSeqAudio + endValueAudio);
|
|
973
1019
|
await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
1020
|
+
await this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio);
|
|
974
1021
|
await this._sessionState.set("slateCount", sessionState.slateCount + 1);
|
|
975
1022
|
await this._playheadState.set("playheadRef", Date.now());
|
|
976
1023
|
|
|
@@ -986,7 +1033,7 @@ class Session {
|
|
|
986
1033
|
let newVod;
|
|
987
1034
|
|
|
988
1035
|
let sessionState = await this._sessionState.getValues(
|
|
989
|
-
["state", "assetId", "vodMediaSeqVideo", "vodMediaSeqAudio", "mediaSeq", "discSeq", "nextVod"]);
|
|
1036
|
+
["state", "assetId", "vodMediaSeqVideo", "vodMediaSeqAudio", "mediaSeq", "mediaSeqAudio", "discSeq", "discSeqAudio", "nextVod"]);
|
|
990
1037
|
|
|
991
1038
|
let isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
992
1039
|
|
|
@@ -1022,7 +1069,7 @@ class Session {
|
|
|
1022
1069
|
let loadPromise;
|
|
1023
1070
|
if (!vodResponse.type) {
|
|
1024
1071
|
debug(`[${this._sessionId}]: got first VOD uri=${vodResponse.uri}:${vodResponse.offset || 0}`);
|
|
1025
|
-
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1072
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
|
|
1026
1073
|
newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
|
|
1027
1074
|
if (vodResponse.timedMetadata) {
|
|
1028
1075
|
Object.keys(vodResponse.timedMetadata).map(k => {
|
|
@@ -1051,7 +1098,9 @@ class Session {
|
|
|
1051
1098
|
debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
|
|
1052
1099
|
//debug(newVod);
|
|
1053
1100
|
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
|
|
1101
|
+
sessionState.mediaSeqAudio = await this._sessionState.set("mediaSeqAudio", 0);
|
|
1054
1102
|
sessionState.discSeq = await this._sessionState.set("discSeq", 0);
|
|
1103
|
+
sessionState.discSeqAudio = await this._sessionState.set("discSeqAudio", 0);
|
|
1055
1104
|
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
1056
1105
|
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
1057
1106
|
await this._playheadState.set("playheadRef", Date.now());
|
|
@@ -1112,10 +1161,10 @@ class Session {
|
|
|
1112
1161
|
this.isAllowedToClearVodCache = true;
|
|
1113
1162
|
}
|
|
1114
1163
|
}
|
|
1115
|
-
debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
1164
|
+
debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
|
|
1116
1165
|
return;
|
|
1117
1166
|
case SessionState.VOD_NEXT_INITIATING:
|
|
1118
|
-
debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
1167
|
+
debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
|
|
1119
1168
|
if (!isLeader) {
|
|
1120
1169
|
debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be initiated`);
|
|
1121
1170
|
}
|
|
@@ -1133,12 +1182,16 @@ class Session {
|
|
|
1133
1182
|
}
|
|
1134
1183
|
const length = currentVod.getLiveMediaSequencesCount();
|
|
1135
1184
|
let endMseqValue;
|
|
1185
|
+
let endMseqValueAudio;
|
|
1136
1186
|
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
1137
1187
|
endMseqValue = currentVod.getLastSequenceMediaSequenceValue();
|
|
1188
|
+
endMseqValueAudio = currentVod.getLastSequenceMediaSequenceValueAudio();
|
|
1138
1189
|
} else {
|
|
1139
1190
|
endMseqValue = currentVod.getLiveMediaSequencesCount();
|
|
1191
|
+
endMseqValueAudio = currentVod.getLiveMediaSequencesCount("audio");
|
|
1140
1192
|
}
|
|
1141
1193
|
const lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
1194
|
+
const lastDiscontinuityAudio = currentVod.getLastDiscontinuityAudio();
|
|
1142
1195
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
1143
1196
|
let vodPromise = this._getNextVod();
|
|
1144
1197
|
if (length === 1) {
|
|
@@ -1155,7 +1208,7 @@ class Session {
|
|
|
1155
1208
|
let loadPromise;
|
|
1156
1209
|
if (!vodResponse.type) {
|
|
1157
1210
|
debug(`[${this._sessionId}]: got next VOD uri=${vodResponse.uri}:${vodResponse.offset}`);
|
|
1158
|
-
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1211
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
|
|
1159
1212
|
newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
|
|
1160
1213
|
if (vodResponse.timedMetadata) {
|
|
1161
1214
|
Object.keys(vodResponse.timedMetadata).map(k => {
|
|
@@ -1198,12 +1251,14 @@ class Session {
|
|
|
1198
1251
|
debug(`[${this._sessionId}]: next VOD loaded (${newVod.getDeltaTimes()})`);
|
|
1199
1252
|
debug(`[${this._sessionId}]: ${newVod.getPlayheadPositions()}`);
|
|
1200
1253
|
currentVod = newVod;
|
|
1201
|
-
debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
|
|
1254
|
+
debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}; audio msequences=${currentVod.getLiveMediaSequencesCount("audio")}`);
|
|
1202
1255
|
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
1203
1256
|
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
1204
1257
|
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + endMseqValue);
|
|
1258
|
+
sessionState.mediaSeqAudio = await this._sessionState.set("mediaSeqAudio", sessionState.mediaSeqAudio + endMseqValueAudio);
|
|
1205
1259
|
sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
1206
|
-
|
|
1260
|
+
sessionState.discSeqAudio = await this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio);
|
|
1261
|
+
debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]_[${sessionState.mediaSeqAudio}][${sessionState.discSeqAudio}]`);
|
|
1207
1262
|
await this._sessionState.remove("nextVod");
|
|
1208
1263
|
sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
1209
1264
|
this.leaderIsSettingNextVod = false;
|
|
@@ -1265,6 +1320,7 @@ class Session {
|
|
|
1265
1320
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
|
|
1266
1321
|
// 2) Set new 'offset' sequences, to carry on the continuity from session-live
|
|
1267
1322
|
let mSeq = this.switchDataForSession.mediaSeq;
|
|
1323
|
+
// TODO: support demux^
|
|
1268
1324
|
let currentVod = await this._sessionState.getCurrentVod();
|
|
1269
1325
|
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
1270
1326
|
// (!) will need to compensate if using this setting on HLSVod Object.
|
|
@@ -1286,6 +1342,7 @@ class Session {
|
|
|
1286
1342
|
await this._sessionState.set("mediaSeq", mSeq);
|
|
1287
1343
|
await this._playheadState.set("mediaSeq", mSeq);
|
|
1288
1344
|
await this._sessionState.set("discSeq", dSeq);
|
|
1345
|
+
// TODO: support demux^
|
|
1289
1346
|
debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
|
|
1290
1347
|
// 3) Set new media segments/currentVod, to carry on the continuity from session-live
|
|
1291
1348
|
debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
|
|
@@ -1324,7 +1381,7 @@ class Session {
|
|
|
1324
1381
|
}
|
|
1325
1382
|
break;
|
|
1326
1383
|
case SessionState.VOD_RELOAD_INITIATING:
|
|
1327
|
-
debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
1384
|
+
debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")})`);
|
|
1328
1385
|
if (!isLeader) {
|
|
1329
1386
|
debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be reloaded`);
|
|
1330
1387
|
if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
@@ -1381,7 +1438,7 @@ class Session {
|
|
|
1381
1438
|
|
|
1382
1439
|
slateVod.load()
|
|
1383
1440
|
.then(() => {
|
|
1384
|
-
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1441
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
|
|
1385
1442
|
hlsVod = new HLSVod(this.slateUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
|
|
1386
1443
|
const timestamp = Date.now();
|
|
1387
1444
|
hlsVod.addMetadata('id', `slate-${timestamp}`);
|
|
@@ -1393,11 +1450,26 @@ class Session {
|
|
|
1393
1450
|
mediaManifestStream.push(null);
|
|
1394
1451
|
return mediaManifestStream;
|
|
1395
1452
|
};
|
|
1396
|
-
if (
|
|
1397
|
-
|
|
1453
|
+
if (this.use_demuxed_audio) {
|
|
1454
|
+
const slateAudioManifestLoader = (audioGroupId, audioLanguage) => {
|
|
1455
|
+
let mediaManifestStream = new Readable();
|
|
1456
|
+
mediaManifestStream.push(slateVod.getAudioManifest(audioGroupId, audioLanguage));
|
|
1457
|
+
mediaManifestStream.push(null);
|
|
1458
|
+
return mediaManifestStream;
|
|
1459
|
+
};
|
|
1460
|
+
if (afterVod) {
|
|
1461
|
+
return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader, slateAudioManifestLoader);
|
|
1462
|
+
} else {
|
|
1463
|
+
return hlsVod.load(null, slateMediaManifestLoader, slateAudioManifestLoader);
|
|
1464
|
+
}
|
|
1398
1465
|
} else {
|
|
1399
|
-
|
|
1466
|
+
if (afterVod) {
|
|
1467
|
+
return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader);
|
|
1468
|
+
} else {
|
|
1469
|
+
return hlsVod.load(null, slateMediaManifestLoader);
|
|
1470
|
+
}
|
|
1400
1471
|
}
|
|
1472
|
+
|
|
1401
1473
|
})
|
|
1402
1474
|
.then(() => {
|
|
1403
1475
|
resolve(hlsVod);
|
|
@@ -1426,7 +1498,7 @@ class Session {
|
|
|
1426
1498
|
|
|
1427
1499
|
slateVod.load()
|
|
1428
1500
|
.then(() => {
|
|
1429
|
-
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments };
|
|
1501
|
+
const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
|
|
1430
1502
|
hlsVod = new HLSVod(nexVodUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
|
|
1431
1503
|
const timestamp = Date.now();
|
|
1432
1504
|
hlsVod.addMetadata('id', `slate-${timestamp}`);
|
|
@@ -1438,10 +1510,24 @@ class Session {
|
|
|
1438
1510
|
mediaManifestStream.push(null);
|
|
1439
1511
|
return mediaManifestStream;
|
|
1440
1512
|
};
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1513
|
+
if (this.use_demuxed_audio) {
|
|
1514
|
+
const slateAudioManifestLoader = (audioGroupId, audioLanguage) => {
|
|
1515
|
+
let mediaManifestStream = new Readable();
|
|
1516
|
+
mediaManifestStream.push(slateVod.getAudioManifest(audioGroupId, audioLanguage));
|
|
1517
|
+
mediaManifestStream.push(null);
|
|
1518
|
+
return mediaManifestStream;
|
|
1519
|
+
};
|
|
1520
|
+
if (afterVod) {
|
|
1521
|
+
return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader, slateAudioManifestLoader);
|
|
1522
|
+
} else {
|
|
1523
|
+
return hlsVod.load(null, slateMediaManifestLoader, slateAudioManifestLoader);
|
|
1524
|
+
}
|
|
1443
1525
|
} else {
|
|
1444
|
-
|
|
1526
|
+
if (afterVod) {
|
|
1527
|
+
return hlsVod.loadAfter(afterVod, null, slateMediaManifestLoader);
|
|
1528
|
+
} else {
|
|
1529
|
+
return hlsVod.load(null, slateMediaManifestLoader);
|
|
1530
|
+
}
|
|
1445
1531
|
}
|
|
1446
1532
|
})
|
|
1447
1533
|
.then(() => {
|
|
@@ -1538,6 +1624,13 @@ class Session {
|
|
|
1538
1624
|
return playheadPositions[sessionState.vodMediaSeqVideo];
|
|
1539
1625
|
}
|
|
1540
1626
|
|
|
1627
|
+
async _getAudioPlayheadPosition(seqIdx) {
|
|
1628
|
+
const currentVod = await this._sessionState.getCurrentVod();
|
|
1629
|
+
const playheadPositions = currentVod.getPlayheadPositions("audio");
|
|
1630
|
+
debug(`[${this._sessionId}]: Current audio playhead position (${seqIdx}): ${playheadPositions[seqIdx]}`);
|
|
1631
|
+
return playheadPositions[seqIdx];
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1541
1634
|
_getLastDuration(manifest) {
|
|
1542
1635
|
return new Promise((resolve, reject) => {
|
|
1543
1636
|
try {
|
package/engine/session_state.js
CHANGED
|
@@ -131,6 +131,8 @@ class SessionStateStore extends SharedStateStore {
|
|
|
131
131
|
super("session", opts, {
|
|
132
132
|
mediaSeq: 0,
|
|
133
133
|
discSeq: 0,
|
|
134
|
+
mediaSeqAudio: 0,
|
|
135
|
+
discSeqAudio: 0,
|
|
134
136
|
vodMediaSeqVideo: 0,
|
|
135
137
|
vodMediaSeqAudio: 0, // assume only one audio group now
|
|
136
138
|
state: SessionState.VOD_INIT,
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyevinn-channel-engine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.0",
|
|
4
4
|
"description": "OTT TV Channel Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc --project ./ && cp ./package.json dist/",
|
|
9
9
|
"debug-server": "DEBUG=engine-*,vod-to-live node dist/server.js",
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "jasmine",
|
|
11
11
|
"postversion": "git push && git push --tags",
|
|
12
12
|
"start": "node dist/server.js",
|
|
13
13
|
"start-demux": "node dist/server-demux.js",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"repository": "https://github.com/Eyevinn/channel-engine",
|
|
24
24
|
"license": "Apache-2.0",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@eyevinn/hls-repeat": "^0.
|
|
27
|
-
"@eyevinn/hls-truncate": "^0.
|
|
28
|
-
"@eyevinn/hls-vodtolive": "^2.
|
|
29
|
-
"@eyevinn/m3u8": "^0.
|
|
26
|
+
"@eyevinn/hls-repeat": "^0.2.0",
|
|
27
|
+
"@eyevinn/hls-truncate": "^0.2.0",
|
|
28
|
+
"@eyevinn/hls-vodtolive": "^2.1.1",
|
|
29
|
+
"@eyevinn/m3u8": "^0.5.3",
|
|
30
30
|
"abort-controller": "^3.0.0",
|
|
31
31
|
"debug": "^3.2.7",
|
|
32
32
|
"memcache-client": "^0.10.1",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"promise.allsettled": "^1.0.4",
|
|
36
36
|
"redis": "^3.0.2",
|
|
37
37
|
"request": ">=2.88.0",
|
|
38
|
-
"restify": "
|
|
38
|
+
"restify": "9.0.0",
|
|
39
39
|
"restify-errors": "^8.0.2",
|
|
40
40
|
"uuid": "^8.3.2"
|
|
41
41
|
},
|
package/server-demux.ts
CHANGED
|
@@ -2,26 +2,53 @@
|
|
|
2
2
|
* Reference implementation of Channel Engine library using demuxed VOD assets.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import {
|
|
6
|
+
ChannelEngine,
|
|
7
|
+
ChannelEngineOpts,
|
|
8
|
+
IAssetManager,
|
|
9
|
+
IChannelManager,
|
|
10
|
+
VodRequest,
|
|
11
|
+
VodResponse,
|
|
12
|
+
Channel,
|
|
13
|
+
ChannelProfile,
|
|
14
|
+
AudioTracks,
|
|
9
15
|
} from "./index";
|
|
10
16
|
|
|
17
|
+
const STITCH_ENDPOINT = "http://localhost:8000/stitch/master.m3u8";
|
|
18
|
+
|
|
11
19
|
class RefAssetManager implements IAssetManager {
|
|
12
20
|
private assets;
|
|
13
21
|
private pos;
|
|
14
22
|
constructor(opts?) {
|
|
15
23
|
this.assets = {
|
|
16
24
|
1: [
|
|
25
|
+
/* #TS+DEMUX HLS VODS */
|
|
26
|
+
// {
|
|
27
|
+
// id: 1,
|
|
28
|
+
// title: "Elephants Dream",
|
|
29
|
+
// uri: "https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8",
|
|
30
|
+
// },
|
|
31
|
+
// {
|
|
32
|
+
// id: 2,
|
|
33
|
+
// title: "Test HLS Bird noises (1m10s)",
|
|
34
|
+
// uri: "https://mtoczko.github.io/hls-test-streams/test-audio-pdt/playlist.m3u8",
|
|
35
|
+
// },
|
|
36
|
+
/* #CMAF+DEMUX HLS VODS */
|
|
17
37
|
{
|
|
18
38
|
id: 1,
|
|
19
|
-
title: "
|
|
20
|
-
uri: "https://
|
|
39
|
+
title: "Nymo Bloopers",
|
|
40
|
+
uri: "https://vod.streaming.a2d.tv/a07ff4eb-6770-4805-a0ad-a4d1b127880d/4fef8b00-6d0b-11ed-89b6-2b1a288899a0_20356478.ism/.m3u8"
|
|
41
|
+
},
|
|
21
42
|
{
|
|
22
43
|
id: 2,
|
|
23
|
-
title: "
|
|
24
|
-
uri: "https://
|
|
44
|
+
title: "Benjamin Sjunger",
|
|
45
|
+
uri: "https://vod.streaming.a2d.tv/3f389c48-03e3-48a2-8e98-a02c55185a68/4c792a30-89ad-11ed-95d9-1b374c4e2f9f_20411056.ism/.m3u8"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 3,
|
|
49
|
+
title: "Idol Vinnarlåten",
|
|
50
|
+
uri: "https://vod.streaming.a2d.tv/369f8dd6-bc62-40a3-b734-4bc1cb50c00b/e9ef93d0-6d10-11ed-83d0-17c0db863fe0_20356484.ism/.m3u8"
|
|
51
|
+
},
|
|
25
52
|
],
|
|
26
53
|
};
|
|
27
54
|
this.pos = {
|
|
@@ -47,8 +74,32 @@ class RefAssetManager implements IAssetManager {
|
|
|
47
74
|
this.pos[channelId] = 0;
|
|
48
75
|
}
|
|
49
76
|
vod.timedMetadata = {
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
"start-date": new Date().toISOString(),
|
|
78
|
+
class: "se.eyevinn.demo",
|
|
79
|
+
};
|
|
80
|
+
const payload = {
|
|
81
|
+
uri: vod.uri,
|
|
82
|
+
breaks: [
|
|
83
|
+
/* #TS+DEMUX HLS BREAK VOD */
|
|
84
|
+
// {
|
|
85
|
+
// pos: 0,
|
|
86
|
+
// duration: 60 * 1000,
|
|
87
|
+
// url: "https://lab-live.cdn.eyevinn.technology/DEMUX_002/master_demux_aac-en-fr.m3u8",
|
|
88
|
+
// },
|
|
89
|
+
/* #CMAF+DEMUX HLS BREAK VOD */
|
|
90
|
+
{
|
|
91
|
+
pos: 0,
|
|
92
|
+
duration: 20 * 1000,
|
|
93
|
+
url: "https://ovpuspvod.a2d-stage.tv/trailers/63ef9c36e3ffa90028603374/output.ism/.m3u8",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
const buff = Buffer.from(JSON.stringify(payload));
|
|
98
|
+
const encodedPayload = buff.toString("base64");
|
|
99
|
+
vod = {
|
|
100
|
+
id: vod.id,
|
|
101
|
+
title: vod.title,
|
|
102
|
+
uri: STITCH_ENDPOINT + "?payload=" + encodedPayload,
|
|
52
103
|
};
|
|
53
104
|
resolve(vod);
|
|
54
105
|
} else {
|
|
@@ -60,7 +111,7 @@ class RefAssetManager implements IAssetManager {
|
|
|
60
111
|
|
|
61
112
|
class RefChannelManager implements IChannelManager {
|
|
62
113
|
getChannels(): Channel[] {
|
|
63
|
-
return [
|
|
114
|
+
return [{ id: "1", profile: this._getProfile(), audioTracks: this._getAudioTracks() }];
|
|
64
115
|
}
|
|
65
116
|
|
|
66
117
|
_getProfile(): ChannelProfile[] {
|
|
@@ -82,11 +133,7 @@ class RefChannelManager implements IChannelManager {
|
|
|
82
133
|
];
|
|
83
134
|
}
|
|
84
135
|
_getAudioTracks(): AudioTracks[] {
|
|
85
|
-
return [
|
|
86
|
-
{ language: "sp", name: "Spanish" },
|
|
87
|
-
{ language: "ru", name: "Russian" },
|
|
88
|
-
{ language: "en", name: "English", default: true },
|
|
89
|
-
];
|
|
136
|
+
return [{ language: "Swedish", name: "Swedish" }];
|
|
90
137
|
}
|
|
91
138
|
}
|
|
92
139
|
|
|
@@ -97,13 +144,16 @@ const engineOptions: ChannelEngineOpts = {
|
|
|
97
144
|
heartbeat: "/",
|
|
98
145
|
averageSegmentDuration: 2000,
|
|
99
146
|
channelManager: refChannelManager,
|
|
100
|
-
|
|
101
|
-
|
|
147
|
+
/* #TS+DEMUX SLATE VOD */
|
|
148
|
+
defaultSlateUri: "https://ovpuspvod.a2d-stage.tv/trailers/bumpers/tv4_spring/output.ism/.m3u8",
|
|
149
|
+
/* #CMAF+DEMUX SLATE VOD */
|
|
150
|
+
//defaultSlateUri: "https://ovpuspvod.a2d-stage.tv/trailers/bumpers/tv4_spring/output.ism/.m3u8",
|
|
102
151
|
slateRepetitions: 10,
|
|
103
152
|
redisUrl: process.env.REDIS_URL,
|
|
104
153
|
useDemuxedAudio: true,
|
|
154
|
+
alwaysNewSegments: true,
|
|
105
155
|
};
|
|
106
156
|
|
|
107
157
|
const engine = new ChannelEngine(refAssetManager, engineOptions);
|
|
108
158
|
engine.start();
|
|
109
|
-
engine.listen(process.env.
|
|
159
|
+
engine.listen(process.env.PORT || 8000);
|