eyevinn-channel-engine 4.1.1 → 4.2.0-rc.2

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.
@@ -138,6 +138,7 @@ var PlayheadStateStore = /** @class */ (function (_super) {
138
138
  mediaSeq: 0,
139
139
  vodMediaSeqVideo: 0,
140
140
  vodMediaSeqAudio: 0,
141
+ vodMediaSeqSubtitle: 0,
141
142
  }) || this;
142
143
  }
143
144
  PlayheadStateStore.prototype.create = function (sessionId) {
@@ -13,6 +13,9 @@ export interface ChannelEngineOpts {
13
13
  maxTickInterval?: number;
14
14
  cloudWatchMetrics?: boolean;
15
15
  useDemuxedAudio?: boolean;
16
+ dummySubtitleEndpoint?: string;
17
+ subtitleSliceEndpoint?: string;
18
+ useVTTSubtitles?: boolean;
16
19
  alwaysNewSegments?: boolean;
17
20
  diffCompensationRate?: number;
18
21
  staticDirectory?: string;
@@ -66,6 +69,7 @@ export interface Channel {
66
69
  id: string;
67
70
  profile: ChannelProfile[];
68
71
  audioTracks?: AudioTracks[];
72
+ subtitleTracks?: SubtitleTracks[];
69
73
  closedCaptions?: ClosedCaptions[];
70
74
  }
71
75
  export interface ClosedCaptions {
@@ -81,6 +85,11 @@ export interface AudioTracks {
81
85
  default?: boolean;
82
86
  enforceAudioGroupId?: string;
83
87
  }
88
+ export interface SubtitleTracks {
89
+ language: string;
90
+ name: string;
91
+ default?: boolean;
92
+ }
84
93
  export interface IChannelManager {
85
94
  getChannels: () => Channel[];
86
95
  }
@@ -104,6 +113,9 @@ export interface IStreamSwitchManager {
104
113
  export declare class ChannelEngine {
105
114
  private options?;
106
115
  private useDemuxedAudio;
116
+ private dummySubtitleEndpoint;
117
+ private subtitleSliceEndpoint;
118
+ private useVTTSubtitles;
107
119
  private alwaysNewSegments;
108
120
  private defaultSlateUri?;
109
121
  private slateDuration?;
@@ -133,9 +145,13 @@ export declare class ChannelEngine {
133
145
  getMasterManifest(channelId: any): Promise<any>;
134
146
  getMediaManifests(channelId: any): Promise<{}>;
135
147
  getAudioManifests(channelId: any): Promise<{}>;
148
+ getSubtitleManifests(channelId: any): Promise<{}>;
136
149
  _handleHeartbeat(req: any, res: any, next: any): void;
137
150
  _handleMasterManifest(req: any, res: any, next: any): Promise<void>;
138
151
  _handleAudioManifest(req: any, res: any, next: any): Promise<void>;
152
+ _handleSubtitleManifest(req: any, res: any, next: any): Promise<void>;
153
+ _handleDummySubtitleEndpoint(req: any, res: any, next: any): Promise<void>;
154
+ _handleSubtitleSliceEndpoint(req: any, res: any, next: any): Promise<void>;
139
155
  _handleMediaManifest(req: any, res: any, next: any): Promise<void>;
140
156
  _handleEventStream(req: any, res: any, next: any): void;
141
157
  _handleStatus(req: any, res: any, next: any): Promise<void>;
@@ -46,6 +46,7 @@ var Session = require('./session.js');
46
46
  var SessionLive = require('./session_live.js');
47
47
  var StreamSwitcher = require('./stream_switcher.js');
48
48
  var EventStream = require('./event_stream.js');
49
+ var SubtitleSlicer = require('./subtitle_slicer.js');
49
50
  var SessionStateStore = require('./session_state.js').SessionStateStore;
50
51
  var SessionLiveStateStore = require('./session_live_state.js').SessionLiveStateStore;
51
52
  var PlayheadStateStore = require('./playhead_state.js').PlayheadStateStore;
@@ -57,6 +58,8 @@ var sessionsLive = {}; // Should be a persistent store...
57
58
  var sessionSwitchers = {}; // Should be a persistent store...
58
59
  var switcherStatus = {}; // Should be a persistent store...
59
60
  var eventStreams = {};
61
+ var DefaultDummySubtitleEndpointPath = "dummyUrl";
62
+ var DefaultSubtitleSpliceEndpointPath = "sliceUrl";
60
63
  var ScheduleStreamType;
61
64
  (function (ScheduleStreamType) {
62
65
  ScheduleStreamType[ScheduleStreamType["LIVE"] = 1] = "LIVE";
@@ -77,6 +80,9 @@ var ChannelEngine = /** @class */ (function () {
77
80
  if (options && options.useDemuxedAudio) {
78
81
  this.useDemuxedAudio = true;
79
82
  }
83
+ this.useVTTSubtitles = (options && options.useVTTSubtitles) ? options.useVTTSubtitles : false;
84
+ this.dummySubtitleEndpoint = (options && options.dummySubtitleEndpoint) ? options.dummySubtitleEndpoint : DefaultDummySubtitleEndpointPath;
85
+ this.subtitleSliceEndpoint = (options && options.subtitleSliceEndpoint) ? options.dummySubtitleEndpoint : DefaultSubtitleSpliceEndpointPath;
80
86
  this.alwaysNewSegments = false;
81
87
  if (options && options.alwaysNewSegments) {
82
88
  this.alwaysNewSegments = true;
@@ -162,7 +168,7 @@ var ChannelEngine = /** @class */ (function () {
162
168
  return [4 /*yield*/, this._handleMasterManifest(req, res, next)];
163
169
  case 1:
164
170
  _a.sent();
165
- return [3 /*break*/, 6];
171
+ return [3 /*break*/, 8];
166
172
  case 2:
167
173
  if (!(m = req.params.file.match(/master(\d+).m3u8;session=(.*)$/))) return [3 /*break*/, 4];
168
174
  req.params[0] = m[1];
@@ -170,7 +176,7 @@ var ChannelEngine = /** @class */ (function () {
170
176
  return [4 /*yield*/, this._handleMediaManifest(req, res, next)];
171
177
  case 3:
172
178
  _a.sent();
173
- return [3 /*break*/, 6];
179
+ return [3 /*break*/, 8];
174
180
  case 4:
175
181
  if (!(m = req.params.file.match(/master-(\S+)_(\S+).m3u8;session=(.*)$/))) return [3 /*break*/, 6];
176
182
  req.params[0] = m[1];
@@ -179,8 +185,17 @@ var ChannelEngine = /** @class */ (function () {
179
185
  return [4 /*yield*/, this._handleAudioManifest(req, res, next)];
180
186
  case 5:
181
187
  _a.sent();
182
- _a.label = 6;
183
- case 6: return [2 /*return*/];
188
+ return [3 /*break*/, 8];
189
+ case 6:
190
+ if (!(m = req.params.file.match(/subtitles-(\S+)_(\S+).m3u8;session=(.*)$/))) return [3 /*break*/, 8];
191
+ req.params[0] = m[1];
192
+ req.params[1] = m[2];
193
+ req.params[2] = m[3];
194
+ return [4 /*yield*/, this._handleSubtitleManifest(req, res, next)];
195
+ case 7:
196
+ _a.sent();
197
+ _a.label = 8;
198
+ case 8: return [2 /*return*/];
184
199
  }
185
200
  });
186
201
  }); };
@@ -235,6 +250,8 @@ var ChannelEngine = /** @class */ (function () {
235
250
  this.server.get('/health', this._handleAggregatedSessionHealth.bind(this));
236
251
  this.server.get('/health/:sessionId', this._handleSessionHealth.bind(this));
237
252
  this.server.get('/reset', this._handleSessionReset.bind(this));
253
+ this.server.get('/channels/:channelId/' + this.dummySubtitleEndpoint, this._handleDummySubtitleEndpoint.bind(this));
254
+ this.server.get('/channels/:channelId/' + this.subtitleSliceEndpoint, this._handleSubtitleSliceEndpoint.bind(this));
238
255
  this.server.on('NotFound', function (req, res, err, next) {
239
256
  res.header("X-Instance-Id", _this.instanceId + "<".concat(version, ">"));
240
257
  return next();
@@ -410,6 +427,9 @@ var ChannelEngine = /** @class */ (function () {
410
427
  sessionId: channel.id,
411
428
  averageSegmentDuration: channel.options && channel.options.averageSegmentDuration ? channel.options.averageSegmentDuration : this.streamerOpts.defaultAverageSegmentDuration,
412
429
  useDemuxedAudio: options.useDemuxedAudio,
430
+ dummySubtitleEndpoint: this.dummySubtitleEndpoint,
431
+ subtitleSliceEndpoint: this.subtitleSliceEndpoint,
432
+ useVTTSubtitles: this.useVTTSubtitles,
413
433
  alwaysNewSegments: options.alwaysNewSegments,
414
434
  noSessionDataTags: options.noSessionDataTags,
415
435
  playheadDiffThreshold: channel.options && channel.options.playheadDiffThreshold ? channel.options.playheadDiffThreshold : this.streamerOpts.defaultPlayheadDiffThreshold,
@@ -419,6 +439,7 @@ var ChannelEngine = /** @class */ (function () {
419
439
  diffCompensationRate: channel.options && channel.options.diffCompensationRate ? channel.options.diffCompensationRate : this.streamerOpts.diffCompensationRate,
420
440
  profile: channel.profile,
421
441
  audioTracks: channel.audioTracks,
442
+ subtitleTracks: channel.subtitleTracks,
422
443
  closedCaptions: channel.closedCaptions,
423
444
  slateUri: channel.slate && channel.slate.uri ? channel.slate.uri : this.defaultSlateUri,
424
445
  slateRepetitions: channel.slate && channel.slate.repetitions ? channel.slate.repetitions : this.slateRepetitions,
@@ -428,6 +449,9 @@ var ChannelEngine = /** @class */ (function () {
428
449
  sessionsLive[channel.id] = new SessionLive({
429
450
  sessionId: channel.id,
430
451
  useDemuxedAudio: options.useDemuxedAudio,
452
+ dummySubtitleEndpoint: this.dummySubtitleEndpoint,
453
+ subtitleSliceEndpoint: this.subtitleSliceEndpoint,
454
+ useVTTSubtitles: this.useVTTSubtitles,
431
455
  cloudWatchMetrics: this.logCloudWatchMetrics,
432
456
  profile: channel.profile,
433
457
  }, this.sessionLiveStore);
@@ -741,6 +765,55 @@ var ChannelEngine = /** @class */ (function () {
741
765
  });
742
766
  });
743
767
  };
768
+ ChannelEngine.prototype.getSubtitleManifests = function (channelId) {
769
+ return __awaiter(this, void 0, void 0, function () {
770
+ var allSubtitleM3U8_1, promises_3, session_3, addM3U8_3, subtitleGroupsAndLangs, _loop_2, _i, _a, _b, subtitleGroup, languages, err;
771
+ var _this = this;
772
+ return __generator(this, function (_c) {
773
+ switch (_c.label) {
774
+ case 0:
775
+ if (!sessions[channelId]) return [3 /*break*/, 3];
776
+ allSubtitleM3U8_1 = {};
777
+ promises_3 = [];
778
+ session_3 = sessions[channelId];
779
+ addM3U8_3 = function (groupId, lang) { return __awaiter(_this, void 0, void 0, function () {
780
+ var subtitleM3U8;
781
+ return __generator(this, function (_a) {
782
+ switch (_a.label) {
783
+ case 0: return [4 /*yield*/, session_3.getCurrentSubtitleManifestAsync(groupId, lang)];
784
+ case 1:
785
+ subtitleM3U8 = _a.sent();
786
+ if (!allSubtitleM3U8_1[groupId]) {
787
+ allSubtitleM3U8_1[groupId] = {};
788
+ }
789
+ allSubtitleM3U8_1[groupId][lang] = subtitleM3U8;
790
+ return [2 /*return*/];
791
+ }
792
+ });
793
+ }); };
794
+ return [4 /*yield*/, session_3.getSubtitleGroupsAndLangs()];
795
+ case 1:
796
+ subtitleGroupsAndLangs = _c.sent();
797
+ _loop_2 = function (subtitleGroup, languages) {
798
+ languages.forEach(function (lang) {
799
+ promises_3.push(addM3U8_3(subtitleGroup, lang));
800
+ });
801
+ };
802
+ for (_i = 0, _a = Object.entries(subtitleGroupsAndLangs); _i < _a.length; _i++) {
803
+ _b = _a[_i], subtitleGroup = _b[0], languages = _b[1];
804
+ _loop_2(subtitleGroup, languages);
805
+ }
806
+ return [4 /*yield*/, Promise.all(promises_3)];
807
+ case 2:
808
+ _c.sent();
809
+ return [2 /*return*/, allSubtitleM3U8_1];
810
+ case 3:
811
+ err = new errs.NotFoundError('Invalid session');
812
+ return [2 /*return*/, Promise.reject(err)];
813
+ }
814
+ });
815
+ });
816
+ };
744
817
  ChannelEngine.prototype._handleHeartbeat = function (req, res, next) {
745
818
  debug('req.url=' + req.url);
746
819
  res.send(200);
@@ -774,6 +847,9 @@ var ChannelEngine = /** @class */ (function () {
774
847
  options.adXchangeUri = this.adXchangeUri;
775
848
  options.averageSegmentDuration = this.streamerOpts.defaultAverageSegmentDuration;
776
849
  options.useDemuxedAudio = this.useDemuxedAudio;
850
+ options.dummySubtitleEndpoint = this.dummySubtitleEndpoint;
851
+ options.subtitleSliceEndpoint = this.subtitleSliceEndpoint;
852
+ options.useVTTSubtitles = this.useVTTSubtitles;
777
853
  options.alwaysNewSegments = this.alwaysNewSegments;
778
854
  options.playheadDiffThreshold = this.streamerOpts.defaultPlayheadDiffThreshold;
779
855
  options.maxTickInterval = this.streamerOpts.defaultMaxTickInterval;
@@ -879,9 +955,106 @@ var ChannelEngine = /** @class */ (function () {
879
955
  });
880
956
  });
881
957
  };
958
+ ChannelEngine.prototype._handleSubtitleManifest = function (req, res, next) {
959
+ return __awaiter(this, void 0, void 0, function () {
960
+ var session, body, err_6, err;
961
+ return __generator(this, function (_a) {
962
+ switch (_a.label) {
963
+ case 0:
964
+ debug("req.url=".concat(req.url));
965
+ session = sessions[req.params[2]];
966
+ if (!session) return [3 /*break*/, 5];
967
+ _a.label = 1;
968
+ case 1:
969
+ _a.trys.push([1, 3, , 4]);
970
+ return [4 /*yield*/, session.getCurrentSubtitleManifestAsync(req.params[0], req.params[1], req.headers["x-playback-session-id"])];
971
+ case 2:
972
+ body = _a.sent();
973
+ res.sendRaw(200, Buffer.from(body, 'utf8'), {
974
+ "Content-Type": "application/vnd.apple.mpegurl",
975
+ "Access-Control-Allow-Origin": "*",
976
+ "Cache-Control": "max-age=".concat(this.streamerOpts.cacheTTL || '4'),
977
+ "X-Instance-Id": this.instanceId + "<".concat(version, ">"),
978
+ });
979
+ next();
980
+ return [3 /*break*/, 4];
981
+ case 3:
982
+ err_6 = _a.sent();
983
+ next(this._gracefulErrorHandler(err_6));
984
+ return [3 /*break*/, 4];
985
+ case 4: return [3 /*break*/, 6];
986
+ case 5:
987
+ err = new errs.NotFoundError('Invalid session');
988
+ next(err);
989
+ _a.label = 6;
990
+ case 6: return [2 /*return*/];
991
+ }
992
+ });
993
+ });
994
+ };
995
+ ChannelEngine.prototype._handleDummySubtitleEndpoint = function (req, res, next) {
996
+ return __awaiter(this, void 0, void 0, function () {
997
+ var session, body, err;
998
+ return __generator(this, function (_a) {
999
+ debug("req.url=".concat(req.url));
1000
+ session = sessions[req.params.channelId];
1001
+ if (session) {
1002
+ try {
1003
+ body = "WEBVTT";
1004
+ res.sendRaw(200, Buffer.from(body, 'utf8'), {
1005
+ "Content-Type": "application/vnd.apple.mpegurl",
1006
+ "Access-Control-Allow-Origin": "*",
1007
+ "Cache-Control": "max-age=".concat(this.streamerOpts.cacheTTL || '4'),
1008
+ "X-Instance-Id": this.instanceId + "<".concat(version, ">"),
1009
+ });
1010
+ next();
1011
+ }
1012
+ catch (err) {
1013
+ next(this._gracefulErrorHandler(err));
1014
+ }
1015
+ }
1016
+ else {
1017
+ err = new errs.NotFoundError('Invalid session');
1018
+ next(err);
1019
+ }
1020
+ return [2 /*return*/];
1021
+ });
1022
+ });
1023
+ };
1024
+ ChannelEngine.prototype._handleSubtitleSliceEndpoint = function (req, res, next) {
1025
+ return __awaiter(this, void 0, void 0, function () {
1026
+ var slicer, body, err_7;
1027
+ return __generator(this, function (_a) {
1028
+ switch (_a.label) {
1029
+ case 0:
1030
+ debug("req.url=".concat(req.url));
1031
+ _a.label = 1;
1032
+ case 1:
1033
+ _a.trys.push([1, 3, , 4]);
1034
+ slicer = new SubtitleSlicer();
1035
+ return [4 /*yield*/, slicer.generateVtt(req.query)];
1036
+ case 2:
1037
+ body = _a.sent();
1038
+ res.sendRaw(200, Buffer.from(body, 'utf8'), {
1039
+ "Content-Type": "application/vnd.apple.mpegurl",
1040
+ "Access-Control-Allow-Origin": "*",
1041
+ "Cache-Control": "max-age=".concat(this.streamerOpts.cacheTTL || '4'),
1042
+ "X-Instance-Id": this.instanceId + "<".concat(version, ">"),
1043
+ });
1044
+ next();
1045
+ return [3 /*break*/, 4];
1046
+ case 3:
1047
+ err_7 = _a.sent();
1048
+ next(this._gracefulErrorHandler(err_7));
1049
+ return [3 /*break*/, 4];
1050
+ case 4: return [2 /*return*/];
1051
+ }
1052
+ });
1053
+ });
1054
+ };
882
1055
  ChannelEngine.prototype._handleMediaManifest = function (req, res, next) {
883
1056
  return __awaiter(this, void 0, void 0, function () {
884
- var session, sessionLive, body, err_6, err;
1057
+ var session, sessionLive, body, err_8, err;
885
1058
  return __generator(this, function (_a) {
886
1059
  switch (_a.label) {
887
1060
  case 0:
@@ -933,8 +1106,8 @@ var ChannelEngine = /** @class */ (function () {
933
1106
  next();
934
1107
  return [3 /*break*/, 11];
935
1108
  case 10:
936
- err_6 = _a.sent();
937
- next(this._gracefulErrorHandler(err_6));
1109
+ err_8 = _a.sent();
1110
+ next(this._gracefulErrorHandler(err_8));
938
1111
  return [3 /*break*/, 11];
939
1112
  case 11: return [3 /*break*/, 13];
940
1113
  case 12:
@@ -17,6 +17,9 @@ declare class Session {
17
17
  _events: any[];
18
18
  averageSegmentDuration: any;
19
19
  use_demuxed_audio: boolean;
20
+ use_vtt_subtitles: any;
21
+ dummySubtitleEndpoint: any;
22
+ subtitleSliceEndpoint: any;
20
23
  cloudWatchLogging: boolean;
21
24
  playheadDiffThreshold: any;
22
25
  maxTickInterval: any;
@@ -27,10 +30,12 @@ declare class Session {
27
30
  prevVodMediaSeq: {
28
31
  video: any;
29
32
  audio: any;
33
+ subtitle: any;
30
34
  };
31
35
  prevMediaSeqOffset: {
32
36
  video: any;
33
37
  audio: any;
38
+ subtitle: any;
34
39
  };
35
40
  waitingForNextVod: boolean;
36
41
  leaderIsSettingNextVod: boolean;
@@ -48,6 +53,7 @@ declare class Session {
48
53
  startWithId: any;
49
54
  _sessionProfile: any;
50
55
  _audioTracks: any;
56
+ _subtitleTracks: any;
51
57
  _closedCaptions: any;
52
58
  _noSessionDataTags: any;
53
59
  slateUri: any;
@@ -87,11 +93,13 @@ declare class Session {
87
93
  }>;
88
94
  getCurrentMediaManifestAsync(bw: any, playbackSessionId: any): Promise<any>;
89
95
  getCurrentAudioManifestAsync(audioGroupId: any, audioLanguage: any, playbackSessionId: any): Promise<any>;
96
+ getCurrentSubtitleManifestAsync(subtitleGroupId: any, subtitleLanguage: any): Promise<any>;
90
97
  incrementAsync(): Promise<any>;
91
98
  getMediaManifestAsync(bw: any, opts: any): Promise<any>;
92
99
  getAudioManifestAsync(audioGroupId: any, audioLanguage: any, opts: any): Promise<any>;
93
100
  getMasterManifestAsync(filter: any): Promise<string>;
94
101
  getAudioGroupsAndLangs(): Promise<{}>;
102
+ getSubtitleGroupsAndLangs(): Promise<{}>;
95
103
  consumeEvent(): any;
96
104
  produceEvent(event: any): void;
97
105
  hasPlayhead(): boolean;
@@ -106,6 +114,7 @@ declare class Session {
106
114
  _getCurrentDeltaTime(): Promise<any>;
107
115
  _getCurrentPlayheadPosition(): Promise<any>;
108
116
  _getAudioPlayheadPosition(seqIdx: any): Promise<any>;
117
+ _getSubtitlePlayheadPosition(seqIdx: any): Promise<any>;
109
118
  _getLastDuration(manifest: any): Promise<any>;
110
119
  _getPlayheadDiffCompensationValue(diffMs: any, thresholdMs: any): number;
111
120
  }