eyevinn-channel-engine 4.0.16 → 4.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ # Submitting Issues
2
+
3
+ We use GitHub issues to track public bugs. If you are submitting a bug, please provide
4
+ as much info as possible to make it easier to reproduce the issue and update unit tests.
5
+
6
+ # Contributing Code
7
+
8
+ This project uses [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary).
9
+
10
+ We follow the [GitHub Flow](https://guides.github.com/introduction/flow/index.html) so all contributions happen through pull requests. We actively welcome your pull requests:
11
+
12
+ 1. Fork the repo and create your branch from master.
13
+ 2. If you've added code that should be tested, add tests.
14
+ 3. If you've changed APIs, update the documentation.
15
+ 4. Ensure the test suite passes.
16
+ 5. Issue that pull request!
17
+
18
+ When submitting code changes your submissions are understood to be under the same MIT License that covers the project. Feel free to contact Eyevinn Technology if that's a concern.
19
+
20
+ # Code of Conduct
21
+
22
+ ## Our Pledge
23
+
24
+ In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
25
+
26
+ ## Our Standards
27
+
28
+ Examples of behavior that contributes to creating a positive environment include:
29
+
30
+ - Using welcoming and inclusive language
31
+ - Being respectful of differing viewpoints and experiences
32
+ - Gracefully accepting constructive criticism
33
+ - Focusing on what is best for the community
34
+ - Showing empathy towards other community members
35
+
36
+ Examples of unacceptable behavior by participants include:
37
+
38
+ - The use of sexualized language or imagery and unwelcome sexual attention or advances
39
+ - Trolling, insulting/derogatory comments, and personal or political attacks
40
+ - Public or private harassment
41
+ - Publishing others' private information, such as a physical or electronic address, without explicit permission
42
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
43
+
44
+ ## Our Responsibilities
45
+
46
+ Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
47
+
48
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
49
+
50
+ ## Scope
51
+
52
+ This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
53
+
54
+ ## Enforcement
55
+
56
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
57
+
58
+ Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
59
+
60
+ ## Attribution
61
+
62
+ This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4
package/README.md CHANGED
@@ -91,6 +91,68 @@ and this means that it is not possible to mix muxed TS with demuxed CMAF.
91
91
 
92
92
  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')`;
93
93
 
94
+ ## Contributing
95
+
96
+ Read [CONTRIBUTING](CONTRIBUTING.md) first and then follow the instructions below to get started with the devolpment environment.
97
+
98
+ ### Development
99
+
100
+ Ensure that you are running node version 16 as version 18 is not supported yet.
101
+
102
+ ```
103
+ node --version
104
+ v16.17.0
105
+ ```
106
+
107
+ Then install the dependencies:
108
+
109
+ ```
110
+ npm install
111
+ ```
112
+
113
+ Build the app:
114
+
115
+ ```
116
+ npm run build
117
+ ```
118
+
119
+ To start the reference implementation in `server.ts` run:
120
+
121
+ ```
122
+ npm start
123
+ ```
124
+
125
+ For full debug mode you set the environment variable `DEBUG`.
126
+
127
+ ```
128
+ DEBUG=* npm start
129
+ ```
130
+
131
+ or if you want be more specific for example:
132
+
133
+ ```
134
+ DEBUG=engine-* npm start
135
+ ```
136
+
137
+ In addition there other reference implemetations that can be used:
138
+ - `server-demux.ts` : example with demuxed audio
139
+ - `server-livemix.ts` : example with live mimxing
140
+ - `server-multicodec.ts` : example with multicodec
141
+
142
+ ### Source linking hls-vodtolive library
143
+
144
+ It is not uncommon that you need to test an local branch of the hls-vodtolive library and in order to do that you can use `npm link`.
145
+
146
+ 1. Go to the directory where you have the source code for the hls-vodtolive library and run `npm link`
147
+ 2. Then go to the channel engine source code directory and run `npm link @eyevinn/hls-vodtolive`
148
+
149
+ To restore and use the npm published version run the following in the channel engine source code directory:
150
+
151
+ ```
152
+ npm uninstall --no-save @eyevinn/hls-vodtolive
153
+ npm install
154
+ ```
155
+
94
156
  ## Support
95
157
 
96
158
  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:
@@ -50,6 +50,7 @@ export interface VodResponse {
50
50
  type?: string;
51
51
  currentMetadata?: VodResponseMetadata;
52
52
  timedMetadata?: VodTimedMetadata;
53
+ unixTs?: number;
53
54
  }
54
55
  export interface IAssetManager {
55
56
  getNextVod: (vodRequest: VodRequest) => Promise<VodResponse>;
@@ -1628,7 +1628,7 @@ var Session = /** @class */ (function () {
1628
1628
  if (!vodResponse.type) {
1629
1629
  debug("[".concat(this._sessionId, "]: got first VOD uri=").concat(vodResponse.uri, ":").concat(vodResponse.offset || 0));
1630
1630
  hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1631
- newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1631
+ newVod = new HLSVod(vodResponse.uri, [], vodResponse.unixTs, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1632
1632
  if (vodResponse.timedMetadata) {
1633
1633
  Object.keys(vodResponse.timedMetadata).map(function (k) {
1634
1634
  newVod.addMetadata(k, vodResponse.timedMetadata[k]);
@@ -1836,7 +1836,7 @@ var Session = /** @class */ (function () {
1836
1836
  if (!vodResponse.type) {
1837
1837
  debug("[".concat(this._sessionId, "]: got next VOD uri=").concat(vodResponse.uri, ":").concat(vodResponse.offset));
1838
1838
  hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1839
- newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1839
+ newVod = new HLSVod(vodResponse.uri, null, vodResponse.unixTs, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1840
1840
  if (vodResponse.timedMetadata) {
1841
1841
  Object.keys(vodResponse.timedMetadata).map(function (k) {
1842
1842
  newVod.addMetadata(k, vodResponse.timedMetadata[k]);
@@ -2147,8 +2147,8 @@ var Session = /** @class */ (function () {
2147
2147
  slateVod_1.load()
2148
2148
  .then(function () {
2149
2149
  var hlsOpts = { sequenceAlwaysContainNewSegments: _this.alwaysNewSegments, forcedDemuxMode: _this.use_demuxed_audio };
2150
- hlsVod_1 = new HLSVod(_this.slateUri, null, null, null, m3u8Header(_this._instanceId), hlsOpts);
2151
2150
  var timestamp = Date.now();
2151
+ hlsVod_1 = new HLSVod(_this.slateUri, null, timestamp, null, m3u8Header(_this._instanceId), hlsOpts);
2152
2152
  hlsVod_1.addMetadata('id', "slate-".concat(timestamp));
2153
2153
  hlsVod_1.addMetadata('start-date', new Date(timestamp).toISOString());
2154
2154
  hlsVod_1.addMetadata('planned-duration', ((reps || _this.slateRepetitions) * _this.slateDuration) / 1000);
@@ -2210,8 +2210,8 @@ var Session = /** @class */ (function () {
2210
2210
  slateVod_2.load()
2211
2211
  .then(function () {
2212
2212
  var hlsOpts = { sequenceAlwaysContainNewSegments: _this.alwaysNewSegments, forcedDemuxMode: _this.use_demuxed_audio };
2213
- hlsVod_2 = new HLSVod(nexVodUri, null, null, null, m3u8Header(_this._instanceId), hlsOpts);
2214
2213
  var timestamp = Date.now();
2214
+ hlsVod_2 = new HLSVod(nexVodUri, null, timestamp, null, m3u8Header(_this._instanceId), hlsOpts);
2215
2215
  hlsVod_2.addMetadata('id', "slate-".concat(timestamp));
2216
2216
  hlsVod_2.addMetadata('start-date', new Date(timestamp).toISOString());
2217
2217
  hlsVod_2.addMetadata('planned-duration', requestedDuration);
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "4.0.16",
3
+ "version": "4.0.17",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "@eyevinn/hls-repeat": "^0.2.0",
28
28
  "@eyevinn/hls-truncate": "^0.2.0",
29
- "@eyevinn/hls-vodtolive": "^2.3.5",
29
+ "@eyevinn/hls-vodtolive": "^2.3.7",
30
30
  "@eyevinn/m3u8": "^0.5.3",
31
31
  "abort-controller": "^3.0.0",
32
32
  "debug": "^3.2.7",
package/engine/server.ts CHANGED
@@ -89,6 +89,9 @@ export interface VodResponse {
89
89
  type?: string;
90
90
  currentMetadata?: VodResponseMetadata;
91
91
  timedMetadata?: VodTimedMetadata;
92
+ // Wall-clock start time of the VOD as unix ts. If set
93
+ // the program-date-time will be added to each segment
94
+ unixTs?: number;
92
95
  }
93
96
 
94
97
  export interface IAssetManager {
package/engine/session.js CHANGED
@@ -1152,7 +1152,7 @@ class Session {
1152
1152
  if (!vodResponse.type) {
1153
1153
  debug(`[${this._sessionId}]: got first VOD uri=${vodResponse.uri}:${vodResponse.offset || 0}`);
1154
1154
  const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1155
- newVod = new HLSVod(vodResponse.uri, [], null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1155
+ newVod = new HLSVod(vodResponse.uri, [], vodResponse.unixTs, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1156
1156
  if (vodResponse.timedMetadata) {
1157
1157
  Object.keys(vodResponse.timedMetadata).map(k => {
1158
1158
  newVod.addMetadata(k, vodResponse.timedMetadata[k]);
@@ -1301,7 +1301,7 @@ class Session {
1301
1301
  if (!vodResponse.type) {
1302
1302
  debug(`[${this._sessionId}]: got next VOD uri=${vodResponse.uri}:${vodResponse.offset}`);
1303
1303
  const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1304
- newVod = new HLSVod(vodResponse.uri, null, null, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1304
+ newVod = new HLSVod(vodResponse.uri, null, vodResponse.unixTs, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts);
1305
1305
  if (vodResponse.timedMetadata) {
1306
1306
  Object.keys(vodResponse.timedMetadata).map(k => {
1307
1307
  newVod.addMetadata(k, vodResponse.timedMetadata[k]);
@@ -1532,8 +1532,8 @@ class Session {
1532
1532
  slateVod.load()
1533
1533
  .then(() => {
1534
1534
  const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1535
- hlsVod = new HLSVod(this.slateUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
1536
1535
  const timestamp = Date.now();
1536
+ hlsVod = new HLSVod(this.slateUri, null, timestamp, null, m3u8Header(this._instanceId), hlsOpts);
1537
1537
  hlsVod.addMetadata('id', `slate-${timestamp}`);
1538
1538
  hlsVod.addMetadata('start-date', new Date(timestamp).toISOString());
1539
1539
  hlsVod.addMetadata('planned-duration', ((reps || this.slateRepetitions) * this.slateDuration) / 1000);
@@ -1592,8 +1592,8 @@ class Session {
1592
1592
  slateVod.load()
1593
1593
  .then(() => {
1594
1594
  const hlsOpts = { sequenceAlwaysContainNewSegments: this.alwaysNewSegments, forcedDemuxMode: this.use_demuxed_audio };
1595
- hlsVod = new HLSVod(nexVodUri, null, null, null, m3u8Header(this._instanceId), hlsOpts);
1596
1595
  const timestamp = Date.now();
1596
+ hlsVod = new HLSVod(nexVodUri, null, timestamp, null, m3u8Header(this._instanceId), hlsOpts);
1597
1597
  hlsVod.addMetadata('id', `slate-${timestamp}`);
1598
1598
  hlsVod.addMetadata('start-date', new Date(timestamp).toISOString());
1599
1599
  hlsVod.addMetadata('planned-duration', requestedDuration);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "4.0.16",
3
+ "version": "4.0.17",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "@eyevinn/hls-repeat": "^0.2.0",
28
28
  "@eyevinn/hls-truncate": "^0.2.0",
29
- "@eyevinn/hls-vodtolive": "^2.3.5",
29
+ "@eyevinn/hls-vodtolive": "^2.3.7",
30
30
  "@eyevinn/m3u8": "^0.5.3",
31
31
  "abort-controller": "^3.0.0",
32
32
  "debug": "^3.2.7",
@@ -468,6 +468,7 @@ describe("The initialize switching", () => {
468
468
  if (m) {
469
469
  mseqNo = Number(m[1]);
470
470
  }
471
+
471
472
  expect(mseqNo).toBe(10);
472
473
  const size = sessionCurrentSegs["1313000"].length;
473
474
  expect(sessionCurrentSegs["1313000"][0]).toEqual({
@@ -484,7 +485,7 @@ describe("The initialize switching", () => {
484
485
  const borderSegment = sessionCurrentSegs["1313000"][size - 1 - 3];
485
486
  expect(borderSegment.duration).toBe(10.880);
486
487
  expect(borderSegment.cue).toBe(null);
487
- expect(borderSegment.timelinePosition).toBe(null);
488
+ expect(borderSegment.timelinePosition).not.toBe(null);
488
489
  expect(borderSegment.uri).toBe("https://maitv-vod.lab.eyevinn.technology/MORBIUS_Trailer_2020.mp4/1000/1000-00000.ts");
489
490
  expect(borderSegment.daterange["id"]).not.toBe(null);
490
491
  expect(borderSegment.daterange["start-date"]).not.toBe(null);
@@ -768,13 +769,11 @@ describe("The initialize switching", () => {
768
769
  }
769
770
 
770
771
  expect(newVodSegments["1313000"][0]).toEqual(expectedLastVODSegItem);
771
- expect(newVodSegments["1313000"][1]).toEqual({
772
- duration: 10.846444,
773
- timelinePosition: null,
774
- cue: null,
775
- uri: 'https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00000.ts',
776
- byteRange: undefined,
777
- });
772
+ expect(newVodSegments["1313000"][1].duration).toEqual(10.846444);
773
+ expect(newVodSegments["1313000"][1].cue).toBeNull();
774
+ expect(newVodSegments["1313000"][1].uri).toEqual('https://maitv-vod.lab.eyevinn.technology/VINN.mp4/600/600-00000.ts');
775
+
776
+ newVodSegments["1313000"][2].timelinePosition = null; // disregard timeline position
778
777
  expect(newVodSegments["1313000"][2]).toEqual(expectedFirstV2LSegItem);
779
778
  // expect(newVodSegments["1313000"][5]).toEqual({
780
779
  // discontinuity: true,
@@ -784,9 +783,11 @@ describe("The initialize switching", () => {
784
783
  expect("start-date" in borderSegment.daterange).toBe(true);
785
784
  expect("id" in borderSegment.daterange).toBe(true);
786
785
  expect(borderSegment.duration).toEqual(11.3447);
787
- expect(borderSegment.timelinePosition).toEqual(null);
786
+ expect(borderSegment.timelinePosition).not.toEqual(null);
788
787
  expect(borderSegment.cue).toEqual(null);
789
788
  expect(borderSegment.uri).toEqual("https://maitv-vod.lab.eyevinn.technology/BECKY_Trailer_2020.mp4/1000/1000-00000.ts");
789
+
790
+ newVodSegments["1313000"][7].timelinePosition = null; // disregard timeline position
790
791
  expect(newVodSegments["1313000"][7]).toEqual(expectedSecondVODSegItem);
791
792
  });
792
793