From 72da994469518443495b1d57b3057909b6912895 Mon Sep 17 00:00:00 2001
From: Harisha Rajam Swaminathan
<35213866+harisha-swaminathan@users.noreply.github.com>
Date: Mon, 24 Jul 2023 18:09:50 -0400
Subject: [PATCH 1/6] chore: update v7.0.0 documentation (#172)
---
CHANGELOG.md | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 390f762..280d6c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,14 +6,15 @@
* Add PDT to each segment ([#168](https://github.com/videojs/m3u8-parser/issues/168)) ([e7c683f](https://github.com/videojs/m3u8-parser/commit/e7c683f))
* output segment title from EXTINF ([#158](https://github.com/videojs/m3u8-parser/issues/158)) ([4adaa2c](https://github.com/videojs/m3u8-parser/commit/4adaa2c))
-### Bug Fixes
-
-* rename daterange to dateRanges ([#166](https://github.com/videojs/m3u8-parser/issues/166)) ([516ab67](https://github.com/videojs/m3u8-parser/commit/516ab67))
-
### Documentation
* correct `customType` option name ([#147](https://github.com/videojs/m3u8-parser/issues/147)) ([4d3e6ce](https://github.com/videojs/m3u8-parser/commit/4d3e6ce))
+### BREAKING CHANGES
+
+* rename `daterange` to `dateRanges`
+* remove `dateTimeObject` and `dateTimeString` from parsed segment and replaces it with `programDateTime` which represents the timestamp in milliseconds
+
# [6.2.0](https://github.com/videojs/m3u8-parser/compare/v6.1.0...v6.2.0) (2023-05-25)
From 6944bb1b2fa2611b5acc318322f820d35eb9b760 Mon Sep 17 00:00:00 2001
From: Harisha Rajam Swaminathan
<35213866+harisha-swaminathan@users.noreply.github.com>
Date: Mon, 7 Aug 2023 11:57:27 -0400
Subject: [PATCH 2/6] fix: add dateTimeObject and dateTimeString for backward
compatibility (#174)
---
README.md | 2 ++
src/parse-stream.js | 1 +
src/parser.js | 11 +++++++++++
test/fixtures/integration/dateTime.js | 6 ++++++
test/fixtures/integration/llhls.js | 6 ++++++
test/fixtures/integration/llhlsDelta.js | 4 ++++
test/parse-stream.test.js | 8 ++++++++
7 files changed, 38 insertions(+)
diff --git a/README.md b/README.md
index 2dc6550..98b661a 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,8 @@ Manifest {
'CLOSED-CAPTIONS': {},
SUBTITLES: {}
},
+ dateTimeString: string,
+ dateTimeObject: Date,
targetDuration: number,
totalDuration: number,
discontinuityStarts: [number],
diff --git a/src/parse-stream.js b/src/parse-stream.js
index 7d8cf72..2b6a691 100644
--- a/src/parse-stream.js
+++ b/src/parse-stream.js
@@ -356,6 +356,7 @@ export default class ParseStream extends Stream {
};
if (match[1]) {
event.dateTimeString = match[1];
+ event.dateTimeObject = new Date(match[1]);
}
this.trigger('data', event);
return;
diff --git a/src/parser.js b/src/parser.js
index 5f32e55..157603f 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -459,6 +459,17 @@ export default class Parser extends Stream {
this.manifest.discontinuityStarts.push(uris.length);
},
'program-date-time'() {
+ if (typeof this.manifest.dateTimeString === 'undefined') {
+ // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
+ // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
+ // to the manifest object
+ // TODO: Consider removing this in future major version
+ this.manifest.dateTimeString = entry.dateTimeString;
+ this.manifest.dateTimeObject = entry.dateTimeObject;
+ }
+ currentUri.dateTimeString = entry.dateTimeString;
+ currentUri.dateTimeObject = entry.dateTimeObject;
+
const { lastProgramDateTime } = this;
this.lastProgramDateTime = new Date(entry.dateTimeString).getTime();
diff --git a/test/fixtures/integration/dateTime.js b/test/fixtures/integration/dateTime.js
index 3e58f3d..a2e4dbf 100644
--- a/test/fixtures/integration/dateTime.js
+++ b/test/fixtures/integration/dateTime.js
@@ -5,12 +5,16 @@ module.exports = {
playlistType: 'VOD',
segments: [
{
+ dateTimeString: '2016-06-22T09:20:16.166-04:00',
+ dateTimeObject: new Date('2016-06-22T09:20:16.166-04:00'),
programDateTime: 1466601616166,
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
+ dateTimeString: '2016-06-22T09:20:26.166-04:00',
+ dateTimeObject: new Date('2016-06-22T09:20:26.166-04:00'),
programDateTime: 1466601626166,
duration: 10,
timeline: 0,
@@ -19,6 +23,8 @@ module.exports = {
],
targetDuration: 10,
endList: true,
+ dateTimeString: '2016-06-22T09:20:16.166-04:00',
+ dateTimeObject: new Date('2016-06-22T09:20:16.166-04:00'),
discontinuitySequence: 0,
discontinuityStarts: []
};
diff --git a/test/fixtures/integration/llhls.js b/test/fixtures/integration/llhls.js
index b394cb3..ae93cbc 100644
--- a/test/fixtures/integration/llhls.js
+++ b/test/fixtures/integration/llhls.js
@@ -1,5 +1,7 @@
module.exports = {
allowCache: true,
+ dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
+ dateTimeString: '2019-02-14T02:13:36.106Z',
dateRanges: [],
discontinuitySequence: 0,
discontinuityStarts: [],
@@ -37,6 +39,8 @@ module.exports = {
partTargetDuration: 0.33334,
segments: [
{
+ dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
+ dateTimeString: '2019-02-14T02:13:36.106Z',
programDateTime: 1550110416106,
duration: 4.00008,
map: {
@@ -143,6 +147,8 @@ module.exports = {
]
},
{
+ dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
+ dateTimeString: '2019-02-14T02:14:00.106Z',
duration: 4.00008,
map: {
uri: 'init.mp4'
diff --git a/test/fixtures/integration/llhlsDelta.js b/test/fixtures/integration/llhlsDelta.js
index 78a32a1..e965d03 100644
--- a/test/fixtures/integration/llhlsDelta.js
+++ b/test/fixtures/integration/llhlsDelta.js
@@ -1,5 +1,7 @@
module.exports = {
allowCache: true,
+ dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
+ dateTimeString: '2019-02-14T02:14:00.106Z',
dateRanges: [],
discontinuitySequence: 0,
discontinuityStarts: [],
@@ -110,6 +112,8 @@ module.exports = {
]
},
{
+ dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
+ dateTimeString: '2019-02-14T02:14:00.106Z',
duration: 4.00008,
programDateTime: 1550110440106,
timeline: 0,
diff --git a/test/parse-stream.test.js b/test/parse-stream.test.js
index 22f62de..9763985 100644
--- a/test/parse-stream.test.js
+++ b/test/parse-stream.test.js
@@ -635,6 +635,10 @@ QUnit.test(
element.dateTimeString, '2016-06-22T09:20:16.166-04:00',
'dateTimeString is parsed'
);
+ assert.deepEqual(
+ element.dateTimeObject, new Date('2016-06-22T09:20:16.166-04:00'),
+ 'dateTimeObject is parsed'
+ );
manifest = '#EXT-X-PROGRAM-DATE-TIME:2016-06-22T09:20:16.16389Z\n';
this.lineStream.push(manifest);
@@ -646,6 +650,10 @@ QUnit.test(
element.dateTimeString, '2016-06-22T09:20:16.16389Z',
'dateTimeString is parsed'
);
+ assert.deepEqual(
+ element.dateTimeObject, new Date('2016-06-22T09:20:16.16389Z'),
+ 'dateTimeObject is parsed'
+ );
}
);
QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function(assert) {
From 73d934ce5812798e709aa6a510f9812027a7c602 Mon Sep 17 00:00:00 2001
From: Harisha Rajam Swaminathan
<35213866+harisha-swaminathan@users.noreply.github.com>
Date: Mon, 7 Aug 2023 11:58:23 -0400
Subject: [PATCH 3/6] fix: merge dateRange tags with same IDs and no
conflicting attributes (#175)
---
src/parser.js | 13 ++++++++++---
test/parser.test.js | 34 ++++++++++++++++++++++++++++++----
2 files changed, 40 insertions(+), 7 deletions(-)
diff --git a/src/parser.js b/src/parser.js
index 157603f..79ffdd4 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -696,7 +696,7 @@ export default class Parser extends Stream {
}
if (dateRange.duration && dateRange.endDate) {
const startDate = dateRange.startDate;
- const newDateInSeconds = startDate.setSeconds(startDate.getSeconds() + dateRange.duration);
+ const newDateInSeconds = startDate.getTime() + (dateRange.duration * 1000);
this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
}
@@ -704,13 +704,20 @@ export default class Parser extends Stream {
dateRangeTags[dateRange.id] = dateRange;
} else {
for (const attribute in dateRangeTags[dateRange.id]) {
- if (dateRangeTags[dateRange.id][attribute] !== dateRange[attribute]) {
+ if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
this.trigger('warn', {
- message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes and same attribute values'
+ message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
});
break;
}
}
+ // if tags with the same ID do not have conflicting attributes, merge them
+ const dateRangeWithSameId = this.manifest.dateRanges.findIndex((dateRangeToFind) => dateRangeToFind.id === dateRange.id);
+
+ this.manifest.dateRanges[dateRangeWithSameId] = Object.assign(this.manifest.dateRanges[dateRangeWithSameId], dateRange);
+ dateRangeTags[dateRange.id] = Object.assign(dateRangeTags[dateRange.id], dateRange);
+ // after merging, delete the duplicate dateRange that was added last
+ this.manifest.dateRanges.pop();
}
},
'independent-segments'() {
diff --git a/test/parser.test.js b/test/parser.test.js
index 25f6c14..7565b32 100644
--- a/test/parser.test.js
+++ b/test/parser.test.js
@@ -1031,7 +1031,7 @@ QUnit.module('m3u8s', function(hooks) {
);
});
- QUnit.test('warns when playlist has multiple #EXT-X-DATERANGE tag same ID but different attribute names and values', function(assert) {
+ QUnit.test('warns when playlist has multiple #EXT-X-DATERANGE tag same ID but different attribute values', function(assert) {
this.parser.push([
'#EXT-X-VERSION:3',
'#EXT-X-MEDIA-SEQUENCE:0',
@@ -1041,12 +1041,12 @@ QUnit.module('m3u8s', function(hooks) {
'#EXT-X-ENDLIST',
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",END-ON-NEXT=YES,CLASS="CLASSATTRIBUTE"',
- '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:20.840000Z"'
+ '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",CLASS="CLASSATTRIBUTE1"'
].join('\n'));
this.parser.end();
const warnings = [
- 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes and same attribute values'
+ 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
];
assert.deepEqual(
@@ -1095,7 +1095,33 @@ QUnit.module('m3u8s', function(hooks) {
);
});
- QUnit.test(' playlist with multiple ext-x-daterange ', function(assert) {
+ QUnit.test('playlist with multiple ext-x-daterange with same ID but no conflicting attributes', function(assert) {
+ const expectedDateRange = {
+ id: '12345',
+ scte35In: '0xFC30200FFF2',
+ scte35Out: '0xFC30200FFF2',
+ startDate: new Date('2023-04-13T18:16:15.840000Z'),
+ class: 'CLASSATTRIBUTE'
+ };
+
+ this.parser.push([
+ '#EXT-X-VERSION:3',
+ '#EXT-X-MEDIA-SEQUENCE:0',
+ '#EXT-X-DISCONTINUITY-SEQUENCE:0',
+ '#EXTINF:10,',
+ 'media-00001.ts',
+ '#EXT-X-ENDLIST',
+ '#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
+ '#EXT-X-DATERANGE:ID="12345",SCTE35-IN=0xFC30200FFF2,START-DATE="2023-04-13T18:16:15.840000Z",CLASS="CLASSATTRIBUTE"',
+ '#EXT-X-DATERANGE:ID="12345",SCTE35-OUT=0xFC30200FFF2,START-DATE="2023-04-13T18:16:15.840000Z"'
+ ].join('\n'));
+ this.parser.end();
+ assert.equal(this.parser.manifest.dateRanges.length, 1, 'two dateranges with same ID are merged');
+ assert.deepEqual(this.parser.manifest.dateRanges[0], expectedDateRange);
+
+ });
+
+ QUnit.test('playlist with multiple ext-x-daterange ', function(assert) {
this.parser.push([
' #EXTM3U',
'#EXT-X-VERSION:6',
From 42472c597964c65c1fd22528f04e2cd5a21f2683 Mon Sep 17 00:00:00 2001
From: Adam Waldron
Date: Mon, 7 Aug 2023 09:19:35 -0700
Subject: [PATCH 4/6] feat: parse content steering tags and attributes (#176)
---
src/parse-stream.js | 10 ++++++++++
src/parser.js | 8 ++++++++
test/parse-stream.test.js | 8 ++++++++
test/parser.test.js | 29 +++++++++++++++++++++++++++++
4 files changed, 55 insertions(+)
diff --git a/src/parse-stream.js b/src/parse-stream.js
index 2b6a691..b09489a 100644
--- a/src/parse-stream.js
+++ b/src/parse-stream.js
@@ -624,6 +624,16 @@ export default class ParseStream extends Stream {
});
return;
}
+ match = (/^#EXT-X-CONTENT-STEERING:(.*)$/).exec(newLine);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'content-steering'
+ };
+ event.attributes = parseAttributes(match[1]);
+ this.trigger('data', event);
+ return;
+ }
// unknown tag type
this.trigger('data', {
diff --git a/src/parser.js b/src/parser.js
index 79ffdd4..77cb1a5 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -722,6 +722,14 @@ export default class Parser extends Stream {
},
'independent-segments'() {
this.manifest.independentSegments = true;
+ },
+ 'content-steering'() {
+ this.manifest.contentSteering = camelCaseKeys(entry.attributes);
+ this.warnOnMissingAttributes_(
+ '#EXT-X-CONTENT-STEERING',
+ entry.attributes,
+ ['SERVER-URI']
+ );
}
})[entry.tagType] || noop).call(self);
},
diff --git a/test/parse-stream.test.js b/test/parse-stream.test.js
index 9763985..ed51fcb 100644
--- a/test/parse-stream.test.js
+++ b/test/parse-stream.test.js
@@ -698,6 +698,14 @@ QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function(assert) {
'avc1.4d400d, mp4a.40.2',
'codecs are parsed'
);
+
+ manifest = '#EXT-X-STREAM-INF:PATHWAY-ID="CDN-A"\n';
+ this.lineStream.push(manifest);
+
+ assert.ok(element, 'an event was triggered');
+ assert.strictEqual(element.type, 'tag', 'the line type is tag');
+ assert.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
+ assert.strictEqual(element.attributes['PATHWAY-ID'], 'CDN-A', 'pathway-id is parsed');
});
QUnit.test('parses #EXT-X-STREAM-INF with arbitrary attributes', function(assert) {
const manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n';
diff --git a/test/parser.test.js b/test/parser.test.js
index 7565b32..230d093 100644
--- a/test/parser.test.js
+++ b/test/parser.test.js
@@ -1158,6 +1158,35 @@ QUnit.module('m3u8s', function(hooks) {
assert.equal(this.parser.manifest.independentSegments, true);
});
+ QUnit.test('parses #EXT-X-CONTENT-STEERING', function(assert) {
+ const expectedContentSteeringObject = {
+ serverUri: '/foo?bar=00012',
+ pathwayId: 'CDN-A'
+ };
+
+ this.parser.push('#EXT-X-CONTENT-STEERING:SERVER-URI="/foo?bar=00012",PATHWAY-ID="CDN-A"');
+ this.parser.end();
+ assert.deepEqual(this.parser.manifest.contentSteering, expectedContentSteeringObject);
+ });
+
+ QUnit.test('parses #EXT-X-CONTENT-STEERING without PATHWAY-ID', function(assert) {
+ const expectedContentSteeringObject = {
+ serverUri: '/bar?foo=00012'
+ };
+
+ this.parser.push('#EXT-X-CONTENT-STEERING:SERVER-URI="/bar?foo=00012"');
+ this.parser.end();
+ assert.deepEqual(this.parser.manifest.contentSteering, expectedContentSteeringObject);
+ });
+
+ QUnit.test('warns on #EXT-X-CONTENT-STEERING missing SERVER-URI', function(assert) {
+ const warning = ['#EXT-X-CONTENT-STEERING lacks required attribute(s): SERVER-URI'];
+
+ this.parser.push('#EXT-X-CONTENT-STEERING:PATHWAY-ID="CDN-A"');
+ this.parser.end();
+ assert.deepEqual(this.warnings, warning, 'warnings as expected');
+ });
+
QUnit.module('integration');
for (const key in testDataExpected) {
From b2d44f204264ff13ede4810ea80fa03156010e26 Mon Sep 17 00:00:00 2001
From: hswaminathan
Date: Mon, 7 Aug 2023 12:26:05 -0400
Subject: [PATCH 5/6] 7.1.0
---
CHANGELOG.md | 16 ++++++++++++++++
package-lock.json | 2 +-
package.json | 2 +-
3 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 280d6c6..bb0db02 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,19 @@
+
+# [7.1.0](https://github.com/videojs/m3u8-parser/compare/v7.0.0...v7.1.0) (2023-08-07)
+
+### Features
+
+* parse content steering tags and attributes ([#176](https://github.com/videojs/m3u8-parser/issues/176)) ([42472c5](https://github.com/videojs/m3u8-parser/commit/42472c5))
+
+### Bug Fixes
+
+* add dateTimeObject and dateTimeString for backward compatibility ([#174](https://github.com/videojs/m3u8-parser/issues/174)) ([6944bb1](https://github.com/videojs/m3u8-parser/commit/6944bb1))
+* merge dateRange tags with same IDs and no conflicting attributes ([#175](https://github.com/videojs/m3u8-parser/issues/175)) ([73d934c](https://github.com/videojs/m3u8-parser/commit/73d934c))
+
+### Chores
+
+* update v7.0.0 documentation ([#172](https://github.com/videojs/m3u8-parser/issues/172)) ([72da994](https://github.com/videojs/m3u8-parser/commit/72da994))
+
# [7.0.0](https://github.com/videojs/m3u8-parser/compare/v6.2.0...v7.0.0) (2023-07-10)
diff --git a/package-lock.json b/package-lock.json
index 942b10d..81c8a93 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "m3u8-parser",
- "version": "7.0.0",
+ "version": "7.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index aaa4f79..3ca5ad3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "m3u8-parser",
- "version": "7.0.0",
+ "version": "7.1.0",
"description": "m3u8 parser",
"main": "dist/m3u8-parser.cjs.js",
"module": "dist/m3u8-parser.es.js",
From f8c9817a95da39ee2b8ec10b889df325daaa846b Mon Sep 17 00:00:00 2001
From: Adam Waldron
Date: Tue, 15 Aug 2023 15:39:09 -0700
Subject: [PATCH 6/6] chore: add content-steering tag to readme (#177)
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 98b661a..c4b58a0 100644
--- a/README.md
+++ b/README.md
@@ -178,6 +178,7 @@ Manifest {
* [EXT-X-MEDIA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.1)
* [EXT-X-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.2)
+* [EXT-X-CONTENT-STEERING](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.6.6)
### Experimental Tags