Skip to content

Commit

Permalink
Merge branch 'main' into feat/i-frames-only
Browse files Browse the repository at this point in the history
  • Loading branch information
mister-ben authored Jul 2, 2024
2 parents fd9c5e6 + f8c9817 commit 13c2885
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 13 deletions.
25 changes: 21 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
<a name="7.1.0"></a>
# [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))

<a name="7.0.0"></a>
# [7.0.0](https://github.com/videojs/m3u8-parser/compare/v6.2.0...v7.0.0) (2023-07-10)

Expand All @@ -6,14 +22,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

<a name="6.2.0"></a>
# [6.2.0](https://github.com/videojs/m3u8-parser/compare/v6.1.0...v6.2.0) (2023-05-25)

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ Manifest {
'CLOSED-CAPTIONS': {},
SUBTITLES: {}
},
dateTimeString: string,
dateTimeObject: Date,
targetDuration: number,
totalDuration: number,
discontinuityStarts: [number],
Expand Down Expand Up @@ -177,6 +179,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

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 12 additions & 0 deletions src/parse-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -623,12 +624,23 @@ export default class ParseStream extends Stream {
});
return;
}

match = (/^#EXT-X-I-FRAMES-ONLY/).exec(newLine);
if (match) {
this.trigger('data', {
type: 'tag',
tagType: 'i-frames-only'
});

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;
}

Expand Down
32 changes: 29 additions & 3 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -685,21 +696,28 @@ 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);
}
if (!dateRangeTags[dateRange.id]) {
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'() {
Expand All @@ -709,6 +727,14 @@ export default class Parser extends Stream {
this.manifest.iFramesOnly = true;

this.requiredCompatibilityversion(this.manifest.version, 4);
},
'content-steering'() {
this.manifest.contentSteering = camelCaseKeys(entry.attributes);
this.warnOnMissingAttributes_(
'#EXT-X-CONTENT-STEERING',
entry.attributes,
['SERVER-URI']
);
}
})[entry.tagType] || noop).call(self);
},
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/integration/dateTime.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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: []
};
6 changes: 6 additions & 0 deletions test/fixtures/integration/llhls.js
Original file line number Diff line number Diff line change
@@ -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: [],
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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'
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/integration/llhlsDelta.js
Original file line number Diff line number Diff line change
@@ -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: [],
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 16 additions & 0 deletions test/parse-stream.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -690,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';
Expand Down
63 changes: 59 additions & 4 deletions test/parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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(
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -1201,6 +1227,35 @@ QUnit.module('m3u8s', function(hooks) {
);
});

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) {
Expand Down

0 comments on commit 13c2885

Please sign in to comment.